ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F103解析富斯i6接收机ibus协议:串口DMA接收14通道数据

STM32F103解析富斯i6接收机ibus协议:串口DMA接收14通道数据 简介基于STM32F103解析富斯i6遥控器IBUS通信的完整工程资源面向嵌入式开发者、遥控模型爱好者及需要实现无线遥控控制的项目人员。资源围绕IBUS单线串行协议给出从GPIO配置、定时器输入捕获、上升沿与下降沿中断读取计数值到根据脉冲宽度还原二进制数据、按通道地址重组为油门/方向/副翼等控制指令并最终输出PWM驱动小车、无人机或云台的完整方案。工程内包含49个h头文件与44个c源码文件覆盖STM32标准外设库、定时器、I2C、ADC、Flash等底层驱动同时提供hex固件、pdf说明文档与Keil工程配置文件可直接编译烧录并借助清理脚本等工具快速梳理项目结构。压缩包共109个文件整体仅1.89MB轻量清晰适合边读边调试也可作为课程设计与毕业设计的参考模板。已有1703人学习下载对理解单线协议解析、中断服务程序设计及嵌入式遥控应用均有直接参考价值。 手边有一套飞行器拆下来的富斯i6遥控器加FS-iA6B接收机是很多玩航模、做机器人或者搞无人车的人都会入手的第一套遥控设备。但真到了要把接收机接到STM32上用的环节我发现网上大量教程还在教你用PWM方式逐通道采集一根线读一个通道6通道就得接6个IO口还得配合输入捕获中断程序又长又容易乱。其实FS-iA6B背后还有一根“I-BUS”信号线这一个引脚就能把全部14个通道数据用串口方式送到stm32f103。这个做法的本质很简单接收机内部已经把各个通道打包成一帧串行数据我们只要用USART按115200波特率收下来按协议拆包就能直接拿到每个通道对应的数值。这篇文章会把基于stm32f103解析富斯i6遥控器和FS-iA6B接收机ibus通信的完整过程从协议层面到实际代码再到我踩过的坑全部摆出来。不管你是给小车加无线控制还是想做一个低成本遥控手柄这篇都能帮你直接落地。1. 方案选型为什么绕开PWM去碰ibus1.1 PWM、PPM、ibus三种模式怎么选FS-iA6B接收机本身支持PWM、PPM两种传统输出同时也支持ibus串行输出。很多教程默认教PWM方式因为每个通道对应一根信号线看起来最直观遥控器推杆对应引脚上就有脉宽变化用定时器输入捕获就能把脉宽读出来。但PWM方式的实际体验不太行。6个通道至少要占6个输入IO接收机上面的排针密密麻麻而且每个通道要单独配置一个输入捕获通道中断也多主循环动不动被打断。更麻烦的是如果后面想扩展机械臂或云台8通道、10通道一上来IO根本不够用。PPM是一个引脚输出所有通道但它本质是模拟式的脉宽串行信号一个周期内按时间顺序编码多个通道。解码时要用定时器不停测量脉宽对中断实时性要求高一旦程序里有关中断的临界区就容易丢脉冲。ibus则完全是数字协议把14个通道的数据打包成固定帧通过串口发出来UART天然适合收这种东西。用DMA接收的话CPU基本不用管一帧数据到了自己就进内存解析也只是简单的字节切片和校验。三种方式对比如下方式接线数量CPU占用实时性通道数PWM每通道1根高需输入捕获/外部中断一般受IO数量限制PPM1根中需要定时器测量脉宽中最多8/9通道ibus1根低USARTDMA后台接收约7ms刷新一帧14通道1.2 整套系统的数据链路从遥控器到单片机数据是这么走的富斯i6遥控器把摇杆、开关、旋钮的状态通过2.4G无线发送给FS-iA6B接收机接收机在内部把各通道数据整理成ibus帧格式从I-BUS引脚用串口电平发出来。STM32F103要做的就是把这一帧收下来从帧里还原出通道值。这个方案里遥控器端不用做任何额外设置接收机默认在I-BUS引脚输出iber帧。只要STM32端的串口参数对得上、帧格式理解正确数据就能稳定拿到。整条链路里面唯一需要自己动手的就是写STM32这一端的接收和解析程序。2. ibus协议数据帧完全拆解2.1 一帧32字节结构非常固定FS-iA6B的ibus输出是标准UART串口协议115200波特率8个数据位1个停止位无校验。一帧固定32个字节结构如下表偏移字节长度内容02帧头固定0x20 0x4022814个通道数据每个通道2字节小端模式302校验和低字节在前通道数据的排布方式从第2字节开始每2个字节是一个通道的数值。比如第2、3字节是CH1第4、5字节是CH2依此类推。每个通道是一个16位无符号整数按小端存储也就是低字节在前高字节在后。读取时的C语言写法是ch_value buf[2 ch_index * 2] | (buf[3 ch_index * 2] 8);通道值的物理意义对应脉宽微秒数范围一般在1000到2000左右。摇杆在中位时约1500推到底是1000或2000开关通道一般直接落在1000和2000两端。这个数值可以直接拿来映射舵机角度、电机油门输出。2.2 校验和算法和帧同步校验和放在第30和第31字节算法是把第0到第29字节共30字节按8位逐字节累加得到一个16位累加和然后把累加和的低8位放在第30字节高8位放在第31字节。举个例子假设前30字节累加和是0x1234那么第30字节就是0x34第31字节就是0x12。接收端计算同样的累加和和帧尾的16位数据比较一致就说明这一帧有效。帧同步这个问题容易被忽略。ibus每帧约7毫秒发一次接收机和STM32之间如果出现电压毛刺、串口干扰或者STM32复位后DMA还没对齐就很可能出现中间少收一个字节、整帧错位的情况。这时候如果还按固定位置取通道数据解出来的全是乱的。所以解析程序里必须先检查帧头是否为0x20 0x40不满足就说明这一帧没有对齐要丢弃重新搜索。我在实际项目里用的是“IDLE中断判断一帧结束 帧头二次确认 累加和校验”三层保障跑下来基本不会出问题。3. 硬件接线与电平匹配3.1 找FS-iA6B的I-BUS引脚FS-iA6B接收机机身侧边会有一排通道排针上面一般印着CH1到CH6侧面有一个独立的排针位置或者丝印标注“I-BUS”。不同批次、不同版本的接收机丝印位置略有区别有的放在排针末尾有的单独一列。判断方式很简单用万用表量这个引脚在接收机上电后通常能看到3.3V左右的高电平而且实际输出波形是一连串不规则的串行数据。没有万用表的话也可以用逻辑分析仪或示波器去点各个引脚能抓到一串密集波形的那个就是I-BUS。3.2 STM32F103这边的接线FS-iA6B的I-BUS输出的是3.3V逻辑电平可以直接接到STM32F103的串口RX引脚。我习惯用USART1RX是PA10接线就三根FS-iA6B的I-BUS信号端子 → STM32的PA10FS-iA6B的GND → STM32的GND接收机供电用5V独立电源不占用STM32的供电特别提醒一下别把接收机的5V直接接到STM32板子的5V引脚给整块板供电。接收机工作瞬间电流不小如果共用一颗LDO容易造成STM32上电复位。我一开始图省事直接从开发板的5V引脚给接收机供电结果每次推油门舵机一动作STM32就重启。后来改成独立5V给接收机两边的GND共地问题立刻消失。信号线上可以串一个1kΩ电阻做保护PA10虽然是FT引脚能容忍5V但长期接高电平还是不推荐。3.3 关于“FS-iA6B是不是5V电平”的误解网上有些说法把I-BUS当成5V TTL这是不对的。FS-iA6B内部用3.3V单片机做信号处理I-BUS引脚输出的也是3.3V电平和STM32F103的IO电平天然兼容。有的人把I-BUS接到USB转TTL模块的5V RX脚上去看数据也能看到因为5V TTL对3.3V高电平的判断阈值大概在1.5V以上能识别出来但你千万别在STM32这边把I-BUS当成5V来硬接。4. STM32F103解析ibus的代码实现4.1 串口IDLE空闲中断 DMA接收ibus一帧32字节115200波特率下大约2.8ms就能收完一帧。如果每次都靠RXNE中断逐字节接收CPU会被频繁打断而且两个字节之间的间隔波动会导致判断“一帧结束”很麻烦。我推荐的做法是DMA接收 USART空闲中断IDLE。思路是这样的DMA空闲时一直在内存缓冲区里待命串口每收到一个字节就自动存进缓冲区。当串口线上出现空闲也就是一帧数据结束时USART会产生IDLE中断我在中断里读DMA当前剩余计数推算出这一帧收到了多少个字节。因为是接收机主动连续发帧两次帧之间有空闲间隔IDLE中断正好能在每帧结束后及时告诉我们“数据到了”。初始化的标准库代码#define IBUS_FRAME_SIZE 32 uint8_t rx_buf[IBUS_FRAME_SIZE]; uint8_t ibus_frame[IBUS_FRAME_SIZE]; volatile uint8_t frame_ready 0; void USART1_GPIO_DMA_Init(void) { GPIO_InitTypeDef gpio_init; USART_InitTypeDef usart_init; DMA_InitTypeDef dma_init; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // PA10 作为USART1的RX输入 gpio_init.GPIO_Pin GPIO_Pin_10; gpio_init.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio_init); // USART1 115200 8N1 usart_init.USART_BaudRate 115200; usart_init.USART_WordLength USART_WordLength_8b; usart_init.USART_StopBits USART_StopBits_1; usart_init.USART_Parity USART_Parity_No; usart_init.USART_Mode USART_Mode_RX; usart_init.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_Init(USART1, usart_init); // DMA1通道5对应USART1_RX dma_init.DMA_PeripheralBaseAddr (uint32_t)USART1-DR; dma_init.DMA_MemoryBaseAddr (uint32_t)rx_buf; dma_init.DMA_DIR DMA_DIR_PeripheralSRC; dma_init.DMA_BufferSize IBUS_FRAME_SIZE; dma_init.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma_init.DMA_MemoryInc DMA_MemoryInc_Enable; dma_init.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; dma_init.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; dma_init.DMA_Mode DMA_Mode_Normal; dma_init.DMA_Priority DMA_Priority_High; dma_init.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel5, dma_init); USART_DMACmd(USART1, USART_DMAReq_RX, ENABLE); USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); USART_Cmd(USART1, ENABLE); DMA_Cmd(DMA1_Channel5, ENABLE); }这里DMA模式用的Normal而不是Circular是因为每次IDLE中断里我会重新设置DMA计数器。Normal模式能让每一帧数据都在缓冲区里从0位置开始解析时逻辑最清晰。Circular模式会不停覆盖缓冲区中间容易出现帧头错位反而增加复杂度。4.2 IDLE中断处理函数USART1的中断服务函数里关键是清掉IDLE标志位。标准库的写法是先后读SR和DR寄存器来清除IDLE标志。void USART1_IRQHandler(void) { uint16_t rx_len; if (USART_GetITStatus(USART1, USART_IT_IDLE) ! RESET) { // 读SR和DR清除IDLE标志 (void)USART1-SR; (void)USART1-DR; // 先停DMA再算长度 DMA_Cmd(DMA1_Channel5, DISABLE); rx_len IBUS_FRAME_SIZE - DMA_GetCurrDataCounter(DMA1_Channel5); if (rx_len IBUS_FRAME_SIZE) { memcpy(ibus_frame, rx_buf, IBUS_FRAME_SIZE); frame_ready 1; } // 重置DMA继续接收下一帧 DMA_SetCurrDataCounter(DMA1_Channel5, IBUS_FRAME_SIZE); DMA_Cmd(DMA1_Channel5, ENABLE); } }为什么在IDLE中断里要先DMA_Cmd(DISABLE)再取计数器因为DMA计数器在运行过程中是实时变化的如果不禁用直接DMA_GetCurrDataCounter可能会在DMA正写内存的瞬间读到不一致的数据轻微但存在风险。加上禁用的动作后DMA停在当前状态计数稳定拷贝出来的帧也是完整对齐的。我的习惯是只把拷贝进ibus_frame这件事放在中断里做解析放到主循环这样中断服务函数的处理时间尽可能短。4.3 解析函数帧头确认、校验、抽通道主循环里只要判断frame_ready标志位然后调用解析函数uint8_t ibus_parse(uint8_t *buf, int16_t *channels) { uint16_t sum 0; uint16_t checksum 0; int i; // 1. 帧头确认 if (buf[0] ! 0x20 || buf[1] ! 0x40) return 0; // 2. 累加前30字节 for (i 0; i 30; i) sum buf[i]; // 3. 取帧尾校验值 checksum buf[30] | (buf[31] 8); if (sum ! checksum) return 0; // 4. 解析14个通道 for (i 0; i 14; i) channels[i] buf[2 i * 2] | (buf[3 i * 2] 8); return 1; }主循环的用法int16_t channels[14]; while (1) { if (frame_ready) { frame_ready 0; if (ibus_parse(ibus_frame, channels)) { // channels[0]~channels[13] 就是遥控器14个通道的值 printf(CH1:%d CH2:%d CH3:%d CH4:%d\r\n, channels[0], channels[1], channels[2], channels[3]); } } }校验通过后channels数组里每个元素的取值范围大约在1000~2000。串口打印出来就能观察到推摇杆对应通道值跟着变。到这里接收机的数据已经完整落到单片机内存里了。5. 调试中的坑和排查技巧5.1 一串乱码或者完全收不到数据先别动代码。把I-BUS引脚从STM32拆下来接到USB转TTL模块的RX上波特率115200打开串口助手看有没有以20 40开头、连续32字节的帧出现。如果有说明接收机输出正常问题出在STM32这一侧如果没有检查接收机是否成功绑定遥控器。富斯i6要对频很简单接收机上电后按住对频键遥控器进入对频模式几秒钟后接收机指示灯常亮就完成了。STM32这一侧最常见的问题是RX接错。有人把I-BUS接在PA9上那个是USART1_TX不是RX自然什么都收不到。另外检查DMA用的是不是DMA1_Channel5USART1_RX对应的是这个通道不是Channel4搞错了数据永远进不了内存。5.2 帧头对但校验和经常失败这种情况多发生在接收机到单片机之间线路较长或者供电不够干净的时候。FS-iA6B输出的串口波形如果受到干扰某个字节电平翻转就会导致累加和不对。排查时先用示波器看I-BUS引脚的波形确认高低电平是否干净边沿是否陡峭。波形毛刺多的话优先在接收机电源引脚并一个100uF电容信号线尽量短必要时用双绞线或屏蔽线。另外有一种隐蔽的情况接收机绑定时设置的失控保护导致某些通道数值超出正常范围虽然不影响帧格式但要注意是否你的程序把通道值当成了有符号数来处理。1000~2000这个范围正好在int16_t正数区间内但如果你用int8_t去接收马上就会出问题。我在解析函数里统一用int16_t避免符号扩展的坑。5.3 偶尔会卡住一两秒然后才恢复这是典型的数据帧失去同步。原因可能是STM32复位瞬间接收机已经在发数据DMA从半路开始收导致收到的数据不是从帧头开始的。虽然我在IDLE中断里只接收长度为32的帧但如果错位后的32字节恰好包含0x20 0x40的组合校验也可能碰巧通过解析出错误的通道值。解决办法是在解析函数里加一重检查通道值必须落在合理范围内比如800~2200之间超过这个范围就判定为无效帧丢弃。ibus正常通道值不会超过这个区间所以这个过滤很有效。更严谨的做法是连续收到N帧校验失败后把DMA重置一遍强制让数据从下个完整的0x20 0x40开始重新累积我的工程里就用了这个逻辑效果很明显。5.4 接收机一上电STM32板子就工作不正常优先查共地和供电。之前说过接收机启动瞬间电流大如果供电是从STM32板上引的瞬时压降会导致单片机复位。标准做法是接收机独立5V供电两边GND连起来信号线再串联一个1kΩ电阻。实际测量的时候接收机工作电流通常在几十毫安但启动瞬间可能冲到上百毫安板载LDO不一定扛得住。5.5 通道值漂移、微调后范围不对富斯i6遥控器本身有摇杆校准和微调功能。如果解析出的通道中位不在1500附近先到遥控器的“功能设置”里做摇杆校准再检查通道显示界面。ibus输出的数值本质上是遥控器内部换算后的结果所以校准实际上是在遥控器端完成的STM32这边只是忠实还原。6. 再进一步把ibus解析变成项目的一部分6.1 通道值直接驱动PWM输出解析出来的通道值天然就是脉宽微秒数可以直接映射到舵机或电调。STM32F103的定时器可以同时输出多路PWM如果只是单一通道点对点赋值逐个改CCR寄存器就够了。但项目里往往要同时控制多个舵机逐个赋值会带来通道之间的相位误差。我后来用定时器PWM DMA的方式先把14个通道值写进一个数组然后由DMA在定时器更新事件时一次性载入所有CCR寄存器这样多路PWM几乎同时更新控制机械臂时动作更同步。用到的引脚就是PA1、PA3这类定时器通道引脚和ibus接收的串口引脚不冲突。6.2 上FreeRTOS以后怎么组织如果工程里用了FreeRTOSibus解析建议单独做成一个任务优先级中等。串口IDLE中断里只置一个事件标志位或发一个二值信号量不直接做解析。解析任务等信号量拿到后调用ibus_parse再把校验通过的数据发布给其他任务使用。这样遥控器数据流和业务逻辑解耦后面加云台控制、路径规划都不互相阻塞。这里有个细节FreeRTOS的临界区或调度器锁可能会影响串口中断响应但IDLE中断很短DMA接收本身不依赖CPU所以影响很小。真遇到帧校验偶发失败我一般先查是不是任务里关了中断时间太长而不是怀疑协议本身。6.3 掉电保存校准值遥控器摇杆的物理行程偶尔有偏差我习惯在第一次开机时让用户把拨杆打满一圈采集每个通道的最大最小值然后存到STM32F103内部的Flash里。之后运行时用这两个极值做归一化把通道值映射成0%~100%的百分比。这样即使遥控器微调有变化或者不同遥控器之间有差异程序也能自适应。Flash写入次数有限所以只有在校准模式下才写正常运行不擦写。6.4 关于内部晶振的提醒有些DIY板子为了省成本用的不是外部8MHz晶振而是STM32F103内部RC振荡器。内部RC的精度在常温下还可以但温度变化时频率会飘串口波特率跟着飘。ibus固定115200STM32端波特率误差太大会导致长时间运行后偶发丢字节、校验错误。如果确认板子上没有外部晶振建议先用定时器测一下实际系统时钟或者干脆换一块带外部晶振的最小系统板。我早期用内部RC调试短时间正常放一晚上第二天上电就出现校验失败排查半天才发现是晶振精度问题。这套方案跑通之后后续无论是做无线遥控小车、机械臂示教还是FPV云台都只需要把接收机数据源接上然后按自己的业务逻辑去处理那14个通道值就行。ibus相比PWM方式最大的优势就是省心一根线解决所有通道帧结构固定还有校验和兜底稳定性和可维护性都高一个档次。踩过几次坑之后我现在拿到一颗新的STM32F103第一件事就是把串口空闲中断DMA这套框架搭好ibus解析只是其中一个很典型的小应用。本文还有配套的精品资源点击获取
返回列表