一、基礎知識
1. 步進電機控制方式
脈沖+方向控制(最常見)
控制信號:
? ? ? ? DIR方向:高低電平決定正轉或反轉;
????????STEP脈沖:每個脈沖電機前進一步(可通過端口拉高拉低來模擬脈沖,或使用pwm來生成脈沖);
方法 定義說明 GPIO 模擬脈沖 用軟件代碼控制 GPIO 引腳“高/低”來模擬脈沖信號,通常通過 delay_us()
等手動延時PWM 脈沖(定時器) 使用硬件定時器自動生成一定頻率和占空比的脈沖波形,直接輸出到 GPIO 引腳
2.?GPIO模擬脈沖和PWM生成脈沖兩者的區別
項目 GPIO 模擬脈沖 PWM 定時器輸出 控制方式 純軟件控制:代碼中手動翻轉引腳 硬件自動輸出,CPU不再關心 精度/頻率穩定性 受 CPU、延時函數精度影響(不穩定) 非常精準(定時器硬件級別) CPU 占用率 高:CPU 要一直跑在 delay 上 低:設置一次,自動輸出 適合任務 簡單、低速、臨時用 實時、高速、精確的脈沖控制 速度上限 通常 <5kHz,超了容易亂 可達幾十 kHz 或更高 支持加減速控制 復雜,需要自己調節 delay 變量 更容易調節頻率,甚至可做變頻波(配合 ARR) 控制靈活性 靈活(逐步手動控制每一脈沖) 受限于定時器結構,但效率高
3. 驅動器細分檔
????????驅動器上的“細分擋位”是用于設置步進電機細分數(microstepping)的開關,它可以控制電機每收到一個STEP脈沖時的實際轉動角度,以實現更平滑、更高精度的運動控制。
????????普通步進電機的步距角通常是:1.8°(即每轉動一圈需要200個整步)。
例如下面的驅動器,可以調節M1、M2、M3來控制細分檔位。
細分數 每微步角度(1.8°電機) 每圈脈沖數 1(全步) 1.8° 200 2(半步) 0.9° 400 4 0.45° 800 8 0.225° 1600 16 0.1125° 3200 32 0.05625° 6400
二、實驗
1. GPIO模擬脈沖方式
實驗現象:正轉2圈,延時,反轉1圈。
main.c
#include "stm32f10x.h"
#include "usart1.h"
#include "delay.h"
#include "string.h"
#include "systick.h"
#include "led.h"
#include "motor.h"
#include "key.h"int main(void)
{SystemInit();delay_init();StepMotor_GPIO_Init();StepMotor_Enable(); // 開啟電機LED_Init();Key_Init();StepMotor_SetDirection(1); // 設置方向while (1) {
// if (GPIO_ReadInputDataBit(KEY_GPIO, KEY1_PIN) == Bit_RESET) // 按鍵1按下(低電平有效)
// {
// StepMotor_SetDirection(1); // 順時針
// StepMotor_StepPulse(200, 500); // 200 步,800us 間隔(約625Hz)
// LED_ON();
// }
// else if (GPIO_ReadInputDataBit(KEY_GPIO, KEY2_PIN) == Bit_RESET) // 按鍵2按下
// {
// StepMotor_SetDirection(0); // 順時針
// StepMotor_StepPulse(200, 500); // 200 步,800us 間隔(約625Hz)
// LED2_ON();
// }else{
// LED_OFF();
// LED2_OFF();
// }StepMotor_TurnOneCircle(1, 2, 500); // 正轉2圈,500us間隔(約1666Hz)delay_ms(8000);StepMotor_TurnOneCircle(0, 1, 500); // 反轉一圈delay_ms(8000);}
}
motor.c
#include "motor.h"void StepMotor_GPIO_Init(void) {RCC_APB2PeriphClockCmd(STEPMOTOR_RCC, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = STEP_PIN | DIR_PIN | EN_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽輸出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(STEPMOTOR_GPIO, &GPIO_InitStructure);GPIO_ResetBits(STEPMOTOR_GPIO, STEP_PIN);GPIO_ResetBits(STEPMOTOR_GPIO, DIR_PIN);GPIO_SetBits(STEPMOTOR_GPIO, EN_PIN); // 默認使能(高電平)
}void StepMotor_Enable(void) {GPIO_SetBits(STEPMOTOR_GPIO, EN_PIN); // 高電平 = 使能
}void StepMotor_Disable(void) {GPIO_ResetBits(STEPMOTOR_GPIO, EN_PIN); // 低電平 = 失能(斷電)
}void StepMotor_SetDirection(uint8_t dir) {if (dir)GPIO_SetBits(STEPMOTOR_GPIO, DIR_PIN);elseGPIO_ResetBits(STEPMOTOR_GPIO, DIR_PIN);
}void StepMotor_StepPulse(uint32_t steps, uint32_t delay_us_val) {for (uint32_t i = 0; i < steps; i++) {GPIO_SetBits(STEPMOTOR_GPIO, STEP_PIN); // STEP 上升沿delay_us(delay_us_val);GPIO_ResetBits(STEPMOTOR_GPIO, STEP_PIN); // 下降沿delay_us(delay_us_val);}
}void StepMotor_TurnOneCircle(uint8_t dir, uint16_t circles, uint32_t speed_us)
{StepMotor_SetDirection(dir); // 設置方向(1=正轉,0=反轉)for (uint32_t i = 0; i < circles*1600; i++) { // 1600步 = 一圈(8細分)GPIO_SetBits(GPIOA, STEP_PIN); // STEP高delay_us(speed_us);GPIO_ResetBits(GPIOA, STEP_PIN); // STEP低delay_us(speed_us);}
}// 簡單延時函數(可使用SysTick或Timer)
void delay_us_function(uint32_t us)
{for (uint32_t i = 0; i < us * 8; i++)__NOP();
}
motor.h
#ifndef __MOTOR_H
#define __MOTOR_H#include "stm32f10x.h"// 步進電機引腳定義(你已說明:PA1 -> STEP,PA2 -> DIR,PA3 -> EN)
// GPIO 配置
#define STEPMOTOR_GPIO GPIOA
#define STEPMOTOR_RCC RCC_APB2Periph_GPIOA#define STEP_PIN GPIO_Pin_1
#define DIR_PIN GPIO_Pin_2
#define EN_PIN GPIO_Pin_3void StepMotor_GPIO_Init(void);
void StepMotor_SetDirection(uint8_t dir);
void StepMotor_Enable(void);
void StepMotor_Disable(void);
void StepMotor_StepPulse(uint32_t steps, uint32_t delay_us_val);
void StepMotor_TurnCircle(uint8_t dir, uint16_t circles, uint32_t speed_us);
void delay_us(uint32_t us);#endif // __STEPMOTOR_H
key.c
#include "key.h"static uint8_t key1_last = 1;
static uint8_t key2_last = 1;void Key_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);GPIO_InitTypeDef gpio;// KEY1/KEY2 輸入,帶上拉gpio.GPIO_Pin = KEY1_PIN | KEY2_PIN;gpio.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(KEY_GPIO, &gpio);}uint8_t Key_Scan(void)
{uint8_t key1_now = GPIO_ReadInputDataBit(KEY_GPIO, KEY1_PIN); //讀取當前電平,0為按下。uint8_t key2_now = GPIO_ReadInputDataBit(KEY_GPIO, KEY2_PIN);uint8_t result = KEY_NONE; //0,沒有按鍵// 按鍵1:檢測下降沿if (key1_last == 1 && key1_now == 0) {delay_ms(20); // 消抖if (GPIO_ReadInputDataBit(KEY_GPIO, KEY1_PIN) == Bit_RESET)result = KEY1_PRESSED; //result =1,表示按鍵1按下。}// 按鍵2:檢測下降沿if (key2_last == 1 && key2_now == 0) {delay_ms(20); // 消抖if (GPIO_ReadInputDataBit(KEY_GPIO, KEY2_PIN) == Bit_RESET)result = KEY2_PRESSED; //result =2,表示按鍵1按下。}key1_last = key1_now;key2_last = key2_now;return result;
}
key.h
#ifndef __KEY_H
#define __KEY_H#include "stm32f10x.h"#define KEY1_PIN GPIO_Pin_1 // 正轉按鍵 //PC1
#define KEY2_PIN GPIO_Pin_3 // 反轉按鍵 //PC3
#define KEY_GPIO GPIOC// 返回值定義(按下事件)
#define KEY_NONE 0
#define KEY1_PRESSED 1
#define KEY2_PRESSED 2
void Key_Init(void);uint8_t Key_Scan(void);
#endif
2. pwm輸出脈沖方式
實驗現象:按下按鍵1電機正轉2圈,按下按鍵2電機正轉1圈。
tim2_pwm.c
#include "tim2_pwm.h"volatile uint32_t step_count;
volatile uint32_t target_steps;void StepMotor_PWM_Init(uint32_t freq_hz)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef gpio;gpio.GPIO_Pin = GPIO_Pin_1;gpio.GPIO_Mode = GPIO_Mode_AF_PP; // 復用推挽輸出gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio);TIM_TimeBaseInitTypeDef tim;uint16_t prescaler = 72 - 1; // 假設主頻72MHz → 1MHz定時器uint16_t period = 1000000 / freq_hz - 1; // 計算周期tim.TIM_Prescaler = prescaler;tim.TIM_CounterMode = TIM_CounterMode_Up;tim.TIM_Period = period;tim.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInit(TIM2, &tim);TIM_OCInitTypeDef oc;oc.TIM_OCMode = TIM_OCMode_PWM1;oc.TIM_OutputState = TIM_OutputState_Enable;oc.TIM_Pulse = period / 2; // 50% 占空比oc.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OC2Init(TIM2, &oc); // PA1 = CH2TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 開啟更新中斷NVIC_InitTypeDef nvic;nvic.NVIC_IRQChannel = TIM2_IRQn;nvic.NVIC_IRQChannelPreemptionPriority = 1;nvic.NVIC_IRQChannelSubPriority = 1;nvic.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&nvic);
}void StepMotor_RunCircles(uint32_t circles)
{step_count = 0;target_steps = circles*1600;TIM_Cmd(TIM2, ENABLE); // 啟動PWM
}void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){TIM_ClearITPendingBit(TIM2, TIM_IT_Update);step_count++;if (step_count >= target_steps){TIM_Cmd(TIM2, DISABLE); // 關閉PWMstep_count = 0;}}
}
main.c
#include "stm32f10x.h"
#include "usart1.h"
#include "delay.h"
#include "string.h"
#include "systick.h"
#include "led.h"
#include "motor.h"
#include "key.h"
#include "tim2_pwm.h"int main(void)
{SystemInit();delay_init();StepMotor_GPIO_Init();LED_Init();Key_Init();StepMotor_PWM_Init(1000); // 設置1kHz頻率StepMotor_Enable();while (1) {uint8_t key = Key_Scan();if (key == KEY1_PRESSED) {StepMotor_SetDirection(1);StepMotor_RunCircles(2);}else if (key == KEY2_PRESSED) {StepMotor_SetDirection(0);StepMotor_RunCircles(1);}}
}
注意:pwm輸出這里有些函數是和GPIO公用的,比如引腳的初始化,這里我沒有再去劃分。