CANopen STM32移植

文章最后附有源码、已标注的资料、移植完成的程序和移植所需的源码部分

https://download.csdn.net/download/qq_27620407/12938986

链接:https://pan.baidu.com/s/1By-HiY4xopeGk7a1yi-p8w
提取码:rkd8

1、移植

步骤一:在新建好的工程目录下新建文件夹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源码调用的,如果找不到这几个函数编译就会报错。

文件目录:
CANopen STM32移植_第1张图片

步骤六:通过以上几步,所有的文件都弄齐了,但是编译一定会出现报错,注释或删除掉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驱动相关。

2、STM32定时器和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;
}

3 python及对象字典生成工具canfestival安装步骤:

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文件,就行了。

SDO 写入

SDO 写入(主机发送)

字节数 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

SDO 写入(主机接收)

成功时:

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

SDO 读取

SDO 读取(主机发送)

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

SDO 读取(主机接收)

成功:

字节数 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 的通信参数

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

你可能感兴趣的:(stm32,canopen,stm32,can,pdo,sdo)