《基于STM32duino的CRSF协议实现:ELRS接收机遥控通道数据解析指南》
本文详细介绍了如何在STM32duino平台上实现CRSF(Crossfire)协议,以解析来自ELRS(ExpressLRS)接收机的遥控通道数据。CRSF协议是一种用于遥控器和飞控之间低延迟、双向通信的协议,广泛应用于FPV无人机等遥控飞行器中。
文档首先概述了CRSF协议的特性和数据包结构,然后提供了一个完整的代码实现,包括串口初始化、CRC校验、数据包解析等核心功能。代码采用模块化设计,易于理解和移植。
本指南不仅展示了如何正确解码16个遥控通道的11位数据,还详细解释了每个函数的作用和实现逻辑。此外,文档还提供了测试验证方法和潜在的应用扩展建议,使读者能够将这一实现应用到实际的飞控开发项目中。
1. CRSF协议简介
CRSF(Crossfire)协议是一种用于遥控器和飞控之间低延迟、双向通信的协议,广泛应用于遥控飞行器(例如FPV无人机)。CRSF协议具备以下特性:
- 低延迟、高速数据传输:CRSF具有高达420,000bps的波特率,确保了遥控器和飞控之间的低延迟。
- 支持16个通道数据传输:每个通道的数据被压缩成11位,可以传输16个通道的信息。
- 双向通信:可以传输飞行控制器的状态数据(如GPS数据、链路状态)到遥控器,同时也传输遥控通道数据到飞控。
2. 硬件串口和波特率配置
在STM32duino中,通过硬件串口(例如 Serial2
)实现与飞控的通信。CRSF协议要求串口波特率设置为 420,000bps。串口的其他配置为:8数据位、无校验位、1停止位(8N1),这是标准的串行通信设置。
代码中的initCRSFSerial()
函数负责初始化串口通信:
// 初始化串口,波特率设置为CRSF协议要求的420000
void initCRSFSerial() {
crsfSerial.begin(CRSF_BAUDRATE);
}
该函数调用了STM32duino的串口库,设置波特率为420,000bps。在STM32中,硬件串口可以根据项目需求选择不同的Serial
端口,例如Serial1
, Serial2
等。
3. CRSF 数据包结构
CRSF协议的数据包结构具有以下特征:
字节 | 内容 | 说明 |
---|---|---|
0 | 设备地址 (device_addr ) | 通常为飞控的地址0xC8 |
1 | 帧长度 (frame_size ) | 表示数据帧的总长度,RC数据帧为22字节 |
2 | 类型 (type ) | 表示数据帧的类型,RC数据的类型为0x16 |
3-24 | 数据 (data ) | 实际的16个通道数据,每个通道用11位表示 |
25 | CRC校验 | 校验整个数据帧的完整性 |
数据部分说明
数据部分存储了16个通道的值,每个通道值使用11位来表示。通道的值范围通常为 1000到2000,对应于舵机的标准PWM脉宽。这些11位数据被紧密打包到22字节中进行传输。
例如:
- 通道1: 11位(例如
1001 1111 1111
,共2047) - 通道2: 11位,紧接着通道1
- …
数据的解码与编码需要考虑到每个通道数据是连续的11位,并需要适当的移位和掩码操作。
4. CRC校验
CRSF协议使用 CRC-8 校验算法,校验值附加在数据包的最后。CRC的作用是确保数据在传输过程中没有被损坏。CRSF协议使用的多项式是 0xD5。
在代码中,calcCRC()
函数负责计算校验值:
// 计算CRC-8校验值,使用多项式0xD5
uint8_t calcCRC(uint8_t *data, uint8_t len) {
uint8_t crc = 0;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80)
crc = (crc << 1) ^ 0xD5;
else
crc = crc << 1;
}
}
return crc;
}
- 该函数接收一个数据数组和其长度,使用多项式
0xD5
来迭代计算校验值。 - 校验值将被附加到数据包的末尾,作为最后一个字节。
5. 打包和发送CRSF数据包
sendCRSFFrame()
函数负责打包16个通道的数据,并通过串口发送。打包过程涉及到将16个通道的11位数据依次存储到22字节的缓冲区中。
void sendCRSFFrame() {
uint8_t crsfData[CRSF_FRAME_SIZE]; // 创建一个数组来存储数据帧
// 数据帧头部
crsfData[0] = CRSF_ADDRESS_FLIGHT_CONTROLLER; // 飞控地址
crsfData[1] = 22; // 数据长度:22字节
crsfData[2] = CRSF_FRAMETYPE_RC_CHANNELS_PACKED; // 数据帧类型:遥控通道数据
// 将16个11位通道数据打包到数据帧中
uint32_t bitBuffer = 0;
uint8_t bitsInBuffer = 0;
int offset = 3; // 从第3个字节开始存放通道数据
for (int i = 0; i < CRSF_MAX_CHANNEL; i++) {
bitBuffer |= ((uint32_t)rcChannels[i] & 0x07FF) << bitsInBuffer; // 每个通道取11位
bitsInBuffer += 11;
while (bitsInBuffer >= 8) {
crsfData[offset++] = bitBuffer & 0xFF;
bitBuffer >>= 8;
bitsInBuffer -= 8;
}
}
// 计算CRC校验值,校验从数据帧的第2字节开始计算,总共22字节
uint8_t crc = calcCRC(&crsfData[2], 22);
crsfData[offset++] = crc; // 将CRC校验字节加到数据帧末尾
// 通过硬件串口发送打包后的数据帧
crsfSerial.write(crsfData, offset);
}
该函数的工作流程为:
- 帧头设置:将设备地址、帧长度和数据类型存储在数据帧的前三个字节。
- 打包16个通道数据:每个通道占用11位,因此需要通过移位和掩码操作将其压缩存储到数据帧的22字节中。
- 计算CRC校验:调用
calcCRC()
函数计算从第2字节到第24字节的CRC值,并将其附加到数据帧末尾。 - 发送数据包:使用
crsfSerial.write()
通过硬件串口将完整数据包发送出去。
6. 主程序流程
setup()
函数初始化硬件串口,loop()
函数则定期更新通道数据并调用sendCRSFFrame()
发送数据包:
void loop() {
// 在此处可以动态更新各个通道的数据,例如:
rcChannels[0] = 1500; // 通道1:中间位置
rcChannels[1] = 1000; // 通道2:最小值
rcChannels[2] = 2000; // 通道3:最大值
rcChannels[3] = 1500; // 通道4:中间位置
// 可以继续设置其他通道的数据...
// 每100ms发送一次CRSF数据包
sendCRSFFrame();
delay(100);
}
void setup() {
// 初始化CRSF串口
initCRSFSerial();
}
setup()
函数确保在系统启动时配置串口,而loop()
函数可以根据实际的遥控输入或飞控状态来更新各个通道的数据。然后,每隔100毫秒调用sendCRSFFrame()
函数,发送更新后的CRSF数据包。
7. 模块化与移植
由于代码模块化设计,函数如initCRSFSerial()
、calcCRC()
和sendCRSFFrame()
可以很方便地移植到其他项目中。你只需修改串口配置和通道数据的来源,就可以将此代码嵌入到你的飞控开发环境中。
- 串口选择:根据硬件情况,可以将
crsfSerial
修改为任意可用的硬件串口(例如Serial1
,Serial2
)。
在 CRSF 协议中,除了遥控通道数据包(RC Channels Packed
)外,还有多种其他类型的数据包,它们用于传递设备状态、传感器数据以及控制命令等。下面详细介绍这些不同的数据包类型、它们的内容及使用场景。
1. GPS 数据包(Frame Type: 0x02
)
描述:该数据包用于传递GPS模块的位置信息,如飞行器的经纬度、高度和速度等数据。
数据包格式:
字段 | 大小 | 内容 |
---|---|---|
Latitude | 4字节 | 纬度,单位为 1/10,000,000 度 |
Longitude | 4字节 | 经度,单位为 1/10,000,000 度 |
Ground Speed | 2字节 | 地速,单位为 1/100 公里/小时 |
GPS Heading | 2字节 | 航向,单位为 1/100 度 |
Altitude | 2字节 | 海拔高度,单位为米,偏移1000米 |
Satellites in use | 1字节 | 当前使用的卫星数量 |
使用场景:在无人机飞行中,GPS数据包用于将当前飞行器的定位信息传送给遥控器或地面站。通过这些数据,操作员可以实时查看飞行器的位置信息。
2. 电池状态数据包(Frame Type: 0x08
)
描述:该数据包用于传递飞行器电池的电压、电流、容量以及电量剩余情况。
数据包格式:
字段 | 大小 | 内容 |
---|---|---|
Voltage | 2字节 | 电压,单位为毫伏(mV) |
Current | 2字节 | 电流,单位为毫安(mA) |
Capacity | 3字节 | 剩余电池容量,单位为毫安时(mAh) |
Battery Remaining | 1字节 | 电池剩余电量,百分比表示 |
使用场景:电池状态数据包对于无人机操作员非常重要。它提供了电池的实时状态,确保操作员能够及时了解电量并进行合理的电池管理,避免因为电池耗尽导致无人机失控或坠落。
3. 链路统计数据包(Frame Type: 0x14
)
描述:该数据包提供了当前遥控器与飞控之间的通信链路的质量信息,包括信号强度、SNR(信噪比)、数据包丢失率等。
数据包格式:
字段 | 大小 | 内容 |
---|---|---|
Uplink RSSI Ant. 1 | 1字节 | 上行RSSI信号强度(天线1),单位为dBm |
Uplink RSSI Ant. 2 | 1字节 | 上行RSSI信号强度(天线2),单位为dBm |
Uplink Packet Success Rate | 1字节 | 上行数据包成功率,单位为百分比 |
Uplink SNR | 1字节 | 上行信噪比,单位为dB |
Diversity Active Antenna | 1字节 | 当前激活的天线编号 |
RF Mode | 1字节 | 当前射频模式 |
Uplink TX Power | 1字节 | 上行发射功率,单位为mW |
Downlink RSSI | 1字节 | 下行RSSI信号强度,单位为dBm |
Downlink Packet Success Rate | 1字节 | 下行数据包成功率,单位为百分比 |
Downlink SNR | 1字节 | 下行信噪比,单位为dB |
使用场景:链路统计数据包用于监控飞控与遥控器之间的通信质量。这对于远程控制飞行器时尤为重要,操作员可以实时掌握通信链路的质量,从而判断是否需要调整天线方向或增加发射功率。
4. 姿态数据包(Frame Type: 0x1E
)
描述:姿态数据包传递飞行器的航向、俯仰角和滚转角,这些数据通常用于在地面站显示飞行器的姿态状态。
数据包格式:
字段 | 大小 | 内容 |
---|---|---|
Pitch | 2字节 | 俯仰角,单位为弧度/10000 |
Roll | 2字节 | 滚转角,单位为弧度/10000 |
Yaw | 2字节 | 航向角,单位为弧度/10000 |
使用场景:姿态数据包通常被用于飞行控制系统和地面站软件中,用于显示飞行器的实时姿态信息。操作员可以通过该数据包在地面站的界面上实时查看飞行器的姿态变化情况。
5. 飞行模式文本数据包(Frame Type: 0x21
)
描述:飞行模式数据包传递当前飞行器的飞行模式,例如“姿态模式”(Angle Mode)、“全手动模式”(Acro Mode)等。
数据包格式:
字段 | 大小 | 内容 |
---|---|---|
Flight Mode | 可变长度 | 飞行模式名称,空终止的字符串 |
使用场景:通过该数据包,遥控器或地面站可以显示当前飞行器的飞行模式,这对于操作员切换飞行模式时非常有用。例如,在无人机的FPV飞行中,操作者可以在遥控器的屏幕上看到飞行模式的变化情况。
6. OSD 命令数据包(Frame Type: 0x05
)
描述:OSD(On-Screen Display)数据包用于控制飞控的屏显功能,例如调整显示内容、切换不同的信息页面。
数据包格式:
字段 | 大小 | 内容 |
---|---|---|
Buttons | 1字节 | 按钮的按下状态,按位表示各个按钮的状态(例如:位7=Enter,位6=向上,位5=向下) |
使用场景:该数据包用于在飞控系统的OSD中传递按钮状态,控制不同的显示内容。例如,可以通过遥控器上的按钮切换飞行器的电压显示、GPS信息显示等。
7. 指令数据包(Frame Type: 0x32
)
描述:该数据包用于发送特定的飞控指令,例如强制解锁、通道缩放、蓝牙复位等。
数据包格式:
字段 | 大小 | 内容 |
---|---|---|
Command ID | 1字节 | 指令编号 |
Command Payload | 可变 | 依据指令不同,数据内容不同 |
常用指令:
- 强制解锁 (
Force Disarm
):用于紧急停止电机。 - 通道缩放 (
Scale Channel
):调整遥控通道的输出范围。
使用场景:指令数据包用于与飞控系统的交互,发送特定的控制命令。例如,当需要通过遥控器执行紧急停止时,可以发送解锁命令。
结论
CRSF协议提供了丰富的功能,不仅仅局限于传递遥控通道数据。通过其他类型的数据包,如GPS数据包、电池状态数据包、链路统计数据包等,操作者可以实时掌握飞行器的状态,并根据这些信息做出调整。在飞控开发中,理解并实现这些数据包的正确处理,是确保飞行器稳定性和安全性的关键。
详细说明文档:在 STM32duino 上实现 CRSF 协议的模块化代码
1. 背景与目标
CRSF(Crossfire)协议被广泛应用于遥控飞行器(如无人机)的通信系统中,用于遥控器和飞控之间的低延迟、高速数据传输。其主要特性包括:
- 支持16个通道数据传输
- 双向通信,支持实时反馈和控制
- 高速通信,波特率为 420,000bps
本文档的目标是提供一个模块化、易于移植的STM32duino实现,以便通过硬件串口发送 CRSF 数据包,适用于飞控开发。
2. 系统配置与硬件连接
硬件平台:STM32 微控制器使用硬件串口与飞控系统进行通信。
串口配置:
- 波特率:420,000 bps
- 数据位:8位
- 停止位:1位
- 校验位:无
配置函数 initCRSFSerial()
负责初始化串口:
void initCRSFSerial() {
crsfSerial.begin(CRSF_BAUDRATE); // 设置波特率为420000
}
在STM32平台上,可以根据具体的硬件选择不同的串口(如 Serial1
, Serial2
等)。
3. CRSF 协议数据包结构
CRSF 协议中的遥控数据帧结构如下:
字节 | 内容 | 说明 |
---|---|---|
0 | 设备地址 (device_addr ) | 0xC8 表示发送给飞控 |
1 | 帧长度 (frame_size ) | 数据包长度,RC 数据为22字节 |
2 | 类型 (type ) | 数据类型,0x16 表示遥控通道数据 |
3-24 | 数据 (data ) | 16个通道的11位数据,打包在22字节中 |
25 | CRC 校验 | 数据包的校验值 |
通道数据格式:
每个遥控通道数据占11位,通过移位和掩码操作将16个通道的值打包到22个字节中。每个通道的值通常为1000到2000,表示标准的RC脉宽。
数据打包示例:
void sendCRSFFrame() {
uint8_t crsfData[CRSF_FRAME_SIZE]; // 创建一个数组来存储数据帧
// 设置数据帧头部
crsfData[0] = CRSF_ADDRESS_FLIGHT_CONTROLLER; // 飞控设备地址
crsfData[1] = 22; // 数据长度为22字节
crsfData[2] = CRSF_FRAMETYPE_RC_CHANNELS_PACKED; // 遥控通道数据类型
// 打包16个通道的11位数据
uint32_t bitBuffer = 0;
uint8_t bitsInBuffer = 0;
int offset = 3; // 数据从第3个字节开始
for (int i = 0; i < CRSF_MAX_CHANNEL; i++) {
bitBuffer |= ((uint32_t)rcChannels[i] & 0x07FF) << bitsInBuffer; // 每个通道占11位
bitsInBuffer += 11;
while (bitsInBuffer >= 8) {
crsfData[offset++] = bitBuffer & 0xFF;
bitBuffer >>= 8;
bitsInBuffer -= 8;
}
}
// 计算CRC校验值并附加到数据包末尾
uint8_t crc = calcCRC(&crsfData[2], 22);
crsfData[offset++] = crc;
// 通过串口发送数据帧
crsfSerial.write(crsfData, offset);
}
4. CRC校验
CRSF 协议使用 CRC-8 校验,以确保数据在传输过程中不被损坏。多项式为 0xD5。CRC校验需要对从第2字节到第24字节的数据进行计算。
CRC 校验函数:
uint8_t calcCRC(uint8_t *data, uint8_t len) {
uint8_t crc = 0;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80)
crc = (crc << 1) ^ 0xD5;
else
crc = crc << 1;
}
}
return crc;
}
该函数遍历数据包的所有字节,逐步计算CRC校验值,最后将其作为数据包的最后一个字节。
5. 模块化设计与移植性
代码模块化设计:
- 串口初始化:
initCRSFSerial()
函数负责串口的初始化设置。 - 数据打包与发送:
sendCRSFFrame()
负责生成并发送CRSF数据包。 - CRC校验:
calcCRC()
负责数据校验。
这种设计使得代码可以方便地移植到其他项目中,只需调整串口配置和数据源即可。
6. 代码使用流程
- 初始化串口:在
setup()
函数中初始化串口,设置波特率为 420,000。 - 定期发送数据:在
loop()
函数中,每隔 100ms 发送一次遥控通道数据。
void setup() {
initCRSFSerial(); // 初始化串口
}
void loop() {
// 更新遥控通道数据
rcChannels[0] = 1500; // 通道1中间位置
rcChannels[1] = 1000; // 通道2最小值
rcChannels[2] = 2000; // 通道3最大值
rcChannels[3] = 1500; // 通道4中间位置
sendCRSFFrame(); // 发送打包后的CRSF数据
delay(100); // 每100ms发送一次
}
7. 结论
该模块化的CRSF协议实现代码不仅适合STM32duino的硬件串口环境,而且易于移植和扩展。在飞控开发过程中,你可以根据实际需要更新遥控通道数据,并通过硬件串口将数据发送给飞控。
8.完整代码
以下是一个在STM32duino上使用硬件串口,将各个通道数据通过CRSF协议打包并发送的模块化代码。此代码便于移植,并且有详细的中文注释,方便你在飞控开发中使用。
完整的CRSF协议发送代码
#include <HardwareSerial.h>
// 定义CRSF协议的常量
#define CRSF_MAX_CHANNEL 16 // CRSF协议支持的最大通道数
#define CRSF_FRAME_SIZE 24 // CRSF数据帧的总字节数
#define CRSF_BAUDRATE 420000 // CRSF协议波特率
#define CRSF_ADDRESS_FLIGHT_CONTROLLER 0xC8 // 飞控设备地址
#define CRSF_FRAMETYPE_RC_CHANNELS_PACKED 0x16 // 遥控通道打包数据帧类型
// 定义串口(以Serial2为例,如果使用其他硬件串口,请修改为相应的串口)
HardwareSerial &crsfSerial = Serial2;
// 定义一个数组存储各个通道的数据
uint16_t rcChannels[CRSF_MAX_CHANNEL] = {1500, 1500, 1500, 1500, 1000, 2000};
// 初始化串口,波特率设置为CRSF协议要求的420000
void initCRSFSerial() {
crsfSerial.begin(CRSF_BAUDRATE);
}
// 计算CRC-8校验值,使用多项式0xD5
uint8_t calcCRC(uint8_t *data, uint8_t len) {
uint8_t crc = 0;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80)
crc = (crc << 1) ^ 0xD5;
else
crc = crc << 1;
}
}
return crc;
}
// 生成CRSF遥控数据包并通过串口发送
void sendCRSFFrame() {
uint8_t crsfData[CRSF_FRAME_SIZE]; // 创建一个数组来存储数据帧
// 数据帧头部
crsfData[0] = CRSF_ADDRESS_FLIGHT_CONTROLLER; // 飞控地址
crsfData[1] = 22; // 数据长度:22字节
crsfData[2] = CRSF_FRAMETYPE_RC_CHANNELS_PACKED; // 数据帧类型:遥控通道数据
// 将16个11位通道数据打包到数据帧中
uint32_t bitBuffer = 0;
uint8_t bitsInBuffer = 0;
int offset = 3; // 从第3个字节开始存放通道数据
for (int i = 0; i < CRSF_MAX_CHANNEL; i++) {
bitBuffer |= ((uint32_t)rcChannels[i] & 0x07FF) << bitsInBuffer; // 每个通道取11位
bitsInBuffer += 11;
while (bitsInBuffer >= 8) {
crsfData[offset++] = bitBuffer & 0xFF;
bitBuffer >>= 8;
bitsInBuffer -= 8;
}
}
// 计算CRC校验值,校验从数据帧的第2字节开始计算,总共22字节
uint8_t crc = calcCRC(&crsfData[2], 22);
crsfData[offset++] = crc; // 将CRC校验字节加到数据帧末尾
// 通过硬件串口发送打包后的数据帧
crsfSerial.write(crsfData, offset);
}
void setup() {
// 初始化CRSF串口
initCRSFSerial();
}
// 主循环函数,定期发送CRSF遥控通道数据
void loop() {
// 在此处可以动态更新各个通道的数据,例如:
rcChannels[0] = 1500; // 通道1:中间位置
rcChannels[1] = 1000; // 通道2:最小值
rcChannels[2] = 2000; // 通道3:最大值
rcChannels[3] = 1500; // 通道4:中间位置
// 可以继续设置其他通道的数据...
// 每100ms发送一次CRSF数据包
sendCRSFFrame();
delay(100);
}
代码说明:
initCRSFSerial()
:初始化串口,将其波特率设置为CRSF协议的要求(420,000 bps),并使用硬件串口进行通信。calcCRC()
:CRC-8校验函数,使用多项式0xD5
。该函数遍历数据包的内容,生成一个校验值,确保数据传输的完整性。sendCRSFFrame()
:这是核心函数,它负责生成一个完整的CRSF遥控数据包,并通过硬件串口发送。数据包包括设备地址、帧长度、帧类型、16个通道的11位数据,以及最后的CRC校验值。loop()
:主循环函数中,定期更新通道数据并调用sendCRSFFrame()
发送打包好的数据。- 详细注释:每个关键步骤都有中文注释,方便理解与移植。
代码模块化设计:
- 串口初始化函数 (
initCRSFSerial
) 和 CRC校验函数 (calcCRC
) 已模块化,可以轻松移植到其他项目中。 - 通过修改
rcChannels[]
数组中的值,可以模拟不同的通道数据,这让代码在飞控开发中具备很大的灵活性。
扩展与移植:
- 你可以将此代码移植到任何STM32的硬件串口环境,只需调整
Serial2
为你实际使用的硬件串口。 - 在飞控开发中,你可以根据实际应用场景动态更新
rcChannels[]
中的数据来模拟或传输不同的控制命令。
这样你可以在基于STM32的项目中通过CRSF协议发送遥控数据,方便进行飞控的开发和调试。
完整的ELRS接收机遥控通道数据解析示例代码(基于Arduino框架,适用于STM32duino)
在使用ELRS(ExpressLRS)接收机与飞控系统时,接收机通过CRSF协议发送遥控通道数据。本示例将演示如何在STM32duino平台上,使用硬件串口读取和解析ELRS接收机发送的遥控通道数据。
1. 项目需求
我们将使用STM32的硬件串口来接收ELRS接收机的遥控通道数据,并解析出每个通道的值。该示例将解析16个通道的CRSF数据包,并打印出每个通道的数值。
2. 数据包结构
根据CRSF协议,遥控通道数据的帧类型为0x16
,包含16个通道的数据,每个通道占用11位。解析时,需要处理以下字段:
- 设备地址(Device Address):
0xC8
(飞控地址) - 帧长度(Frame Length):22字节(不包含设备地址和CRC校验)
- 类型(Type):
0x16
(RC通道数据) - 数据部分:16个通道,每个通道11位打包
3. 完整代码
#include <HardwareSerial.h>
// 定义CRSF协议相关常量
#define CRSF_MAX_CHANNEL 16 // CRSF协议支持的最大通道数
#define CRSF_FRAME_SIZE 24 // CRSF数据帧总大小(包含CRC)
#define CRSF_ADDRESS_FLIGHT_CONTROLLER 0xC8 // 飞控地址
#define CRSF_FRAMETYPE_RC_CHANNELS_PACKED 0x16 // 遥控通道数据包类型
// 使用STM32硬件串口(以Serial2为例)
HardwareSerial &crsfSerial = Serial2;
// 用于存储解码后的16个通道值
uint16_t rcChannels[CRSF_MAX_CHANNEL];
// 初始化串口,波特率为420,000(符合CRSF协议要求)
void initCRSFSerial() {
crsfSerial.begin(420000);
}
// 计算CRC-8校验值,使用多项式0xD5
uint8_t calcCRC(uint8_t *data, uint8_t len) {
uint8_t crc = 0;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80)
crc = (crc << 1) ^ 0xD5;
else
crc = crc << 1;
}
}
return crc;
}
// 处理接收到的CRSF数据包
void processCRSFPacket(uint8_t *packet) {
// 检查数据包的类型是否为遥控通道数据包
if (packet[0] == CRSF_ADDRESS_FLIGHT_CONTROLLER && packet[2] == CRSF_FRAMETYPE_RC_CHANNELS_PACKED) {
uint32_t bitBuffer = 0;
uint8_t bitsInBuffer = 0;
int byteIndex = 3; // 数据部分从第3个字节开始
// 解析16个通道的11位数据
for (int i = 0; i < CRSF_MAX_CHANNEL; i++) {
while (bitsInBuffer < 11) {
bitBuffer |= ((uint32_t)packet[byteIndex++] << bitsInBuffer); // 从数据流中提取字节并移位
bitsInBuffer += 8;
}
rcChannels[i] = bitBuffer & 0x07FF; // 取低11位作为通道值
bitBuffer >>= 11;
bitsInBuffer -= 11;
}
// 打印解析出的各个通道值
for (int i = 0; i < CRSF_MAX_CHANNEL; i++) {
Serial.print("通道 ");
Serial.print(i + 1);
Serial.print(": ");
Serial.println(rcChannels[i]);
}
}
}
// 从串口接收并解析CRSF数据包
void receiveCRSF() {
static uint8_t rxBuffer[CRSF_FRAME_SIZE];
static uint8_t rxPos = 0;
// 检查串口是否有数据
while (crsfSerial.available()) {
uint8_t byteReceived = crsfSerial.read();
if (rxPos == 0 && byteReceived != CRSF_ADDRESS_FLIGHT_CONTROLLER) {
continue; // 忽略不符合CRSF协议的字节
}
// 将接收到的字节存储到缓冲区
rxBuffer[rxPos++] = byteReceived;
// 检查数据包是否完整(接收到完整的24字节数据包)
if (rxPos == CRSF_FRAME_SIZE) {
// 检查CRC校验
uint8_t calculatedCRC = calcCRC(&rxBuffer[2], CRSF_FRAME_SIZE - 3); // 从第2字节到倒数第2字节进行CRC校验
if (calculatedCRC == rxBuffer[CRSF_FRAME_SIZE - 1]) {
processCRSFPacket(rxBuffer); // 处理有效的CRSF数据包
} else {
Serial.println("CRC校验失败!");
}
rxPos = 0; // 重置缓冲区位置
}
}
}
void setup() {
// 初始化串口和CRSF
Serial.begin(115200); // 用于调试,波特率115200
initCRSFSerial(); // 初始化硬件串口用于CRSF协议接收
}
void loop() {
receiveCRSF(); // 循环接收并解析CRSF数据包
}
4. 代码说明
4.1. initCRSFSerial()
该函数负责初始化STM32的硬件串口,波特率设置为420,000 bps,这是CRSF协议的标准波特率。我们使用Serial2
作为硬件串口与ELRS接收机通信。
4.2. calcCRC()
该函数用于计算CRC-8校验值,校验多项式为0xD5
。它遍历数据包的各个字节,并生成一个CRC值,以确保数据在传输过程中未损坏。
4.3. processCRSFPacket()
这是解析CRSF数据包的核心函数。具体步骤如下:
- 数据校验:首先检查数据包的类型是否为遥控通道数据包(
0x16
)。 - 通道解析:从数据包的第3字节开始,依次读取16个通道的11位数据。每个通道的值通过移位和掩码提取。
- 数据输出:解析完成后,通过串口打印出各个通道的值,便于调试和验证。
4.4. receiveCRSF()
该函数负责从硬件串口接收数据,并将其存储到缓冲区中。当接收到完整的CRSF数据包(24字节)后,执行CRC校验。如果校验通过,则调用processCRSFPacket()
解析数据包。
4.5. setup()
初始化硬件串口,分别用于接收CRSF数据(Serial2
)和调试信息(Serial
)。
4.6. loop()
主循环函数中,receiveCRSF()
会不断检查是否有新的数据包到来,并对数据进行解析。
5. 解析过程与数据结构说明
在解析遥控通道数据时,需要处理CRSF协议的11位通道值。这些值紧凑地打包在22字节的数据段中,通过移位操作将每个通道的11位值提取出来。解析出的通道值通常范围在 1000到2000 之间,对应于标准的RC脉宽值。
6. 测试与验证
将ELRS接收机与STM32通过串口连接后,上传该代码。你可以通过调试串口查看解析出的各个遥控通道的值。确保接收机和发射机的通信正常,遥控器的摇杆或开关动作会反映在通道数据的变化上。
7. 扩展与应用
该代码可以轻松移植到其他飞控项目中。通过修改接收通道数据的方式,可以将数据传递到飞控系统,进而用于控制无人机的飞行状态。