打印
[应用相关]

请问 有搞 CANOPEN 协议的吗?

[复制链接]
楼主: goodluck09876
手机看帖
扫描二维码
随时随地手机跟帖
21
步骤二:
将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文件。

使用特权

评论回复
22
engineerDC| | 2018-8-21 20:24 | 只看该作者
步骤三:
将CanFestival\src目录下的所有.c文件添加到工程;将canfestival\driver目录下的stm32_canfestival.c文件添加到工程;
如果实现的是从设备,再将canfestival\driver目录下的TestSlave.c文件添加到工程,如果实现的是主设备,则将TestMaster.c文件添加到工程;

使用特权

评论回复
23
engineerDC| | 2018-8-21 20:24 | 只看该作者
步骤四:
将文件目录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);
}

移植到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总线。

使用特权

评论回复
24
Diyer2015| | 2018-8-21 20:26 | 只看该作者
使用STM32CubeMX新建一个工程,仅需对CAN、RCC、SYS做简单的配置即可,CAN波特率配置为500k,点击Project -> Generate Code生成Keil工程与代码;

使用特权

评论回复
25
Diyer2015| | 2018-8-21 20:27 | 只看该作者
CanFestival官网下载最新版的源代码,在工程目录下新建CanFestival文件夹,将相应的文件拷入其中,其中ObjDict.c,ObjDict.h的生成在博文使用Objdictedit生成CanFestival所需的对象字典文件中讲述,can_STM32.c,timer_STM32.c参考源代码中的can_AVR.c,timer_AVR.c修改而来,文件树形结构如下:
├─CanFestival
│ │
│ ├─driver
│ │ can_STM32.c
│ │ timer_STM32.c
│ │
│ ├─include
│ │ │ can.h
│ │ │ can_driver.h
│ │ │ data.h
│ │ │ dcf.h
│ │ │ def.h
│ │ │ emcy.h
│ │ │ lifegrd.h
│ │ │ lss.h
│ │ │ nmtMaster.h
│ │ │ nmtSlave.h
│ │ │ objacces.h
│ │ │ ObjDict.h
│ │ │ objdictdef.h
│ │ │ pdo.h
│ │ │ sdo.h
│ │ │ states.h
│ │ │ sync.h
│ │ │ sysdep.h
│ │ │ timer.h
│ │ │ timers_driver.h
│ │ │
│ │ └─STM32
│ │ applicfg.h
│ │ canfestival.h
│ │ config.h
│ │ timerscfg.h
│ │
│ └─src
│ dcf.c
│ emcy.c
│ lifegrd.c
│ lss.c
│ nmtMaster.c
│ nmtSlave.c
│ objacces.c
│ ObjDict.c
│ pdo.c
│ sdo.c
│ states.c
│ sync.c
│ timer.c

使用特权

评论回复
26
Diyer2015| | 2018-8-21 20:27 | 只看该作者
在timer_STM32.c中需要实现setTimer,getElapsedTime,timerCanFestival三个函数,在can_STM32.c中需要实现canSend函数,在dcf.c中需要在inline前添加static,否则编译不通过,原因不明,在main.c中需要调用setNodeId和setState对协议栈进行配置;
编译通过后下载至战舰STM32F1开发板,使用周立功USBCAN-E-P主站卡进行测试,在CANmanager for CANopen中添加从站,点击启动按钮,启动成功后界面如下:

使用特权

评论回复
27
Lovemcu2212| | 2018-8-21 20:31 | 只看该作者
最近由于项目的需要,移植了CAN FESTIVAL 的源代码,当前的主控是STM32F103C8,移植后各种修改对象字典,都是只能发送一个 BOOT UP ,NMT指令发送后没有任何响应,跟踪代码发现,bDeviceNodeId的值并不是初始化时的设置值,所以代码一直进不去。恳请有这方面经验高人赐教为感!!

使用特权

评论回复
28
Lovemcu2212| | 2018-8-21 20:31 | 只看该作者
最近由于项目的需要,移植了CAN FESTIVAL 的源代码,当前的主控是STM32F103C8,移植后各种修改对象字典,都是只能发送一个 BOOT UP ,NMT指令发送后没有任何响应,跟踪代码发现,bDeviceNodeId的值并不是初始化时的设置值,所以代码一直进不去。恳请有这方面经验高人赐教为感!!

使用特权

评论回复
29
MCUmaker1984| | 2018-8-21 20:32 | 只看该作者
因为要用到CANopen,看协议太枯燥,移植了canfestival的开源代码,使用RM57L作为CANopen slave,初步可以使用:

1、uart串口波特率115200kbps, 可以看到debug msg,貌似运行还是正常的,但是打开一个Timer作为Heartbeat后,一段时候会死机。

但是usb仿真器连接的时候,不会死机。单独运行的话,就会死机,原因正在找,主要对这个机制还不是太清楚。

2、可以使用canopen协议仿真器来调试,基本上所有的消息,命令SDO,PDO,远程帧啥的都可以。

ps:其他人是否有移植好的代码希望可以共享一下,非常感谢!

使用特权

评论回复
30
MCUmaker1984| | 2018-8-21 20:33 | 只看该作者
prefetchEntry:当CPU取指时访问了非法地址,就会跳到这个异常中断中

underEntry:当CPU读到一个未定义的指令(实际上很可能就是访问到了不是放程序的区域),就会跳到这个异常中断中

出现这种异常时,如果连接着仿真器,就查看CP15寄存器组下记录下对应异常来源的地址,以方便分析。

另外,请问你是否有使用MPU功能?如果用了,是怎么配置的?

使用特权

评论回复
31
doit888| | 2018-8-21 20:34 | 只看该作者
CAN 只定义物理层和数据链路层,没有规定应用层,本身并不完整,需要一个高层协议来定义 CAN 报文中的 11/29 位标识符、8 字节数据的使用。

使用特权

评论回复
32
doit888| | 2018-8-21 20:35 | 只看该作者
CANopen 协议被认为是在基于 CAN 的工业系统中占领导地位的标准。
CANOpen在CAN基础上规定了应用层协议。

使用特权

评论回复
33
doit888| | 2018-8-21 20:36 | 只看该作者
CANopen与CAN的关系,如图

1.png (110.69 KB )

1.png

使用特权

评论回复
34
doit888| | 2018-8-21 20:36 | 只看该作者
物理结构区别:大多数重要的设备类型,例如数字 和模拟的输入输出模块、驱动设备、操作设备、控制器、可编程控制器或编码器,都在称为“设备描述” 的协议中进行描述;“设备描述”定义了不同类型的标准设备及其相应的功能。
依靠 CANopen 协议的支持, 可以对不同厂商的设备通过总线进行配置(仅仅需要描述文件EDS 或DCF)。

2.png (65.54 KB )

2.png

使用特权

评论回复
35
EDA设计爱好者| | 2018-8-21 20:38 | 只看该作者
CAN与CANOpen(一)基本概念
CAN与CANOpen(二)报文格式
CAN与CANOpen(三)错误处理
CAN与CANOpen(四)CANOpen对象字典
CAN与CANOpen(五)PDO和SDO
CAN与CANOpen(六)网络管理和CAN FD

使用特权

评论回复
36
EDA设计爱好者| | 2018-8-21 20:39 | 只看该作者
CANOpen协议所在网络架构中的位置

3.png (90.14 KB )

3.png

使用特权

评论回复
37
EDA设计爱好者| | 2018-8-21 20:39 | 只看该作者
对象字典的结构

592355b7c081409080.png (71.49 KB )

592355b7c081409080.png

使用特权

评论回复
38
EDA设计爱好者| | 2018-8-21 20:40 | 只看该作者
对象字典是CANOpen最重要的特性,它将设备的描述标准化。

对象字典中的每一个对象都由16位的索引和8位的子索引来寻址。对于单个的对象字典项子索引总是0.所以一个数据字典最多有65536项。

静态数据类型包含标准数据类型的定义,比如BOOLEAN, INTEGER, floating, string,等。

复杂数据类型包含那些由标准数据类型构成的预定义的数据结构,他们对所有设备都是通用的。

制造商定义的复杂数据类型是由标准数据类型构成的数据结构,但是这些数据类型只是特定的设备使用。

设备子协议可能会为它们的设备定义一些额外的数据类型其中静态数据类型存放在0060-007F,复杂数据类型存放在0080-009F。

通讯子协议区指定了CAN网络的一些参数,它们对所有设备都是通用的。

标准设备子协议区包含了一类设备的所有数据对象。它们可以通过CAN网络读写。

使用特权

评论回复
39
EDA设计爱好者| | 2018-8-21 20:40 | 只看该作者
数据类型

5965b7c083fcb2b0.png (13.55 KB )

5965b7c083fcb2b0.png

使用特权

评论回复
40
磨砂| | 2018-8-21 21:25 | 只看该作者
是不是就是can总线啊

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则