第一个拓展任务是实现串口设置电机的过流阈值、控制电机的启停,同时将电机的过流阈值写到MCU的片上Flash中。
# 串口收发数据
开发板上串口2和板载的调试器通讯,上位机可以通过USART2先MCU发送数据。
在ST的示例程序中,参考示例程序中的USART_Communication_RX_IT示例。
在CubeMX中对USART2的参数和中断进行设置。
ST为外设提供简易封装的LL驱动,在CubeMX中可以选择LL库作为外设的驱动库,使用LL接口可以提升代码的运行效率。
完成上述配置并生成代码。在函数中添加以下代码,实现printf重定向到USART2。
#include "stdio.h" #ifdef __GNUC__ /* With GCC, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif /* __GNUC__ */ /** * @brief Retargets the C library printf function to the USART. * @param None * @retval None */ PUTCHAR_PROTOTYPE { /* Place your implementation of fputc here */ /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */ LL_USART_TransmitData8(USART2, ch); while (!LL_USART_IsActiveFlag_TXE(USART2)); return ch; }
在生成的代码中,需要添加额外的代码来使能USART2的接收中断功能,添加的位置和代码如下。
LL_USART_EnableIT_RXNE(USART2); LL_USART_EnableIT_ERROR(USART2);
USART2的中断函数中的处理如下。
void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ /* USER CODE END USART2_IRQn 0 */ /* USER CODE BEGIN USART2_IRQn 1 */ /* Check RXNE flag value in SR register */ if(LL_USART_IsActiveFlag_RXNE(USART2) && LL_USART_IsEnabledIT_RXNE(USART2)) { cli_recv=LL_USART_ReceiveData8(USART2); /* RXNE flag will be cleared by reading of DR register (done in call) */ /* Call function in charge of handling Character reception */ if(USART_RX_CNT < USART_REC_LEN) { USART_RX_BUF[USART_RX_CNT]=cli_recv; //记录接收到的值 USART_RX_CNT++; //接收数据增加1 } } /* USER CODE END USART2_IRQn 1 */ }
# 在Flash中存储电流阈值信息。
在STM32F411的参考手册中可以看到片上FLASH的分布情况。用户使用其中的一个Sector作为存储电流阈值信息的位置。
在配套的示例代码中可以参考F4系列的其他芯片的Flash示例程序来使用相应的Flash函数。
在指定的Flash地址中写入数据的代码如下。
/*Flash store*/ #define ADDR_FLASH_SECTOR_7 ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */ #define FLASH_USER_START_ADDR ADDR_FLASH_SECTOR_7 /* Start @ of user Flash area */ FLASH_EraseInitTypeDef pEraseInit; uint32_t Address,SECTORError = 0; //Write currentValue into flash pEraseInit.TypeErase = TYPEERASE_SECTORS; pEraseInit.Sector = FLASH_SECTOR_7; pEraseInit.NbSectors = 1; pEraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; HAL_FLASH_Unlock(); HAL_FLASHEx_Erase(&pEraseInit, &SECTORError); Address=FLASH_USER_START_ADDR; HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, currentValue); HAL_FLASH_Lock();
从Flash读取写入的数据的操作代码如下
uint32_t Address; Address=FLASH_USER_START_ADDR; Power_Info.Cur_Threshold=*(uint32_t*)Address;
# 串口接收数据解析以及过流保护
上述对串口USART2的数据收发以及Flash的读写的关键代码进行介绍,结合教程中对串口指令解析的代码,可以实现对电机的阈值管理、启动和停止功能。相关的代码如下:
uint8_t *rx_p; uint8_t CTR_CMD,Model1_CMD,Model2_CMD,StartRun_CMD,StopRun_CMD; uint32_t TEXT_Buffer[1]; uint32_t dataTemp[1]; uint8_t CTR_Mode=0; uint8_t Unlock_Current=0; char Can_Run_Status=0; void Processing_data(void) { char *thresholdStr = strstr((char*)Ext_USART_RX_BUF,"CurrentThreshold:" ); FLASH_EraseInitTypeDef pEraseInit; uint32_t Address,SECTORError = 0; if(ext_recpack_finish_flag) { ext_recpack_finish_flag=0; if(thresholdStr!=NULL) { thresholdStr+=strlen("CurrentThreshold:"); char *endPtr; long currentValue = strtol(thresholdStr,&endPtr,10); if(endPtr!=thresholdStr && *endPtr == 'm' && *(endPtr+1)== 'A') { TEXT_Buffer[0]= currentValue; //Write currentValue into flash pEraseInit.TypeErase = TYPEERASE_SECTORS; pEraseInit.Sector = FLASH_SECTOR_7; pEraseInit.NbSectors = 1; pEraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3; HAL_FLASH_Unlock(); HAL_FLASHEx_Erase(&pEraseInit, &SECTORError); Address=FLASH_USER_START_ADDR; HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, currentValue); HAL_FLASH_Lock(); //Read back currentValue to check whether writing success dataTemp[0]=*(uint32_t*)Address; if(TEXT_Buffer[0]==dataTemp[0]) { Power_Info.Cur_Threshold = dataTemp[0]; } else { Power_Info.Cur_Threshold= Default_Cur_Threshold_value; } } } } rx_p=&Ext_USART_RX_BUF[0]; CTR_CMD=StringContain((char*)rx_p,"RecoveryControl"); StartRun_CMD=StringContain((char*)rx_p,"CanRun"); StopRun_CMD=StringContain((char*)rx_p,"StopRun"); Model1_CMD=StringContain((char*)rx_p,"CurrentMode1"); Model2_CMD=StringContain((char*)rx_p,"CurrentMode2"); if(CTR_CMD>0) { rx_p+=CTR_CMD; Unlock_Current=1; } if(StartRun_CMD>0) { rx_p+=StartRun_CMD; Can_Run_Status=1; HAL_GPIO_WritePin(Relay_GPIO_Port,Relay_Pin,GPIO_PIN_SET); } if(StopRun_CMD>0) { rx_p+=StopRun_CMD; Can_Run_Status=-1; HAL_GPIO_WritePin(Relay_GPIO_Port,Relay_Pin,GPIO_PIN_RESET); } if(Model1_CMD>0) { rx_p+=Model1_CMD; CTR_Mode=Self_Locking_Mode; } if(Model2_CMD>0) { rx_p+=Model2_CMD; CTR_Mode=Hiccup_Mode; } }
INA219采集到电流数据后,与阈值电流进行比较,据此控制继电器的通断,从而实现对电机的过流保护。
if(Power_Info.current>Power_Info.Cur_Threshold) { HAL_GPIO_WritePin(Relay_GPIO_Port,Relay_Pin,GPIO_PIN_RESET); OverCurrent_Flag = 1; printf("Self_Locking_Mode Current Overflow!\r\n"); } if(Unlock_Current==1 && OverCurrent_Flag) { OverCurrent_Flag=0; Unlock_Current=0; if(Can_Run_Status==1) { HAL_GPIO_WritePin(Relay_GPIO_Port,Relay_Pin,GPIO_PIN_SET); HAL_Delay(2000); } printf("Self_Locking_Mode Overflow Released!\r\n"); }
# 效果演示
通过串口控制电机的效果视频如下。
https://www.bilibili.com/video/BV1PzTezgE6p/?vd_source=c3c72f263ba4f17695f3ce613d1d0024
# 总结
教程中提供的串口解析指令和控制流程的效果不错,通过参考其中的代码,实现串口控制电机和在Flash中写入过流阈值信息。