跳转到主要内容HOME
KOLIKO / 20262026年06月08日 / 24 min read

STM32CUBEMX 使用笔记

本文记录了使用STM32CubeMX配置STM32微控制器的过程,涵盖时钟树设置、PWM、ADC/DMA采集、OLED显示、蓝牙串口通信、CAN总线等外设的配置要点,并整理了函数重定向、空闲中断、DMP欧拉角计算、看门狗及CLion环境搭建等常见问题的解决方法。

2026年06月08日24 min read
嵌入式STM32STM32CubeMXCFreeRTOS

STM32CUBEMX 使用笔记

image-20260608190729425

[TOC]

基本操作


  1. 打开 rcc 的高速时钟
  2. 配置时钟树为最大的72mhz
  3. 选择debug为ser...的那个
  4. project上选择开发的mdk编辑器
  5. code generator上勾选第一个

一些模块的使用

PWM

频率=定时器时钟/(pre 预分频+1)/(count period 计数值+1)

占空比=Pulse 对比值/(count period 计数值)%

1 HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//定时器通道使能 2 __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,z);//修改占空比,使用z值来修改

舵机配置(占空比范围500-2500)

image-20231226135706173

ADC 轮询采集

x server: port: 80​spring: datasource:   druid:     driver-class-name: com.mysql.cj.jdbc.Driver     url: jdbc:mysql://localhost:3306/user?serverTimeZone=UTC     username: root     password: abc123456​#配置表前缀mybatis-plus: global-config:   db-config:     table-prefix: userproperties

打开持续转换

1HAL_ADC_Start(&hadc1);//启动ADC转换 2 3HAL_ADC_PollForConversion(&hadc1,50);//等待转换完成,第二个参数表示超时时间,单位ms 4if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1),HAL_ADC_STATE_REG_EOC)){ 5 AD_Value= HAL_ADC_GetValue(&hadc1); 6 printf("[\tmain]info:v=%.1fmv\r\n",AD_Value*3300.0/4096); 7} 8HAL_Delay(500);

DMA转换

先调adc设置

<img src="images/stmcubemx.assets/image-20230405222508760.png" alt="image-20230405222508760" style="zoom:67%;" /> <img src="images/stmcubemx.assets/image-20230405220610684.png" alt="image-20230405220610684" style="zoom: 67%;" />

然后,可以更改一下长度为word,模式为circular

<img src="images/stmcubemx.assets/image-20230405220647288.png" alt="image-20230405220647288" style="zoom:80%;" />

写业务代码,DMA的搬运模式是从通道一然后通道二再循环回来搬运,从ADC_Value[0]到ADC_Value[99]然后再回到ADC_Value[0]。数组列这么长只是为了一点滤波,其实也可以不用这么长。

1HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value,100);//启动ADC转换,第二个参数为数据存储起始地址,第三个参数为DMA传输数据的长度 2 3//循环z 4 HAL_Delay(1000); 5 //简单的滤波 6 for (i = 0, ad1 = 0, ad2 = 0; i < 20; i += 2) { 7 ad1 += ADC_Value[i]; 8 ad2 += ADC_Value[i + 1]; 9 } 10 ad1 /= 10; 11 ad2 /= 10; 12 printf("\r\n********ADC-DMA-Example*********\r\n"); 13 printf("[\tmain]info:v=%1.3fmv\r\n", ad1 * 5000.0 / 4096); 14 printf("[\tmain]info:v=%1.3fmv\r\n", ad2 * 5000.0 / 4096); 15

OLED 显示

参考网站:https://blog.csdn.net/qq_39542860/article/details/105907958

注意:

  • 包含头文件oled.h
  • 初始化OLED.Init()
  • OLED_Clear(); 清屏操作
  • OLED_Display_On();操作打开显示屏!!!很重要(之前漏加了导致一直没有上电显示),后面则不需要类似 reflesh 的操作
  • C6T6 的板子 Cubemx 有一个很傻逼的 bug,在 i2c 那里要修改一行代码 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  • 最好不要用 oled 来 debug(OLED 的代码过长,如果在串口使用时,消息会丢失)
  • 在cubemx中使用的时候,&hi2c需要extern使用

HC-05 蓝牙模块

参考资料:https://blog.csdn.net/qq_38410730/article/details/80368485 > https://blog.csdn.net/lzzzzzzm/article/details/114916509

默认波特率:9600 AT 模式下波特率:38400

USART 串口收发

参考资料:https://www.bilibili.com/video/BV1q4411d7RX?p=9&vd_source=0eab86b58f0ee8a55b25e7648743b65a

  • 阻塞式收发(必须要等收发完成后才执行下一步)

    1 uint8_t temp[] = "test"; 2 HAL_UART_Transmit(&huart2,temp,5,50);
  • printf 重定向

    1 int fputc(int ch, FILE *f){ 2 uint8_t temp[1] = {ch}; 3 HAL_UART_Transmit(&huart|, temp ,1, 2); 4 return ch; 5 }

    !!!使用重定向必须勾选 MicroLIB (搞了我好久)

在串口收发的是时候不要轻易加操作,可能会触发中断导致接收异常(比如在接收里面加串口的发送)

DMA串口收发

STM32CubeMX HAL库串口+DMA+IDLE空闲中断不定长度数据接收和发送_hal 库 清除 dma接收中断标志位-CSDN博客

【笔记】STM32CubeMx+串口空闲中断+DMA——利用函数HAL_UARTEx_ReceiveToIdle_DMA实现不定长数据接收——STM32F103ZET6(匿名上位机/助手基本收发可用)_stm32cubemx 串口空闲中断dma-CSDN博客

这里给出一些DMA串口的操作

DMA在串口打开后,将rx引脚设置为上拉

<img src="images/stmcubemx.assets/image-20241217114810129.png" alt="image-20241217114810129" style="zoom:50%;" /> <img src="images/stmcubemx.assets/image-20241217114557295.png" alt="image-20241217114557295" style="zoom:67%;" />

使用函数:

DMA发送函数

HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

DMA空闲接收函数
1#define BUF_SIZE 200 2uint8_t rx_buffer[BUF_SIZE]; // 创建接收缓存,大小为BUF_SIZE 3 4HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);//开启空闲中断 5 6//空闲中断回调 7void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) 8{ 9 if (huart->Instance == USART1) 10 { 11 Size = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); 12 HAL_UART_Transmit(&huart1, rx_buffer, Size, 0xffff); //将接受到的数据再发回上位机 13 memset(rx_buffer, 0, Size); 14 15 //放在void USART1_IRQHandler(void)函数里,不要放在这 16 //HAL_UARTEx_ReceiveToIdle_DMA(&huart1,rx_buffer,BUF_SIZE);不要放在这里,如果放在这里,不仅上位机的波特率改变不能正常接收,就算改回去了也会接收不了 17 } 18}

IO 口操作

Input 操作:

    HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_13)

Output 操作

1 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_RESET) 2 HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET)

矩阵键盘

  • 可参考 22 年的省赛作品

扫描检测,一边使用推挽输出,一边使用 Input,上拉,按键按下时,下降沿触发中断,结合宏定义,节省代码时间,建议搭配定时器中断使用。

1 A1;B1;C1; //输出IO口置高,然后在进行检测 2 uint8_t KeyNum=0; 3 4 A0; //A端IO口拉低检测 5 if(a==0){KeyNum=1;HAL_Delay(20);while(a==0)HAL_Delay(20);} 6 //下降沿触发,20ms消抖 7 if(b==0){KeyNum=4;HAL_Delay(20);while(b==0)HAL_Delay(20);} 8 if(c==0){KeyNum=7;HAL_Delay(20);while(c==0)HAL_Delay(20);} 9 if(d==0){KeyNum=10;HAL_Delay(20);while(d==0)HAL_Delay(20);} 10 A1;

定时器的中断

cubemx 层

  • 选择定时器
  • 选择 Internal clock 配置预分频值和计数值(具体可参考 pwm 输出)
  • 打开定时器中断,配置优先级。

keil 层

  • HAL_TIM_Base_Start_IT(&htim2); 打开定时器中断(写在 tim 初始化函数的下面

  • 写定时器中断回调函数,可以直接在it.c文件里面写

    1void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 2{ 3 static unsigned char ledState = 0; 4 if (htim == (&htim2)) 5 { 6 7 } 8}

再补充记录一些中断回调函数

串口接收中断

1uint8_t Uart1_RxData; 2HAL_UART_Receive_IT(&huart2, (uint8_t *)&Uart1_RxData, 1); //先开启接收中断
1void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 2{ 3 UNUSED(huart); 4 if(huart == &huart2)//判断 5 { 6 //写协议 7 HAL_UART_Receive_IT(&huart2, (uint8_t *)&Uart1_RxData, 1); //再开启接收中断 8 } 9}

ADC中断

1 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) 2 { 3 if(hadc.Instance == ADC1) 4 { 5 //在这里写代码,实现需要的功能 6 } 7 } 8

EXIT中断

1void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin) 2{ 3 if(GPIO_Pin == GPIO_PIN_12) //EXTI12 4 { 5 } 6 if(GPIO_Pin == GPIO_PIN_13) //EXTI13 7 { 8 } 910

定时器中断

1HAL_TIM_Base_Start_IT(&htim2);//开启定时器中断 2 3void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//中断回调 4{ 5 static unsigned char ledState = 0; 6 if (htim == (&htim2)) 7 { 8 if (ledState == 0) 9 HAL_GPIO_WritePin(GPIOE,GPIO_PIN_15,GPIO_PIN_RESET); 10 else 11 HAL_GPIO_WritePin(GPIOE,GPIO_PIN_15,GPIO_PIN_SET); 12 ledState = !ledState; 13 } 14}

MPU6050

使用I2C与mpu6050进行通信,加速的测量过于简单所以这里就不做总结,重点在于如何求出欧拉角

参考教程

STM32F1基于STM32CubeMX配置移植dmp库

MPU6050如何通过惯性积分计算旋转角度(Yaw角)

这个难点在于移植还有欧拉角yaw的一个零偏处理,移植的问题在第一个教程当中有比较详细的源码可以参考(找了很久,走了很多错的方法),而零偏的话,可以参考第二个教程,相当于每次采样都减去偏移值。

us级延时

因为hal库只有ms级的延时所以有时候需要使用us的延时,这里利用systick来做

1/* 2Systick功能实现us延时 3*/ 4uint32_t fac_us; 5 6void HAL_Delay_us_init() 7{ 8 fac_us=HAL_RCC_GetHCLKFreq() / 1000000; //获取MCU的主频; 9} 10 11void HAL_Delay_us(uint32_t nus) 12{ 13 uint32_t ticks; 14 uint32_t told,tnow,tcnt=0; 15 uint32_t reload=SysTick->LOAD; 16 ticks=nus*fac_us; 17 told=SysTick->VAL; 18 while(1) 19 { 20 tnow=SysTick->VAL; 21 if(tnow!=told) 22 { 23 if(tnow<told)tcnt+=told-tnow; 24 else tcnt+=reload-tnow+told; 25 told=tnow; 26 if(tcnt>=ticks)break; 27 } 28 }; 29}

CAN收发FIFO(使用瓴控电机为例子)

<img src="images/stmcubemx.assets/image-20240409095720937.png" alt="image-20240409095720937" style="zoom: 80%;" /> <img src="images/stmcubemx.assets/image-20240409095747897.png" alt="image-20240409095747897" style="zoom:80%;" />

先配置stm32cubemx,配置波特率,配置中断。

注意:在使用通信的的时候,建议设备5v也要上电,因为在HAL_CAN_Start(&hcan)这个函数初始化如果rx没有拉高会初始化失败

然后编写一下函数,这里的添加过滤器和初始化can数据包头都需要放到can初始化后,可以放在MX_CAN_Init后面或者里面,代码中还以位置控制作为数据发送包的例子,接收回复编码器的值作为接收中断的例子

1#include "can.h" 2 3CAN_TxHeaderTypeDef canTxHeader; 4CAN_RxHeaderTypeDef canRxHeader; 5uint8_t canTxData[8]; 6uint8_t canRxData[8]; 7 8uint16_t encoderValue = 0; 9 10 11static uint16_t ctlValue = 10 * 100; 12static uint32_t txMailBox; 13 14//测试包大小 15#define test_msg_size 5000 16static uint16_t Send_msg = 0, Receive_msg = 0; 17 18/** 19 * @brief MX_CAN_Filter:添加can过滤器,开启can以及接收中断 20 * @param null 21 * @retval null 22 */ 23void MX_CAN_Filter() { 24 CAN_FilterTypeDef sFilterConfig; 25 /* config can filter1 */ 26 sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式 27 sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; //32位掩码模式 28 sFilterConfig.FilterIdHigh = (DEVICE_STD_ID) << 5; // id,过滤器高16位 29 sFilterConfig.FilterIdLow = 0x0000; 30 sFilterConfig.FilterMaskIdHigh = 0xFC00; // id mask // 0xFC00 31 sFilterConfig.FilterMaskIdLow = 0x0006; 32 sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; 33 sFilterConfig.FilterActivation = ENABLE; 34 sFilterConfig.FilterBank = 0; 35 if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK) { 36 /* Initialization Error */ 37 Error_Handler(); 38 } 39 40 /* config can filter2 */ 41 sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // Identifier mask mode 42 sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; 43 sFilterConfig.FilterIdHigh = (DEVICE_STD_BOARDCAST_ID) << 5; // id 44 sFilterConfig.FilterIdLow = 0x0000; 45 sFilterConfig.FilterMaskIdHigh = 0xFFE0; // id mask // 0xFFE0 46 sFilterConfig.FilterMaskIdLow = 0x0006; 47 sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; 48 sFilterConfig.FilterActivation = ENABLE; 49 sFilterConfig.FilterBank = 14; 50 if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK) { 51 /* Initialization Error */ 52 Error_Handler(); 53 } 54 55 /*##-3- Start the CAN peripheral ###########################################*/ 56 if (HAL_CAN_Start(&hcan) != HAL_OK) { 57 /* Start Error */ 58 Error_Handler(); 59 } 60 61 __HAL_CAN_ENABLE_IT(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING); // enable FIFO 0 message pending interrupt 62} 63 64/********************************************************************************* 65* @brief HAL_CAN_RxFifo0MsgPendingCallback, called in can interrupt can接收中断 66* @param None 67* @retval None 68*********************************************************************************/ 69void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { 70 /* Get RX message */ 71 if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &canRxHeader, canRxData) != HAL_OK) { 72 /* Reception Error */ 73 Error_Handler(); 74 } 75 76 if ((canRxHeader.StdId - DEVICE_STD_ID > 0) && (canRxHeader.DLC == 8)) { 77 /* get encoder value */ 78// encoderValue = (canRxData[7] << 8) + canRxData[6]; 79 if (canRxData[0] == 0x9A) { 80 Receive_msg++; 81 } 82// printf("EncoderValue: %d\r\n", encoderValue); 83 } 84} 85 86/** 87 * @brief HAL_UART_RxCpltCallback 这个函数用来测试丢包的值 88 * @param htim 定时器的编号 89 * @retval 无 90 */ 91void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//中断回调 92{ 93 static float loss_rate; 94 if (htim == (&htim2)) { 95 96 if (Send_msg >= test_msg_size) { 97 //计算丢包率 98 loss_rate = ((float) Send_msg - (float) Receive_msg) / (float) Send_msg; 99 printf("传输:%d/%d ", Receive_msg, Send_msg); 100 printf("丢包率:%.2f\r\n", loss_rate * 100); 101 102 Send_msg = 0; 103 Receive_msg = 1;//这里取1是为了保证丢包率的准确率 104 } 105 106 if (Send_msg % 2 == 1) { 107 GetMotorState(1); 108 } else if (Send_msg % 2 == 0) { 109 GetMotorState(2); 110 } 111 112 Send_msg++; 113 } 114} 115 116 117/** 118 * @brief 初始化can数据包头 119 * @param motorId 电机id 120 * @retval none 121 */ 122void CanTxHeaderInit(uint8_t motorId) { 123 canTxHeader.StdId = DEVICE_STD_ID + motorId; 124 canTxHeader.ExtId = 0x00; 125 canTxHeader.RTR = CAN_RTR_DATA; 126 canTxHeader.IDE = CAN_ID_STD; 127 canTxHeader.DLC = 8; 128} 129 130/** 131 * @brief 电机关闭命令,将电机从开启状态(上电后默认状态)切换到关闭状态,清除电机转动圈数及之前接收的控 132 制指令,LED 由常亮转为慢闪。此时电机仍然可以回复控制命令,但不会执行动作。 133 * @param motorId 电机id 134 * @retval NULL 135 */ 136void CloseMotor(uint8_t motorId) { 137 CanTxHeaderInit(motorId); 138 139 canTxData[0] = 0x80; 140 canTxData[2] = 0x00; 141 canTxData[2] = 0x00; 142 canTxData[3] = 0x00; 143 canTxData[4] = 0x00; 144 canTxData[5] = 0x00; 145 canTxData[6] = 0x00; 146 canTxData[7] = 0x00; 147 148 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 149} 150 151/** 152 * @brief 电机运行命令,将电机从关闭状态切换到开启状态,LED 由慢闪转为常亮。此时再发送控制指令即可控制电机动作。 153 * @param motorId 电机id 154 * @retval NULL 155 */ 156void OpenMotor(uint8_t motorId) { 157 CanTxHeaderInit(motorId); 158 159 canTxData[0] = 0x88; 160 canTxData[2] = 0x00; 161 canTxData[2] = 0x00; 162 canTxData[3] = 0x00; 163 canTxData[4] = 0x00; 164 canTxData[5] = 0x00; 165 canTxData[6] = 0x00; 166 canTxData[7] = 0x00; 167 168 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 169} 170 171/** 172 * @brief 电机停止命令,停止电机,但不清除电机运行状态。再次发送控制指令即可控制电机动作。 173 * @param motorId 电机id 174 * @retval NULL 175 */ 176void StopMotor(uint8_t motorId) { 177 CanTxHeaderInit(motorId); 178 179 canTxData[0] = 0x88; 180 canTxData[2] = 0x00; 181 canTxData[2] = 0x00; 182 canTxData[3] = 0x00; 183 canTxData[4] = 0x00; 184 canTxData[5] = 0x00; 185 canTxData[6] = 0x00; 186 canTxData[7] = 0x00; 187 188 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 189} 190 191/** 192 * @brief 开环控制命令,主机发送该命令以控制输出到电机的开环电压 193 * @param motorId 电机id 194 * @param powerControl int16_t 类型,数值范围-850~ 850,(电机电流和扭矩因电机而异) 195 * @retval NULL 196 */ 197void OpenLoopControlMotor(uint8_t motorId, uint16_t powerControl) { 198 CanTxHeaderInit(motorId); 199 200 canTxData[0] = 0xA0; 201 canTxData[2] = 0x00; 202 canTxData[2] = 0x00; 203 canTxData[3] = 0x00; 204 canTxData[4] = *(uint8_t *) &powerControl; 205 canTxData[5] = *((uint8_t *) &powerControl + 1); 206 canTxData[6] = 0x00; 207 canTxData[7] = 0x00; 208 209 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 210} 211 212/** 213 * @brief 转矩闭环控制命令,主机发送该命令以控制电机的转矩电流输出 214 * @param motorId 电机id 215 * @param iqControl int16_t 类型,数值范围-2048~ 2048,对应 MF 电机实际转矩电流范围-16.5A~16.5A,对应 MG 电机实际转矩电流范围-33A~33A 216 * @retval NULL 217 */ 218void TorqueClosedLoopControl(uint8_t motorId, uint16_t iqControl) { 219 CanTxHeaderInit(motorId); 220 221 canTxData[0] = 0xA1; 222 canTxData[2] = 0x00; 223 canTxData[2] = 0x00; 224 canTxData[3] = 0x00; 225 canTxData[4] = *(uint8_t *) &iqControl; 226 canTxData[5] = *((uint8_t *) &iqControl + 1); 227 canTxData[6] = 0x00; 228 canTxData[7] = 0x00; 229 230 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 231} 232 233/** 234 * @brief 速度闭环控制命令,主机发送该命令以控制电机的速度 235 * @param motorId 电机id 236 * @param speedControl int32_t 类型,对应实际转速为0.01dps/LSB int32_t 类型 237 * @retval NULL 238 */ 239void SpeedClosedLoopControl(uint8_t motorId, uint32_t speedControl) { 240 CanTxHeaderInit(motorId); 241 242 canTxData[0] = 0xA2; 243 canTxData[2] = 0x00; 244 canTxData[2] = 0x00; 245 canTxData[3] = 0x00; 246 canTxData[4] = *(uint8_t *) &speedControl; 247 canTxData[5] = *((uint8_t *) &speedControl + 1); 248 canTxData[6] = *((uint8_t *) &speedControl + 2); 249 canTxData[7] = *((uint8_t *) &speedControl + 3); 250 251 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 252} 253 254/** 255 * @brief 多圈位置闭环控制命令 1,主机发送该命令以控制电机的位置(多圈角度) 256 * @param motorId 电机id 257 * @param angleControl int32_t 类型,对应实际位置为 0.01degree/LSB,即 36000 代表 360°,电机转动方向由目标位置和当前位置的差值决定。 258 * @retval NULL 259 */ 260void MultiturnPositionClosedLoopControl(uint8_t motorId, uint32_t angleControl) { 261 CanTxHeaderInit(motorId); 262 263 canTxData[0] = 0xA3; 264 canTxData[2] = 0x00; 265 canTxData[2] = 0x00; 266 canTxData[3] = 0x00; 267 canTxData[4] = *(uint8_t *) &angleControl; 268 canTxData[5] = *((uint8_t *) &angleControl + 1); 269 canTxData[6] = *((uint8_t *) &angleControl + 2); 270 canTxData[7] = *((uint8_t *) &angleControl + 3); 271 272 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 273} 274 275/** 276 * @brief 多圈位置闭环控制命令 2,主机发送该命令以控制电机的位置(多圈角度) 277 * @param motorId 电机id 278 * @param maxSpeed 限制了电机转动的最大速度,为 uint16_t 类型,对应实际转速 1dps/LSB,即 360 代表 360dps。 279 * @param angleControl int32_t 类型,对应实际位置为 0.01degree/LSB,即 36000 代表 360°,电机转动方向由目标位置和当前位置的差值决定。 280 * @retval NULL 281 */ 282void MultiturnPositionClosedLoopSpeedControl(uint8_t motorId, uint16_t maxSpeed, uint32_t angleControl) { 283 CanTxHeaderInit(motorId); 284 285 canTxData[0] = 0xA3; 286 canTxData[2] = 0x00; 287 canTxData[2] = *(uint8_t *) &maxSpeed; 288 canTxData[3] = *((uint8_t *) &maxSpeed + 1); 289 canTxData[4] = *(uint8_t *) &angleControl; 290 canTxData[5] = *((uint8_t *) &angleControl + 1); 291 canTxData[6] = *((uint8_t *) &angleControl + 2); 292 canTxData[7] = *((uint8_t *) &angleControl + 3); 293 294 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 295} 296 297/** 298 * @brief SetMotorAngle设置电机单圈角度,主机发送该命令以控制电机的位置(单圈角度) 299 * @param motorId 电机id 300 * @param spinDirection 设置电机转动的方向,为 uint8_t 类型,0x00 代表顺时针,0x01 代表逆时针 301 * @param angleControl 对应实际位置为0.01degree/LSB,即36000代表360° 302 * @retval NULL 303 */ 304void SetMotorAngle(uint8_t motorId, uint8_t spinDirection, uint32_t angleControl) { 305 CanTxHeaderInit(motorId); 306 307 canTxData[0] = 0xA5; 308 canTxData[1] = spinDirection; 309 canTxData[2] = 0x00; 310 canTxData[3] = 0x00; 311 canTxData[4] = *(uint8_t *) &angleControl; 312 canTxData[5] = *((uint8_t *) &angleControl + 1); 313 canTxData[6] = *((uint8_t *) &angleControl + 2); 314 canTxData[7] = *((uint8_t *) &angleControl + 3); 315 316 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 317} 318 319/** 320 * @brief SetMotorAngleMaxSpeed 带最大速度设置单圈角度 321 * @param motorId 电机id 322 * @param spinDirection 设置电机转动的方向,为 uint8_t 类型,0x00 代表顺时针,0x01 代表逆时针 323 * @param angleControl 对应实际位置为0.01degree/LSB,即36000代表360° 324 * @param maxSpeed 限制了电机转动的最大速度,为 uint16_t 类型,对应实际转速1dps/LSB,即 360 代表 360dps 325 * @retval NULL 326 */ 327void SetMotorAngleMaxSpeed(uint8_t motorId, uint8_t spinDirection, uint32_t angleControl, uint16_t maxSpeed) { 328 CanTxHeaderInit(motorId); 329 330 canTxData[0] = 0xA6; 331 canTxData[1] = spinDirection; 332 canTxData[2] = *(uint8_t *) (&maxSpeed); 333 canTxData[3] = *((uint8_t *) (&maxSpeed) + 1); 334 canTxData[4] = *(uint8_t *) (&angleControl); 335 canTxData[5] = *((uint8_t *) (&angleControl) + 1); 336 canTxData[6] = *((uint8_t *) (&angleControl) + 2); 337 canTxData[7] = *((uint8_t *) (&angleControl) + 3); 338 339 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 340} 341 342/** 343 * @brief SetMotorAddAngle 增量式位置控制闭环 344 * @param motorId 电机id 345 * @param angleIncrement 对应实际增加角度 为0.01degree/LSB,即36000代表360° 346 * @retval NULL 347 */ 348void SetMotorAddAngle(uint8_t motorId, uint32_t angleIncrement) { 349 CanTxHeaderInit(motorId); 350 351 canTxData[0] = 0xA7; 352 canTxData[1] = 0x00; 353 canTxData[2] = 0x00; 354 canTxData[3] = 0x00; 355 canTxData[4] = *((uint8_t *) &angleIncrement + 0); 356 canTxData[5] = *((uint8_t *) &angleIncrement + 1); 357 canTxData[6] = 0x00; 358 canTxData[7] = 0x00; 359 360 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 361} 362 363 364/** 365 * @brief GetMotorState 读取当前电机的温度、电压和错误状态标志 366 * @param motorId 获取电机信息的id 367 * @retval NULL 368 */ 369void GetMotorState(uint8_t motorId) { 370 CanTxHeaderInit(motorId); 371 372 canTxData[0] = 0x9A; 373 canTxData[1] = 0x00; 374 canTxData[2] = 0x00; 375 canTxData[3] = 0x00; 376 canTxData[4] = 0x00; 377 canTxData[5] = 0x00; 378 canTxData[6] = 0x00; 379 canTxData[7] = 0x00; 380 381 HAL_CAN_AddTxMessage(&hcan, &canTxHeader, canTxData, &txMailBox); 382} 383 384

再加一下.h文件

1#ifndef MOTOR_TEST_CAN_H 2#define MOTOR_TEST_CAN_H 3 4#include "main.h" 5 6#define DEVICE_STD_ID (0x140) 7#define DEVICE_STD_BOARDCAST_ID (0x280) 8 9#define CAN_SHDN_Pin GPIO_PIN_7 // shutdown 10#define CAN_SHDN_GPIO_Port GPIOB 11#define CAN_SHDN_ENABLE() HAL_GPIO_WritePin(CAN_SHDN_GPIO_Port, CAN_SHDN_Pin, GPIO_PIN_SET) 12#define CAN_SHDN_DISABLE() HAL_GPIO_WritePin(CAN_SHDN_GPIO_Port, CAN_SHDN_Pin, GPIO_PIN_RESET) 13 14extern CAN_HandleTypeDef hcan; 15extern TIM_HandleTypeDef htim2; 16extern CAN_TxHeaderTypeDef canTxHeader; 17extern CAN_RxHeaderTypeDef canRxHeader; 18extern uint8_t canTxData[]; 19extern uint8_t canRxData[]; 20 21void MX_CAN_Filter(void); 22 23void CanTxHeaderInit(uint8_t motorId); 24 25void CloseMotor(uint8_t motorId); 26 27void OpenMotor(uint8_t motorId); 28 29void StopMotor(uint8_t motorId); 30 31void OpenLoopControlMotor(uint8_t motorId, uint16_t powerControl); 32 33void TorqueClosedLoopControl(uint8_t motorId, uint16_t iqControl); 34 35void SpeedClosedLoopControl(uint8_t motorId, uint32_t speedControl); 36 37void MultiturnPositionClosedLoopControl(uint8_t motorId, uint32_t angleControl); 38 39void MultiturnPositionClosedLoopSpeedControl(uint8_t motorId, uint16_t maxSpeed, uint32_t angleControl); 40 41void SetMotorAngle(uint8_t motorId, uint8_t spinDirection, uint32_t angleControl); 42 43void SetMotorAngleMaxSpeed(uint8_t motorId, uint8_t spinDirection, uint32_t angleControl, uint16_t maxSpeed); 44 45void SetMotorAddAngle(uint8_t motorId, uint32_t angleIncrement); 46 47void GetMotorState(uint8_t motorId); 48 49#endif //MOTOR_TEST_CAN_H 50

编码器使用

STM32CubeMX 编码器测速

先使用Encoder Mode

image-20240426202126476

然后配置编码器,10是滤波

<img src="images/stmcubemx.assets/image-20240426204521613.png" alt="image-20240426204521613" style="zoom:67%;" />

在引入encoder.c和.h文件

encoder.c

1#include "encode.h" 2/************************************************************************** 3函数功能:单位时间读取编码器计数 4入口参数:定时器 5返回 值:速度值 6**************************************************************************/ 7int Read_Encoder(void)//读取计数器的值 8{ 9 int Encoder_TIM; 10 11 Encoder_TIM=(short)ENCODE_TIMX->CNT; ENCODE_TIMX ->CNT=0; 12 13 return Encoder_TIM; 14}

encoder.h

1#ifndef __ENCODE_H 2#define __ENCODE_H 3#include "main.h" 4#define ENCODE_TIMX TIM4 5int Read_Encoder(void);//读取计数器的值 6#endif

在定时器中断里面调用即可

1void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) 2{ 3 if(htim==(&htim9))//因为我采用的是定时器6 4 { 5 printf("%d",Read_Encoder()); 6 } 7}

注意,ENCODE_TIMX这里面的要改成自己的定时器TIM(?)

1Encoder_TIM=(short)ENCODE_TIMX->CNT; ENCODE_TIMX ->CNT=0;

看门狗

勾选独立看门狗

<img src="images/stmcubemx.assets/image-20240723164616818.png" alt="image-20240723164616818" style="zoom:80%;" />

配置时间

<img src="images/stmcubemx.assets/image-20240723164650372.png" alt="image-20240723164650372" style="zoom: 80%;" /> <img src="images/stmcubemx.assets/image-20240723165454273.png" alt="image-20240723165454273" style="zoom:80%;" />

然后在程序里面溢出时间内喂狗就好了HAL_IWDG_Refresh(&hiwdg);//喂狗

优雅的嵌入式开发(配合Clion使用)

这里参考稚晖君的文章配置CLion用于STM32开发【优雅の嵌入式开发】 - 知乎 (zhihu.com)

图里面的Toolchain可以换成stm32cubeIDE

<img src="images/stmcubemx.assets/image-20230330191101324.png" alt="image-20230330191101324" style="zoom:80%;" />

前期的安装都没什么问题,但是在stlink的设置和printf的重定向就有点问题了

stlink有时候因为芯片是盗版的需要加入芯片的型号(在RCT6中出现)

stlink烧录问题

正常配置如下

1source [find interface/stlink-v2.cfg] 2 3transport select hla_swd 4 5source [find target/stm32f1x.cfg] 6 7#reset_config srst_only 8reset_config none

加了芯片型号需要修改成

1source [find interface/stlink-v2.cfg] 2set CPUTAPID 0x2ba01477 #zhe'li's 3transport select hla_swd 4 5source [find target/stm32f1x.cfg] 6 7#reset_config srst_only 8reset_config none 9

printf重定向问题

先新建retarget.cretarget.h文件

1#ifndef _RETARGET_H__ 2#define _RETARGET_H__ 3 4#include "stm32f1xx_hal.h" 5#include <sys/stat.h> 6#include <stdio.h> 7 8void RetargetInit(UART_HandleTypeDef *huart); 9 10int _isatty(int fd); 11 12int _write(int fd, char *ptr, int len); 13 14int _close(int fd); 15 16int _lseek(int fd, int ptr, int dir); 17 18int _read(int fd, char *ptr, int len); 19 20int _fstat(int fd, struct stat *st); 21 22#endif //#ifndef _RETARGET_H__
1 #include <_ansi.h> 2 #include <_syslist.h> 3 #include <errno.h> 4 #include <sys/time.h> 5 #include <sys/times.h> 6 #include <retarget.h> 7 #include <stdint.h> 8 9 #if !defined(OS_USE_SEMIHOSTING) 10 11 #define STDIN_FILENO 0 12 #define STDOUT_FILENO 1 13 #define STDERR_FILENO 2 14 15 UART_HandleTypeDef *gHuart; 16 17 void RetargetInit(UART_HandleTypeDef *huart) 18 { 19 gHuart = huart; 20 21 /* Disable I/O buffering for STDOUT stream, so that 22 * chars are sent out as soon as they are printed. */ 23 setvbuf(stdout, NULL, _IONBF, 0); 24 } 25 26 int _isatty(int fd) 27 { 28 if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) 29 return 1; 30 31 errno = EBADF; 32 return 0; 33 } 34 35 int _write(int fd, char *ptr, int len) 36 { 37 HAL_StatusTypeDef hstatus; 38 39 if (fd == STDOUT_FILENO || fd == STDERR_FILENO) 40 { 41 hstatus = HAL_UART_Transmit(gHuart, (uint8_t *) ptr, len, HAL_MAX_DELAY); 42 if (hstatus == HAL_OK) 43 return len; 44 else 45 return EIO; 46 } 47 errno = EBADF; 48 return -1; 49 } 50 51 int _close(int fd) 52 { 53 if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) 54 return 0; 55 56 errno = EBADF; 57 return -1; 58 } 59 60 int _lseek(int fd, int ptr, int dir) 61 { 62 (void) fd; 63 (void) ptr; 64 (void) dir; 65 66 errno = EBADF; 67 return -1; 68 } 69 70 int _read(int fd, char *ptr, int len) 71 { 72 HAL_StatusTypeDef hstatus; 73 74 if (fd == STDIN_FILENO) 75 { 76 hstatus = HAL_UART_Receive(gHuart, (uint8_t *) ptr, 1, HAL_MAX_DELAY); 77 if (hstatus == HAL_OK) 78 return 1; 79 else 80 return EIO; 81 } 82 errno = EBADF; 83 return -1; 84 } 85 86 int _fstat(int fd, struct stat *st) 87 { 88 if (fd >= STDIN_FILENO && fd <= STDERR_FILENO) 89 { 90 st->st_mode = S_IFCHR; 91 return 0; 92 } 93 94 errno = EBADF; 95 return 0; 96 } 97 98 #endif //#if !defined(OS_USE_SEMIHOSTING)

添加这两个文件到工程,更新CMake,编译之后会发现,有几个系统函数重复定义了

被重复定义的函数位于Src目录的syscalls.c文件中,我们把里面重复的几个函数删掉即可。

在main函数的初始化代码中添加对头文件的引用并注册重定向的串口号:

1#include "retarget.h" 2 3RetargetInit(&huart1);

然后就可以愉快地使用printfscanf啦:

1char buf[100]; 2 3printf("\r\nYour name: "); 4scanf("%s", buf); 5/

中文乱码问题

stm32cubemx在重新生成代码的时候,会有中文乱码的问题,这里可以采用添加环境变量的方法解决。

  • 变量名称:JAVA_TOOL_OPTIONS
  • 变量值:-Dfile.encoding=UTF-8

image-20231214165135471

烧录H7时候报错

1FAILED: Ctr_Board.elf 2 3d:/app/jetbrains/stm32-clion/gcc-arm-none-eabi-10.3-2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld.exe:D:/document/ClionProject/Ctr_Board/STM32H723VGTX_FLASH.ld:91: non constant or forward reference address expression for section .ARM.extab 4collect2.exe: error: ld returned 1 exit status

原因: 最新的 STM32CubeMx 生成的 .ld 文件中含有 READONLY 关键字,此关键字只能在 gcc 11 版本及以后使用,gcc 10及以下版本解析不了报错。(在后面生成的注释中也有说明)

解决方法: 打开 .ld 文件,删除所有 (READONLY) 字段

image-20240711141119786

使用FreeRTOS时报错

image-20240712150919219

1#Uncomment for hardware floating point 2add_compile_definitions(ARM_MATH_CM4;ARM_MATH_MATRIX_CHECK;ARM_MATH_ROUNDING) 3add_compile_options(-mfloat-abi=hard -mfpu=fpv4-sp-d16) 4add_link_options(-mfloat-abi=hard -mfpu=fpv4-sp-d16)

需要将cmakelist.txt的这个地方取消注释掉