文章最后附有源码、已标注的资料、移植完成的程序和移植所需的源码部分
https://download.csdn.net/download/qq_27620407/12938986
链接:https://pan.baidu.com/s/1By-HiY4xopeGk7a1yi-p8w
提取码:rkd8
步骤一:在新建好的工程目录下新建文件夹CanFestival,再在CanFestival下新建文件夹driver、inc和src,再在inc文件夹下面新建stm32文件夹
步骤二:
将CanFestival-3-10\src目录下的dcf.c、emcy.c、lifegrd.c、lss.c、nmtMaster.c、nmtSlave.c、objacces.c、pdo.c、sdo.c、states.c、sync.c、timer.c共12个文件拷贝到CanFestival\src目录下;symbols.c不要
将CanFestival-3-10\include目录下的所有.h文件共19个文件全部拷贝到CanFestival\inc目录下,
再把CanFestival-3-10\examples\AVR\Slave目录下的ObjDict.h文件拷贝过来,一共20个.h文件;
将CanFestival-3-10\include\AVR目录下的applicfg.h、canfestival.h、config.h、timerscfg.h共4个头文件拷贝到canfestival\inc\stm32目录下;
最后是对象字典TestSlave.c、TestSlave.h、TestMaster.h、TestMaster.c拷贝到canfestival\driver目录下,并在该目录下新建stm32_canfestival.c文件。这四个文件是对象字典,这个在官方库中不好找,有的例程下是只有.h文件的,移植时先用字典生成工具生成一个空白的就可以,TestMaster是主机的名称,TestSlave是从机的名。
步骤三:
将CanFestival\src目录下的所有.c文件添加到工程;
将canfestival\driver目录下的stm32_canfestival.c文件添加到工程;
如果实现的是从设备,再将canfestival\driver目录下的TestSlave.c文件添加到工程,
如果实现的是主设备,则将TestMaster.c文件添加到工程;
步骤四:将文件目录canfestival\inc、canfestival\inc\stm32、canfestival\driver等路径添加到工程包含路径。
步骤五:在stm32_canfestival.c中包含头文件#include “canfestival.h”,并定义如下函数:
void setTimer(TIMEVAL value)
{
}
TIMEVAL getElapsedTime(void)
{
return 1;
}
unsigned char canSend(CAN_PORT notused, Message *m)
{
return 1;
}
可以先定义一个空函数,等到编译都通过了之后,再往里面添加内容,这几个函数都是定义来供canfestival源码调用的,如果找不到这几个函数编译就会报错。
步骤六:通过以上几步,所有的文件都弄齐了,但是编译一定会出现报错,注释或删除掉config.h文件中的如下几行就能编译通过:
#include
#include
#include
#include
#include
#include
步骤七:解决了所有的编译错误后,接下来实现刚才定义的3个空函数,
函数void setTimer(TIMEVAL value)主要被源码用来定时的,时间到了就需要调用一下函数TimeDispatch(),
函数TIMEVAL getElapsedTime(void)主要被源码用来查询距离下一个定时触发还有多少时间,
unsigned char canSend(CAN_PORT notused, Message *m)函数主要被源码用来发一个CAN包的,需要调用驱动来将一个CAN包发出去。
步骤八:timerscfg.h文件
#define MS_TO_TIMEVAL(ms) ((ms) * 1)
#define US_TO_TIMEVAL(us) ((us)/1)
这个涉及到CANopen的单位时间,必须匹配!!!!1ms定时器中断就用这个参数
我们在stm32_canfestival.c文件里定义几个变量如下:
unsigned int TimeCNT=0;//时间计数
unsigned int NextTime=0;//下一次触发时间计数
unsigned int TIMER_MAX_COUNT=70000;//最大时间计数
static TIMEVAL last_time_set = TIMEVAL_MAX;//上一次的时间计数
setTimer和getElapsedTime函数实现如下:
//Set the next alarm //
void setTimer(TIMEVAL value)
{
NextTime=(TimeCNT+value)%TIMER_MAX_COUNT;
}
// Get the elapsed time since the last occured alarm //
TIMEVAL getElapsedTime(void)
{
int ret=0;
ret = TimeCNT> last_time_set ? TimeCNT - last_time_set : TimeCNT + TIMER_MAX_COUNT - last_time_set;
//last_time_set = TimeCNT;
return ret;
}
另外还要开一个1毫秒的定时器,每1毫秒调用一下下面这个函数。这个函数放在1ms定时器中断服务函数里
void timerForCan(void)
{
TimeCNT++;
if (TimeCNT>=TIMER_MAX_COUNT)
{
TimeCNT=0;
}
if (TimeCNT==NextTime)
{
TimeDispatch();
}
}
void TIM7_IRQHandler(void)
{
if(TIM7->SR&0X0001)//中断
{
}
TIM7->SR&=~(1<<0);//清除中断标志位
last_time_set = TimeCNT;
timerForCan();
}
最后
//从机
#include "TestSlave.h"
unsigned char nodeID=0x21;
extern CO_Data TestSlave_Data;
//在调用函数(比如main函数)里调用以下代码初始化
setNodeId(&TestSlave_Data, nodeID);
setState(&TestSlave_Data, Initialisation); // Init the state
其中TestSlave_Data在TestSlave.c中定义
//主机
#include "TestMaster.h"
unsigned char nodeID=0x21;
extern CO_Data TestMaster_Data;
//在调用函数(比如main函数)里调用以下代码初始化
setNodeId(&TestMaster_Data, nodeID);
setState(&TestMaster_Data, Initialisation); // Init the state
其中TestMaster_Data在TestMaster.c中定义
然后开启调用TimerForCan()的1毫秒定时器,在接收CAN数据那里调用一下源码函数canDispatch(&TestSlave_Data, &m);
canfestival源码就可以跑了,如果需要跟主设备联调,还要实现canSend函数,这个与平台的Can驱动相关。
STM32方面,根据自己的程序需要重写发送接收。
接收中断里,把信息读取出来之后需要调用void canDispatch(CO_Data* d, Message *m)
/*CAN中断服务函数*/
void USB_LP_CAN1_RX0_IRQHandler(void)
{
Message m;
u8 *ide;
CAN_Rx2_Msg(0,&m.cob_id,ide,&m.rtr,&m.len,m.data);//接收数据
canDispatch(&TestMaster_Data, &m);
}
/*接收数据结构体*/
typedef struct {
UNS16 cob_id;
UNS8 rtr;
UNS8 len;
UNS8 data[8];
} Message;
/*接收数据函数*/
//fifox:邮箱号
//id:标准ID(11位)/扩展ID(11位+18位)
//ide:0,标准帧;1,扩展帧
//rtr:0,数据帧;1,远程帧
//len:接收到的数据长度(固定为8个字节,在时间触发模式下,有效数据为6个字节)
//dat:数据缓存区
void CAN_Rx2_Msg(u8 fifox,u16 *id,u8 *ide,u8 *rtr,u8 *len,UNS8 dat[8])
{
*ide=CAN1->sFIFOMailBox[fifox].RIR&0x04; //得到标识符选择位的值
if(*ide==0)//标准标识符
{
*id=CAN1->sFIFOMailBox[fifox].RIR>>21;
}else //扩展标识符
{
*id=CAN1->sFIFOMailBox[fifox].RIR>>3;
}
*rtr=CAN1->sFIFOMailBox[fifox].RIR&0x02; //得到远程发送请求值.
*len=CAN1->sFIFOMailBox[fifox].RDTR&0x0F; //得到DLC
//*fmi=(CAN1->sFIFOMailBox[FIFONumber].RDTR>>8)&0xFF;//得到FMI
//接收数据
dat[0]=CAN1->sFIFOMailBox[fifox].RDLR&0XFF;
dat[1]=(CAN1->sFIFOMailBox[fifox].RDLR>>8)&0XFF;
dat[2]=(CAN1->sFIFOMailBox[fifox].RDLR>>16)&0XFF;
dat[3]=(CAN1->sFIFOMailBox[fifox].RDLR>>24)&0XFF;
dat[4]=CAN1->sFIFOMailBox[fifox].RDHR&0XFF;
dat[5]=(CAN1->sFIFOMailBox[fifox].RDHR>>8)&0XFF;
dat[6]=(CAN1->sFIFOMailBox[fifox].RDHR>>16)&0XFF;
dat[7]=(CAN1->sFIFOMailBox[fifox].RDHR>>24)&0XFF;
if(fifox==0)CAN1->RF0R|=0X20;//释放FIFO0邮箱
else if(fifox==1)CAN1->RF1R|=0X20;//释放FIFO1邮箱
}
发送,由于这里没用使用库函数,而是寄存器方式,所以接收函数无法使用简单的库函数,不过这样移植性更好。
/*发送寄存结构体*/
typedef struct
{
uint32_t StdId;
uint32_t ExtId;
uint8_t IDE;
uint8_t RTR;
uint8_t DLC;
uint8_t Data[8];
} CanTxMsg;
/*发送寄存接口*/
typedef struct {
u16 cob_id; /**< message's ID */
u8 rtr; /**< remote transmission request. (0 if not rtr message, 1 if rtr message) */
u8 len; /**< message's length (0 to 8) */
u8 data[8]; /**< message's datas */
} can_message_t; //CAN消息基本结构
#define CAN_Id_Standard ((uint32_t)0x00000000) /*!< Standard Id */
//can发送一组数据,标准帧格式发送
//返回值:0, 发送成功;
// 其他,发送失败;
u8 Can_Send_Msg(can_message_t *msg)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId = msg->cob_id; // 标准标识符
TxMessage.ExtId = 0; // 设置扩展标示符
TxMessage.IDE = CAN_Id_Standard; // 标准帧
TxMessage.RTR = msg->rtr; // 数据帧
TxMessage.DLC = msg->len; // 要发送的数据长度
for(i=0; i<msg->len; i++)
{
TxMessage.Data[i] = msg->data[i];
}
mbox=CAN_Tx_Msg(TxMessage.StdId,TxMessage.IDE,TxMessage.RTR,TxMessage.DLC,TxMessage.Data); //发送数据
i=0;
while((CAN_Tx_Staus(mbox)!=0X07)&&(i<0XFFF))i++;//等待发送结束
if(i>=0XFFF)return 1;
return 0;
}
/*Canopen发送接口*/
unsigned char canSend(CAN_PORT notused, Message *m)
{
return Can_Send_Msg((can_message_t *) m); //发送CAN消息
}
/*发送函数*/
//id:标准ID(11位)/扩展ID(11位+18位)
//ide:0,标准帧;1,扩展帧
//rtr:0,数据帧;1,远程帧
//len:要发送的数据长度(固定为8个字节,在时间触发模式下,有效数据为6个字节)
//*dat:数据指针.
//返回值:0~3,邮箱编号.0XFF,无有效邮箱.
u8 CAN_Tx_Msg(u32 id,u8 ide,u8 rtr,u8 len,u8 *dat)
{
u8 mbox;
if(CAN1->TSR&(1<<26))mbox=0; //邮箱0为空
else if(CAN1->TSR&(1<<27))mbox=1; //邮箱1为空
else if(CAN1->TSR&(1<<28))mbox=2; //邮箱2为空
else return 0XFF; //无空邮箱,无法发送
CAN1->sTxMailBox[mbox].TIR=0; //清除之前的设置
if(ide==0) //标准帧
{
id&=0x7ff;//取低11位stdid
id<<=21;
}else //扩展帧
{
id&=0X1FFFFFFF;//取低32位extid
id<<=3;
}
CAN1->sTxMailBox[mbox].TIR|=id;
CAN1->sTxMailBox[mbox].TIR|=ide<<2;
CAN1->sTxMailBox[mbox].TIR|=rtr<<1;
len&=0X0F;//得到低四位
CAN1->sTxMailBox[mbox].TDTR&=~(0X0000000F);
CAN1->sTxMailBox[mbox].TDTR|=len; //设置DLC.
//待发送数据存入邮箱.
CAN1->sTxMailBox[mbox].TDHR=(((u32)dat[7]<<24)|
((u32)dat[6]<<16)|
((u32)dat[5]<<8)|
((u32)dat[4]));
CAN1->sTxMailBox[mbox].TDLR=(((u32)dat[3]<<24)|
((u32)dat[2]<<16)|
((u32)dat[1]<<8)|
((u32)dat[0]));
CAN1->sTxMailBox[mbox].TIR|=1<<0; //请求发送邮箱数据
return mbox;
}
1.安装python-2.6.2.msi,直接选择默认安装路径,装完之后设置环境变量,若是默认模式,环境变量路径设为C:\Python26;(注意环境变量前后有分号隔开),若是自定义路径,请将环境变量改为你自定义安装路径,目的是让操作系统能找到python命令
a.在canfestival文件夹中进入 objdictgen目录
b.解压Gnosis_Utils-current.tar.gz
c.打开cmd,cd命令进入Gnosis_Utils-1.2.2目录, 运行python setup.py install.
2.再安装wxPython3.0-win32-3.0.0.0-py26.exe,装完之后,直接双击运行objdictgen目录里的 objdictedit.py文件,就行了。
字节数 | ID | RTR | 功能码 | Index_L | Index_H | Sub index | data | data | data | data |
---|---|---|---|---|---|---|---|---|---|---|
一个字节 | 600 + ServNodeId | 0 | 2F | Index_L | Index_H | Sub index | D0 | - | - | - |
二个字节 | 600 + ServNodeId | 0 | 2B | Index_L | Index_H | Sub index | D0 | D1 | - | - |
三个字节 | 600 + ServNodeId | 0 | 27 | Index_L | Index_H | Sub index | D0 | D1 | D2 | - |
四个字节 | 600 + ServNodeId | 0 | 23 | Index_L | Index_H | Sub index | D0 | D1 | D2 | D3 |
成功时:
ID | RTR | 功能码 | Index_L | Index_H | Sub index | data | data | data | data |
---|---|---|---|---|---|---|---|---|---|
580 + Serv NodeId | 0 | 60 | Index_L | Index_H | Sub index | 00 | 00 | 00 | 00 |
失败时:
ID | RTR | 功能码 | Index_L | Index_H | Sub index | data[0…3] |
---|---|---|---|---|---|---|
580 + Serv NodeId | 0 | 80 | Index_L | Index_H | Sub index | SDO abort code error |
实例:DI:0x621 DLC 8 标准帧 数据帧 DATA:2F 01 14 02 FD 00 00 00
写入ID为0x21的0x1401,子指标0x02,数据为0xFD
成功:DI:0x5A1 DLC 8 标准帧 数据帧 DATA:60 01 14 02 00 00 00 00
DI:0x621 DLC 8 标准帧 数据帧 DATA:23 01 16 02 FD 65 43 21
写入ID为0x21的0x1601,子指标0x02,数据为0x314365FD
ID | RTR | 功能码 | Index_L | Index_H | Sub index | data | data | data | data |
---|---|---|---|---|---|---|---|---|---|
600 + Serv NodeId | 0 | 40 | Index_L | Index_H | Sub index | 00 | 00 | 00 | 00 |
成功:
字节数 | ID | RTR | 功能码 | Index_L | Index_H | Sub index | data | data | data | data |
---|---|---|---|---|---|---|---|---|---|---|
一个字节 | 580 + ServNodeId | 0 | 4F | Index_L | Index_H | Sub index | D0 | - | - | - |
二个字节 | 580 + ServNodeId | 0 | 4B | Index_L | Index_H | Sub index | D0 | D1 | - | - |
三个字节 | 580 + ServNodeId | 0 | 47 | Index_L | Index_H | Sub index | D0 | D1 | D2 | - |
四个字节 | 580 + ServNodeId | 0 | 43 | Index_L | Index_H | Sub index | D0 | D1 | D2 | D3 |
失败
ID | RTR | 功能码 | Index_L | Index_H | Sub index | data[0…3] |
---|---|---|---|---|---|---|
580+ Serv NodeId | 0 | 8 | Index_L | Index_H | Sub index | SDO abort code error |
实例:DI:0x621 DLC 8 标准帧 数据帧 DATA:40 01 14 02 00 00 00 00
读取ID为0x21的0x1401,子指标0x02
成功:DI:0x5A1 DLC 8 标准帧 数据帧 DATA:60 01 14 02 FD 00 00 00
读取到的数值为0xFD
DI:0x621 DLC 8 标准帧 数据帧 DATA:40 01 16 02 00 00 00 00
成功:DI:0x5A1 DLC 8 标准帧 数据帧 DATA:60 01 14 02 FD 65 43 21
读取到的数据为0x314365FD
PDO 的通信参数
PDO 通信参数,定义了该设备所使用的 COB-ID、传输类型、定时周期等。RPDO 通讯
参数位于对象字典索引的 1400 h to 15FF h ,TPDO 通讯参数位于对象字典索引的 1800 h to
19FF h 。每条索引代表一个 PDO 的通信参数集,其中的子索引分别指向具体的各种参数。如
表 7.2 所示。
表 7.2 PDO 的通信参数
Index 索引 | Sub-index 子索引 | Description 描述 | Data type 数据类型 |
---|---|---|---|
RPDO:1400 h to15FF h | 00 h | Number of entries 参数条目数量 | Unsigned8 |
TPDO:1800 h to19FF h | 01 h | COB-ID:发送/接收这个 PDO 的帧 ID | Unsigned32 |
… | 02 h | Transmission type 发送类型 | Unsigned8 |
… | 02 h | 00 h :非循环同步 | Unsigned8 |
… | 02 h | 01 h :循环同步 | Unsigned8 |
… | 02 h | FC h :远程同步 | Unsigned8 |
02 h | FD h :远程异步 | Unsigned8 | |
02 h | FE h :异步,制造商特定事件 | Unsigned8 | |
02 h | FF h :异步,设备子协议特定事件 | Unsigned8 | |
03 h | Inhibit time 生产禁止约束时间(1/10ms) | Unsigned16 | |
05 h | Event timer 事件定时器触发的时间(单位 ms) | Unsigned16 | |
06 h | SYNC start value 同步起始值 | Unsigned8 |
● Number of entries 参数条目数量:即本索引中有几条参数;
● COB-ID:即这个 PDO 发出或者接收的对应 CAN 帧 ID;
● 发送类型:即这个 PDO 发送或者接收的传输形式,通常使用循环同步和异步制造商特定事件较多;
● Inhibit time 生产禁止约束时间(1/10ms):约束 PDO 发送的最小间隔,避免导致总线负载剧烈增加,比如数字量输入过快,导致状态改变发送的 TPDO 频繁发送,总线负载加大,所以需要一个约束时间来进行“滤波”,这个时间单位为 0.1ms;
● Event timer 事件定时器触发的时间(单位 ms):定时发送的 PDO,它的定时时间,如果这个时间为 0,则这个 PDO 为事件改变发送。
● SYNC start value 同步起始值:同步传输的 PDO,收到诺干个同步包后,才进行发送,这个同步起始值就是同步包数量。比如设置为 2,即收到 2 个同步包后才进行发送。
PDO主要就是通过0x1400-0x15FF(写入)和0x1800-0x19FF的六个子指标,实现SYNC和PDO远程,以及定时和值改变完成发送接收。注意Canopen需要处于operational操作状态。
定时发送功能要求:FE h :异步,制造商特定事件 Event timer !=0;
值改变发送要求: 0xFE或0xFF 需要在值改变后加 sendPDOevent(&TestSlave_Data);
SYNC 触发:1 – 0xF0:同步,时间触发模式 ,每 t 一周期
PAO远程触发:FC h :远程同步 或
FD h :远程异步 0xfd 时 表示节点接收到一个远程帧后发送一次 PDO
605 2F 00 18 02 FF 00 00 00 --设置索引Index 1800,事件传输
605 2F 00 18 05 CB 00 00 00 --设置索引Index 1800,时间间隔200ms
605 2F 00 1A 00 00 00 00 00 --设置子索引禁用
605 23 00 1A 01 10 00 30 40 --0x40300010,设置映射索引0x4030,子索引00,大小0x10(16位)
605 23 00 1A 02 20 00 10 20 --0x20100020,设置映射索引0x2010,子索引00,大小0x20(32位)
605 2F 00 1A 00 02 00 00 00 --设置映射数量,用多少设多少,这里用了01–02
注意:定义映射时,先设置子索引禁用;再设置相应映射;然后设置映射数量
实例:
ID=0x700+ID,rtr=1(远程帧) 请求节点状态
DI=0x00,rtr=0,DLC=2,Data[0]=0x01(Start),Data[1]=指定设备地址,全部则为0. 设置节点start
DI=0x00,rtr=0,DLC=2,Data[0]=0x02(Stop),Data[1]=指定设备地址,全部则为0. 设置节点Stop
DI=0x180+ID,RTR=0 PDOTX1遥控帧
DI=0x280+ID,RTR=0 PDOTX2遥控帧
https://download.csdn.net/download/qq_27620407/12938986
链接:https://pan.baidu.com/s/1By-HiY4xopeGk7a1yi-p8w
提取码:rkd8