跳到主要内容

10. 对象字典、数据类型和小端序

学习形式概念课
硬件要求不需要
配套 Demo
本课动作不写电机对象

上一课只能从 Heartbeat 知道电机节点在线,以及它当前处于哪一种 NMT 通信状态。如果还想知道“这是什么类型的设备”,就需要读取电机提供的设备信息。

这个设备信息应该到哪里找?

CANopen 的共同通信规范 CiA 301 定义了一个标准参数:Device Type。它在对象字典中的地址是:

0x1000:00

其中 0x100000 都不是本教程或电机厂家临时选择的数字。0x1000 Device Type 是 CiA 301 定义的共同对象。本课会先查看资料和已有的原始通信记录,弄清这些结论各自来自哪里;第 11 课再由学员自己的 ESP32 发出读取请求。

现在又出现了新的问题:规范为什么用 0x1000:00 指向这个参数?冒号两边分别代表什么?一台电机内部还有故障状态、当前位置、目标速度和许多配置项,ESP32 又怎样准确说明“我要读其中哪一项”?

只发送“读取当前位置”这几个汉字肯定不行。不同国家、不同厂家和不同程序使用的名称可能不一样,CAN 报文也只携带数字和字节。

CANopen 的做法是给设备对外开放的每项数据安排一个数字地址。例如,前面 Device Type 的标准地址是 0x1000:00。ESP32 想读取设备类型时,会通过 CANH、CANL 发送一条带有这个地址的 CANopen 请求;电机看到地址后,找到对应数据并返回结果。

先不要急着背“对象字典”的定义。我们先追踪一次真实读取:地址从哪份资料查到、原始字节是谁发送的、程序又根据什么把字节解释成数值。走完这条过程后,再回头给对象字典下定义。

这一课不向电机发送报文。我们先学会回答四个问题:

0x1000:00 的两个数字分别是什么?
为什么一个参数地址还需要冒号和两部分数字?
同样四个字节为什么可能是正数,也可能是负数?
数值 0x12345678 为什么会排列成 78 56 34 12?

下一课会学习 CANopen 中专门访问这些参数的通信方式,它叫 SDO。先把上面的问题弄清楚,下一课才能真正读懂一组 SDO 请求和响应,而不是照着八个字节死记。

先弄清这些答案从哪里来

先直接回答最容易困惑的地方:

电机不会主动把“0x1000 叫 Device Type、它是 UNSIGNED32”这些说明发给 ESP32。

上一课看到的 Heartbeat 是电机按照周期主动发送的。对象字典参数不是这样工作的。读取 Device Type 时,信息来自两个不同地方:

协议规范、厂家手册或 EDS 告诉我们地址、名称、类型和访问权限
电机的 CANopen 响应 告诉我们这台电机当前返回的实际字节

程序员先看资料,知道应该访问哪个地址、怎样解释结果;然后程序向电机发请求。电机返回的是字节,不会返回一行中文解释。

因此,“看懂一次对象读取”其实要分成四个问题:

要回答的问题答案从哪里来
该读哪个地址CiA 规范、厂家手册或当前设备的 EDS
这个地址的类型和权限是什么同样查规范、手册或 EDS
这台电机实际返回了什么查看电机的 SDO 响应帧
返回字节最终表示多少程序按数据类型和小端序解码

这不是靠经验看一眼字节就能猜出来的。如果没有对象定义资料,只看 92 01 04 00,最多只能说电机返回了四个字节;不能凭空断定它叫 Device Type,也不能断定它是有符号还是无符号数。

这条过程可以顺着下图从上向下看:

从协议资料和电机响应得到对象值的过程

接下来不要求相信图中的结论。我们分别去看它们来自哪里。

0x1000 Device Type 从哪里查到

第 07 课已经使用过 CAN in Automation 发布的 CiA 301 CANopen 海报。当时查看的是默认 CAN-ID,这次看同一张海报左下方的对象字典表。

先只找表格第一行,不需要一次看完所有对象:

CiA 301 海报中的通用通信对象表

图片来源:CAN in Automation,CiA 301 CANopen poster(2024),第 1 页 General communication objects 区域。

第一行写着:

Index 1000h
Object VAR
Name Device type

海报中的 1000h 与 C 代码中的 0x1000 是同一个十六进制数。因此,我们可以从这行资料得到:

0x1000 这个地址的名称是 Device Type
它是 VAR,也就是只包含一个值的对象

这不是根据电机返回的 0x00040192 猜出来的,也不是从另一款电机代码抄来的。地址和名称先由 CANopen 共同规范给出。

同一张表中还能找到:

1001h Error register
1017h Producer heartbeat time
1018h Identity object

现在先认识这些行在哪里,不需要记住整张表。

数据类型和只读权限从哪里查到

海报适合快速查地址和名称,但没有展开每个条目的全部属性。实际开发还会查完整规范、当前设备的通信手册或 EDS。

EDS 是 Electronic Data Sheet,中文常称电子数据表。它是一种文本格式,用来描述 CANopen 设备实现的对象、数据类型、访问权限和默认值等信息。

一份标准示例 EDS 中,0x1000 条目写成:

[1000]
ParameterName=Device type
ObjectType=0x7
DataType=0x0007
AccessType=ro
DefaultValue=0x00000000

现在逐行读,不跳过来源:

EDS 内容怎样理解
[1000]这一段描述 Index 0x1000
ParameterName=Device type参数名称是 Device Type
DataType=0x0007数据类型编号 0x0007,即 UNSIGNED32
AccessType=roro 是 read only,只允许读取
DefaultValue=0x00000000这份示例文件给出的默认值

数据类型编号同样来自 CANopen 的类型定义。初学阶段先使用下面三个常见对应关系:

0x0005 UNSIGNED8
0x0006 UNSIGNED16
0x0007 UNSIGNED32

必须注意:上面是一份公开的标准示例 EDS,不是 IG35EC020 的 EDS。它可以帮助我们学习怎样查看 EDS,也能核对标准对象的写法,但不能把示例中的 DefaultValue 当成当前电机的实际值。

示例原文件:DS301_profile.eds

本教程现有的 IG35EC020 资料没有提供完整 EDS。因此,标准对象的地址和类型来自 CANopen 规范,当前电机是否实现、实际返回什么,还要通过真实通信确认;厂商私有对象则不能猜。

电机实际发送了什么

课程保留的最早完整原始记录,当时运行的是原始 TWAI 和手写 SDO 实验工程。

ESP32 先发送:

TX CAN-ID 0x601 DLC 8 DATA 40 00 10 00 00 00 00 00

这里的 0x1000:00 并没有消失,而是按 SDO 格式放进了 DATA 的固定位置。这个字节布局可以在 CiA 301 CANopen 海报第 1 页的 SDO services 表 中核对。先把 8 个字节标上序号:

DATA 位置DATA[0]DATA[1]DATA[2]DATA[3]DATA[4]DATA[7]
本次内容4000100000 00 00 00
SDO 中的用途读取请求Index 低字节Index 高字节Sub-index读取请求中未使用

CANopen 在 SDO 帧中先放 Index 的低字节,再放高字节。因此 DATA[1]DATA[2] 要按小端序组合:

Index = DATA[1] | (DATA[2] << 8)
= 0x00 | (0x10 << 8)
= 0x1000

DATA[3]00,它就是 Sub-index 0x00。所以这帧 DATA 实际表达的是:

40 00 10 00 00 00 00 00
----- --
0x1000 00

请求读取对象 0x1000:00

注意不要把 CAN-ID 0x601 和对象地址 0x1000:00 混在一起:

CAN-ID 0x601 表示这是发给 Node-ID 0x01 的默认 SDO 请求
DATA 中的 00 10 00 表示这次要读的对象是 0x1000:00

前者说明“这帧发给哪个节点的哪种服务”,后者说明“这次具体读哪个参数”。

DATA[0] 为什么是 0x40,属于 SDO 命令字的完整定义,第 11 课再详细拆解。当前只需要知道:0x40 表示客户端想读取一个对象。

电机随后返回:

RX CAN-ID 0x581 DLC 8 DATA 43 00 10 00 92 01 04 00

响应中的 DATA[1]DATA[2]DATA[3] 仍然是 00 10 00。电机把这次访问的 Index 和 Sub-index 原样带回,ESP32 因此可以检查:这的确是对 0x1000:00 请求的回复,而不是其他对象的报文。

对照两帧可以看得更清楚:

报文DATA[0] 命令DATA[1..2] IndexDATA[3] Sub-indexDATA[4..7]
ESP32 请求4000 100x100000未使用
电机响应4300 100x10000092 01 04 00

0x6010x581 和响应命令字 0x43 的完整定义留到第 11 课。当前继续看最后四个对象数据字节:

92 01 04 00

这四个字节才是电机针对这次请求返回的实际内容。在组合它们之前,先把两个判断依据落到可以打开核对的资料上。

依据一:0x1000UNSIGNED32

CANopenNode 的标准 DS301 示例 EDS0x1000 条目中写着:

[1000]
ParameterName=Device type
DataType=0x0007
AccessType=ro

DataType=0x0007 还需要查数据类型表才能翻译。CANopenNode 的 CANopen 基本数据类型表 列出:

0x0007 = UNSIGNED32 = 32 位无符号整数

所以 0x1000:00 的值应当按 4 字节无符号整数解释。

依据二:CANopen 的多字节数值使用小端序。

CANopenNode 的基础定义文档 明确说明 CANopen itself is little endian。在它的对应源码注释 中也可以查到同样的说明。

因此,程序现在有了完整依据:数据长度是 4 字节,类型是无符号整数,最低有效字节放在前面。所以组合结果为:

92 01 04 00 -> 0x00040192

于是日志最终打印:

Device Type 0x1000:00 = 0x00040192

串口中的英文 Device Type 也不是电机发回来的。SDO 客户端程序中会先写好要读取的对象信息,形式类似:

// 0x1000:00 决定向电机读取哪个对象,SDO_VALUE_U32 决定按 4 字节无符号数解码。
// "Device Type" 只用于本地日志,电机响应中不会传回这段英文名称。
{0x1000, 0x00, SDO_VALUE_U32, "Device Type"}

这行代码的意思是:请求 Index 0x1000、Sub-index 0x00,按无符号 32 位数解码,打印时显示名称 Device Type。地址、类型和名称都是程序员查资料后写入程序的;电机的响应只补上这次的实际数据字节。

这也说明了为什么不能随便猜对象类型:如果程序表里一开始就写错,日志仍然可能打印出一个看似完整、实际解释错误的结果。

现在可以分清哪些内容是谁提供的:

0x1000 的名称和类型 来自 CANopen 资料
92 01 04 00 来自电机这次实际响应
0x00040192 程序按资料规定的类型和字节顺序组合出的结果

现在再理解什么是对象字典

一台 CANopen 设备会提供很多可访问的数据。对每一项数据,都需要说明:

Index 和 Sub-index 访问哪一项数据
Data type 占几个字节,是否允许表示负数
Access type 允许读取、写入,还是两者都允许
Value 当前设备保存或计算出来的实际数据

这些条目组织在一起,就叫 Object Dictionary,中文叫对象字典。可以先把它理解成设备对外提供的参数接口表:

对象地址名称数据类型定义从哪里查
0x1000:00Device TypeUNSIGNED32CiA 301 通信对象定义
0x1001:00Error RegisterUNSIGNED8CiA 301 通信对象定义
0x1018:02Product CodeUNSIGNED32CiA 301 Identity Object 定义
0x6064:00Position Actual ValueINTEGER32CiA 402 驱动器对象定义

表中的地址、名称和类型是查资料得到的,不是电机在 CAN 报文中发来的文字。电机只有在收到具体读取请求后,才会返回该地址的当前值,或者返回“对象不存在”等错误。

对一个只写过普通 C 程序的读者,可以这样理解它的用处。假设电机程序内部有一个保存当前位置的数值:

// 普通 C 变量只在本程序内可见;放入对象字典后才可通过 CANopen 地址访问。
int32_t position_actual;

这里只是为了讲解而写的假设,不是说 IG35EC020 内部一定有这个 C 变量。ESP32 不能知道另一块芯片里的变量名,更不能直接读取它的内存。CiA 402 因此规定了一个对外通信地址 0x6064:00。只要某台电机宣称并实现了这个对象,控制器就可以用这个数字地址请求当前位置,而不需要知道电机内部代码怎样写。

对象字典不是电机上电后自动发出的一张完整表,也不是互联网数据库。它是设备实现的一组 CANopen 参数接口。ESP32 使用 SDO 等 CANopen 通信方式访问某个具体地址,电机再返回该地址对应的数据或错误响应。

对象字典也不一定对应芯片中一块连续内存。有些条目对应普通变量,有些值由设备实时计算,还有些条目在读写时会执行专门处理。对通信双方来说,重要的是地址、类型、权限和行为符合资料定义。

一个完整对象地址由两部分组成

对象地址通常写成:

0x1018:02

冒号左边是 Index,中文叫主索引;冒号右边是 Sub-index,中文叫子索引。

0x1018 : 02
Index Sub-index

Index 是 16 位数字,常写成四位十六进制;Sub-index 是 8 位数字,常写成两位十六进制。

看下面这张图时,先看上半部分怎样拆地址,再看橙色一行怎样从 0x1018 中选出第 02 项:

CANopen 对象地址由 Index 和 Sub-index 组成

所以 0x1018:02 不是一个带小数点的数,也不是 CAN-ID。它表示:

先找到 Index 0x1018:Identity Object
再找到 Sub-index 0x02:Product Code

完整书写时建议保留四位 Index 和两位 Sub-index:

0x1000:00
0x1018:02
0x6041:00

这样一眼就能看出冒号两边各自是什么。厂家资料有时写成 1018h, sub 02h,其中后缀 h 同样表示十六进制;它与 0x1018:02 表达的是同一组地址。

为什么还需要 Sub-index

有些对象只保存一个值。例如:

0x1001:00 Error Register

这时通常使用 Sub-index 00 访问它。

另一些对象需要把一组相关数据放在同一个 Index 下。0x1018 Identity Object 就是很好的例子:

完整地址内容类型
0x1018:00支持的最高子索引UNSIGNED8
0x1018:01Vendor IDUNSIGNED32
0x1018:02Product CodeUNSIGNED32
0x1018:03Revision NumberUNSIGNED32
0x1018:04Serial NumberUNSIGNED32

只说“读取 0x1018”还不完整,因为设备不知道你要的是厂家编号、产品编号还是序列号。必须同时给出 Sub-index。

0x1018:00 的值通常是这个记录支持的最高子索引。这里若读到 04,说明可以继续查看 0104。不要把这句话扩展成“所有对象的 Sub-index 00 永远都是数量”;不同对象的定义仍要查对应规范或设备资料。

在正式术语中:

  • 只有单个值的对象常称为 VAR
  • 多个相同类型条目组成的对象常称为 ARRAY
  • 多个不同含义或不同类型条目组成的对象常称为 RECORD

0x1018RECORD。当前阶段先会根据 Index 和 Sub-index 找到具体条目即可,不需要背下所有对象类型编码。

Index 的范围能告诉我们什么

对象字典不是从 0x0000 开始随意编号。不同范围留给不同用途。

初学阶段先认识三个最常遇到的区域:

Index 范围主要用途本教程例子
0x10000x1FFFCANopen 通信参数0x10000x10170x1018
0x20000x5FFF厂商自定义参数必须查当前厂家的资料
0x60000x9FFF标准设备规范相关参数CiA 402 的 0x60410x6064

第 08 课讲过 CiA 301 和 CiA 402 的分工。现在可以看到这种分工怎样落到对象字典里:

0x1017 Producer Heartbeat Time 属于共同通信参数
0x6041 Statusword 属于驱动与运动控制参数
0x6064 Position Actual Value 属于驱动与运动控制参数

上面三行的名称不是从电机返回字节中识别出来的。0x1017 要查 CiA 301,0x60410x6064 要查 CiA 402。再向具体电机发起读取,才能确认它是否实现了该对象,以及它当前的值。

对象落在某个标准区域,只说明应该去哪里查它的定义,不代表每台设备一定实现这个区域中的全部可选对象。

例如,某个地址在 CiA 402 中有标准定义,当前电机仍可能因为不支持对应功能而没有实现它。收到“对象不存在”的明确响应时,应记录实际结果,不能换一个相似地址继续猜。

0x20000x5FFF 更不能跨厂家照搬。两家设备都使用 0x2001,其含义也可能完全不同。

数据类型决定怎样解释 DATA

找到对象地址,只解决了“读哪一项”。还必须知道它的数据类型,才能决定:

有效数据有几个字节?
它能不能表示负数?
这些字节组合后应该得到什么数?

本教程最常见的六种整数类型如下:

CANopen 名称C 中常用类型字节数数值范围
UNSIGNED8uint8_t10~255
INTEGER8int8_t1-128~127
UNSIGNED16uint16_t20~65535
INTEGER16int16_t2-32768~32767
UNSIGNED32uint32_t40~4294967295
INTEGER32int32_t4-2147483648~2147483647

UNSIGNED 表示无符号,只表示零和正数;INTEGER 在这里表示有符号整数,可以表示负数。

例如,下面三个对象的名称和数据类型分别由 CiA 301 和 CiA 402 定义:

0x1001:00 Error Register UNSIGNED8
0x6041:00 Statusword UNSIGNED16
0x6064:00 Position Actual Value INTEGER32

当前位置可能位于人为定义零点的正方向或负方向,所以 0x6064:00 使用有符号 32 位数。若错误地按 UNSIGNED32 打印,负位置可能变成一个非常大的正数。

相同字节可以有不同结果

假设四个有效数据字节都是 FF,组合后的 32 位比特是:

0xFFFFFFFF

按不同类型解释:

UNSIGNED32 4294967295
INTEGER32 -1

原始比特没有改变,改变的是解释方式。因此不能只看“响应有 4 字节”就决定使用 uint32_t,必须先查对象定义。

小端序解决多字节排列问题

一字节数值没有排列问题。两字节或四字节数值需要约定:先放哪一个字节?

CANopen 的多字节数值使用小端序,英文是 little-endian。可以先记成一句话:

最低有效字节放在前面。

以 32 位数值 0x12345678 为例,把它从右向左每两位分成一个字节:

12 34 56 78
^^
最低字节

放入 CANopen 数据区时,最低字节 78 先进入 DATA[0]

CANopen 多字节数值的小端序排列

最终排列是:

DATA[0] DATA[1] DATA[2] DATA[3]
78 56 34 12

不要把“低字节在前”理解成把每个字节内部的二进制位也倒过来。78 这个字节本身仍然是 0x78;变化的是四个字节在数据区中的先后位置。

接收端怎样组合 16 位数值

假设收到两个有效字节:

37 06

低字节是 0x37,高字节是 0x06。组合时把高字节左移 8 位:

value = 0x37 + (0x06 << 8)
= 0x37 + 0x0600
= 0x0637

对应的 C 函数可以写成:

// 调用前要先确认响应至少有 2 字节;本函数只负责按 CANopen 小端序组合数据。
// data[0] 保持在低 8 位,data[1] 左移到高 8 位,按位或后得到完整 uint16_t。
static uint16_t read_le_u16(const uint8_t *data)
{
return (uint16_t)data[0]
| ((uint16_t)data[1] << 8);
}

data[0] 直接放在最低 8 位;data[1] << 8 把第二个字节移动到第 8~15 位;按位或 | 再把两部分合到一个 uint16_t 中。

强制转换 (uint16_t) 写在移位之前,明确告诉编译器先把字节扩展成 16 位数,再进行左移。

接收端怎样组合 32 位数值

四字节版本按同样规律继续左移 16 位和 24 位:

// 调用前要先确认响应至少有 4 字节,不能拿短响应直接进入这个函数。
// 四个字节分别放入结果的第 0、8、16、24 位,再用按位或合并。
static uint32_t read_le_u32(const uint8_t *data)
{
return (uint32_t)data[0]
| ((uint32_t)data[1] << 8)
| ((uint32_t)data[2] << 16)
| ((uint32_t)data[3] << 24);
}

例如 78 56 34 12

data[0] 0x00000078
data[1] << 8 0x00005600
data[2] << 16 0x00340000
data[3] << 24 0x12000000
按位或后 0x12345678

下一课分析 SDO 时,还会看到 Index 本身也按低字节在前放入数据区。例如 Index 0x6041 会出现为:

41 60

Sub-index 只有一个字节,因此 0x00 直接放入对应位置,不需要再讨论字节先后。

有符号数应该在什么时候转换

接收 INTEGER32 时,可以先按相同的小端规则组合出 32 位比特,再把它解释成 int32_t

// 字节顺序与 UNSIGNED32 完全相同;区别只发生在最后一步的类型解释。
// 转为 int32_t 后,最高位为 1 的结果会按补码解释为负数。
static int32_t read_le_i32(const uint8_t *data)
{
return (int32_t)read_le_u32(data);
}

例如收到:

FF FF FF FF

read_le_u32() 先得到 0xFFFFFFFF,最后转换为 int32_t 后得到 -1

这里仍然要先确认对象类型确实是 INTEGER32。如果对象定义为 UNSIGNED32,同样的比特就应该保留为 4294967295

访问权限决定能不能读写

对象字典条目通常还带有访问权限:

常见标记英文含义
roread only只能读取
wowrite only只能写入
rwread/write可以读取和写入
constconstant固定常量

访问权限不是给文档看的装饰。设备收到请求后会检查它。

例如 0x1018:02 Product Code 是只读身份信息。控制器可以请求读取,但不应该尝试写入。下一课会看到,访问不存在的对象、写只读对象或使用错误数据长度时,设备会通过 SDO Abort 明确拒绝请求。

“可以写”也不表示“现在适合写”。电机控制对象即使标为 rw,仍可能受到 NMT 状态、CiA 402 状态、数值范围和安全条件限制。

用已有硬件记录练习

本课没有增加新的 ESP32 固件,也不需要再次烧录。因为此时还没有学习 SDO 请求格式,提前发送参数访问报文只会变成照抄字节。下面使用 2026 年 8 月 5 日保存的实际通信记录;第 11 课再从空工程写出只读 SDO 客户端,由学员自己重新完成请求和验证。

不要先看程序打印的参数名和最终数值。我们直接看保存下来的原始帧:

读取 0x1018:00
TX ID=0x601 DATA=[40 18 10 00 00 00 00 00]
RX ID=0x581 DATA=[4F 18 10 00 04 00 00 00]

读取 0x6041:00
TX ID=0x601 DATA=[40 41 60 00 00 00 00 00]
RX ID=0x581 DATA=[4B 41 60 00 31 02 00 00]

读取 0x6064:00
TX ID=0x601 DATA=[40 64 60 00 00 00 00 00]
RX ID=0x581 DATA=[43 64 60 00 AA 16 00 00]

完整节选记录保存在 IG35EC020 实际 SDO 读取记录

现在仍然不解释每帧前四个字节,它们属于第 11 课的 SDO 格式。本课只使用已经查到的对象类型,练习解码响应帧后面的有效数据。

0x1018:00 为什么只需要一字节

对象类型是 UNSIGNED8,数值是 0x04

有效数据长度 1 字节
数据字节 04
含义 最高子索引为 04

0x6041:00 怎样得到 0x0231

CiA 402 定义 Statusword 的类型是 UNSIGNED16,所以响应中的有效数据是 2 字节。这次电机实际返回:

31 02

手算:

0x31 | (0x02 << 8) = 0x0231

当前只练习字节组合。0x0231 中每一位对应什么驱动状态,要到 CiA 402 状态机课程再查定义和解释。

0x6064:00 怎样排列成四字节

这次电机的响应帧实际带回四个数据字节:

AA 16 00 00

按小端序组合:

AA | (16 << 8) | (00 << 16) | (00 << 24)
= 0x000016AA
= 5802

CiA 402 定义 0x6064:00 的类型是 INTEGER32。当前组合结果的最高位为 0,所以十进制结果是正数 5802。将来读到最高位为 1 的位置值时,需要按 int32_t 解释负数。

自己完成四道拆解题

先不要看后面的答案,拿纸写出每一步。

题目一:找对象

地址:0x1018:03

写出它的 Index、Sub-index、名称和数据类型。

题目二:组合两字节

一个 UNSIGNED16 对象返回:

34 12

组合后的十六进制数值是多少?

题目三:组合四字节

一个 UNSIGNED32 对象返回:

92 01 04 00

组合后的十六进制数值是多少?

题目四:同一组比特按两种类型解释

数据字节是:

FF FF FF FF

分别按 UNSIGNED32INTEGER32 解释。

对照答案

题目一:

Index 0x1018
Sub-index 0x03
名称 Revision Number
数据类型 UNSIGNED32

题目二:

0x34 | (0x12 << 8) = 0x1234

题目三:

92 01 04 00 -> 0x00040192

这个结果与课程开发阶段从 IG35EC020 读取到的 Device Type 数值一致,但题目只训练小端序,不在本课解释 Device Type 各个位的含义。第 11 课完成 SDO 实验后,学员会从自己的电机获得并核对实际结果。

题目四:

UNSIGNED32 4294967295
INTEGER32 -1

如果这四题能独立完成,下一课看到下面这种 SDO 数据时,就已经能认出 Index 的低高字节和返回值的低高字节:

4B 41 60 00 37 06 00 00

现在不需要猜 4B。它是 SDO 协议自己的命令字,下一课会从请求和响应过程开始解释。

回到开头的 0x1000:00

现在可以把它完整读成:

0x1000 Index,找到 Device Type 对象
00 Sub-index,选择这个对象的第 00 项
UNSIGNED32 数据类型,结果占 4 字节且不表示负数
ro 访问权限,只读取

对象字典解决“访问哪个参数”,数据类型解决“怎样解释参数”,小端序解决“多字节怎样排列”。这三件事不能互相替代:

只有地址,没有类型 不知道数据长度和正负
只有类型,没有地址 不知道正在访问哪项数据
忽略小端序 会把字节组合成错误数值

下一课会把这些知识放进真实 CAN 帧:ESP32 作为 SDO Client,请求电机读取 0x1000:000x1001:000x1018,再检查电机返回的数据或 Abort Code。

资料链接

CAN in Automation:CANopen:

CiA:CANopen

该页面介绍对象字典在 CANopen 通信和应用程序之间的作用,以及通信参数所在的 Index 区域。

CAN in Automation:CiA 306 Electronic Device Description:

CiA 306 系列:电子设备描述

该页面说明 EDS 和 DCF 的用途及区别。

CANopenNode:CANopen 基本数据类型和字节序:

CANopenNode 数据类型

该页面列出 CANopen 数据类型编号,并明确说明 CANopen 使用小端序。

CiA 301 CANopen 海报:

CANopen poster 2024

海报集中列出了对象字典、常用通信对象、数据类型和 SDO 报文格式,可用于核对本课中的标准对象地址。