STM32 移植 CANopenNode 主站、控制伺服器位置模式运行

简介

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

一、核心前提

  1. CANopenNode 原生支持主站(v4.0+ 完美支持)
  2. 主站需要额外开启:
    • CO_CONFIG_MULTIPLE_NODES=1
    • CO_CONFIG_NMT_MASTER=1
    • CO_CONFIG_SDO_CLIENT=1
    • CO_CONFIG_PDO_MASTER=1
  3. 只需要实现 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,主站会自动收发。


七、最常见问题(避坑)

  1. 主站不工作
    必须开启 CO_CONFIG_MULTIPLE_NODES=1
  2. CAN 发不出去
    检查波特率、终端电阻(120Ω)、CAN 收发器供电
  3. 中断不进
    必须开启 CAN_IT_RX_FIFO0_MSG_PENDING
  4. 从站无响应
    先用 NMT 启动从站:ENTER_OPERATIONAL

控制伺服器位置模式运行SDO

模式:位置模式(Profile Position Mode)
协议:CiA 402 驱动标准
适配:汇川 SV660、SV630、SV820、IS620 全系列


一、汇川伺服必须参数(你必须先设置)

1. 伺服面板设置

  1. 模式Cd01 = 4(CANopen 模式)
  2. 节点IDCd02 = 3(举例用 3,代码里对应)
  3. 波特率Cd03 = 3(500kbps,和代码一致)
  4. 控制模式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 = 1048576
  • P1-01 = 1

1048576 脉冲 = 1 圈

3. 控制字顺序(必须严格)

  1. 清故障 0x80
  2. 使能 0x0F
  3. 写模式 0x6060=1
  4. 写速度 0x6081
  5. 写位置 0x607A
  6. 发启动 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);
        }
    }
}

七、关键补充(必看,解决不动/抖动)

  1. 绝对/相对位置

    汇川默认:0x607A = 绝对位置

    需要相对运动:修改伺服参数 P1-09=1

  2. 到位判断

c 复制代码
// 判断运动完成
bool Servo_Is_InPosition(void)
{
    return (g_ServoRx.statusWord & STA_MOVE_DONE) ? 1 : 0;
}
  1. 停止伺服
c 复制代码
void Servo_Stop(void)
{
    uint16_t ctrl = CW_SWITCH_ON;
    SDO_Write_Slave(SERVO_ID, 0x6040, 0, (uint8_t*)&ctrl, 2);
}
  1. 为什么PDO比SDO快
  • SDO:问答式,单次通讯 5~20ms
  • PDO:生产者/消费者,无握手,5ms周期稳定控制,适合轨迹连续运动

相关推荐
【ql君】qlexcel9 天前
可跑在STM32上的CANopen主机协议栈
协议栈·canopen·canfestival·canopennode
SysMax16 天前
免费CANopen上位机kh-canopentool 0.2.1 发布
can通信·canopen·pcan
疆鸿智能研发小助手16 天前
当EtherNet/IP遇见CANopen:一台网关的储能产线“破壁”实录
工业自动化·变频器·ethernet ip·canopen·工业通讯·协议转换网关·伺服
疆鸿智能研发小助手19 天前
EtherCAT转CANopen网关模块在工业机器人系统中的典型应用案例解析
工业自动化·ethercat·变频器·仪表·canopen·协议转换网关·机械手臂
疆鸿智能研发小助手1 个月前
【无标题】
工业自动化·profinet·canopen·工业通讯·协议转换网关·伺服
ZZZ_XXJ3 个月前
[EtherCAT]对象字典从0x1000到0xFFFF功能分区详解——2026.02.09
stm32·嵌入式开发·ethercat·工业以太网·canopen·对象字典·工控协议
疆鸿智能研发小助手3 个月前
疆鸿智能PROFINET转CANOPEN网关:铝加工热轧制造的数据互联引擎
网关·温度传感器·工业自动化·profinet·canopen·工业通讯·协议转换网关
rosemary5125 个月前
canopen(CanFestival) SDO Read/Write
canopen·canfestival·sdo read/write
xqlily5 个月前
CanOpen控制系统概述
canopen