Bootstrap

细说STM32单片机DMA中断收发RTC实时时间并改善其鲁棒性的另一种方法

目录

一、工程配置

二、软件代码

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 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时,视具体的字符组合情况,可能和上面的情况一样,也可能连续输入几次以后,串口助手就有错误信息提示。但无论如何,程序并没有进入死循环,再次输入任意的正确指令,都能正确执行。

        这两种特殊情况下,通过编写更完整的程序,自然可以让处理结果更简洁。

;