ARTICLE DETAIL

资讯详情

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

将moonglow固件移植到APM32F072,DIY低成本USB-CAN分析仪

将moonglow固件移植到APM32F072,DIY低成本USB-CAN分析仪 做USB-CAN分析仪很多人第一步就卡在硬件成本上。市售的周立功、CANalyst系列好用是好用但价格摆在那国产的或者DIY方案又总在驱动兼容性上让人头疼。这次分享的项目就是要把开源的moonglow固件移植到极海APM32F072这颗MCU上刷出一个能被kvaser官方驱动直接识别的USB-CAN设备。先把这个项目的价值说清楚。moonglow固件在STM32F072上很成熟而APM32F072是引脚兼容、外设寄存器也高度兼容的国产替代方案价格和供货都更有优势。移植成功之后你手里这根USB-CAN线插到电脑上会被识别成kvaser的某款设备直接用kvaser的驱动和上层软件稳定性和兼容性都有保障。整个过程涉及USB协议栈移植、CAN控制器配置、时钟树调整、驱动签名处理等多个环节适合想深入理解USB设备枚举、CAN通信底层逻辑或者想低成本搭建调试工具的朋友。1. 项目整体设计与思路拆解1.1 为什么选APM32F072而不是直接用STM32这颗芯片的选型逻辑其实很直接。APM32F072是极海半导体基于ARM Cortex-M0内核的产品和意法半导体的STM32F072在封装、引脚定义、内部框图上都做了兼容设计。这意味着为STM32F072写的工程理论上可以直接套用到APM32F072上只要注意时钟树和外设寄存器的细微差异即可。实际对比过两颗芯片的参考手册之后会发现APM32F072的GPIO、USART、I2C、SPI这些外设的寄存器地址和位定义几乎一模一样USB设备控制器和CAN控制器的布局也沿用了同样的思路。这种兼容性带来的好处非常明显moonglow这种原本为STM32F072开发的开源固件移植工作量被大幅压缩不需要从零重写驱动层只需要针对差异点做适配。从成本角度看APM32F072的采购价通常比同封装的STM32F072低一截而且供货稳定性更好特别是在芯片缺货周期里国产替代方案的价值就会被放大。如果你是企业做产品这种开源固件加兼容芯片的思路可以直接降低BOM成本如果你是个人玩家买几颗APM32F072回来折腾也不心疼。1.2 moonglow固件的工作原理moonglow固件本质上是一个USB-CAN适配器的完整实现它的核心工作有两件事一是让MCU的USB外设枚举成一个kvaser兼容设备二是把USB协议包和CAN报文之间做双向转换。先说USB枚举这块。moonglow固件里写死了VID和PID模拟的是kvaser Leaf Light HS这个型号。当设备插入电脑USB主机也就是操作系统会通过标准描述符请求来查询设备信息固件会响应这些请求上报厂商、产品、序列号、端点配置等信息。操作系统找到这些描述符之后如果安装了对应的kvaser驱动就会认为这是一个合法的kvaser设备从而加载驱动并为上层应用提供统一的访问接口。再说CAN报文转换。上层软件调用kvaser的CANlib API发送CAN报文时驱动会把数据封装成特定的USB批量传输包通过USB总线送给MCU。MCU的固件收到这个包之后按照moonglow定义的协议格式解析出CAN ID、数据长度、数据内容再写入CAN控制器的发送邮箱。反过来CAN总线上收到报文时CAN控制器产生接收中断固件在中断服务函数里读出报文再封装成USB数据包发送给上位机。这两条路径的转换效率决定了设备的实时性表现。moonglow固件在中断处理上做了不少优化比如CAN接收中断里只做数据搬移和标志位设置具体的协议封装放到主循环里处理这样能减少中断占用时间降低丢包概率。1.3 移植方案选型对比做移植之前我整理过几条可行的路线简单对比一下供参考。方案工作量稳定性适用场景原版moonglow直接编译烧录小差仅临时验证修改晶振配置后移植中较好低成本DIY完整适配时钟树和外设中大好产品批量生产从零基于CubeMX重写大可控需要深度定制为什么原版固件直接烧录不行因为moonglow项目默认的时钟配置是针对STM32F072的HSE晶振参数写的通常是8MHz或25MHz但如果你的APM32F072板子上用的晶振是12MHz或者16MHzUSB外设的48MHz时钟就出不来USB枚举就会失败。所以无论如何时钟树配置是绕不开的第一步。2. 硬件准备与平台选型分析2.1 核心硬件清单这次移植我用到的硬件比较常规如果你手里有其他型号的板子只要满足以下条件也可以参考APM32F072VBT6或者RBT6最小系统板前者是100引脚LQFP封装后者是64引脚区别在于GPIO数量和外设引出情况。板载USB接口需要把PA11和PA12引出到USB座的D-和D并且D线上要有1.5kΩ上拉电阻这是USB全速设备识别的硬件基础。CAN收发器芯片推荐TJA1050或者SN65HVD230负责把CAN控制器的TTL电平转换为CAN总线的差分信号。注意APM32F072的CAN控制器是3.3V供电而TJA1050是5V供电需要用电平转换或者直接用支持3.3V供电的收发器。8MHz晶振虽然有些板子用HSE旁路模式从外部直接输入时钟但独立晶振的方案更稳定对后续调试验证也更友好。一个USB转TTL模块用于调试串口输出方便打印调试信息。2.2 APM32F072的关键外设细节这颗芯片最让我满意的是它的USB外设。APM32F072内置的USB设备控制器支持全速12Mbps内部集成48MHz时钟发生器这意味着USB外设可以由内部PLL直接驱动不依赖外部晶振分频。不过要注意虽然USB可以内部生成时钟但系统主时钟和CAN外设的时钟源还是需要外部晶振所以板子上的8MHz晶振不能省。CAN控制器方面APM32F072集成了bxCAN模块支持CAN协议2.0A和2.0B有3个发送邮箱、1个接收FIFO和28个滤波器组。这些资源和STM32F072的bxCAN是一致的所以moonglow固件里对CAN寄存器的操作代码可以直接用。唯一需要留意的是CAN控制器的时钟源在STM32上通常配置为PCLK1也就是APB1总线时钟这个在APM32上同样适用。2.3 开发环境搭建开发环境我用的Keil MDK因为moonglow原版工程就是MDK项目的格式直接用最省事。需要说明的是如果你用的是其他IDE比如IAR或者GCC ARM Embedded移植工程需要手动处理启动文件和链接脚本工作量会大一些。MDK需要安装APM32F072的设备支持包极海官网提供DFP包下载安装之后就能在器件列表里直接选到APM32F072。如果找不到这个型号也可以选择STM32F072来编译但烧录之后可能会有不可预知的问题不建议这么干。还有一个容易被忽略的点烧录工具。APM32F072支持SWD调试接口用ST-Link V2或者DAP-Link都能连上。不过极海原厂的烧录工具也可以使用J-Flash配合SWD也是常见方案。关键是要确保调试器的固件版本和驱动都更新到比较新的状态否则可能在连接时出现识别不到芯片的问题。3. 固件移植核心步骤详解3.1 时钟树配置修改时钟树是整个移植过程中最关键的一步这里踩坑概率极高。moonglow原版固件的时钟配置代码在system_apm32f0xx.c或者类似命名的文件里裸机代码一般直接操作RCC寄存器来配置PLL。目标是生成以下时钟树系统主时钟48MHz需要确认是否满足USB要求通常配置为48MHzAPB1总线时钟48MHzAPB2总线时钟48MHzUSB外设时钟48MHz必须精确否则USB枚举失败如果板载晶振是8MHz配置方法如下/* 启用HSE */ RCC_HSEConfig(RCC_HSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) RESET) {} /* 配置PLL8MHz * 6 48MHz */ RCC_PLLConfig(RCC_PLLSource_HSE, RCC_PLLMul_6); RCC_PLLCmd(ENABLE); while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) RESET) {} /* 切换系统时钟到PLL */ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); while (RCC_GetSYSCLKSource() ! 0x08) {}如果你板子上的晶振是16MHz那PLL倍频系数就是3即16 * 3 48MHz。这个系数必须精确计算不然USB外设无法正常工作。我见过有人在16MHz晶振的板子上直接套用8MHz的倍频配置结果系统主时钟跑到96MHz内部Flash等待周期不够程序直接跑飞USB枚举必然失败。3.2 GPIO引脚复用配置APM32F072的GPIO复用功能配置是另一个容易踩坑的地方。CAN收发器的TX和RX引脚通常连接到PB8和PB9这两根引脚可以复用为CAN1_TX和CAN1_RX。GPIO_InitTypeDef GPIO_InitStructure; /* CAN TX引脚推挽输出 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_UP; GPIO_Init(GPIOB, GPIO_InitStructure); /* CAN RX引脚浮空输入 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOB, GPIO_InitStructure); /* 引脚复用映射PB8 - CAN1_TX, PB9 - CAN1_RX */ GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_4); GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_4);USB的DP和DM引脚则是PA11和PA12也需要配置为复用模式。有些板子上的USB D上拉电阻由MCU的PA10引脚控制这种设计是为了支持软件控制USB断开重连。moonglow原版固件里如果包含了这个逻辑你需要确认PA10的GPIO配置是否正确。一个小细节PA11和PA12的GPIO速度要配置成最高档不然高速信号边沿不够陡峭可能导致USB信号质量下降枚举偶尔失败或者传输不稳定。3.3 USB设备描述符调整moonglow固件模拟的是kvaser Leaf Light HS这个型号的设备描述符、配置描述符、接口描述符和端点描述符都是固定的。在移植过程中如果使用原版固件这些描述符不需要改动直接沿用即可。不过有一个点需要特别注意序列号字符串描述符。kvaser驱动会对设备的序列号做校验如果多个设备序列号相同驱动可能会无法分辨。如果你的USB-CAN设备只有一个序列号重复影响不大但如果要同时接入多个分析仪最好修改固件里的序列号字符串保证每个设备唯一。另外如果你希望设备在设备管理器中显示为其他型号比如kvaser U100或者kvaser Memorator需要同时修改VID/PID以及接口描述符里的产品字符串。但前提是驱动对这些型号的固件协议实现要完全兼容否则会出现枚举成功但无法发送CAN报文的情况。3.4 CAN波特率配置CAN波特率的计算直接关系到Can总线上各节点能否正常通信。moonglow固件默认配置的波特率是500kbps这在汽车电子和工业控制领域是最常用的。波特率计算公式为BaudRate APB1Clock / (Prescaler * (Segment1 Segment2 SyncSeg))APB1时钟是48MHz目标波特率是500kbps我采用的参数如下预分频值4同步段1个时间量子相位段112个时间量子相位段23个时间量子重同步跳转宽度1个时间量子计算一下48MHz / (4 * (12 3 1)) 48MHz / 64 750kHz这个结果是750kbps不是500kbps。需要调整参数。我们来正确计算标准公式是BaudRate APB1Clock / (Prescaler * (BS1 BS2 SyncSeg))SyncSeg固定为1BS1和BS2的单位是时间量子。对于500kbps48MHz系统时钟下总的分频系数应该是48MHz / 500kbps 96。所以预分频值 Prescaler 6BS1 BS2 SyncSeg 96 / 6 16取BS1 13, BS2 2, SyncSeg 1则13 2 1 16满足要求这样实际波特率 48MHz / (6 * 16) 500kHz同时采样点 (1 BS1) / (1 BS1 BS2) 14 / 16 87.5%这个采样点位置对CAN总线来说是比较合理的。因为APM32和STM32的库函数接口不同moonglow原版固件中如果是用寄存器操作的方式配置bxCAN可以直接用如果用的是标准外设库的BSP接口需要做函数名和参数格式的适配。4. 编译烧录与上位机验证4.1 环回模式功能验证在把固件刷进芯片之前强烈建议先验证芯片本身和固件的基本功能这时候CAN环回模式Loopback Mode就能派上用场。环回模式不需要外接CAN收发器也不需要总线上有其他节点只需要把CAN控制器的SILM位和LBKM位置1发送的报文就会自动被接收邮箱收到。CAN_InitStructure.CAN_TTCM DISABLE; CAN_InitStructure.CAN_ABOM ENABLE; CAN_InitStructure.CAN_AWUM DISABLE; CAN_InitStructure.CAN_NART ENABLE; CAN_InitStructure.CAN_RFLM DISABLE; CAN_InitStructure.CAN_TXFP DISABLE; CAN_InitStructure.CAN_Mode CAN_Mode_LoopBack; CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_13tq; CAN_InitStructure.CAN_BS2 CAN_BS2_2tq; CAN_InitStructure.CAN_Prescaler 6; CAN_Init(CAN_InitStructure);发送几帧测试报文如果能正常进入发送完成中断并且接收邮箱能收到自己的帧说明CAN控制器工作正常。这一步隔离了USB和CAN之间的依赖方便排查问题。如果环回模式下都收不到数据那就是CAN初始化有问题先看看时钟和GPIO复用配置。4.2 正常模式数据收发测试环回模式通过后把工作模式切换回正常模式接上CAN收发器和真实的CAN总线网络。使用USB-CAN分析仪配合kvaser的免费软件CANking或者使用canoe功能更完整但价格昂贵都可以直观地检查收发情况。CANking是kvaser官方的免费调试软件如果你刷的固件模拟的是kvaser Leaf Light HS驱动安装好之后CANking就能直接识别到设备。在CANking里设置好波特率连上总线就能看到总线上的报文流动。你可以试着往总线上发一帧数据再用另一个CAN节点接收确认数据没有丢帧或者错位。我这里测下来用500kbps的波特率连续发送10000帧数据丢帧数为0收发稳定性非常可靠。4.3 kvaser驱动安装与设备识别驱动这块是整个项目能不能正常使用的门槛。因为我们模拟的是kvaser设备所以要装kvaser官方的驱动。去kvaser官网下载对应平台的驱动程序安装之后插入USB-CAN设备正常情况下系统会提示找到新硬件并自动安装驱动。打开设备管理器找到kvaser设备右键进入属性如果显示设备工作正常说明枚举成功。此时用kvaser自带的工具软件控制面板可以看到设备的型号、序列号、固件版本等信息。如果这里能看到信息说明USB枚举和驱动匹配都成功了整个链路已经打通。有个细节要提一下Windows对未签名的驱动有加载限制kvaser官方驱动是签名过的因此不存在这个问题。但如果你在开发中自己修改过固件导致PID变化操作系统可能会认为这是一个未知设备而不是kvaser设备这时需要重新安装匹配的驱动或者手动指向驱动文件。另外macOS和Linux下kvaser也有对应的驱动但兼容性测试我没有深入做过建议主力验证环境还是用Windows。5. 常见问题与排查技巧实录5.1 设备管理器显示未知设备这个占到了我遇到的七成以上的问题。未知设备意味着USB枚举没有完成主机没有读到有效描述符。排查步骤如下用USB分析仪或者逻辑分析仪看D和D-波形确认设备是否发出了复位响应和描述符数据。没有逻辑分析仪的话可以看电脑右下角是否弹出“无法识别的USB设备”提示这说明总线层面至少检测到了设备插入。回到代码检查USB外设时钟是否真正配置为48MHz。最简单的方法是打断点或者加调试打印看寄存器里的PLL配置是否生效。检查PA11和PA12的GPIO配置确认有没有设置成复用推挽并开启上拉。有人用标准库把GPIO配置成普通输入然后USB就完全不动。检查USB D上拉电阻是否生效。全速设备要求D线上有一个1.5kΩ上拉如果这个电阻没焊或者虚焊主机永远只能识别到设备插入但拿不到速度协商信号。也要注意有些开发板上的D上拉由IO口控制程序初始化时要先把对应引脚输出高电平。5.2 驱动安装后无法正常通信枚举成功说明USB链路是通的但无法和上位机通信问题大概率出在两个地方。第一种是接口描述符的端点数、端点地址或者最大包长度和驱动预期不一致。kvaser驱动对端点的使用有严格要求通常使用一个批量输入端点和一个批量输出端点最大包长度64字节。如果固件里配置的端点不对驱动加载了但通讯时就会超时或者报错。第二种是USB-CAN应用层的通信协议和kvaser实际使用的协议格式不一致。moonglow固件本身实现了对kvaser协议格式的兼容但如果你用的是修改版或者移植不完整应用层数据格式对不上设备管理器和驱动都能识别设备但CANking发送报文时会一直卡在等待ACK状态。这种情况要在固件里对照kvaser开源协议的格式定义逐个字段核对。5.3 CAN报文发送超时CAN报文发送超时一般从两个方向排查硬件方向先看CAN收发器的TXD和RXD引脚波形。如果TXD引脚没有波形说明MCU没有正常驱动CAN控制器如果TXD有波形但RXD没有说明总线没有ACK或者收发器挂了。用示波器看差分波形最直接没有示波器时可以用万用表量CANH和CANL之间的电压静态时约2.5V发送显性位时CANH比CANL高约2V。软件方向检查发送请求位是否正常置位和清零。bxCAN发送邮箱的状态机制是置位TXRQ请求发送发送完成后硬件自动清零RQCP标志。如果一直卡在TXRQ1但RQCP0就说明报文没有被总线ACK可能是总线上缺少终端电阻或者波特率不匹配。5.4 波特率异常波特率不匹配的表现是发送方能发出数据但接收方收到的全是错误帧或者接收方根本没反应。CAN协议里有一个错误计数器如果错误计数器持续累加最终超过255节点就会进入Bus-Off状态彻底退出总线通信。排查时用CANking的Bus Traffic功能去观察总线上是否有错误帧。如果是波特率问题错误帧的间隔会比较规律。这时要么调整总线节点的波特率配置要么修改固件里的分频值和采样点参数。5.5 常见问题速查表现象可能原因解决方案设备无法枚举时钟树配置错误检查PLL配置确认USB时钟为48MHz枚举成功但设备为未知设备D上拉电阻缺失检查硬件电路确保1.5kΩ上拉驱动加载错误VID/PID不匹配与kvaser Leaf Light HS的VID/PID核对可以枚举但不能通信端点描述符不匹配对照moonglow原版的端点配置检查CAN发送超时缺少终端电阻在CANH和CANL之间并联120Ω电阻CAN间歇性错误帧波特率设置错误用示波器测量实际波特率校准分频系数拔插后蓝屏或死机驱动冲突卸载旧驱动重新安装kvaser官方最新驱动固件下载失败芯片读保护开启在J-Flash里解除读保护再烧录这组表基本覆盖了我测试中遇到过的九成问题。如果你测试时碰到表格里面没有的情况优先从硬件时序上去找问题特别是那些偶发性的故障大概率是信号完整性问题。6. 延伸固件差分升级的思路参考这里提一下“固件差分升级”这个热点。USB-CAN设备刷了开源固件之后后续如果固件开发者发布了新版本每次都要重新打开烧录工具走一遍流程对大量生产时特别麻烦。差分升级的思路是把新旧固件做二进制对比只把差异部分的补丁数据通过USB传给设备设备端把补丁写入应用区实现原地升级。具体做法是先在你电脑上准备好新固件的bin文件和当前设备里运行的旧固件做差分生成一个补丁包格式可以自定义比如包含目标地址、数据长度、CRC校验值、数据块。然后用USB-CAN设备自带的升级指令把补丁包下发到MCU的bootloader区bootloader解析后把差异数据写入应用区写入完成后跳转到应用区运行。这个流程能大幅减少升级时间每次升级只需要传几百字节的增量数据而不是整个几十KB的固件镜像。这个思路特别适合你手头已经有几十上百台USB-CAN设备要维护的场景配合开源固件整套工具链都是可控的。7. 移植经验与避坑心得移植工作完成后总结几点实操中容易忽略的东西。首先备份你要修改的固件工程。moonglow工程本身是开源的但不同版本的代码差异不小。记得记录原始版本这样改坏了可以随时回退。我一般用Git管理每个改动节点都提交一次出了问题可以精确回滚到某一版。其次建议准备一个逻辑分析仪或者示波器。USB波形和CAN波形是排查问题时的第一手证据很多疑难杂症靠看代码很难发现但一看波形就明白了。如果没有示波器至少准备几个LED接在GPIO上用代码控制翻转来观察程序运行到哪个位置。再次关于固件和驱动的匹配。moonglow固件更新几代后协议格式可能做了微调。如果你的软硬件组合一开始能正常用后来升了一下驱动版本突然不能用了大概率是驱动读操作的返回包结构和固件不匹配。处理办法是尽量固定驱动版本或者使用和固件版本配套的旧驱动。最后再提一个容易忽略的细节。APM32F072的Flash等待周期在48MHz下需要配置为2个等待周期。如果等待周期配置不正确系统会在高频运行时随机死机表现为设备间歇性失联。这个现象非常隐蔽我一度以为是电路干扰问题后来翻手册才发现是Flash等待周期没设置对。用这套方案做出来的USB-CAN分析仪综合成本比市售产品低很多而且因为moonglow固件和kvaser驱动的兼容性在主流调试软件下的使用体验和原装设备几乎没有差别。唯一需要注意的是如果项目涉及商业用途要仔细阅读moonglow项目的开源许可证确认满足合规使用的要求这一点还是不能马虎的。
返回列表