小言_互联网的博客

CAN笔记(23) CanFestival移植

1251人阅读  评论(0)


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下新建文件夹candriverincsrc
再在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\incCANTest3\CanFestival\inc\stm32CANTest3\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.cbsp_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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场