uart——串口数据帧处理

前言

在串口上,我们经常要对传过来的数据进行处理与解析。

最经典的就是处理数据帧,数据帧是什么呢?数据帧在我看来就是一连串的数据单元。数据帧定义了一些基本的协议。例如,数据帧一般会定义出帧头、帧尾、校验位、数据位、数据长度、命令字节(CMD)命令。

串口是进行处理串口数据帧的方式:一般我们通过对数据帧的处理判断接受到的数据是否符合协议上的要求,解析到数据帧的格式符合要求则执行我们要执行的操作,否则丢弃这一帧的数据包,等待下一次的数据传输过来。

那么,要怎么判断接受的数据帧是正确的格式呢,我认为可以通过一个字节一个字节轮番接受,并且每个字节的判断。即当帧头接受到时,判断帧头的格式,当帧头正确时就再次接受下一个字节,当接受到的字节判断错误时则丢弃数据包。这就是使用了一个简单的状态机思想去对数据进行解析。

环境:hal库处理数据帧

状态机

那么什么才是状态机呢?在我认为状态机就是一种具有多个状态事件的一种模型。

就拿一个简单的例子来说,在学习单片机时按键是最常见的一种外设。
按键有按下和弹起两种状态,事件上有长按和短按或者其他的一些事件。
在状态机上当我们没有按下按键的时候按键是不是就处于一种弹起状态,反之,当我们按下按键的时候状态是不是就从弹起切换到按下状态。状态是互斥的不可能同时触发。事件也是和状态一样的想法。

在本文中就拿一种简单的状态机思想去处理数据帧。

数据解析代码分析

在本文中处理的数据帧格式为:帧头 + 数据长度 + CMD + 头校验 + 数据位 + 数据校验 + 帧尾的方式进行处理数据帧。

处理数据帧方式:

  • 定义一个枚举类型定义状态机的每一种状态
  • 定义一个结构体存放接受到的数据帧
  • 定义一个数组进行存放接受到的数据帧
  • 定义一个数组下标进行每一位的数据帧的切换读取
  • 通过状态机对接受到每一位的数据帧进行解析

1、状态机的状态

这里定义的状态机的状态就是对每一个字节的判断。即:帧头、帧尾、数据长度等等的状态进行判断。

typedef enum
{
	HEAD_CHECK1,
	HEAD_CHECK2,
	DATA_LENGTH1,
	DATA_LENGTH2,
	CMD1,
	CMD2,
	HEAD_CHECKSUM,
	DATA,
	DATA_CHECKSUM,
	FOOT_CHECK1,
	FOOT_CHECK2,
	FRAME_ERROR,
} parse_state_t;

2、结构体定义存放的数据帧

这里定义了一个结构体就是为了和接收到的数据帧进行判断,无论我们接受到的是什么数据,都将其存入到对应的结构体成员当中。然后对接受到的字节进行判断,当存放的结构体成员和接收到的数据是完全对应上的时候说明接受到的是正确的数据,则进行下一个数据的判断,反之,丢弃所有的数据,对结构体的所有结构体成员进行清空。

typedef struct
{
	uint16_t header; // 帧头
	uint16_t length; // 数据长度
	uint16_t cmd;	 // 命令字
	uint8_t check1;	 // 头校验
	uint8_t *data;	 // 数据指针
	uint8_t check2;	 // 数据校验
	uint16_t footer; // 帧尾
} Data_Frame;

3、定义了一个数组进行存放数据帧

这里就是简单的代码上的hal库对代码的接受处理了,就不多啰嗦了,不明白的可以看看官方的uart的demo例程。

HAL_UART_Receive_IT(&UART_Config, &RX_buf[rx_pos], 1);

4、状态机解析数据

这里就来到重头戏了,对数据帧的解析,解析上我们在串口的中断回调函数上进行处理。即对每一个字节进行判断处理。代码如下:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	uint8_t byta = RX_buf[rx_pos++];
	// static uint8_t rx_state = 0;
	static uint8_t CON = 0;

	switch (parse_state)
	{
	case HEAD_CHECK1:
		if (byta == 0xAA)
		{
			LOG_I("head1");
			parse_state = HEAD_CHECK2;
		}
		else
		{
			parse_state = FRAME_ERROR;
			LOG_I("head1 erroc");
		}
		break;
	case HEAD_CHECK2:
		if (byta == 0x55)
		{
			recv_frame.header = 0xAA55;
			parse_state = DATA_LENGTH1;
			LOG_I("head2");
		}
		else
		{
			parse_state = FRAME_ERROR;
			LOG_I("head2 erroc");
		}
		break;
	case FRAME_ERROR:
	{
		recv_frame.check1 = 0;
		recv_frame.check2 = 0;
		recv_frame.cmd = 0;
		recv_frame.data = NULL;
		recv_frame.footer = 0;
		recv_frame.header = 0;
		recv_frame.length = 0;
		parse_state = HEAD_CHECK1;
		rx_pos = 0;
		HAL_UART_Transmit(&UART_Config, "error\r\n", 7, 1000);
		// RX_buf[BUF_SIZE] = {0};
	}
	break;
	default:
		break;
	}

	HAL_UART_Receive_IT(&UART_Config, &RX_buf[rx_pos], 1);
}

这里就简单的对帧头的处理和接收到错误的数据帧的处理方式。后面就根据自己的数据帧格式进行改动就行了。

  • 3
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

叶同学要努力呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值