目录
快速搭建嵌入式传感器通信协议框架
在嵌入式开发中,尤其是传感器和通信模块的开发中,构建一个高效、可靠的通信框架是非常重要的。一个良好的通信框架不仅能保证数据的正确传输,还能简化代码的维护和扩展。本文总结了适用于大部分嵌入式传感器开发的通信框架,着重于数据结构定义、数据组装、读取和处理的过程。
如下为某传感器开发中的,传感器通过通信接口返回的数据帧结构:
1. 数据结构体定义
首先,我们需要为通信定义一个通用的数据结构体。该结构体将包含所有传输的数据字段,并且设计成灵活、扩展性强的格式,能够适应不同类型的数据。
数据结构体定义:
typedef struct {
uint8_t startByte; // 帧的起始字节
uint8_t length; // 数据长度域
uint16_t command; // 命令字段
uint8_t data[128]; // 数据域,存储实际数据内容
uint8_t checksum; // 校验和,保证数据传输的完整性
} CommunicationFrame;
说明:
startByte
:通常是一个固定的起始字节,用于标识数据帧的开始。length
:表示数据部分的长度,方便接收端了解接收数据的大小。command
:用来指定数据帧的具体命令或操作类型,可以根据实际需求自定义。data[]
:存储实际的数据内容,通常是传感器读取的原始数据。checksum
:用来校验数据是否完整有效,常用的是异或校验。
2. 数据组装
在嵌入式开发中,传输的数据通常需要根据特定的格式进行组装。这一步骤包括将接收到的原始数据转化为适合传输的格式,并对数据进行校验和计算。
数据组装:
void BuildFrame(CommunicationFrame *frame, uint16_t command, uint8_t *data, uint8_t data_len) {
frame->startByte = 0xFE; // 设置帧的起始字节
frame->length = data_len + 3; // 数据长度包括命令和校验和
frame->command = command; // 设置命令字段
memcpy(frame->data, data, data_len); // 将实际数据存入数据域
// 计算校验和
frame->checksum = CalculateChecksum((uint8_t*)frame, frame->length);
}
说明:
BuildFrame()
函数将输入的命令和数据按照定义好的数据结构组装成完整的数据帧。- 校验和计算确保数据传输时的完整性,可以使用简单的异或校验或更复杂的CRC校验,这里的校验函数采用厂家提供的数据校验方法进行。
3. 数据发送与接收
数据的发送和接收是嵌入式通信中至关重要的部分。在这一步,我们将组装好的数据帧发送出去,并通过串口或其他通信接口进行接收。
其中的comSendBuf、comGetChar是我编写的串口FIFO读写机制,所以对于传感器数据的读取和发送均是在后台进行处理,并不会影响系统的实时性。
数据发送:
// 发送和接收缓冲区
static uint8_t sendBuffer[LORA_MAX_FRAME_SIZE];
static uint16_t sendBufferLen = 0;
void SendData(CommunicationFrame *frame)
{
sendBufferLen = 0;
sendBuffer[sendBufferLen++] = frame->startByte;
sendBuffer[sendBufferLen++] = frame->length;
sendBuffer[sendBufferLen++] = (frame->command >> 8) & 0xFF; // 高字节
sendBuffer[sendBufferLen++] = frame->command & 0xFF; // 低字节
if (frame->length > 0)
{
memcpy(&sendBuffer[sendBufferLen], frame->data, frame->length);
sendBufferLen += frame->length;
}
sendBuffer[sendBufferLen++] = frame->checksum;
// 调用串口发送函数
comSendBuf(COM2, sendBuffer, sendBufferLen);
}
数据接收:
//数据接收缓存区
static uint8_t receiveBuffer[LORA_MAX_FRAME_SIZE];
static uint16_t receiveBufferLen = 0;
#define MAX_FRAME_SIZE 1024 //数据帧长度
void ReceiveData(void)
{
// 检查是否接收中断
uint8_t receive_date = 0;
if(comGetChar(COM2,&receive_date) != 0)
{
uint8_t received_byte = receive_date;
if (receiveBufferLen < LORA_MAX_FRAME_SIZE)
{
receiveBuffer[receiveBufferLen++] = received_byte;
// 检查是否接收到完整帧(以0xFE开始,长度域指示数据长度)
if (receiveBufferLen >= 3)
{
// 至少需要3字节(起始字节、长度、命令)
if (receiveBuffer[0] != FRAME_START_BYTE)
{
// 帧头错误,重置缓冲区
receiveBufferLen = 0;
return;
}
uint8_t expected_length = receiveBuffer[1] + 2; // 命令域 2 + 数据域
if (receiveBufferLen >= (2 + expected_length + 1))
{
// 起始字节 + 长度 + 数据 + 校验和
// 计算校验和
uint8_t calculated_fcs = CalculateChecksum(&receiveBuffer[1], expected_length+1);
uint8_t received_fcs = receiveBuffer[2 + expected_length];
if (calculated_fcs == received_fcs)
{
// 校验通过,构建帧结构体
LoraFrame frame;
frame.startByte = receiveBuffer[0];
frame.length = receiveBuffer[1];
frame.command = (receiveBuffer[2] << 8) | receiveBuffer[3];
if (frame.length - 2> 0)
{
//&receiveBuffer[4] 表示lora接收数据 中的 数据域的起始地址
//frame.length = 数据域长度 = 发送端地址长度 + 接受内容长度
memcpy(frame.data, &receiveBuffer[4], frame.length);
}
frame.checksum = receiveBuffer[2 + expected_length];//数据拼接完成
// 处理接收到的帧 lora完整的接收数据帧
ProcessReceivedFrame(&frame);
}
// 重置接收缓冲区
receiveBufferLen = 0;
}
}
}
else
{
// 缓冲区溢出,重置
receiveBufferLen = 0;
}
}
}
说明:
SendData()
函数将完整的数据帧逐字节通过串口发送出去。需要注意的是,校验和也作为数据的一部分一起发送。ReceiveData()
函数通过c
omGetChar
从串口接收数据缓存区(FIFO)读取一个字节数据,读取的这个字节的数据会被保存到buffer
中,直到接收到完整的消息或达到最大长度。ProcessReceivedFrame()
函数传入来自传感器的、完整的、校验后的数据帧,做后续的应用处理,到这里就完成了对传感器数据的读写。接收到的数据通常需要进行处理和解析。在嵌入式系统中,通常需要验证数据的完整性、解析命令、处理传感器数据并根据结果执行操作。
总结
搭建一个通用的嵌入式传感器通信框架,主要包括以下步骤:
- 数据结构体定义:设计通信的数据结构,确保数据格式规范并易于扩展。
- 数据组装:根据协议要求将数据组装成数据帧,并进行校验和计算。
- 数据发送与接收:实现数据的发送和接收,确保数据能够正确传输。
- 数据处理与解析:接收到的数据需要进行解析、校验和验证,确保数据完整性。
通过这种结构化的框架,嵌入式开发者可以迅速搭建起一个可靠的通信系统,适用于多种传感器和通信模块。系统的模块化设计使得后续扩展和维护更加容易,可以轻松地适应不同的传感器和硬件平台。