ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

APM32F072移植moonglow实现Kvaser兼容USB-CAN分析仪实战

APM32F072移植moonglow实现Kvaser兼容USB-CAN分析仪实战 手里有APM32F072开发板想把它做成一个带Kvaser协议兼容的USB-CAN分析仪用Kvaser官方软件直接收发报文——这个需求听起来小众但实际做起来非常有意思而且能省下一大笔买原装进口CAN卡的钱。moonglow这个开源项目恰好就是干这件事的它让你手头的普通USB-CAN硬件刷上固件之后直接变成Windows下免驱识别的Kvaser设备。这篇文章我按自己实际折腾的完整流程来写从选型依据、编译环境、移植改动到烧录验证、踩坑记录全部记录下来。目标是把APM32F072这颗芯片跑通moonglow/kvaser固件的整个路径讲清楚你在复现的时候能少走几步弯路。1. 为什么选APM32F072做USB-CAN不只是“便宜大碗”很多人一提到USB-CAN适配器第一反应是STM32F103系列毕竟资料多、用的人多。但如果你真的打算跑moonglow这类固件F103其实是个比较尴尬的选择——它的USB控制器是Device模式做全速USB没问题但架构本身就比较老DMA支持和中断响应在高速CAN报文场景下会显得吃力。相比之下APM32F072属于M0内核虽然是Cortex-M0但它的USB模块是带内部收发器的全速USB Device配合CAN控制器恰好踩在moonglow固件的设计预期上。1.1 极海APM32F072和STM32F072的兼容性APM32F072是极海半导体推出的、和STM32F072引脚级兼容的32位MCU。这个“引脚级兼容”意味着PCB不用改直接把STM32F072的封装替换成APM32F072就能跑同一套原理图。更关键的是它的SVD文件和寄存器映射基本对齐了ST原厂所以moonglow里针对STM32F072写的USB和CAN驱动代码理论上可以直接复用。这颗芯片的特点很明确内核ARM Cortex-M0主频48MHzFlash128KBSRAM16KBUSB全速Device内置收发器CAN1路CAN 2.0B带3个发送邮箱、2个接收FIFO供电2.8V~3.6VUSB口供电完全没问题moonglow固件对硬件的核心需求是一个USB Device接口一个CAN控制器再加上足够的Flash空间存固件。APM32F072这三点全都满足而且ST生态的调试工具链可以直接用这就省去了搭新环境的大量麻烦。1.2 moonglow固件到底做了什么moonglow是一个基于ECAN项目的开源固件目标很简单让兼容Kvaser通讯协议的硬件刷上它之后能够被Kvaser官方软件如Kvaser CANKing、Kvaser Database Editor等直接识别和使用。说白了就是把普通CAN卡“升级”成支持Kvaser协议的高端分析仪。固件的核心价值有两块第一协议层实现。Kvaser设备走的是USB批量传输自定义的CAN报文封装格式。moonglow用ST的USB库把端点配置好再在协议层把Kvaser的报文格式解析出来映射到CAN控制器的收发FIFO里。这部分代码逻辑清晰适合作为学习USB-CAN协议栈的入门范例。第二免驱能力。Windows下Kvaser设备有自己的驱动moonglow刷进去之后设备插上电脑会被识别成Kvaser旗下对应的型号这时候装好Kvaser官方驱动系统就把它当成一个正规CAN卡来用。所以整个项目的技术链条是这样的APM32F072的USB模块负责和PC通信CAN模块负责总线上报文的收发固件内部做协议转换——一边是Kvaser的USB协议一边是CAN 2.0B的帧格式。1.3 为什么推荐直接买APM32F072核心板来玩动手之前先泼盆冷水如果你只是想快速体验moonglow/Kvaser固件不要从零画板子。万用板手飞线搞USB和CAN走线信号完整性会很痛苦尤其USB的D/D-差分对手工焊接飞线试试就知道了。而APM32F072的核心板现在很便宜二三十块钱一片板上已经把USB座子、CAN收发器、稳压电路全做好了拿来直接就能刷固件。我自己用的就是一块最小系统板主控APM32F072C8T6板载一个USB TYPE-C座子和一个TJA1050 CAN收发器。烧录口是SWD四根线就能搞定。这块板子刷完moonglow之后插电脑直接识别为“Kvaser Leaf Light HS v2”属实香。2. moonglow固件的编译准备从拉代码到出hex前置环境没有太多花活但第一次搞的时候确实有几个地方容易卡住。我按自己实际敲过的命令和流程来讲。2.1 交叉编译工具链的安装moonglow的Makefile基于ARM GCC工具链用arm-none-eabi-gcc编译。系统环境我用的Ubuntu 22.04在Windows底下用WSL也可以。安装工具链的方式很简单Ubuntu直接APT装sudo apt update sudo apt install gcc-arm-none-eabi make git有个细节线上源的arm-none-eabi-gcc版本可能不是最新但编译moonglow足够了。真正要留意的是make版本太老的GNU Make在一些目标规则的解析上会报错建议确保make在4.x以上make --version2.2 拉取moonglow源码和子模块moonglow项目基于ECAN仓库里有一些USB库、CMSIS相关的子模块直接git clone不会自动拉子模块所以必须用--recursive参数git clone --recursive https://github.com/moonglow/ecan.git cd ecan如果你已经clone完了才发现没带上子模块也可以补拉git submodule update --init --recursive这一步必须做否则编译的时候找不到USB库头文件直接一堆红字报错。2.3 选对板卡配置从generic到自己的板子moonglow仓库里包含多个板卡模板其中和APM32F072/STM32F072相关的配置在boards目录下有通用的ecan板、也有针对某个特定开发板的适配。编译之前需要先确定Makefile目标。查看目标列表make help如果你手上的板子刚好有对应模板直接指定BOARD编译即可make BOARDSTM32F072_DISCOVERY但我用的板子是第三方的仓库里没有现成模板。这就得自己改配置了下文会讲具体改动。如果没有现成模板可以先找一个最接近的作为baseline然后针对自己的板子改引脚映射。这里要去看板子的原理图确认USB D/D-接到了PA11/PA12CAN RX/TX接到了哪两个引脚。2.4 编译产物在哪里编译完成后build目录下会生成.bin和.hex文件这两个就是要烧录的固件镜像。bin和hex的区别只是格式烧录器两种都认看个人习惯选。ls -lh build/*.hex build/*.bin我习惯烧hex因为J-Flash、ST-LINK Utility这些工具对hex格式的地址信息识别得更明确。3. 移植到APM32F072的关键改动不只是一颗芯片的事这里说句实话moonglow固件对APM32F072的支持并不是开箱即用虽然它是STM32F072的兼容替代但极海原厂库和ST库在个别外设定义上还是有点差别。我在移植时遇到过几个问题逐个说下解决办法。3.1 时钟配置48MHz USB需要的时钟链路APM32F072内部有HSI高速内部时钟和HSE外部晶振两个时钟源。USB外设对时钟精度要求很严格全速USB要求48MHz而且这个48MHz的时钟源误差不能太大否则PC端会识别不到设备。moonglow默认用的是HSE驱动PLL来给USB提供48MHz。如果你的板子没有外部晶振或者晶振频率和默认配置不一致系统时钟跑错USB直接歇菜。这部分要看板子图纸确认晶振频率。常见的072最小系统板用的是8MHz晶振也有12MHz的。检查固件里时钟配置的宏定义#if defined(USE_HSE_8MHz) #define HSE_VALUE ((uint32_t)8000000) #define PLL_MUL 6 // 8MHz * 6 48MHz #elif defined(USE_HSE_12MHz) #define HSE_VALUE ((uint32_t)12000000) #define PLL_MUL 4 // 12MHz * 4 48MHz #endif如果你手头的板子用的晶振频率和配置不一样要修改对应的PLL倍频系数确保最后USB时钟稳定落在48MHz。3.2 USB描述符改VID/PID让系统认出Kvasermoonglow固件默认的设备VID/PID对应的是ECAN原版设备VID 0x0C72PID 0x1005。刷进去之后Windows设备管理器里能看到一个未知设备但不会识别成Kvaser。要让系统把它认成Kvaser设备需要修改固件里的USB描述符改成Kvaser对应型号的VID/PID。Kvaser Leaf Light HS v2的VID是0x0C72PID有好几个子型号对应的数值moonglow的README里提到了几个可以用在ECAN上的PID。修改位置在usb_desc.c或者usb_prop.c里找到设备描述符数组const uint8_t USBD_DeviceDesc[18] { 0x12, USB_DESC_TYPE_DEVICE, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x72, 0x0C, // idVendor 0x0C72 0x05, 0x10, // idProduct 0x1005 0x00, 0x01, 0x01, 0x02, 0x03, 0x00 };把idVendor保持0x0C72不变idProduct改成你想要模拟的型号。Kvaser Leaf Light HS v2对应PID是0x1005这个组合实测能稳定识别。还要同步改字符串描述符里的iProduct名称否则Windows驱动安装完后设备名会显示成奇怪的名称。这个纯文字不影响硬件功能但影响体验改了舒服很多。3.3 引脚重新映射从默认模板到目标板不同板子的USB和CAN引脚接法可能差异很大。APM32F072的USB D/D-可以映射到PA11/PA12这个基本是固定的不用改。但CAN的TX/RX就要看板子原理图了。查看原理图的时候重点关注两个地方CAN收发器如TJA1050的TXD/RXD接在MCU的哪两个引脚以及CAN收发器有没有STBStandby引脚需要软件拉低才能正常工作。TJA1050的STB脚不能悬空必须接GND或者由单片机输出低电平否则CAN总线收发不工作。moonglow的Board配置里一般是用宏定义来指定CAN引脚#define GPIO_CAN_TX_PORT GPIOA #define GPIO_CAN_TX_PIN GPIO_PIN_12 #define GPIO_CAN_RX_PORT GPIOA #define GPIO_CAN_RX_PIN GPIO_PIN_11如果你的板子把CAN接在了PB口或者别的地方按实际原理图改这三个宏就行。3.4 APM32F072和STM32F072库函数差异的处理APM32F072的固件库函数名和ST的标准外设库非常接近但个别地方不完全一致。moonglow原来用的是ST标准外设库的API在GPIO、RCC、CAN、USB这些外设上极海的库要么函数名一样要么功能完全等价只是放在不同的头文件里。最稳的办法是编译报错哪里就改哪里。比如极海库的头文件是apm32f0xx.h而不是stm32f0xx.h需要在几个关键C文件里把include路径切过来。直接在Makefile的CFLAGS里加-I参数指定极海库的inc目录再把源文件里包含的头文件名改成apm32f0xx.h编译通过率极高。4. 烧录、识别、验证从hex到真正跑起Kvaser协议编译烧录的环节我用的是极海官方的烧录工具其实ST-LINK也能烧APM32F072因为这个芯片支持SWD协议。这里分步骤说下完整过程。4.1 SWD接线与烧录APM32F072核心板预留了SWD接口四根线SWDIO、SWCLK、GND、3V3。用ST-LINK或者DAP-Link连接电脑然后用STM32CubeProgrammer或者极海自家烧录工具STM32_Programmer_CLI -c portSWD modeUR -w build/ecan.hex -v如果烧录工具识别不到芯片先检查SWDIO和SWCLK有没有接反再看板子的供电是不是从USB取电、核心板的3V3和ST-LINK的3V3有没有冲突。这里有个很坑的细节如果ST-LINK给板子供了3.3V板子上的USB也供电两边电压不一样就会导致烧录不稳定。建议烧录时只保留ST-LINK供电拔掉USB线。4.2 第一次插上电脑设备枚举的瞬间烧录完成后拔掉ST-LINK用USB线把核心板连到电脑。这时候Windows右下角应该弹出新设备提示打开设备管理器在“通用串行总线设备”下能看到“Kvaser Leaf Light HS v2”或者类似名称。如果没有正确枚举常见原因有三个USB描述符没有改对。Windows对VID/PID识别非常严格IDVendor写成别的厂商会导致驱动不匹配。时钟配置不对。USB枚举对48MHz时钟的稳定度要求高HSE频率设错或者PLL倍频不对USB引擎根本起不来。用示波器测XOUT引脚有没有波形能快速判断外部晶振是否起振。USB D/D-上拉电阻问题。APM32F072内部USB收发器依赖外部1.5k上拉电阻到3.3V用来告诉主机这是全速设备。如果板子在D上没有这个1.5k上拉USB枚举必然失败。多数成品核心板都设计好了但有些精简板会偷工减料遇到未识别的设备时要检查这个电阻是否存在。4.3 安装Kvaser驱动并验证识别设备枚举成功不代表驱动装好了。Windows需要安装Kvaser官方驱动才能把USB设备映射成CAN接口。到Kvaser官网下载驱动安装包装上之后重新插拔一次设备。此时再打开设备管理器展开“Kvaser”分支能看到类似“Kvaser Leaf Light HS v2”的设备节点。到这一步系统已经把这个几十块钱的板子当成一块正经CAN卡了。在命令行可以验证设备是否就绪。用Kvaser的CANKing软件选择对应通道如果能够打开通道不报错说明协议栈已经正常通信了。4.4 用实际报文验证CAN收发功能设备能识别只算成功了一半真正要验证的是CAN收发功能是否正常。我手头有另一块CAN分析仪两种方法可以快速测试方法一双机环回把你的USB-CAN板子A的CAN_H和CAN_H短接CAN_L和CAN_L短接再接一个120Ω终端电阻用Kvaser CANKing发报文另一块板子接收如果收到说明Kvaser协议栈收发通路都通了。方法二自发自收很多CAN控制器支持Loopback模式即CAN控制器内部把发送的数据回环到接收路径上。在Kvaser软件里把CAN控制器配置成环回模式如果能收到自己发的报文说明CAN控制器工作正常问题可能出在外部收发器上。实际上我用CANKing发了一组ID为0x123的标准帧数据在另一块CAN卡上成功收到了完全一致的报文CRC校验也通过确认整个链路没问题。5. 踩坑记录移植过程中遇到的几个典型问题这部分是实战中印象最深刻的教训每个问题都花了不少时间排查写出来帮你避坑。5.1 极海库和ST标准外设库混编导致的编译冲突moonglow仓库里有些驱动文件用的是ST标准外设库的写法包含着stm32f0xx.h。APM32F072虽然是兼容芯片但极海的库头文件名是apm32f0xx.h两者混在一起编译器要么找不到头文件要么出现重复定义的宏冲突。我的解决思路是在Makefile里建立一个board_config.h把极海库的头文件和ST头文件做一个统一转发这样源文件里原来的include不用每个都改只要在编译选项里指定-include这个转发头文件即可。具体操作是建一个stm32f0xx.h的替身文件内容只有一行#include apm32f0xx.h把工程里的include path优先指向这个转发目录这样原来源码里所有include stm32f0xx.h就会自动变成包含apm32f0xx.h不动源码就完成了库切换。5.2 USB枚举不稳定时而识别时而不识别在开发过程中遇到过一次USB设备插上之后偶尔能被识别、偶尔不能的情况。用逻辑分析仪看D/D-的电平变化发现D的上拉电阻值不稳定焊接虚了。重新焊了那个1.5k电阻问题消失。另一个隐藏因素是供电。USB总线供电的核心板如果板上还有其他器件在吃电流特别是CAN收发器在总线活动时瞬间电流较大会造成USB的VCC电压跌落进而影响USB收发器工作。解决方法是给后级加一个电容稳压100μF电解电容并联100nF陶瓷电容能明显改善瞬态响应。5.3 CAN波特率配置不对导致报错moonglow固件默认的CAN波特率是500kbps但我第一次用Kvaser软件打开通道的时候软件提示总线错误通道红灯闪。排查了半天发现是总线上另一端的设备工作在250kbps两边波特率不一致CAN错误计数器直接爆表。这不是固件问题是使用习惯问题。在Kvaser CANKing的设置里把总线波特率改成和总线对手一致即可。moonglow支持在USB命令里动态配置波特率所以不需要重新编译固件直接用软件修改就行。5.4 收发器TJA1050不工作的问题还有一次焊好板子后Kvaser识别到了但CAN报文怎么都发不出去。用示波器量MCU的CAN_TX引脚波形正常问题出在CAN收发器上。TJA1050的STB引脚悬空导致收发器一直处于待机模式总线驱动器不工作。把STB引脚直接接地问题解决。买TJA1050模块的朋友要注意有些模块上的STB引脚默认已经拉低了有些没有拿到手先看原理图再连线。6. 进阶玩法去掉开发板做成独立USB-CAN分析仪核心板玩通之后有人会想把它做成真正的硬件产品脱离开发板直接集成到自己的电路里。这块我给一个精简的设计思路。6.1 最小系统电路设计一个完整的USB-CAN分析仪核心部分其实就四个模块主控部分APM32F072C8T648MHz主频LQFP48封装配8MHz晶振、复位电路、BOOT0下拉到地USB部分TYPE-C座子D/D-直接连PA11/PA12D上的1.5k上拉电阻不要省VBUS接5V再经过稳压到3.3VCAN部分TJA1050收发器TXD接PA12RXD接PA11CANH/CANL经过共模电感后接DB9或者接线端子两端加120Ω终端电阻选择跳线电源部分USB的5V通过AMS1117-3.3降到3.3V加π型滤波特别注意CAN总线的终端电阻是标配很多小白第一次做CAN卡不知道要在总线上加120Ω电阻导致通信不稳定、误码率高。6.2 外壳和接口选择产品化的细节做产品化的时候外壳开孔要提前规划好USB座子和CAN接插件的位置。CAN接插件建议用标准DB9公头因为市面上的CAN调试线缆基本都是DB9接口直接插就能用不用自己焊线。DB9的引脚定义有个行业惯性要注意引脚2是CAN_L引脚7是CAN_H引脚3是GND这个不是Kvaser独有而是CiACAN in Automation推荐的DS-102标准。跟着这个标准走后续对接其他CAN设备就顺。一个实用的小设计是加一个LED状态灯固件里可以用一个GPIO控制LED平时慢闪表示USB已连接CAN报文收发时快速闪烁方便现场排查问题。7. 后续拓展把moonglow固件改成你的私有CAN工具到这里moonglow固件刷机、识别、收发验证的完整路径已经走通了。最后聊一个很多人关心的方向固件差分包更新和远程维护这也是我在实际项目里下一步准备做的事。moonglow本身没有OTA功能但它的Flash分区是固定的bootloader区域和应用区域可以分离。做个IAP引导程序放在0x08000000区域应用固件放在0x08004000之后PC端配合一个简单的Python脚本通过USB口把新的应用固件分包发送到设备端设备在ram里缓存完整个包校验CRC后再擦写Flash这就是一个最基础的差分升级流程。核心逻辑不复杂// IAP跳转函数 void jump_to_app(void) { uint32_t app_addr 0x08004000; // 应用固件起始地址 void (*app_reset)(void) (void (*)(void))(*(volatile uint32_t *)(app_addr 4)); // 设置栈顶指针 __set_MSP(*(volatile uint32_t *)app_addr); // 跳转到应用复位向量 app_reset(); }配合一个host端脚本算出新旧固件的diff数据只把差异部分传下去能大幅减少USB传输的数据量。这在带宽受限或者报文密集的场景下比整包升级实用得多。另一个可以拓展的方向是通过USB-CAN通道做远程固件更新把固件分片封装成CAN报文通过另一端的CAN设备发送过来实现不拆机远程升级。这个玩法虽然操作上更复杂但用到量产现场维护上很香尤其在设备装进机箱不方便拆开的时候。我自己目前已经把moonglow跑在APM32F072上识别为Kvaser Leaf Light HS v2日常配合CANKing和Python-can库做总线调试体验和原装Kvaser没有感知差异。如果你手头也有APM32F072或者类似兼容芯片的板子强烈建议照着这思路试一遍你会打开一个低价高性能CAN调试工具的大门。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进