硬汉嵌入式论坛

 找回密码
 立即注册
查看: 7821|回复: 1
收起左侧

[客户分享] 只需三步完成动态内存+环形队列库+CAN驱动(LPC17XX)

[复制链接]

2

主题

3

回帖

9

积分

新手上路

积分
9
QQ
发表于 2014-8-3 16:16:34 | 显示全部楼层 |阅读模式
首先看一组数据,
一般数据收发可以分为三种方式:
(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的方式都是首选。




8.JPG (67.94 KB, 下载次数: 0)下载附件
2012-11-18 23:39 上传



9.JPG (69.03 KB, 下载次数: 0)下载附件
2012-11-18 23:39 上传



10.JPG (65.1 KB, 下载次数: 0)下载附件
2012-11-18 23:39 上传

<一>CAN收发+动态内存+环形队列库的使用方法
闲话少说,最近一个信号采集板要使用CAN总线,LPC1754的平台,移植了这个功能。
首先介绍使用方法,很简单,只有三步:
1:工程中包含这几个函数



1.JPG (4.57 KB, 下载次数: 0)下载附件
2012-11-18 23:41 上传
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总线是设备互联和组网的理想选择。



2.JPG (78.3 KB, 下载次数: 0)下载附件
2012-11-18 23:41 上传
正是如此,CAN总线在大量应用在的工控、自动化、航空、安全领域。



3.JPG (181.87 KB, 下载次数: 0)下载附件
2012-11-18 23:41 上传
有的人可能会以为,相对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,低电平),这时,总线检测到了输出与输入状态不一致,就会自动退出总线占用权的竞争。



4.JPG (205.35 KB, 下载次数: 0)下载附件
2012-11-18 23:41 上传



5.JPG (54.88 KB, 下载次数: 0)下载附件
2012-11-18 23:41 上传
  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收发器接受到的数据一致。



6.JPG (694.28 KB, 下载次数: 1)下载附件
2012-11-18 23:41 上传

(2)极限接收与发送,每10mS发送一次的连续10帧数据,ID自加1,微控制器收到这个数据以后原封发出给周立功CAN收发器。周立功CAN收发器一共发送了100000帧数据,也接收到了100000帧数据,没有错误。



7.JPG (679.18 KB, 下载次数: 1)下载附件
2012-11-18 23:41 上传
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:接下来我想测试一下在带上其他负载的情况,可惜现在没有装备,只有等到以后再测了。

这个是后来经过修正以后的程序,在工程车辆上装机了大约几千台,前期在使用的过程中发现了一些小问题,前后修正了几次,发出来大家参考参考。
在这里学习了几年,论坛入门简单,做深的不多,在这里学习到了很多东西,现在也提供一些有价值的资料,如果想深入了解的可以参考一下。

这是其中一个文件的版本记录:

/*******************************************************************************
* 文件名         : 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[CAN1SendQBoxPre].Index);
                   CAN1SendByte(CAN1SendTCB[CAN1SendQBoxTail]);
                   应该颠倒位置
                   为了版本管理的需要,本版本的程序将不更新
* 修改日期         : 16/3/2013   
                   更新位置
                   CAN1SendUpdate程序中:
                   if(CAN1RunningFlag==1)OSMemDelete(OSQCAN1Index,CAN1SendTCB[CAN1SendQBoxPre].Index);
                   CAN1SendByte(CAN1SendTCB[CAN1SendQBoxTail]);
                   为:
                   CAN1SendByte(CAN1SendTCB[CAN1SendQBoxTail]);
                   if(CAN1RunningFlag==1)OSMemDelete(OSQCAN1Index,CAN1SendTCB[CAN1SendQBoxPre].Index);
* 修改日期         : 23/3/2013   
                   1)在调用内存函数的时候,关闭了中断,使用    __disable_irq()与__disable_irq()函数
                   由于使用嵌套调用出现了问题,暂时使用该函数实现
                   2)同时对申请内存的错误进行了修正,
                   以前是:
                   CAN1SendTCB[CAN1SendQBoxHost].Index=(unsigned char *)IOSMemGet(OSQIndex,&err);
                   现在是:
                   do
                    {
                        err=0;
                        index=(unsigned char *)IOSMemGet(OSQIndex,&err);
                    }while(err!=0);
                    CAN1SendTCB[CAN1SendQBoxHost].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发送的所有变量清零
*******************************************************************************/
LPC1758_CAN.rar (18 KB, 下载次数: 217) LPC2119_CAN.rar (13 KB, 下载次数: 186)
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106726
QQ
发表于 2014-8-3 18:48:39 | 显示全部楼层
感谢楼主分享,加精
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-5-1 17:26 , Processed in 0.207632 second(s), 28 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表