当前位置: 首页 > news >正文

CANopenNode协议栈之对象字典概念

对象字典(OD)是赋予 CANopenNode 身份与行为的核心数据结构。它由 CiA 301 标准定义,是影响应用对象、通信对象以及设备状态机行为的所有数据项的集合。它作为通信栈与应用层之间的唯一接口——每一次 SDO 读取、每一次 PDO 传输以及每一次心跳交换,最终都会触及某个 OD 变量。

字典中的每个对象均通过16 位索引8 位子索引进行寻址,从而构成一张二维查找表,将每个数据项映射至已知位置。理解了对象字典,也就理解了所有 CANopen 设备的核心骨干。

对象字典的结构

种对象类型

CANopenNode 在字典中定义了三种基本对象类型,每种类型都有对应的 C 结构体表示:

对象类型CiA 301 代码C 结构体描述
VAR7OD_obj_var_t任意数据类型的单一变量,位于子索引 0
ARRAY8OD_obj_array_t相同类型变量组成的数组;子索引 0 保存元素数量
RECORD9OD_obj_record_t[]由不同类型子元素组成的结构体,每个元素拥有各自的属性

OD_obj_var_t结构体最为简单——它包含一个指向实际数据的指针(dataOrig)、一个属性位掩码(attribute)以及以字节为单位的数据长度(dataLength)。OD_obj_array_t在此基础上进行了扩展,为子索引 0 和数组元素分别提供了独立的指针和属性,并增加了用于指针运算的dataElementSizeofOD_obj_record_t是一个由类OD_obj_var_t条目组成的数组,每个条目都标有各自的subIndex

OD 条目与 OD 根

在更高一层的抽象中,每个 OD 对象由一个OD_entry_t表示——这是一个目录条目,包含 16 位索引、子条目数量、对象类型标识符、指向特定类型对象数据的指针(odObject),以及指向可选应用扩展的指针(extension)。这些条目被收集到一个有序数组中,而OD_t结构体则使用一个大小字段对该数组进行了封装:

typedef struct { uint16_t index; // 例如 0x1000 uint8_t subEntriesCount; // 包含子索引 0 在内的子条目总数 uint8_t odObjectType; // ODT_VAR, ODT_ARR 或 ODT_REC CO_PROGMEM void* odObject; // 指向特定类型对象数据的指针 OD_extension_t* extension; // 可选的应用层扩展(默认为 NULL) } OD_entry_t; typedef struct { uint16_t size; // 条目数量,不包含空白终止符 OD_entry_t* list; // OD 条目的有序数组 } OD_t;

下图展示了这些结构体之间的关系:

OD 条目列表必须按索引升序排列。这是一项硬性要求——OD_find()使用复杂度为 O(log N) 的二分查找算法,如果条目顺序混乱,查找操作将发生静默失败。

存储组:按生命周期组织数据

OD 变量并非以扁平数据块的形式存储,而是被组织成多个存储组,这些组是 C 结构体,其名称体现了持久化语义。在示例 OD 中,定义了两个组:

存储组用途持久化至 NVM?
PERSIST_COMM跨越电源周期的通信参数(设备类型、COB-ID、心跳时间、PDO 参数)
RAM易失性运行时数据(错误寄存器、错误历史、SDO 服务器参数)

生成的头文件将每个组声明为 typedef 结构体和 extern 全局变量。例如,OD_PERSIST_COMM_t包含所有持久化通信参数,而OD_RAM_t存放易失性运行时状态。这种分组机制使得存储子系统能够选择性地将特定组保存至非易失性存储器或从中恢复。

访问 OD 变量:接口层

通用访问模式

CANopenNode 通过 OD 接口提供了一种简洁且与类型无关的访问模式。无需直接访问底层 C 结构体。其操作流程始终如下:

  1. 使用OD_find()通过索引查找OD 条目——返回指向OD_entry_t的指针

  2. 使用OD_getSub()通过子索引获取子对象——填充OD_IO_t结构体

  3. 通过OD_IO_t中的函数指针进行读或写——基于流操作,类似于 POSIX 的read()/write()

OD_IO_t结构体包含一个OD_stream_t(持有dataOrigdataLengthdataOffsetattributeindexsubIndex)以及函数指针readwrite。当未配置扩展时,这些指针默认指向OD_readOriginal()OD_writeOriginal()——这是简单的memcpy操作,通过dataOffset处理部分数据传输。

辅助 Getter 与 Setter 函数

对于简单的单次访问,CANopenNode 在OD_get_value()OD_set_value()的基础上提供了类型化的便捷封装。这些是static inline函数,可自动处理类型大小:

函数C 类型CANopen 类型
OD_get_i8/OD_set_i8int8_tINTEGER8
OD_get_u16/OD_set_u16uint16_tUNSIGNED16
OD_get_u32/OD_set_u32uint32_tUNSIGNED32
OD_get_f32/OD_set_f32float32_tREAL32
OD_get_u64/OD_set_u64uint64_tUNSIGNED64
OD_getPtr()void*直接指针访问

辅助函数方式更为简单,但在频繁访问时效率较低,因为每次调用都会重新执行完整的OD_find()OD_getSub()链。对于热点路径变量,建议在初始化后缓存OD_IO_t并直接调用io.read()

直接全局访问

对于简单应用,生成的头文件还会暴露直接的 C 全局变量和条目快捷方式:

#include "OD.h" /* 直接变量访问(无锁,无查找) */ uint32_t devType = OD_PERSIST_COMM.x1000_deviceType; OD_PERSIST_COMM.x1018_identity.serialNumber = 0x12345678; /* 直接条目指针访问 */ OD_entry_t *entry = OD_ENTRY_H1001_errorRegister;

如果已通过OD_extension_init()在某条目上注册了 OD 扩展,直接变量访问将绕过该扩展,从而读取到陈旧或无意义的数据。在扩展处于活动状态时,请务必使用 IO 接口进行访问。

属性:定义访问权限

每个 OD 子对象都带有一个 8 位属性位掩码(OD_attr_t),用于控制谁可以访问它以及如何访问。这些标志是 CANopen 访问模型的强制执行机制:

属性数值含义
ODA_SDO_R0x01SDO 服务器可读
ODA_SDO_W0x02SDO 服务器可写
ODA_TPDO0x04可映射至 TPDO(发送)
ODA_RPDO0x08可映射至 RPDO(接收)
ODA_TSRDO0x10可映射至发送 SRDO
ODA_RSRDO0x20可映射至接收 SRDO
ODA_MB0x40多字节变量(int16 及以上——需处理大小端)
ODA_STR0x80字符串类型——较短的写入操作将以零填充剩余部分

属性通过按位或运算组合。例如,地址 0x1001 处的错误寄存器定义为ODA_SDO_R | ODA_TPDO(可通过 SDO 读取,可映射至 TPDO),而地址 0x1017 处的生产者心跳时间则使用ODA_SDO_RW | ODA_MB(可通过 SDO 读写,多字节用于大小端转换)。内联函数OD_mappable()通过测试ODA_TRPDO | ODA_TRSRDO位来检查变量是否可以映射至任何 PDO 或 SRDO。

OD 扩展:自定义读/写行为

OD_extension_t机制是 CANopenNode OD 接口最强大的功能。它允许应用使用自定义函数覆盖默认基于memcpy的读/写操作,从而有效地将任何 OD 条目转换为按需计算其值的虚拟变量。

当对某条目调用OD_extension_init()时,extension指针即被设置。随后,OD_getSub()会检查该指针:如果非 NULL,它会将io.readio.write指向扩展的函数指针(如果指针为 NULL,则指向OD_readDisabled/OD_writeDisabled)。该扩展还携带一个可选的object指针,该指针会通过stream->object传递给自定义函数,此外还有一个用于事件驱动 TPDO 触发的flagsPDO位域。

OD_readOriginal()OD_writeOriginal()函数可在自定义扩展函数内部使用。这允许扩展在增强行为(例如验证取值范围)的同时,仍将实际的内存操作委托给默认实现处理。

返回码与 SDO 终止映射

每个 OD 访问函数都会返回一个ODR_t枚举值。这些代码与 CiA 301 SDO 终止码一一对应,可通过OD_getSDOabCode()函数进行转换。这种设计意味着 SDO 服务器可以直接将 OD 返回码传递给网络,而无需单独的错误映射层:

ODR_t 值SDO 终止码含义
ODR_OK(0)0x00000000成功
ODR_PARTIAL(-1)部分传输,需再次调用
ODR_IDX_NOT_EXIST(5)0x06020000索引不在字典中
ODR_SUB_NOT_EXIST(14)0x06090011未找到子索引
ODR_READONLY(4)0x06010002尝试写入只读对象
ODR_TYPE_MISMATCH(11)0x06070010数据类型/长度不匹配
ODR_INVALID_VALUE(15)0x06090030值超出有效范围

CANopen 数据类型

CANopenNode 使用映射至 CiA 301 / IEC 61131-3 数据类型的标准 C 类型。下表展示了整个 OD 中使用的核心映射关系:

CANopenNode C 类型IEC 61131-3CANopen 名称dataType 代码
bool_tBOOLBOOLEAN0x01
int8_tSINTINTEGER80x02
int16_tINTINTEGER160x03
int32_tDINTINTEGER320x04
uint8_tUSINTUNSIGNED80x05
uint16_tUINTUNSIGNED160x06
uint32_tUDINTUNSIGNED320x07
uint64_tULINTUNSIGNED640x1B
float32_tREALREAL320x08
char[]STRINGVISIBLE_STRING0x09
uint8_t[]BITSTRINGOCTET_STRING0x0A

完整的 OD 文件对

一个真正的对象字典由生成的文件对OD.hOD.c进行定义,它们是由 CANopenEditor 工具根据 XML 设备描述生成的。其结构遵循一致的模式:

文件部分内容示例
计数器用于对象计数的#defineOD_CNT_RPDO 4,OD_CNT_TPDO 4
存储组结构体按生命周期分组的 C typedefOD_PERSIST_COMM_t,OD_RAM_t
Extern 声明全局变量和 OD 指针声明extern OD_PERSIST_COMM_t OD_PERSIST_COMM;
条目快捷方式用于直接条目访问的#define#define OD_ENTRY_H1001 &OD->list[1]
数据初始化所有存储组的默认值.x1000_deviceType = 0x00000000
OD 对象常量类型描述符 (VAR/ARRAY/RECORD)OD_obj_var_t,OD_obj_record_t[]
OD 条目列表带有终止符的OD_entry_t有序数组{0x1000, 0x01, ODT_VAR, ...}, ...
OD 根封装条目列表的OD_t结构体OD_t *OD = &_OD;

生成文件中的警告十分明确:“请勿手动编辑此文件”。手动修改会破坏与 XML 源文件的同步,且极易引发错误。整个 OD 定义应通过编辑器工具及其 XML 项目文件(遵循 CiA 311 标准的.xpd/.xdd格式)进行管理。

线程安全考量

OD 变量会在多个执行上下文中被访问。CANopenNode 通常至少运行在两个线程中:一个快速实时线程(处理 PDO、SYNC)和一个慢速主线程(处理 SDO 服务器、心跳)。两者可能会并发读写同一个 OD 变量。

协议栈提供了CO_LOCK_OD()CO_UNLOCK_OD()宏用于临界区保护。在主线程中访问可映射至 PDO 的变量时,必须使用这些宏。然而,在实时线程中绝对不可使用加锁操作(因为这会阻塞 ISR),在自定义的read()/write()扩展函数内部也绝对不可使用加锁操作(因为这会导致死锁)。在初始化阶段,即线程启动之前,则无需加锁。

http://www.jsqmd.com/news/1158071/

相关文章:

  • 宇舶中国官方售后服务中心|地址与联系电话权威信息通告(2026年7月最新) - 亨得利官方服务中心
  • TPS61170+PIC18F4682高电压DC-DC升压转换系统设计
  • 斯坦福AutoMem框架:优化Agent记忆管理,提升长任务性能2-4倍
  • 2024年AI大模型应用落地:从RAG架构到成本控制的实践路线图
  • PixVerse Team Ultra:团队协作AI视频生成平台核心功能与实战指南
  • 2026年7月最新嘉兴天梭官方售后客服服务电话及地址网点大全 - 天梭服务中心
  • C++ COM 自动化 Word 2021 实战:3种方法创建文档并插入表格与图片
  • 2026年有实力的通体大理石瓷砖定做厂家盘点 - 品牌排行榜
  • Android ML Kit OCR 性能优化:识别速度提升 30% 的 3 个关键配置
  • Python 3.12 + Ray 分布式爬虫实战:30省份水稻数据采集,6核CPU效率提升3倍
  • Java内存泄漏排查:一次线上故障的完整复盘
  • C++算法小结
  • 爱彼官方更换表蒙价格查询|地址与售后电话权威信息公告(2026年7月最新) - 爱彼中国官方服务中心
  • MERRA-2 AOD 数据下载:3种官方方法对比与 wget 批量下载实战
  • NSK RA25BN 滚子直线导轨技术详解
  • 用了两周 WorkBuddy,我把免费算力和「对话搭系统」的坑都踩了一遍(附 12 本保姆级笔记)
  • 190、日志驱动调试:不依赖断点的异步代码、分布式系统排错方法论
  • 计算机大数据毕设实战-基于 Django 的区域人口普查数据可视化系统的设计与实现 基于 Django 的人口普查信息综合管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • Web与移动端地理位置获取技术:从基础API到隐私保护实践
  • 新手必看!用Python轻松打造你的第一个RAG项目,玩转AI知识库问答!
  • 亲身到店探访海口亨得利官方名表服务中心|地址与官方客服热线(2026年7月更新) - 亨得利官方
  • 何恺明团队新研究:从多视图学习本质视觉表征,突破监督学习泛化瓶颈
  • 【AI专栏】图解Transformer - 第05章:LLM 推理工程
  • SAP HANA JDBC ngdbc 2.20.11 实战:3种高级调试与性能追踪配置详解
  • 全屋WiFi怎么选?Mesh和WDS差别巨大,别再白花冤枉钱
  • 2026年7月最新万国龙湖杭州上城天街维修保养服务电话 - 万国中国官方服务中心
  • EL-JY-II 实验箱 3 种存储器访问模式对比:手动开关、键盘监控与 FPGA 模拟
  • PixWorld像素空间扩散:3D场景生成与重建的统一框架解析
  • 还在为网络中断发愁?tchMaterial-parser让电子课本随时随地为你服务
  • 京东十大靶向人群 vs 天猫八大策略人群:5 个维度的精细化运营对比指南