ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AM32电调遥测功能实现:DMA+USART与CRC8校验

AM32电调遥测功能实现:DMA+USART与CRC8校验 1. AM32电调遥测功能的整体设计思路1.1 为什么要在AM32上做遥测AM32是一款开源的电调固件基于STM32系列芯片在穿越机、固定翼和车模领域都有大量用户。默认情况下AM32只负责驱动无刷电机飞控通过PWM或DShot信号告诉电调“转多快”电调执行就完了整个过程是单向的。但实际飞行中飞控和飞手都想知道电调的实时状态输入电压是多少、电流拉了多少、电调温度有没有超标、电机转速稳不稳。这些数据如果拿不到飞控就没法做电压补偿飞手也没法判断电池是不是快撑不住了。遥测功能要解决的就是这个“反向通信”的问题。电调把采集到的电压、电流、温度、转速、错误码等数据按照约定的格式打包通过信号线回传给飞控。飞控解析后显示在OSD上或者传给地面站记录。对于调参和故障排查来说遥测数据是刚需——没有它你只能靠猜。AM32支持的遥测协议主要有几种KISS Telemetry、MAVLink部分场景、以及基于DShot的扩展遥测。不同协议的数据格式、波特率、触发方式都不一样。我这次做的是基于串口USART的异步遥测发送配合DMA搬运数据用CRC8做校验。选这个方案的原因很简单CPU占用低、数据完整性好、实现起来不依赖飞控端的特殊支持。1.2 方案选型的几个关键取舍做遥测发送第一个要决定的是“什么时候发”。有两种主流做法一种是飞控主动发请求电调收到请求后再回数据这叫“问答式”另一种是电调按照固定周期主动往外发飞控只管收这叫“广播式”。问答式的好处是总线利用率高不会一直占着线坏处是飞控端要额外做请求逻辑而且请求和响应之间的延迟不好控制。广播式实现简单电调端定时器一到就发飞控端只要开着接收就行缺点是如果波特率低、数据量大可能会影响其他通信。我选的是广播式周期定在10ms左右。这个周期是权衡后的结果太快了没必要电压电流的变化没那么剧烈太慢了飞控做电压补偿会滞后。10ms对应100Hz的更新率对绝大多数应用场景都够用了。第二个要决定的是“怎么发”。最直接的办法是用HAL_UART_Transmit阻塞发送代码写起来简单但问题很大——阻塞期间CPU什么都干不了电机控制环路会被打断。AM32的主循环里要跑换相逻辑、ADC采样、PWM更新任何一个环节被阻塞超过几十微秒都可能出问题。所以阻塞发送直接排除。中断发送比阻塞好一些HAL_UART_Transmit_IT把数据丢进发送寄存器发完一个字节进一次中断CPU在中断里填下一个字节。但这样每发一个字节就要进一次中断一帧数据假设20个字节就是20次中断。在115200波特率下一个字节大约87微秒意味着每87微秒CPU就被打断一次。虽然每次中断执行时间很短但频繁进出中断本身就有开销而且会影响其他中断的响应实时性。DMA发送是更优的方案。把整帧数据准备好放在内存缓冲区里配置DMA通道启动一次传输DMA控制器自动把数据从内存搬到USART的发送寄存器全程不需要CPU干预。CPU只需要在DMA传输完成中断里做一下收尾工作比如标记缓冲区空闲、准备下一帧数据。这样CPU占用率极低对电机控制环路的影响可以忽略不计。第三个要决定的是“数据怎么组织”。遥测帧需要包含哪些字段、每个字段几个字节、字节序是大端还是小端、校验怎么算这些都要提前定好。我参考了KISS Telemetry的格式结合AM32实际能采集到的数据定义了一个固定长度的帧结构。帧头用两个字节的同步字方便接收端做帧同步后面跟数据载荷最后加一个CRC8校验字节。1.3 硬件层面的约束条件AM32电调通常用的MCU是STM32F051、STM32F103或者AT32F421这类资源比较紧张的芯片。以STM32F051K8为例Flash只有64KBRAM只有8KB。在这种资源条件下做遥测必须精打细算。USART外设方面AM32一般用USART1或者USART2。具体用哪个要看PCB布局和引脚分配。我手上这块板子用的是USART1TX引脚是PA9RX引脚是PA10。遥测只需要发送所以RX可以不用但为了调试方便我还是把RX也配置上了方便后面接串口助手看数据。DMA通道的选择要注意STM32F0系列的DMA通道和USART的对应关系是固定的USART1_TX对应DMA1_Channel2USART1_RX对应DMA1_Channel3。这个不能随便改查参考手册的DMA请求映射表就能确认。STM32F103的映射又不一样USART1_TX是DMA1_Channel4。所以换芯片平台的时候DMA通道号一定要重新确认这是最容易踩坑的地方之一。时钟配置方面USART的波特率来自APB总线时钟。STM32F051的USART1挂在APB2上默认时钟是48MHz。要得到115200的波特率USART_BRR寄存器的值要算对。用HAL_UART_Init的话HAL库会自动算但前提是huart-Init.BaudRate设对了而且时钟配置没有错。我遇到过因为系统时钟配置成内部HSI而不是外部晶振导致实际波特率偏差超过3%接收端解析出一堆乱码的情况。2. 遥测帧格式与CRC8校验的细节拆解2.1 帧结构定义与字段说明遥测帧的结构设计要兼顾“紧凑”和“可扩展”。太长了浪费带宽太短了装不下必要的数据。我定义的帧结构如下字段长度字节说明同步字11固定0xAA同步字21固定0x55帧长度1从帧头到校验前的总字节数电压2单位0.01V大端电流2单位0.01A大端温度1单位摄氏度偏移40度转速2单位100RPM大端错误码1位域表示CRC81前面所有字节的校验总长度是13个字节。同步字用0xAA55是因为这两个字节在数据载荷里同时出现的概率很低接收端可以用状态机做帧同步。帧长度字段是为了将来扩展——如果以后要加字段接收端可以根据长度字段跳过不认识的字节保证向前兼容。电压字段用两个字节表示0.01V为单位最大能表示655.35V对航模电池来说绰绰有余。电流同理0.01A为单位最大655.35A。温度用一个字节偏移40度能表示-40到215度覆盖了电调可能的工作范围。转速用两个字节单位100RPM最大65535*100655万RPM实际上电机转速也就几万RPM够用了。错误码用位域bit0表示过压bit1表示欠压bit2表示过流bit3表示过温bit4表示堵转bit5表示信号丢失bit6和bit7保留。这样接收端可以一次性拿到所有告警状态。2.2 CRC8校验的算法选择与实现CRC8的算法有很多变种区别在于多项式、初始值、是否反射输入输出、最终异或值。我选的是CRC-8/MAXIM多项式0x31初始值0x00输入输出都不反射最终异或0x00。这个变种在嵌入式领域很常见计算速度快查表实现只需要256字节的ROM。为什么不用CRC16或者CRC32因为帧很短13个字节CRC8的检错能力已经足够了。CRC8能检测出所有单比特错误、所有双比特错误、所有奇数个错误以及大部分突发错误。对于遥测这种对实时性要求高、但对绝对可靠性要求不是极端苛刻的场景CRC8是性价比最高的选择。查表法的实现是这样的先预计算一个256字节的表格每个表项是索引值经过CRC计算后的结果。发送端和接收端用同一张表。计算时初始CRC值设为0然后对每个字节做crc table[crc ^ byte]。13个字节算下来13次查表和异或操作在48MHz的Cortex-M0上大概几微秒就完成了。static const uint8_t crc8_table[256] { 0x00, 0x31, 0x62, 0x53, 0xC4, 0xF5, 0xA6, 0x97, /* ... 省略中间项 ... */ 0x00, 0x31, 0x62, 0x53, 0xC4, 0xF5, 0xA6, 0x97 }; uint8_t crc8_calculate(const uint8_t *data, uint8_t len) { uint8_t crc 0x00; for (uint8_t i 0; i len; i) { crc crc8_table[crc ^ data[i]]; } return crc; }注意CRC表的生成可以用在线工具或者自己写脚本算但一定要确认多项式、初始值、反射配置和最终异或值这四个参数完全一致。我见过有人用在线工具生成的表但工具默认是CRC-8/MAXIM而代码里按CRC-8/ROHC的初始值0xFF来算结果校验永远对不上。2.3 字节序与数据打包的注意事项STM32是小端芯片也就是说一个16位变量在内存里是低字节在前、高字节在后。但网络传输和大多数协议习惯用大端也就是高字节在前。我在打包电压、电流、转速这些16位字段时统一转成大端发送。转换方法很简单static void pack_u16_be(uint8_t *buf, uint16_t val) { buf[0] (uint8_t)(val 8); buf[1] (uint8_t)(val 0xFF); }接收端解析时反过来static uint16_t unpack_u16_be(const uint8_t *buf) { return ((uint16_t)buf[0] 8) | buf[1]; }这里有个容易忽略的点如果发送端和接收端的字节序约定不一致数据会完全错乱。比如电压实际是12.60V打包成0x04EC大端发送是0x04 0xEC。如果接收端按小端解析会变成0xEC04也就是60420除以100得到604.20V明显不对。所以协议文档里一定要写清楚字节序。温度字段的处理稍微特殊一点。我用的是偏移编码实际温度加上40然后存成一个无符号字节。比如25度存成65-10度存成30。这样做的好处是避免了有符号数的符号扩展问题接收端减40就还原了。偏移量选40是因为电调工作温度范围大概在-20到125度之间加40之后都是正数一个字节够用。3. DMAUSART发送的完整实现过程3.1 CubeMX配置与初始化代码我用STM32CubeMX做外设初始化配置芯片选STM32F051K8Tx。配置步骤如下第一步在Pinout视图里找到USART1把Mode设成AsynchronousTX引脚PA9自动分配RX引脚PA10也分配上虽然遥测不用但调试时方便。波特率设115200字长8位无校验1位停止位。第二步在DMA Settings标签页里点Add选USART1_TX方向Memory to Peripheral优先级Medium模式Normal不是Circular。为什么要用Normal而不是Circular因为遥测帧是离散的发完一帧就停等下一帧准备好再启动。Circular模式会一直循环发送缓冲区里的内容不适合这种场景。第三步在NVIC Settings里使能DMA1_Channel2_3_IRQn中断优先级设成比电机控制中断低。这一点很关键遥测发送的中断不能抢占电机换相中断否则会导致电机抖动甚至失步。STM32F051的NVIC优先级分组设成2位抢占优先级、2位子优先级电机控制中断给抢占优先级0DMA中断给抢占优先级2或3。生成代码后MX_DMA_Init()和MX_USART1_UART_Init()会被自动调用。但要注意CubeMX生成的DMA初始化顺序有时候会有问题——DMA时钟使能必须在USART初始化之前否则USART的DMA请求配置会失败。我遇到过生成的代码里MX_DMA_Init()在MX_USART1_UART_Init()之后的情况导致DMA发送一直不工作。解决办法是在main()里手动调整调用顺序把DMA初始化提前。3.2 发送缓冲区的管理与双缓冲思路DMA发送的核心问题是缓冲区管理。如果只用一块缓冲区发送过程中不能修改它否则DMA搬走的数据就是错的。但遥测数据是周期性更新的10ms就要发一帧如果等DMA发完再准备下一帧中间会有空档。我用的方案是双缓冲准备两块缓冲区A和B。当前用A发送时往B里填充下一帧数据A发完了切换成用B发送往A里填充再下一帧。这样发送和准备可以并行不会互相阻塞。具体实现上用一个标志位tx_buf_index表示当前正在发送的缓冲区索引另一个标志位tx_busy表示DMA是否在忙。主循环里检查tx_busy如果空闲就启动下一次发送。#define TX_BUF_SIZE 16 static uint8_t tx_buf[2][TX_BUF_SIZE]; static volatile uint8_t tx_buf_index 0; static volatile uint8_t tx_busy 0; void telemetry_send(void) { if (tx_busy) return; uint8_t idx tx_buf_index; uint8_t len build_telemetry_frame(tx_buf[idx]); tx_busy 1; HAL_UART_Transmit_DMA(huart1, tx_buf[idx], len); tx_buf_index 1 - idx; }DMA传输完成回调里清除tx_busyvoid HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { tx_busy 0; } }注意HAL_UART_TxCpltCallback是在中断上下文里执行的里面不要做耗时操作。我一开始在里面直接调用build_telemetry_frame准备下一帧数据结果因为ADC读取和浮点运算耗时太长导致中断执行时间超过100微秒影响了其他中断的响应。后来改成只置标志位实际的数据准备放在主循环里做。3.3 发送触发时机的选择与定时器配置遥测发送的触发时机我用的是定时器中断。TIM14配置成10ms周期在中断里置一个telemetry_trigger标志主循环检测到这个标志就调用telemetry_send()。为什么不在定时器中断里直接调用telemetry_send()因为HAL_UART_Transmit_DMA内部会做一些状态检查和寄存器操作执行时间不确定。放在中断里可能拉长中断响应时间。用“中断置标志、主循环执行”的方式中断执行时间可以控制在几微秒以内。TIM14的配置时钟源用内部时钟48MHz预分频器设成47999这样计数器时钟是1kHz。自动重装载值设成9这样每10个计数周期产生一次中断也就是10ms。计算过程48MHz / (479991) 1kHz1kHz的周期是1ms计数到9就是10ms。void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM14) { telemetry_trigger 1; } }主循环里while (1) { if (telemetry_trigger) { telemetry_trigger 0; telemetry_send(); } /* 其他任务 */ }这里有个细节如果主循环里还有其他耗时任务比如电机换相计算可能会导致telemetry_send()的调用被延迟。延迟几毫秒问题不大但如果延迟超过10ms就会丢帧。我的做法是在主循环里把遥测发送的优先级设得比较高放在换相计算之前执行。3.4 数据采集与帧打包的实操细节电压采集用的是ADC1的通道0接在电池分压电路上。分压比是11:1也就是电池电压经过分压后是原来的1/11。ADC是12位精度参考电压3.3V。实际电压的计算公式是实际电压 ADC值 / 4095 * 3.3 * 11比如ADC读到3720实际电压 3720 / 4095 * 3.3 * 11 32.97V对应8S电池满电状态。电流采集用的是ADC1的通道1接在电流传感器的输出上。传感器是ACS712-30A灵敏度66mV/A零点输出是2.5V。电流计算公式实际电流 (ADC值 / 4095 * 3.3 - 2.5) / 0.066温度采集用的是MCU内部的温度传感器通道16。STM32F051的内部温度传感器精度不高但用来做趋势监测够了。计算公式参考数据手册温度 (V25 - ADC值 / 4095 * 3.3) / Avg_Slope 25其中V25是1.43VAvg_Slope是4.3mV/度。转速计算稍微复杂一点。AM32用反电动势过零检测来估算换相时机换相频率和电机转速成正比。我直接读取换相间隔的定时器计数值换算成RPM。具体公式和电机极对数有关RPM 60 * 定时器时钟 / (换相间隔 * 6 * 极对数)比如定时器时钟1MHz换相间隔1000极对数7则RPM 60 * 1000000 / (1000 * 6 * 7) 1428RPM。打包的时候电压和电流先乘以100转成整数再转成大端。温度加40转成无符号字节。转速除以100转成整数。错误码从全局状态变量里读取。uint8_t build_telemetry_frame(uint8_t *buf) { uint16_t voltage (uint16_t)(get_voltage() * 100); uint16_t current (uint16_t)(get_current() * 100); uint8_t temp (uint8_t)(get_temperature() 40); uint16_t rpm (uint16_t)(get_rpm() / 100); uint8_t err get_error_flags(); uint8_t idx 0; buf[idx] 0xAA; buf[idx] 0x55; buf[idx] 0; /* 长度占位 */ pack_u16_be(buf[idx], voltage); idx 2; pack_u16_be(buf[idx], current); idx 2; buf[idx] temp; pack_u16_be(buf[idx], rpm); idx 2; buf[idx] err; buf[2] idx 1; /* 长度 当前索引 CRC字节 */ buf[idx] crc8_calculate(buf, idx); idx; return idx; }4. 调试过程中踩过的坑与排查方法4.1 DMA发送不启动的几种原因第一次调DMA发送的时候代码编译通过烧进去之后串口助手什么都没收到。排查过程记录如下先确认USART本身能不能发。把HAL_UART_Transmit_DMA换成HAL_UART_Transmit阻塞发送串口助手能收到数据。说明USART配置没问题问题出在DMA上。检查DMA时钟使能。在MX_DMA_Init()里应该有__HAL_RCC_DMA1_CLK_ENABLE()。用调试器看DMA1的时钟使能寄存器确认时钟已经打开。检查DMA通道配置。USART1_TX对应DMA1_Channel2这个在STM32F051上是固定的。看hdma_usart1_tx.Init结构体里的配置方向是DMA_MEMORY_TO_PERIPH外设地址是huart1.Instance-TDR内存地址是缓冲区地址数据长度是帧长度模式是DMA_NORMAL外设和内存的数据宽度都是DMA_PDATAALIGN_BYTE和DMA_MDATAALIGN_BYTE。发现一个问题CubeMX生成的代码里hdma_usart1_tx的初始化是在MX_DMA_Init()里做的但HAL_UART_Transmit_DMA内部会检查huart-hdmatx指针是否为空。如果DMA初始化在USART初始化之后huart-hdmatx可能还没赋值。解决办法是在MX_USART1_UART_Init()之后手动调用__HAL_LINKDMA(huart1, hdmatx, hdma_usart1_tx)或者调整初始化顺序。还有一个坑DMA的中断优先级配置。如果DMA中断优先级低于某个正在执行的中断DMA传输完成中断会被延迟响应导致tx_busy标志清除不及时下一帧发送被跳过。我把DMA中断优先级设成1比定时器中断优先级0低但比主循环里的其他任务高。4.2 遥测数据错乱的排查思路数据能收到了但解析出来电压是0电流是负数温度是200多度。这种问题一般是数据打包或解析的字节序搞错了。先用调试器看发送缓冲区里的原始字节。在HAL_UART_Transmit_DMA调用之前打个断点查看tx_buf的内容。假设电压是12.60V打包后应该是0x04 0xEC。如果看到的是0xEC 0x04说明打包时没有转大端。再看接收端的解析代码。如果发送端是大端接收端也要按大端解析。我一开始接收端用的是memcpy直接拷贝到uint16_t变量在小端机器上就变成了小端解析数据全错。温度200多度的问题检查偏移量。如果发送端加了40接收端忘了减4025度就变成了65度。如果接收端减了40但发送端没加25度就变成了-15度转成无符号就是241度。电流负数的问题ACS712的零点输出是2.5V但实际电路中可能有偏移。我用万用表量了传感器输出零电流时是2.52V不是理想的2.5V。在代码里把零点校准值改成2.52V后零电流时读数就接近0了。4.3 CRC校验失败的常见原因CRC校验失败的表现是接收端算出的CRC和帧里的CRC字节不一致。排查步骤第一步确认CRC表的生成参数。我用的是CRC-8/MAXIM多项式0x31初始值0x00不反射最终异或0x00。用在线工具生成表的时候要确认这些参数都选对了。我一开始选成了CRC-8/ROHC初始值0xFF结果校验永远失败。第二步确认CRC计算的范围。我定义的是从帧头到错误码的所有字节不包括CRC字节本身。如果计算范围多算了或少算了字节结果就不对。在代码里用crc8_calculate(buf, idx)其中idx是CRC字节之前的长度。第三步确认发送端和接收端用的是同一张表。如果发送端用查表法接收端用逐位计算法只要参数一致结果应该相同。但如果参数不一致就会出问题。我建议发送端和接收端用同一份代码避免不一致。第四步检查是否有字节在传输过程中被修改。比如DMA传输过程中如果缓冲区被其他代码修改了CRC就会对不上。用双缓冲可以避免这个问题但要注意切换缓冲区的时机。4.4 常见问题速查表现象可能原因排查方法解决办法串口收不到任何数据DMA时钟未使能查看RCC寄存器在DMA初始化里使能时钟收到数据但全是乱码波特率不匹配示波器测TX引脚波形检查系统时钟和BRR寄存器数据偶尔错乱缓冲区被覆盖在DMA发送时打断点改用双缓冲CRC校验失败多项式或初始值不对对比在线工具参数统一CRC参数电压读数偏大或偏小分压比计算错误万用表实测分压后电压重新计算分压比电流零点漂移传感器零点偏移零电流时读ADC值校准零点偏移温度读数异常偏移量未处理检查发送和接收的偏移统一加40减40DMA发送间隔不稳定中断优先级冲突查看NVIC配置调整DMA中断优先级发送几帧后停止DMA传输完成中断未清除标志在回调里打断点确保tx_busy被清除电机转动时遥测丢帧遥测发送阻塞了电机控制测量主循环执行时间降低遥测优先级或缩短帧长4.5 几个实用的调试技巧用串口助手调试的时候把接收模式设成HEX显示不要用ASCII。遥测帧里有很多不可打印字符ASCII模式下看起来就是一堆问号没法分析。如果手头有逻辑分析仪抓一下TX引脚的波形可以直接看到每个字节的起始位、数据位、停止位确认波特率是否准确。115200波特率下一个位的宽度是8.68微秒一个字节10位是86.8微秒。如果测出来位宽偏差超过3%接收端就可能解析错误。在代码里加一个调试计数器记录发送帧数、CRC错误帧数、DMA忙跳过帧数。通过串口打印出来可以快速判断遥测系统的健康状态。比如发送1000帧CRC错误0帧DMA忙跳过2帧说明系统运行很稳定。提示调试遥测的时候先把电机断开只给MCU供电。这样可以排除电机干扰对串口通信的影响。等遥测数据稳定了再接上电机测试。我遇到过电机一转遥测就乱码的情况后来发现是电机线离TX线太近耦合了噪声。把TX线远离电机线并在TX线上串一个100欧姆的电阻问题就解决了。5. 性能优化与扩展思路5.1 降低CPU占用率的几个手段虽然DMA已经把CPU占用率降得很低了但在STM32F051这种48MHz的M0核上还是可以再抠一抠。第一个手段是减少帧长度。13个字节的帧在115200波特率下需要1.13ms才能发完。如果降到10个字节就只要0.87ms。省掉的字节可以从错误码和转速里抠——错误码其实可以合并到状态字节里转速精度要求不高的话可以只用一个字节。第二个手段是降低发送频率。10ms发一帧每秒100帧。如果改成20ms一帧每秒50帧CPU占用率直接减半。对于电压电流这种缓变量50Hz的更新率完全够用。只有转速可能需要高一点的更新率但也可以单独处理。第三个手段是用DMA的Half-Transfer中断。DMA传输一半的时候产生中断可以在中断里准备后半段数据实现流水线式的发送。不过这个对遥测这种短帧意义不大帧太短了半传输中断和完成中断几乎同时发生。第四个手段是关闭USART的TX中断。用DMA发送时USART的TXE中断和TC中断都不需要可以在初始化时关掉减少不必要的中断开销。5.2 从单向上报扩展到双向通信现在的方案是电调单向发送飞控只收不发。如果要做双向通信比如飞控给电调发参数配置命令就需要用到USART的RX功能。RX用DMA接收的话配置和TX类似但要注意几点RX是Peripheral to Memory方向DMA通道是USART1_RX对应的通道3。接收长度不确定所以要用Circular模式或者配合空闲中断IDLE来检测一帧结束。空闲中断的思路是DMA一直开着接收数据来了自动搬到缓冲区。当总线空闲超过一个字节时间USART产生IDLE中断在中断里读取DMA的剩余计数算出实际接收了多少字节然后处理数据。处理完后重新设置DMA接收长度准备下一帧。void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(huart1); HAL_UART_DMAStop(huart1); uint8_t len RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(hdma_usart1_rx); process_rx_data(rx_buf, len); HAL_UART_Receive_DMA(huart1, rx_buf, RX_BUF_SIZE); } }双向通信的协议设计要比单向复杂一些需要定义命令码、应答机制、超时重传等。如果只是偶尔改个参数也可以用单向遥测帧里的保留位来传递简单命令不一定非要开RX。5.3 不同MCU平台的移植注意事项AM32支持的MCU不止STM32F051还有STM32F103、AT32F421等。移植遥测代码的时候主要改几个地方DMA通道映射。STM32F103的USART1_TX是DMA1_Channel4不是F051的Channel2。AT32F421的DMA映射又不一样。这个必须查对应芯片的参考手册不能想当然。时钟配置。不同芯片的APB总线时钟频率不同波特率计算参数要重新算。HAL库会自动算但要确保系统时钟配置正确。中断向量名。STM32F051的DMA1_Channel2_3_IRQHandler在F103上可能是DMA1_Channel4_IRQHandler。用CubeMX生成代码的话会自动处理手动移植的话要注意改。GPIO复用功能。USART1的TX引脚在F051上是PA9的AF1在F103上也是PA9的AF0。AF编号不同配置的时候要查数据手册。Flash和RAM大小。F051只有64KB Flash和8KB RAMF103C8有64KB Flash和20KB RAMAT32F421有64KB Flash和16KB RAM。如果代码里用了大的查找表或者缓冲区移植到RAM小的芯片上要缩减。5.4 遥测数据在飞控端的应用电调把遥测数据发出来只是第一步飞控端怎么用这些数据才是最终目的。电压数据可以用来做电池低压告警。飞控设置一个阈值比如3.5V每节低于这个值就在OSD上闪烁警告。更高级的用法是做电压补偿——根据当前电压调整PID参数电压低的时候适当降低P值避免震荡。电流数据可以用来估算剩余电量。配合电池容量对电流做积分得到已消耗的mAh再用总容量减去已消耗的就是剩余电量。这个比单纯看电压准确得多因为电压会随着负载波动。温度数据可以用来做降额保护。电调温度超过100度时飞控可以主动限制油门输出防止电调过热烧毁。转速数据可以用来做电机堵转检测。如果油门给了但转速上不去说明电机可能堵转了飞控可以及时切断输出保护电调。错误码是最直接的告警来源。任何一个错误位置位飞控都应该在OSD上显示对应的告警信息并记录到黑匣子里供事后分析。我在实际使用中发现遥测数据最有价值的场景是调参。以前调PID只能靠感觉现在有了转速和电流数据可以直观地看到电机响应是否线性、有没有过冲、电流峰值是多少。这些数据让调参从玄学变成了科学。最后再分享一个小技巧如果飞控端不支持自定义遥测协议可以把AM32的遥测帧转换成飞控支持的格式。比如用一块小MCU做协议转换一边收AM32的遥测帧一边按飞控要求的格式转发。这样不用改飞控固件就能用上遥测数据。
返回列表