发新帖本帖赏金 1.00元(功能说明)我要提问
12345下一页
返回列表
打印

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

[复制链接]
33258|116
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 daboy5279 于 2015-2-27 22:50 编辑

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

到网上下载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-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目录下;将CanFestival-3-10\include目录下的所有.h文件共19个文件全部拷贝到CanFestival\inc目录下,再把CanFestival-3-10\examples\AVR\Slave目录下的ObjDict.h文件拷贝过来,一共20个;将CanFestival-3-10\include\AVR目录下的applicfg.h、canfestival.h、config.h、timerscfg.h共4个头文件拷贝到canfestival\inc\stm32目录下;将CanFestival-3-10\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 1;
}
unsigned char canSend(CAN_PORT notused, Message *m)
{
        return 1;
}
可以先定义一个空函数,等到编译都通过了之后,再往里面添加内容,这几个函数都是定义来供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=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毫秒调用一下下面这个函数。
void timerForCan(void)
{
        TimeCNT++;
        if (TimeCNT>=TIMER_MAX_COUNT)
        {
                TimeCNT=0;
        }
        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 =72-1; //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 = 0;

    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 = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    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 = 0; 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 = 18;
  CAN_Init(CAN1, &CAN_InitStructure);
  CAN_Init(CAN2, &CAN_InitStructure);
  /* CAN1 filter init */
  CAN_FilterInitStructure.CAN_FilterNumber = 0;
  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 = 0;
  CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
  CAN_FilterInit(&CAN_FilterInitStructure);
  /* CAN2 filter init */
  CAN_FilterInitStructure.CAN_FilterNumber = 14;
  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=1;
        else if(RxMessage.RTR == CAN_RTR_DATA)
                m.rtr=0;
          m.len=RxMessage.DLC;
        for(i = 0; i < RxMessage.DLC; i++)
                m.data=RxMessage.Data;
  canDispatch(&TestSlave_Data, &m);
}

打赏榜单

xcodes 打赏了 1.00 元 2016-10-19
理由:好!

相关帖子

沙发
daboy5279|  楼主 | 2015-2-4 11:20 | 只看该作者
移植到VC或其他C++平台说明:
由于源码全是c文件,如果要移植到C++平台,需要将以上所有涉及的.c文件改成.cpp文件,

如果是移植到MFC,则还要在cpp文件中包含头文件
#include "stdafx.h"
移植到VC等一些比较牛的编译器下面时,由于检查得更严格,所以编译还会出现一些指针不匹配的问题,如:pdo.cpp文件的145、216、332行就会报错,只要强制转换一下指针就能通过,如将
pwCobId = d->objdict[offset].pSubindex[1].pObject;
改成
pwCobId = (unsigned long *)d->objdict[offset].pSubindex[1].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总线。



使用特权

评论回复
板凳
yindahu| | 2015-2-12 23:41 | 只看该作者
楼主幸苦了,谢谢经验

使用特权

评论回复
地板
daboy5279|  楼主 | 2015-2-13 11:27 | 只看该作者
yindahu 发表于 2015-2-12 23:41
楼主幸苦了,谢谢经验

好像用CanOpen协议的人不多呀

使用特权

评论回复
5
magicoctoier| | 2015-2-18 00:01 | 只看该作者
好东西,正在研究Canopen协议,辛苦

使用特权

评论回复
6
daboy5279|  楼主 | 2015-2-27 22:47 | 只看该作者
magicoctoier 发表于 2015-2-18 00:01
好东西,正在研究Canopen协议,辛苦

你们公司做什么产品的?现在汽车用这个协议比较多,不知道以后物联网会不会用

使用特权

评论回复
7
mpuhome| | 2015-3-2 15:32 | 只看该作者
工业自动化,运动控制上,伺服驱动器用的也比较多

使用特权

评论回复
8
magicoctoier| | 2015-3-5 20:45 | 只看该作者
daboy5279 发表于 2015-2-27 22:47
你们公司做什么产品的?现在汽车用这个协议比较多,不知道以后物联网会不会用 ...

大型机械上的传感器输出基本上不是“电流”就是“canopen”。

使用特权

评论回复
9
freeloop| | 2015-3-16 11:35 | 只看该作者
请问楼主用dsp2812实现与上位机canopen通信也可以同样移植吗?

使用特权

评论回复
评论
daboy5279 2016-2-21 11:37 回复TA
都可以 
10
freeloop| | 2015-3-16 11:37 | 只看该作者
才开始接触canopen,楼主又没什么学习经验或者介绍这方面比较清楚的资料供参考,多谢了!

使用特权

评论回复
评论
daboy5279 2016-2-21 11:38 回复TA
上网找吧,我也是上面找的,找资料也是一种能力 
11
franki_18| | 2015-3-17 15:14 | 只看该作者
写的很好  ,楼主能否把STM32 的工程共享一下 ?

使用特权

评论回复
12
franki_18| | 2015-3-19 16:33 | 只看该作者
请问楼主 ,移植到stm32后 ,如何测试呢 ?
能否指点一下!:handshake

使用特权

评论回复
评论
daboy5279 2016-2-21 11:40 回复TA
移植好后当然是和别的节点通信呀,我当时是写了个上位机做Master,下位机是从节点,就这样调试呀 
13
daboy5279|  楼主 | 2015-4-16 21:44 | 只看该作者
freeloop 发表于 2015-3-16 11:35
请问楼主用dsp2812实现与上位机canopen通信也可以同样移植吗?

可以啊  目前发现C、C++的平台都可以

使用特权

评论回复
14
daboy5279|  楼主 | 2015-4-16 21:48 | 只看该作者
freeloop 发表于 2015-3-16 11:37
才开始接触canopen,楼主又没什么学习经验或者介绍这方面比较清楚的资料供参考,多谢了! ...

给个CanOpen协议的文档资料你看,边看边实践,慢慢的就全掌握了http://www.doc88.com/p-793226285265.html

使用特权

评论回复
15
daboy5279|  楼主 | 2015-4-16 21:50 | 只看该作者
franki_18 发表于 2015-3-19 16:33
请问楼主 ,移植到stm32后 ,如何测试呢 ?
能否指点一下!

你如果是移植的从节点,就得找一个Master来控制它,心跳、OD读写等等都正常了就可以了

使用特权

评论回复
16
daboy5279|  楼主 | 2015-4-16 21:57 | 只看该作者
本帖最后由 daboy5279 于 2015-4-29 09:08 编辑

我最近在玩物联控制的远程控制,如用手机APP或微信来控制家里的智能插座等硬件,之前一直玩嵌入式,现在接触网页、服务器、APP的部署等东西,感觉有点头大

使用特权

评论回复
17
zhonglong1215| | 2015-5-14 11:24 | 只看该作者
谢谢楼主,最近也在搞CANopen,就是不知道公司是不是要写自己的协议栈,要写的话就比较麻烦了,感觉协议量还是比较大的。

使用特权

评论回复
18
daboy5279|  楼主 | 2015-5-30 00:54 | 只看该作者
zhonglong1215 发表于 2015-5-14 11:24
谢谢楼主,最近也在搞CANopen,就是不知道公司是不是要写自己的协议栈,要写的话就比较麻烦了,感觉协议量 ...

不用自己写啊,CanFestival就是人家帮你写好的了,只是你要用就必须移植到你自己的平台上,这个帖子就是教你怎么移植的

使用特权

评论回复
19
zhonglong1215| | 2015-5-30 08:52 | 只看该作者
daboy5279 发表于 2015-5-30 00:54
不用自己写啊,CanFestival就是人家帮你写好的了,只是你要用就必须移植到你自己的平台上,这个帖子就是 ...

恩,这个协议栈实现了部分DS301的功能,不知道楼主对它内部的时钟机制是怎么理解的,它那个图我对照着源码看一直不太理解。 比如setTimer函数实时修改比较寄存器的值,这个跟源码比较起来感觉跟图上描述的有点区别呢。不知道楼主是怎么理解的。

使用特权

评论回复
评论
daboy5279 2015-10-13 15:15 回复TA
源码跟上图是一致的 
20
ergelove| | 2015-6-15 13:28 | 只看该作者
多谢楼主,请问一下,移植成功后,代码量有多大啊,有10K的代码量吗?

使用特权

评论回复
评论
daboy5279 2015-10-26 15:02 回复TA
几十K那样子吧 
发新帖 本帖赏金 1.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

91

主题

182

帖子

10

粉丝