转自http://bbs.21ic.com/icview-878522-1-1.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-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);
}
移植到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总线。
- CanOpen协议【CanFestival】移植方法 支持VC、QT、STM32
前段时间学习了CanOpen协议,到网上下载的CanFestival3-10源码,移植到VC.QT.STM32等平台,由于网上的资源较少,走了不少弯路,移植好使用过程中才逐渐暴露出各种问题,比如OD字 ...
- 【转】(笔记)CANopen协议【CANFestival】移植方法
一.背景 CAN组网就必须得要应用层协议,原因就在于 * 便于网络管理与控制 * 确认数据的收发 * 发送大于8个字节的数据块(CAN每帧数据传输大小为8字节) * 为不同节点分配不同的报文标识符 * ...
- 郑重推荐开源CANopen协议栈CANFestival(LGPL许可)!!!!!!!!
郑重推荐开源CANopen协议栈CANFestival(LGPL许可)!!!!!!!!(这条文章已经被阅读了 次) 时间:2010/03/04 06:47am 来源:winshton [这个贴子最后由 ...
- AM335x(TQ335x)学习笔记——WM8960声卡驱动移植
经过一段时间的调试,终于调好了TQ335x的声卡驱动.TQ335x采用的Codec是WM8960,本文来总结下WM8960驱动在AM335x平台上的移植方法.Linux声卡驱动架构有OSS和ALSA两 ...
- 嵌入式linux应用程序移植方法总结
嵌入式linux应用程序移植方法总结 前段时间一直在做openCapwap的移植和调试工作,现在工作已接近尾声,编写本文档对前段工作进行一个总结,分享下openCapwap移植过程中的经验和感悟.江浩 ...
- swift学习笔记之-协议
//协议(Protocols) import UIKit /*协议(Protocols) 1.协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法.属性,以及其他需要的东西 2.类.结构体或枚 ...
- NDK Android* 应用移植方法
概述 本指南用于帮助开发者将现有的基于 ARM* 的 NDK 应用移植到 x86.假设您已经拥有一个正常执行的应用,须要知道怎样可以高速让 x86 设备在 Android* Market 中找到您的应 ...
- Go学习笔记07-结构体与方法
Go学习笔记07-结构体与方法 Go语言 面向对象 结构的定义与创建 面向对象 Go语言只支持封装,不支持继承和多态. Go语言中只有struct,即结构体:没有class. 结构的定义与创建 pac ...
- 旧文备份:简单CANOpen 协议说明
(十年前的旧文,不舍等扔) 创建日期:2005-11-17 修改日期:2005-11-17 文件名称:简单CANOpen 协议说明.doc 作者:winshton 版本:V1.0 (注:本文以24in ...
随机推荐
- 浅析Java中的final关键字--转
转载自:http://www.importnew.com/18586.html#comment-581628 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关 ...
- 3-6-汉诺塔(Hanoi Tower)问题-栈和队列-第3章-《数据结构》课本源码-严蔚敏吴伟民版
课本源码部分 第3章 栈和队列 - 汉诺塔(Hanoi Tower)问题 ——<数据结构>-严蔚敏.吴伟民版 源码使用说明 链接☛☛☛ <数据结构-C语言版> ...
- bitcoin双花
https://en.bitcoin.it/wiki/Irreversible_Transactions https://www.reddit.com/r/Bitcoin/comments/2e7bf ...
- angular学习笔记(三十)-指令(4)-transclude
本篇主要介绍指令的transclude属性: transclude的值有三个: 1.transclude:false(默认值) 不启用transclude功能. 2.transclude:true 启 ...
- vue-cli+webpack在生成的项目中使用bootstrap方法(一)
在一个html页面中加入bootstrap是很方便,就是一般的将css和js文件通过Link和Script标签就行. 那么在一个用vue-vli生成的前端项目中如何加入?因为框架不一样了,略微要适应一 ...
- matlab M文件分析工具使用(Code Analyzer and Profiler)
Code Analyzer and Profiler Matlab中,对写在m文件(.m文件)里的代码有分析的工具,可以进行优化,这里做一个简单的介绍. Code Analyzer Code Anal ...
- Testng生成的测试报告乱码解决办法
Testng生成的测试报告乱码解决办法 2017-06-16 1 问题描述 乱码是程序编码不统一,比如Java源代码是utf-8,编译是gbk,这时会乱码. 代码如下: org.testng.Repo ...
- 【Ubuntu】VirtualBox 您没有查看“sf_VirtualDisk”的内容所需的权限。
转自:https://www.cnblogs.com/laishenghao/p/5346651.html 最终解决办法: sudo adduser lqr vboxsf 这里lqr是我的用户名 然后 ...
- spring batch初识
Spring Batch是什么? Spring Batch是一个基于Spring的企业级批处理框架,按照我师父的说法,所有基于Spring的框架都是使用了spring的IoC特性,然后加上自己的一些 ...
- IBM ILOG JViews Charts 产品及功能介绍
摘抄连接:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1004_lidb_ilogjchart/ IBM I ...