所以诞生了这个基于 STM32G0B1 的 USB 转 CAN 小工具。电脑通过 USB 虚拟串口连接设备,发送 SLCAN(LAWICEL)命令;设备收到命令后,把数据转成 CAN 帧发到总线上,也会把收到的 CAN 帧转回 SLCAN 格式发给电脑。
当前V1.0版本比较简单,但已经能跑通一套完整链路:
固件:USB CDC、SLCAN 命令解析、CAN 收发
测试脚本:用 Python 自动测试 SLCAN 命令
图形化上位机:用 Python/tkinter 发送、接收和查看 CAN 帧
兼容SavvyCAN开源上位机的部分功能
硬件
总览



小事小事,PCB没有调整丝印……
CAN 收发器
CAN 收发器使用 MAX33042EAKA+T。这颗芯片的保护能力比较强,封装也很小,适合做这种体积不大的 USB-CAN 设备。
| 参数 | 规格 |
封装 | 8 引脚 SOT-23 |
ESD 防护(HBM) | ±40 kV |
共模范围 | ±25 V |
总线故障保护 | ±40 V |
最大数据速率 | 4 Mbps |
待机电流 | 3 µA(典型值) |
I/O 电平 | 5 V |
供电电压 | 4.5 V ~ 5.5 V |
工作温度 | -40 °C ~ +125 °C |
MCU
主控是 STM32G0B1CBT6,ARM Cortex-M0+ 内核,主频 64 MHz。资源足够应付 USB CDC、SLCAN 解析和 CAN 收发,当然价格也比一些更入门的型号高一点。
| 参数 | 规格 |
内核 | ARM Cortex-M0+(32-bit) |
最高主频 | 64 MHz |
Flash | 128 KB |
SRAM | 144 KB |
工作电压 | 1.71 V ~ 3.6 V |
封装 | LQFP48 |
引脚分配
工程中的 FDCAN 引脚是 PB8/PB9,USB 使用 PA11/PA12,二者没有复用冲突……
| 外设 | 引脚 | 功能 | 电平 |
FDCAN1_RX | PB8 | CAN 收发器 RXD | 3.3 V |
FDCAN1_TX | PB9 | CAN 收发器 TXD | 3.3 V |
USB_D- | PA11 | USB 2.0 Full-Speed D- | 3.3 V |
USB_D+ | PA12 | USB 2.0 Full-Speed D+ | 3.3 V |
LED1(TX) | PB14 | CAN 发送指示 | 低电平有效 |
LED2(RX) | PB15 | CAN 接收指示 | 低电平有效 |
硬件架构

CAN 波特率
FDCAN 时钟来自 APB1,频率为 64 MHz。当前位时序参数如下:
Prescaler = 8
TimeSeg1(BS1)= 13
TimeSeg2(BS2)= 2
SyncJumpWidth = 1

固件
整体结构
固件没有使用 RTOS,就是一个裸机主循环。USB 收到数据时,由 CDC 回调把字节放进接收环形队列;主循环再慢慢取出来解析。CAN 接收也是主循环轮询 FDCAN RX FIFO0,不依赖 FDCAN 接收中断。

主循环很简单:
while (1)
{
SLCAN_Bridge_Process();
}每次调用 SLCAN_Bridge_Process(),按下面的顺序处理:
| 顺序 | 函数 | 做什么 |
1 | SLCAN_ProcessLeds() | 检查 LED 是否已经亮够 50 ms |
2 | SLCAN_ReportOverflows() | 上报并清除环形队列溢出标志 |
3 | SLCAN_ProcessUsbInput() | 从 USB RX 队列取数据,拼出并处理命令 |
4 | SLCAN_ProcessCanReception() | 轮询 FDCAN RX FIFO0,转成 SLCAN 帧 |
5 | SLCAN_ProcessUsbOutput() | 从 USB TX 队列取数据,分块发给电脑 |
数据流

SLCAN 命令
所有命令都以回车符 r 结尾。换行符 n 会被忽略。命令成功通常返回 r,格式错误或当前状态不允许时返回响铃字符 a。
| 命令 | 格式 | 作用 | 成功响应 | 失败响应 |
O | Or | 打开 CAN 总线 | r | a |
C | Cr | 关闭 CAN 总线 | r | a |
S6 | S6r | 设置为 500 kbps | r | a |
t | tIIID...Dr | 发送标准数据帧(11-bit ID) | zr | a |
T | TIIIIIIIID...Dr | 发送扩展数据帧(29-bit ID) | Zr | a |
帧格式举例:
标准帧发送:t1232AABBr → ID=0x123,DLC=2,数据 AA BB 扩展帧发送:T123456782AABBr → ID=0x12345678,DLC=2,数据 AA BB 空数据帧: t0000r → ID=0x000,DLC=0
ID 是否是合法的十六进制数,并且没有超出标准帧或扩展帧范围
DLC 是否在 0~8 之间
数据长度是否正好等于 DLC 对应的字节数
CAN 是否已经打开
环形缓冲区
| 缓冲区 | 大小 | 用途 | 溢出时的处理 |
USB RX 队列 | 512 B | 暂存电脑发来的数据 | 丢弃新数据,置 rx_overflow |
USB TX 队列 | 2048 B | 暂存准备发给电脑的数据 | 丢弃新数据,置 tx_overflow |
命令缓冲区 | 32 B | 暂存当前正在解析的命令 | 拒绝当前命令,置 overflow |
溢出标志在 USB 回调和主循环之间共享,读取和清除时会进入临界区,避免中断和主循环同时修改状态。
LED 指示
| LED | 触发条件 | 行为 |
POWER | 3.3V 电源正常 | 常亮 |
LED1(PB14) | CAN 帧发送成功 | 点亮 50 ms 后自动熄灭 |
LED2(PB15) | CAN 帧接收成功 | 点亮 50 ms 后自动熄灭 |
LED 使用非阻塞方式控制。触发时只记录时间戳,主循环检查超时后再熄灭,不会因为闪灯卡住通信。
上位机工具
项目里有两个 Python 工具:一个负责测试,一个负责日常调试。
SLCAN 协议测试:slcan_test.py
CAN 打开、关闭和重复操作
S6 波特率设置及状态限制
标准帧和扩展帧发送
非法 ID、非法 DLC、数据长度错误等输入校验
空命令、超长命令和连续快速发送
CAN 状态转换
n、rn、nr 等换行情况
python slcan_test.py COM3 python slcan_test.py COM3 --verbose
运行结果:

脚本依赖 pyserial。串口参数里的 115200 只是虚拟串口的兼容配置,USB CDC 本身不靠这个数值传输
图形化调试工具:slcan_gui.py
自动枚举和连接 USB 虚拟串口
打开、关闭 CAN 总线
发送标准帧和扩展帧
实时显示收到的 CAN 帧
按 ID 或 TX/RX 方向过滤消息
使用预设帧快速发送
查看收发计数和状态日志
运行方式:
python slcan_gui.py python slcan_gui.py COM3
运行效果:

工程目录
USB_CAN/
├── Core/
│ ├── Inc/ # 用户头文件,重要: main.h、slcan_bridge.h
│ └── Src/ # 用户源文件,重要: main.c、slcan_bridge.c
├── Drivers/ # STM32 HAL / CMSIS 驱动
├── Middlewares/ # STM32 USB Device Library
├── USB_Device/ # USB CDC 应用层
├── cmake/ # CMake 工具链文件
├── doc/ # 项目说明文档
├── CMakeLists.txt # 顶层构建配置
├── slcan_test.py # 协议测试脚本
└── slcan_gui.py # 图形化上位机
开发环境
| 工具 | 说明 |
MCU | STM32G0B1CBT6 |
IDE | VS Code + CMake |
代码生成 | STM32CubeMX |
编译器 | arm-none-eabi-gcc |
构建系统 | CMake |
调试器 | ST-Link |
使用时注意
CAN 总线需要正确接线,并按总线拓扑做好终端电阻。
固件当前只支持 500 kbps 的 Classic CAN,不支持 CAN FD,也不能通过 S 命令切换其他波特率。
测试脚本主要验证 USB 到 FDCAN 的发送链路;如果要验证接收链路,还需要总线上有其他 CAN 节点主动发帧。
实物展示
部分进行了飞线,硬件总览的原理图上已完成修改




总结
这个版本的目标是测试CAN芯片(MAX33042EAKA),把 USB-CAN 的基本链路跑通,所以功能范围比较克制:只保留了项目实际用到的 SLCAN 命令,CAN 速率也固定为 500 kbps。后续V1.1再补充多波特率、帧记录导出等功能。
我要赚赏金
