STM32单片机芯片与内部45 UART 不定长度接收 标志位结束 定时器超时 串口空闲中断

目录

一、标志位结束法

1、实现原理

2、代码

3、优缺点

优点:

缺点:

总结:

二、串口空闲中断

1、中断读取

2、DMA接收

3、优缺点

优点:

缺点:

三、定时器空闲超时

1、实现原理

2、代码

3、优缺点

优点:

缺点:


前文的发送已经很好的实现了发送一个数据,但是接收端仅介绍了每次接收一个字符,如果是双方通信,例如发送端以串口发送如下:

1,125,238,475,359

其中1表示命令号;125表示传感器1的数据,238表示传感器2的数据,475表示传感器3的数据,359表示传感器4的数据。

2,19,45,37,28

其中2表示命令号;19表示设定模块1数据,45表示设定模块2数据,37表示设定模块3数据,28表示设定模块4数据。

可以看到两个命令是不等长的(但是每一个相同命令号的应是等长的)

不等长意味着不能接收到第一个字符进行计数,固定长度后停止。

一、标志位结束法

1、实现原理

在数据尾部加入特殊字符,一般在工业界采用'\r''\n'来作为帧尾,也就是说串口收到数据后进行数组的存储,当连续收到\r\n,则表示该帧结束。

2、代码

cpp 复制代码
void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 
#endif	

在主函数判断即可实现。

cpp 复制代码
		if(USART_RX_STA&0x8000)
		{					   
			len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
			printf("\r\n您发送的消息为:\r\n\r\n");
			for(t=0;t<len;t++)
			{
				USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
			}
			USART_RX_STA=0; //一定要清除接收标志
		}

3、优缺点

优点:
  1. 易于解析

    • 固定的结束符 :使用固定的字符如\r\n作为帧结束符,可以让接收端程序简单地检测到数据帧的结束,容易实现数据包的分帧与解析。
    • 简化的协议设计 :接收端只需要检查收到的数据流中是否出现了\r\n,就能知道一帧数据的结尾,不需要复杂的帧长度或校验机制。
  2. 广泛兼容性

    • 标准化的结束符\r\n是许多通信协议中标准的结束符(如ASCII协议、Modbus、Telnet等)。采用此约定的通信系统非常普遍,兼容性好。
    • 跨平台支持 :不同的操作系统(如Windows、Linux)和编程环境对\r\n的支持也非常广泛,使得协议更具跨平台的兼容性。
  3. 错误检测能力

    • 如果在数据流中间收到了\r\n,而没有完全接收到完整的帧,接收端可以利用这种信息判断数据不完整或者错误,增加了一定的容错能力。
  4. 便于调试和查看

    • 在一些串口调试工具中,\r\n作为帧尾,可以方便地让开发人员快速识别数据帧的结束,增强了调试过程的可视性。
缺点:
  1. 数据中可能出现特殊字符

    • 数据内容与帧尾冲突 :如果数据本身有\r\n字符(比如文本数据或二进制数据),会导致接收端错误地认为数据帧结束,从而导致错误的帧解析。虽然可以使用转义字符或特殊的约定(如用某些分隔符代替\r\n),但这种方法可能增加协议的复杂性。
  2. 数据帧的长度不定

    • 使用\r\n作为帧结束符时,每个数据帧的长度可能不一样,造成接收端需要动态调整内存或缓冲区来存储不同长度的数据帧。对于高数据速率或大数据量传输的情况,可能需要更精确的控制和优化。
  3. 性能问题

    • 逐字符处理 :如果数据量较大,每次接收时需要逐字符检测是否遇到\r\n,这样会增加CPU负担,特别是在高数据速率下。
    • 帧边界检测开销 :每次检测\r\n的过程可能会影响实时性,尤其是在嵌入式系统中,可能需要额外的处理时间来确认是否达到了帧尾。
  4. 不适合二进制数据

    • 如果传输的是二进制数据流,\r\n可能与数据中的其他字节重叠,导致误判帧的结束。这种情况下需要采用其他更可靠的帧分隔符,如固定长度或基于长度的协议。
  5. 对实时性要求较高的系统不太适用

    • 对于实时性要求较高的系统,帧的结束符可能导致额外的延迟,尤其是在接收到大数据流时,可能需要等待完整的\r\n符号才能解析一帧数据,造成一定的延迟。
总结:

使用 \r\n 作为帧结束符有许多优点,如简单易用、协议兼容性好和易于调试。但也有一些缺点,主要是在数据帧长度不确定、数据中可能出现\r\n字符,以及性能和实时性问题。

在设计串口通信协议时,使用\r\n作为帧尾是一个不错的选择,尤其适合字符型数据和简单的协议,但对于高吞吐量、大数据量或包含二进制数据的通信场景,可能需要使用更精确的帧分隔方案(如固定长度帧或基于长度的协议)。

二、串口空闲中断

比如给上位机给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。

空闲态中断会在串口接收线处于空闲状态一段时间时触发。结合空闲态中断,接收端可以基于空闲时间来判断帧的结束。

cpp 复制代码
	// 使能串口接收中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
	// 使能串口空闲中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE);	

1、中断读取

可以看到在总中断中判断当前是接收中断还是空闲中断,如果是空闲中断则单次数据结束。此处可以添加一个flag标志,在main中判断即可。

cpp 复制代码
// 串口中断服务函数
void DEBUG_USART_IRQHandler(void)
{
	
	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
	{		
		test_data.data[test_data.len] = USART_ReceiveData(DEBUG_USARTx);
	
		if(++test_data.len > data_size - 2) //让第 data_size - 1 位置一直是空终止位
		{
			test_data.len = 0;                //演示从简处理,如果接收超过尺寸从头开始覆盖
		}	  
	}	 
	

	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)!=RESET)
	{	

    USART_ReceiveData(DEBUG_USARTx);	        //查阅参考手册 软件序列清除标志位流程
		test_data.data[test_data.len] = '\0';     //以空闲中断认为接收完成 设置空终止位
		test_data.flag = 1;			                  //赋值标志作为程序判断是否完成
	}	 
	
}

2、DMA接收

可以看到在总中断中判断当前是否空闲中断,如果是空闲中断则单次数据结束。此处可以添加一个flag标志,在main中判断即可。如果空闲则把接收数据做个处理或输出一个标志。

cpp 复制代码
void USART1 IROHandler(void)
{
    uint8 ClearFlag;
    uint16 count =0:
    //处理接收空闲中断
    if(USART_GetITStatuS(USART1,USART_IT_IDLE)!=RESET)
    {
    //清除标志:先读SR,再读DR
    ClearFlag = USART1->SR;
    ClearFlag = USART1->DR;

/*获取接收数据个数 */

    count = DMA_GetCurrDataCounter(DMA1 Channe15);
    Touch_tx_flag=1024-count;    //已收到多少个数据
    /*处理数据,将DMA搬运的数据 复制到发送处  当然不用发送就不用写这个了*/
    memcpy((uint8 t *)Touch tx buffer,(uint8 t *)Touch rx buffer,Touch tx flag);
    /*DMA接收复位 */
    DMA_Cmd(DMA1_Channel5,DISABLE);
    DMA_SetCurrDatacounter(DMA1_channe15,1024);
    DMA_Cmd(DMA1_Channe15,ENABLE);
    }
}

在main中,记得清除flag等。

3、优缺点

优点:
  1. 无额外字符干扰

    • 不需要额外添加特殊字符(如\r\n)来标识数据帧的结束,因此对于数据中可能包含这些特殊字符的情况(如文本或二进制数据流),不会发生干扰。
  2. 实时性较好

    • 空闲中断通常会在数据接收完成后触发,能够实时感知数据传输的结束,避免了轮询和等待帧结束标志的时间延迟。
  3. 适用于二进制数据

    • 该方法适合于包含二进制数据的协议,因为它不依赖于数据内容,可以避免数据帧中包含分隔符或特殊字符的冲突。
缺点:
  1. 可能出现误判

    • 如果数据传输的速度非常慢,或者接收的数据量小且间隔较长,空闲时间过长可能会被误判为一帧数据结束。例如,如果数据传输中有较长的静默期,空闲中断可能会提前触发。
  2. 空闲检测延迟

    • 空闲状态的判断是基于一定的时间窗口(如几百微秒到几毫秒),如果数据帧没有及时接收完整,可能会导致检测到空闲状态后才处理数据,增加处理延迟。
  3. 硬件资源消耗

    • 空闲中断需要硬件中断资源,如果系统中有多个串口或者大量的空闲中断,可能会增加中断处理的复杂性,影响系统的实时性。
  4. 实时性较低

    • 由于空闲状态是基于"静默期"判断的,它的实时性可能比其他基于定时器或流控制的方案要低。尤其是高速数据流时,空闲中断可能没有办法及时捕获数据帧结束的时机。

三、定时器空闲超时

1、实现原理

可以看到串口空闲虽好,但是无法调节时间,因此可以和定时器进行结合。例如如果收到一次数据且进入了空闲中断或接收中断(可自行选择),idle_timeout_count开始递减至0,主函数可以判断如果为0说明数据已结束,则可以处理接收数据的数组。

2、代码

cpp 复制代码
#include "stm32f10x.h"

#define UART_RX_BUFFER_SIZE 1024
#define IDLE_TIMEOUT 1000  // 空闲时长设置为1000ms,即1秒

// 接收缓冲区和索引
uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE];
uint16_t uart_rx_index = 0;
volatile uint32_t idle_timeout_count = 0;  // 计时器,用于空闲时长控制

void USARTx_Init(void)
{
    // USART1初始化,略过,参照前面的配置

    // 启用空闲中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    
    // 配置定时器来模拟空闲时长
    SysTick_Config(SystemCoreClock / 1000);  // 每1ms中断一次
}

void SysTick_Handler(void)
{
    if (idle_timeout_count > 0) {
        idle_timeout_count--;
    }
}

void USART1_IRQHandler(void)
{
    // 判断是否是空闲中断
    if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) {
        // 清除空闲中断标志
        USART_ClearITPendingBit(USART1, USART_IT_IDLE);

        // 读取当前接收到的数据
        uint8_t received_byte = USART_ReceiveData(USART1);

        // 将接收到的数据存入缓冲区
        uart_rx_buffer[uart_rx_index++] = received_byte;

        // 重置空闲计时器
        idle_timeout_count = IDLE_TIMEOUT;  // 重新开始空闲计时

        // 如果缓冲区已满,处理数据帧
        if (uart_rx_index >= UART_RX_BUFFER_SIZE) {
            process_uart_data(uart_rx_buffer, uart_rx_index);
            uart_rx_index = 0;  // 重置索引,准备接收下一帧数据
        }
    }
}

void process_uart_data(uint8_t* data, uint16_t len)
{
    // 这里可以对接收到的数据进行帧解析、校验等操作
    // 例如输出数据,或者将数据存入其他地方处理
    for (uint16_t i = 0; i < len; i++) {
        printf("Received byte: %c\n", data[i]);
    }
}

3、优缺点

优点:

  • 灵活性高: 用户可以调整 IDLE_TIMEOUT 变量的值来控制数据帧的结束时长,增加灵活性。比如可以设定不同的数据帧结束时间以适应不同的应用场景。
  • 响应及时: 通过定时器的使用,可以确保在一定时间内没有新的数据时,我们能及时处理完接收到的帧。

缺点:

  • 额外的定时器开销: 需要使用系统定时器(如 SysTick)来检测空闲时长,可能会对系统的其他定时任务产生一定影响。
  • 延迟可能较长: 如果设置的空闲时长较长,可能会导致数据处理的延迟。需要根据实际应用的需求平衡延迟和空闲时间。
相关推荐
陌夏微秋2 小时前
STM32单片机芯片与内部47 STM32 CAN内部架构 介绍
数据库·stm32·单片机·嵌入式硬件·架构·信息与通信
7yewh14 小时前
Linux驱动开发 IIC I2C驱动 编写APP访问EEPROM AT24C02
linux·arm开发·驱动开发·嵌入式硬件·嵌入式
上海易硅智能科技局有限公司14 小时前
AG32 MCU 的电机控制方案
单片机·嵌入式硬件
程序员JerrySUN14 小时前
Yocto 项目 - 共享状态缓存 (Shared State Cache) 机制
linux·嵌入式硬件·物联网·缓存·系统架构
嵌入式小强工作室16 小时前
stm32能跑人工智能么
人工智能·stm32·嵌入式硬件
MikelSun16 小时前
电压控制环与电流控制环
单片机·嵌入式硬件·物联网
挥剑决浮云 -18 小时前
STM32学习之 按键/光敏电阻 控制 LED/蜂鸣器
c语言·经验分享·stm32·单片机·嵌入式硬件·学习
Whappy00119 小时前
第13部分 1.STM32之PWR电源控制-----4个实验
stm32·单片机·嵌入式硬件
冲,干,闯20 小时前
单片机里不想阻塞系统的延时
单片机·嵌入式硬件