記錄一次使用面向對象的C語言封裝步進電機驅動

簡介

(2025/4/21)

????????

????????本庫對目前僅針對TB6600驅動下的42步進電機的基礎功能進行了一定的封裝, 也是我初次嘗試以面向對象的思想去編寫嵌入式代碼, 和直流電機的驅動步驟相似在調用stepmotor_attach()函數和stepmotor_init()函數之后僅通過結構體數組stepm然后指定枚舉變量中的id即可完成對步進電機的基礎操作, 其中最核心的是控制函數step_move的實現, 該函數可以在開環狀態下指定步進電機的步數頻率(速度)進行控制, 后續可能會更新一些經典的控制模型如梯形加減速.
先貼一下項目代碼
項目源碼

cubemx 配置

如果要使用此庫, 你只需要在cubemx中完成以下配置:

  1. 打開定時器的PWM通道并將 Prescaler設置為83(我的時鐘主頻為84分頻后為1MHz, 這個并不是一定得是1MHz, 后面會說)

  2. 打開NVIC

  3. 配置相關的GPIO, 如方向引腳, 這個很簡單我就不貼圖了

移植

如果你的硬件平臺和我一樣, 無腦粘貼就行, 如果不一樣, 則需要修改相關代碼

  1. 修改類型(如果不是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.cstep_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;}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77901.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77901.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77901.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[創業之路-376]:企業法務 - 創業,不同的企業形態,個人承擔的風險、收益、稅費、成本不同

在企業法務領域&#xff0c;創業時選擇不同的企業形態&#xff0c;個人在風險承擔、收益分配、稅費負擔及運營成本方面存在顯著差異。以下從個人獨資企業、合伙企業、有限責任公司、股份有限公司四種常見形態展開分析&#xff1a; 一、個人承擔的風險 個人獨資企業 風險類型&…

GNOME桌面隱藏回收站和分區

dconf-editor 搜索 trash&#xff0c;關閉 show-trash 搜索 volumes&#xff0c;關閉 show-volumns

準確--Tomcat更換證書

具體意思是&#xff1a; Starting Coyote HTTP/1.1 on http-8080: HTTP 連接器&#xff08;端口 8080&#xff09;啟動成功了。嚴重: Failed to load keystore type PKCS12 with path conf/jlksearch.fzsmk.cn.pfx due to failed to decrypt safe contents entry: javax.crypt…

禁止ubuntu自動更新

由于ubuntu server和desktop版本都默認 啟動了&#xff0c;自動更新內核的操作。這對于生 產環境來說是不友好的。容易導致億賽通 無法啟動 默認開啟了內核自動更新所以我們關閉自 動內核更新。 1.禁止更新執行 sudo apt-mark hold linux-image-generic linux-headers-generic…

vue3 + element-plus中el-drawer抽屜滾動條回到頂部

el-drawer抽屜滾動條回到頂部 <script setup lang"ts" name"PerformanceLogQuery"> import { ref, nextTick } from "vue"; ...... // 詳情 import { performanceLogQueryByIdService } from "/api/performanceLog"; const onD…

【重走C++學習之路】16、AVL樹

目錄 一、概念 二、AVL樹的模擬實現 2.1 AVL樹節點定義 2.2 AVL樹的基本結構 2.3 AVL樹的插入 1. 插入步驟 2. 調節平衡因子 3. 旋轉處理 4. 開始插入 2.4 AVL樹的查找 2.5 AVL樹的刪除 1. 刪除步驟 2. 調節平衡因子 3. 旋轉處理 4. 開始刪除 結語 一、概念 …

char32_t、char16_t、wchar_t 用于 c++ 語言里存儲 unicode 編碼的字符,給出它們的具體定義

&#xff08;1&#xff09; #include <iostream> #include <string>int main() { std::u16string s u"C11 引入 char16_t"; // 定義 UTF-16 字符串for (char16_t c : s) // 遍歷輸出每個 char16_t 的值std::cout << std::hex << (…

redis數據類型-基數統計HyperLogLog

redis數據類型-基數統計HyperLogLog 文檔 redis單機安裝redis常用的五種數據類型redis數據類型-位圖bitmap 說明 官網操作命令指南頁面&#xff1a;https://redis.io/docs/latest/commands/?nameget&groupstringHyperLogLog介紹頁面&#xff1a;https://redis.io/docs…

邏輯思維:從混沌到秩序的理性推演在軟件開發中的應用

引言 在軟件開發的過程中&#xff0c;邏輯思維就像是開發者的“GPS導航”&#xff0c;幫助我們從混沌的需求中找到清晰的解決方案。想象一下&#xff0c;如果沒有邏輯思維&#xff0c;我們可能會在需求的海洋中迷失方向&#xff0c;最終寫出一堆“看似聰明但毫無意義”的代碼。…

Spring AI Alibaba Graph基于 ReAct Agent 的天氣預報查詢系統

1、在本示例中&#xff0c;我們僅為 Agent 綁定了一個天氣查詢服務&#xff0c;接收到用戶的天氣查詢服務后&#xff0c;流程會在 AgentNode 和 ToolNode 之間循環執行&#xff0c;直到完成用戶指令。示例中判斷指令完成的條件&#xff08;即 ReAct 結束條件&#xff09;也很簡…

HCIP(綜合實驗2)

1.實驗拓補圖 2.實驗要求 1.根據提供材料劃分VLAN以及IP地址&#xff0c;PC1/PC2屬于生產一部員工劃分VLAN10,PC3屬于生產二部劃分VLAN20 2.HJ-1HJ-2交換機需要配置鏈路聚合以保證業務數據訪問的高帶寬需求 3.VLAN的放通遵循最小VLAN透傳原則 4.配置MSTP生成樹解決二層環路問題…

使用 rebase 輕松管理主干分支

前言 最近遇到一個技術團隊的 dev 環境分支錯亂&#xff0c;因為是多人合作大家各自提交信息&#xff0c;導致出現很多交叉合并記錄&#xff0c;讓對應 log 看起來非常混亂&#xff0c;難以閱讀。 舉例說明 假設我們有一個項目&#xff0c;最初develop分支有 3 個提交記錄&a…

使用openssl為localhost創建自簽名

文章目錄 自簽名生成命令安裝安裝證書瀏覽器證書管理器 自簽名 生成命令 使用openssl生成私鑰和證書。 openssl req -x509 -newkey rsa:4096 -nodes -days 365 -subj "/CNlocalhost" -addext "subjectAltNameDNS:localhost" -keyout cert.key -out cer…

AI編程助手Cline之快速介紹

Cline 是一款深度集成在 Visual Studio Code&#xff08;VSCode&#xff09; 中的開源 AI 編程助手插件&#xff0c;旨在通過結合大語言模型&#xff08;如 Claude 3.5 Sonnet、DeepSeek V3、Google Gemini 等&#xff09;和工具鏈&#xff0c;為開發者提供自動化任務執行、智能…

1.微服務拆分與通信模式

目錄 一、微服務拆分原則與策略 業務驅動拆分方法論 ? DDD&#xff08;領域驅動設計&#xff09;中的限界上下文劃分 ? 業務功能正交性評估&#xff08;高內聚、低耦合&#xff09; 技術架構拆分策略 ? 數據層拆分&#xff08;垂直分庫 vs 水平分表&#xff09; ? 服務粒…

Element Plus表格組件深度解析:構建高性能企業級數據視圖

一、架構設計與核心能力 Element Plus的表格組件&#xff08;el-table&#xff09;基于Vue 3的響應式系統構建&#xff0c;通過聲明式配置實現復雜數據渲染。其核心設計理念體現在三個層級&#xff1a; 數據驅動&#xff1a;通過data屬性綁定數據源&#xff0c;支持動態更新與…

07前端項目----面包屑

面包屑 效果實現代碼全局事件總線-$bus 效果 實現代碼 上節searchParams中參數categoryName是表示一二三級分類所點擊的列表名 <!--bread面包屑--> <div class"bread"><ul class"fl sui-breadcrumb"><li><a href"#"…

kafka jdbc connector適配kadb數據實時同步

測試結論 源端增量獲取方式包括&#xff1a;bulk、incrementing、timestamp、incrementingtimestamp&#xff08;混合&#xff09;&#xff0c;各種方式說明如下&#xff1a; bulk: 一次同步整個表的數據 incrementing: 使用嚴格的自增列標識增量數據。不支持對舊數據的更新…

基于Hadoop的音樂推薦系統(源碼+lw+部署文檔+講解),源碼可白嫖!

摘要 本畢業生數據分析與可視化系統采用B/S架構&#xff0c;數據庫是MySQL&#xff0c;網站的搭建與開發采用了先進的Java語言、爬蟲技術進行編寫&#xff0c;使用了Spring Boot框架。該系統從兩個對象&#xff1a;由管理員和用戶來對系統進行設計構建。主要功能包括&#xff…

CentOS的安裝以及網絡配置

CentOS的下載 在學習docker之前&#xff0c;我們需要知道的就是docker是運行在Linux內核之上的&#xff0c;所以我們需要Linux環境的操作系統&#xff0c;當然了你也可以選擇安裝ubuntu等操作系統&#xff0c;如果你不想在本機安裝的話還可以考慮買阿里或者華為的云服務器&…