简介
CANopenNode 是一个轻量、开源、工业级的 CANopen 协议栈(ANSI C),遵循 CiA 301 标准,支持主 / 从站,广泛用于嵌入式(STM32 等)与 Linux 系统,商用友好(Apache 2.0 许可证)。
一、核心特点
- 开源许可:Apache 2.0,可闭源商用,无需开源修改部分。
- 架构:事件驱动、单线程、非抢占式,资源占用小(ROM < 15KB,RAM < 2KB),适合 Cortex‑M0/M3 等资源受限 MCU。
- 功能完备:
- NMT(网络管理):主 / 从站、节点启停 / 复位
- SDO(服务数据对象):客户端 / 服务器,支持分段 / 加速传输
- PDO(过程数据对象):实时周期 / 事件触发,支持映射
- EMCY(紧急报文)、SYNC(同步)、LSS(节点地址设置)、心跳 / 节点保护
- 对象字典(OD):核心数据结构,16 位索引 + 8 位子索引,统一管理通信 / 设备 / 自定义参数,支持网络与本地访问。
- 平台支持:裸机、RTOS(FreeRTOS、RT‑Thread)、Linux(含主站);提供 STM32、PIC、ESP32 等移植示例。
二、代码结构(v4 版本)
开源地址:https://github.com/CANopenNode/CANopenNode

301/ - CANopen 应用层与通信协议规范
CO_config.h - CANopenNode 功能配置宏定义文件
CO_driver.h - CAN 硬件与 CANopenNode 协议栈的底层适配接口
CO_ODinterface.h/.c - CANopen 对象字典操作接口
CO_Emergency.h/.c - CANopen 紧急报文协议
CO_HBconsumer.h/.c - CANopen 心跳消费者(接收心跳)协议
CO_NMT_Heartbeat.h/.c - CANopen 网络管理(NMT)与心跳生产者(发送心跳)协议
CO_PDO.h/.c - CANopen 过程数据对象(PDO)协议
CO_SDOclient.h/.c - CANopen SDO客户端协议(实现主站功能)
CO_SDOserver.h/.c - CANopen SDO服务端协议(实现从站功能)
CO_SYNC.h/.c - CANopen 同步报文协议(同步生产者/消费者)
CO_TIME.h/.c - CANopen 时间戳同步协议
CO_fifo.h/.c - SDO 与网关数据传输的FIFO缓存管理
crc16-ccitt.h/.c - CCITT标准 CRC16 校验算法计算
303/ - CANopen 配套推荐规范
CO_LEDs.h/.c - CANopen 设备状态LED指示驱动
304/ - CANopen 安全相关数据对象,遵循 EN 50325-5:2010 标准
CO_SRDO.h/.c - CANopen 安全型过程数据(SRDO)协议
CO_GFC.h/.c - CANopen 全局故障保护指令(生产者)
305/ - CANopen 层设置服务(LSS)相关协议
CO_LSS.h - LSS层设置服务公共基础协议
CO_LSSmaster.h/.c - LSS主站协议
CO_LSSslave.h/.c - LSS从站协议
309/ - CANopen 跨网络访问协议
scii.h/.c - ASCII码网关协议:支持NMT主站、LSS主站、SDO客户端指令
storage/ 文件夹
CO_storage.h/.c - CANopen 数据存储底层基础驱动
CO_storageEeprom.h/.c - CANopen 掉电存储驱动,适配EEPROM等块存储设备
CO_eeprom.h - EEPROM硬件适配接口,需根据单片机平台自行实现
extra/ 文件夹
open 数据追踪组件,用于周期记录变量波形/日志
example/ 文件夹:通用基础示例,可在任意单片机编译运行
CO_driver_target.h - 硬件底层配置示例定义
CO_driver_blank.c - 空白版CAN硬件适配模板(无硬件依赖)
main_blank.c - 主程序与多线程框架空白模板
CO_storageBlank.h/.c - 空白版掉电存储适配模板
Makefile - 示例工程编译配置文件
DS301_profile.xpd - DS301标准设备描述文件,包含CANopenNode自定义参数
DS301_profile.eds、DS301_profile.md - 标准CANopen设备描述文件(EDS)与说明文档,由工具自动生成
OD.h/.c - 对象字典自动生成代码(由CANopenEditor工具导出)
doc/ - 官方文档目录
CHANGELOG.md - 版本更新日志
deviceSupport.md - 各单片机平台适配说明
objectDictionary.md - 对象字典使用说明文档
CANopenNode.png - 项目图标
html/ - 网页版文档目录(需Doxygen工具手动生成)
CANopen.h/.c - 协议栈总入口,负责全部CANopen组件初始化与周期调度
Doxyfile - Doxygen 文档生成配置文件
LICENSE - 开源协议文件
MISRA.md - MISRA C:2012 编码规范说明
README.md - 项目介绍说明文档
CANopenNode分为三个线程进行运行,分别为:
-
主线程
负责处理大部分协议栈相关函数。 -
定时中断线程
1ms执行一次,负责处理和时间相关的任务。 -
CAN接收线程
当接收到CAN帧时进入到这里并处理。----------------------- | 程序启动 | ----------------------- | ----------------------- | CANopen 协议初始化 | ----------------------- | ----------------------- | 启动多线程 | ----------------------- / | \ / | \
| CAN 接收线程 | | 定时周期线程 | | 主线程 |
| | | | | |
| - 快速响应 | | - 实时线程,固定周期 | | - 处理耗时型任务 |
| - 识别 CAN 报文ID | | 通常为 1ms | | (CANopen 对象): |
| - 初步处理报文 | | - 网络同步 | | - SDO 服务端 |
| - 将数据复制到对应 | | - 将输入(RPDO、硬件) | | - 紧急报文 |
| CANopen 对象中 | | 复制到对象字典 | | - 网络状态管理 |
| | | - 可调用应用层处理 | | - 心跳 |
| | | - 将对象字典变量复制 | | - LSS 从站 |
| | | 到输出(TPDO、硬件) | | - 网关(可选): |
| | | | | - NMT 主站 |
| | | | | - SDO 客户端 |
| | | | | - LSS 主站 |
| | | | | - 可循环调用应用程序 |
STM32 移植 CANopenNode 主站
官方有专门的STM32移植示例:
https://github.com/CANopenNode/CanOpenSTM32
一、核心前提
- CANopenNode 原生支持主站(v4.0+ 完美支持)
- 主站需要额外开启:
CO_CONFIG_MULTIPLE_NODES=1CO_CONFIG_NMT_MASTER=1CO_CONFIG_SDO_CLIENT=1CO_CONFIG_PDO_MASTER=1
- 只需要实现 3 个硬件接口:
- CAN 发送
- CAN 接收(中断)
- 1ms 定时器
二、工程文件结构
YourProject/
├─ CANopenNode/ # 官方源码
├─ Drivers/
│ └─ CANopen/ # 你要写的硬件适配
│ ├─ CO_driver.h
│ └─ CO_driver.c
├─ Core/
│ └─ main.c # 主站初始化 + 主循环
三、关键配置(必须开启)
创建 CO_config_user.h 或直接修改 CO_config.h,开启主站功能:
c
#define CO_CONFIG_MULTIPLE_NODES 1 // 多节点 = 主站必须
#define CO_CONFIG_NMT_MASTER 1 // NMT 主站
#define CO_CONFIG_SDO_CLIENT 1 // SDO 客户端(读/写从站)
#define CO_CONFIG_PDO_MASTER 1 // PDO 主站
#define CO_CONFIG_GATEWAY 1 // 网关/主站模式
// 基础功能保留
#define CO_CONFIG_NMT CO_CONFIG_NMT_MASTER
#define CO_CONFIG_SDO CO_CONFIG_SDO_CLIENT | CO_CONFIG_SDO_SERVER
#define CO_CONFIG_PDO CO_CONFIG_PDO_MASTER | CO_CONFIG_PDO_SLAVE
四、STM32 硬件驱动(直接复制)
1. CO_driver.h
c
#ifndef CO_DRIVER_H
#define CO_DRIVER_H
#include "CANopen.h"
#include "stm32xx_hal.h" // 根据你的芯片改 F1/F4/F7
extern CAN_HandleTypeDef hcan;
extern CO_t *CO;
void CAN_Filter_Init(void);
void CO_CANsetConfigurationMode(void *CANptr);
void CO_CANsetNormalMode(void *CANptr);
CO_ReturnError_t CO_CANinit(CO_t *co, uint16_t bitRate);
void CO_timer1ms(void); // 1ms 定时器中断调用
#endif
2. CO_driver.c(CAN 驱动 + 1ms 定时器)
c
#include "CO_driver.h"
static uint8_t CAN_initStatus = 0;
// CAN 过滤器初始化
void CAN_Filter_Init(void) {
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.FilterBank = 0;
HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
}
// CAN 进入配置模式
void CO_CANsetConfigurationMode(void *CANptr) {
HAL_CAN_Stop(&hcan);
}
// CAN 进入正常模式
void CO_CANsetNormalMode(void *CANptr) {
HAL_CAN_Start(&hcan);
HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
}
// CAN 初始化 + 波特率配置
CO_ReturnError_t CO_CANinit(CO_t *co, uint16_t bitRate) {
if (CAN_initStatus) return CO_ERROR_NO;
hcan.Instance = CAN1;
hcan.Init.Prescaler = 3; // 500k @ 72MHz (F1)
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_9TQ;
hcan.Init.TimeSeg2 = CAN_BS2_4TQ;
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = ENABLE;
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE;
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = ENABLE;
if (HAL_CAN_Init(&hcan) != HAL_OK) return CO_ERROR_SYSTEM;
CAN_Filter_Init();
CAN_initStatus = 1;
return CO_ERROR_NO;
}
// CAN 发送(CANopen 底层调用)
int16_t CO_CANsend(void *CANptr, CO_CANtx_t *txBuff) {
uint32_t TxMailbox;
CAN_TxHeaderTypeDef TxHeader;
TxHeader.StdId = txBuff->ident;
TxHeader.ExtId = 0;
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.IDE = CAN_ID_STD;
TxHeader.DLC = txBuff->DLC;
if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, txBuff->data, &TxMailbox) != HAL_OK)
return CO_ERROR_TX_FULL;
return CO_ERROR_NO;
}
// CAN 接收中断(必须放到 HAL_CAN_RxFifo0MsgPendingCallback)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData);
CO_CANrxMsg_t msg;
msg.ident = RxHeader.StdId;
msg.DLC = RxHeader.DLC;
memcpy(msg.data, RxData, 8);
CO_CANrx(CO, 0, &msg); // 交给 CANopenNode
}
// 1ms 定时器(在 TIM 中断里调用)
volatile uint32_t CO_timer1ms_cnt = 0;
void CO_timer1ms(void) {
CO_timer1ms_cnt++;
}
五、主站初始化(main.c 核心)
c
#include "CANopen.h"
#include "CO_driver.h"
// CANopen 实例(全局)
CO_t CO;
uint8_t CANopenMaster_Init(void) {
// 1. 初始化 CAN 硬件
if (CO_CANinit(&CO, 500) != CO_ERROR_NO) return 1;
// 2. CANopen 主站初始化(节点ID=0x01,主站推荐 1~8)
if (CO_init(&CO, 0x01, 0) != CO_ERROR_NO) return 2;
// 3. 启动协议
CO_start(&CO);
return 0;
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_CAN1_Init();
MX_TIM7_Init(); // 1ms 定时器
// 启动 1ms 定时器中断
HAL_TIM_Base_Start_IT(&htim7);
// 初始化 CANopen 主站
CANopenMaster_Init();
while (1) {
// CANopen 主站主循环(非阻塞)
CO_process(&CO, CO_timer1ms_cnt);
// =============================================
// 你可以在这里写主站逻辑:NMT/SDO/PDO
// =============================================
}
}
// 定时器 1ms 中断服务函数
void TIM7_IRQHandler(void) {
HAL_TIM_IRQHandler(&htim7);
CO_timer1ms();
}
六、主站常用功能(直接调用 API)
1. NMT 主站:启动/停止从站
c
// 启动节点 0x02
CO_NMT_sendNmt(&CO->NMT, CO_NMT_ENTER_OPERATIONAL, 0x02);
// 停止节点 0x02
CO_NMT_sendNmt(&CO->NMT, CO_NMT_ENTER_STOPPED, 0x02);
// 复位节点 0x02
CO_NMT_sendNmt(&CO->NMT, CO_NMT_RESET_NODE, 0x02);
2. SDO 客户端:读取从站 OD
c
// 从节点 2 读取 0x1000.00(U32 设备类型)
uint32_t devType;
CO_SDOclientUpload(&CO->SDOclient[0],
2, // 从站节点ID
0x1000, // 索引
0x00, // 子索引
(uint8_t*)&devType,
4,
1000); // 超时 ms
3. SDO 客户端:写入从站 OD
c
// 写入节点 2 的 0x2000.00 = 0x1234
uint16_t val = 0x1234;
CO_SDOclientDownload(&CO->SDOclient[0],
2,
0x2000,
0x00,
(uint8_t*)&val,
2,
1000);
4. PDO 主站:接收/发送实时数据
CANopenNode 自动处理 TPDO/RPDO,你只需要配置对象字典 OD,主站会自动收发。
七、最常见问题(避坑)
- 主站不工作
必须开启CO_CONFIG_MULTIPLE_NODES=1 - CAN 发不出去
检查波特率、终端电阻(120Ω)、CAN 收发器供电 - 中断不进
必须开启CAN_IT_RX_FIFO0_MSG_PENDING - 从站无响应
先用 NMT 启动从站:ENTER_OPERATIONAL
控制伺服器位置模式运行SDO
模式:位置模式(Profile Position Mode)
协议:CiA 402 驱动标准
适配:汇川 SV660、SV630、SV820、IS620 全系列
一、汇川伺服必须参数(你必须先设置)
1. 伺服面板设置
- 模式 :
Cd01 = 4(CANopen 模式) - 节点ID :
Cd02 = 3(举例用 3,代码里对应) - 波特率 :
Cd03 = 3(500kbps,和代码一致) - 控制模式 :
P-100 = 1(位置模式)
2. 汇川 CiA402 关键对象字典(固定不变)
| 索引 | 子索引 | 功能 | 读写 | 说明 |
|---|---|---|---|---|
| 0x6040 | 0 | 控制字 | W | 启停/使能/触发 |
| 0x6060 | 0 | 模式字 | W | 1=位置模式 |
| 0x607A | 0 | 目标位置 | W | 单位:脉冲 |
| 0x6081 | 0 | 轮廓速度 | W | 位置模式速度 |
| 0x6064 | 0 | 实际位置 | R | 读当前位置 |
| 0x6041 | 0 | 状态字 | R | 伺服状态 |
| 0x6061 | 0 | 实际模式 | R | 1=位置 |
二、STM32F407 主站核心控制代码(直接用)
1. 定义(放在 main.c 顶部)
c
// 汇川伺服节点ID
#define SERVO_NODE_ID 3
// CiA402 模式
#define PROFILE_POSITION_MODE 1
// 控制字命令
#define CW_ENABLE 0x000F // 伺服使能
#define CW_START_POS 0x001F // 触发位置运行
#define CW_FAULT_RESET 0x0080 // 清除故障
2. 1. 伺服初始化(使能 + 切换位置模式)
c
// 伺服初始化:清除故障 → 使能 → 设为位置模式
void Servo_Init(void)
{
uint16_t ctrl;
uint8_t mode;
// 1. 清除故障
ctrl = CW_FAULT_RESET;
SDO_Write_Slave(SERVO_NODE_ID, 0x6040, 0, (uint8_t*)&ctrl, 2);
HAL_Delay(100);
// 2. 伺服使能
ctrl = CW_ENABLE;
SDO_Write_Slave(SERVO_NODE_ID, 0x6040, 0, (uint8_t*)&ctrl, 2);
HAL_Delay(100);
// 3. 设置位置模式
mode = PROFILE_POSITION_MODE;
SDO_Write_Slave(SERVO_NODE_ID, 0x6060, 0, &mode, 1);
HAL_Delay(100);
// 4. 设置运行速度(单位:脉冲/s)
uint32_t speed = 2000; // 你自己改速度
SDO_Write_Slave(SERVO_NODE_ID, 0x6081, 0, (uint8_t*)&speed, 4);
HAL_Delay(100);
}
3. 2. 位置模式运行(关键函数)
c
// 目标位置:单位 脉冲(根据你电子齿轮计算)
void Servo_Move_Pos(int32_t target_pos)
{
uint16_t ctrl;
// 1. 写入目标位置
SDO_Write_Slave(SERVO_NODE_ID, 0x607A, 0, (uint8_t*)&target_pos, 4);
HAL_Delay(20);
// 2. 发送启动运动控制字
ctrl = CW_START_POS;
SDO_Write_Slave(SERVO_NODE_ID, 0x6040, 0, (uint8_t*)&ctrl, 2);
}
4. 3. 读取伺服状态(可选)
c
// 读取实际位置
int32_t Servo_Read_ActualPos(void)
{
int32_t pos = 0;
SDO_Read_Slave(SERVO_NODE_ID, 0x6064, 0, (uint8_t*)&pos, 4);
return pos;
}
// 读取伺服状态字
uint16_t Servo_Read_Status(void)
{
uint16_t sta = 0;
SDO_Read_Slave(SERVO_NODE_ID, 0x6041, 0, (uint8_t*)&sta, 2);
return sta;
}
三、main.c 完整主循环(直接跑)
c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_CAN1_Init();
MX_TIM7_Init();
HAL_TIM_Base_Start_IT(&htim7);
// 初始化 CANopen 主站
Master_Init();
// 【1】初始化伺服
Servo_Init();
HAL_Delay(500);
int32_t target = 100000; // 目标位置 10万脉冲
while (1)
{
CO_process(&coMaster, co1ms);
// 【2】每2秒发一次位置指令
static uint32_t t = 0;
if(co1ms - t >= 2000)
{
t = co1ms;
// 伺服走位置
Servo_Move_Pos(target);
// 正负交替
target = (target == 100000) ? -100000 : 100000;
}
}
}
四、你必须注意的 4 个关键点(否则不动)
1. 伺服急停、使能信号
汇川伺服 必须接 DI1(使能)、DI4(清故障),或者在伺服里设置:
P2-00 = 1开机自动使能P2-10 = 1取消急停
否则 CAN 发指令也不会动。
2. 电子齿轮比(决定你发的脉冲对应多少圈)
汇川默认:
- 一圈 = 1048576 脉冲
你可以在伺服设置: P1-00 = 1048576P1-01 = 1
发 1048576 脉冲 = 1 圈
3. 控制字顺序(必须严格)
- 清故障
0x80 - 使能
0x0F - 写模式
0x6060=1 - 写速度
0x6081 - 写位置
0x607A - 发启动
0x1F
4. 必须加延时
SDO 通信不能发太快,每个指令之间加 20~100ms 延时
PDO 高速位置控制版本
基于 STM32F407 + CANopenNode主站 ,CiA402 标准,RPDO/TPDO 周期1~10ms高速控制,替代慢速SDO,工业稳定。
一、汇川伺服固定参数(必设)
1. 通讯参数
- Cd01 = 4 :通讯协议 CANopen
- Cd02 = 3 :伺服节点ID = 3
- Cd03 = 3 :波特率 500kbps
2. 运动参数
- P1-00 / P1-01 :电子齿轮(默认一圈 1048576)
- P1-09 = 0 :绝对位置模式
- P2-00 = 1 :总线使能有效
- P2-10 = 1 :屏蔽外部急停,纯总线控制
3. PDO出厂默认映射(汇川通用,无需手动改)
接收PDO(RPDO,主站→伺服)
- 0x6040 控制字 uint16
- 0x607A 目标位置 int32
发送PDO(TPDO,伺服→主站)
- 0x6041 状态字 uint16
- 0x6064 实际位置 int32
二、CiA402 关键定义
c
// 伺服节点ID
#define SERVO_ID 3
// 运行模式:轮廓位置模式
#define MOD_PROFILE_POS 1
// 控制字
#define CW_QUICK_STOP 0x0006
#define CW_SWITCH_ON 0x0007
#define CW_ENABLE_OP 0x000F
#define CW_MOVE_TRIG 0x001F
#define CW_FAULT_RST 0x0080
// 状态字掩码
#define STA_READY 0x0001
#define STA_SWITCHED_ON 0x0002
#define STA_OP_ENABLED 0x0004
#define STA_MOVE_DONE 0x1000
三、CANopenNode 主站PDO关键配置
CO_config.h
c
#define CO_CONFIG_MULTIPLE_NODES 1
#define CO_CONFIG_NMT_MASTER 1
#define CO_CONFIG_SDO_CLIENT 1
#define CO_CONFIG_PDO_MASTER 1
#define CO_CONFIG_SYNC 1 // 开启SYNC同步(推荐)
#define CO_PDO_MAX_RX_COUNT 8
#define CO_PDO_MAX_TX_COUNT 8
四、伺服初始化(SDO一次性配置)
上电只执行一次:切位置模式、设置轮廓速度、清除故障
c
// 伺服总线初始化
void Inovance_Servo_Init(void)
{
uint8_t mode = MOD_PROFILE_POS;
uint32_t profileVel = 50000; // 轮廓速度
// 1. 故障复位
uint16_t ctrl = CW_FAULT_RST;
SDO_Write_Slave(SERVO_ID, 0x6040, 0, (uint8_t*)&ctrl, 2);
HAL_Delay(50);
// 2. 设置位置运行模式
SDO_Write_Slave(SERVO_ID, 0x6060, 0, &mode, 1);
HAL_Delay(50);
// 3. 设置轮廓运动速度
SDO_Write_Slave(SERVO_ID, 0x6081, 0, (uint8_t*)&profileVel, 4);
HAL_Delay(50);
// 4. 伺服上电使能
ctrl = CW_ENABLE_OP;
SDO_Write_Slave(SERVO_ID, 0x6040, 0, (uint8_t*)&ctrl, 2);
}
五、PDO 周期位置控制(核心)
全局变量
c
// ==============================
// 汇川伺服 RPDO1 / TPDO1 结构体
// 对应:0x1600 = 从站接收PDO映射
// 对应:0x1A00 = 从站发送PDO映射
// ==============================
typedef struct {
uint16_t ctrlWord; // 0x6040 控制字
int32_t targetPos; // 0x607A 目标位置
} ServoRPDO_t;
typedef struct {
uint16_t statusWord; // 0x6041 状态字
int32_t actualPos; // 0x6064 实际位置
} ServoTPDO_t;
ServoRPDO_t g_ServoTx;
ServoTPDO_t g_ServoRx;
周期PDO发送函数
c
// 周期调用:1~10ms 一次
void Servo_PDO_Cycle(int32_t tarPos)
{
// 1. 填充RPDO数据
g_ServoTx.ctrlWord = CW_MOVE_TRIG; // 允许运动+触发
g_ServoTx.targetPos = tarPos;
// ==========================================
// 2. 写入【主站发送RPDO】映射区
// 汇川伺服默认 RPDO1 映射:
// 0x1600:1 = 0x60400020 (控制字)
// 0x1600:2 = 0x607A0020 (目标位置)
// ==========================================
// 写入主站PDO映射缓存,协议栈自动周期下发
// 对应汇川RPDO1:0x6040 + 0x607A
OD_write(0x1600, 1, (uint8_t*)&g_ServoTx.ctrlWord, 2);
OD_write(0x1600, 2, (uint8_t*)&g_ServoTx.targetPos, 4);
// 3. 触发TX PDO发送
CO_PDOrequest(&coMaster, CO_PDO_TX, 0);
// 4. 读取伺服TPDO反馈(自动更新到OD)
OD_read(0x1A00, 1, (uint8_t*)&g_ServoRx.statusWord, 2);
OD_read(0x1A00, 2, (uint8_t*)&g_ServoRx.actualPos, 4);
}
六、主循环高速调度(最终可用)
c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_CAN1_Init();
MX_TIM7_Init();
HAL_TIM_Base_Start_IT(&htim7);
Master_Init(); // CANopen主站初始化
Inovance_Servo_Init(); // 伺服初始化
int32_t posBuf = 0;
uint32_t pdoTick = 0;
while(1)
{
// 协议栈必须循环调用
CO_process(&coMaster, co1ms);
// 5ms 高速PDO控制
if(co1ms - pdoTick >= 5)
{
pdoTick = co1ms;
// 往返运动
static uint8_t dir = 0;
if(dir == 0)
{
posBuf += 2000;
if(posBuf >= 100000) dir = 1;
}
else
{
posBuf -= 2000;
if(posBuf <= -100000) dir = 0;
}
// PDO下发位置指令
Servo_PDO_Cycle(posBuf);
}
}
}
七、关键补充(必看,解决不动/抖动)
-
绝对/相对位置
汇川默认:
0x607A= 绝对位置需要相对运动:修改伺服参数
P1-09=1 -
到位判断
c
// 判断运动完成
bool Servo_Is_InPosition(void)
{
return (g_ServoRx.statusWord & STA_MOVE_DONE) ? 1 : 0;
}
- 停止伺服
c
void Servo_Stop(void)
{
uint16_t ctrl = CW_SWITCH_ON;
SDO_Write_Slave(SERVO_ID, 0x6040, 0, (uint8_t*)&ctrl, 2);
}
- 为什么PDO比SDO快
- SDO:问答式,单次通讯 5~20ms
- PDO:生产者/消费者,无握手,5ms周期稳定控制,适合轨迹连续运动