CRSF 协议 RC 通道接收解析模块设计
CRSF 协议 RC 通道数据接收解析模块详细设计文档
文档信息
| 项目 | 内容 |
|---|---|
| 设计目标 | 指导实现 CRSF 协议 0x16 类型帧解析,输出 16 路 RC 通道数值 |
| 协议依据 | CRSF Protocol Rev07 |
| 适用场景 | 无人机飞控 / 遥控设备的 CRSF RC 数据接收解析 |
一、文档概述
1.1 设计目标
本文档定义 CRSF 协议中0x16类型帧(RC通道打包数据)的接收解析模块设计逻辑,需实现以下核心目标:
-
遵循 CRSF 协议 Rev07 的 RC 通道数据打包规则,将 22 字节负载解析为 16 个 11 位的 RC 通道数值;
-
提供标准化的宏定义、数据结构和函数接口,保障代码可维护性;
-
适配中断接收场景,确保数据读取的实时性和准确性;
-
具备基础的容错能力(如帧长度校验),过滤无效数据。
1.2 核心约束
-
协议约束:严格遵循 CRSF 0x16 帧结构(总长度 26 字节,负载 22 字节,16 通道 ×11 位);
-
硬件约束:适配 UART 中断接收场景,数据读取需避免编译器优化;
-
数值约束:RC 通道数值业务范围为 191~1792(原生协议 0~2047,业务层裁剪)。
二、需求分析
2.1 功能需求
| 需求 ID | 需求描述 | 优先级 |
|---|---|---|
| F01 | 定义 CRSF RC 通道相关常量(帧长度、数值范围) | 高 |
| F02 | 定义 16 路 RC 通道索引枚举,提升代码可读性 | 中 |
| F03 | 声明 / 初始化全局 RC 通道数值数组 | 高 |
| F04 | 实现 0x16 帧解析函数,提取 16 路 RC 通道值 | 高 |
| F05 | 预留 0x14 帧处理接口,便于扩展 | 低 |
| F06 | 帧长度校验,过滤无效 0x16 帧 | 高 |
2.2 性能需求
-
解析效率:单帧解析耗时≤10μs(适配 4ms / 帧的 RC 数据更新率);
-
数据准确性:解析后的 RC 通道值与协议打包值完全一致;
-
中断安全:全局数据在中断 / 主循环交互时无数据不一致问题。
三、总体设计
3.1 模块架构
模块分为 “定义层” 和 “实现层”,分层设计如下:
┌─────────────────┐ 头文件(crsf_RecvPro.h)
│ 宏定义 │ - CRSF协议常量、RC数值范围
│ 枚举定义 │ - 16通道索引枚举
│ 全局变量声明 │ - RC通道数值数组声明
│ 函数声明 │ - 0x14/0x16帧处理函数声明
└─────────────────┘
↓
┌─────────────────┐ 源文件(crsf_RecvPro.c)
│ 全局变量初始化 │ - RC数组默认值(中位值)
│ 函数实现 │ - 0x16帧解析核心逻辑
│ │ - 0x14帧预留空实现
└─────────────────┘
3.2 数据流程
UART中断接收CRSF帧 → 判断帧类型为0x16 → 调用0x16解析函数 → 帧长度校验 → 负载字节拼接为位流 → 按11位拆分通道值 → 存储到全局数组 → 主循环读取数组使用
四、详细设计
4.1 宏定义设计
4.1.1 设计思路
基于 CRSF 协议规则和业务需求,定义以下核心宏,需满足 “可读性 + 可维护性”,避免硬编码。
4.1.2 具体宏定义
| 宏名 | 设计值 | 设计依据 | 代码实现示例 |
|---|---|---|---|
CRSF_PAYLOAD_SIZE_RC_CHANNELS |
22 | CRSF 协议:16 通道 ×11 位 = 176 位 = 22 字节 | #define CRSF_PAYLOAD_SIZE_RC_CHANNELS 22 |
CRSF_MAX_CHANNEL |
16 | CRSF 协议标准 RC 通道数 | #define CRSF_MAX_CHANNEL 16 |
CRSF_0X16_FRAME_SIZE |
26 | 0x16 帧总长度 = 同步字节 1 + 长度 1 + 类型 1 + 负载 22+CRC1 | #define CRSF_0X16_FRAME_SIZE 26 |
CRSF_RC_MIN_VALUE |
191 | 业务层裁剪:原生 0~2047,取 191 作为最小有效值 | #define CRSF_RC_MIN_VALUE 191 |
CRSF_RC_MAX_VALUE |
1792 | 业务层裁剪:原生 0~2047,取 1792 作为最大有效值 | #define CRSF_RC_MAX_VALUE 1792 |
CRSF_RC_MID_VALUE |
(191+1792)/2 | 通道中位值(摇杆居中时的默认值) | #define CRSF_RC_MID_VALUE ((CRSF_RC_MAX_VALUE + CRSF_RC_MIN_VALUE) / 2) |
CRSF_RC_VALUE_SCALE |
1792-191 | 通道有效行程范围(用于后续量程转换) | #define CRSF_RC_VALUE_SCALE (CRSF_RC_MAX_VALUE - CRSF_RC_MIN_VALUE) |
4.2 枚举设计
4.2.1 设计思路
将 16 路 RC 通道编号(1~16)映射为数组索引(0~15),提升代码可读性(如RC_CHANNELS_5直接对应第 5 通道)。
4.2.2 代码实现示例
enum
{
RC_CHANNELS_1 = 0, // 通道1 → 数组索引0
RC_CHANNELS_2, // 通道2 → 数组索引1
RC_CHANNELS_3,
RC_CHANNELS_4,
RC_CHANNELS_5,
RC_CHANNELS_6,
RC_CHANNELS_7,
RC_CHANNELS_8,
RC_CHANNELS_9,
RC_CHANNELS_10,
RC_CHANNELS_11,
RC_CHANNELS_12,
RC_CHANNELS_13,
RC_CHANNELS_14,
RC_CHANNELS_15,
RC_CHANNELS_16, // 通道16 → 数组索引15
};
4.3 全局变量设计
4.3.1 设计思路
-
定义全局数组存储 16 路 RC 通道值,需加
volatile修饰(中断中修改,主循环读取,避免编译器优化); -
初始化值为中位值(
CRSF_RC_MID_VALUE),避免上电未接收数据时出现无效值。
4.3.2 代码实现示例
// 头文件声明(供外部调用)
extern volatile uint16_t rcChannelValues[CRSF_MAX_CHANNEL];
// 源文件初始化
volatile uint16_t rcChannelValues[CRSF_MAX_CHANNEL] = {
CRSF_RC_MID_VALUE,
CRSF_RC_MID_VALUE,
// ... 共16个元素,均初始化为中位值
CRSF_RC_MID_VALUE
};
4.4 函数设计
4.4.1 函数列表
| 函数名 | 输入参数 | 输出参数 | 功能说明 |
|---|---|---|---|
CRSF_TYPE_0x14_REC |
ptr:帧数据首地址len:帧长度 | 无 | 预留接口,处理 0x14 类型帧 |
CRSF_TYPE_0x16_REC |
ptr:帧数据首地址len:帧长度 | 无 | 解析 0x16 帧,提取 RC 通道值到全局数组 |
4.4.2 CRSF_TYPE_0x14_REC 设计
-
设计思路:预留接口,暂不实现逻辑,仅定义函数体;
-
代码实现示例:
void CRSF_TYPE_0x14_REC(uint8_t *ptr, uint8_t len)
{
// 空实现,后续扩展0x14帧解析时补充
}
4.4.3 CRSF_TYPE_0x16_REC 设计(核心)
1. 设计思路
| 步骤 | 设计逻辑 |
|---|---|
| 1 | 定义局部变量:64 位位缓冲区(存储拼接的字节流)、有效位数计数器、通道索引 |
| 2 | 帧长度校验:仅处理长度为 26 的 0x16 帧,过滤无效数据 |
| 3 | 遍历负载字节:从帧第 3 字节(负载起始位)开始,拼接 22 字节负载为连续位流 |
| 4 | 提取通道值:每累计 11 位,提取低 11 位作为 1 个通道值,存储到全局数组 |
| 5 | 位缓冲区清理:提取后右移 11 位,继续处理剩余位流 |
2. 关键变量设计
| 变量名 | 类型 | 设计值 | 设计依据 |
|---|---|---|---|
bitBuffer |
uint64_t | 初始 0 | 64 位足够容纳拼接的位流(单次最多拼接 2 字节 = 16 位),避免溢出 |
bitsInBuffer |
int | 初始 0 | 记录位缓冲区中已拼接的有效位数,用于判断是否可提取 11 位通道值 |
channelIndex |
int | 初始 0 | 记录当前解析的通道索引,范围 0~15 |
3. 核心算法设计(位拼接与提取)
CRSF 协议中,16 个 11 位通道值会被连续拼接为 176 位流,按 8 位 / 字节拆分为 22 字节负载(存储顺序:先低字节后高字节,先低 11 位后高 11 位)。
-
拼接逻辑:将每个负载字节左移
bitsInBuffer位后拼接到bitBuffer,并累加有效位数; -
提取逻辑:当有效位数≥11 时,用
0x7FF(11 位全 1)掩码提取低 11 位,右移 11 位清理已提取位。
4. 代码实现模板
void CRSF_TYPE_0x16_REC(uint8_t *ptr, uint8_t len)
{
// 步骤1:定义局部变量
uint64_t bitBuffer = 0; // 临时位缓冲区
int bitsInBuffer = 0; // 缓冲区有效位数
int channelIndex = 0; // 通道索引
int i;
// 步骤2:帧长度校验(容错设计)
if (len != CRSF_0X16_FRAME_SIZE)
{
return;
}
// 步骤3:遍历22字节负载(帧索引3\~24)
for (i = 3; i < CRSF_PAYLOAD_SIZE_RC_CHANNELS + 3; ++i)
{
// 3.1 拼接当前字节到位缓冲区(低位在前)
bitBuffer |= (ptr[i]) << bitsInBuffer;
bitsInBuffer += 8;
// 3.2 累计≥11位时,提取通道值
while (bitsInBuffer >= 11 && channelIndex < CRSF_MAX_CHANNEL)
{
// 提取低11位(0x7FF为11位掩码)
rcChannelValues[channelIndex] = (bitBuffer & 0x7FF);
// 清理已提取的11位
bitBuffer >>= 11;
bitsInBuffer -= 11;
// 解析下一个通道
channelIndex++;
}
}
}
五、实现步骤(按此步骤编写代码)
步骤 1:创建头文件(crsf_RecvPro.h)
-
引入工程核心头文件
main.h; -
定义 4.1.2 节的所有宏;
-
定义 4.2.2 节的通道索引枚举;
-
声明全局变量
rcChannelValues; -
声明
CRSF_TYPE_0x14_REC和CRSF_TYPE_0x16_REC函数。
步骤 2:创建源文件(crsf_RecvPro.c)
-
包含头文件
crsf_RecvPro.h; -
初始化全局数组
rcChannelValues(16 个元素均为CRSF_RC_MID_VALUE); -
实现
CRSF_TYPE_0x14_REC空函数; -
按 4.4.3 节的设计实现
CRSF_TYPE_0x16_REC函数。
步骤 3:代码验证(关键检查点)
-
宏定义数值是否与设计一致;
-
volatile修饰符是否添加; -
0x16 帧解析中,负载遍历范围是否为
i=3到i=24; -
位提取时是否使用
0x7FF掩码; -
通道索引是否限制在 0~15 范围内。
六、测试验证设计
6.1 功能测试
| 测试项 | 测试步骤 | 预期结果 |
|---|---|---|
| 帧长度校验 | 传入长度≠26 的 0x16 帧,调用解析函数 | 函数直接返回,全局数组值不变 |
| 通道值解析准确性 | 构造已知负载的 0x16 帧(如通道 1=191,通道 2=1792),调用解析函数 | 全局数组对应位置值与构造值一致 |
| 中位值初始化 | 上电后未接收任何帧,读取全局数组 | 所有元素均为CRSF_RC_MID_VALUE(991) |
6.2 中断安全测试
| 测试项 | 测试步骤 | 预期结果 |
|---|---|---|
| 中断 / 主循环数据一致性 | 在 UART 中断中调用解析函数,主循环每秒读取 1000 次全局数组 | 读取值与中断解析值一致,无数据错乱 |
七、设计扩展建议
7.1 容错能力扩展
-
增加 CRC 校验:解析帧最后 1 字节的 CRC 值(多项式 0xD5),验证帧完整性;
-
通道值范围校验:解析后判断值是否在 191~1792 范围内,超出则重置为中位值;
-
通道数校验:解析完成后检查
channelIndex是否为 16,不足则补中位值。
7.2 功能扩展
-
实现
CRSF_TYPE_0x14_REC函数,支持 0x14 帧(遥测数据)解析; -
增加 RC 通道值量程转换函数(如将 191~1792 映射为 0~1000);
-
增加数据更新标志位,主循环仅在数据更新时读取。
八、设计约束与注意事项
-
字节序约束:CRSF 协议为大端序,但 RC 通道负载为连续位流,解析时无需字节序转换;
-
中断约束:解析函数需在 UART 接收完成中断中调用,确保帧数据完整;
-
性能约束:位操作需简洁,避免嵌套循环过多导致解析耗时超标;
-
维护约束:宏定义集中在头文件,后续修改协议参数仅需调整宏值。

浙公网安备 33010602011771号