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

news2024/11/27 21:39:29

目录

一、工程目的

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、时间数值超过范围

        显示输入的指令字符串,并消息提示,无效的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2235777.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis设计与实现 学习笔记 第十六章 Sentinel

Sentinel&#xff08;哨岗、哨兵&#xff09;是Redis的高可用性&#xff08;high availability&#xff09;解决方案&#xff1a;由一个或多个Sentinel实例&#xff08;instance&#xff09;组成的Sentinel系统可以监视任意多个主服务器&#xff0c;以及这些主服务器属下的从服…

前端三件套-css

一、元素选择器 元素选择器&#xff1a;利用标签名称。p,h1-h6...... 行内样式&#xff08;内联样式&#xff09;&#xff1a;例如<p style"color:red;font-size:50px"> id选择器&#xff1a;针对某一个特定的标签来使用。以#定义。 class&#xff08;类&a…

MoonBit 双周报 Vol.59:新增编译器常量支持,改进未使用警告,支持跨包函数导入...多个关键技术持续优化中!

2024-11-04 MoonBit更新 增加了编译期常量的支持。常量的名字以大写字母开头&#xff0c;用语法 const C ... 声明。常量的类型必须是内建的数字类型或 String。常量可以当作普通的值使用&#xff0c;也可以用于模式匹配。常量的值目前只能是字面量&#xff1a; const MIN_…

新疆高校大数据实验室案例分享

高校大数据实验室建设&#xff0c;企业可以提供技术支持、实训平台和项目案例&#xff0c;高校则提供科研和教学资源&#xff0c;实现产学研一体化。不仅有利于大数据技术的应用和人才培养也有利于区域发展。 泰迪与新疆合作的院校包括新疆大学、昌吉学院等 新疆大…

Capcut,更适合做TikTok运营的“剪映”

剪映这一次的更新&#xff0c;可谓是引来许多视频创作者的怒火。原本免费的功能&#xff0c;更新之后需要vip才能使用了&#xff1b;原本的vip功能&#xff0c;则需要升级至svip&#xff0c;甚至一些功能&#xff0c;需要会员积分才能使用。 许多运营TikTok的小伙伴一直在用剪映…

linux centos 安装redis

安装 wget https://download.redis.io/releases/redis-7.4.0.tar.gz解压redis-7.4.0.tar.gz文件 tar -zxvf redis-7.4.0.tar.gz进入redis安装目录 cd redis-7.4.0make时报错&#xff0c;因为需要安装gcc&#xff0c;gcc安装需要联网安装 修改端口 编辑文件用vi。nano命令cen…

第一个纯血鸿蒙应用(Napi开发-ArtTS调用C/C++)

1.行业背景 纯血鸿蒙&#xff0c;即鸿蒙Next版已于2014年1月正式发版&#xff0c;鸿蒙生态设备数量已经突破10亿台&#xff0c;已经有超过15000个应用和元服务上架。鸿蒙生态不只是移动设备这么简单&#xff0c;他打造的是一个18n的全场景战略&#xff0c;真正做到了“万物互联…

2.ARM_ARM是什么

CPU工作原理 CPU与内存中的内容&#xff1a; 内存中存放了指令&#xff0c;每一个指令存放的地址不一样&#xff0c;所需的内存空间也不一样。 运算器能够进行算数运算和逻辑运算&#xff0c;这些运算在CPU中都是以运算电路的形式存在&#xff0c;一个运算功能对应一种运算电…

【Ant.designpro】上传图片

文章目录 一、前端二、后端 一、前端 fieldProps:可以监听并且获取到组件输入的内容 action{“/api/upload_image”} 直接调用后端接口 <ProFormUploadButtonlabel{"上传手续图片"}name{"imgs"}action{"/api/upload_image"}max{5} fieldPro…

CSS基础知识六(浮动的高度塌陷问题及解决方案)

目录 1.浮动高度塌陷概念 2.下面是几种解决高度塌陷的几种方案&#xff1a; 解决方案一&#xff1a; 解决方案二&#xff1a; 解决方案三&#xff1a; 1.浮动高度塌陷概念 在CSS中&#xff0c;高度塌陷问题指的是父元素没有正确地根据其内部的浮动元素或绝对定位元素来计…

【云原生开发】K8S集群管理后端开发设计与实现

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

计算机网络——SDN

分布式控制路由 集中式控制路由

qt QTableWidgetItem详解

1、概述 QTableWidgetItem 是 Qt 框架中的一个类&#xff0c;专门用于在 QTableWidget&#xff08;一个基于项的表格视图&#xff09;中表示单个单元格的内容。QTableWidget 继承自 QAbstractItemView&#xff0c;而 QTableWidgetItem 则作为表格中的一个单元格项&#xff0c;…

DevExpress中文教程 - 如何使用AI模型检查HTML编辑中的语法?

DevExpress .NET MAUI多平台应用UI组件库提供了用于Android和iOS移动开发的高性能UI组件&#xff0c;该组件库包括数据网格、图表、调度程序、数据编辑器、CollectionView和选项卡组件等。 目前许多开发人员正在寻找多种方法将AI添加到解决方案中&#xff08;这通常比想象的要…

vue中html如何转成pdf下载,pdf转base64,忽略某个元素渲染在pdf中,方法封装

一、下载 html2Canvas jspdf npm install jspdf html2canvas二、封装转换下载方法 htmlToPdf.js import html2Canvas from html2canvas import JsPDF from jspdf/*** param {*} reportName 下载时候的标题* param {*} isDownload 是否下载默认为下载&#xff0c;传false不…

day05(单片机)SPI+数码管

目录 SPI数码管 SPI通信 SPI总线介绍 字节交换原理 时序单元 ​​​​​​​SPI模式 模式0 模式1 模式2 模式3 数码管 介绍 74HC595芯片分析 ​​​​​​​原理图分析 ​​​​​​​cubeMX配置​​​​​​​ 程序编写 硬件SPI ​​​​​​​软件SPI 作业&#xff1a; SPI数…

越学越爽!4小时从零入门大模型教程,2024最详细的学习路线,让你少走99%弯路!(大模型/LLM/Agent/提示工程)

第一阶段&#xff1a;基础理论入门 目标&#xff1a;了解大模型的基本概念和背景。 内容&#xff1a; 人工智能演进与大模型兴起。 大模型定义及通用人工智能定义。 GPT模型的发展历程。 第二阶段&#xff1a;核心技术解析 目标&#xff1a;深入学习大模型的关键技术和工…

论文速读:简化目标检测的无源域适应-有效的自我训练策略和性能洞察(ECCV2024)

中文标题&#xff1a;简化目标检测的无源域适应&#xff1a;有效的自我训练策略和性能洞察 原文标题&#xff1a;Simplifying Source-Free Domain Adaptation for Object Detection: Effective Self-Training Strategies and Performance Insights 此篇文章为论文速读&#xff…

小白入门学习计算机辅助工具--Git和Github

虽然平时大家都有听过Github&#xff0c;但这实际上要分为Git和Github&#xff0c;我们可以简单理解为前者是用于本地&#xff0c;后者是远程端。下面我们来看看一些基本的操作。 Github创建仓库 让我们先从Github开始&#xff0c;点击右边的绿色按钮new进入创建库界面&#x…

【C++】哈希表封装 unordered_map 和 unordered_set 的实现过程

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriori…