目录
一、工程配置
二、软件代码
1、软件代码
2、usart.h
3、usart.c
4、rtc.c
三、运行与调试
1、合规的指令
2、proBuffer[0]不是 ‘#’ 或proBuffer[4]不是 ‘;’
3、指令长度小于5
4、proBuffer[2]或proBuffer[3]至少一个不是数字
5、';' 位于proBuffer[2]或proBuffer[3]位置
6、proBuffer[2]和proBuffer[3]数字超范围
7、输入的指令字符串长度>5,且不含有 '#' 和 ':'
8、以 '#' 开头且长度=5,或包含有 '#' 且 '#' 后的字符数=5。都不含有 ‘;’
9、以 '#' 开头且长度<5,或包含有 '#' 且 '#' 后的字符数<5。都不含有 ‘;’
在本文作者的文章中:细说STM32单片机DMA中断收发RTC实时时间并改善其鲁棒性的方法-CSDN博客 https://wenchm.blog.csdn.net/article/details/143844395 曾总结过“当输入的指令的长度不等于5时,程序容错能力是比较弱的,鲁棒性并不明显。这是因为串口接收设置数据长度=5导致的,rxBuffer[5]以后内容并不能被memset()清空,残余的数据影响了紧邻的下一次接收。”
在本文中,作者设置DMA每次只能接受1个字节的指令字符,然后在程序里判断数据起始字符和结束字符。当接收到起始字符 ‘#’ 时,位索引值 = 0,当接收到结束字符 ‘;’ 时,显示并更新RTC时间。每次更新RTC时间后,清空缓存。
当输入的字符长度>5时,且不包含有结束字符 ';' ,限制位索引值 = 5,并显示和处理指令字符。无论输入何种类型的错误指令,程序的容错能力显著提高,并能正确响应再次输入正确的指令。鲁棒性得到极大的改善。
特殊情况1, 以 '#' 开头或包含 '#' 但 '#' 后面的字符数量<4,且不含有 ';' 时,每次发送指令后,串口助手没有显示改变,貌似没有响应,实际后台是有响应的,只是每次读到 '#' 时,都有rxBufPos = 0,又因 '#' 后面的字符数量<4,程序不满足任何跳转。按任意 “#***;” 格式的字符串,程序就可以跳出。
特殊情况2,输入任意长度的指令字符串,但不含有起始字符 '#' 和结束字符 ';' ,累计发送的字符的长度为5的倍数时,或输入任意 “#***;” 格式的字符串,程序就可以跳出。
特殊情况1、2是可以编程解决并变得更完美的,从而完美解决程序的容错能力。这项工作,就留给感兴趣的网友吧。
一、工程配置
同参考文章。
二、软件代码
1、软件代码
同参考文章。
2、usart.h
/* USER CODE BEGIN Includes */
#define RX_CMD_LEN 1 // Only accept one character at a timeextern uint8_t rxBuffer[]; // Serial port receiving data buffer
extern uint8_t isUploadTime; // upload RTCtime switch
/* USER CODE END Includes */
/* USER CODE BEGIN Prototypes */
void updateRTCTime();
/* USER CODE END Prototypes */
3、usart.c
/* USER CODE BEGIN 0 */
#include "rtc.h"
#include "dma.h"
#include <string.h>
#include <ctype.h>uint8_t proBuffer[10]; //用于处理数据, #H12; #M23; #S43;
uint8_t rxBuffer[10]; //接收缓存数据, #H12; #M23; #S43;
uint8_t isUploadTime = 1; //是否上传时间数据unsigned char hello1[]="Invalid command\n";
unsigned char hello2[]="Invalid data\n";/* 新增的两句用于接收不定长数据 */
#define PRO_CMD_LEN 5 // String length must be 5.
uint8_t rxBufPos = 0; // Receive buffer bit index
/* 新增的两句用于接收不定长数据 *//* USER CODE END 0 */
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //DMA
{if (huart->Instance == USART2){uint8_t ch = rxBuffer[0];// If # is received, the instruction header position is 0if(ch == '#')rxBufPos = 0;// When the length of the input string is greater than 5 and does not contain';',// the index value is cleared to zero,// and the unfinished string can continue to be received until it encounters';'.if (rxBufPos < PRO_CMD_LEN){proBuffer[rxBufPos] = ch;rxBufPos++;// The input command string must start with # and contain ';',// whether or not ';' is at the end,// ';' represents the end of the command.if(ch == ';' ){// Upload the received command string, less than or equal to 5 char, and must be delayed,// otherwise, the command string displayed on the serial port will be incomplete.// HAL_UART_Transmit(huart,proBuffer, strlen((char*)(proBuffer)), 200);HAL_UART_Transmit_DMA(&huart2,proBuffer,PRO_CMD_LEN);HAL_Delay(10);// Update the RTC time with the received command.updateRTCTime();// Clear the cache after each command is sent to the serial port assistant.memset(rxBuffer, '\0', sizeof(rxBuffer));memset(proBuffer, '\0', sizeof(proBuffer));// Bit index reset,// this operation is particularly useful when the correct format of the input contains erroneous data.rxBufPos = 0;return;}// When the length of the input string is greater than 5 and does not contain';',// the index value is cleared to zero,// and the unfinished string can continue to be received until it encounters';'.//if((strchr((const char *)proBuffer, ';') == NULL) || (rxBufPos == PRO_CMD_LEN))if(rxBufPos == PRO_CMD_LEN){rxBufPos = 0;HAL_UART_Transmit_DMA(&huart2,proBuffer,PRO_CMD_LEN);HAL_Delay(10);HAL_UART_Init(&huart2);HAL_UART_Transmit(&huart2,hello1,sizeof(hello1),200);memset(rxBuffer, '\0', sizeof(rxBuffer));memset(proBuffer, '\0', sizeof(proBuffer));return;}}}
}// Update according to the command string received by the serial port.
void updateRTCTime()
{uint8_t timeSection=proBuffer[1]; // type characters, such as "#H12;"uint8_t tmp10=proBuffer[2]-0x30; // tens digitsuint8_t tmp1 =proBuffer[3]-0x30; // single digitsuint8_t val=10*tmp10+tmp1;// Identify the start_bit is '#' and the end_bit is ';'or not.// Determine whether the number of characters received is equal to 5.if (proBuffer[0] != '#' || proBuffer[PRO_CMD_LEN -1] != ';'){HAL_UART_Init(&huart2); // Restart serial portHAL_UART_Transmit(&huart2,hello1,sizeof(hello1),200);memset(rxBuffer, '\0', sizeof(rxBuffer));memset(proBuffer, '\0', sizeof(proBuffer));return; //An error has occurred, so it will naturally jump out of this callback.}// Identify the data_bit is digits or notif (isalpha(proBuffer[2]) || isalpha(proBuffer[3])){HAL_UART_Init(&huart2);HAL_UART_Transmit(&huart2,hello2,sizeof(hello2),200);memset(rxBuffer, '\0', sizeof(rxBuffer));memset(proBuffer, '\0', sizeof(proBuffer));return;}//update RTCtimeRTC_TimeTypeDef sTime;RTC_DateTypeDef sDate;if (HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK){// After calling HAL_RTC_GetTime(),// you must call HAL_RTC_GetDate() to continuously update Date and Time.HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);switch (timeSection){case 'H': // Modify hours{if(val <= 24)sTime.Hours = val;else{HAL_UART_Init(&huart2);HAL_UART_Transmit(&huart2,hello2,sizeof(hello2),200);memset(proBuffer, '\0', sizeof(proBuffer));return;}}break;case 'M': // Modify minutes{if(val <= 60)sTime.Minutes = val;else{HAL_UART_Init(&huart2);HAL_UART_Transmit(&huart2,hello2,sizeof(hello2),200);memset(proBuffer, '\0', sizeof(proBuffer));return;}}break;case 'S': // Modify seconds{if(val <= 60)sTime.Seconds = val;else{HAL_UART_Init(&huart2);HAL_UART_Transmit(&huart2,hello2,sizeof(hello2),200);memset(proBuffer, '\0', sizeof(proBuffer));return;}}break;case 'U':{if( tmp1 == 0){isUploadTime = 0;//pausereturn;}elseisUploadTime = 1; //resume}break;default: // If it is not H, M, S, U then return{HAL_UART_Init(&huart2);HAL_UART_Transmit(&huart2,hello1,sizeof(hello1),200);memset(proBuffer, '\0', sizeof(proBuffer));}return;} //switch//Set the RTC time and will affect the next RTC wake-up interrupt.HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);} //if GetTime
}
/* USER CODE END 1 */
4、rtc.c
同参考文章。
三、运行与调试
下载,运行,首先显示字符串“Hello,DMA transmit”,然后连续显示时间,间隔1s。下面根据不同的指令输入情况,展示运行结果。
1、合规的指令
输入正确格式的指令,修改时分秒、暂停、恢复、及再次输入正确格式的指令。
2、proBuffer[0]不是 ‘#’ 或proBuffer[4]不是 ‘;’
输入字符串长度=5,但首字符≠#或结束字符≠;时,能正常进行容错处理并消息提示,可以继续输入正确的指令:
3、指令长度小于5
输入字符串的长度<5,但包含有 ‘#’ 和 ‘;’ 。程序能自动处理并跳出纠错循环,此后输入正确的指令后,执行并显示正确的结果。
4、proBuffer[2]或proBuffer[3]至少一个不是数字
程序能自动执行错误处理,并跳出纠错循环。此后输入正确的指令后,执行并显示正确的结果。
5、';' 位于proBuffer[2]或proBuffer[3]位置
程序能自动执行错误处理,并跳出纠错循环。此后输入正确的指令后,执行并显示正确的结果。
6、proBuffer[2]和proBuffer[3]数字超范围
程序能自动执行错误处理,并跳出纠错循环。此后输入正确的指令后,执行并显示正确的结果。
7、输入的指令字符串长度>5,且不含有 '#' 和 ':'
程序能自动执行错误处理,并跳出纠错循环。此后输入正确的指令后,执行并显示正确的结果。
8、以 '#' 开头且长度=5,或包含有 '#' 且 '#' 后的字符数=5。都不含有 ‘;’
程序能自动执行错误处理,并跳出纠错循环。此后输入正确的指令后,执行并显示正确的结果。
9、以 '#' 开头且长度<5,或包含有 '#' 且 '#' 后的字符数<5。都不含有 ‘;’
以 '#' 开头且长度<5,串口助手没有任何显示,无论你输入多少次,这是因为每次读入 '#' 时,都有rxBufPos = 0。但程序并没有进入死循环,再次输入任意的正确指令,都能正确执行。
而含有 '#' 且 '#' 后的字符数<5时,视具体的字符组合情况,可能和上面的情况一样,也可能连续输入几次以后,串口助手就有错误信息提示。但无论如何,程序并没有进入死循环,再次输入任意的正确指令,都能正确执行。
这两种特殊情况下,通过编写更完整的程序,自然可以让处理结果更简洁。