CAN笔记(23) CanFestival移植
1. CanFestival
Canfestival官方主页:https://canfestival.org/index.html.en
从上面主页可以看得出来,网址主要包含三部分内容:
- Code源码
提供代码资源库 - Documentation文档
主要讲述Canfestival这套框架里面的一些内容
如遵循许可、CanFestival提供了一些GUI和命令行工具
帮助创建一个新的CANOpen节点并编辑对象字典,供主从使用 - Applications应用
主要讲Canfestival应用场合和产品
如Product产品、Research研究方面
Canfestival是一套免费开源的CANOpen协议栈框架,遵循ANSI-C,支持多平台
可以借助这一套开源框架来学习和使用CANOpen
2. 源码下载
点击进入Code
选择 ngélibre.fr,点击链接 branch ,进入页面
点击Downloads,在点击Download repository下载 Mongo-canfestival-3-xxx.zip
3. 移植准备
- CanFestival源码
- 基于STM32标准HAL库的CAN通讯基础工程CANTest2
在 CAN笔记(15) STM32-M4 CAN通讯 的例程CANTest1的基础上
- 添加FreeRTOS
- 开启CAN接受中断
- 删除屏幕显示
- 添加串口交互
拷贝CANTest2另存为CANTest3作为stm32F4移植CanFestival的工程
4. 移植
4.1 CanFestival移植
在新建好的工程CANTest3
目录下新建文件夹CanFestival
再在CanFestival下新建文件夹can
、driver
、inc
和src
再在inc
文件夹下面新建stm32
文件夹
解压刚刚下载的Mongo-canfestival-3-xxx.zip源码压缩文件,重命名为 Mongo-canfestival-3
将Mongo-canfestival-3\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个文件拷贝到CANTest3\CanFestival\src
目录下
将Mongo-canfestival-3\include
目录下的所有.h文件共19个文件全部拷贝到CANTest3\CanFestival\inc
目录下
再把Mongo-canfestival-3\examples\AVR\Slave
目录下的ObjDict.h
文件拷贝过来,一共20个
将Mongo-canfestival-3\include\AVR
目录下的applicfg.h、canfestival.h、config.h、timerscfg.h共4个头文件拷贝到CANTest3\CanFestival\inc\stm32
目录下
将Mongo-canfestival-3\examples\kerneltest
目录下的测试例程TestMaster.h、TestMaster.c、TestMaster.od、TestSlave.c、TestSlave.h、TestSlave.od拷贝到CANTest3\CanFestival\can
目录下
在CANTest3\CanFestival\driver
下新建canfestival_drv.c.c
文件
将CANTest3\CanFestival\src
目录下的所有.c文件添加到工程
将CANTest3\CanFestival\driver
目录下的canfestival_drv.c.c
文件添加到工程
将文件目录CANTest3\CanFestival\inc
、CANTest3\CanFestival\inc\stm32
、CANTest3\CanFestival\can
等路径添加到工程包含路径
在canfestival_drv.c
中包含头文件canfestival.h,并定义如下函数:
#include "canfestival.h"
void setTimer(TIMEVAL value)
{
}
TIMEVAL getElapsedTime(void)
{
return 1;
}
unsigned char canSend(CAN_PORT notused, Message* m)
{
return 1;
}
可以先定义一个空函数,等到编译都通过了之后,再往里面添加内容,这几个函数都是定义来供canfestival源码调用的
通过以上几步,所有的文件都弄齐了,还需要注释或删除掉CANTest3\CanFestival\inc\stm32\config.h
文件中的如下几行代码:
(在Mongo-canfestival-3-asc-1a25f5151a8d.zip中为33-38行)
#else // GCC
//#include <inttypes.h>
//#include <avr/io.h>
//#include <avr/interrupt.h>
//#include <avr/pgmspace.h>
//#include <avr/sleep.h>
//#include <avr/wdt.h>
#endif // GCC
由于移植的文件中带有can.h文件,与之前定义的can底层驱动头文件can.h冲突
所以,把CANTest3\HARDWARE\CAN
下的驱动改成bsp_can.c
和bsp_can.h
随之的内容也要修改(这个就不多说了)
并且源码是采用标准库的而不是HAL库,所以在bsp_can.h
中添加一下CAN通讯消息结构定义
/**
* @brief CAN Tx message structure definition
*/
typedef struct
{
uint32_t StdId;
uint32_t ExtId;
uint8_t IDE;
uint8_t RTR;
uint8_t DLC;
uint8_t Data[8];
} CanTxMsg;
/**
* @brief CAN Rx message structure definition
*/
typedef struct
{
uint32_t StdId;
uint32_t ExtId;
uint8_t IDE;
uint8_t RTR;
uint8_t DLC;
uint8_t Data[8];
uint8_t FMI;
} CanRxMsg;
然后在CANTest3\CanFestival\src\dcf.c
文件中,修改俩个函数
- inline void start_node(CO_Data* d, UNS8 nodeId)
- inline void start_and_seek_node(CO_Data* d, UNS8 nodeId)
函数位于59和98行代码删除 inline 关键字
void start_node(CO_Data* d, UNS8 nodeId)
void start_and_seek_node(CO_Data* d, UNS8 nodeId)
最后还要添加定时器中断驱动文件bsp_timer.c
便于启动通用定时器5
这里就不再详细叙述了
添加C99 Mode
添加文件,整理工程如下:
此时,编译就能通过
不过存在1个warning,由于时间关系,以后再找找原因吧
争取 0 error(s),0 warning(s)
接下来实现刚才定义的3个空函数
4.2 定时调度函数
在canfestival_drv.c
中函数void setTimer(TIMEVAL value)
主要被源码用来设置定时,时间到了就需要调用一下函数TimeDispatch()
#include "sys.h"
#include <stdbool.h>
#include "canfestival.h"
#include "bsp_can.h"
unsigned int TimeCNT = 0; //时间计数
unsigned int NextTime = 0; //下一次触发时间计数
unsigned int TIMER_MAX_COUNT = 60000; //最大时间计数60s
static TIMEVAL last_time_set = TIMEVAL_MAX; //上一次的时间计数
/*******************************************************************************
* @Brief 定时调度函数
* @Param value:定时时间数
* @Return None
* @Date 2019-09-19
* @Version V1.0
* @Note 本程序只供学习使用,未经作者许可,不得用于其它任何用途
被源码用来设置定时,时间到了就需要调用一下函数TimeDispatch()
* @Author Jove
*******************************************************************************/
void setTimer(TIMEVAL value)
{
NextTime = (TimeCNT + value) % TIMER_MAX_COUNT;
}
4.3 查询调度函数
在canfestival_drv.c
中函数TIMEVAL getElapsedTime(void)
主要被源码用来查询距离下一个定时触发还有多少时间
/*******************************************************************************
* @Brief 查询调度函数
* @Param None
* @Return ret:剩余时间
* @Date 2019-09-19
* @Version V1.0
* @Note 本程序只供学习使用,未经作者许可,不得用于其它任何用途
查询距离下一个定时触发还有多少时间
* @Author Jove
*******************************************************************************/
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;
}
4.4 CAN发送函数
函数unsigned char canSend(CAN_PORT notused, Message *m)
主要被源码用来发一个CAN包的
需要调用驱动来将一个CAN包发出去
/*******************************************************************************
* @Brief CAN发送函数
* @Param notused:未使用参数
*m: 消息参数
* @Return None
* @Date 2019-09-19
* @Version V1.0
* @Note 本程序只供学习使用,未经作者许可,不得用于其它任何用途
* @Author Jove
*******************************************************************************/
unsigned char canSend(CAN_PORT notused, Message *m)
{
u32 i;
CAN1_Handler.pTxMsg->StdId = m->cob_id;
if (m->rtr)
CAN1_Handler.pTxMsg->RTR = CAN_RTR_REMOTE;
else
CAN1_Handler.pTxMsg->RTR = CAN_RTR_DATA;
CAN1_Handler.pTxMsg->IDE = CAN_ID_STD;
CAN1_Handler.pTxMsg->DLC = m->len;
printf("\r\nm->cob_id=%x\r\n", m->cob_id);
for (i = 0; i < m->len; i++)
CAN1_Handler.pTxMsg->Data[i] = m->data[i];
if (HAL_CAN_Transmit(&CAN1_Handler, 10) == HAL_OK)
{
printf("发送成功\r\n");
return 0xff;
}
else
{
printf("发送失败\r\n");
return 0x00;
}
}
4.5 时间计数运算函数
另外首先还要开一个1毫秒的定时器
利用定时器中断驱动文件bsp_timer.c
启动通用定时器5
然后每1毫秒调用一下时间计数运算函数void timerForCan(void)
/*******************************************************************************
* @Brief 定时器中断回调函数函数
* @Param *htim:定时器句柄
* @Return None
* @Date 2019-09-18
* @Version V1.0
* @Note 本程序只供学习使用,未经作者许可,不得用于其它任何用途
* @Author Jove
*******************************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == (&TIM5_Handler))
{
timerForCan();
}
}
在canfestival_drv.c
中,添加时间计数运算函数void timerForCan(void)
来计算时间计数
/*******************************************************************************
* @Brief 时间计数运算函数
* @Param None
* @Return None
* @Date 2019-09-19
* @Version V1.0
* @Note 本程序只供学习使用,未经作者许可,不得用于其它任何用途
每1毫秒调用一下这个函数
* @Author Jove
*******************************************************************************/
void timerForCan(void)
{
TimeCNT++;
if (TimeCNT >= TIMER_MAX_COUNT)
{
TimeCNT = 0;
}
if (TimeCNT == NextTime)
{
TimeDispatch();
}
}
4.6 CAN中断处理过程函数
CAN中断处理过程函数需做部分修改
/*******************************************************************************
* @Brief CAN中断处理过程函数
* @Param *hcan:CAN句柄
* @Return None
* @Date 2019-09-19
* @Version V2.0
* @Note 本程序只供学习使用,未经作者许可,不得用于其它任何用途
此函数会被CAN_Receive_IT()调用
* @Author Jove
*******************************************************************************/
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* hcan)
{
unsigned int i = 0;
//CAN_Receive_IT()函数会关闭FIFO0消息挂号中断,因此需要重新打开
__HAL_CAN_ENABLE_IT(&CAN1_Handler, CAN_IT_FMP0);//重新开启FIF00消息挂号中断
Message RxMSG;
RxMSG.cob_id = (uint16_t)(CAN1_Handler.pRxMsg->StdId);
if (CAN1_Handler.pRxMsg->RTR == CAN_RTR_REMOTE)
{
RxMSG.rtr = 1;
}
else
{
RxMSG.rtr = 0;
}
RxMSG.len = CAN1_Handler.pRxMsg->DLC;
printf("\r\n接受的数据:");
for (i = 0; i < RxMSG.len; i++)
{
RxMSG.data[i] = CAN1_Handler.pRxMsg->Data[i];
printf("%3d ",RxMSG.data[i]);
}
printf("\r\n");
canDispatch(&CanOpen_Data, &(RxMSG));
}
修改和添加canDispatch()
函数解析协议
如果是主站就使用 canDispatch(&TestMaster_Data, &(RxMSG))
如果是从站就使用 canDispatch(&TestSlave_Data, &(RxMSG))
这里使用宏定义CanOpen_Data
代替,会在下面讲解
4.7 主函数
保留了之前一些初始化和普通发送接收的功能
此时还需要
- 添加对应头文件
- 修改默认模式为普通模式
- 初始化配置节点
- 开启定时器TIM5
#include "TestMaster.h"
#include "TestSlave.h"
//CAN1初始化,默认波特率500Kbps普通模式
CAN1_Mode_Init(CAN_SJW_1TQ, CAN_BS2_6TQ, CAN_BS1_8TQ, 6, CAN_MODE_NORMAL);
//初始化配置节点
setNodeId(&CanOpen_Data, CanOpenNodeID);
setState(&CanOpen_Data, Initialisation);
setState(&CanOpen_Data, Operational);
TIM5_Init(100-1,900-1); //定时器3初始化,定时器时钟为90M,分频系数为900-1,
//所以定时器3的频率为90M/900=100K,自动重装载为100-1,那么定时器周期就是1ms
这里主从机的文件都一起添加进来工程
然后采用在bsp_can.h
的宏定义定义主从机和节点ID
如主机:
#define CanOpen_Data TestMaster_Data //设置主从机 TestMaster_Data TestSlave_Data
#define CanOpenNodeID 0x00 //节点ID
如从机:
#define CanOpen_Data TestSlave_Data //设置主从机 TestMaster_Data TestSlave_Data
#define CanOpenNodeID 0x01 //节点ID
这样选择不同模式,编译下载就行了
5. 测试
设置一个主机,一个从机,上电测试
然后等待一段时间,就开始发送心跳报文
相关推荐:
CAN笔记(22) 特殊协议
CAN笔记(21) 服务数据对象
CAN笔记(20) 过程数据对象
CAN笔记(19) 网络管理
CAN笔记(18) 对象字典
谢谢!
转载:https://blog.csdn.net/qq_32618327/article/details/101289442