目录
2、proBuffer[0]不是 ‘#’ 或proBuffer[4]不是 ‘;’
4、proBuffer[2]或proBuffer[3]至少一个不是数字
5、';' 位于proBuffer[2]或proBuffer[3]位置
6、proBuffer[2]和proBuffer[3]数字超范围
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 time
extern 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 0
if(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 digits
uint8_t tmp1 =proBuffer[3]-0x30; // single digits
uint8_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 port
HAL_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 not
if (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 RTCtime
RTC_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;//pause
return;
}
else
isUploadTime = 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时,视具体的字符组合情况,可能和上面的情况一样,也可能连续输入几次以后,串口助手就有错误信息提示。但无论如何,程序并没有进入死循环,再次输入任意的正确指令,都能正确执行。
这两种特殊情况下,通过编写更完整的程序,自然可以让处理结果更简洁。