您现在的位置是:首页 > 汽车

汽车ECU标定系统CAN驱动模块的实现

2019-12-31 18:26:40

1  前言

标定是指根据整车的各种性能要求(如动力性、经济性、排放及辅助功能等),来调整、优化和确定整车上各ECU(如发动机、AT等各子系统 ECU)控制参数的控制算法。标定系统主要是由上位机和底层ECU这二部分组成,因此,上位机和底层ECU的通信方式对整个标定系统的性能起到了至关重要的作用。目前,一般的标定系统都是采用基于串行口的点对点的通信方式,这种通信方式容易实现,但存在着通信速度较慢、可靠性较低等缺陷。这里我们采用的是CAN总线的通信方式,相对串口通信,基于CAN总线的通信方式具有通信可靠[1]、传输速度快、可实现在线编程等优点。

2  总体设计

CAN通信可视为系统的一个I/O字符流设备[3],它在完成普通收发功能的同时,还要能实现驱动程序必备的设备无关性。即驱动程序应将系统所有的硬件特性封装起来,为使用该设备的应用程序提供与硬件无关的、通用的编程接口,应用层程序编写人员无需了解设备的原理,即可顺利实现对设备的控制,通过该设备实现可靠的数据交换。另外,针对CAN通信和嵌入式系统的实时性要求,该驱动程序要求收发数据代码可靠,延迟短,占用系统时间短,中断执行时间短,关闭中断时间短,并在收发错误和发生异常情况时,向应用程序汇报。另外,该驱动程序需要监控CAN控制器的工作状态,在出现致命错误和脱离总线时,为CAN模块复位,并向系统汇报。

图1 驱动程序总体结构图

基于以上需求分析,结合其他OS中实现I/O串行设备的驱动方案及CAN的总线要求特点,设计总体驱动程序结构如图1。   

3  CAN驱动模块的实现

基于以上总体设计框架,首先定义一个CAN类来封装CAN通信中的数据结构和函数,最下面一层为中断级程序,中断处理程序在每次CAN控制器完成收发时,唤醒驱动程序,进行下一步工作。在中断处理程序中,根据不同的中断向量来确定当前发生的是发送完成中断还是接受完成中断,并完成相应工作。中间一层为底层驱动程序,底层驱动程序主要是通过对CAN控制器寄存器的读写,完成对CAN端口的配置和状态检测等工作,同时为设备无关软件和用户程序提供接口。在这一层中,必须要建立一个环状缓冲结构,该缓冲由一个接收环状缓冲区和一个发送环状缓冲区组成,其数据结构如下代码所示,对于每个环状缓冲区,设计了一个存入指针指向下一个待存入CANMsg的存入地址,一个读出指针指向缓冲区下一个待取出的(最旧的)CANMsg的地址,一个计数器记录目前缓冲区中有多少个CANMsg待取出,一个信号量,用于与应用程序交换消息。接收环状缓冲区用于缓冲接收到的总线消息,等待应用程序处理,发送环状缓冲区用于缓冲应用程序发送出的消息,等待发送中断程序来处理。

typedef struct{   //环形缓冲区的数据结构

INT16U   RingBufRxCtr;       //接收计数器

OS_EVENT *RingBufRxSem;    //信号量

CAN_msg  *RingBufRxInPtr;    //接收缓冲区的存入指针

CAN_msg  *RingBufRxOutPtr;  //接收缓冲区的读出指针

CAN_msg  RingBufRx[CAN_RX_BUF_SIZE];  //接收缓冲区的消息存储

INT16U   RingBufTxCtr;  //发送计数器

OS_EVENT *RingBufTxSem;

CAN_msg  *RingBufTxInPtr;    //发送缓冲区的存入指针

CAN_msg  *RingBufTxOutPtr;  //发送缓冲区的读出指针

CAN_msg  RingBufTx[CAN_TX_BUF_SIZE];  //发送缓冲区的消息存储

}CAN_RING_BUF;

 3.1 底层驱动

底层驱动模块为我们应用程序提供了接收和发送消息的接口函数。

图2  CAN接收消息

当接收消息时[3],如图2所示,应用程序在信号量处等待;收到一个消息后,ISR从串行端口读入消息,将其存入环型缓冲区。然后ISR发出信号量,通知在等待串口数据的任务已收到一个消息。等待任务收到信号量后,进入就绪状态,准备被OS调度器激活。当内核调度该任务运行时,该任务从环状缓冲区中取出消息,完成接收消息的过程。

void CAN_GetMsg(CAN_msg *msg){

INT8U   oserr;

OS_CPU_SR cpu_sr;

CAN_RING_BUF *pbuf;

pbuf = &ringbuf;

OSSemPend(pbuf->RingBufRxSem,0,&oserr);   //等待信号量

OS_ENTER_CRITICAL();//关中断

pbuf->RingBufRxCtr--;//接收计数器减1

CopyMsg(pbuf->RingBufRxOutPtr++,msg); //从环形缓冲区中取出信号量         

if(pbuf->RingBufRxOutPtr==&pbuf->RingBufRx[CAN_RX_BUF_SIZE]) {pbuf->RingBufRxOutPtr= &pbuf->RingBufRx[0];

//如果环形缓冲区的读出指针达到缓冲区的最末端,将其改为指向缓冲区的首地址  }

OS_EXIT_CRITICAL();  //开中断,允许CPU响应中断   }

发送CAN消息与接受消息类似。后台进程将欲发送的消息帧存储于发送环状缓冲区中。当CAN端口准备发送一帧消息时,产生一个中断,CAN消息从缓冲区中取出,并由ISR输出[4]。但其中出现了一个问题:CAN端口只能在发送上一个数据结束的时候才会产生一个中断,这个产生中断的时刻与我们需要执行中断任务的时间是不一致的。解决这个问题的方法就是,禁止发送端中断使能直到需要再发送消息为止。在系统启动时,禁止发送中断,发送一个启动消息帧,这时发送完成中断标志位已经被置位,但由于发送中断使能位为低,所以无法发生中断,系统继续执行。当需发送第一个消息时,将该消息放入发送环状缓冲区,然后运行发送中断,这时,上一次发送消息完成中断产生,发送该消息。在发送消息结束时,若发送环状缓冲区中有其他数据需要发送,则清中断源,等待该消息发送完成中断产生,来发送下一个消息,若没有其他数据需要被发送,则直接禁止发送中断,将该消息发送完成时产生的中断保留到下一次有消息需要发送时发生。

图3  CAN发送消息

发送消息的方法如图3。当发送环状缓冲区已满时,信号量作为指示,暂停发送任务。发送消息时,任务等待信号量。如果环状缓冲区未满,则任务继续向环状缓冲区存储欲发送的消息。如果存储的消息是缓冲区第一个字节,则发送中断允许,中断程序准备启动。CAN发送ISR从环行缓冲区中取出最旧的消息,同时发送信号量,通知发送任务,表明环状缓冲区有空间接收另外的消息,接着ISR将消息从发送到总线上。其实现代码如下所示:

void CAN_PutMsg(CAN_msg *msg) {

INT8U oserr;

OS_CPU_SR cpu_sr;

CAN_RING_BUF *pbuf;

pbuf = &ringbuf;

OSSemPend(pbuf->RingBufTxSem, 0, &oserr);   //等待信号量

OS_ENTER_CRITICAL();//关中断

pbuf->RingBufTxCtr++;   //发送计数器加1

CopyMsg(msg, pbuf->RingBufTxInPtr++);  //将消息放入环形缓冲区

if(pbuf->RingBufTxInPtr==&pbuf->RingBufTx[CAN_TX_BUF_SIZE]) {pbuf->RingBufTxInPtr=&pbuf->RingBufTx[0];

     }

if (pbuf->RingBufTxCtr==1) {

     CAN_TxIntEn();//为环形缓冲区的第一则消息,开发送中断

     }

     OS_EXIT_CRITICAL();

}

3.2 中断服务程序

根据前面谈到发送和接收消息的软件结构,在CAN初始化时就要求CAN的接收中断处入开启状态,而发送中断仅仅是在发送缓冲区里面有了第一则消息后再开启的,因此在这里设计两个接口函数,CAN.TxIntEn()和CAN.TxIntDis(),分别将发送屏蔽位置1(允许发送完成中断)和置0(禁止发送完成中断)。

图4  发送接收中断程序流程图

中断级程序的核心就是CANRX_ISR()和CANTX_ISR(),它们由初始化时对该模块的中断设置寄存器设置的中断级别。如图4所示,若为接收完成中断,则清除中断源,将接收到的消息放入接收缓冲区;将该消息存入接收缓冲区存入指针所指向的地址,将该指针向下移动,接收缓冲区计数器加1,并发出信号量通知应用程序有新的消息已经接收到,若有任务正在等待CAN上的新消息,则该任务进入就绪状态等待OS的调度。若为发送完成中断,则将发送缓冲区的待发送消息读出;将有待发送消息且优先级最高的一个中读取最旧的消息(缓冲区取出指针所指向的消息),发送缓冲区计数器减1,发出信号量通知应用程序有一个消息被发出,并汇报当前发送缓冲区的状态;还应判断是否为最后一个待发送的消息,若不是,则清除中断源并将消息发送到总线上,若是最后一个,则禁止发送完成中断后发送该消息,将这个发送完成中断保留到应用程序下一次发送消息的时候允许并产生。

3.3 应用

该驱动程序的应用,如下代码所示,这里使用的是uCOS-II,首先定义一个CAN消息对象(msg)和一个环状缓冲区数据结构(CANRingBuf),在主程序中,初始化OS以后调用Ringbuf_Init()函数初始化环形缓存区,然后调用CAN_Init()函数初始化CAN端口。在启动OS后,用户就可用在任何任务中调用CAN_PutMsg(CAN_msg *msg)和CAN_GetMsg(CAN_msg *msg)发送和接收总线消息了。

CAN_msg msg;

CAN_RING_BUF CANRingBuf;

void main(void) { 

OSInit();

Ringbuf_Init();

CAN_Init();

/* Creat task1 */

       OSStart();    }

void task1 (void * data)

{     CAN_PutMsg(&msg);

CAN_GettMsg(&msg);

}

4  结束语

通过改变芯片总线频率、CAN通信速率这样多次反复不断的调试,此CAN驱动在实时操作系统上运行稳定可靠,未出现数据丢失,较好的实现了上位机与ECU的通信,因此,具有很强的实用价值。