目录
一、工程目的
1、目标
2、通讯协议及应对错误指令的处理目标
二、工程设置
三、程序改进
四、下载与调试
1、合规的指令
2、不以#开头,但以;结束,长度不限
3、以#开头,不以;结束,也不包含;,长度不限
4、以#开头,以;结束,长度<5
5、以#开头,以;结束,长度>5
6、非数字位于proBuffer[2]或proBuffer[3]位置
7、';'位于proBuffer[2]或proBuffer[3]位置
8、时间数值超过范围
在本文作者的文章(参考文章)里,细说STM32单片机USART中断收发RTC实时时间并改善其鲁棒性的方法_stm32 usart中断 at指令-CSDN博客 https://wenchm.blog.csdn.net/article/details/143461698,谈到了一种方法,对不同情况下输入的错误指令,在程序中提供了对应的应急处理方法和错误信息提示,提高了程序的鲁棒性和可用性。参考文章的程序中,每次接收5个字节的指令字符,然后对接收到的5个字节数据在updateRTCTime()函数里进行判断和处理。
本文对参考文章进行了修改,每次接收中断只接收1个字节(也就是1个字符)的数据,先在接收回调(含内部调用的自定义函数)函数里对输入指令的字符串合规性、指令字符串长度长度进行大范围的判断和处理。然后再在updateRTCTime()函数里进行细节(数据头尾格式、数值范围、是否数字、是否指令字)方面的判断和处理。
一、工程目的
1、目标
再次提供一种新方法,每次接收中断只接收1个字符,重点解决程序设计的鲁棒性:对不同的指令输入的应急容错处理能力、消息提示。
2、通讯协议及应对错误指令的处理目标
通讯协议(参照参考文章的表格)。
需要注意的是:
- 对符合规则的指令字符串输入,比如“#H23;",程序显示指令字符串,并更新RTC时间;
- 对长度<=5,其他方面符合规则输入,比如“#H3;",“#HT3;",“#T23;",程序显示指令字符串,并消息提示。
- 对以#开头的、指令字符串长度大于5,并且以;结束的输入,比如在串口助手发送“#H236;",助手会显示;H236,并消息提示,无效的指令。无论多长的数据,其处理规则是,每隔5个字符,后面的数据从左侧开始覆盖前面的数据。
- 对以#开头的、指令字符串长度不限,并不含有;的输入,比如在串口助手发送“#H256",无论串口助手发送多少次,助手都没有显示,直到串口助手发送包含;在内的且长度不大于5的字符串为止,显示最后接收到的5个字符。并消息提示,无效的指令。
- 不以#开头或不含有#,但以;结尾,长度<=5的字符串输入,显示输入的字符串,并消息提示,无效的指令。
- 不以#开头或不含有#,也不含有;,长度不限的字符串输入,无论串口助手发送多少次,助手都没有显示,直到串口助手发送包含;在内的且长度不大于5的字符串为止,显示最后接收到的5个字符。并消息提示,无效的指令。
二、工程设置
与参考文章相同。
三、程序改进
受影响的程序有usart.c,usart.h。
修改 usart.h,定义RX_CMD_LEN = 1。这个常量用于控制函数HAL_UART_Receive_IT()每次接收数据的长度,修改为1则每次只接收1字节的数据,即1个字符。
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
#include <ctype.h>
/* USER CODE END Includes */
extern UART_HandleTypeDef huart2;
/* USER CODE BEGIN Private defines */
#define RX_CMD_LEN 1 //每次接收到的指令长度1字节
extern uint8_t rxBuffer[]; //5字节的输入缓冲区,如#H15;
extern uint8_t isUploadTime; //是否上传时间数据
/* USER CODE END Private defines */
void MX_USART2_UART_Init(void);
/* USER CODE BEGIN Prototypes */
void on_UART_IDLE(UART_HandleTypeDef *huart); //IDLE中断函数
void updateRTCTime(); //根据接收指令更新RTC
/* USER CODE END Prototypes */
修改usart.c,增加一个变量定义rxBufPos和一个宏定义PRO_CMD_LEN。
此处,切记接收缓存和发送缓存不要赋初值,不然可能引起某些情况下的指令输入产生混乱。
/* USER CODE BEGIN 0 */
#include "rtc.h"
#include <string.h>
#include <stdio.h>
/* 新增的两句用于接收不定长数据 */
#define PRO_CMD_LEN 5 // String length must be 5。
uint8_t rxBufPos = 0; // Receive buffer bit index
/* 新增的两句用于接收不定长数据 */
uint8_t proBuffer[10]; //为DEBUG观察方便,两个缓存数组不赋初值
uint8_t rxBuffer[10];
uint8_t rxCompleted = RESET; //HAL_UART_Receive_IT()接收是否完成
uint8_t isUploadTime = 1; //是否上传时间数据
/* USER CODE END 0 */
修改usart.c,修改函数HAL_UART_RxCpltCallback()和on_UART_IDLE()的代码,修改并删除函数updateRTCTime()冗余的判断和操作。
/* USER CODE BEGIN 1 */
/*串口接收完毕中断回调函数*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
rxCompleted = SET;
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); //允许IDLE中断
}
}
/*IDLE事件中断的检测与处理,获得proBuffer*/
void on_UART_IDLE(UART_HandleTypeDef *huart)
{
if (__HAL_UART_GET_IT_SOURCE(huart, UART_IT_IDLE) == RESET)
return;
__HAL_UART_CLEAR_IDLEFLAG(huart); //清除IDLE挂起标志
__HAL_UART_DISABLE_IT(huart, UART_IT_IDLE); //禁止IDLE事件中断
if (rxCompleted)
{
uint8_t ch = rxBuffer[0];
if(ch == '#')
rxBufPos = 0; //收到#则指令头部位置0
//最多接收5个字符
if (rxBufPos < PRO_CMD_LEN)
{
proBuffer[rxBufPos] = ch;
rxBufPos++;
// 输入的指令字符串必须#开头,并且包含;无论;是否在末尾,;都代表指令结束
if(ch == ';' ) //遇到;则打印指令并更新RTC
{
//把接收到的指令字符显示到串口助手
HAL_UART_Transmit(huart,proBuffer, strlen((char*)(proBuffer)), 200);
HAL_Delay(10);
updateRTCTime(); //把接收到的指令更新RTC时间
// 每次发送指令到串口助手后清除缓存
memset(rxBuffer, '\0', sizeof(rxBuffer));
memset(proBuffer, '\0', sizeof(proBuffer));
// 位索引复位,这个操作尤其在正确输入之后遭遇错误输入的时候有意义
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)
{
rxBufPos = 0;
}
/*更新完RTC时间后,要把rxCompleted复位,并再次启动串口接收,让程序处于等待串口输入的状�??*/
rxCompleted = RESET;
/* 再次启动串口接收 */
HAL_UART_Receive_IT(huart, rxBuffer, RX_CMD_LEN);
}
}
/* 对接收到的指令做是否合规的判断,更新修改RTC时间 */
void updateRTCTime()
{
unsigned char hello1[]="Invalid command\n";
unsigned char hello2[]="Invalid data\n";
// Identify the start_bit is '#',the end_bit is ';'or not,
// Regardless of whether the input characters are less than 5 or more than 5,
// they will all be processed by this program in the end.
// No matter how you clear the buffer, the data received next time will not be complete.
// This situation will never improve until the serial port is restarted.
if (proBuffer[0] != '#' || proBuffer[PRO_CMD_LEN - 1] != ';' )
{
HAL_UART_Transmit(&huart2,hello1,sizeof(hello1),200);
HAL_UART_Init(&huart2); //重启串口
return;
}
// Identify the data_bit is digits or not
if (isalpha(proBuffer[2]) || isalpha(proBuffer[3]))
{
HAL_UART_Transmit(&huart2,hello2,sizeof(hello2),200);
return;
}
//update RTCtime
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
/* -0x30 操作用于将ASCII码表示的字符(假设是数字'0'~'9')转换为其对应的整数 */
uint8_t timeSection = proBuffer[1]; //类型字符
uint8_t tmp10 = proBuffer[2]-0x30; //十位
uint8_t tmp1 = proBuffer[3]-0x30; //个位
uint8_t val= 10*tmp10+tmp1;
if (HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK)
{
//调用HAL_RTC_GetTime()之后必须调用HAL_RTC_GetDate()以解锁数据,才能连续更新Date and Time
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
switch (timeSection)
{
case 'H': // 修改小时
{
if(val <= 24)
sTime.Hours = val;
else
{
HAL_UART_Transmit(&huart2,hello2,sizeof(hello2),200);
return;
}
}
break;
case 'M': // 修改分钟
{
if(val <= 60)
sTime.Minutes = val;
else
{
HAL_UART_Transmit(&huart2,hello2,sizeof(hello2),200);
return;
}
}
break;
case 'S': // 修改秒
{
if(val <= 60)
sTime.Seconds = val;
else
{
HAL_UART_Transmit(&huart2,hello2,sizeof(hello2),200);
return;
}
}
break;
case 'U':
{
if( tmp1 == 0)
{
isUploadTime = 0;//pause
return;
}
else
isUploadTime = 1; //resume
}
break;
default: // 不是 'H', 'M' , 'S','U'则返回
{
HAL_UART_Transmit(&huart2,hello1,sizeof(hello1),200);
}
return;
}
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); //设置RTC时间影响到下一次唤醒
}
}
/* USER CODE END 1 */
新增了一个宏定义PRO_CMD_LEN,其值为5,即表示一条指令的长度5字符;
新增了一个变量rxBufPos,用于表示缓冲区proBuffer的当前存储位置索引。
调用HAL_UART_Receive_IT()以中断方式接收数据时,长度设置为RX_CMD_LEN=1,这样每收到一个字符,就会执行一次回调函数HAL_UART_RxCplCallback(),这个函数里开启IDLE事件中断后,就会执行on_UART_IDLE()。函数on_UART_IDLE()的功能是对接收到的一个字符ch进行判断和处理。每当起始符为#,就将rxBufPos设置为0;如果rxBufPos小于5,就将ch存入缓冲区proBuffer,并且使rxBufPos加1;如果ch是指令结束符“;”。就调用函数updateRTCTime()对指令进行解析和处理。
on_UART_IDLE()中第一个判断语句中的函数是__HAL_UART_GET_IT_SOURCE(),不再是__HAL_UART_GET_FLAG()。因为上位机连续发送5字节,MCU串口接收到1字节后就开启了IDLE事件中断,但是因为后续还有连续的数据接收,所以IDLE事件的中断标志位并不会立刻置位,而是在接收完5字节后才置位。如果使用_HAL_UART_GET_FLAG()判断IDLE事件中断的中断标志位,中断一次后就不会再处理后续的数据了。
相对于参考文章,其它地方的程序修改,都是为了提高程序的容错能力。这些修改,作者都DEBUG过,十分好用。
四、下载与调试
本文的重点在于调试。测试各种情况下新设计的程序的鲁棒性:对各种错误的输入的应急处理能力和信息提示。
1、合规的指令
显示输入的指令字符串,并更新RTC时间。
2、不以#开头,但以;结束,长度不限
只显示最后接收到的5个字符,并消息提示,无效的指令输入。
3、以#开头,不以;结束,也不包含;,长度不限
不论发送多少次,串口助手没显示,直到发送包含;在内且长度<=5的指令后,才只显示最后接收到的5个字符,并消息提示,无效的指令输入。
依次发送截图中的指令,串口助手没有响应(程序的后台是有相应的) ,直到发送“#H8”,并显示无效的指令。最终的结果的指令是否合规,要看机缘。
4、以#开头,以;结束,长度<5
显示输入的指令字符串,并消息提示,无效的指令
5、以#开头,以;结束,长度>5
只显示最后接收到的5个指令字符,并消息提示,无效的指令
6、非数字位于proBuffer[2]或proBuffer[3]位置
显示输入的指令字符,并消息提示,无效的数据。
7、';'位于proBuffer[2]或proBuffer[3]位置
只显示;之前的数据。并消息提示,无效的指令。
8、时间数值超过范围
显示输入的指令字符串,并消息提示,无效的数据。