以前做项目,不注意记录调试过程中遇到的问题,以后应该注意这一点。今天抽空总结一下PCI-CAN卡驱动与数据通信调试过程中遇到的问题,方便以后回忆和思考。

1. 中断服务之字节流报文组包状态机

这是一个典型的适合采用状态机来思考和处理数据的场合。报文一般分为这几个字段:报文头,长度,命令,数据,校验和。在报文接收端,能看到的只是一连串的字节,这需要状态机的控制。

状态机分这几个状态:(1)接收报文头;(2)接收报文长度字段;(3)接收剩余部分,以报文长度作为跳出判断状态条件;(4)校验报文;(5)数据加工存储,组包完成。

  
                            图1 状态机之报文组包

在最初的代码中,没有用状态机的思想,比较混乱。在使用状态机的过程中,注意状态转移条件和计数器。

2. 双端口RAM数据读写过程分析

为了保证数据的完备性,双端口RAM要求PCI和MCU不能在同一时刻读写同一个memory uint,为此双端口RAM提供了独占使用的Sepahores,特殊的电路设计保证了Sempahores被一端写入0后,另一端就会写入0失败。通过Sepahores协调MCU和PCI对RAM的同时Read/Write操作。

将双端口RAM作为循环队列,在RAM的某个指定地址放置读写指针。以PCI向MCU下行数据为例说明:循环队列缓冲区从0~0x0FFF,0x3FE0放置MCU读指针,0x3FE4放置PCI写指针,指针占用4个字节。PCI写指针缩写pWR, MCU读指针缩写pRD.

如图2所示,当pRD、pWR递增到0x1000时自动复位到0x0000(队列缓冲区不包含0x1000),pRD和pWR在队列的位置分图2所示的两种情况。循环队列的深度是0x1000个字节,当写入的字节总数 - 读出的字节总数 > =0x1000时,则发生队列溢出,造成数据丢失。

在情况1时,pWR >= pRD,pRD~pWR之间灰色RAM区域,是等待被读出的有效数据。读取数据的字节数 = pWR-pRD;

在情况2时,pWR < pRD,pRD~0x1000, 0~pWR之间灰色RAM区域,是等待被读出的有效数据。读取数据的字节数 = pWR+0x1000-pRD。

  
                         图2 循环队列读写过程分析

如果将循环队列的头尾假想的连续起来,可以把队列想象成无限长度,那么永远满足:pRD < pWR,换句话说,读操作发生在小于pWR的区域,写操作发生在大于等于pWR的区域,所以我们可以不用担心读写数据区域的重叠(?),只需关心pWR指针本身读写的完备性。所以,PCI写数据和MCU读数据的过程是这样的:

PCI写数据过程:
     (1) 从RAM中读pWR;
     (2) 向 >= pWR区域写入数据,此时pWR保持不变,维护1个局部的字节计数WRCnt;
     (3) 获取锁/Sepahore;
     (4) 在RAM中更新pWR, pWR = pWR+WRCnt;
     (5) 释放锁/Sepahore;
     (6) 向MCU发中断。

MCU读数据过程:
     (1) 获取锁/Sepahore;
     (2) 从RAM读出pWR;
     (3) 释放锁/Sepahore;
     (4) 从RAM读出pRD,计算应该读出数据的字节数;
     (5) 循环读数据,此时pRD保持不变,维护1个局部的字节计数RDCnt;
     (6) 在RAM中更新pRD, pRD = pRD+RDCnt (或 pRD = pWR)。

细心的你可能会发现,我在上文中标注了1个红色的问号。这里存在了一种假设,FIFO深度足够大,读写数据的速度足够匹配。只有在满足该假设的情况下,我们才不用担心数据区域的重叠。当此假设不满足时,很可能存在这样一种情况,pWR马上就要追上pRD(追上!!不是pRD<pWR么?不过这里说的没错,如果你不赞同,尝试理解一下我的意思),此时读写数据就会重叠。这种情况下,怎么办?这需要进行写操作是清楚的知道pRD在哪里,如果必要的话,增加pRD(快点快点,我都快追上你了,踢你一脚,别挡我的路!哈哈)。在写入数据前,判断pWR==pRD?满足该条件时,递增pRD。这样会降低效率!!!

很幸运,我们的系统满足这个假设,所以就不用进行这种复杂的操作啦。很多时候,一个系统设计的并不完美,存在逻辑上的漏洞,可是如果简单简洁本身就是一种美妙的事情,那么我们用这个漏洞去换取这份美妙,也很棒。

3. PCI本地总线写RAM失败现象

像你说话的时候,牙齿竟然咬到自己的舌头。ou, my god!不过这确实发生了。

引用PCI local bus spec. “This bridge provides a low latency path through which the processor may directly access PCI devices mapped anywhere in the memory or I/O address spaces”.是的,我们的双端口RAM映射在CPU的memory空间,理论上说,CPU读写双端口RAM与CPU读写内存之间不存在任何区别,我以前从来没有怀疑过CPU竟然会读写内存失败,所以这个问题一直隐藏的很深,让我很费解很费解,为什么竟然丢数据丢的这么莫名其妙!

让我们分析一下,双端口RAM的读写时序,如图3所示。

写数据:EN#是RAM使能信号,此后RAM在不需要任何驱动的情况下,其内部逻辑自动定位到ADDR指示的memory uint;WR#是写RAM选通信号,触发RAM内部电路对DATA进行锁存,并将数据写入ADDR指示的memory unit。在WR#选通时,需要保证DATA和ADDR的信号时稳定的,也就是需要在ADDR/DATA有效与WR#选通之间有latency,可能是1或几个clk.

读数据:与写数据逻辑相反,DATA是输出,EN#选通后,RAM将数据放置到DATA线上,确保DATA和ADDR是稳定时,选通RD#。


                       图3 双端口RAM的写时序和读时序

我们在看看9030本地总线的读写时序,为了准确,直接截图9030 datasheet.


                      图4 9030本地写时序


                       图5 9030本地读时序

已经很清楚了, 注意Write Strobe Delay和Read Strobe Delay,这两个值是9030的EEPROM设置的。


                   图6 9030 EEPROM space descriptor典型值

参考9030 data sheet的典型值,设置EEPROM,复位9030芯片。从此之后,再也没有碰到CPU读写双端口RAM失败。难道牙齿再也不会咬到舌头了么?我不是很确定,但很久没有咬到了。

4. 谨慎对待计数器的位宽

问题:函数RecvFunc从缓冲区读取数据,返回读取数据的个数。函数内部设置1个计数器,返回此计数器的值。很不小心,这个内部计数器被设计为8位宽,而我读取数据个数有可能超过0xFF,导致经常发现数据丢失?

这个问题很简单,也很隐藏。当一个系统存在多个原因导致数据通信丢失时,这个不起眼的问题最初会掩盖掉重要的问题所在,致使问题复杂化。单独列出来,是为了警告自己,不要犯类似的错误。

5. 谨慎对待数组下标

问题:从报文中获取某个字段,并将该字段作为数组下标使用。起初,我考虑到数据通信失败时,数组下标越界的可能性,但是想到经过报文校验,应该不会发生吧。不过确实发生了,导致非法的内存访问,程序崩溃退出。

问题虽然很简单,很幼稚,缺经常发生。

6. 使用try-catch块避免程序自动崩溃结束

在遇到问题5这样的情况,程序会直接被OS结束掉。这给会造成极其恶劣的用户体验,一定要避免。设计的程序要具备一项性能,不关用户怎么操作,可以向用户输出失败,但决不允许莫名其妙的跑飞,崩溃。

可是貌似不管你怎么细心的设计,程序还是可能在不起眼的地方出现意外。C++中有一项重要的机制,try-catch块,windows也实现了结构化异常机制。请恰当的使用这些机制!

7. 接收数据缓冲区/FIFO留足够空间

数据通信丢失报文的一个重要原因,可能是FIFO深度不够。测试FIFO深度够不够,可以设置一个FIFO深度使用的最大计数。

8. windows系统偶发性的时延,数据挤压而在某个小的时间段喷涌数据

数据通信丢失报文的另一个重要原因,可能是FIFO深度已经很深的情况下,数据突发性的向总线喷涌,而总线带宽有限,这时就需要更大的FIFO深度。我们的测试程序运行在xp环境下,VC编写。windows应用程序基于消息框架,每个进程和线程都有自己的消息队列。我们的测试程序使用多媒体timer,每隔1ms产生一次TimerHandler调用。这一过程可以这么理解,windows每隔1ms向TimerHandler消息队列发1个timer消息,如果消息没有及时处理(windows很复杂,干着很多让你摸不着头脑的事情,所以不要奢望每个消息发出后,繁忙的windows都会把宝贵的CPU及时交给TimerHandler),那么消息队列就会变得很长。例如某个100ms时间段,TimerHandler一直没有得到调用,其消息队列堆积了100个timer消息,然后CPU使用权终于交给TimerHandler了,TimerHandler迫不及待的的把堆积的100个消息,恨不得在1ms都给处理了。所以数据从windows向洪水泄闸一样涌向RAM,又从RAM涌向MCU,MCU不停的往内部FIFO灌报文数据,而CAN总线最高只有1Mbps的数据带宽,即使CAN控制器以最快的速度发,MCU内部FIFO也会很可能堆满数据,造成FIFO溢出,报文丢失啦,就像长江洪水一样。

这只是我们的假想,能不能实际看看呢?通过逻辑分析仪采集中断信号,验证了我们的结果。

PCI-CAN卡驱动与数据通信调试小记的更多相关文章

  1. dialogic d300语音卡驱动重装后启动报错问题解决方法

    dialogic d300 驱动重装后 dlstart  报错解决 问题描述:dlstart  后如下报错 [root@BJAPQ091 data]#dlstop Stopping Dialogic ...

  2. S3C2416裸机开发系列十六_sd卡驱动实现

    S3C2416裸机开发系列十六 sd卡驱动实现 象棋小子    1048272975 SD卡(Secure Digital Memory Card)具有体积小.容量大.传输数据快.可插拔.安全性好等长 ...

  3. tiny4412 --Uboot移植(6) SD卡驱动,启动内核

    开发环境:win10 64位 + VMware12 + Ubuntu14.04 32位 工具链:linaro提供的gcc-linaro-6.1.1-2016.08-x86_64_arm-linux-g ...

  4. vmware esxi 查看网卡、Raid卡驱动

    vmware esxi 查看网卡.Raid卡驱动 http://blog.51cto.com/adamcrab/1942763 查看网卡 [root@localhost:~] esxcfg-nics  ...

  5. 全网最新Kali Linux系统如何安装N卡驱动

    转载请注明来源:全网最新Kali Linux系统如何安装N卡驱动[亲测-暗影精灵3-1050TI有效] - 大家好,我系渣渣辉 https://www.zzhsec.com/255.html 1.更换 ...

  6. sd 卡驱动--基于高通平台

    点击打开链接 内容来自以下博客: http://blog.csdn.net/qianjin0703/article/details/5918041 Linux设备驱动子系统第二弹 - SD卡 (有介绍 ...

  7. SD卡驱动分析(二)

    三.下面分析一下高通的android2.3的代码中SD卡驱动的流程. 在kernel中,SD卡是作为平台设备加入到内核中去的,在/kernel/arch/arm/mach-msm/devices-ms ...

  8. SD卡驱动分析(一)

    Android下的SD卡驱动与标准LINUX下的SD卡驱动好像没有太大的区别,这里就以高通的ANDROID 2.3以代表,来简要分析一下LINUX下SD卡驱动的写法.由于小弟的技术有限,分析的有错的地 ...

  9. windows server 2008 集成raid卡驱动

    给服务器安装2008系统,一般都需要通过引导盘和操作系统盘来进行安装,安装过程比较繁琐时间也比较长,于是就想做一个集成了服务器驱动的2008系统盘,这样就可以直接用光盘安装,简单方便,第一步需要解决的 ...

随机推荐

  1. 并不对劲的bzoj1972:loj2885:p2482[SDOI2010]猪国杀

    题目大意 只能放链接了. 题目中有一点没说:并不是保证牌够用,而是在牌不够用时反复抽最后一张牌. 题解 发现玩家的数量比较少,所以可以不太在意时间够不够用. 考虑三件事:1.基本操作,如摸牌.出牌.玩 ...

  2. PyQt5创建多线程

    参阅: https://blog.csdn.net/chengmo123/article/details/96477103 https://www.cnblogs.com/zhuminghui/p/9 ...

  3. Struts2 流程原理

    一.流程图 (转) 二.流程详解 1.服务器传递来的请求,通过ActionContextClearUp.other filters.最后到达StrutsPrepareAndExecuteFilter ...

  4. winfrom 点击按钮button弹框显示颜色集

    1.窗体托一个按钮button: 2.单击事件: private void btnForeColor_Click(object sender, EventArgs e) { using (ColorD ...

  5. VUE神速搭建项目

    1.npm install -g vue-cli 全局安装vue-cli脚手架 2.vue init webpack vueTest 初始化一个基于webpack的项目 3.cd vueTest 进入 ...

  6. ubuntu - 14.04,安装docker(源代码管理工具)

    一,安装docker: 1,安装curl:在shell中执行:sudo apt-get install curl 2,shell中执行:curl -sSL https://get.daocloud.i ...

  7. lumen时区

    今天用 Lumen 框架写代码时, 也是初次体验 Lumen, 遇到了一个问题, 从数据库里查出的时间比数据库里保存的 TIMESTAMP 时间慢了8个小时, 很明显这是一个时区设置的问题, 本以为可 ...

  8. Flink 1.0到1.9特性

    Flink API 历史变迁 在 Flink 1.0.0 时期,加入了 State API,即 ValueState.ReducingState.ListState 等等.State API 可以认为 ...

  9. 第十章、random模块

    目录 第十章.random模块 第十章.random模块 #随机生成0-1之间的小数 import random print(random.random()) print(random.randint ...

  10. (九)How to use the audio gadget driver

    Contents [hide]  1 Introduction 2 Audio Gadget Driver 1.0 2.1 Enabling the audio gadget driver 2.2 U ...