一、背景

  CAN组网就必须得要应用层协议,原因就在于

  * 便于网络管理与控制

  * 确认数据的收发

  * 发送大于8个字节的数据块(CAN每帧数据传输大小为8字节)

  * 为不同节点分配不同的报文标识符

  * 定义帧报文的内容及含义(这在我看来是最主要的原因)

  * 网络的监控,节点故障的诊断与标识

  CAN上层协议有许多,用大家都公认的,便于产品的兼容,因此,CANopen成为备选项。

  

  CANopen有个开源协议栈【CANFestival】,同时有一位大神已经做了移植并记录,在此就厚着脸皮转载过来以做备份。

  转载地址:http://www.cnblogs.com/tdyizhen1314/p/4348725.html

二、正文:

  前段时间学习了CanOpen协议,到网上下载的CanFestival3-10源码,移植到VC、QT、STM32等平台,由于网上的资源较少,走了不少弯路,移植好使用过程中才逐渐暴露出各种问题,比如OD字符串传输、心跳时间不准确等等,现在已经解决了遇到的所有问题,移植出来的工程能够完好支持CanOpen协议,花了点时间,整理出一个简单易用的移植方法说明,也写了一些比较实用的调试工具,本来还想整理SDO、PDO、EDS文件装载等相关知识的,可惜比较忙,等什么时候有空了再整理其他的吧!先把移植的贴上来,希望能帮到大家。
  如果是第一次,整个移植过程还比较麻烦,需要有耐心,按照下面说的一步步来肯定可以的,移植成功一次后,再移植到其他平台就非常轻松了。

  到网上下载CanFestival源码CanFestival-3-1041153c5fd2,解压出来,并将文件夹名字改为CanFestival-3-10,我们移植需要用到的源文件在CanFestival-3-10\src目录下,头文件在CanFestival-3-10\include目录下。

CanFestival-3-10\src下的文件如下图所示:

CanFestival-3-10\include下的文件如下图所示:

接下来开始移植:
步骤一:
在新建好的工程目录下新建文件夹CanFestival,再在CanFestival下新建文件夹driver、inc和src,再在inc文件夹下面新建
stm32文件夹(我这里主要以移植到stm32为例说明,如果是移植到VC或其他平台下,这里也可以命名为其他名字,如vc)。
  
步骤二:
将CanFestival--\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目录下;
将CanFestival--\include目录下的所有.h文件共19个文件全部拷贝到CanFestival\inc目录下,
再把CanFestival--\examples\AVR\Slave目录下的ObjDict.h文件拷贝过来,一共20个;
将CanFestival--\include\AVR目录下的applicfg.h、canfestival.h、config.h、timerscfg.h共4个头文件拷贝到canfestival\inc\stm32目录下;
将CanFestival--\examples\TestMasterSlave目录下的TestSlave.c、TestSlave.h、TestMaster.h、TestMaster.c拷贝到canfestival\driver目录下,并在该目录下新建stm32_canfestival.c文件。
   步骤三:
将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 ;
}
unsigned char canSend(CAN_PORT notused, Message *m)
{
  return ;
}
可以先定义一个空函数,等到编译都通过了之后,再往里面添加内容,这几个函数都是定义来供canfestival源码调用的,如果找不到这几个函数编译就会报错。 步骤六:通过以上几步,所有的文件都弄齐了,但是编译一定会出现报错,注释或删除掉config.h文件中的如下几行就能编译通过:
#include <inttypes.h>
#include <avr\io.h>
#include <avr\interrupt.h>
#include <avr/pgmspace.h>
#include <avr\sleep.h>
#include <avr\wdt.h>
如果还有其他报错,那有可能是因为不同源码版本、不同平台、不同人遇到的错误也会不相同,这里的过程只能做一定的参考,不一定完全相同,解决这些错误需要有一定的调试功底,需要根据编译出错提示来进行修改对应地方,一般都是有些函数没声明或者某个头文件没有包含或者包含了一些不必要的头文件而该文件不存在或者是一些变量类型不符合需定义之类的,如果能够摆平所有的编译出错,那么移植就算成功了,如果你被编译出错摆平了,那么游戏就结束,没得玩了。 步骤七:
解决了所有的编译错误后,接下来实现刚才定义的3个空函数:
函数void setTimer(TIMEVAL value)主要被源码用来定时的,时间到了就需要调用一下函数TimeDispatch(),
函数TIMEVAL getElapsedTime(void)主要被源码用来查询距离下一个定时触发还有多少时间,
函数unsigned char canSend(CAN_PORT notused, Message *m)主要被源码用来发一个CAN包的,需要调用驱动来将一个CAN包发出去。
我们在stm32_canfestival.c文件里定义几个变量如下: unsigned int TimeCNT=;//时间计数
unsigned int NextTime=;//下一次触发时间计数
unsigned int TIMER_MAX_COUNT=;//最大时间计数
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=;
  ret = TimeCNT> last_time_set ? TimeCNT - last_time_set : TimeCNT + TIMER_MAX_COUNT - last_time_set;
  last_time_set = TimeCNT;
  return ret;
}
另外还要开一个1毫秒的定时器,每1毫秒调用一下下面这个函数。
void timerForCan(void)
{
  TimeCNT++;
  if (TimeCNT>=TIMER_MAX_COUNT)
  {
    TimeCNT=;
  }
  if (TimeCNT==NextTime)
  {
    TimeDispatch();
  }
}
can发包函数canSend跟CAN驱动有关,CAN通道可以使用真实的CAN总线,也可以使用虚拟的CAN通道(如文件接口、网络通道等等)。
启动时初始化:
在初始化的文件里(比如main.c)添加以下几行代码
#include "TestSlave.h"
unsigned char nodeID=0x21;
extern CO_Data TestSlave_Data;
在调用函数(比如main函数)里调用以下代码初始化
setNodeId(&TestSlave_Data, nodeID);
setState(&TestSlave_Data, Initialisation); // Init the state
其中T estSlave_Data在TestSlave.c中定义
然后开启调用TimerForCan()的1毫秒定时器,在接收CAN数据那里调用一下源码函数canDispatch(&TestSlave_Data, &m);
canfestival源码就可以跑了,如果需要跟主设备联调,还要实现canSend函数,这个与平台的Can驱动相关。 Stm32平台下的驱动实现:
开启一个1毫秒定时器,可参考如下代码,调用一下函数TIM4_start();即可:
/* TIM4 configure */
static void TIM4_Configuration(void)
{
  /* 时钟及分频设置 */
  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  /* Time Base configuration */
  /* 72M / 72 = 1us */
  // 这个就是预分频系数,当由于为0时表示不分频所以要减1
  TIM_TimeBaseStructure.TIM_Prescaler =-; //72000 - 1;
  //计数模式:向上计数
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  //这个就是自动装载的计数值,由于计数是从0开始的
  //TIM_TimeBaseStructure.TIM_Period =0xffff;//
  TIM_TimeBaseStructure.TIM_Period =0x03e8;//1ms
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  //重新计数的起始值
  TIM_TimeBaseStructure.TIM_RepetitionCounter = ;
  TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
  // TIM IT enable
  TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE); //打开溢出中断
  // TIM enable counter
  TIM_Cmd(TIM4, ENABLE);//计数器使能,开始工作
}
static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
  /* Enable the TIM4 global Interrupt */
  NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = ;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = ;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}
static void RCC_Configuration(void)
{
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
  /* TIM4 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
/* clock enable */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA ,ENABLE);
}
void TIM4_start(void)
{
RCC_Configuration();
/* configure TIM4 for remote and encoder */
NVIC_Configuration();
TIM4_Configuration();
}
void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_CC1) != RESET)
{
//printf("enter tim4");
TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);
}
TimerForCan();
}
canSend函数实现如下:
unsigned char canSend(CAN_PORT notused, Message *m)
{
uint32_t i;
CanTxMsg *ptx_msg=&TxMessage;
ptx_msg->StdId = m->cob_id;
if(m->rtr) {
ptx_msg->RTR = CAN_RTR_REMOTE;
}
else {
ptx_msg->RTR = CAN_RTR_DATA;
}
ptx_msg->IDE = CAN_ID_STD;
ptx_msg->DLC = m->len;
for(i = ; i < m->len; i++) {
ptx_msg->Data = m->data;
} if( CAN_Transmit( CAN1, ptx_msg )==CAN_NO_MB) {
return 0xff;
}
else {
return 0x00;
}
}
其中CAN_Transmit为stm32提供的库函数,在stm32f10x_can.c中定义。
在使用stm32之前需要初始化一下CAN
void CAN_Config(void)
{
/* CAN register init */
CAN_DeInit(CAN1);
CAN_DeInit(CAN2);
CAN_StructInit(&CAN_InitStructure); /* CAN1 cell init */
CAN_InitStructure.CAN_TTCM = DISABLE;
CAN_InitStructure.CAN_ABOM = DISABLE;
CAN_InitStructure.CAN_AWUM = DISABLE;
CAN_InitStructure.CAN_NART = DISABLE;
CAN_InitStructure.CAN_RFLM = DISABLE;
CAN_InitStructure.CAN_TXFP = DISABLE;
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
//Fpclk=72M/2/CAN_Prescaler
//BitRate=Fpclk/((CAN_SJW+1)*((CAN_BS1+1)+(CAN_BS2+1)+1));
//1M
/*CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_3tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_5tq;
CAN_InitStructure.CAN_Prescaler = 4;*/
//125K
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_7tq;
CAN_InitStructure.CAN_Prescaler = ; CAN_Init(CAN1, &CAN_InitStructure);
CAN_Init(CAN2, &CAN_InitStructure); /* CAN1 filter init */
CAN_FilterInitStructure.CAN_FilterNumber = ;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = ;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure); /* CAN2 filter init */
CAN_FilterInitStructure.CAN_FilterNumber = ;
CAN_FilterInit(&CAN_FilterInitStructure);
} Can 接收中断实现:
void CAN1_RX0_IRQHandler(void)
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
//接收处理
m.cob_id=RxMessage.StdId; if(RxMessage.RTR == CAN_RTR_REMOTE)
m.rtr=;
else if(RxMessage.RTR == CAN_RTR_DATA)
m.rtr=;
m.len=RxMessage.DLC;
for(i = ; i < RxMessage.DLC; i++)
m.data=RxMessage.Data; canDispatch(&TestSlave_Data, &m);
} 移植到VC或其他C++平台说明:
由于源码全是c文件,如果要移植到C++平台,需要将以上所有涉及的.c文件改成.cpp文件, 如果是移植到MFC,则还要在cpp文件中包含头文件
#include "stdafx.h"
移植到VC等一些比较牛的编译器下面时,由于检查得更严格,所以编译还会出现一些指针不匹配的问题,如:pdo.cpp文件的145、、332行就会报错,
只要强制转换一下指针就能通过,如将
pwCobId = d->objdict[offset].pSubindex[].pObject;
改成
pwCobId = (unsigned long *)d->objdict[offset].pSubindex[].pObject;
即可通过。
还有407行由于代码跨平台出现些乱码错误,将
MSG_ERR (0x1948, " Couldn't build TPDO n�", numPdo);
改成
MSG_ERR (0x1948, " Couldn't build TPDO \n", numPdo);
即可。 这时编译还不能通过,需修改除了dcf.h和canfestival.h以外的所有头文件,在开头加上
#ifdef __cplusplus
extern "C" {
#endif
头文件结尾加上
#ifdef __cplusplus
};
#endif
例如:data.c改成data.cpp后,data.h中添加位置如下:
#ifndef __data_h__
#define __data_h__ #ifdef __cplusplus
extern "C" {
#endif
//省略掉中间内容
#ifdef __cplusplus
};
#endif #endif /* __data_h__ */ 另外,源码文件文件还有一个错误,这个错误在keil里表现不出来,在VC里就会导致出错,花了些时间才找到这些错误。如下:
文件dcf.cpp第40行,将
extern UNS8 _writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
UNS8 subIndex, UNS8 count, UNS8 dataType, void *data, SDOCallback_t Callback, UNS8 endianize);
改成
extern UNS8 _writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
UNS8 subIndex, UNS32 count, UNS8 dataType, void *data, SDOCallback_t Callback, UNS8 endianize); 移植到VC或QT时,由于电脑没有CAN接口,这时要么用USB-CAN,要么得使用虚拟的CAN总线通道,Linux下面有虚拟的CAN总线,
windows下没有,只能通过走文件接口或网口来虚拟CAN总线。

记录地点:深圳WZ

记录时间:2016年7月29日

【转】(笔记)CANopen协议【CANFestival】移植方法的更多相关文章

  1. (笔记)CanOpen协议【CanFestival】移植方法 支持VC、QT、STM32

    转自http://bbs.21ic.com/icview-878522-1-1.html   前段时间学习了CanOpen协议,到网上下载的CanFestival3-10源码,移植到VC.QT.STM ...

  2. CanOpen协议【CanFestival】移植方法 支持VC、QT、STM32

    前段时间学习了CanOpen协议,到网上下载的CanFestival3-10源码,移植到VC.QT.STM32等平台,由于网上的资源较少,走了不少弯路,移植好使用过程中才逐渐暴露出各种问题,比如OD字 ...

  3. 郑重推荐开源CANopen协议栈CANFestival(LGPL许可)!!!!!!!!

    郑重推荐开源CANopen协议栈CANFestival(LGPL许可)!!!!!!!!(这条文章已经被阅读了 次) 时间:2010/03/04 06:47am 来源:winshton [这个贴子最后由 ...

  4. AM335x(TQ335x)学习笔记——WM8960声卡驱动移植

    经过一段时间的调试,终于调好了TQ335x的声卡驱动.TQ335x采用的Codec是WM8960,本文来总结下WM8960驱动在AM335x平台上的移植方法.Linux声卡驱动架构有OSS和ALSA两 ...

  5. 嵌入式linux应用程序移植方法总结

    嵌入式linux应用程序移植方法总结 前段时间一直在做openCapwap的移植和调试工作,现在工作已接近尾声,编写本文档对前段工作进行一个总结,分享下openCapwap移植过程中的经验和感悟.江浩 ...

  6. iOS阶段学习第19天笔记(协议-Protocol)

    iOS学习(OC语言)知识点整理 一.关于协议(Protocol)的介绍 1)概念:协议指多个对象之间协商的一个接口对象,协议提供了一些方法用在协议的实现者和代理者      之间通讯的一种方式 2) ...

  7. swift学习笔记之-协议

    //协议(Protocols) import UIKit /*协议(Protocols) 1.协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法.属性,以及其他需要的东西 2.类.结构体或枚 ...

  8. Block作为property属性实现页面之间传值(代替Delegate代理与协议结合的方法)

    需求:在ViewController中,点击Button,push到下一个页面NextViewController,在NextViewController的输入框TextField中输入一串字符,返回 ...

  9. 前端学习笔记汇总(之merge方法)

    学习笔记 关于Jquery的merge方法 话不多说,先上图 使用jquery时,其智能提示如上,大概意思就是合并first和second两个数组,得到的结果是first+(second去重后的结果) ...

随机推荐

  1. 解析:使用easyui的form提交表单,在IE下出现类似附件下载时提示是否保存的现象

    之前开发时遇到的一个问题,使用easyui的form提交表单,在Chrome下时没问题的,但是在IE下出现类似附件下载时提示是否保存的现象. 这里记录一下如何解决的.其实这个现象不光是easyui的f ...

  2. ubuntu 设置hostname

    永久修改hostname: # sudo vim /etc/hostname # sudo vim /etc/hosts

  3. Docker distrubution in django

    https://www.syncano.io/blog/configuring-running-django-celery-docker-containers-pt-1/ Update: Fig ha ...

  4. Yocto开发笔记之《串口驱动调试》(QQ交流群:519230208)

    QQ群:519230208,为避免广告骚扰,申请时请注明 “开发者” 字样 ======================================================== 串口驱动各 ...

  5. js017-错误处理与调试

    js017-错误处理与调试 本章内容 理解浏览器报告的错误 处理错误 调试JS代码 17.2 错误处理 17.2.1 try-catch语句 try{ //possible error code }c ...

  6. 最近在 OS-10.9下配置opencv, cgal, latex, qt, pillow

    其实我之前使用的Mac os的版本是10.8的雪豹,可是最近想体验一下Mac os10.9新版本,于是就开始更新Mac os,经过10多个小时的下载和成功安装后,发现之前的配置全乱了,首先是发现lat ...

  7. Apache 使用localhost(127.0.0.1)可以访问 但是使用本机IP(局域网)不能访问

  8. Linux查看CPU和内存使用情况

    在系统维护的过程中,随时可能有需要查看 CPU 使用率,并根据相应信息分析系统状况的需要.在 CentOS 中,可以通过 top 命令来查看 CPU 使用状况.运行 top 命令后,CPU 使用状态会 ...

  9. centos nc命令安装

    yum install nc.x86_64 nc命令的参数 参数 作用-i 设置数据报传送时间间隔-l 以服务器方式运行-k 重复接收并处理某个端口上的所有连接,必须与-l选项一起使用-n 使用ip地 ...

  10. SpringMVC处理请求流程

    SpringMVC核心处理流程: 1.DispatcherServlet前端控制器接收发过来的请求,交给HandlerMapping处理器映射器 2.HandlerMapping处理器映射器,根据请求 ...