外观
CANopen 控制电机
2025-10-29
本文将介绍如何基于 睿擎工业开发平台 实现 CANopen 通信,以开发板作为 CANopen 主机,与从机(支持 CANopen 协议的电机)通信,演示控制电机启停,需要读者具有一定 CANopen 协议相关基础知识。
这里使用的 CANopen 从机是 TLC42C-24V-04 两相 42 闭环一体机,站号设置为 2 ,波特率设置为 1Mbps ,站号表配置如下:
- SW1 : OFF
- SW2 : ON
- SW3 : OFF
- SW4 : OFF
- SW5 : OFF
- SW6 : ON
控制电机启停示例
本示例将演示如何通过 CANopen 协议与电机通信,控制电机启停。
硬件连接
电机电源接口

开发板 CAN 接口与电机 CAN 接口连接

创建工程点击展开
依次点击 “文件” -> “新建” -> "RT-Thread RuiChing App 项目"。

在弹出新建向导中选择 开发版 、BSP: 、示例 、 调试器/下载器。选择好之后点击 “完成”。

点击 “完成” 后,等待工程创建完成。

创建完成。

构建工程点击展开
单击工程使工程进入 Active-Debug 模式。

点击工具栏上的构建按钮进行工程编译。

构建成功后,会显示构建成功的信息。

固件下载点击展开
固化设备树

固化 APP

核心示例代码
canopen 协议栈初始化
applications/master402_canopen.c
void canopen_task(void *pram)
{
CAN_PORT port;
OD_Data->heartbeatError = master402_heartbeatError;
OD_Data->initialisation = master402_initialisation;
OD_Data->preOperational = master402_preOperational;
OD_Data->operational = master402_operational;
OD_Data->stopped = master402_stopped;
OD_Data->post_sync = master402_post_sync;
OD_Data->post_TPDO = master402_post_TPDO;
OD_Data->storeODSubIndex = (storeODSubIndex_t)master402_storeODSubIndex;
OD_Data->post_emcy = (post_emcy_t)master402_post_emcy;
PDODisable(OD_Data,1);
PDODisable(OD_Data,3);
port = canOpen(&agv_board, OD_Data);
if (port != RT_NULL)
{
OD_Data->canHandle = port;
}
else
{
return;
}
initTimer();
StartTimerLoop(&InitNodes);
//挂钩相关指针
slave_conf.list = &can_node[1];
can_node[1].nmt_state = &OD_Data->NMTable[2];
can_node[0].nmt_state = &OD_Data->nodeState;
}can 硬件初始化以及创建 can 接收线程。
applications/canopen_rtthread.c
CAN_PORT canOpen(s_BOARD *board, CO_Data *d)
{
rt_thread_t tid;
rt_err_t ret;
if ((board == RT_NULL) || (d == RT_NULL))
{
return RT_NULL;
}
candev = can_init(board->busname, can_baud_to_rtt(board));
if (candev == RT_NULL)
{
rt_kprintf("can_init failed\n");
return RT_NULL;
}
ret = rt_sem_init(&can_data.sem, "co-rx", 0, RT_IPC_FLAG_PRIO);
if (ret != RT_EOK)
{
rt_kprintf("canfestival init semaphore failed, err = %d\n", ret);
return RT_NULL;
}
canfstvl_mutex = rt_mutex_create("canfstvl", RT_IPC_FLAG_PRIO);
if (canfstvl_mutex == RT_NULL)
{
return RT_NULL;
}
OD_Data = d;
tid = rt_thread_create("cf_recv", canopen_recv_thread_entry, &can_data,
10240, CANFESTIVAL_RECV_THREAD_PRIO, 20);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
else
{
rt_mutex_delete(canfstvl_mutex);
canfstvl_mutex = RT_NULL;
}
return candev;
}预操作状态下用 SDO 指令配置 PDO 通信参数和映射表。
master402_canopen.c
static UNS8 (*NODECFG_Operation_2[])(uint8_t nodeId) =
{
//TPDO1通道操作
NODE2_DIS_SLAVE_TPDO1,
NODE2_Write_SLAVE_TPDO1_Type,
NODE2_Clear_SLAVE_TPDO1_Cnt,
NODE2_Write_SLAVE_TPDO1_Sub1,
NODE2_Write_SLAVE_TPDO1_Sub2,
NODE2_Write_SLAVE_TPDO1_Sub0,
NODE2_EN_SLAVE_TPDO1,
//TPDO2通道操作
NODE2_DIS_SLAVE_TPDO2,
NODE2_Write_SLAVE_TPDO2_Type,
NODE2_Clear_SLAVE_TPDO2_Cnt,
NODE2_Write_SLAVE_TPDO2_Sub1,
NODE2_Write_SLAVE_TPDO2_Sub0,
NODE2_EN_SLAVE_TPDO2,
//RPDO1通道操作
NODE2_DIS_SLAVE_RPDO1,
NODE2_Write_SLAVE_RPDO1_Type,
NODE2_Clear_SLAVE_RPDO1_Cnt,
NODE2_Write_SLAVE_RPDO1_Sub1,
NODE2_Write_SLAVE_RPDO1_Sub2,
NODE2_Write_SLAVE_RPDO1_Sub0,
NODE2_EN_SLAVE_RPDO1,
//RPDO2通道操作
NODE2_DIS_SLAVE_RPDO2,
NODE2_Write_SLAVE_RPDO2_Type,
NODE2_Clear_SLAVE_RPDO2_Cnt,
NODE2_Write_SLAVE_RPDO2_Sub1,
NODE2_Write_SLAVE_RPDO2_Sub2,
NODE2_Write_SLAVE_RPDO2_Sub0,
NODE2_EN_SLAVE_RPDO2,
//写入心跳
NODE2_Write_SLAVE_P_heartbeat,
//结束配置
NODE2_MotorCFG_Done,
};配置主机判断从机超时的时间,并将发送 NMT 状态切换请求将电机从预操作状态切换到操作状态。
master402_canopen.c
void config_node(uint8_t nodeId)
{
if(Write_SLAVE_control_word(nodeId,0x80) == 0xFF)//初始化进行错误重置
{
rt_kprintf("nodeId:%d,Failed to clear error.The current node is not in operation\n",nodeId);
rt_sem_init(&(slave_conf.finish_sem), "servocnf1", 0, RT_IPC_FLAG_FIFO);
if(rt_sem_take(&(slave_conf.finish_sem), SDO_REPLY_TIMEOUT) != RT_EOK)
{
slave_conf.err_code = NODEID_CONFIG_NO_RESPOND;
//掉线情况执行,重新上电情况无需处理
rt_kprintf("Waiting for the repair to complete, CAN communication is currently unavailable\n");
master402_fix_config_err(OD_Data,nodeId);
}
rt_sem_detach(&(slave_conf.finish_sem));
}
else
{
slave_conf.state = 0;
slave_conf.try_cnt = 0;
rt_sem_init(&(slave_conf.finish_sem), "servocnf1", 0, RT_IPC_FLAG_FIFO);
EnterMutex();
config_node_param(nodeId, &slave_conf);
LeaveMutex();
rt_sem_take(&(slave_conf.finish_sem), RT_WAITING_FOREVER);
rt_sem_detach(&(slave_conf.finish_sem));
if(slave_conf.err_code != 0X00)//因配置错误导致的退出
{
rt_kprintf("Failed to configure the dictionary for node %d\n",nodeId);
if(slave_conf.err_code == NODEID_CONFIG_NO_SEND)
{
rt_kprintf("The configuration was not sent because the local dictionary failed\n");
}
else if(slave_conf.err_code == NODEID_CONFIG_NO_RESPOND)
{
rt_kprintf("The configuration reply did not respond, and the node dictionary failed\n");
}
rt_kprintf("Waiting for the repair to complete, CAN communication is currently unavailable\n");
master402_fix_config_err(OD_Data,nodeId);
return; //退出线程
}
else
{
UNS32 errorCode,map_val, size = SDO_MAX_LENGTH_TRANSFER;
UNS8 data_type;
errorCode = readLocalDict(OD_Data, 0x1016, nodeId - 1, &map_val, &size, &data_type, 0);
if(errorCode == OD_SUCCESSFUL)
{
/**写入主机消费者/接收端判断心跳超时时间 DS301定义**/
/**有格式定义,字典工具没有支持,需要自己写入**/
UNS32 consumer_heartbeat_time = HEARTBEAT_FORMAT(nodeId,CONSUMER_HEARTBEAT_TIME);//写入节点的心跳时间
errorCode = writeLocalDict(OD_Data, 0x1016, nodeId - 1, &consumer_heartbeat_time, &size, 0);
if(errorCode != OD_SUCCESSFUL)
rt_kprintf("index:0X%04X,subIndex:0X%X,write Local Dict false,abort code is 0X%08X\n",0x1016,nodeId - 1,errorCode);
}
else
{
rt_kprintf("index:0X%04X,subIndex:0X%X,write Local Dict false,abort code is 0X%08X\n",0x1016,nodeId - 1,errorCode);
rt_kprintf("Node %d is not configured with a consumer heartbeat\n",nodeId);
}
//节点进入操作状态
masterSendNMTstateChange(OD_Data, nodeId, NMT_Start_Node);
rt_kprintf("Node %d configuration Complete\n",nodeId);
rt_thread_mdelay(200);//确保NMT命令下发成功
}
}
}运行程序
使用 IDE 调试并运行程序后,在终端输入以下命令:
初始化 canopen 协议栈
msh 命令行运行
canopen_start启动电机
msh 命令行运行
motor_start停止电机
msh 命令行运行
motor_stop
