当前位置:首页 > 工业控制 > 工业控制
[导读]CANopen主站不是“会发CAN帧”就行,核心是四件事:NMT把从站拉进Operational、SDO配对象字典和PDO映射、PDO跑实时过程数据、心跳做节点保活。伺服和IO模块差异只在对象字典索引,主站框架是同一套。


CANopen主站不是“会发CAN帧”就行,核心是四件事:NMT把从站拉进Operational、SDO配对象字典和PDO映射、PDO跑实时过程数据、心跳做节点保活。伺服和IO模块差异只在对象字典索引,主站框架是同一套。

一、COB-ID预定义连接集

主站按Node-ID拼ID即可,不用动态分配:

NMT命令:0x000

SDO发:0x600+ID,回:0x580+ID

RPDO1:0x200+ID(主站→从站)

TPDO1:0x180+ID(从站→主站)

心跳:0x700+ID

二、NMT状态机:先预操作再运行

从站上电自动进Pre-operational,此时SDO可用、PDO沉默;主站配完参数再发Start_Node切Operational。

void nmt_send(uint8_t cs, uint8_t node){

   uint8_t d[2] = {cs, node};

   can_tx(0x000, d, 2);

}


/* 启动单个伺服 */

nmt_send(0x01, 3);     // Start_Remote_Node

/* 广播启动全部 */

nmt_send(0x01, 0x00);

/* 复位通信(重配前常用) */

nmt_send(0x82, 3);

状态值:0x04 Stopped、0x05 Operational、0x7F Pre-op。

三、SDO配置:快速SDO读写参数

SDO是请求-响应模型,配PDO映射、设伺服模式、读厂商参数都走它。≤4字节用快速SDO。

写控制模式(伺服6040/6060类):

/* 主站→从站 0x600+ID,命令字0x23表示快速写 */

void sdo_write(uint8_t node, uint16_t idx, uint8_t sub,

              uint32_t val, uint8_t len){

   uint8_t d[8] = {0x23, idx&0xFF, idx>>8, sub,

                   val&0xFF,(val>>8)&0xFF,(val>>16)&0xFF,(val>>24)&0xFF};

   can_tx(0x600+node, d, 8);

}

/* 读响应在0x580+ID,Data[0]=0x43/0x4B/0x4F按长度 */

配置RPDO映射前必须先禁PDO:把0x1400:01的COB-ID最高位置1,再清0x1600:00映射数,写映射项,最后恢复。

sdo_write(node, 0x1400, 1, 0x80000200+node, 4); // 禁RPDO1

sdo_write(node, 0x1600, 0, 0, 1);              // 映射项数=0

sdo_write(node, 0x1600, 1, 0x60400010, 4);     // 控制字6040h:00 16bit

sdo_write(node, 0x1600, 2, 0x607A0010, 4);     // 目标位置607Ah 16bit

sdo_write(node, 0x1600, 0, 2, 1);              // 映射项数=2

sdo_write(node, 0x1400, 1, 0x00000200+node, 4); // 重新使能

映射值格式:(index<<16)|(sub<<8)|bits,长度是bit不是byte,16位写0x10。

四、PDO实时控制:不握手

进Operational后,伺服控制字/目标位置走RPDO,主站周期发;状态字/实际位置从TPDO收。

void servo_send_rpdo1(uint8_t node, uint16_t ctrl, int32_t tar_pos){

   uint8_t d[8];

   d[0]=ctrl&0xFF; d[1]=ctrl>>8;

   d[2]=tar_pos&0xFF; d[3]=(tar_pos>>8)&0xFF;

   d[4]=(tar_pos>>16)&0xFF; d[5]=(tar_pos>>24)&0xFF;

   can_tx(0x200+node, d, 6);   // 6字节:控制字+目标位置

}


/* 主循环1ms/2ms定时发 */

void pdo_task(void){

   for(int i=0;i<n_servo;i++)

       servo_send_rpdo1(sv[i].node, sv[i].ctrl, sv[i].tar_pos);

}

收TPDO直接按偏移解,不查索引:

void on_tpdo(uint8_t node, uint8_t*d, int len){

   uint16_t status = d[0]|(d[1]<<8);

   int32_t act_pos = d[2]|(d[3]<<8)|(d[4]<<16)|(d[5]<<24);

   sv_update(node, status, act_pos);

}

IO模块更简单:RPDO1映射输出线圈,TPDO1映射输入状态,8数字量拼一字节。

五、心跳监控从站存活

从站周期发0x700+ID,主站记录最后时间戳,超时判离线:

#define HB_TO_MS 1500

static uint32_t hb_last[128];


void on_heartbeat(uint8_t node, uint8_t state){

   hb_last[node] = sys_ms();

   if(state!=0x05) node_fault(node);

}

void hb_check(void){

   for(int n=1;n<128;n++){

       if(dev_exist[n] && sys_ms()-hb_last[n] > HB_TO_MS)

           node_offline(n);   // 停该轴RPDO输出,报告警

   }

}

六、主站调度结构

上电→CAN初始化

 →NMT广播Reset_Comm

 →等Boot-up(0x700+ID)

 →SDO逐个配PDO映射

 →NMT Start_Node

 →周期任务发RPDO

 →收TPDO/SDO响应/心跳

 →异常时SDO读错误码0x603F

SDO配置阶段别发RPDO;Operational后别轮询SDO读实时数据,带宽会被吃光。

七、落坑清单

PDO映射长度写byte不写bit→帧错位

单PDO超8字节→映射拒绝

不禁用PDO直接改映射→写失败

Operational前发RPDO→从站不处理

控制字/状态字方向反(RPDO进、TPDO出)→伺服不动

SDO实时轮询位置→总线拥堵

心跳超时不断RPDO→离线轴还发指令

Node-ID冲突→两站抢同一COB-ID

伺服6040使能顺序错→驱动器报错

TPDO解析按索引查→多此一举还慢

八、落地顺序

CAN驱动打通,NMT 0x000能发能收

等从站Boot-up,SDO读0x1000确认设备类型

SDO禁PDO→配RPDO/TPDO映射→使能

NMT Start切Operational

周期发RPDO(伺服控制字+目标值,IO输出)

解析TPDO更新状态/位置/输入

心跳超时离线保护

故障用SDO读0x603F/0x1003,不占周期带宽

CANopen主站对接伺服和IO,代码骨架是通用的:NMT管状态、SDO只管配置、PDO管实时、心跳管存活。难点不在发帧,在“预操作阶段把映射配对”——控制字进RPDO、状态字出TPDO、长度按bit、单帧不超8字节、改映射先禁用。配完进Operational后主站就只干两件事:周期发RPDO、收TPDO解包。SDO留作上电配置和故障诊断,别拿来跑控制环,总线立马清爽。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭