簡介
(2025/4/21)
????????
????????本庫對目前僅針對TB6600驅動下的42步進電機的基礎功能進行了一定的封裝, 也是我初次嘗試以面向對象的思想去編寫嵌入式代碼, 和直流電機的驅動步驟相似在調用stepmotor_attach()函數和stepmotor_init()函數之后僅通過結構體數組stepm然后指定枚舉變量中的id即可完成對步進電機的基礎操作, 其中最核心的是控制函數step_move的實現, 該函數可以在開環狀態下指定步進電機的步數和頻率(速度)進行控制, 后續可能會更新一些經典的控制模型如梯形加減速.
先貼一下項目代碼
項目源碼
cubemx 配置
如果要使用此庫, 你只需要在cubemx中完成以下配置:
- 打開定時器的PWM通道并將 Prescaler設置為83(我的時鐘主頻為84分頻后為1MHz, 這個并不是一定得是1MHz, 后面會說)
- 打開NVIC
- 配置相關的GPIO, 如方向引腳, 這個很簡單我就不貼圖了
移植
如果你的硬件平臺和我一樣, 無腦粘貼就行, 如果不一樣, 則需要修改相關代碼
- 修改類型(如果不是stm32HAL庫)
主要是硬件層結構體的類型聲明, 和stepmotor_attach函數, 需要修改類型
typedef struct {// todo 硬件參數層TIM_HandleTypeDef* htim; //定時器句柄uint32_t Channel; // 輸出通道GPIO_TypeDef* enType; //使能引腳類別uint16_t enPin; //使能引腳pinGPIO_TypeDef* dirType; //方向引腳類別uint16_t dirPin; //方向引腳pin
}STEPMOTOR_HARDWARE;
void stepmotor_attach(STEPID id,TIM_HandleTypeDef* htim,uint32_t Channel, // 輸出通道GPIO_TypeDef* enType, //使能引腳類別uint16_t enPin, //使能引腳pinGPIO_TypeDef* dirType, //方向引腳類別uint16_t dirPin //方向引腳pin);
? ? 2. 底層接口函數封裝
這里都添加在了step_motor.c中的部分以static聲明的函數當中, 根據注釋功能修改函數的實現:
static void en_set(STEPID id){// todo 使能置高STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];hw->enType->BSRR = hw->enPin;
}
static void en_reset(STEPID id){// todo 使能置低STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];hw->enType->BSRR = (uint32_t)hw->enPin << 16U; // 置位
}static void dir_set(STEPID id){// todo 方向置高STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];hw->dirType->BSRR = hw->dirPin;
}
static void dir_reset(STEPID id){// todo 方向置低STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];hw->dirType->BSRR = (uint32_t)hw->dirPin << 16U; // 置位
}static void tim_start(STEPID id){// todo 定時器啟動STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];hw->htim->Instance->CR1 |= TIM_CR1_CEN;
}
static void tim_stop(STEPID id){// todo 定時器停止并清0STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];__HAL_TIM_SET_COUNTER(hw->htim, 0);__HAL_TIM_CLEAR_FLAG(hw->htim, TIM_FLAG_UPDATE);
}static void pwm_start(STEPID id){// todo pwm啟動STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];HAL_TIM_PWM_Start(hw->htim, hw->Channel);
}
static void pwm_stop(STEPID id){// todo pwm停止 其實將比較值賦為0就行STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];HAL_TIM_PWM_Stop(hw->htim, hw->Channel);
}static void pwm_startIT(STEPID id){// todo pwm中斷開啟STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];HAL_TIM_PWM_Start_IT(hw->htim, hw->Channel);
}
static void pwm_stopIT(STEPID id){// todo pwm中斷停止STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];HAL_TIM_PWM_Stop_IT(hw->htim, hw->Channel);
}
static void pwm_setcompare(STEPID id, int32_t freq){// todo 設置50%的占空比// todo 重新設置占空比(占空比永遠為比較值的一半, 即50%占空比)STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];if(freq > 0){hw->htim->Instance->ARR = Hclk/freq - 1;__HAL_TIM_SET_COMPARE(hw->htim, hw->Channel, hw->htim->Instance->ARR/2); // 占空比無所謂 一半即可}
}
? ? 3. 頻率宏定義
在step_motor.h中的:
#define Hclk 1000000 //時間總頻
這個是預分頻后的頻率
根據實際情況來, 如果你是72MHz主頻, 定時器的Prescaler設置為71, 則不用改, 因為分頻后依然為1MHz
如果你的定時器的Prescaler值設置為0, 那么這個就是你的時鐘主頻了
這個值參與ARR的賦值計算(在**static void pwm_setcompare(STEPID id, int32_t freq);**中), 所以你得好好根據自己ARR的量程來, 像c8t6是65535最大, 那么你最好將Prescaler值給大一些? ? ? ? ? ? ??
? ? 4. 中斷修改(如果不是stm32HAL庫)
具體的修改邏輯看看下面的步數&速度控制這個標題下的內容, 根據你的平臺中斷邏輯進行修改.
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{if(htim == stepm[SM1].hw->htim) // 回調函數檢測為某個步進電機對應的定時器{Stepper_UpdateHandler(SM1);}
}
初始化
成員變量概覽
先看看一個對象所包含的屬性, 這里我都敲了注釋, 主要是使用STEPMOTOR作為對象的類型.
typedef struct {// todo 硬件參數層TIM_HandleTypeDef* htim; //定時器句柄uint32_t Channel; // 輸出通道GPIO_TypeDef* enType; //使能引腳類別uint16_t enPin; //使能引腳pinGPIO_TypeDef* dirType; //方向引腳類別uint16_t dirPin; //方向引腳pin// todo 硬件API重寫層STEPMOTOR_INTERFACE enSet; // 方向引腳置位STEPMOTOR_INTERFACE enReset; // 方向引腳置位STEPMOTOR_INTERFACE dirSet; // 方向引腳置位STEPMOTOR_INTERFACE dirReset; // 方向引腳置位STEPMOTOR_INTERFACE htimStop; // 停止并復位定時器STEPMOTOR_INTERFACE htimStart; // 重新啟動定時器STEPMOTOR_INTERFACE pwmStop; // pwm停止STEPMOTOR_INTERFACE pwmStart; // pwm啟動STEPMOTOR_INTERFACE pwmStopIT; // pwm中斷計數停止STEPMOTOR_INTERFACE pwmStartIT; // pwm中斷計數啟動STEPMOTOR_SPEED_INTERFACE pwmSetCompare; // pwm設置為50%的占空比}STEPMOTOR_HARDWARE;typedef struct{STEPMOTOR_HARDWARE* hw; // 硬件接口封裝層// todo 參數層uint32_t cur_freq; // 當前頻率Hzuint16_t cur_step; // 當前步數uint8_t dir; // 當前方向// todo 目標值uint16_t tar_step; // 目標步數// todo 限制層uint8_t is_limit_step; // 你是否要限制步數uint8_t is_finish; // 是否完成路程int16_t accumulate_step; // 累計步數int32_t max_step; // 最大步數 - 配合累計步數以限幅int32_t min_step; // 最小步數 - 配合累計步數以限幅uint32_t min_freq; // 最小運行頻率uint32_t max_freq; // 最大運行頻率// todo 函數接口層STEPMOTOR_INTERFACE limitStep;STEPMOTOR_INTERFACE noLimitStep;STEPMOTOR_STEP_INTERFACE stepMove; // 指定步數和速度進行移動STEPMOTOR_INTERFACE stop; // 立即停止STEPMOTOR_RANGE_INTERFACE setRange; // 設置相關范圍的接口}STEPMOTOR;
我們需要將step_motor.c和step_motor.h添加到你的工程目錄下面, 然后調用初始化函數
void stepmotor_attach(STEPID id, // 電機idTIM_HandleTypeDef* htim, // 電機對應定時器uint32_t Channel, // 輸出通道GPIO_TypeDef* enType, //使能引腳類別uint16_t enPin, //使能引腳pinGPIO_TypeDef* dirType, //方向引腳類別uint16_t dirPin //方向引腳pin);
stepmotor_init();
前者stepmotor_attach是引腳定向, 為了后面我們可以用結構體數組引出各種屬性和函數進行調用, 以下是函數的實現流程
void stepmotor_attach(STEPID id,TIM_HandleTypeDef* htim,uint32_t Channel, // 輸出通道GPIO_TypeDef* enType, //使能引腳類別uint16_t enPin, //使能引腳pinGPIO_TypeDef* dirType, //方向引腳類別uint16_t dirPin //方向引腳pin){STEPMOTOR_HARDWARE* hw = &stepmotorhws[id];// todo 硬件引腳重指定hw->htim = htim; // 硬件接口初始化hw->Channel = Channel;hw->enType = enType;hw->enPin = enPin;hw->dirType = dirType;hw->dirPin = dirPin;// todo 硬件函數接口hw->enSet = en_set;hw->enReset = en_reset;hw->dirSet = dir_set;hw->dirReset = dir_reset;hw->htimStart = tim_start;hw->htimStop = tim_stop;hw->pwmStop = pwm_stop;hw->pwmStart = pwm_start;hw->pwmStartIT = pwm_startIT;hw->pwmStopIT = pwm_stopIT;hw->pwmSetCompare = pwm_setcompare;// todo 控制接口函數stepm[id].hw = hw;stepm[id].limitStep = limit_step;stepm[id].noLimitStep = no_limit_step;stepm[id].stepMove = step_move; // 移動函數接口賦值stepm[id].stop = step_stop; // 立即停止接口指定stepm[id].setRange = set_range; // 范圍設置指向
}
stepmotor_init() 是初始化函數, 在這里cubemx已經初始化完成, 我只添加了相關外設如定時器中斷等啟動函數也可以添加自己的初始化代碼.
void stepmotor_init(void){for(uint8_t i = 0; i < STEP_SUM; i++){STEPMOTOR* m = &stepm[i]; // 獲取對象指針// todo 其他初始化// todo 啟動m->hw->pwmStartIT(i); // PWM中斷m->hw->enSet(i); // 使能引腳使能}
}
使用庫進行控制
步數&速度控制
目前沒有添加太多的算法, 僅僅是開環的指定速度和位移移動的函數, 不過這應該也是后續底層最為核心的函數:
// 指定id id 指定走多少步 指定速度/頻率
stepm[STEPID].stepMove(STEPID, int32_t, uint32_t);
僅僅是使用的話, 只需要傳入對應電機對象id和相關參數即可, 其中第二個參數是用于指定目標是多少步, 支持正負號, 可以輸入負值, 它意味著電機朝反方向轉.
同時, 因為我們在初始化配置的時候開啟了PWM中斷所以在這里我們也需要在中斷中添加部分代碼:
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{if(htim == stepm[SM1].hw->htim) // 回調函數檢測為某個步進電機對應的定時器{Stepper_UpdateHandler(SM1);}
}
在中斷回調函數HAL_TIM_PWM_PulseFinishedCallback中, 我們只需要重復執行Stepper_UpdateHandler函數就行, 關于這個函數的實現其實很簡單, 下面我會說明.
而stepMove函數的具體實現如下:
// todo 以指定速度(頻率), 移動指定步數
static void step_move(STEPID id, int32_t tar_steps, uint32_t freq){STEPMOTOR* m = &stepm[id];if(tar_steps == 0 || freq <=0){m->is_finish = 1;return;}m->is_finish = 0; // 刷新完成標志位// todo 停止并復位定時器m->hw->pwmStopIT(id);m->hw->htimStop(id);//todo 方向引腳置位m->dir = (tar_steps >= 0) ? 1 : 0;if(m->dir) m->hw->dirSet(id);else m->hw->dirReset(id);// todo 設置目標步數 并對其進行步數限幅if(m->is_limit_step){int32_t pre_accumulate_step = m->accumulate_step; // 存儲上一次的步數m->accumulate_step = (int16_t) LIMIT(m->accumulate_step + tar_steps, m->max_step, m->min_step);m->tar_step = abs( m->accumulate_step - pre_accumulate_step);}else{m->tar_step = tar_steps;}m->cur_step = 0; // 每刷新一次當前步數置為0// todo 限制頻率范圍 注意符號和取值范圍freq = LIMIT((int32_t)freq, (int32_t)m->max_freq, (int32_t)m->min_freq);m->cur_freq = freq; // 獲取當前頻率// todo 重新設置占空比(占空比永遠為比較值的一半, 即50%占空比)m->hw->pwmSetCompare(id, freq);// todo 重新啟動定時器m->hw->htimStart(id);// todo 啟用更新中斷m->hw->pwmStartIT(id);
}
以及中斷函數
// todo (重要)定時器更新中斷處理(需在stm32f4xx_it.c(或者中斷回調函數)中調用)
void Stepper_UpdateHandler(STEPID id) {STEPMOTOR* m = &stepm[id];if(m->cur_step < m->tar_step) {m->cur_step++;} else { // todo 如果檢測到當前步數到了目標值就直接PWM_Stop不發波了m->is_finish = 1;m->hw->pwmStopIT(id);}
}
每一步的操作我都敲了注釋, 總體上的控制思路如下(這也是比較常用的控制思路):
通過PWM中斷去對每一步進行計數存入成員變量cur_step中, 方波每個周期進一次中斷, 同時步進電機收到一個脈沖, 電機走一步. 當電機的當前步數大于等于目標步數pwm停止.
電機停止
stepm[STEPID].stop(STEPID);
調用即可, 函數實現僅僅是讓目標步數等于當前步數
// todo 立即停止電機
static void step_stop(STEPID id) {STEPMOTOR * m = &stepm[id];//__HAL_TIM_DISABLE_IT(m->hw->htim, TIM_IT_UPDATE);m->tar_step = m->cur_step;
}
設置范圍
// id 模式 最大值 最小值
stepm[STEPID].setRange(STEPID, char , int32_t , int32_t );
這里提供了兩種模式:
- 'f': 設置速度/頻率范圍 -- 較為常見
- 's': 設置最大累計步數范圍 -- 我控制二維步進云臺時用的, 防止云臺跑到范圍外, 對累計的步數進行限幅
// todo 立即停止電機
static void set_range(STEPID id, char mode, int32_t ma, int32_t mi){STEPMOTOR* m = &stepm[id];switch (mode) {case 'f': // todo 設置頻率范圍m->max_freq = ma;m->min_freq = mi;break;case 's':m->max_step = ma;m->min_step = mi;break;}
}
是否啟用位移限幅
主要是對標志位is_limit_step進行操作, 如果你不需要累計位移控制, 比如說小車的應用場景, 可以置位不再限幅
//todo 調用示例
stepm[STEPID].limitStep(STEPID);
stepm[STEPID].noLimitStep(STEPID);//todo 實現
static void limit_step(STEPID id){stepm[id].is_limit_step = 1;}
static void no_limit_step(STEPID id){stepm[id].is_finish = 0;}