先看使用方式再对源码进行分析
Usage
SDO Read
Usage:
-
sdo_read [type]
-
type: u8|u16|u32|i8|i16|i32|domain (default u32)
Examples:
-
./build/sdo_read can0 1 1000 00 u32 # 读取 0x1000:00 设备类型
-
./build/sdo_read can0 1 1018 01 u32 # 读取 VendorId
-
./build/sdo_read can0 1 1017 00 u16 # 读取心跳时间
Notes:
- 工具会动态配置主站的 SDO Client (0x1280) 指向目标节点 (0x600+ID / 0x580+ID),并向目标发送 NMT Start。
- 对于 domain 类型,会显示长度与前若干字节的十六进制内容。
SDO Write
Usage:
-
sdo_write
-
type: u8|u16|u32|i8|i16|i32
-
value: 十进制或 0x 前缀十六进制
Examples:
-
./build/sdo_write can0 1 1017 00 u16 1000 # 写入心跳时间 1000ms
-
./build/sdo_write can0 1 2000 01 u8 0x12 # 写入 0x2000:01 = 0x12
-
./build/sdo_write can0 1 3000 02 u32 0x1F4 # 写入 0x1F4
Notes:
-
某些对象需要通过 0x1010 存储或重置通信才能生效,参考设备手册。
SDO Read source code
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include "canfestival.h"
#include "masterdic.h" // reuse generated OD for types and CO_Data
#include "objdictdef.h" // data type codes (uint8/uint16/...)
#include "objacces.h" // writeLocalDict
static volatile int g_run = 1;
static void on_sigint(int sig){ (void)sig; g_run = 0; }
static void usage(const char* prog){
fprintf(stderr, "Usage: %s <can-if> <nodeId> <index-hex> <subidx-hex> [type]\n", prog);
fprintf(stderr, " type: u8|u16|u32|i8|i16|i32|domain (default u32)\n");
}
static UNS8 parse_type(const char* s){
if(!s) return uint32;
if(!strcmp(s, "u8")) return uint8;
if(!strcmp(s, "u16")) return uint16;
if(!strcmp(s, "u32")) return uint32;
if(!strcmp(s, "i8")) return int8;
if(!strcmp(s, "i16")) return int16;
if(!strcmp(s, "i32")) return int32;
if(!strcmp(s, "domain")) return domain;
return uint32;
}
static void post_SlaveStateChange(CO_Data* d, UNS8 nodeId, e_nodeState newState){
(void)d;
const char* name = "Unknown";
switch(newState){
case Initialisation: name = "Initialisation"; break;
case Disconnected: name = "Disconnected"; break;
case Connecting: name = "Connecting/Preparing"; break; /* Preparing has same value */
case Stopped: name = "Stopped"; break;
case Operational: name = "Operational"; break;
case Pre_operational: name = "Pre_operational"; break;
case Unknown_state: name = "Unknown_state"; break;
default: break;
}
printf("Node %u -> %s\n", nodeId, name);
}
int main(int argc, char** argv){
if(argc < 5){ usage(argv[0]); return 1; }
const char* ifname = argv[1];
int nodeId = (int)strtol(argv[2], NULL, 0);
UNS16 index = (UNS16)strtol(argv[3], NULL, 16);
UNS8 sub = (UNS8)strtol(argv[4], NULL, 16);
UNS8 dtype = parse_type(argc > 5 ? argv[5] : NULL);
s_BOARD board = {0};
board.busname = (char*)ifname;
board.baudrate = ""; // use pre-configured bitrate on interface
// Prepare minimal CO_Data using masterdic_Data from example
extern CO_Data masterdic_Data;
CO_Data* d = &masterdic_Data;
// Install minimal callbacks
d->post_SlaveStateChange = post_SlaveStateChange;
if(!canOpen(&board, d)){
fprintf(stderr, "Cannot open CAN interface %s\n", ifname);
return 2;
}
signal(SIGINT, on_sigint);
// Start timers (for timeouts) and set node state
TimerInit();
// Put master into Pre-operational and then Operational
setNodeId(d, 100);
setState(d, Pre_operational);
setState(d, Operational);
printf("Reading SDO 0x%04X:%02X from node %d on %s (type=%d)\n", index, sub, nodeId, ifname, (int)dtype);
// Configure SDO client (0x1280) to address the requested nodeId
{
UNS8 nodeU8 = (UNS8)nodeId;
UNS32 cob_tx = 0x600 + (UNS32)nodeU8; // client->server
UNS32 cob_rx = 0x580 + (UNS32)nodeU8; // server->client
UNS32 sz32 = sizeof(UNS32);
UNS32 sz8 = sizeof(UNS8);
if (writeLocalDict(d, 0x1280, 1, &cob_tx, &sz32, RW) != OD_SUCCESSFUL)
fprintf(stderr, "Warn: failed to set 0x1280:1 (TX COB-ID)\n");
if (writeLocalDict(d, 0x1280, 2, &cob_rx, &sz32, RW) != OD_SUCCESSFUL)
fprintf(stderr, "Warn: failed to set 0x1280:2 (RX COB-ID)\n");
if (writeLocalDict(d, 0x1280, 3, &nodeU8, &sz8, RW) != OD_SUCCESSFUL)
fprintf(stderr, "Warn: failed to set 0x1280:3 (Server NodeID)\n");
}
// Ensure target node is started
masterSendNMTstateChange(d, (UNS8)nodeId, NMT_Start_Node);
// Initiate SDO read
UNS8 ret = readNetworkDict(d, (UNS8)nodeId, index, sub, dtype, 0);
if(ret){
fprintf(stderr, "readNetworkDict failed: 0x%02X\n", ret);
canClose(d);
return 3;
}
// Poll for result
int loops = 0;
union { UNS8 u8; UNS16 u16; UNS32 u32; INTEGER8 i8; INTEGER16 i16; INTEGER32 i32; } val;
memset(&val, 0, sizeof(val));
unsigned char domainBuf[512];
while(g_run){
UNS32 size;
switch(dtype){
case uint8:
case int8: size = 1; break;
case uint16:
case int16: size = 2; break;
case uint32:
case int32: size = 4; break;
case domain: size = sizeof(domainBuf); break;
default: size = sizeof(val); break;
}
void* dataPtr = (dtype == domain) ? (void*)domainBuf : (void*)&val;
UNS32 abortCode = 0;
UNS8 st = getReadResultNetworkDict(d, (UNS8)nodeId, dataPtr, &size, &abortCode);
if(st == SDO_FINISHED){
switch(dtype){
case uint8: printf("Value: 0x%02X (u8=%u)\n", val.u8, (unsigned)val.u8); break;
case uint16: printf("Value: 0x%04X (u16=%u)\n", (unsigned)val.u16, (unsigned)val.u16); break;
case uint32: printf("Value: 0x%08X (u32=%u)\n", (unsigned)val.u32, (unsigned)val.u32); break;
case int8: printf("Value: 0x%02X (i8=%d)\n", (unsigned)(UNS8)val.i8, (int)val.i8); break;
case int16: printf("Value: 0x%04X (i16=%d)\n", (unsigned)(UNS16)val.i16, (int)val.i16); break;
case int32: printf("Value: 0x%08X (i32=%d)\n", (unsigned)val.i32, (int)val.i32); break;
case domain: {
printf("Value: <domain> size=%u\n", (unsigned)size);
unsigned toShow = size < 32 ? size : 32;
printf(" data:");
for(unsigned i=0;i<toShow;i++) printf(" %02X", domainBuf[i]);
if(size>toShow) printf(" ...");
printf("\n");
break;
}
default: printf("Value: <unknown type>\n"); break;
}
break;
} else if(st == SDO_ABORTED_RCV || st == SDO_ABORTED_INTERNAL){
fprintf(stderr, "SDO aborted (st=0x%02X): 0x%08X\n", st, (unsigned)abortCode);
canClose(d);
return 4;
} else if (st == SDO_PROVIDED_BUFFER_TOO_SMALL) {
fprintf(stderr, "Provided buffer too small (need %u+)\n", (unsigned)size);
canClose(d);
return 6;
}
usleep(1000 * 10);
if(++loops > 1000){
fprintf(stderr, "Timeout waiting SDO response\n");
canClose(d);
return 5;
}
}
canClose(d);
return 0;
}
- 初始化与参数解析(interface, nodeId, index, subidx, type)。
- 打开 CAN(canOpen(&board, d)),安装 SIGINT 处理以允许中断(g_run)。
- 启动定时器:TimerInit(),设置本地 master node:
- setNodeId(d, 100)
- setState(d, Pre_operational)
- setState(d, Operational)
- 配置 SDO 客户端(Object 0x1280 子项 1/2/3):
- TX COB-ID = 0x600 + node
- RX COB-ID = 0x580 + node
- Server NodeID = node
(写入失败时仅打印警告)
- 确保目标节点启动:masterSendNMTstateChange(d, node, NMT_Start_Node)
- 发起 SDO 读取:readNetworkDict(d, node, index, sub, dtype, 0);若非 0 则视为启动失败并退出。
- 轮询获取结果:
- 根据 dtype 决定缓冲区大小(整型使用 union val,domain 使用 domainBuf[512])
- 调用 getReadResultNetworkDict(d, node, dataPtr, &size, &abortCode)
若返回 SDO_FINISHED:按类型打印值(十六进制 + 十进制 / domain 显示前 32 字节)
若返回 SDO_ABORTED_RCV 或 SDO_ABORTED_INTERNAL:打印中止代码并退出
若返回 SDO_PROVIDED_BUFFER_TOO_SMALL:打印需要的大小并退出(当前实现) - 每次循环 sleep 10 ms,最多循环 1000 次(约 10 秒)作为超时保护
- 退出前关闭 CAN:canClose(d)
SDO Write
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include "canfestival.h"
#include "masterdic.h"
#include "objdictdef.h"
#include "objacces.h"
static volatile int g_run = 1;
static void on_sigint(int sig){ (void)sig; g_run = 0; }
static void usage(const char* prog){
fprintf(stderr, "Usage: %s <can-if> <nodeId> <index-hex> <subidx-hex> <type> <value>\n", prog);
fprintf(stderr, " type: u8|u16|u32|i8|i16|i32\n");
fprintf(stderr, " value: decimal or 0x-prefixed hex\n");
}
static UNS8 parse_type(const char* s){
if(!s) return 0;
if(!strcmp(s, "u8")) return uint8;
if(!strcmp(s, "u16")) return uint16;
if(!strcmp(s, "u32")) return uint32;
if(!strcmp(s, "i8")) return int8;
if(!strcmp(s, "i16")) return int16;
if(!strcmp(s, "i32")) return int32;
return 0;
}
static unsigned size_of_type(UNS8 t){
switch(t){
case uint8: case int8: return 1;
case uint16: case int16: return 2;
case uint32: case int32: return 4;
default: return 0;
}
}
static void post_SlaveStateChange(CO_Data* d, UNS8 nodeId, e_nodeState newState){
(void)d;
const char* name = "Unknown";
switch(newState){
case Initialisation: name = "Initialisation"; break;
case Disconnected: name = "Disconnected"; break;
case Connecting: name = "Connecting/Preparing"; break;
case Stopped: name = "Stopped"; break;
case Operational: name = "Operational"; break;
case Pre_operational: name = "Pre_operational"; break;
case Unknown_state: name = "Unknown_state"; break;
default: break;
}
printf("Node %u -> %s\n", nodeId, name);
}
int main(int argc, char** argv){
if(argc < 7){ usage(argv[0]); return 1; }
const char* ifname = argv[1];
int nodeId = (int)strtol(argv[2], NULL, 0);
UNS16 index = (UNS16)strtol(argv[3], NULL, 16);
UNS8 sub = (UNS8)strtol(argv[4], NULL, 16);
UNS8 dtype = parse_type(argv[5]);
const char* valStr = argv[6];
if(dtype == 0){ fprintf(stderr, "Unknown type: %s\n", argv[5]); usage(argv[0]); return 1; }
// Parse value (hex or dec)
long long sval = 0;
if(strncmp(valStr, "0x", 2) == 0 || strncmp(valStr, "0X", 2) == 0)
sval = strtoll(valStr, NULL, 16);
else
sval = strtoll(valStr, NULL, 0);
s_BOARD board = {0};
board.busname = (char*)ifname;
board.baudrate = "";
extern CO_Data masterdic_Data;
CO_Data* d = &masterdic_Data;
d->post_SlaveStateChange = post_SlaveStateChange;
if(!canOpen(&board, d)){
fprintf(stderr, "Cannot open CAN interface %s\n", ifname);
return 2;
}
signal(SIGINT, on_sigint);
TimerInit();
setNodeId(d, 100);
setState(d, Pre_operational);
setState(d, Operational);
// Configure SDO client to this node
{
UNS8 nodeU8 = (UNS8)nodeId;
UNS32 cob_tx = 0x600 + (UNS32)nodeU8;
UNS32 cob_rx = 0x580 + (UNS32)nodeU8;
UNS32 sz32 = sizeof(UNS32);
UNS32 sz8 = sizeof(UNS8);
if (writeLocalDict(d, 0x1280, 1, &cob_tx, &sz32, RW) != OD_SUCCESSFUL)
fprintf(stderr, "Warn: failed to set 0x1280:1 (TX COB-ID)\n");
if (writeLocalDict(d, 0x1280, 2, &cob_rx, &sz32, RW) != OD_SUCCESSFUL)
fprintf(stderr, "Warn: failed to set 0x1280:2 (RX COB-ID)\n");
if (writeLocalDict(d, 0x1280, 3, &nodeU8, &sz8, RW) != OD_SUCCESSFUL)
fprintf(stderr, "Warn: failed to set 0x1280:3 (Server NodeID)\n");
}
masterSendNMTstateChange(d, (UNS8)nodeId, NMT_Start_Node);
unsigned sz = size_of_type(dtype);
if(sz == 0){ fprintf(stderr, "Unsupported type for write.\n"); canClose(d); return 3; }
// Prepare value container matching type
UNS8 v_u8; UNS16 v_u16; UNS32 v_u32;
INTEGER8 v_i8; INTEGER16 v_i16; INTEGER32 v_i32;
void* pData = NULL;
switch(dtype){
case uint8: v_u8 = (UNS8)(sval & 0xFF); pData = &v_u8; break;
case uint16: v_u16 = (UNS16)(sval & 0xFFFF); pData = &v_u16; break;
case uint32: v_u32 = (UNS32)(sval & 0xFFFFFFFFu); pData = &v_u32; break;
case int8: v_i8 = (INTEGER8)(sval & 0xFF); pData = &v_i8; break;
case int16: v_i16 = (INTEGER16)(sval & 0xFFFF); pData = &v_i16; break;
case int32: v_i32 = (INTEGER32)(sval & 0xFFFFFFFFu); pData = &v_i32; break;
default: break;
}
printf("Writing SDO 0x%04X:%02X to node %d on %s (type=%s)\n", index, sub, nodeId, ifname, argv[5]);
// Initiate SDO write
UNS8 ret = writeNetworkDict(d, (UNS8)nodeId, index, sub, sz, dtype, pData, 0);
if(ret){
fprintf(stderr, "writeNetworkDict failed: 0x%02X\n", ret);
canClose(d);
return 4;
}
// Wait for completion
int loops = 0;
while(g_run){
UNS32 abortCode = 0;
UNS8 st = getWriteResultNetworkDict(d, (UNS8)nodeId, &abortCode);
if(st == SDO_FINISHED){
switch(dtype){
case uint8: printf("Wrote: 0x%02X (u8=%u)\n", v_u8, (unsigned)v_u8); break;
case uint16: printf("Wrote: 0x%04X (u16=%u)\n", v_u16, (unsigned)v_u16); break;
case uint32: printf("Wrote: 0x%08X (u32=%u)\n", v_u32, (unsigned)v_u32); break;
case int8: printf("Wrote: 0x%02X (i8=%d)\n", (unsigned)(UNS8)v_i8, (int)v_i8); break;
case int16: printf("Wrote: 0x%04X (i16=%d)\n", (unsigned)(UNS16)v_i16, (int)v_i16); break;
case int32: printf("Wrote: 0x%08X (i32=%d)\n", (unsigned)v_i32, (int)v_i32); break;
default: break;
}
break;
} else if(st == SDO_ABORTED_RCV || st == SDO_ABORTED_INTERNAL){
fprintf(stderr, "SDO write aborted (st=0x%02X): 0x%08X\n", st, (unsigned)abortCode);
canClose(d);
return 5;
}
usleep(1000 * 10);
if(++loops > 1000){
fprintf(stderr, "Timeout waiting SDO write response\n");
canClose(d);
return 6;
}
}
canClose(d);
return 0;
}
只阐述重要部分
- 使用 size_of_type(dtype) 计算字节宽度;仅支持整型(1/2/4 字节)。
- 为每种类型准备局部变量(v_u8、v_u16、v_u32、v_i8、v_i16、v_i32),pData 指向对应变量的地址并传给 writeNetworkDict。
Reference
CanFestival Tools (sdo_read / sdo_write) -->可以帮忙点点Star