[i=s] 本帖最后由 lulugl 于 2026-9-17 21:17 编辑 [/i]
基于 STM32C5 的 CAN二维云台总线电机控制
本文基于本仓库(STM32C542_CAN)的实际代码,介绍如何用 STM32C5 的 FDCAN外设通过 CAN 总线控制 JC4310 系列电机驱动器(三轴云台场景)。内容涵盖硬件接线、FDCAN 外设配置、JC 通信协议、软件实现和调试排错,可作为入门 CAN 电机控制的实操参考。以及web网页控制。
目录
1. 整体方案
┌─────────────┐ UART ┌──────────────────┐ CAN ┌─────────────────┐
│ PC 串口终端 │ ◄─────► │ NUCLEO-C542RC │ J9 │ 电机驱动器 x N │
│ (Shell 命令) │ USART2 │ (板载 CAN 收发器) │ ◄───► │ (JC4310, ID1~3) │
└─────────────┘ └──────────────────┘ └─────────────────┘
- 电脑上通过串口终端输入命令(
pos 90、speed 60 等);
- STM32 把命令翻译成 JC 协议的 CAN 帧发给电机驱动器;
- 驱动器回复执行结果和实时反馈(位置/速度/电流),STM32 解析后打印。
代码分层:
| 文件 |
职责 |
main.c |
系统初始化、FDCAN 位时序覆盖为 1 Mbps、主循环 |
generated/hal/mx_fdcan1.c |
CubeMX2 生成的 FDCAN1 外设初始化 |
jc4310.c/.h |
JC 协议层:寄存器读写、PV/PVT 运动、同步问答 |
shell.c/.h |
UART 命令行:解析命令、调用协议层、打印反馈 |
2. 硬件准备
NUCLEO-C542RC 板载 CAN FD 收发器,已在板上连接到 FDCAN1 的
PB8(RX)/PB9(TX),无需外接收发器。CAN 总线通过板上的 J9 免螺丝
按压端子 引出,插上 JP9 跳帽 即可接入板载 120 Ω 终端电阻(R29)
(详见 ST 用户手册 UM3615)。
| 项目 |
说明 |
| 收发器 |
板载 CAN FD 收发器(FDCAN1 PB8/PB9 已内部连接) |
| 总线接口 |
J9 端子接 CANH / CANL,并与电机驱动器共地 |
| 终端电阻 |
总线两端各一个 120 Ω;板端插上 JP9 跳帽即接入板载 R29,另一端在最后一台电机处 |
| 波特率 |
1 Mbps(JC 电机默认值) |
| 电机 ID |
出厂需用调试工具设置,三轴云台一般设为 1/2/3 |
接线示意:
NUCLEO-C542RC J9
┌────────────────────┐ CANH ─────┬──────┬──────┬────── CANH
│ FDCAN1 ── 板载收发器 │ ▼ ▼ ▼
│ (JP9 跳帽 = 120Ω) │ CANL ──── 电机1 电机2 电机3 ── 120Ω
└────────────────────┘ GND ──── 共地 ────────────────
3. CAN / FDCAN 基础知识
- CAN 2.0A 标准帧:11 位标识符(ID),数据场 0~8 字节。本项目全部
使用标准帧、DLC 固定 8。
- ID 即优先级:总线仲裁时 ID 越小优先级越高;ID 也用来寻址,
JC 协议里一台电机占一对 ID(主机发 0x600+id,驱动器回 0x580+id)。
- 多主结构:总线上任何节点都可以主动发送;多个节点同时发送靠仲裁
决定谁先,不像 UART/SPI 有主从片选。
- 错误处理硬件化:发送需要有节点 ACK,否则控制器自动重发;错误
计数器超阈值会进入 error passive / bus-off。STM32 的 FDCAN 外设可以
读出这些状态(见 canstat 命令)。
- FDCAN:STM32C5 的 CAN 控制器,兼容经典 CAN 2.0 和 CAN FD。本项目
只用经典 CAN 模式(电机不支持 FD),因此数据位时序不用配置。
4. STM32C5 FDCAN 配置
4.1 CubeMX2 生成的基础配置

generated/hal/mx_fdcan1.c 中的 mx_fdcan1_init():
- 内核时钟源:PCLK1(经 RCC 配置为 144 MHz),时钟分频 1;
- 帧格式:
HAL_FDCAN_FRAME_FORMAT_CLASSIC_CAN(经典 CAN);
- 工作模式:NORMAL;自动重发使能;
- 发送模式:TX FIFO(先请求先发);
- 不配置验收过滤器,改用全局过滤器把所有标准帧路由到 RX FIFO0
(HAL_FDCAN_NO_MATCH_TO_RX_FIFO_0),由软件按 ID 过滤——调试阶段
这样最灵活,可以收到总线上所有帧;
- 生成时按 500 kbps 配置(不满足 JC 电机的 1 Mbps,见下节覆盖)。
4.2 位时序:覆盖为 1 Mbps
CAN 的波特率由"位时序"决定:每个位时间由若干时间量子(tq)组成,
波特率 = 内核时钟 / (prescaler × (1 + seg1 + seg2))
`main.c` 在 `HAL_FDCAN_Start()` 之前用 `HAL_FDCAN_SetNominalBitTiming()`
覆盖为 1 Mbps:
```c
hal_fdcan_nominal_bit_timing_t bit_timing;
bit_timing.nominal_prescaler = 1;
bit_timing.nominal_time_seg1 = 115;
bit_timing.nominal_time_seg2 = 28;
bit_timing.nominal_jump_width = 28;
验算:144 MHz / (1 × (1 + 115 + 28)) = 144 MHz / 144 = 1 MHz ✓
采样点:(1 + 115) / 144 = 80.6 %(常用推荐区间 75%~87.5%)。
驱动包 stm32c5xx_drivers/utils/ 里还提供了一个 fdcan_timing
辅助工具,可根据目标波特率自动算位时序参数。
4.3 启动顺序
mx_fdcan1_init(); // 外设 + GPIO 初始化(500 kbps 配置)
HAL_FDCAN_SetNominalBitTiming(...); // 空闲状态下覆盖为 1 Mbps
HAL_FDCAN_Start(hfdcan1); // 上线
JC4310_Init(hfdcan1); // 协议层绑定句柄
5. JC4310 CAN 通信协议
依据《JC 系列 CAN 通信说明》V2.4。要点:
| 项目 |
说明 |
| 帧格式 |
CAN 2.0A 标准帧,DLC 固定 8 |
| 主机发送 ID |
0x600 + 电机ID |
| 驱动器回复 ID |
0x580 + 电机ID |
| 字节序 |
大端(高位字节在前) |
5.1 命令字(数据第 0 字节)
| 命令字 |
含义 |
帧格式 |
0x4B |
读 16 位寄存器 |
4B regH regL 00 00 00 00 00,回复回显命令字,值在 data[4..5] |
0x43 |
读 32 位寄存器 |
43 regH regL 00 00 00 00 00,回复值在 data[4..7] |
0x2B |
写 16 位寄存器 |
2B regH regL 00 valH valL 00 00 |
0x23 |
写 32 位寄存器 |
23 regH regL 00 val(b3..b0) |
0x24 |
PV 运动 |
24 pos(b3..b0) speed(b1..b0) 00 |
0x25 |
PVT 运动 |
25 pos(b3..b0) speed(b1..b0) torque% |
0x2A |
写命令的统一回复 |
2A pos(b2..b0) speed(b1..b0) current(b1..b0) |
5.2 常用寄存器
| 地址 |
含义 |
位宽 |
缩放 |
| 0x0004 |
母线电压 |
16 |
÷10 → V |
| 0x0005 |
电流 |
16 |
÷100 → A |
| 0x0006 |
实际速度 |
32 |
÷100 → rpm |
| 0x0008 |
实际位置 |
32 |
÷100 → deg |
| 0x000A |
驱动器温度 |
16 |
÷10 → °C |
| 0x000B |
电机温度 |
16 |
÷10 → °C |
| 0x000C |
错误码 |
32 |
原始值 |
| 0x0020 |
力矩设定 |
16 |
×100 → 0.01 Nm |
| 0x0021 |
速度设定 |
32 |
×100 → 0.01 rpm |
| 0x0023 / 0x0025 |
绝对/相对位置设定 |
32 |
×100 → 0.01 deg |
| 0x0027 |
低速模式速度设定 |
16 |
×100 |
| 0x0060 |
控制模式(0~5) |
16 |
— |
| 0x00A0 / 0x00A2 |
退出/进入闭环 |
16 |
写 1 |
| 0x00A3~0x00A7 |
擦除/保存/重启/设原点/临时原点 |
16 |
写 1 |
控制模式:0 力矩,1 速度,2 位置梯形轨迹,3 位置平滑滤波,
4 位置直接,5 低速大扭矩。
5.3 统一回复的巧妙设计
所有写命令的回复都是 0x2A + 实时反馈(24 位位置 + 16 位速度 +
16 位电流)。也就是说每发一条运动指令,顺手就能拿到最新状态,不需要
额外轮询。协议层用 jc_parse_feedback() 统一解析。
6. 软件实现
6.1 同步问答模型
协议层(jc4310.c)采用"发一帧、等回复"的同步模型,核心是三个内部
函数:
jc_send() // 组帧入 TX FIFO,最多等 20 ms 空位(等不到 = 无人 ACK)
jc_wait_reply() // 轮询 RX FIFO0,只认 0x580+id 且首字节匹配的回复,
// 100 ms 超时;其他帧丢弃
jc_parse_feedback() // 解析 0x2A 回复里的位置/速度/电流
所有上层 API(JC4310_SetSpeed()、JC4310_ReadPosition() …)都是
这两个原语的组合,结构非常规整:
hal_status_t JC4310_SetSpeed(uint8_t id, float speed_rpm, jc4310_feedback_t *fb)
{
return jc_write32(id, JC4310_REG_SPEED_SET, jc_round(speed_rpm * 100.0f), fb);
}
6.2 值得注意的实现细节
-
24 位有符号位置需要符号扩展(jc4310.c:137):
回复中位置是 3 字节大端有符号数,拼成 32 位后要检查 bit23,
为 1 时高 8 位补 0xFF,否则负角度会被解析成巨大的正数。
-
超时分级:
- 发送等待 TX FIFO 空位:20 ms(超时通常意味着没人 ACK → 总线没接好);
- 等回复:100 ms;
- 重启/设原点这类慢命令:500 ms。
有超时兜底,shell ***不会死等。
-
收发无过滤器:硬件全局过滤器把全部标准帧送进 FIFO0,软件在
jc_wait_reply() 里按 ID + 命令字过滤。好处是 sniff/probe
调试命令可以直接看到总线上的所有流量。
-
浮点缩放:电机侧全部用定点(×100 或 ×10),MCU 侧收发时做
float ↔ 定点的换算,jc_round() 不依赖 libm 完成四舍五入。
6.3 调用示例
jc4310_feedback_t fb;
JC4310_EnterClosedLoop(1, &fb); // 1 号电机上闭环
JC4310_SetMode(1, JC4310_MODE_SPEED, &fb);
JC4310_SetSpeed(1, 60.0f, &fb); // 60 rpm
printf("pos=%.2f deg, current=%.2f A\r\n", fb.pos_deg, fb.current_a);
7. 操作演示(UART Shell)
固件在 USART2(ST-Link 虚拟串口)上跑一个行命令 shell,提示符
jc<N> 中的 N 是当前目标电机 ID。
典型流程(1 号电机速度模式):
jc1> id 1 # 选择 1 号电机(TX 0x601 / RX 0x581)
jc1> enable # 上闭环
jc1> mode 1 # 速度模式
jc1> speed 60 # 60 rpm 运转
OK
pos=1234.56 deg, speed=59.98 rpm, current=0.31 A
jc1> status # 电压/电流/速度/位置/温度/错误码 一次全读
jc1> speed 0 # 停
jc1> disable # 退出闭环
位置/PV/PVT 运动:
jc1> mode 2 # 位置梯形轨迹模式
jc1> pos 90 # 绝对位置 90°
jc1> rpos -90 # 相对转回
jc1> pv 180 60 # 位置 180°、速度 60 rpm
jc1> pvt 180 60 50 # 再加 50% 力矩限制
完整命令说明见 shell_usage.md。
除串口终端外,也可以用网页控制台 web/jc4310_console.html(Chrome /
Edge 打开,基于 Web Serial API 直连 ST-Link 虚拟串口):把 enable、
mode、speed、pos、pv/pvt 等命令做成按钮和滑块,自动轮询 status
并以仪表盘显示位置/速度/电流,适合演示和分享给不熟悉命令行的人。
8. 调试与排错
8.1 内置调试命令
| 命令 |
作用 |
canstat |
读 FDCAN 控制器状态:activity、LEC(最近协议错误)、error passive、bus-off、TX/RX 错误计数器、FIFO 水位 |
probe |
发一帧读电压请求,然后 dump 收到的所有帧并标出匹配的回复;能区分"根本没 ACK"(发送失败)和"ACK 了但不回答"(ID/协议不对) |
sniff [ms] |
总线监听,打印指定时间内收到的所有帧(默认 2 秒) |
raw <b0..b7> |
发原始 8 字节帧并打印回复,用来验证协议手册上的任意命令 |
canstat 输出示例(正常状态):
jc1> canstat
activity = sync
last error = none
error passive = 0
bus off = 0
tx/rx errors = 0 / 0
tx fifo free = 3
rx fifo fill = 0
出现异常时还会附带一行诊断提示,例如 bus-off 时提示检查收发器/
终端电阻/波特率,ACK error(LEC=3)时提示"帧发出去了但没人 ACK"。
9. 网页远程控制
我在pc端上制作了一个web网络控制台:

这样就可以通过pc方便的控制云平台的各种方式运行。