canopen(CanFestival) SDO Read/Write

先看使用方式再对源码进行分析

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;
}
  1. 初始化与参数解析(interface, nodeId, index, subidx, type)。
  2. 打开 CAN(canOpen(&board, d)),安装 SIGINT 处理以允许中断(g_run)。
  3. 启动定时器:TimerInit(),设置本地 master node:
  • setNodeId(d, 100)
  • setState(d, Pre_operational)
  • setState(d, Operational)
  1. 配置 SDO 客户端(Object 0x1280 子项 1/2/3):
  • TX COB-ID = 0x600 + node
  • RX COB-ID = 0x580 + node
  • Server NodeID = node
    (写入失败时仅打印警告)
  1. 确保目标节点启动:masterSendNMTstateChange(d, node, NMT_Start_Node)
  2. 发起 SDO 读取:readNetworkDict(d, node, index, sub, dtype, 0);若非 0 则视为启动失败并退出。
  3. 轮询获取结果:
  • 根据 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 秒)作为超时保护
  1. 退出前关闭 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

相关推荐
xqlily2 天前
CanOpen控制系统概述
canopen
rosemary5123 个月前
CANopen - DCF(Device Configuration File) 介绍
canopen·dcf
rosemary5123 个月前
CANopen EDS(Electronic Data Sheet) 介绍
eds·canopen
sayang_shao4 个月前
STM32H7+FreeRTOS+LwIP移植EtherCAT开源主站SOEM
stm32·soem·ethercat·canopen
worthsen4 个月前
CanOpen--SDO 数据帧分析
canopen
NYKJ.Co5 个月前
开疆智能CCLinkIE转Canopen网关连接UV紫外灯配置案例
canopen·cclinkie
NYKJ.Co5 个月前
开疆智能CCLinkIE转CANopen网关连接GBS20机器人配置案例
机器人·canopen·cclinkie
工控小楠6 个月前
CANopen转EtherCAT协议转换案例解析
ethercat·can协议·canopen
hlpinghcg6 个月前
CanFestival移植到STM32G4
stm32·canopen