菜鸟也想出把力 RT-Thread + STM32 + CanOpen(开源的CanFestival)

发布于 2012-04-28 10:13:47
现在手头有一个项目,需要驱动一些电机。电机驱动模块外购,支持CanOpen协议(DS301 + DS402)。之前在论坛上搜了一下,发现只有一篇提到CanOpen
因为没什么基础,先了解Can总线,再读DS301的DataSheet,又在网上找到了开源的CanOpen协议栈源代码CanFestival学习了一下。CanFestival算是开源的比较完善的DS301协议栈实现(主站,从站均可以支持),支持Linux,Windows平台,也支持单片机平台,同时也有一些例程提供。了解之后,决定在项目中采用该协议栈。
之前在一个研究性的项目中使用过RT-Thread,手头也有网络收音机的板子(刚好有CAN口)和STM32的评估板(CAN通信至少要两个节点),就尝试CanFestival + STM32 + RT-Thread的结合,
1.先将CanFestival移植到STM32。由于有其他单片机平台的移植例程,移植过程不是很复杂。主要是在具体的硬件平台上实现几个函数:void setTimer(TIMEVAL value);TIMEVAL getElapsedTime(void);定时器的设置以及定时器的中断处理函数的实现。
2.加入RT-Thread。其实如果只是完成简单的CanOpen通信,不一定要上OS,在加入RT-Thread之前,用中断 + 主程序的前后台方式也实现了一个测试例程。不过因为项目比较大,后续还有其他的控制要求,也包括人机界面,还是决定加入RT-Thread。CanFestival在OS上的移植还是比较简单的,也有Windows上的例程提供。和OS有关的,主要包括创建定时器和CanOpen数据包处理两个线程,同时利用了OS提供的互斥量,完成线程间的协调。
3.在RT-Thread + STM32上实现一个Canfestival提供的例程,TestMasterSlave。如果该例程能顺利跑起来的话,说明Canfestival的移植完成,同时对CanFestival的使用和DS301协议也有进一步的了解(例程的测试只是第一步,后续需要利用CanFestival的API接口实现控制功能)。

先将相关代码发上来:
下载附件[CanFestival.rar]
将CanFestival.rar解压到RT-Thread的Components目录下。
下载附件[project.rar]
将project.rar解压到RT-Thread的spstm32f10x目录下。
用MDK打开。工程中包含两个工程,一个主站,一个从站。分别编译,写入两块开发板(我这边是STM32评估板做Master, 网络收音机板做Slave。主从的区别主要在与CanFestival相关的Master.c, Slave.c, TestMaster.c, TestSlave.c文件中,后面两个Test打头的是用Beremiz(CanFestival是它的子项目)生成的数据字典文件;主从与开发板关系不大,只是几个LED的口不同而已,线程的实现都是完全一样的。

PC抓到的Can数据包.JPG

不好意思,有点长。待续...

查看更多

关注者
1
被浏览
88.6k
102 个回答
softwind
softwind 2012-04-28
Canfestival的主页

国内关于Can和CanOpen能找到的比较好的网站
http://www.dndev.com/cgi-bin/forum/forums.cgi?forum=2
prife
prife 2012-04-28
好贴!顶!楼主辛苦
softwind
softwind 2012-04-28
由于本人还是菜鸟一枚,关于提交的例程,还有几点与RT-Thread或者说与OS的使用相关的基础知识,即是说明,也是问题,还请各位方家指教。
1)CanFestival中有两个与移植相关的关键函数,canDispatch()和TimeDispatch()。这两个操作是互斥的,在一般的OS,例如Windows上是利用Mutex实现的;单片机平台上是利用关中断或者直接利用不能嵌套的同级中断实现。TimeDispatch()对时间的要求相对比较严格,执行的时间也不太长,我直接放在中断中调用;而canDispatch()在线程中调用,相关代码:
void can_recv_thread(void* parameter)
{
recv_sem = rt_sem_create("recvsem", 0, RT_IPC_FLAG_FIFO);
tran_sem = rt_sem_create("transem", 0, RT_IPC_FLAG_FIFO);

can_slave_init();

test_slave();

/* Infinite loop*/
while(1)
{
rt_sem_take(recv_sem, RT_WAITING_FOREVER);

{
uint32_t next;
Message *pmsg;

next = rx_read;
pmsg = &rx_msg_buf[next];

/* Disable the Interrupt sources */
TIM5->DIER &= (uint16_t)~TIM_IT_CC1;
canDispatch(&TestSlave_Data, pmsg);
/* Enable the Interrupt sources */
TIM5->DIER |= TIM_IT_CC1;

next++;
if(next >= RX_BUF_LEN) next = 0;
rx_read = next;
}
}
}


void TIM5_IRQHandler(void)
{
uint16_t capture = 0;

if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1);
last_time_set = TIM_GetCounter(TIM5);
TimeDispatch();
}
}

通过关中断实现与TimeDispatch()的互斥。
也可以创建一个单独的定时器线程,等待在信号量上;在定时中断中发送信号量;然后在调用canDispatch()和TimeDispatch()之前加入rt_mutex_take();

2)下面是我不太有把握的地方,请高手不吝赐教

CAN数据包的接收在中断中进行:
void USB_LP_CAN1_RX0_IRQHandler(void)
{
{
Message *pmsg;
uint32_t i, next;

CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);

next = (rx_save + 1) ;
if(next >= RX_BUF_LEN) next = 0;
if(next == rx_read) return;

pmsg = &rx_msg_buf[rx_save];
/**< message's ID */
pmsg->cob_id = (UNS16)RxMessage.StdId;
/**< remote transmission request.(0 if not rtr, 1 if rtr) */
if(RxMessage.RTR == CAN_RTR_REMOTE)
pmsg->rtr = 1;
else
pmsg->rtr = 0;

/**< message's length (0 to 8) */
pmsg->len = (UNS8)RxMessage.DLC;
for(i = 0; i < pmsg->len; i++)
pmsg->data
    = RxMessage.Data
      ;

      rx_save = next;

      rt_sem_release(recv_sem);
      }
      }


接收到的数据包放在一个循环队列中,然后发送信号量到can_recv_thread()线程。这应该算典型的生产者和消费者问题。我看了RT-Thread编程指南的相关章节,对这个问题也有不同的解决方法,既要用到信号量,也有对临界资源的加锁操作。我这里只有一个生产者,一个消费者。生产者负责写入,修改尾指针;消费者读取,修改头指针。在上面的程序中,recv_sem相当于空闲单元的资源计数器,如果头尾指针的读取和写入都是原子操作,那么操作该队列的时候是否可以不加锁?
bernard
bernard 2012-04-28
非常好,非常好,大力支持

不知道lz后续时间如何,如果有可能希望能够把这块维护起来。另外关于CanOpen的实现,我得先看看它的许可证协议。
bernard
bernard 2012-04-28
许可证协议问题不算太大:

Free software CANopen framework

CanFestival focuses on providing an ANSI-C platform independent CANOpen? stack that can be built as master or slave nodes on PCs, Real-time IPCs, and Microcontrollers.

Runtime code is licensed LGPLv2, whereas accompanying developer tools are licensed GPLv2.

CanFestival was made free in 2001, and grow since thank to user community source code contributions.


另外,这块东西也可以考虑在RT-Thread中变成一个独立的应用模块。RealTouch中有CAN模块吗?如果没有,考虑在ART上把这套东西给折腾起来。
softwind
softwind 2012-04-28
非常好,非常好,大力支持

不知道lz后续时间如何,如果有可能希望能够把这块维护起来。另外关于CanOpen的实现,我得先看看它的许可证协议。


项目本身是工作的一部分,时间没有问题,只是能力比较有限。
此次提交的代码只是个开始,主要是测试CanFestival协议栈的移植,后续要以此为基础进行下一步的开发。对我而言,不管是RT-Thread,还是CanFestival,还有很多需要深入的地方。我会尽力而为,有新的进展我也会汇报上来。

也希望对Can和CanOpen有兴趣或比较了解的同学多提宝贵意见。
bernard
bernard 2012-04-28
建议加个maillist吧,这样有什么问题也可以在邮件列表中沟通。

CAN是工业总线中极为常用的一种,我也希望RT-Thread在其中能够做好。
softwind
softwind 2012-04-29
建议加个maillist吧,这样有什么问题也可以在邮件列表中沟通。

CAN是工业总线中极为常用的一种,我也希望RT-Thread在其中能够做好。


好。
申请加入不成功,邮件总是被退回来。
rt-thread-cnusers+subscribe@googlegroups.com没错吧?
bernard
bernard 2012-04-29
web方式的你能够访问吗?


如果能够访问,那么可以在上面提交加入。
pilgrim_kevin
pilgrim_kevin 2012-06-08
楼主很不错!这个工作还是很有意义的,因为CanOpen的应用还是很有价值,应用场合也比较广泛。

我的小组最近也在做一些CanOpen相关的工作,也是用CanFestival,在ARM和AVR上用。
jeffwei
jeffwei 2012-06-08
前几天下载了CAN协议规范,正无从下手呢,楼主真是及时雨啊 [s:175]
jokeyli
jokeyli 2012-07-06
RT-T有继续维护CAN吗?
knight_hu
knight_hu 2012-09-06
看到这个帖子太激动了,先收藏吧,正好刚搞了快AT9SAM9的板子准备弄CANOpen,应用层用过一两年,也准备在RTT上实现CANOpen,将来一起讨论讨论。
lianxiaolei
lianxiaolei 2012-09-12
顶,版主的解决方法不错,
4476203
4476203 2012-09-14
很需求,也很有用。
suifan@163.com
suifan@163.com 2012-11-09
想看看,可是没权限 [s:182]
gpfrank
gpfrank 2012-12-25
slave_board.c 文件中的
#define RX_BUF_LEN 1024
Message rx_msg_buf[RX_BUF_LEN];
uint32_t rx_save, rx_read;

#define TX_BUF_LEN 1024
uint32_t tx_save, tx_read;
CanTxMsg tx_msg_buf[TX_BUF_LEN
为什么要这么大的buffer呢? 占用了32K的RAM?这个值设置的缘由是什么?

另外一个CANOPEN的STACK的CANOPENNODE的STM32的裸机编译仅仅20K ROM,2K RAM左右。
这个100K的FLASH, 42K的RAM占用,有点吓人啊!
asher
asher 2012-12-27
想在PC机上建立一个主站,需要做哪些工作呢?
CANOPEN
CANOPEN 2013-01-08
弱弱的问一句 工程中在哪里对CANfestival协议进行初始化的?协议入口怎么找不到?
sxf_zero
sxf_zero 2013-01-08
留个脚印,关注下
myciga
myciga 2013-06-24
弱弱的问一句 工程中在哪里对CANfestival协议进行初始化的?协议入口怎么找不到?



同样的问题, 高手出来解释一下啊
softwind
softwind 2013-06-24
弱弱的问一句 工程中在哪里对CANfestival协议进行初始化的?协议入口怎么找不到?



同样的问题, 高手出来解释一下啊


印象中不需要做什么特别的初始化工作,以Master节点为例:
1. TestMaster.c文件是用工具软件自动生成的数据对象字典文件,该文件的最后最后一句
CO_Data TestMaster_Data = CANOPEN_NODE_DATA_INITIALIZER(TesterMaster)

TestMaster_Data的"初始化"(或者说定义)是在编译的时候,由宏CANOPEN_NODE_DATA_INITIALIZER完成的;
2. Master_Board.c中
static int test_master(void)
{
if(strcmp(MasterBoard.baudrate, "none"))
{
TestMaster_Data.heartbeatError = TestMaster_heartbeatError;
TestMaster_Data.initialisation = TestMaster_initialisation;
TestMaster_Data.preOperational = TestMaster_preOperational;
TestMaster_Data.operational = TestMaster_operational;
TestMaster_Data.stopped = TestMaster_stopped;
TestMaster_Data.post_sync = TestMaster_post_sync;
TestMaster_Data.post_TPDO = TestMaster_post_TPDO;
TestMaster_Data.post_emcy = TestMaster_post_emcy;
TestMaster_Data.post_SlaveBootup=TestMaster_post_SlaveBootup;
}

// Start timer thread
StartTimerLoop(&InitNodes);
return 0;
}

定义一些回调函数,然后启动CanFestival协议栈的定时器;数据包的处理是依靠两个关键的函数canDispatch()和TimeDispatch()实现的;
3. 另外,对于Master节点,可以调用
setState(&TestMaster_Data, Initialisation);

setState(&TestMaster_Data, Operational);

完成状态的切换;一般而言,按照DS301协议,从节点上电初始化后自动进入Pre-Operation状态,由Master节点控制进入Operation()状态。这些功能都可以调用相关的API函数实现,例如:
masterSendNMTstateChange()


PS: 一般而言,调用canDispatch()之后,协议栈就开始运行。
myciga
myciga 2013-06-25
多谢softwind指点, 还在努力理解协议中, 有问题再继续请教.
myciga
myciga 2013-07-02
Step By Step 执行了一遍, 依然没有看到是如何启动的, Slave的启动大致如下: test_slave() - StartTimerLoop - TIM5_start() - TIM5_IRQHandler - TimeDispatch 但是始终没有看到是如何进入到 test_slave()的.
softwind
softwind 2013-07-02
Step By Step 执行了一遍, 依然没有看到是如何启动的, Slave的启动大致如下: test_slave() - StartTimerLoop - TIM5_start() - TIM5_IRQHandler - TimeDispatch 但是始终没有看到是如何进入到 test_slave()的.


刚才把原来发的程序重新下载看了一下,可能给出的代码不全,只包含了与Canfestivsal相关的部分,抱歉。
Slave_board.c文件中
void can_recv_thread(void* parameter)
{
recv_sem = rt_sem_create("recvsem", 0, RT_IPC_FLAG_FIFO);
tran_sem = rt_sem_create("transem", 0, RT_IPC_FLAG_FIFO);

can_slave_init();

test_slave();

/* Infinite loop*/
while(1)
{
rt_sem_take(recv_sem, RT_WAITING_FOREVER);

{
uint32_t next;
Message *pmsg;

next = rx_read;
pmsg = &rx_msg_buf[next];

/* Disable the Interrupt sources */
TIM5->DIER &= (uint16_t)~TIM_IT_CC1;
canDispatch(&TestSlave_Data, pmsg);
/* Enable the Interrupt sources */
TIM5->DIER |= TIM_IT_CC1;

next++;
if(next >= RX_BUF_LEN) next = 0;
rx_read = next;
}
}
}

void can_send_thread(void *parameter)
{
while(1)
{
rt_sem_take(tran_sem, RT_WAITING_FOREVER);

{
uint32_t next;
uint8_t mailbox_no;
CanTxMsg *ptx_msg;

next = tx_read;
ptx_msg = &tx_msg_buf[next];

mailbox_no = CAN_Transmit(CAN1, ptx_msg);
if(mailbox_no != CAN_NO_MB)
{
next++;
if(next >= TX_BUF_LEN) next = 0;
tx_read = next;
}
else
{
rt_sem_release(tran_sem);
}
}
}
}


需要在应用程序中启动这两个收发线程,你可以先试一下。有问题再讨论。
myciga
myciga 2013-07-03
 | /
- RT - Thread Operating System
/ | 1.0.2 build Jul 3 2013
2006 - 2011 Copyright by rt-thread team
rtc is not configured
please configure with set_date and set_time
finsh>>list_thread()
thread pri status sp stack size max used left tick error
-------- ---- ------- ---------- ---------- ---------- ---------- ---
tidle 0x1f ready 0x00000058 0x00000100 0x0000005c 0x00000019 000
tshell 0x14 ready 0x00000088 0x00000800 0x000001b0 0x00000008 000
Cantx 0x08 init 0x00000040 0x00000800 0x00000040 0x00000014 000
Canrx 0x08 init 0x00000040 0x00000800 0x00000040 0x00000014 000
led 0x14 suspend 0x00000078 0x00000200 0x00000078 0x00000005 000
0, 0x00000000


多谢softwind指导, 如上, 仿真通过,明天写到板子上再试试.
show8912
show8912 2013-07-24
softwind,Test打头的数据字典文件是怎么用Beremiz生成的??我下载了Beremiz相关的软件,但还是摸不着头脑,本人比较笨,望指点
softwind
softwind 2013-07-24
softwind,Test打头的数据字典文件是怎么用Beremiz生成的??我下载了Beremiz相关的软件,但还是摸不着头脑,本人比较笨,望指点


下载最新的Beremiz,安装,在BeremizCanFestival-3objdictgen下,有objdictedit.py,运行该文件。
PS:需要安装Python。
详细的使用方法可以参阅BeremizCanFestival-3objdictgendocmanual_en文件Page28 9.1.2)Installation and usage on Windows小节。
ioocuo
ioocuo 2013-08-03
我想问的是关于STM32定时器5的使用问题,在定时器发生中断里面读取定时器计数器的值,这个值在定时中断发生时就已经复位了呀,但是还在中断里面读取的话,会不会一直是0呢?那这样还会有什么意义?
想更多的请教下您,不知道能否提供QQ或者其他的联系方式
softwind
softwind 2013-08-05
我想问的是关于STM32定时器5的使用问题,在定时器发生中断里面读取定时器计数器的值,这个值在定时中断发生时就已经复位了呀,但是还在中断里面读取的话,会不会一直是0呢?那这样还会有什么意义?
想更多的请教下您,不知道能否提供QQ或者其他的联系方式


我的理解,中断中读取的是当前Counter Register的值,不存在复位的问题。Counter是一直在计数,Overflow或Underflow后会重新开始计数;SetTimer()函数改变的是Compare/Capture Register的值,也就是下一次定时中断触发的时间。
CANOPEN
CANOPEN 2013-08-09
想问一下 你的PDO与IO短脚映射关系是怎么实现的程序里好像没有找到……
softwind
softwind 2013-08-09
想问一下 你的PDO与IO短脚映射关系是怎么实现的程序里好像没有找到……


PDO是CANOpen协议中的概念,通过软件实现。"与I/O引脚的映射关系",不太明白你的意思。
CANOPEN
CANOPEN 2013-08-14
看了下你的TestSlave.c只有TPDO的描述和映射分别映射到index 0x2000(SlaveMap1)以后, 没有RPDO的描述和映射关系。 好像你的映射关系都固定了,我现在想用SDO来配置可变PDO映射,不知道CANfestival是否支持,不知道从哪下手,我主站用的是CANpro协议分析软件,希望大神给予帮助......
softwind
softwind 2013-08-14
看了下你的TestSlave.c只有TPDO的描述和映射分别映射到index 0x2000(SlaveMap1)以后, 没有RPDO的描述和映射关系。 好像你的映射关系都固定了,我现在想用SDO来配置可变PDO映射,不知道CANfestival是否支持,不知道从哪下手,我主站用的是CANpro协议分析软件,希望大神给予帮助......


CANpro没有用过。如果你的从站支持动态的PDO映射,就可以通过SDO进行配置。Canfestivsal本身是支持SDO读写操作的,所以可以实现动态的PDO映射,就是读写从站的数据字典。不过修改PDO映射的话,还是有些规则的,具体过程可参考二楼的回复,另外DS301协议中也有详细的描述(DS301 Version 4.02 Page109 Object 1600h - 17FFh: Receive PDO Mapping Parameter)。
ioocuo
ioocuo 2013-08-26
有个问题,我现在使用的定时器计数器的位数是24位的,与源码及你用的STM32(定时器计数器为16位)都不一样,所以现在我产生心跳报文的时间不准备,尝试看了一下源码,不知道从哪部分下手改。希望能给予帮助,谢谢!
softwind
softwind 2013-08-27
有个问题,我现在使用的定时器计数器的位数是24位的,与源码及你用的STM32(定时器计数器为16位)都不一样,所以现在我产生心跳报文的时间不准备,尝试看了一下源码,不知道从哪部分下手改。希望能给予帮助,谢谢!


timerscfg.h 文件中有几个宏定义,需要根据你定时器的实际情况修改:

#define TIMEVAL_MAX (定时器的最大计数值, 16位为0xFFFF)

#define MS_TO_TIMEVAL(ms) (1ms对应的计数值, 例如计数频率为1us,则此值为1000)
#define US_TO_TIMEVAL(us) (1us对应的计数值, 例如计数频率为1us,则此值为1)
ioocuo
ioocuo 2013-08-30
#define TIMEVAL_MAX 0XFFFFFF(定时器的最大计数值, 24位为0xFFFFFF)
#define MS_TO_TIMEVAL(ms) (1ms对应的计数值, 例如计数频率为1us,则此值为1000)
#define US_TO_TIMEVAL(us) (1us对应的计数值, 例如计数频率为1us,则此值为1)
如果改成上面那样的计数频率为1US,设置心跳报文为10S,但实际间隔只有9.8S多;
但改成计数频率为10uS,#define MS_TO_TIMEVAL(ms) (1ms对应的计数值, 例如计数频率为10us,则此值为100)
此时仍然设置心跳为10S,但实际间隔只有5S多。不知道为何
shuixui
shuixui 2013-10-06
大四了,正在学arm和canopen。版主能否将canopen应用的stm32上的工程压缩发我邮箱:doudoushuixiu@gmail.com。
ljt8015
ljt8015 2013-10-08
canopen的资料很少,楼主还能上传点?
shuixui
shuixui 2013-10-17
仔细看了rt-thread下的canopen实现代码,但是工程里,并没有初始化canopen的线程的建立啊?application里面也找不到与canopen的函数相关的任何痕迹,是还没有完成吗?
zhonghua0402
zhonghua0402 2013-11-18
仔细看了rt-thread下的canopen实现代码,但是工程里,并没有初始化canopen的线程的建立啊?application里面也找不到与canopen的函数相关的任何痕迹,是还没有完成吗?

请注意第三页作者回复的内容,讲的很清楚!
lh2008071102
lh2008071102 2014-01-09
楼主你好!
有几个问题想请教一下:
1)按你的方式打开工程后,不知道怎么开始用,比如初始化,怎么发送、接收数据等等
2)对CanFestival的结构还是很模糊,301也只是了解一点,楼主能不能解释一下,这个里面那些函数时我们可以调用的,哪些不可以
lyd0429
lyd0429 2014-01-10
成熟canopen代码,协议栈是库形式的,已经开发出一些产品,对应RT-thread和canopen比较熟悉,
邮箱:lyd0429@sina.com
bernard
bernard 2014-01-10
提供商业级canopen代码,协议栈是库形式的,应用的工程师直接可以调用函数,直接向相应的缓冲区放数据就可以完成CANOPEN通信开发。
提供文件:
1.试验例程(工程文件带协议栈库文件)
2.OD编辑软件
3.usbcan一个
4.说明书
邮箱:lyd0429@sina.com


请在商业板块提供更多的信息,是否能够应用于RT-Thread,在硬件上是否有限制等等信息。
原野牧歌
原野牧歌 2014-02-24
关注这个CAN总线CanOpen的实现问题,作为控制器预留的接口,在工业上也有较大的应用范围。
CANOPEN
CANOPEN 2014-04-15
看了一下你的主站和从站,好像没什么区别,如果使用你的主站Master是怎样向从站发送NMT报文(Start remote node)使从站将数据不断的发送给主站的?还是主从建立好发送接收任务后连接起来就能完成数据的交换?

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友