第一个拓展任务是实现串口设置电机的过流阈值、控制电机的启停,同时将电机的过流阈值写到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中写入过流阈值信息。
我要赚赏金
