尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于CanFestival的CANopen电机控制主站工程实践

基于CanFestival的CANopen电机控制主站工程实践 简介一份面向嵌入式与工业控制开发者的CanFestival电机控制方案资料包基于开源CANopen协议栈实现电机驱动控制适合需要快速掌握CANopen通信、SDO与PDO对象字典配置、以及电机调速应用的工程师。压缩包共306个文件以142个头文件与98个C源代码文件为核心覆盖协议栈移植、对象字典、SDO和PDO通信、任务调度等关键模块另含工程配置文件、启动脚本及编译描述文件便于在CCS等集成环境中直接导入整体包体仅861KB结构紧凑。已有653人学习阅读适合中高级嵌入式开发者参考。资料内含任务调度、CANopen主从站配置与电机控制示例配套工程组织清晰可帮助读者理解协议栈运行机制并快速搭建可运行的电机控制原型是一份实用度较高的参考素材。1. 从 PD4Master.c 看 CanFestival 电机控制工程拿到这份 Firmwares 源码包时最先勾住我的不是 tasks.c也不是 sdo.c而是角落里的 PD4Master.c。PD4 是达妙系列电机主站命令的惯用前缀一个文件把 CanFestival 协议栈和真实关节电机拉到一起。整个包的文件结构很典型portasm.asm 管临界区、queue.c 管发送缓冲、sdo.c 负责读写对象字典、tasks.c 驱动协议栈状态机timers.bin 提供定时器节拍。这种组合说明它不是“跑个 CANopen 从站点灯”的教学工程而是一套面对 bldc 电机、要实际承担关节控制的主站框架。适合两类人一类是电机控制入门不久、想低成本试 CANopen 的嵌入式工程师另一类是已经在用自研 CAN 协议、想把对象字典和标准化启动流程接进来的老手。接下来按文件分工、SDO 配置、PD4 控制帧、最后到整链路调试的顺序拆。2. CanFestival 协议栈的文件分工与电机控制边界CanFestival 是 CANopen 协议栈里少见的、从 8 位单片机能一路跑到 ARM 平台的实现。它把“对象字典 通信对象 应用”三层拆得很清楚电机控制工程真正需要改的只有对象字典和应用层。但这份包里多出来的几个文件值得先读portasm.asm、queue.c、timers.bin。它们决定了控制帧在总线竞争下会不会丢心跳是否准时SYNC 能否对齐。以下按文件逐个拆映射到电机控制里的职责。2.1 对象字典是电机控制参数的“内存地图”CANopen 的核心不是收发函数而是对象字典。0x6040 控制字、0x6041 状态字、0x6060 运行模式、0x607A 目标位置全部以“索引-子索引”的形式挂在对象字典里。电机控制里上电阶段把电机参数写进 0x20000x2FFF 厂商区运行阶段由 PDO/SDO 交换 0x60000x6FFF 区域的数据。这份工程里由 ObjDict.c/ObjDict.h 生成字典表sdo.c 就是对这张表的网络访问接口。注意对象字典的入口在 CanFestival 中是一个 const 表运行期改写值会落到运行时变量。常见坑是不少人把厂商区当临时变量随手写结果协议栈卡在 SDO 阻塞因为写入的值长度不匹配。比如写 0x1017 心跳周期时用了 8 位而字典定义是 UNS32写入后协议栈读到的只是低字节心跳周期直接变成不符合预期的值。给一段初始化对象字典条目的典型写法#include objdictdef.h #include objdict.h /* 在应用初始化阶段把厂商区 0x2000 子索引 1 写入当前电机最大力矩 */ UNS32 max_torque_limit 12000; /* 0.001 Nm, 即 12 Nm */ setODentryByIndex(canopen_Data, 0x2000, 1, max_torque_limit, sizeof(max_torque_limit));参数说明第 1 个参数是 CanFestival 的 CO_Data 实例指针第 2、3 个参数对应对象字典的索引和子索引第 4 个是待写入值缓冲最后一个参数是字节长度。setODentryByIndex 内部会根据对象字典的类型定义做类型转换当值长度与字典不匹配时多数版本只做截断或扩展不会报错所以这里写 4 字节与 UNS32 对齐是首选。2.2 portasm.asm 与 queue.cCAN 总线竞争的临界区电机控制主站和普通从站的最大差异是发送频繁。从站只在接收请求、心跳、PDO 时发送主站在每个控制周期都要推一帧 0x140 控制帧速度极快时中断和主循环几乎同时在操作发送队列如果不用临界区保护队列头指针可能被中断改掉半截CAN 控制器发出的是错帧。portasm.asm 在这套工程里的角色就是提供进入/退出临界区的原语在 Cortex-M 上通常是 CPSID i / CPSIE i 两条指令的封装。在其它平台可能是关中断或原子位操作。queue.c 则是协议栈和用户代码共用的发送缓冲入队处要把“队列空判断”和“入队”做成一个原子动作。#include portasm.h #include queue.h static void can_send_with_guard(CO_Data *d, Message *msg) { UNS8 sr; sr portasm_enter_critical(); /* 关中断返回原中断状态 */ if (queue_get_free_count(tx_queue) 0) { queue_push(tx_queue, msg); canSend(d, msg); /* 触发 CAN 外设发送 */ } else { /* 满队列时丢弃当前控制帧避免阻塞主循环 */ tx_drop_count; } portasm_exit_critical(sr); /* 恢复原中断状态 */ }注意 send 放在临界区里调用不是最好的做法CAN 外设发送会循环等待几个总线周期在 1Mbit/s 下约 130us临界区内耗时太长会影响定时器中断。我一般会把临界区收缩到“队列指针更新”这一步或者明确知道队列有空位时再触发发送。这段代码省掉了工程里没写的 CAN 驱动部分但保护边界是合格的。2.3 tasks.c 与 timers.bin协议栈状态机的运行节拍tasks.c 是 CanFestival 的主任务里面实现了 NMT 状态处理、心跳消费、PDO 定时刷新、SDO 超时管理。它需要在定时器中断里周期调用 tmr_has_new_event / TimeDispatch主循环里调用 CANopen_Task 处理句柄。timers.bin 是给部分平台预编译好的定时器节拍目标文件和 portasm.asm 一样是移植产物不推荐去改内容真正要改的是 TimerForCANopen 的节拍常量。文件协议栈模块在电机控制工程里的角色sdo.cSDO 服务端/客户端配置电机节点 ID、心跳、控制字读故障码tasks.c主任务与 NMT驱动对象字典状态机处理复位命令queue.c发送队列仲裁上层控制帧与 CAN 中断的写竞争portasm.asm移植层提供临界区指令保护队列操作timers.bin定时器节拍派生 SYNC 周期、心跳周期、SDO 超时PD4Master.c应用层打包达妙电机控制帧解析反馈帧这张表基本说明了 CanFestival 工程里哪些文件不用动、哪些文件必须改。移植时最容易失控的是 timers.bin 对应的时间基准如果定时器节拍从 1ms 改成 10msSYNC 周期 0x1006 就会按 10 倍误差走关节控制周期直接失真。后面第五章会专门讲这个对齐。3. SDO 读写电机状态字与参数配置SDO 是 CANopen 里面向对象字典的访问通道确认机制可靠但效率低。电机控制里它不适合放在实时控制环却非常适合做上电配置设定节点 ID、心跳、电机最大力矩读取状态字、故障码执行使能/失能。达妙电机这类常规 CAN 协议的设备没有标准对象字典但只要把主站侧挂上 CanFestival并让电机运行在客户自定义的映射模式SDO 就能在启动阶段把参数预置好之后的关节控制全走 PDO/原生控制帧。3.1 用 writeNetworkDict 下发 SDO 配置CanFestival 作为主站发起 SDO 请求时最常用的函数是 writeNetworkDict 和 readNetworkDict。以下示例把电机节点 2 的心跳周期配置为 100ms#include sdo.h #include data.h extern CO_Data canopen_Data; UNS8 node_id 2; UNS32 heartbeat 100; /* 100ms范围 0~65535 */ UNS8 sdo_result 0; /* 阻塞模式等待从站回复或超时超时时间由协议栈定时器派生 */ sdo_result writeNetworkDict(canopen_Data, node_id, 0x1017, 0x00, UNS32, heartbeat, 0, 0, 0); if (sdo_result ! 0) { /* 常见做法失败后先读 0x1003 预定义错误字段 */ readNetworkDict(canopen_Data, node_id, 0x1003, 0x00, UNS32, sdo_result, 0, 0, 0); }参数说明writeNetworkDict 的第 3、4 个参数是索引和子索引第 5 个是数据类型枚举第 6 个是数据指针后三个是 SDO 回复回调、数据长度和数据对象传 0 表示不挂回调。阻塞模式下函数会等 SDO 回复或超时才返回启动阶段可以用运行阶段一旦进入控制环任何等待都会破坏周期要用回调版本。3.2 电机使能流程里 SDO 与 0x6040 控制字的配合CANopen 驱动器的标准使能流程是先写状态字 0x6040 进入 Ready to Switch On再置 bit3、bit2 进入 Operation Enabled。以下按对象字典写入方式完成UNS32 cw_val; /* 上电默认状态: Shutdown */ cw_val 0x0006; writeNetworkDict(canopen_Data, node_id, 0x6040, 0x00, UNS32, cw_val, 0, 0, 0); /* 进入 Ready to Switch On: bit0 bit1 置 1 */ cw_val 0x0007; writeNetworkDict(canopen_Data, node_id, 0x6040, 0x00, UNS32, cw_val, 0, 0, 0); /* 进入 Operation Enabled: bit3 使能 */ cw_val 0x000F; writeNetworkDict(canopen_Data, node_id, 0x6040, 0x00, UNS32, cw_val, 0, 0, 0);这里很多人会犯一个错直接从 0x0000 跳到 0x000F很多从站状态机不认会停留在 Operation Enable Disabled。按 CiA 402 状态机的“状态机顺序”逐级写即使跳步也要保持 bit0/bit1 已置位比逐级稳健的做法是驱动板手册里提供了“快速使能命令”时直接写 0x000F 或厂商自定义值。3.3 常用对象字典索引速查索引子索引含义电机控制中的典型值0x10170心跳生产者时间100ms 主站监控用0x10181节点 ID 前导0x01~0x7F0x60400控制字0x06/0x07/0x0F0x60410状态字0x0231 等0x60600运行模式3: 位置, 4: 速度, 10: 周期同步0x607A0目标位置用户0x60830加加速度 Jerk控制平滑的关键表里 0x6083 是电机控制入门最容易忽略的项。CanFestival 本身不会校验 Jerk它只是把值搬到字典里但 PD4Master.c 那边打包控制帧时会把 Jerk 作为 Byte0 带出去所以第 4 章会看到它和原生控制帧如何衔接。SDO 只负责把数值写对不负责让电机平滑平滑是控制帧设计的事。4. PD4Master.c 的实现从 CANopen PDO 到达妙电机控制帧现在说说这个工程最特别的部分PD4Master.c。达妙电机通过 CAN 总线实现精准关节控制时主干不是标准 CiA 402而是达妙自家定义的 8 字节控制帧CAN ID 0x140 节点号数据帧字节全是大端Byte0 是模式/Jerk剩余字节按模式映射目标位置、速度、力矩和增益。反馈帧占 0x240 节点号包含位置、速度、力矩、母线电压和温度。4.1 CANopen PDO 与达妙原生帧的接法常见做法不是把达妙帧塞进标准 PDO 当 payload而是让 CanFestival 的 PDO 只承担“发送时机”把达妙控制帧映射到一个自定义 PDO 的 COB-ID传输类型设为 1即 SYNC 驱动。这样控制帧会在每个 SYNC 后自动发出帧内容仍由 PD4Master.c 自己拼。好处是保留 CANopen 的 NMT、心跳、错误控制又不牺牲实时性。8 字节控制帧常见布局如下字段偏移以 PD4Master.c 实际定义为准Byte内容说明0mode/jerk0xFC 使能位置速度模式0xFD 速度模式0xFF 失能低 6 位为 Jerk1~4target_posint32 定点数缩放系数见驱动板手册5~6target_velint160.01 dps 量级常见7target_torint16 截断为高字节视固件版本下面这段是主站侧打包函数也是 PD4Master.c 里最核心的入口#define DM_CMD_COB_ID(id) (0x140u (id)) #define DM_FB_COB_ID(id) (0x240u (id)) typedef struct { uint8_t mode; /* 0xFC 使能, 0xFF 失能 */ int32_t pos; /* 目标位置, 定点数 */ int16_t vel; /* 目标速度 */ int16_t tor; /* 力矩前馈 */ uint16_t jerk; /* 加加速度, 拆到 Byte0 低 6 位 */ } dm_cmd_t; void PD4_SendCmd(CO_Data *d, uint8_t id, const dm_cmd_t *c) { Message m; uint8_t *p m.data; m.cob_id DM_CMD_COB_ID(id); m.rtr 0; m.len 8; *p (c-mode 0xC0) | (c-jerk 0x3F); *p (c-pos 24) 0xFF; *p (c-pos 16) 0xFF; *p (c-pos 8) 0xFF; *p (c-pos 0) 0xFF; *p (c-vel 8) 0xFF; *p (c-vel 0) 0xFF; *p (c-tor 8) 0xFF; /* 用 queue.c 的队列发出而不是直接 canSend */ queue_push(tx_queue, m); }字段说明pos 是 4 字节有符号定点数int32 移位拆成大端顺序vel 取 2 字节tor 只留高字节是常见工程取舍力矩指令的精度让位给 Byte0 的 Jerk 字段。如果电机手册里力矩缩放是 0.01Nmint16 全放能覆盖 ±327Nm留高字节也够用拆帧时按同样偏移解回来即可。注意 m.data 只有 8 字节不要在默认 Message 结构上直接 memcpy达妙帧不带 CANopen 的 data[0..7] 之外的任何元数据。4.2 反馈帧解析与闭环typedef struct { int32_t pos; int16_t vel; int16_t tor; uint8_t temp; /* 温度从 Byte7 高 2 位解出 */ } dm_fb_t; void PD4_ParseFeedback(const Message *m, dm_fb_t *fb) { if ((m-cob_id 0xFF00) ! 0x2400) { return; } fb-pos (m-data[0] 24) | (m-data[1] 16) | (m-data[2] 8) | m-data[3]; fb-vel (m-data[4] 8) | m-data[5]; fb-tor (m-data[6] 8) | m-data[7]; fb-temp (m-data[7] 6) 0x03; }同样的字节序。若反馈中带温度通常在 Byte 6/7 里挤出高 2 位工程里做 mask 时不要直接把 8bit 全读成 int16先看驱动板手册确认符号位。读回的位置是编码器原始计数还是角度定点数直接决定闭环比例系数。PD4Master.c 里常见做法是单独维护一个关节状态数组反馈解析后写入 joint_fb[]控制环在最上层读取。这样可以避免电机控制代码直接碰 CANopen 对象字典的运行时变量也方便在单关节调试时打印反馈。闭环里我一般只用反馈帧的 pos 和 veltor 留作观测和碰撞检测不直接做力矩前馈除非电机手册明确给了力矩标定系数。5. SYNC 周期与关节控制时序的验证技巧最后讲的是把整个链路拉起来之后怎么确认控制周期的对齐是可靠的。CanFestival 里 SYNC 对象由主站周期发送0x1006 定义周期微秒数0x1005 定义 COB-ID。达妙控制帧本身不依赖 SYNC但如果要把多个电机的控制帧在一个关节周期里对齐最稳的是让 SYNC 中断先翻转一个软件标志主循环检测到标志后统一发 0x140 帧。static volatile uint8_t sync_flag; void TimerForCANopen_SyncHandler(void) { sync_flag 1; /* SYNC 中断里只置位 */ } void JointControlLoop(CO_Data *d) { if (sync_flag) { sync_flag 0; /* 所有关节电机命令在这一拍内更新 */ PD4_SendCmd(d, 1, joint_cmd[0]); PD4_SendCmd(d, 2, joint_cmd[1]); } }节拍验证时先看三个常数是否匹配定时器节拍、0x1006、PDO 传输类型。定时器 1ms、0x1006 设为 1000、传输类型为 1则控制帧严格 1ms 一次有一处换算错总线抓帧周期直接对不上。外部验证用 candump 旁观在 1Mbit/s 下抓 0x140、0x240、0x80 三组 ID。0x80 是 NMT 节点复位若 SDO 配置阶段失败大概率能在总线上看到 0x80 复位请求重发控制帧的报文时间戳间隔抖动小于 ±1ms 时说明主循环和中断没有互相饿死。下表是抓帧时的对照关系时间点事件验证T0主站发 SDO 写 0x1017从站回 0x580T1主站发 SYNC (0x080)周期等于 0x1006T2主站发 0x140 控制帧candump 间隔恒定T3电机回 0x240 反馈帧数据与编码器对照如果在 T2 到 T3 间隔里看到偶发的大抖动优先检查 queue.c 的满队列丢弃计数 tx_drop_count 和控制周期里是否插入了阻塞式 SDO 读写。这两个是最常见的时序破坏者。反馈帧的符号位和缩放系数最终以驱动板手册里的实测标定值为准不要直接信代码注释。本文还有配套的精品资源点击获取
返回列表