只需三步完成动态内存+环形队列库+CAN驱动(LPC17XX)
本帖最后由 linquan315 于 2012-11-18 23:45 编辑首先看一组数据,
一般数据收发可以分为三种方式:
(1)等待方式;
(2)中断方式;
(3)DMA方式;
这三种方式的详细思路在笔者的帖子:
STM32串口驱动(拼音检索测试通过)(环形队列+内存动态分配+DMA)(申请加酷,让更多的人
http://www.amobbs.com/thread-4516795-1-1.html
中有详细介绍
在STM32F407的开发板上,笔者对这三种方式,使用USART做了一个测试。
测试在不同发送模式下所占的CPU
测试不同发送模式下,所占用的CPU。测试方法为,在主程序中开启一个定时器,定时0.5S,在超级循环中对一个变量进行连续自加操作,当0.5S时间到达以后,结束变量的自加,记下这个值。然后将所有的测试变量置为初始状态,开启定时操作并调用串口数据发送函数,在程序主循环中仍然调用变量进行自加操作。在定时结束以后,保存在发送状态下该变量能够完成多少次变量自加工作,两者相除就能得到在启动发送函数时CPU的使用率。一共测试了三种发送方式,普通的等待模式,中断的方式和DMA的方式,使用内存分配的方式,每一个内存块的大小为100字节。
测试的条件是在不同波特率下,定时0.5S,发送对应数据所占用的CPU时间,图中横轴表示发送的数据,不同波特率在单位时间内能发送的数据不一样。从图中可以看出,DMA发送所占CPU最少,而传统的等待方式最多,当单位时间内发送数据量接近饱和时,几乎占用了100%的CPU,在对响应特性要求严格的嵌入式系统中是不能容忍的。在中断的方式下,进入和退出异常需要保存现场,需要占用一定的时间,当波特率较高且发送数据较大的时候,所占用的CPU会有所增加。而使用DMA模式的时候,进入异常的时间大大减少,即使在发送饱和的情况下,仍然只占用了不到1%的时间,可见DMA的发送方式最适合作为操作系统底层驱动的设计方案。推而广之,只要涉及到慢速设备,不论是发送还是接收,不论是USART还是IIC、IIS、SPI、CAN甚至USB和以太网,使用DMA的方式都是首选。
<一>CAN收发+动态内存+环形队列库的使用方法
闲话少说,最近一个信号采集板要使用CAN总线,LPC1754的平台,移植了这个功能。
首先介绍使用方法,很简单,只有三步:
1:工程中包含这几个函数
2:配置发送、接收缓冲区的大小
//定义CAN缓冲区的大小
#define OS_MEM_CAN1_MAX 1024 //CAN缓冲区的内存大小
#define OS_MEM_CAN1_BLK 16 //每一个块的长度
#define CAN1_SEND_MAX_Q (OS_MEM_CAN1_BLK-4) //CAN内存块内最大空间
#define CAN1_SEND_MAX_BOX (OS_MEM_CAN1_MAX/OS_MEM_CAN1_BLK)
//CAN一共有CAN1_SEND_MAX_BOX个BOX
#define CAN1_RECV_MAX_BOX (OS_MEM_CAN1_MAX/OS_MEM_CAN1_BLK)
//CAN一共有CAN1_RECV_MAX_BOX个BOX
直接按照默认配置就可以了,需要自己配置的只用配置前面两个参数就可以了。
想弄明白的这里解释一下,OS_MEM_CAN1_MAX是定义缓冲区的大小,是数据缓冲区,也即是这里不会存放CAN协议帧的ID啊、类型啊、数据量啊什么的,只放数据,OS_MEM_CAN1_BLK是指每一个内存块内最多可以存放多少数据,由于内存块的前面四个字节已经被用作单向链表的地址了,所以实际可以用的是CAN1_SEND_MAX_Q,即(OS_MEM_CAN1_BLK-4)。第三个宏定义CAN1_SEND_MAX_Q就不用管啦,因为8位、16位有可能只用两个字节作链表地址,这里为了版本兼容。最后两个是发送、接收缓冲区的大小,也不用管了,已经通过前面的值算出来了。
3:调用初始化函数与应用函数
初始化函数
CAN1_Configuration(0, BPS_1000K);/* 初始化第1路CAN,波特率1000K*/
发送函数:
CAN1WriteDataToBuffer(buffer, num, ID, ff, mbox, chl)/* 注释见函数说明 */
接收函数:
CAN1RecvFun() /* 注释见函数说明 */
/*******************************************************************************
* 文件名 : CAN1WriteDataToBuffer
* 描述 : 检查发送缓冲区的大小,若空间足够,将待发送的数据放入到发送缓冲
区中去,并且启动发送
* 输入 : buffer待发送的数据的指针,num待发送的数据的数量,ID:CAN帧的ID号
ff:ID类型,0代表11位模式,1代表29位模式,mbox:CAN发送邮箱,有三个
对应1,2,3,chl: CAN通道,LPC1754一共两个,CAN通道0与通道1
* 输出 : 无
* 返回 : 若正确放入到发送缓冲区中去了,就返回0x00 ,否则返回0x01
*******************************************************************************/
CAN1WriteDataToBuffer(buffer, num, ID, ff, mbox, chl)
/*******************************************************************************
* 文件名 : CAN1RecvFun
* 描述 : 当接收到完整的一帧数据以后的处理函数,这个函数是用户的接口函数
CAN1QRecvTest中存放最近一次接收的CAN数据包,用户可以使用。
* 输入 : 无
* 输出 : 无
* 返回 : 无
*******************************************************************************/
CAN1RecvFun()
OK,所有的使用方法就这些了。
<二>CAN总线的简单介绍
CAN的好处就不用说了,抛开繁琐的官方介绍不讲,看看下图,只需要两根线,就可以将110个设备挂在总线上,最远的通信距离能够达到15Km,而且可靠性、速度都比较优异。可以说CAN总线是设备互联和组网的理想选择。
正是如此,CAN总线在大量应用在的工控、自动化、航空、安全领域。
有的人可能会以为,相对485+Modbus而言,CAN总线的成本比较高,其实不然。现在的微控制器的成本已经大大降低了,一般50-100M左右的Cortex-M0、Cortex-M3都是非常低廉,一般只有8-20元左右,内部资源较丰富,至少也有128KFlash,20K以上的RAM,内部资源十分丰富。CAN总线自然不在话下,所以笔者十分推崇使用低成本的ARM+开源操作系统+CAN总线的模式,可以大大减少软件的开发时间。随着技术的发展,集成电路的功能越来越集成化,硬件设计越来越傻瓜化,开发人员将大量的精力朝软件方向偏移。
笔者在读书的过程中,经常见到有的人使用51+汇编,或者使用Cortex-M3处理器,自己编写库文件。笔者觉得在技术日新月异的今天,抱残守缺不如与时俱进。
一些朋友不理解,为什么一个收发程序要写的那么复杂,其实呢,我是想把软件架构写简单一点的,当时是考虑USART.C或者CAN.C是与硬件无关的,而USARTConfig.C、CANConfig.C是与硬件相关的,在任何平台上USART.C、CAN.C都是一模一样的,用户只需要实现几个简单的配置函数就可以实现移植,不管是用8、16、32位,还是上千种各种厂商生产的微控制器,还是IIC、SPI、IIS、USART、CAN、IrDon还是单总线,都可以使用这个结构。
CAN每一个数据帧的结构如上图所示,前面说CAN总线可以使多主模式,其实准确来说,同时只能有一个主机。CAN总线上有很多设备,它们之间是相互独立的,只有两条数据线,当总线上有两个甚至更多设备同时通信的时候,有一套机制保证它们不会相互干扰。CAN的基本信元是一个显性电平和隐性电平,显性是0,隐性是1,很好理解,当一个1与一个0相与的时候,就会变成0了。当大家都在通信的时候,在帧的ID阶段,就在竞争谁能抢得总线的占有权,CAN的收发器管脚在输出电平的时候,也在检测该管脚上的电平值,当我输出一个隐性电平(逻辑1,高电平),但是总线上有其他设备和我竞争,此时它输出的是一个显性电平(逻辑0,低电平),总线上的电平被拉成了显性电平(逻辑0,低电平),这时,总线检测到了输出与输入状态不一致,就会自动退出总线占用权的竞争。
1:使用LPC1754的CAN发送数据的时候,只有3个缓冲区,也就是说,当我们把数据写入到发送缓冲区中时,如果此时CAN总线上面有其他数据,且ID比当前任务的ID值优先级要高,那么这个数据暂时是发送不出去的。只有当总线空闲的时候,该数据才能够发送出去。所以CAN总线的发送邮箱一般有几个,(汽车级或者安全等级更高的微控制器更多,例如Infineon的XC2000系列、TI的安全控制器TMS570系列就有多达128个收发邮箱甚至更多)。
如果使用本收发例程,可以实现发送缓冲区可配置的功能,也就是可以定义这个发送缓冲区的大小,这就先进了很多。打个比方,如果现在CAN总线的负载率很高,我现在要发送一包数据出去,但是由于这个包的ID优先级不高,总是抢不到总线的使用权,CPU不用老停在这里等待CAN总线空闲,如果总线上100ms之内都是忙的状态,CPU岂不是要等待100ms,这在嵌入式系统中是不可容忍的。我们将它放入缓冲区,等待总线空闲以后将自动数据发送出去,这就将CPU“解放”出来了。
当然,硬件缓冲区比软件缓冲区要好的多,当缓冲区中有128个待发送的数据包的时候,硬件可以将这么多数据包中最重要的数据优先发送,而软件方式只能按照入栈的先后顺序发送(其实软件也可以做,但是麻烦)。
2:本例程是在LPC1754下,8M有源晶振,CPU运行在96MHz,CAN模块的时钟为24MHz,使用P0.0\P0.1作为CAN发送、接收管脚。
如果是其他管脚,需要重新配置。
3:本函数是使用中断的方式实现发送和接收,没有也不考虑使用等待和查询的方式,也不打算与这种方式兼容。写入的时间很短,在几个微秒以内,如果需要大量的发送数据,需要将缓冲区设大一点。
为了验证可靠性,作了几组实验,在最极限的情况下,测试是否有问题。
(1)极限发送,在超级循环中让CAN一直在发送数据,将发送缓冲区撑满,数据包ID每次自加1,同时数据包中数据也自加1,早CAN总线负载率为100%的情况下,发送了一个晚上。累计发送75384598帧数据包,数据的大小与ID号以及周立功CAN收发器接受到的数据一致。
(2)极限接收与发送,每10mS发送一次的连续10帧数据,ID自加1,微控制器收到这个数据以后原封发出给周立功CAN收发器。周立功CAN收发器一共发送了100000帧数据,也接收到了100000帧数据,没有错误。
3:使用下述情况进行收发:
A)100K、125K、250K、500K、800K、1000K的波特率的情况下进行发送和接收;
B)标准ID格式与扩展ID格式下的测试;
C)数据包分别为0,1,2,3,4,5,6,7,8的情况下的测试;
D)缓冲区大小分别为128、512、1024情况下的测试;
E)发送缓冲区分别为邮箱1、邮箱2、邮箱3,以及组合使用的情况。
现在我能想到的测试就只有这些了,没有发现问题,可能是没有加上其他负载的情况下,目前我还没有其他的板子,做了实验再挂上。
4:接下来我想测试一下在带上其他负载的情况,可惜现在没有装备,只有等到以后再测了。
mark.............. 最下面的两张图倒过来了 悲催,看不了图
明天再研究 mark..............
不错,这样深入性的研究是很有必要的。提高软件的效率和稳定性是开发的基本原则!学习了! 问下楼主,调试CAN时的工具用的是什么?方便吗啊??? 底层要强壮{:victory:} y574924080 发表于 2012-11-19 00:51 static/image/common/back.gif
最下面的两张图倒过来了
故意的,当时是放在Word文档中,为了达到分辨率最好。其实可以将一页纸变成横向的,忘记了。 多谢,下载看看!! 楼主真有心,分析好详细 缓冲区和RTX的邮箱有些相似的地方,很深入的研究 我用infineon的mcu,有很多报文,把数据送到报文里就不用管了,节点空闲时会自动发送,很方便。
我拆过台达的CANopen住站,也是用的infineon的mcu,infineon的mcu开发can通信还是很不错的,就是价格有点高! 正要学习CAN总线呢 mtswz.213 发表于 2013-1-9 22:25 static/image/common/back.gif
我用infineon的mcu,有很多报文,把数据送到报文里就不用管了,节点空闲时会自动发送,很方便。
我拆过台达 ...
I use Infineon for about one years,there is few people use Infineon MCU,because its price too high,but its very good and excellence. 非常感谢,学习中~~~~~~~~~~~~~~~~~~ 有时间学习学习,先mark下 LZ思路有问题,发个串口数据尽然能占用CPU 100%是不可能的
为什么要0.5秒发一次 而且一次发送完毕
为什么不能定时中断0.5ms(具体定时时间由串口波特率决定,一般定时器中断频率 = 波特率 / 14 )发送一次,一次发送一个字节,那几乎不占用CPU的
应该流程是0.5ms后定时器中断 ---> 检测上次数据是否发送完毕,如果发送完毕串口就绪,则拷贝下一个数据到位移寄存器,然后跳出中断,(期间只需要占用CPU 10个左右的指令周期,看MCU的指令效率了) -->继续其他操作,且等待定时器中断
LZ把等待位移寄存器的发送完毕的时间也计算进去了,这个操作时不占CPU时间的, ele_eye 发表于 2013-1-21 16:25 static/image/common/back.gif
LZ思路有问题,发个串口数据尽然能占用CPU 100%是不可能的
为什么要0.5秒发一次 而且一次发送完毕
楼上的,建议你好好看看我的思路,因为你没有明白我使用的什么方式。
我是用的中断+内存管理的方式下载的,DMA更好,数据直接搬运。之所以写这么多是因为移植方便,笔者在刻苦练习自己的编程风格,简洁、跨平台、通用、强大的功能块。
后来发现程序在多CAN节点且重负载率(超过60%)的情况下会有万分之几的丢包率,需要使用三级邮箱,这个问题已经找到,有时间挂上来。
LPC才3个,ST才2个,笔者使用的TI的安全控制器TMS570(军用级)和Infineon的汽车级的控制器,CAN邮箱有多达128甚至256个,真是一分钱一分货,呵呵。 果断收藏之 mark..............
MARK
................. mark。。。。。。。。。。。。。。。。。。。。。。。。。。。 谢谢分享!
标记下。 大神,膜拜! 收藏,多谢讲解! 非常详细..谢谢 多谢楼主分享 顶楼主好东西咯 非常好,让我学到了不少东西,刚开始学习STM32。 楼主辛苦。谢了。 记录一下 mark下。以后肯定用的到。 mark{:loveliness:} mark,留存后看 楼主搞出来的东西感觉都比较的有深度 。。。 请问楼主怎么练就的啊? 是否有借鉴的资料推荐啊。。。? 呵呵 楼主吧改好后的程序挂上来吧,谢谢了啊! mark........... mark...... {:smile:}{:smile:}{:smile:} lz 厉害谢谢分享 以前琢磨过总线管理问题,有人提出过令牌环方式 果断mark 谢谢分享! 图文并茂,容易理解 环形队列内纯管理 好贴 mark123456 mark一下 最后的两张图治好了多年的颈椎病 标记一下 大师啊{:lol:} 最后两个图片 碉堡额····环形队列动态内存····dma 标记一下 做个记号,有时间来拜读!
环形队列,内存管理,CAN。
最近想学学CAN苦于没有设备,只有一个STM32的开发板,怎么搞啊! market!!!!!!!!!!!!!!!! 好东西!! 记下了 学习 mark................ 这么好的帖子不mark不行啊 真是大牛人啊谢谢楼主分享 必须的,很不错的,帖子啊 mark..... 1754的can好像不支持dma方式? CAN 标记 不错的帖子,标记! 多谢分享~~ mark一下,貌似值得学习的样子 mark精华 谢谢分享的成果 顶楼主,谢谢分享 好资料,学习。。。 楼主真有心 记下{:handshake:} 收藏了 mark。。。。 学习了!! 谢谢学习一下 标记,只需三步完成动态内存+环形队列库+CAN驱动 不错,研究深入 mark........ 这个现场总线的方式一定要标记。然后学习之。 mark {:tongue:}CAN调试过程当中 是否有比较好的工具,1.保证不丢失数据包。有些CAN的工具会丢包的。2.数据缓冲够大,有些数据接收到一定程度就不更新了。3.携带测试方便,现场问题才是王道,所以携带测试方便的工具没有?
大家在调试工程当中有什么好的推荐的? linquan315 发表于 2013-2-19 22:47
后来发现程序在多CAN节点且重负载率(超过60%)的情况下会有万分之几的丢包率,需要使用三级邮箱,这个问题 ...
楼主,麻烦抽时间更新下修改后的程序啊,谢谢! 感谢,学习一下,谢谢! mark....... 马瑞卡 感谢分享 感谢分享 这个是后来经过修正以后的程序,在工程车辆上装机了大约几千台,前期在使用的过程中发现了一些小问题,前后修正了几次,发出来大家参考参考。
在这里学习了几年,论坛入门简单,做深的不多,在这里学习到了很多东西,现在也提供一些有价值的资料,如果想深入了解的可以参考一下。
这是其中一个文件的版本记录:
/*******************************************************************************
* 文件名 : CAN1.c
* 描述 : CAN的驱动函数
* 移植步骤 : 中间层函数
* 输入 : 无
* 输出 : 无
* 返回 : 无
* 版本信息 : 2012年11月18日完成第一版本,接收CAN总线采用专用的缓冲区,
使用动态内存分配,但是是环形的,同样的使用了头指针与尾指针,头
指针指向的是最新写入的CAN数据包,尾指针指向的是最后读出的CAN数
据包。
CAN缓冲区存放的是数据,没有存放CAN协议帧的ID、类型、数据量等,
发送的应用函数CAN1WriteDataToBuffer
接收的应用函数在CAN1RecvFun
CAN1QRecvTest中存放的是最近接收到的一帧数据,测试或者简单应用
的时候使用
* 修改日期 : 15/3/2013
此时发现了该版本的一个问题;
CAN1SendUpdate程序中:
if(CAN1RunningFlag==1)OSMemDelete(OSQCAN1Index,CAN1SendTCB.Index);
CAN1SendByte(CAN1SendTCB);
应该颠倒位置
为了版本管理的需要,本版本的程序将不更新
* 修改日期 : 16/3/2013
更新位置
CAN1SendUpdate程序中:
if(CAN1RunningFlag==1)OSMemDelete(OSQCAN1Index,CAN1SendTCB.Index);
CAN1SendByte(CAN1SendTCB);
为:
CAN1SendByte(CAN1SendTCB);
if(CAN1RunningFlag==1)OSMemDelete(OSQCAN1Index,CAN1SendTCB.Index);
* 修改日期 : 23/3/2013
1)在调用内存函数的时候,关闭了中断,使用 __disable_irq()与__disable_irq()函数
由于使用嵌套调用出现了问题,暂时使用该函数实现
2)同时对申请内存的错误进行了修正,
以前是:
CAN1SendTCB.Index=(unsigned char *)IOSMemGet(OSQIndex,&err);
现在是:
do
{
err=0;
index=(unsigned char *)IOSMemGet(OSQIndex,&err);
}while(err!=0);
CAN1SendTCB.Index=index;
本程序的作用是,如果申请不到内存的话,就一直申请,直到申请完成为止
这样避免申请不到内存以后发送失败
3)对于不是非常紧迫的SPI通信或者其他任务而言,如果申请不到内存,可以返回一个错误值,这样
应用程序就可以知道,启动重复发送命令。
4)在以前的时候,CAN1SendQFree在第一次申请内存以后,没有进行维护,本次修正
* 修改日期 : 17/7/2013
1)在做短路测试的时候,CAN总线关闭,导致无法进入发送中断,原先的CAN错误检测初始化在
发送中断中实现,故短路以后无法重现初始化(经过检查发现LPC的CAN设备在关闭超过6次以后
,直接从CAN网络上断开,此时使用CAN错误检测寄存器无法读取到CAN错误计数器的值),修正
的方法是,当CAN总线短路超过一定的时间以后,只要CAN总线还在发送数据,就会引起内存的
溢出(CAN无法正常发送数据了),当检查到内存溢出以后,直接启动CAN总线的复位就可以避
免出现无法恢复的情况。
2)CAN总线初始化的时候,将CAN发送的所有变量清零
*******************************************************************************/ 这是图片,好像不好发图片了 之前有做过串口的驱动,与之比较类似,但是只适用于某些领域,如果要使用在可靠性与交互性比较强的地方,还需要添加另外一部分。
同时也做了CAN的发送驱动,发送邮箱少的问题是可以软件解决的,动态分配内存以及发送链表都做好了,解决起来就很方便了,只要在结构中添加
一个钩子函数,和一个超时定时器,以及一个软件发送优先级(可有可无),检测到超时或者发送失败,再一次将该消息插入到发送队列的尾部就可以了 之前使用STM8测试过,不过由于从上一家公司离职,代码贴不上来了,除了软件优先级之外,实际上还有一个时间参数,汽车总线上的消息往往需要比较准时,所以在发送失败后,需要相应的优先级和时间参数来确定从新发送时到底应该放在发送队列尾部还是其他更合适的地方 恩,言简意赅,切中要害!
还要考虑短路,断路,误接高压电源的情况,这个程序是顺序发送的,没有加优先级 支持楼主的精神,楼主的时间跨度真大,12年的帖子,14年还在更新维护,这个不多见!{:hug:} 支持楼主,佩服楼主。 好专业! mark 好帖子,多谢LZ分享
页:
[1]
2