- 测试can是否正常运行:
1.1.打开开发板CAN总线
通过野火自带得fire-config工具打开can总线设备(这里要注意,野火得485引脚和can引脚冲突,在打开can设备之前,先关闭485设备)
sudo fire-config
![](https://i-blog.csdnimg.cn/blog_migrate/6dbc6adf7e7ec26e9c6441f298b48c62.png)
然后重启开发板
sudo reboot #重启开发板
ifconfig -a #查看can设备是否在线
1.2.下载can调试工具测试
sudo apt-get -y install can-utils #下载can调试工具
如出现报错 E: Unable to locate package can-utils,更新一下下载源就可以
sudo apt-get update
sudo ip link set can0 type can bitrate 1000000 #设置can0,can0对应得你ifconfig -a的设备名,1000000为波特率
sudo ip link set can0 up #启动can0
sudo ip link set can1 type can bitrate 1000000
sudo ip link set can1 up #同时打开can1
candump can0 #打开一个终端,使用can0接收数据
cansend can1 123#abcdabcd #代开另外一个终端,使用can1发送数据
can0接收如下:
![](https://i-blog.csdnimg.cn/blog_migrate/7f75ad23f174b2fa672f4bd31a67e0ab.png)
- 下载并编译canfestival
2.1 源码下载
为了方便开发,下面代码的编译和移植放在ubuntu上进行,最后进行交叉编译即可(ubuntu18.04)
sudo apt install git #先安装git工具
sudo git clone https://github.com/ljessendk/CanFestival.git #下载源码
cd CanFestival #进入源码目录
./configure --help #先查看一下configure的参数说明
![](https://i-blog.csdnimg.cn/blog_migrate/293ffd5e9715cc0484d2b0c70cb57bb8.png)
2.2编译源码
sudo ./configure --prefix=$PWD/myinstall --can=socket --debug=WAR,MSG
#这里编译源码
#--prefix 为动态库的生成位置,后面应用程序运行时,要添加此路径
#--debug 这里我把所有的debug信息都打开了,方便后续调试
![](https://i-blog.csdnimg.cn/blog_migrate/4cf92a908328ad5ab173f549dd00f373.png)
sudo make all
sudo make install
![](https://i-blog.csdnimg.cn/blog_migrate/6850ab1641084a3b9e7d9d59f537c5ef.png)
我这里有报错信息,暂时不管他,这里进到编译目录下
sudo modprobe vcan #加载虚拟can口模块
sudo ip link add dev vcan0 type vcan #配置虚拟can口
sudo ip link set up vcan0 #打开虚拟can口
ifconfig -a #查看虚拟can口是否运行
cd myintall/bin
./CANOpenShell load#../lib/libcanfestival_can_socket.so,vcan0,500k,1,1 #启动脚本测试
candump -tz vcan0 #在另外一个终端查看vcan0发送的消息
![](https://i-blog.csdnimg.cn/blog_migrate/230249f19364ee48d6623fa3ba9ebf1b.png)
上图所示vcan0发送了节点启动的报文,则证明canfestival编译成功
- canfestival使用之SDO
3.1 例程解析
我使用的例程是CANFESTIVAL/example/TestMasterSlave,该例程已经帮你配置了主站和从站
![](https://i-blog.csdnimg.cn/blog_migrate/8c2869460c6878b09d13437676078817.png)
上图是该例程的使用教程,-l为动态库的目录(在之前configure指定的目录下面的lib目录里面)
再配置主从的can口和波特率即可实现主从之间的通信。
if(strcmp(SlaveBoard.baudrate, "none")){
TestSlave_Data.heartbeatError = TestSlave_heartbeatError;
TestSlave_Data.initialisation = TestSlave_initialisation;
TestSlave_Data.preOperational = TestSlave_preOperational;
TestSlave_Data.operational = TestSlave_operational;
TestSlave_Data.stopped = TestSlave_stopped;
TestSlave_Data.post_sync = TestSlave_post_sync;
TestSlave_Data.post_TPDO = TestSlave_post_TPDO;
TestSlave_Data.storeODSubIndex = TestSlave_storeODSubIndex;
TestSlave_Data.post_emcy = TestSlave_post_emcy;
if(!canOpen(&SlaveBoard,&TestSlave_Data)){
eprintf("Cannot open Slave Board (%s,%s)\n",SlaveBoard.busname, SlaveBoard.baudrate);
goto fail_slave;
}
}
上面是main函数里面的一段代码,主要是将回调函数注册到TestSlave_Data结构体中,然后使用canOpen函数进行对从站的初始化。
StartTimerLoop(&InitNodes);这个函数是对节点的初始化配置,设置nodeid,并启动定时器。
3.2 读写SDO
3.2.1 写SDO
![](https://i-blog.csdnimg.cn/blog_migrate/3d15f0cd8a1c288e65dfc15b7ed203c7.png)
可以参考源码Master.c里面主站对从站的写SDO,这两个地址就是对从站的TPDO进行参数配置的,
也就是对索引0x1800,子索引的0x02写入Transmission_Type这个值,然后注册了一个回调函数CheckSDOAndContinue:
static void CheckSDOAndContinue(CO_Data* d, UNS8 nodeId)
{
UNS32 abortCode;
/*这里是判断sdo写完成没有,主站对从站写sdo之后,从站回返回一个写成功命令,这里建议用一个while循环去get返回结果,不然的话返回的稍微慢一点就会报错*/
if(getWriteResultNetworkDict (d, nodeId, &abortCode) != SDO_FINISHED)
eprintf("Master : Failed in initializing slave %2.2x, step %d, AbortCode :%4.4x \n", nodeId, init_step, abortCode);
/* Finalise last SDO transfer with this node */
closeSDOtransfer(&TestMaster_Data, nodeId, SDO_CLIENT);
ConfigureSlaveNode(d, nodeId);
}
3.2.2读SDO
![](https://i-blog.csdnimg.cn/blog_migrate/0a2f939da341b9c594c53b2c7fb639bf.png)
根据读sdo可以看到最终调用sdo.h下面_writeNetworkDict,那么写sdo就是下面这个函数:
u8 ReadDict(u8 nodeId, u16 index, u8 subindex)
{
UNS32 abortCode = 0;
UNS8 buf[4];
UNS32 size=4;
readNetworkDict(&masterObjdict_Data,nodeId,index,subindex,0,0);//读SDO
//while(readNetworkDict(&masterObjdict_Data,nodeId,index,subindex,0,0)==0xff);
if(getReadResultNetworkDict(&masterObjdict_Data,nodeId,buf,&size, &abortCode)!=SDO_FINISHED){//获取读到结果,读到的数据会传到参数“buf”里面,这里也一样,建议用while循环去获取结果,不然第一次获取不到就跳出了
closeSDOtransfer(&masterObjdict_Data,nodeId,SDO_CLIENT);
}
delay_ms(25);
closeSDOtransfer(&masterObjdict_Data,nodeId,SDO_CLIENT);
return 0;
}