【STM32实战】机械臂快递分拣系统(一)——机械臂控制程序(下位机)编写
【STM32實戰】機械臂快遞分揀系統(一)——機械臂控制程序(下位機)編寫
- 前言
- 題目分析
- 工程模板生成
- 藍牙模塊的使用
- 藍牙接收數據解析與機械臂控制
- 測試
前言
近期回校上最后一門課,剛好是做機械臂有關的題目,所以寫文記錄一下。主要實現的是可以自動識別獲取快遞位置,機械臂可以抓取快遞,以及根據自動識別快遞上的條形碼獲得目的地點,機械臂可以將快遞抓取并移動到目的地點,此外就是還可以通過上位機來控制機械臂,上位機可以是PC,也可以是另一部STM32,需要有圖形化界面。
題目分析
自動識別獲取快遞位置以及識別快遞上的條形碼需要用到攝像頭模塊,需要解決STM32和攝像頭模塊之間的通信問題,以及快遞形狀。通過上位機來控制機械臂則需要使用到藍牙或者WIFI模塊,圖形化界面可以使用物聯網云平臺來開發,這里用的是阿里云。如果使用另一部32進行控制,則還可在另一部32上做界面。
但購買的機械臂是已經組裝完畢的,且上面帶有STM32系統板,型號為STM32F103C8T6,只引出了串口3的接口,也就是說如果要連接攝像頭模塊,就無法與上位機進行通信。所以我打算將該機械臂作為一個下位機來接收命令,上位機采用另一部STM32來做,上位機來處理串口接收到的來自云平臺或者攝像頭的數據,然后算出各個舵機的PWM占空比重裝載值,通過藍牙模塊發送到下位機機械臂,以此進行控制。
工程模板生成
這部分因人而異,如果是自己組裝的機械臂,則PWM輸出引腳設置上比較自由,但我這一款機械臂是已經組裝完畢的,因此需要根據這一款機械臂的原理圖來設置對應引腳,方便起見,這里采用STM32CubeMX來生成工程模板,機械臂上的STM32系統板引腳說明如下。
點擊 ACCESS TO MCU SELECTOR
搜索并選擇F103C8T6,如果你的主控是其他型號,就選擇其他型號。
點擊選擇高速時鐘為外部晶振。
如圖所示設置時鐘頻率為72M。
設置串口1為 Asynchronous,這個串口在我這款機械臂的STM32系統板上接的是CH340,轉成USB。
同樣的方法設置串口3。
如果有Debug需求的話可以設置一下SW的調試方式,我剛創建這個工程模板的時候是有設置的,后來發現如果只是用來接收命令的話也沒什么必要。
然后就是根據上面的引腳說明圖設置PWM了,PB3在TIM2的CH2上。
PB4在TIM3的CH1上。
其他的引腳都在TIM4上,因為TIM4默認PWM通道就是這幾個引腳,就不需要重映射。
然后設置一下TIM2,TIM3,TIM4的預分頻系數71,重裝載值19999,使能自動重裝載即可。
然后設置一下定時器1,這個主要用來控制各任務運行周期。
然后就是在NVIC中使能一下中斷。
然后設置好你要保存到路徑和工程名字,IDE。
這里我習慣只復制用得到的庫,以及生成額外的c和h文件。
然后生成代碼即可。
藍牙模塊的使用
藍牙模塊就是一種透傳設備,串口給它發送什么,它就接收什么,然后它將接收到的數據通過某種協議發送到跟它配對的藍牙上,這個藍牙將這些數據解析,最后還原成最初的未經處理的數據,這就是透明傳輸意思。這跟使用杜邦線將兩個模塊串口的RT對接是一樣的。
推薦購買的藍牙模塊是這種帶有按鍵的,按下按鍵然后接到電腦(通電)即可進入AT模式,設置藍牙配對。
配對方法也很簡單,接CH340模塊(需要注意RT對接),在按鍵按下的狀態,將CH340接入電腦USB口,即可讓藍牙進入AT模式,一般此模式下藍牙模塊的波特率為38400,在串口工具上發送命令AT,勾選發送新行,即可看到窗口返回OK。
然后可以使用AT命令設置藍牙名稱AT+NAME=Bluetooth-Marster,然后點擊發送,這里設置名稱為Bluetooth-Marster,另一個藍牙的名稱可以設置為Bluetooth-Slave。
然后設置藍牙配對碼AT+PSWD=0425,然后點擊發送,這里設置為0425,另一個藍牙的配對碼也要是這個,才能成功配對。
然后設置工作模式為主機模式,命令是AT+ROLE=1,然后點擊發送,另一個藍牙需要設置為從機模式,命令就是AT+ROLE=0
然后配置藍牙串口參數AT+UART=115200,0,0,然后點擊發送即可,兩個藍牙都要設置一樣。
然后設置一下模式為無需地址綁定模式,這樣只要配對碼一樣就能配對成功了。命令AT+CMODE=1,然后發送即可。
藍牙模塊主機這就配置完畢了,接下來配置從機,也就是上面所說的另一個藍牙。
這里因為我的兩個藍牙不是一套的,所有前面報錯。這個藍牙設置配對碼需要加上冒號。
然后設置為從機模式
設置波特率115200
設置任意地址綁定模式
然后就將兩個CH340模塊拔掉再插入電腦,即可在串口工具上測試藍牙能否配對成功了。
需要注意的是現在的波特率就不是38400了。
這里發送111,可以看到另一個藍牙成功接收到了,說明配對成功了。
藍牙接收數據解析與機械臂控制
這里需要用到如下代碼文件,將這些代碼文件添加進工程即可。
uartconfig.c
uartconfig.h
#ifndef __UARTCONFIG_H #define __UARTCONFIG_H#include "stm32f1xx_hal.h" #include "uartprotocol.h"// 重命名方便定義 typedef signed char int8_t; typedef short int int16_t; typedef int int32_t; typedef unsigned char uint8_t; typedef unsigned short int uint16_t; typedef unsigned int uint32_t;// 串口發送相關函數 void Data_Transmit(DataTransmit *data, UART_HandleTypeDef *huart); void Data_Pack_Transmit(DataTransmit *data, UART_HandleTypeDef *huart);// 串口接收相關函數 uint8_t Buffer_Receive(DataReceive *data, UART_HandleTypeDef *huart);#endifuartprotocol.c
/********************************************************************/ // 作用: 串口發送與接收 // 作者: FITQY // 時間: 2022.08.29 /********************************************************************/ #include "uartprotocol.h"/********************************************************************串口發送數據結構體初始化函數 Data_Transmit_Init各參數作用DataTransmit *data: 選擇要初始化的串口發送數據結構體head1: 幀頭1 head2: 幀頭2 length: 有效數據長度 ********************************************************************/ void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length) {data -> head1 = head1;data -> head2 = head2;data -> length = length;data -> cnt = length + 4;for(uint8_t i = 0; i < length; i++){data -> data[i] = 0;}for(uint8_t j = 0; j < length + 4; j++){data -> transmit_data[j] = 0;}}/********************************************************************串口發送數據打包函數 Data_Pack各參數作用DataTransmit *data: 選擇要打包的串口發送數據結構體********************************************************************/ void Data_Pack(DataTransmit *data) {data -> transmit_data[0] = data -> head1;data -> transmit_data[1] = data -> head2;data -> transmit_data[2] = data -> length;for(uint8_t i = 0; i < data -> length; i++){data -> transmit_data[3+i] = data -> data[i];}uint8_t sum = 0;for(uint8_t j = 0; j < data -> length + 3; j++){sum = sum + data -> transmit_data[j];}data -> transmit_data[data -> length + 3] = sum;}// 至此串口數據發送函數定義結束 使用例如下 /*DataTransmit data_transmit_uart; // 聲明全局結構體 data_transmit_uart 這個要放在 main 函數外面Data_Transmit_Init(&data_transmit_uart,0xAA,0xAA,1); // main函數中在 while 前 對結構體 data_transmit_uart 進行初始化 幀頭都為 0xAA 有效數據長度為 1 data_transmit_uart.data[0] = 1; // 將發送的第一個數據賦值為 1 位置不限 // HAL庫 使用方法 // 打包與發送分開 Data_Pack(&data_transmit_uart); // 對數據進行打包 每次要發送的數據改變的時候 都要重新打包 這個可放在 while 中Data_Transmit(&data_transmit_uart,&huart1); // 將數據通過USART1 發送// 打包與發送合并 Data_Pack_Transmit(&data_transmit_uart,&huart1); // 對數據進行打包發送// 固件庫 使用方法 // 打包與發送分開 Data_Pack(&data_transmit_uart); // 對數據進行打包 每次要發送的數據改變的時候 都要重新打包 這個可放在 while 中Data_Transmit(&data_transmit_uart, USART1); // 將數據通過USART1 發送// 打包與發送合并 Data_Pack_Transmit(&data_transmit_uart, USART1); // 對數據進行打包 并通過USART1 發送*/ // 至此串口數據發送函數使用例結束 /********************************************************************串口接收數據結構體初始化函數 Data_Receive_Init各參數作用DataReceive *data: 選擇要初始化的串口接收數據結構體head1: 幀頭1head2: 幀頭2********************************************************************/ void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2) {data -> head1 = head1;data -> head2 = head2;data -> length = 0;data -> cnt = 0;data -> state = 0;data -> i = 0;data -> data = 0;for(uint8_t j = 0; j < 50; j++){data -> receive_data[j] = 0;}}/********************************************************************串口接收數據函數 Data_Receive各參數作用DataReceive *data: 選擇要接收的串口接收數據結構體buf: 接收數據********************************************************************/ void Data_Receive(DataReceive *data, uint8_t buf) {if(data -> state == 0 && buf == data -> head1){data -> state = 1;data -> receive_data[0] = buf;}else if(data -> state == 1 && buf == data -> head2){data -> state = 2;data -> receive_data[1] = buf;}else if(data -> state == 2 && buf < 40){data -> state = 3;data -> length = buf;data -> cnt = buf+4;data -> receive_data[2] = buf;}else if(data -> state == 3 && data -> length > 0){data -> length = data -> length - 1;data -> receive_data[3 + data -> i] = buf;data -> i = data -> i + 1;if(data -> length == 0){data -> state = 4;}}else if(data -> state == 4){data -> receive_data[3 + data -> i] = buf;data -> state = 0;data -> i = 0;}else{data -> state = 0;data -> i = 0;}}// 至此串口數據接收函數定義結束 使用例如下 /*DataReceive data_receive_uart; // 聲明全局結構體 data_receive_uart 這個要放在 main 函數外面Data_Receive_Init(&data_receive_uart,0xAA,0xAA); // main函數中在 while 前 對結構體 data_receive_uart 進行初始化 設置幀頭為 0xAA// HAL庫 使用方法 Buffer_Receive(&data_receive_uart,&huart1); // main的函數在 while 前 HAL庫需要接收一次 使之可以進入中斷回調// 串口中斷回調函數 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) // 中斷回調函數中調用即可 函數 HAL_UART_RxCpltCallback 是一個弱定義 可以在 main.c 的 USER CODE BEGIN 4 中進行重寫 {if( huart == &huart1 ) // 檢測到串口中斷1{Data_Receive(&data_receive_uart,Buffer_Receive(&data_receive_uart,&huart1));}}// 固件庫 使用方法 直接將中斷函數復制到 main.c 下面即可// 串口1 中斷函數 void USART1_IRQHandler(void) {if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 判斷接收數據寄存器是否有數據{Data_Receive(&data_receive_uart, USART_ReceiveData(USART1)); // 從 串口1 接收1個數據USART_ClearFlag(USART1,USART_IT_RXNE); // 清空中斷標志 準備下一次接收}}*/ // 至此串口數據接收函數使用例結束 // 以下為串口接收數據解析函數 此類函數可自行定義 增刪改變量 方法不唯一 /********************************************************************串口接收數據結構體初始化函數 Target_Init各參數作用TargetAttribute *target: 選擇要初始化的結構體********************************************************************/ void Target_Init(TargetProperty *target) {target -> servo1 = 0;target -> servo2 = 0;target -> servo3 = 0;target -> servo4 = 0;target -> servo5 = 0;target -> servo6 = 0;}/********************************************************************串口接收數據解析函數 Target_Parse各參數作用 DataReceive *data: 選擇被解析的結構體TargetProperty *target: 選擇解析完成保存的結構體********************************************************************/ void Target_Parse(DataReceive *data, TargetProperty *target) {uint8_t sum = 0;uint8_t i = 0;while(i < data -> cnt - 1){sum = sum + data -> receive_data[i];i = i + 1;}if(sum == data -> receive_data[data -> cnt - 1]){target -> servo1 = data -> receive_data[3]*256 + data -> receive_data[4];target -> servo2 = data -> receive_data[5]*256 + data -> receive_data[6];target -> servo3 = data -> receive_data[7]*256 + data -> receive_data[8];target -> servo4 = data -> receive_data[9]*256 + data -> receive_data[10];target -> servo5 = data -> receive_data[11]*256 + data -> receive_data[12];target -> servo6 = data -> receive_data[13]*256 + data -> receive_data[14];}}// 至此數據解析函數定義結束 使用例如下 /*TargetProperty target; // 聲明全局結構體 target 這個要放在 main 函數外面Target_Init(&target); // main函數中在 while 前 對結構體 target 進行初始化 Target_Parse(&data_receive_uart,&target); // 解析接收數據 可以放在 while 中 也可放在其他地方 不唯一*/ // 解析函數可根據自身需求自由定義 方法不唯一// 下位機/* USER CODE BEGIN Includes *///#include "uartconfig.h" //#include "uartprotocol.h"/* USER CODE BEGIN 0 *///uint16_t task_num = 0; // 任務號 //uint16_t task_flag = 0; // 任務完成標志 //uint16_t task_max = 40; // 任務號最大值//DataTransmit data_transmit_uart3; // 聲明全局結構體 data_transmit_uart3 用于 串口發送//DataReceive data_receive_uart3; // 聲明全局結構體 data_receive_uart3 用于 串口接收//TargetProperty tmaster; // 聲明全局結構體 tmaster 用于 保存發給上位機的參數//TargetProperty tslave; // 聲明全局結構體 tslave 用于 保存上位機下發的參數//void System_Init(void); // 聲明系統初始化函數//void Task_Run(void); // 聲明任務運行函數/* USER CODE BEGIN 2 */// System_Init(); // 系統初始化/* USER CODE BEGIN 3 */// Task_Run(); // 任務運行/* USER CODE BEGIN 4 */ 系統初始化函數 //void System_Init(void) //{ // // Data_Transmit_Init(&data_transmit_uart3,0xFC,0xAA,12); // 串口3 發送結構體初始化 設置幀頭FC AA 有效數據長度12 // // Data_Receive_Init(&data_receive_uart3,0xFC,0xAA); // 串口3 接收結構體初始化 設置幀頭 FC AA // // Target_Init(&tmaster); // 發送上位機參數初始化 // // Target_Init(&tslave); // 上位機下發參數初始化 // // tslave.servo1 = 1500; // 各舵機初始裝載值設置 // tslave.servo2 = 1500; // tslave.servo3 = 2100; // tslave.servo4 = 1500; // tslave.servo5 = 1500; // tslave.servo6 = 1500; // data_receive_uart3.receive_data [3] = 1500/256; // RX TX 短接測試用部分 注釋 data_receive_uart3.receive_data [4] = 1500%256; data_receive_uart3.receive_data [5] = 1500/256; data_receive_uart3.receive_data [6] = 1500%256; data_receive_uart3.receive_data [7] = 2100/256; data_receive_uart3.receive_data [8] = 2100%256; data_receive_uart3.receive_data [9] = 1500/256; data_receive_uart3.receive_data [10] = 1500%256; data_receive_uart3.receive_data [11] = 1500/256; data_receive_uart3.receive_data [12] = 1500%256; data_receive_uart3.receive_data [13] = 1500/256; data_receive_uart3.receive_data [14] = 1500%256; // // HAL_TIM_Base_Start_IT(&htim1); // 開啟 定時器1 中斷 // // HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2); // 開啟 定時器2 PWM CH2 // HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); // 開啟 定時器3 PWM CH1 // HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1); // 開啟 定時器4 PWM CH1 // HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_2); // 開啟 定時器4 PWM CH2 // HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3); // 開啟 定時器4 PWM CH3 // HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4); // 開啟 定時器4 PWM CH4 // // Buffer_Receive(&data_receive_uart3,&huart3); // 串口3 接收一次數據 使之可以正確觸發中斷 // //} 定時器中斷回調函數 //void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //{ // // if( htim == (&htim1) ) // { // // if(task_num < task_max) // 任務號如果沒超過上限值 // { // // task_num = task_num + 1; // 任務切換 // task_flag = 1; // 任務完成標志 0完成 1未完成 每次只做一次任務 // // } // else // { // // task_num = 0; // 重頭開始執行任務 // task_flag = 1; // 任務完成標志 0完成 1未完成 每次只做一次任務 // // } // // } // //} 運行任務函數 //void Task_Run(void) //{ // // if(task_num == 0 && task_flag == 1) // 任務1 各參數上發上位機 // { // // data_transmit_uart3.data[0] = tmaster.servo1/256; // data_transmit_uart3.data[1] = tmaster.servo1%256; // // data_transmit_uart3.data[2] = tmaster.servo2/256; // data_transmit_uart3.data[3] = tmaster.servo2%256; // // data_transmit_uart3.data[4] = tmaster.servo3/256; // data_transmit_uart3.data[5] = tmaster.servo3%256; // // data_transmit_uart3.data[6] = tmaster.servo4/256; // data_transmit_uart3.data[7] = tmaster.servo4%256; // // data_transmit_uart3.data[8] = tmaster.servo5/256; // data_transmit_uart3.data[9] = tmaster.servo5%256; // // data_transmit_uart3.data[10] = tmaster.servo6/256; // data_transmit_uart3.data[11] = tmaster.servo6%256; // // Data_Pack_Transmit(&data_transmit_uart3, &huart3); // // task_flag = 0; // // } // else if(task_num == 10 && task_flag == 1) // 任務2 上位機數據解析 // { // // Target_Parse(&data_receive_uart3,&tslave); // // task_flag = 0; // // } // else if(task_num == 20 && task_flag == 1) // 任務3 各舵機PWM占空比設置 // { // // __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, tslave.servo1); // __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, tslave.servo2); // __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, tslave.servo3); // __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, tslave.servo4); // __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_2, tslave.servo5); // __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, tslave.servo6); // // task_flag = 0; // // } // else if(task_num == 30 && task_flag == 1) // 任務4 各舵機占空比重裝載值賦值 準備上發上位機 // { // // tmaster.servo1 = tslave.servo1; // tmaster.servo2 = tslave.servo2; // tmaster.servo3 = tslave.servo3; // tmaster.servo4 = tslave.servo4; // tmaster.servo5 = tslave.servo5; // tmaster.servo6 = tslave.servo6; // // task_flag = 0; // // } // //} 串口中斷回調函數 //void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //{// if( huart == &huart3 ) // 檢測到串口中斷3 則接收1個數據 通過函數 Data_Receive 保存 // {// Data_Receive(&data_receive_uart3,Buffer_Receive(&data_receive_uart3,&huart3));// }//}uartprotocol.h
#ifndef __UARTPROTOCOL_H #define __UARTPROTOCOL_H// 重命名方便定義 typedef signed char int8_t; typedef short int int16_t; typedef int int32_t; typedef unsigned char uint8_t; typedef unsigned short int uint16_t; typedef unsigned int uint32_t;// 串口發送相關結構體 typedef struct {uint8_t head1; // 幀頭1uint8_t head2; // 幀頭2uint8_t length; // 有效數據長度uint8_t cnt; // 總數據長度uint8_t data[40]; // 有效數據數組uint8_t transmit_data[50]; // 實際發送的數組 附帶上幀頭1 幀頭2 有效數據長度位 校驗位}DataTransmit;// 串口發送相關函數 void Data_Transmit_Init(DataTransmit *data, uint8_t head1, uint8_t head2, uint8_t length); void Data_Pack(DataTransmit *data);// 串口接收相關結構體 typedef struct {uint8_t head1; // 幀頭1uint8_t head2; // 幀頭2uint8_t length; // 有效數據長度uint8_t cnt; // 總數據長度uint8_t state; // 接收狀態uint8_t i; // 有效數據下標uint8_t data; // 接收數據緩沖位uint8_t receive_data[50]; // 實際接收的數組 附帶上幀頭1 幀頭2 有效數據長度位 校驗位}DataReceive;// 串口接收相關函數 void Data_Receive_Init(DataReceive *data, uint8_t head1, uint8_t head2); void Data_Receive(DataReceive *data, uint8_t buf);// 接收數據解析相關結構體 typedef struct {uint16_t servo1; uint16_t servo2;uint16_t servo3;uint16_t servo4;uint16_t servo5;uint16_t servo6;}TargetProperty;// 接收數據解析相關函數 void Target_Init(TargetProperty *target); void Target_Parse(DataReceive *data, TargetProperty *target);#endif然后在main.c中的這些位置添加如下代碼即可,這部分邏輯簡單,我也將注釋都寫在后面了,就不再解釋了。
/* USER CODE BEGIN Includes */
/* USER CODE BEGIN 0 */
uint16_t task_num = 0; // 任務號 uint16_t task_flag = 0; // 任務完成標志 uint16_t task_max = 40; // 任務號最大值DataTransmit data_transmit_uart3; // 聲明全局結構體 data_transmit_uart3 用于 串口發送DataReceive data_receive_uart3; // 聲明全局結構體 data_receive_uart3 用于 串口接收TargetProperty tmaster; // 聲明全局結構體 tmaster 用于 保存發給上位機的參數TargetProperty tslave; // 聲明全局結構體 tslave 用于 保存上位機下發的參數void System_Init(void); // 聲明系統初始化函數void Task_Run(void); // 聲明任務運行函數/* USER CODE BEGIN 2 */
System_Init(); // 系統初始化/* USER CODE BEGIN 3 */
Task_Run(); // 任務運行/* USER CODE BEGIN 4 */
// 系統初始化函數 void System_Init(void) {Data_Transmit_Init(&data_transmit_uart3,0xFC,0xAA,12); // 串口3 發送結構體初始化 設置幀頭FC AA 有效數據長度12Data_Receive_Init(&data_receive_uart3,0xFC,0xAA); // 串口3 接收結構體初始化 設置幀頭 FC AATarget_Init(&tmaster); // 發送上位機參數初始化Target_Init(&tslave); // 上位機下發參數初始化tslave.servo1 = 1500; // 各舵機初始裝載值設置tslave.servo2 = 1500;tslave.servo3 = 2100;tslave.servo4 = 1500;tslave.servo5 = 1500;tslave.servo6 = 1500;HAL_TIM_Base_Start_IT(&htim1); // 開啟 定時器1 中斷 HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2); // 開啟 定時器2 PWM CH2HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); // 開啟 定時器3 PWM CH1HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1); // 開啟 定時器4 PWM CH1 HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_2); // 開啟 定時器4 PWM CH2HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_3); // 開啟 定時器4 PWM CH3HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_4); // 開啟 定時器4 PWM CH4Buffer_Receive(&data_receive_uart3,&huart3); // 串口3 接收一次數據 使之可以正確觸發中斷}// 定時器中斷回調函數 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {if( htim == (&htim1) ){if(task_num < task_max) // 任務號如果沒超過上限值{task_num = task_num + 1; // 任務切換task_flag = 1; // 任務完成標志 0完成 1未完成 每次只做一次任務}else{task_num = 0; // 重頭開始執行任務task_flag = 1; // 任務完成標志 0完成 1未完成 每次只做一次任務}}}// 運行任務函數 void Task_Run(void) {if(task_num == 0 && task_flag == 1) // 任務1 各參數上發上位機{data_transmit_uart3.data[0] = tmaster.servo1/256; data_transmit_uart3.data[1] = tmaster.servo1%256; data_transmit_uart3.data[2] = tmaster.servo2/256; data_transmit_uart3.data[3] = tmaster.servo2%256; data_transmit_uart3.data[4] = tmaster.servo3/256; data_transmit_uart3.data[5] = tmaster.servo3%256; data_transmit_uart3.data[6] = tmaster.servo4/256; data_transmit_uart3.data[7] = tmaster.servo4%256; data_transmit_uart3.data[8] = tmaster.servo5/256; data_transmit_uart3.data[9] = tmaster.servo5%256; data_transmit_uart3.data[10] = tmaster.servo6/256; data_transmit_uart3.data[11] = tmaster.servo6%256; Data_Pack_Transmit(&data_transmit_uart3, &huart3); task_flag = 0;}else if(task_num == 10 && task_flag == 1) // 任務2 上位機數據解析{Target_Parse(&data_receive_uart3,&tslave); task_flag = 0;}else if(task_num == 20 && task_flag == 1) // 任務3 各舵機PWM占空比設置{__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_2, tslave.servo1);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_3, tslave.servo2);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_4, tslave.servo3);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, tslave.servo4);__HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_2, tslave.servo5);__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, tslave.servo6);task_flag = 0;}else if(task_num == 30 && task_flag == 1) // 任務4 各舵機占空比重裝載值賦值 準備上發上位機{tmaster.servo1 = tslave.servo1;tmaster.servo2 = tslave.servo2;tmaster.servo3 = tslave.servo3;tmaster.servo4 = tslave.servo4;tmaster.servo5 = tslave.servo5;tmaster.servo6 = tslave.servo6;task_flag = 0;}}// 串口中斷回調函數 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {if( huart == &huart3 ) // 檢測到串口中斷3 則接收1個數據 通過函數 Data_Receive 保存{Data_Receive(&data_receive_uart3,Buffer_Receive(&data_receive_uart3,&huart3));}}然后就可以編譯燒入運行了,還是講下運行函數。這里的task_num在定時器中斷觸發的時候會被加1,直到加到40然后清零,每一次觸發定時器中斷都會將task_flag置為1,在執行完任務后,task_flag將會置0,也就是說一段時間內不管執行幾次Task_Run,里面的任務都只會執行一次。
task_num為0的時候,將接收到的數據發回給上位機,這一步可以沒有,因為我是用這一步來判斷發送是否出錯,是一個回顯的作用。
task_num為10的時候,將解析上位機發送過來的數據,
task_num為20的時候,將解析后獲得的各舵機占空比重裝載值裝載,調節占空比。
task_num為30的時候,將解析后的數據賦值給發送到上位機的參數,等待下一個task_num為0的時候,將數據發送給上位機,就是完成回顯。
測試
給舵機模塊上電,這里可以用串口助手進行測試,幀頭是FC,AA,有效數據長度是12,對應十六進制是0C,1500對應的十六進制是05 DC,校驗碼是F8,如果藍牙成功連接上的話,舵機將會直立,因為1500對應的度數是90度(這個跟舵機安裝方式有關)
如果要調節1號舵機,也就是底盤,對應的序號是3和4,這里將3改成04,因此校驗碼就要減1,變成F7
可以看到舵機轉了,再測試一下二號舵機,將5改成04,校驗碼改成F6。
到這里不難發現用串口調試助手的缺點是,每一次調節舵機占空比都需要計算對應十六進制的值,比如1500的十六進制是05 DC,04 DC對應的是1244,雖然差是256,但是05 DC 和 04 DC相加的結果只差1,所以校驗碼是F8改成F7,計算會比較繁瑣,這時上位機的作用就來了,但上位機部分的代碼還比較雜亂,我還沒注釋,因此我們下期再見啦!
總結
以上是生活随笔為你收集整理的【STM32实战】机械臂快递分拣系统(一)——机械臂控制程序(下位机)编写的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java操作redis队列
- 下一篇: matlab中无穷小的数如何,2018考