本文主要讲解对象字典配置与使用关系
一、移植前准备
按照网上CanFestival的移植将底层移植好即可。
二、对象字典生成工具使用
按其它教程安装好objdictedit, 打开objdictedit.py ,新建工程。
主要看几个地方
0x1000-0x1029主要是通信参数,配置同步、心跳、节点守护等。
0x1200-0x12FF配置的是主机/从机的SDO参数,我们使用的是主机,因此要配置从机(客户端)的SDO,如下图所示,我配置了7个客户端,每一个配置有TSDO的COB ID、RSDO的COB ID和节点号,例如SDO1号配置TSDO的COB ID为0X601,RSDO的COB ID为0X581,节点号为1,那么SDO2号配置TSDO的COB ID为0X602,RSDO的COB ID为0X582,节点号为2,因此在使用SDO进行通信的时候,CAN主机发送的ID为0x60x,从机返回的ID为0x58x,x代表了几号从设备。
0x1400-0x15FF为RPDO的参数配置,下图为我配置好的参数,RPDO1号的COB ID为0x181,同步类型为非同步,inhibit time抑制时间为0,Compatibity Entry兼容性条目为0,Event Timer 事件时间为0,SYNC start value 同步开始值为0,以上为0均为非使能,具体参数意义大家自己查阅,因为我也一知半懂。。。。。
其余的RPDO按如此配置,COB ID为0x18x即可。
0x1600-0X17FF是RPDO的参数映射,我当前控制的是电机设备,遵循CIA402的协议。
当前映射了状态字、实际速度、错误码三个值,状态字占用16位,实际速度占用32位,错误码占用16位,总共64位数据,根据CAN2.0的协议,数据位不超过64位即8个字节,因此我们最多配置8个字节在这个映射上,如果要配置多个参数,暂时还未找到解决方法。
配置的值例如status_word的地址是0x2004,这个0x2004是自己添加的一个参数,比如状态字的原始词典(CIA402)是0x6041,在代码中对0X6041通过SDO写入到从机的TXPDO上,后续有说明,这里只是定义了主机接收到0X181这个ID的数据代表的含义,这边配置的是8个字节,前两个字节代表状态字,中间四个字节代表了实际速度,最后两个字节代表的是错误码。
其它的RPDO也如此配置。
0X1800-0X19FF配置TPDO的参数,和0X1400差不多,COB ID配置0x201,同步类型配置为1,循环同步,这块建议配置为循环同步,循环同步的话会自动循环发送该帧,非同步则会在数值发生改变的时候发生该帧一次,若掉包则会接收不到。
其余TPDO也如此配置,0x20x。
0X1A00-0X1BFF为TPDO的参数映射,和0X1600类似,这边配置映射值都是控制相关的,例如电机模式,控制字,速度值等。
0X2000-0X5FFF代表用户自定义参数
根据0X1600和0X1A00我们配置的值都是从这里配置后选择的,理论上这个软件配置在配置完0x1000-0x1029之后就先配置0x2000的参数,这样后续才能选择映射的参数。
如下图,我配置了7个电机的参数,其中参数有ctrl_word、celocity1、motor_mode、velocity_act1、status_word、err_word,这些都是第一个电机的参数,所有其它电机都以此为例创建了各自参数。
参数如何建立,点击左下方的添加(地图变量),弹窗后指标代表的是映射地址,从0x2000开始,0x5FFF结束,类型默认VAR,名字按照自己需要填写,例如ctrl_word,确定之后就创立了一个ctrl_word的映射变量,点击该变量,选择类型,UNSIGNED16代表是无符号16位,INTEGER16代表的是有符号16位,根据自己的参数配置好就行。值可改变,改变后生成的代码会初始化该值,其它参数以此类推。
至此,对象词典就配置完成,点击左上角文件、建立词典,生成的C文件和H文件就可以直接拿来用。
三、代码的调用
1.生成的代码部分展示
通过对象词典创建的0x2000-0x5FFF变量,自动生成出来的值,如果改变了变量初始值,则会在定义的时候自动生成,例如motor_mode = 0x3,是自动生成的。
在0x1280的配置中,我将UNS8 master_obj1280_Node_ID_of_the_SDO_Server = 0x1;改为了const格式,因为在使用中,master_obj1280_Node_ID_of_the_SDO_Server会变成0(未知原因),因此我将它配置为const类型。
2.CANOPEN初始化
2.1CANOPEN的发送的问题
对于CANOPEN底层配置就不多赘述,关于CAN发送这块,由于同步发送的原因,导致了CAN在发送数据时较快,部分同步数据发送失败(可能是软件底层的原因),因此我在CAN发送函数后添加了一段延时,保证每次数据都发送完毕,实测通过,未造成阻塞。
unsigned char canSend(CAN_PORT notused, Message *m)
{
uint8_t i;
static CanTxMsg TxMsg;
TxMsg.StdId = m->cob_id;
if(m->rtr)
TxMsg.RTR = CAN_RTR_REMOTE;
else
TxMsg.RTR = CAN_RTR_DATA;
TxMsg.IDE = CAN_ID_STD;
TxMsg.DLC = m->len;
for(i=0; i<m->len; i++)
TxMsg.Data[i] = m->data[i];
CAN_Transmit(CAN1, &TxMsg) ;
DrvTimer_DelayUs(60); // 延迟等待发送完毕,不然会掉包,延迟时间与PDO数量有关,延迟不要太长
return 0;
}
2.2 初始化配置
unsigned char nodeID = 0x00; //节点ID
int i = 0;
// 初始自身化节点
setNodeId(&master_Data, nodeID);
setState(&master_Data, Initialisation);
for(i = 1; i < NMT_MAX_NODE_ID;i++)
master_Data.NMTable[i] = Unknown_state;
setState(&master_Data, Operational);
while(master_Data.nodeState != Operational)
{
vTaskDelay(50);
}
vTaskDelay(1000);
stopSYNC(&master_Data);
for( i = 1; i < 5; i++)
{
if( master_Data.NMTable[i] != Unknown_state)
slave_num++;
}
CANOPEN_DEBUG("slave count %d\r\n",slave_num);
for(i = 1; i < 5;i++)
{
// PDO映射初始化
DevCANOpen_SDO_Init(i);
}
vTaskDelay(1000);
startSYNC(&master_Data);
for(i = 1; i < 5;i++)
{
masterSendNMTstateChange(&master_Data, i, NMT_Reset_Node );
masterSendNMTstateChange(&master_Data, i, NMT_Start_Node );
}
这里初始化了1-4号电机,ID从1开始而不是从0开始。
主要的配置为SDO_Init的初始化,以下代码是SDO的初始化,这些也是参考的网上进行修改的,第一个SDO写值为恢复默认参数。
第二到第五则是用SDO配置电机内部参数,0x600+id是SDO写ID,0x23为数据长度是4,0x83 0x60为0x6083的寄存器(CIA402),0X00代表0x6083的内部索引是0,写的值0x0f4240=1000000。
写映射参数,从第五个开始,{0x600+id,0x23,0x00,0x16,0x01,0x08,0x00,0x60,0x60},0x600+id代表COB ID,0x23代表数据长度为4,0x00,0x16为0x1600,配置从机的RPDO,0x01代表第一个索引,0x08,0x00,代表0x0008,表示8位,0x0010代表16位,0x60,0x60代表0x6060寄存器,表示控制模式。
依次到第七行写入了三个参数,控制模式、控制字、控制速度,第八行{0x600+id,0x2F,0x00,0x16,0x00,0x03,0x00,0x00,0x00},
0x600+id代表COB ID,0x2F代表数据长度为1,0x00,0x16为0x1600,配置从机的RPDO,0x00表示索引0,0x03表示写入3个映射参数,即第五行、第六行、第七行三个参数。
后续面有注释,RPDO以此类推。
void DevCANOpen_SDO_Init(INT8U id)
{
uint16_t Init_sdo[][9]= // 主站(单片机),恢复从站的初始值
{
{0x600+id,0x23,0x11,0x10,0x01,0x6C,0x6F,0x61,0x64}, // 【写SDO 1011.01,64616F6C】恢复默认值的写入暗号
{0x600+id,0x23,0x81,0x60,0x00,0x20,0xA1,0x07,0x00}, // 写协议速度 6081 500000 0x07a120
{0x600+id,0x23,0x83,0x60,0x00,0x40,0x42,0x0f,0x00}, // 写最大加速度 6083 1000000 0x0f4240
{0x600+id,0x23,0x84,0x60,0x00,0x40,0x42,0x0f,0x00}, // 写最大减速度 6084 1000000 0x0f4240
{0x600+id,0x2B,0x17,0x10,0x00,0xf4,0x01,0x00,0x00}, // 写最大减速度 6084 1000000 0x0f4240
};
for (int i=0;i<5;i++)
{
DevCANOpen_send_sdo(Init_sdo[i]);
vTaskDelay(20);
}
uint16_t message_sdo[][9]=
{
// 写参数映射
{0x600+id,0x23,0x00,0x14,0x01,id,0x02,0x00,0x80}, // 失能pdo: 发送sdo 600+id,22 00 14 01 01 02 00 80
{0x600+id,0x2F,0x00,0x14,0x02,0x01,0x00,0x00,0x00}, //②Hex1400_02,循环同步,同步方式参数2
{0x600+id,0x2B,0x00,0x14,0x03,0x20,0x00,0x00,0x00}, //③Hex1400_03,Inhibit time是0;
{0x600+id,0x2F,0x00,0x16,0x00,0x00,0x00,0x00,0x00}, // 消去个数: 发送sdo 600+id,22 00 16 00 00 00 00 00
{0x600+id,0x23,0x00,0x16,0x01,0x08,0x00,0x60,0x60}, // 写入参数: 发送sdo 600+id,22 00 16 01 20 00 ff 60
{0x600+id,0x23,0x00,0x16,0x02,0x10,0x00,0x40,0x60},
{0x600+id,0x23,0x00,0x16,0x03,0x20,0x00,0xFF,0x60},
{0x600+id,0x2F,0x00,0x16,0x00,0x03,0x00,0x00,0x00}, //⑥Hex1600_0,启用【映射参数】 【实际映射多少组】
{0x600+id,0x23,0x00,0x14,0x01,id,0x02,0x00,0x00}, // 使能pdo: 发送sdo 600+id,22 00 14 01 01 02 00 00
// 读参数映射
{0x600+id,0x23,0x00,0x18,0x01,0x80+id,0x01,0x00,0x80}, // 失能Tpdo: 发送sdo 600+id,22 00 14 01 01 02 00 80
{0x600+id,0x2F,0x00,0x18,0x02,0x02,0x00,0x00,0x00}, //②Hex1800_02,循环同步,同步方式参数2
{0x600+id,0x2B,0x00,0x18,0x03,0x00,0x00,0x00,0x00}, //③Hex1800_03,Inhibit time是0;
{0x600+id,0x2F,0x00,0x1A,0x00,0x00,0x00,0x00,0x00}, // 消去个数: 发送sdo 600+id,22 00 16 00 00 00 00 00
{0x600+id,0x23,0x00,0x1A,0x01,0x10,0x00,0x41,0x60}, // 写入参数: 发送sdo 600+id,22 00 16 01 20 00 41 60 // 状态字
{0x600+id,0x23,0x00,0x1A,0x02,0x20,0x00,0x6C,0x60}, // 写入参数: 发送sdo 600+id,22 00 16 01 20 00 6C 60 //速度
{0x600+id,0x23,0x00,0x1A,0x03,0x10,0x00,0x3F,0x60}, // 写入参数: 发送sdo 600+id,22 00 16 01 20 00 6C 60 //速度
{0x600+id,0x2F,0x00,0x1A,0x00,0x03,0x00,0x00,0x00}, //⑥Hex1A00_0,启用【映射参数】 【实际映射多少组】
{0x600+id,0x23,0x00,0x18,0x01,0x80+id,0x01,0x00,0x00}, // 使能pdo: 发送sdo 600+id,22 00 14 01 01 02 00 00
// 蕾赛电机写看门狗时间,厂商自定义词典5004.06
{0x600+id,0x2B,0x04,0x50,0x06,0xF4,0x01,0x00,0x00}, // 蕾赛电机写看门狗时间,【写SDO 5004.06,03e8】写看门狗1000ms
};
for (int i=0;i<19;i++)
{
DevCANOpen_send_sdo(message_sdo[i]);
vTaskDelay(10);
}
}
至此所有的参数都配置完毕,主函数调用ctrl_word这个变量即可对电机的状态进行控制,其它的变量也可以进行控制。