找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2014

积分

0

好友

290

主题
发表于 2025-12-25 19:58:12 | 查看: 31| 回复: 0

睿擎派(Ruijing Pi)是一款基于瑞芯微 RK3506 主控芯片的嵌入式开发平台,其底层运行着 RT-Thread 操作系统,并构建于专为工业场景设计的睿擎工业平台之上。该平台整合了数据采集、通信、控制、工业协议等多重功能。

在开发基于该平台的工业网关时,我们发现官方提供的示例代码主要围绕 CANOpen 协议下的 DS402(伺服电机控制)设备规范,而缺少针对 DS401(IO模块)协议的操作文档与案例。经过一段时间的深入研究与实践,我们成功实现了对雷赛 EM32DX-C4 IO 模块的信号采集与输出控制。本文将分享相关的 CANOpen 核心知识、调试方法及具体的代码实现。

一、CANOpen 协议核心概念解析

CANOpen 是一种基于 CAN 总线的、遵循 EN 50325-4 标准的工业通信协议。其核心优势在于通过标准化的对象字典(Object Dictionary, OD) 确保设备互操作性,并提供了 PDO(过程数据对象)、SDO(服务数据对象)等多种灵活的通信机制。

1. 设备状态机

CANOpen 设备必须遵循一个严格的状态机,主要包括以下四个状态:

  • 初始化状态 (Initialization):设备上电自检及协议栈初始化,不参与网络通信。
  • 预操作状态 (Pre-operational):设备就绪,允许通过 SDO 进行参数配置,但 PDO 通信被禁止。
  • 操作状态 (Operational):设备正常工作的状态,PDO 和 SDO 通信均被启用,可进行实时数据交换。
  • 停止状态 (Stopped):设备功能受限的安全状态,仅允许 NMT 命令和心跳通信,PDO 被禁用。

CANOpen状态机

2. 核心通信模型与概念

  • 对象字典 (OD):设备所有参数和行为定义的集合,采用“16位索引 + 8位子索引”进行寻址。DS301协议定义了所有设备必须支持的通用对象字典,而 DS401 等子协议则定义了特定设备(如IO模块)的专有字典条目。

对象字典结构
(DS301通用对象字典示例)

EM32DX-C4设备参数
(EM32DX-C4设备参数对象字典示例)

  • COB-ID:即11位 CAN 帧ID,由高4位功能码和低7位节点地址组成。
  • 网络管理 (NMT):用于控制节点状态(启动、停止、复位)的服务。
  • 服务数据对象 (SDO):用于读写对象字典中的参数,适合非实时、低频次的配置操作。
  • 过程数据对象 (PDO):用于设备间高速、实时的数据传输,是工业控制中数据交互的关键通道。
  • 心跳 (Heartbeat):从节点定期发送的报文,用于向主站宣告自身状态和在线情况。
  • 同步 (SYNC):由主站发起的同步报文,用于协调多个从节点的数据采集与输出动作。

二、CANOpen DS401 IO模块控制实战

睿擎派官方示例基于开源的 CanFestival 协议栈实现。我们在其 06_bus_canopen_master_motor(DS402 主站示例)的基础上,进行了大幅修改以适应 DS401 IO 模块的控制。

1. 硬件连接

EM32DX-C4 模块的 CANOpen 接口采用 RJ45 以太网口定义。接线时,仅需将模块的 CAN_H(白橙线)和 CAN_L(橙线)分别连接至睿擎派的 CAN_HCAN_L 接口即可。

接线定义

调试利器:在开发初期,使用 PCAN-USB 模块配合 PCAN-View 软件监听总线数据,能极大提升问题排查效率。

PCAN-View调试

2. 关键代码适配与实现

a. 主站对象字典简化 (master401_od.c)
由于主站(睿擎派)的角色是控制IO模块,因此需要定义自身的对象字典,用于映射和缓存IO数据。我们大幅删减了原DS402示例中关于伺服电机的复杂字典条目,并新增了用于缓存DO输出和DI输入数据的对象。

// 主站对象字典索引表(简化后)
const indextable master401_objdict[] = {
  { (subindex*)master401_Index1000, sizeof(master401_Index1000)/sizeof(master401_Index1000[0]), 0x1000},
  { (subindex*)master401_Index1001, sizeof(master401_Index1001)/sizeof(master401_Index1001[0]), 0x1001},
  // ... 其他DS301必要索引 ...
  { (subindex*)master401_Index2000, sizeof(master401_Index2000)/sizeof(master401_Index2000[0]), 0x2000}, // 新增:DO缓存
  { (subindex*)master401_Index2001, sizeof(master401_Index2001)/sizeof(master401_Index2001[0]), 0x2001}, // 新增:DI缓存
};

其中,0x20000x2001 索引的定义如下,分别对应16位DO输出值和DI输入值:

/* 0x2000: 本地DO输出缓存 */
uint16_t master401_obj2000_do_val = 0x0000; // 关联全局DO变量
subindex master401_Index2000[] = {
  { RO, uint8,  sizeof (UNS8), (void*)&master401_highestSubIndex_obj2000, NULL },
  { RW, uint16, sizeof (uint16_t), (void*)&master401_obj2000_do_val, NULL } // 可读可写
};

/* 0x2001: 本地DI输入缓存 */
uint16_t master401_obj2001_di_val = 0x0000; // 关联全局DI变量
subindex master401_Index2001[] = {
  { RO, uint8,  sizeof (UNS8), (void*)&master401_highestSubIndex_obj2001, NULL },
  { RO, uint16, sizeof (uint16_t), (void*)&master401_obj2001_di_val, NULL } // 只读
};

b. 从站PDO映射配置 (master401_canopen.c)
这是实现IO通信的核心。我们需要通过SDO配置从站(EM32DX-C4)的PDO,将它的DI输入映射到它的TPDO1发送给主站,并配置它的RPDO1来接收主站发送的DO输出命令。

配置过程遵循标准流程:禁用PDO -> 设置传输类型 -> 清除旧映射 -> 写入新映射 -> 设置映射条目数 -> 启用PDO。

关键配置代码示例如下(以TPDO1,即从站发送DI为例):

static UNS8 IO_Write_SLAVE_TPDO1_Map(uint8_t nodeId) {
    // TPDO1映射:将从站索引0x6100子索引0x01(16位DI值)映射到TPDO1
    UNS32 pdo_map_val = 0x61000110;  // 索引0x6100 + 子索引0x01 + 16位长度(0x10)
    return writeNetworkDictCallBack(OD_Data, nodeId, 0x1A00, 1, 4, uint32, &pdo_map_val, config_node_param_cb, 0);
}

同样地,需要配置RPDO1映射到从站的0x6300子索引0x01(16位DO值)。所有这些配置函数被组织成一个数组顺序执行。

c. 上层应用API
为了方便测试和控制,我们封装了简单的IO操作函数,并注册为RT-Thread的MSH命令。

/* 设置所有DO输出 */
rt_err_t em32dx_set_do(uint16_t do_val) {
    if (*can_node[1].nmt_state != Operational) {
        return -RT_ERROR;
    }
    g_em32dx_do = do_val;
    // 通过写本地对象字典0x2000,触发PDO发送
    UNS32 size = 2;
    UNS32 errorCode = writeLocalDict(OD_Data, 0x2000, 1, &do_val, &size, 0);
    return (errorCode == OD_SUCCESSFUL) ? RT_EOK : -RT_ERROR;
}

/* 读取所有DI输入 */
rt_err_t em32dx_get_di() {
    uint16_t di_val = 0;
    UNS32 size = 2;
    UNS8 data_type;
    // 从本地对象字典0x2001读取(由从站TPDO1自动更新)
    UNS32 errorCode = readLocalDict(OD_Data, 0x2001, 1, &di_val, &size, &data_type, 0);
    if(errorCode == OD_SUCCESSFUL){
        g_em32dx_di = di_val;
        rt_kprintf("Read DI: 0x%04X\n", di_val);
    }
    return (errorCode == OD_SUCCESSFUL) ? RT_EOK : -RT_ERROR;
}
MSH_CMD_EXPORT(em32dx_get_di, Get EM32DX-C4 DI input);

/* 控制单路DO通道 */
rt_err_t em32dx_set_do_channel(uint8_t argc, char **argv) {
    uint8_t channel = atoi(argv[1]);
    uint8_t state = atoi(argv[2]);
    if (channel >= 16) return -RT_ERROR;
    // 更新全局DO变量对应位
    if (state) g_em32dx_do |= (1 << channel);
    else g_em32dx_do &= ~(1 << channel);
    // 调用设置函数
    return em32dx_set_do(g_em32dx_do);
}
MSH_CMD_EXPORT(em32dx_set_do_channel, Set single DO channel (channel 0-15, state 0/1));

三、部署与测试

  1. 启动服务:在睿擎派控制台执行 canopen_start,初始化CANOpen主站并尝试与从站建立连接。
  2. 读取输入:执行 em32dx_get_di 命令,可以读取EM32DX-C4模块上16路数字量输入(DI)的当前状态。
  3. 控制输出:执行 em32dx_set_do_channel 1 1 命令。第一个参数为通道号(0-15),第二个参数为状态(0/1)。此命令将控制模块上对应的数字量输出(DO)通道导通,观察模块上的指示灯会发生相应变化。

命令测试输出

通过上述步骤,我们成功在睿擎派平台上实现了基于CANOpen DS401协议对第三方工业IO模块的读写控制,为构建支持多协议、可编程逻辑控制器(PLC)功能的工业网关奠定了基础。

源码与资料

  • 本文涉及的完整项目源码可通过文末提供的链接下载。
  • CANOpen DS301、DS401、DS402等协议官方文档可在CAN in Automation (CiA)官网获取。



上一篇:最近点对算法实现:分治思想与Python代码详解
下一篇:for循环与while循环的本质区别:从语法到编程思维的深度解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 11:55 , Processed in 0.215588 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表