Uart串口通讯协议与环形队列(裸机/RTOS)

MCU上使用的稳定Uart通讯协议(环形队列)

协议的主要内容:

接收:字节间超时判断、环形队列接收、非阻塞式接收整帧数据、接收查错;
发送:未应答重发(超过3次后反馈错误指令,若有应答继续发送原来数据)、
	 可选发送次数和间隔时间、CRC校验、环形队列解码;
若在RTOS中使用需添加互斥锁;

Uart总结:

  1. Uart—通用异步收发器,按位进行数据收发的一种串行通信接口,相比于IIC\SPI,Uart没有CLK线使其保持同步,在RS485、LIN总线中可见等。
  2. 使用硬件串口时,只需了解中断/DMA的逻辑即可实现收发,在TX/RX时,使用的是同名异址寄存器SBUF,接收/发送寄存器SBUF的地址虽然相同,但在物理上它们是2个不同的寄存器,一个只能读取,一个只能写入,以此保证不会出现紊乱。
  3. 各种串口、并口协议只需搞懂 0 / 1 表达的方式,其他大致相同。

移植过多种MCU、SOC,暂无问题
数据帧:
在这里插入图片描述

接收和发送数据都存放在循环队列中,以队列为空判断是否发送或处理接收到的数据

可在头文件中使用宏控制参数

#define UART_REC_BUF_GROUP 10   //接收循环队列
#define UART_SEND_BUF_GROUP 10  //发送循环队列
#define UART_BUF_SIZE 	30	    //发送与接收长度
#define UART_FIXED_BYTE 6		  //一帧数据中的固定字节数
#define UART_LENGTH_POSITION 2  //数组中代表帧长度 的位置

#define UART_REC_OVER_TIME_CNT 	100  //500ms ,字节与字节之间接收超时设置的阈值时间
#define UART_SEND_TIMES 		3          //一个数据帧发送次数
#define UART_SAME_DATA_INTERVAL_TIME 10 	//同一帧多次发送的间隔时间
#define UART_SEND_FINISH_INTERVAL_TIME 50   //发送完成间隔时间
#define ACK_OVERTIME_TIME 200	 	 		//ACK应答超时时间
#define UART_ACK_RESEND_TIMES 3		 		//限制的重发次数

#define STARTMARK 0x41 			//起始符//0xAA  
#define ENDMARK   0x55      	//0x55  //结束符
#define ENDMARK_SIZE 1			//结束符个数
#define CRC_CODE_SIZE 2			//CRC校验码个数
/* 
	1byte起始符  1byte类型(最高位预留,剩下7位为数据类型)  1byte数据长度   nbyte数据    2byte校验码  1byte结束符
	((type & 0x7F) == UartEventType)
	数据长度 = nbyte + 6byte
	CRC校验参数模型:CRC-16/MODBUS  x16+x15+x2+1
*/
//能使用堆空间时,初始化使用malloc();创建堆空间
typedef struct Queue 			//头删,尾插
{
	int MaxSize;				//最大个数
	int head;        			//队头
	int fail;					//队尾
	unsigned char (*DataBuf)[UART_BUF_SIZE]; //队列缓冲区
}UartQueue;

extern uchar Gu8RecStep;				//接收步骤
//extern uchar Gu8UartRecByteSum;  		//接收字节数
extern uchar Gu8UartRecFeedDog;     	//喂狗,字节与字节之间的接收时间是否超时
extern uchar Gu8UartRecQueueBufScriptNub;//接收内存下标

extern uchar UartRecQueueBuf[UART_REC_BUF_GROUP][UART_BUF_SIZE];//缓冲区
extern uchar UartSendQueueBuf[UART_SEND_BUF_GROUP][UART_BUF_SIZE]; 
extern uchar UartSendBuf[UART_BUF_SIZE];	

extern UartQueue UartRecQueueInfo;	//接收队列
extern UartQueue UartSendQueueInfo; //发送队列

//=========================================================
typedef enum //串口接收或发送的第二个字节为当前数据帧的类型            
{       
    UART_FET,			//自定义
	UART_CONTROL,	
    UART_ALARM,			
	UART_CONFIG,		
	UART_ACK,			//应答类型
}UartEventType;
//====================================
void UartQueueAdd(UartQueue *UartQueue,uchar info[]);   //入队
void UartSendData_Task(UartQueue *UartQueue);			  
void UartRecDataHandle_Task(UartQueue *UartQueue); 	
uint UartCrcCheck(uchar *buf,uchar lenth);				         //CRC校验
//======================UserDemo
/*使用时,先用Handle处理数据,再用SendData发送SendBuf*/
extern void UartQueueInit(UartQueue *UartQueue,uchar (*Buf)[],bit Type);  //初始化队列,并添加一个队列
extern void UartSendDataHandle(const uchar *DataBuf,uchar DataBufLength,UartEventType DataType,uchar *SendBuf);  //制作数据帧
extern void UartSendData(uchar SendBuf[]);
extern void Uart_Task(void); 		//1ms软件定时器中

串口发送

unsigned char InitBuf[] = {"Init"};	//你要发送的原始数据
//处理数据,添加数据类型、长度等信息,若在Linux下,添加fd描述符
UartSendDataHandle(InitBuf,sizeof(InitBuf)-1,UART_CONFIG,UartSendBuf);
UartSendData(UartSendBuf);//发送数据,可以和第二步整合在一起

串口接收

//在Uart中断服务函数中
temp = UART_BUF; 		  //不同内核sfr名字不一定相同
	
Gu8UartRecFeedDog = 1; 	  //检测字节与字节之间接收是否超时 
switch(Gu8RecStep)
{
	case 0:
		if(temp == STARTMARK)
		{
			UartRecQueueInfo.DataBuf[UartRecQueueInfo.fail][Gu8UartRecQueueBufScriptNub++] = temp;  
			Gu8RecStep++;
		}
	break;
		
	case 1:
		if(Gu8UartRecQueueBufScriptNub <= UART_BUF_SIZE) 
		{
			
			UartRecQueueInfo.DataBuf[UartRecQueueInfo.fail][Gu8UartRecQueueBufScriptNub++] = temp;				
			//结束符和[2]位的长度同时判断	
			if((UartRecQueueInfo.DataBuf[UartRecQueueInfo.fail][Gu8UartRecQueueBufScriptNub - 1 ] == ENDMARK) && (UartRecQueueInfo.DataBuf[UartRecQueueInfo.fail][UART_LENGTH_POSITION] == Gu8UartRecQueueBufScriptNub)) 
			{
				UartRecQueueInfo.fail = (UartRecQueueInfo.fail+1)%UartRecQueueInfo.MaxSize; //入队
				
				Gu8RecStep = 0;		
				Gu8UartRecQueueBufScriptNub = 0;
			}
		}
		else
		{
			Gu8RecStep = 0;
			Gu8UartRecQueueBufScriptNub = 0;
		}
		break;
	default:
		break;
}

使用注意点

/*
1. 发送和接收的数据存在循环队列中,判断队列是否为空作为发送条件或处理条件,1ms定时器中轮询;
2. 发送的次数可变,同一数据的发送间隔时间和不同数据的发送间隔时间可调;
3. 接收数据设置超时丢弃数据,超时时间,发送无应答时,超时重发,超过一定次数上报。
4. 数据发送协议发生变化时,需要修改部分代码,一般是数字 和 buf[1]&0x7F;
5. 不校验结束标志ENDMARK;
6. 不能在uart中断服务函数中直接入队,可能出现同时修改同一个buf的情况,
		在rtos下需要添加互斥锁保护此buf;
7. 若接受数据频繁,将接收任务设置高优先级,或者在Main函数中直接处理以达到高处理速度
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
在SYS/BIOS下,DSP的串口(UART)接收可以通过环形队列来实现。 环形队列是一种常见的数据结构,它可以实现在固定尺寸的数组中循环存储数据。在串口接收中使用环形队列,可以按照接收到的数据顺序将其存储在队列中,并且可以循环利用已存储的空间。 在使用环形队列实现串口接收时,首先需要定义一个固定尺寸的数组作为队列,以及两个指针:一个指向队列头部(读取数据的位置),一个指向队列尾部(存储新数据的位置)。初始时,两个指针指向同一个位置。 当串口接收到新数据时,将该数据存储在队列的尾部,并将尾部指针后移一位。如果尾部指针达到队列的末尾,则将其重置为队列的起始位置,即实现了循环存储。同时,可以对接收的数据进行相关处理,比如检查是否接收完整的数据包或者进行数据解析等。 在读取数据时,从队列的头部开始读取,并将头部指针后移一位。如果头部指针达到队列的末尾,则将其重置为队列的起始位置,即实现了循环读取。 通过使用环形队列,可以解决串口接收中的数据丢失问题,因为即使接收速度过快导致数据暂时无法处理,新的数据仍然可以存储在队列中,等到处理能力跟上之后再进行处理。 同时,可以通过设定合适的队列尺寸来平衡内存占用和数据处理的能力,确保队列不会溢出或浪费内存。 总结而言,在SYS/BIOS下,使用环形队列可以实现串口接收的一种有效方式,可以解决数据丢失的问题,并提供灵活的数据存储和读取方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MECHT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值