单片机串口——队列的使用

背景

  最近做单片机开发经常遇见要用串口接收数据的情况,实际项目中肯定不能当串口接收中断一来就去处理,于是我们可以用到队列这个数据结构来保存上一帧数据,想用的时候取出即可。

开始

  1. 新建队列结构体
#define BUFFER_MAX 20		//队列缓冲区大小,根据实际情况来定

typedef struct uart_queue{
    unsigned char head;						//队列头指针
    unsigned char tail;						//队列尾指针
    unsigned char buffer[BUFFER_MAX];		//队列缓存数组
	unsigned char RXReceiving_Flag;			//正在接收标志
	unsigned char RXFinish_Flag;			//一帧数据接收完成标志
	unsigned char RXTimer_Cnt;				//字节接收间隔计时
	unsigned char RXLength;					//接收自己总长度
}UART_QueueTypeDef;

UART_QueueTypeDef uart0_queue;	//新建一个队列结构体

接收机制说明:

  当数据入队后,队列尾指针+1,当数据出队后,队列头指针+1;

  当一帧数据完成后,RXLength记录长度,用户可以取出相应数据,进行处理。

  1. 建立出/入队函数
//入队函数
unsigned char uart_enqueue(const unsigned char _buf)
{
    //如果尾指针小于最大缓存,表示还可以入队
    if(uart0_queue.tail < BUFFER_MAX)
    {
        uart0_queue.buffer[uart0_queue.tail++] = _buf;
        uart0_queue.RXLength++;		//入队后数据长度加1
        return 1;					//入队成功
    }
    else
    {
        return 0;					//入队失败
    }
}

//出队函数
unsigned char uart_dequeue(unsigned char * _buf)
{
    //如果头指针不等于尾指针,表示还是有数据可以出队
    if(uart0_queue.head != uart0_queue.tail)	
    {
        *_buf = uart0_queue.buffer[uart0_queue.head++];		//从数组中获取数据
        uart0_queue.RXLength--;								//入队后数据长度减1
        return 1;		//出队成功
    }
    else
    {
        return 0;		//出队失败
    }
}
  1. 队列初始化函数
//将队列全部设为0
void reset_uart_queue(void)
{
    unsigned char i;
    
    for(i=0; i<BUFFER_MAX; i++)
    {
        uart0_queue.buffer[i] = 0x00;
    }
    uart0_queue.head = 0;
    uart0_queue.tail = 0;    
    uart0_queue.RXLength = 0;
}
  1. 指定个数数据取出函数
//从串口缓冲队列取出指定个数数据
unsigned char POP_uart_queue(unsigned char * _data,unsigned char num)
{
    unsigned char byte = 0;
    unsigned char count = 0;
    
    while((count < num) && (uart_dequeue(&byte)))	//当长度小于num且出队成功
    {
        _data[count++] = byte;						//保存数据到数组中
    }
    
    if(uart0_queue.head != num)
        return 0;		//接收失败
    else
        return 1;		//接收成功
}
  1. 接收机制

  现在我们来理一理接收机制,当串口接收到数据之后,会产生中断,此时我们得立马取出数据,接收下一个,当一字节接收完之后,5ms内无下一字节视为一帧数据接收完成

  • 原理:假设串口波特率为9600bps,一个起始位,一个停止位,8个数据位,无校验位,总共10位数据,发送一个字节次所花时间为:(10/9600)*1000 = 1.41ms,这个意思就是在一帧数据内,字节与字节接收时间不超过1.41ms,我们暂且扩大为5ms。

  我们定义一个接收完成标志***RXFinish_Flag***,接收完一帧数据后,我们将这个标志置位1,取出后复位0,在函数主任务内轮询监控此标志状态,为1则取出数据并做出相应动作。

  期间需要计时,要用到定时器,我们先设置定时器0中断为1ms一次,定义变量***RXTimer_Cnt***来计时,

//如果正在接收,判断数据接收时间是否大于5ms
if(uart0_queue.RXReceiving_Flag)
{
    if(uart0_queue.RXTimer_Cnt++>=5)		//大于5ms表示一帧数据接收完成
    {
        uart0_queue.RXFinish_Flag = 0x01;	//接收完成标志置1
        uart0_queue.RXReceiving_Flag=0;		//正在接收标志置0
    }
}
  1. 串口中断处理
void RxIntCallback(void)
{
		unsigned char _buf;
		_buf = M0P_UART1->SBUF;				//保存uart1接收到的一个字节
    	
		//如果已经接收完一帧数据,接收完成标志还为1,说明上一帧数据未处理;
		//再次来数据表示是下一帧的,就清空上一帧,只要最新一帧数据;
		if(uart0_queue.RXFinish_Flag == 1)	
		{
			reset_uart_queue();				//初始化队列参数
		}
		uart0_queue.RXReceiving_Flag=1;		//正在接收
		uart_enqueue(_buf);					//数据入队
		uart0_queue.RXTimer_Cnt=0;			//连续接收计时清零
		
}
  1. 数据处理

主函数中采用轮询的方式查询是否接收完成一帧数据,如果接收完成则做出相应处理,此处我是把数据原封不动返回。

//新建静态数组来保存接收到的数据
static uint8_t uart_testarr[BUFFER_MAX] = {0};

while(1)
{
    if(uart0_queue.RXFinish_Flag == 1)		//如果接收完成标志为1,表示接收完成
    {
        unsigned char uart1_rx_length=0;	//定义变量来保存接收长度
        uart0_queue.RXFinish_Flag=0;		//清空接收完成标志
        uart1_rx_length = uart0_queue.RXLength;				//保存长度
        POP_uart_queue(uart_testarr, uart1_rx_length);		//取出数据
        my_uartSendString(uart_testarr, uart1_rx_length);	//串口发送数据
        reset_uart_queue();					//初始化队列参数
    }
}

你可能感兴趣的:(单片机串口——队列的使用)