串口队列-数据解析

        串口是MCU最常用的外设资源之一,现在市面上很多传感器或者模组的控制方式用到都是串口,这就不得不面对一个很严肃的问题:如果合理使用串口才能尽可能地介绍数据的丢失和解析异常。

        串口数据解析的方法有很多,最简单的办法就是定义一个数组,然后将串口接收到的数据以此填充到这个数组里面,然后再做解析,就像这样:

void USART3_IRQHandler(void)                	//串口3中断服务程序
{
	u8 Res=0;
	Res = USART_ReceiveData(USART3);
	Uart3_Buf[First_Int] = Res;  	  //将接收到的字符串存到缓存中
	First_Int++;                	  //缓存指针向后移动
	if(First_Int >= Buf3_Max)       	  //如果缓存满,将缓存指针指向缓存的首地址
	{
		First_Int = 0;
	}
} 


//寻找字符串
//返回:1  已找到  0  未找到
u8 Find(char *a)
{ 
	if(strstr(Uart3_Buf, a)!=NULL)
	{
		return 1;
	}	
	else
	{
		return 0;
	}
		
}


//发送AT指令
//*b:需要发送的字符串
//*a:查找是否返回的字符串
//wait_time:发送的次数
//interval_time:每次等待的时间
u8 UART3_Send_AT_Command(char *b,char *a,u8 wait_time,u32 interval_time)         
{
	u8 i;
	i = 0;
	while(i < wait_time)    //如果没有找到 就继续再发一次指令 再进行查找目标字符串                
	{
		UART3_Send_Command(b);//串口2发送 b 字符串 他会自动发送\r\n  相当于发送了一个指令
		delay_ms(interval_time); //等待一定时间 传50的话就是 50*20ms = 1秒
		if(Find(a))            //查找需要应答的字符串 a
		{
			return 1;
		}
		i++;
	}
	
	return 0;
}

/* 检测GSM是否可以正常使用 */
int check_status(void)
{
	int ret;
	
	ret = UART3_Send_AT_Command("AT","OK",3,50);//测试通信是否成功
	if(ret == 0)
	{
		return -1;
	}
	
	ret = UART3_Send_AT_Command("AT+CPIN?","READY",3,50);//查询卡是否插上
	if(ret == 0)
	{
		return -2;
	}
	
	ret = UART3_Send_AT_Command("AT+CREG?","0,1",3,50);查询卡是否注册到网络
    
	if(ret == 0)
	{
		return -3;
	}
	return 1;
}

/***************************************************************
发送短信
注:当然你可以返回其他值,来确定到底是哪一步发送指令出现失败了。
****************************************************************/
int send_text_message(char *content,char *phone_num)
{
	u8 ret;
	char end_char[2];
	char String_Phone[21];
	
	end_char[0] = 0x1A;//结束字符
	end_char[1] = '\0';

	
	sprintf(String_Phone,"AT+CMGS=\"%s\"",phone_num); //字符串拼接函数(库函数)

	ret = UART3_Send_AT_Command("AT+CMGF=1","OK",3,50);//配置为TEXT模式
	if(ret == 0)
	{

		return -5;
		
	}
	
	ret = UART3_Send_AT_Command(String_Phone,">",3,50);//输入收信人的电话号码
	if(ret == 0)
	{

		return -7; 
	}
	
	UART3_SendString(content);   //发送提示内容

	ret = UART3_Send_AT_Command_End(end_char,"OK",3,250);//发送结束符,等待返回ok,等待5S发一次,因为短信成功发送的状态时间比较长
	if(ret == 0)
	{

		return -8;
	}
	
	return 1;
}

        上述代码段取自GPRS模块的AT指令串口解析,显然,类似这种,定义一个相对比较大的数据,发送AT指令,等待回复再去解析的方式在这种场合下是足够使用的,但是对于大多数MCU而言,尤其是嵌入式MCU,一般资源都十分有限。前一段时间接到一个项目,与我们协作的另一方MCU工程师使用的MCU RAM一共128个字节,堆栈只给分配了16个字节,已经到了一个字节一个字节要谨慎使用的地步,这个时候,要是再定义一个比较大的数组,显然是不可取的。那应该如何在定义一个小的数组的情况下,还能保证数据尽量不丢失呢(当然这也是相对的)?这里需要扯到一个数据结构里面的内容------队列。有关队列的学习和说明,在之前的博文中已经提到:

队列

这里使用的是环形队列(顺序队列),其基本思想和队列是一致的。需要说明的是,重要的思维方式,代码只是帮助理解:

1.首先,定义一个结构体,当然,你也可以直接像我队列里面举例那样处理,显然相对而言会更加节约空间一点:

#define UART_DATA_MAX 20

typedef struct 
{
    uint8_t tail;   /* 队列尾,入队时需要自加 */
    uint8_t head;   /* 队列头,出队时需要自减 */
    uint8_t uart_length;  /* 保存的是当前队列有效数据的长度 */
    uint8_t recv_buf[UART_DATA_MAX];  /* 数据缓冲区 */
}uart_ring_buffer_t;

2.然后初始化这个队列:

static uart_ring_buffer_t ring_buffer;
void uart_ring_buffer_init(void)
{
    ring_buffer.head = 0;
    ring_buffer.tail = 0;
    ring_buffer.uart_length = 0;
    memset(ring_buffer.recv_buf, 0, sizeof(ring_buffer.recv_buf));
}

3.接下来就是对队列的读写:

/* 读队列 */
bool read_uart_ring_buffer(uint8_t *data)
{
    if(ring_buffer.uart_length == 0)
    {
        return false;
    }        
    *data = ring_buffer.recv_buf[ring_buffer.head];
    ring_buffer.recv_buf[ring_buffer.head] = 0;
    ring_buffer.head = (ring_buffer.head++) % UART_DATA_MAX;  /* 防止数组溢出 */
    ring_buffer.uart_length--;
    return true;
}

/* 往队列里面写 */
bool write_uart_ring_buffer(uint8_t data)
{
    if(ring_buffer.uart_length >= UART_DATA_MAX)
    {
        return false;
    }
    ring_buffer.recv_buf[ring_buffer.tail] = data;
    ring_buffer.tail = (ring_buffer.tail++) % UART_DATA_MAX; /* 防止数组溢出 */
    ring_buffer.uart_length++;
    return true;
}

/* 获取当前队列有效数据(未处理数据)长度 */
uint8_t get_uart_ring_buffer(void)
{
    return ring_buffer.uart_length;
}

4.数据协议解析(我的串口数据协议格式为AA 03 命令码(1byte) 有效数据(1byte) 校验和(前两个字节的算术和)):

/*
	串口0中断服务函数
*/
uint8_t ready_rx_flag = 0,rx_complete_cmd_flag = 0;
void	UART0_Handler(void)
{
	uint32_t	iWK = 0 ;
	uint8_t		tWK = 0 ;
	uint8_t data_check_result = 0;

	__read_hw_reg32(UART0_IIR , iWK);
	iWK &= 0x0F;

	if((iWK != 0x04) && (iWK != 0x0c)) return;
	
	__read_hw_reg32(UART0_RBR , tWK);
	
	
    write_uart_ring_buffer(tWK);	
}


int main(void)
{
    uint8_t check_sum = 0;
    uint8_t handler_buf[5] = {0};
    uart_init();
	uart_ring_buffer_init();
    while(1)
    {
        
        if(read_uart_ring_buffer(&data))    /* 串口数据接收后处理 */        
        {
            switch(recv_flag)
            {
                case 0:
                    if(data == 0xAA)
                    {
                        handler_buf[0] = data;
                        recv_flag = 1;
                    }
                    else
                    {
                        recv_flag = 0;
                    }
                    break;
                case 1:
                    if(data == 0x03)
                    {
                        handler_buf[1] = data;
                        recv_flag = 2;
                    }
                    else
                    {
                        recv_flag = 0;
                    }
                    break;
                case 2:
                    handler_buf[2] = data;
                    recv_flag = 3;
                    break;
                case 3:       
                    handler_buf[3] = data;
                    recv_flag = 4;
                    break;
                case 4:
                    handler_buf[4] = data;
                    check_sum = (handler_buf[2] + handler_buf[3]) & 0x0FF;
                    if(check_sum == handler_buf[4])
                    {
                        uart_data_handle(handler_buf,5);   /* 数据处理 */                                                                                                                                                               
                        recv_flag = 0;
                        memset(handler_buf, 0, sizeof(handler_buf));
                    }
                    break;                    
            }
            
        }
		
    }

}

       事实上,这样处理要保证数据不丢失,还得保证处理器的处理速度足够快才行,也就是要保证,数据解析的速度跟上数据接收的速度。如果你的MCU处理速度不够快,也可以定义一个相对较大的队列长度,这样,来不及处理的数据可以先暂时保存在这个队列里面,以空间换取时间。

  • 14
    点赞
  • 112
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
串口数据解析函数的具体实现方式取决于具体的通信协议和数据格式。以下是一个简单的串口数据解析函数的示例代码: ```c #include <stdint.h> typedef enum { PACKET_STATE_IDLE, PACKET_STATE_HEADER, PACKET_STATE_LENGTH, PACKET_STATE_DATA, PACKET_STATE_CHECKSUM } PacketState; typedef struct { uint8_t header[2]; uint8_t length; uint8_t data[256]; uint8_t checksum; PacketState state; } Packet; Packet packet; uint8_t packetBuffer[260]; uint8_t bufferIndex = 0; void parsePacket(uint8_t byte) { switch (packet.state) { case PACKET_STATE_IDLE: if (byte == 0xAA) { packet.state = PACKET_STATE_HEADER; packet.header[0] = byte; } break; case PACKET_STATE_HEADER: if (byte == 0x55) { packet.state = PACKET_STATE_LENGTH; packet.header[1] = byte; } else { packet.state = PACKET_STATE_IDLE; } break; case PACKET_STATE_LENGTH: packet.length = byte; if (packet.length > 0) { packet.state = PACKET_STATE_DATA; } else { packet.state = PACKET_STATE_CHECKSUM; } break; case PACKET_STATE_DATA: packet.data[bufferIndex++] = byte; if (bufferIndex >= packet.length) { packet.state = PACKET_STATE_CHECKSUM; } break; case PACKET_STATE_CHECKSUM: packet.checksum = byte; uint8_t calculatedChecksum = packet.header[0] + packet.header[1] + packet.length; for (int i = 0; i < packet.length; i++) { calculatedChecksum += packet.data[i]; } if (calculatedChecksum == packet.checksum) { // 数据校验通过,进行处理 processData(packet.data, packet.length); } packet.state = PACKET_STATE_IDLE; bufferIndex = 0; break; default: break; } } ``` 该函数用于解析一个简单的通信协议,其中数据包的格式为:包头 0xAA 0x55,数据长度,数据内容,校验和。具体的解析过程如下: 1. 初始化一个 Packet 结构体,用于存储解析出来的数据包内容。 2. 定义一个状态机,用于记录当前解析器的状态。初始状态为 PACKET_STATE_IDLE。 3. 接收到一个字节时,依据当前的状态进行相应的处理。 4. 如果当前状态为 PACKET_STATE_IDLE,说明正在等待接收数据包的包头,如果接收到的字节是 0xAA,说明接下来应该接收到 0x55,进入 PACKET_STATE_HEADER 状态,否则保持 PACKET_STATE_IDLE 状态。 5. 如果当前状态为 PACKET_STATE_HEADER,说明正在接收数据包的包头,如果接收到的字节是 0x55,说明包头接收完成,进入 PACKET_STATE_LENGTH 状态,否则回到 PACKET_STATE_IDLE 状态。 6. 如果当前状态为 PACKET_STATE_LENGTH,说明正在接收数据包的长度字段,接收到的字节即为数据长度,保存之后进入 PACKET_STATE_DATA 状态。如果接收到的长度为 0,则说明数据包中没有数据,直接进入 PACKET_STATE_CHECKSUM 状态。 7. 如果当前状态为 PACKET_STATE_DATA,说明正在接收数据包的数据内容,将接收到的字节存储到 packet.data 数组中,直到接收完所有数据,进入 PACKET_STATE_CHECKSUM 状态。 8. 如果当前状态为 PACKET_STATE_CHECKSUM,说明正在接收数据包的校验和,将接收到的字节保存到 packet.checksum 变量中,然后计算数据包的校验和,如果计算出来的校验和与接收到的校验和一致,则说明数据包接收完整,进行处理,否则丢弃该数据包。最后回到 PACKET_STATE_IDLE 状态,等待接收下一个数据包。 9. 如果当前状态为其他状态,则保持当前状态不变,等待下一个字节的到来。 需要注意的是,由于串口数据的接收是异步的过程,因此在实际使用中需要考虑多个数据包同时到达的情况,可以使用队列数据结构进行处理。同时还需要根据具体的需求进行修改和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

all of the time

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

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

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

打赏作者

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

抵扣说明:

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

余额充值