I2C 适配器 驱动 调试 总结

来源:互联网 发布:数据库防护系统 编辑:程序博客网 时间:2024/05/26 07:30

 

连续发送一个start和stop命令进行如下测试:

1.发送start命令,寻址一个存在的设备,收到了ack,然后发stop命令,一切正常,看到了stop波形。

2.发送start命令,寻址一个不存在的从设备,没有收到响应,发送stop命令释放总线,结果无法看到总线释放的波形。只看到SCL一直为低,SDA一直为高!

(以上测试中两个命令之间没有延时,如果要加延时,可以用打log代替,因为测试发现一个udelay大约为25ms,这个事件太长了。而打印一个字符约需要60微秒左右)

i2c adapter 发出start命令和slaver地址后如果没有收到slaver的响应,则导致adapter的TFE标志为0。也就是发送缓冲区一直有待发数据。看来当没有收到从设备的响应时这个标志是不会置1的

 

为什么在连续读取过程中总是读不到最后一个字节呢?

经测试,该问题在打开log的时候不会出现,所以基本可以确认应该是某处的延时不够所致

通过示波器观察波形可见其实最后一个字节已经被读出来了,其后也可见到stop波形,因此确定应该是在最后一次读取rx——fifo的时候最后一个字节还未进入fifo所致。因此在发出stop对发送缓冲区为空状态位进行判断,如果不为空则延时直到其为空,以此确保所读的内容都能进入rx fifo 这样就可以避免问题发生。

 

当发出一个start命令寻址一个存在的slaver,并且flag为read后,收到一个来自从设备的响应,然后紧接着发送一个stop命令,发现该stop并没有对应的波形产生。这导致总线一直繁忙,(状态为SDA保持为低,SCL保持为高)甚至在reset适配器后,总线还是繁忙状态,我开始怀疑reset是否发生了作用。甚至将主板reset都不行,只有将板子断电后才能解除繁忙状态。

这个该死的问题!

答案: 当寻址到一个从设备后,并且想要从该设备读取数据时,该设备会控制SDA线,在没有开始数据传输时,sda会被从设备一直拉低,总线便一直是繁忙的,从设备会以收到一个NACK作为释放SDA线的标志,如果你不给出这个NACK,那么除非将从设备reset或者断电才可以,要么你就将整个板子断电!好了,还有什么办法呢?当然了,你也可以从该设备读取一个字节并给以个NACK 然后再STOP,何必那么着急STOP呢。。。。呵呵!这样问题就解决了!

 

start+read之后如果没有寻址到从设备,则SDA为高,SCL为低  此后立即执行stop的话发现这个命令无法发出去,会一直放在TX——FIFO中!但是如果用soft reset适配器可以释放总线!

start+read之后如果寻址到了从设备,则SDA为低,SCL为低    此后不能立即执行stop,如果你执行stop,会发现没有什么结果,而且执行完了后会导致SDA为低,SCL为高!如果用soft reset适配器不能释放总线,我是这么来理解的:stop可以正确执行,并且会把两条线都拉高,但是从设备会立刻把SDA拉低,因为从设备没有收到释放总线的信号(一个NACK)此时出现总线死锁,唯一的办法就是读取从设备并给一个NACK然后再STOP

 

start+write之后如果没有寻址到从设备 则SDA为高,SCL为低 此时发stop发现无法发出,因为看到TX——FIFO一直非空(还可看到IC_TXFLR寄存器的值一直为1 )! 此时用soft reset可以释放总线

start+write之后如果寻址到了从设备   则SDA为高,SCL为低  此时STOP可正常发出!!!!!

 

前面还有一个问题困扰我N久,在I2C的适配器中有两个FIFO,分别是TX fifo和RX fifo ,前者用于缓冲所有送到适配器的待处理命令,包括读从设备的命令和写从设备的命令。针对我们目前正在做的这个适配器,就是那8条CMD命令,S0~S7  分别为:

S0:IDLE

S1:send start and one byte

S2:write one byte

S3:write one byte and send stop

S4:read one byte and send an ACK

S5:read one byte and send an NACK

S6:read one byte and send an NACK and send stop

S7:send stop

这8条命令中的任何一条都会被送到TX fifo等待处理,其中S4、S5、S6用于从slaver读取数据,当这三条中的任何一条被执行后会从slaver读到一个字节放在RX FIFO中,同时RX FIFO 的count计数器会增1,当从RX FIFO中读出一个数据后,该计数器会减1.

在驱动中我就是用该计数器作为我读取RX FIFO的标志,当RX FIFO缓冲区的数据个数超过设定的阈值后,我便会连续读取缓冲区,每读出一个值,我会检查该计数器,知道该计数器的值变成0为止。按照这样的思想,驱动可以正常的运行。但是又一次因为调试别的问题,我突然发现,在通过trace做单步调试的过程中,驱动竟然不能从slaver读到数据,而同样的代码在连续执行的时候是没有任何问题的,只有在单步执行时才会读不出数据,这很邪门!然后我就将示波器接上,发现根本不是读不出slaver的数据,因为每次单步执行S4、S5、S6中的任何一个命令的时候都看到有波形发生,而且是正确的波形!可见数据时能读出来的。那为什么驱动没有获得这些数据呢?单步跟踪发现,当驱动想要读取RX fifo时,会先检查上述计数器的值,当这个值大于0的时候说明fifo中有待读取的数据,然而单步的时候发现这个值总是0,程序便会认为fifo为空,从而放弃读取,难道是计数器出了问题,那刚才明明看到有波形出现,说明fifo当中应该有数据才对,那我可以越过计数器的检查,强行读取fifo试试看。TNND,fifo当中竟然真的没有数据!那数据到哪里去了呢?

还是同样的代码,我没有做单步调试,而是让程序一下跑完,结果还是完全正确,看来问题只能出现在单步上了。但是 单步执行和一口气执行在逻辑上是完全一样的呀,怎么会有读不出数据的问题呢?我开始怀疑是不是这个适配器在内部有什么别的机制能改变这个计数器?我能想到的单步和一口气执行唯一的差别就是单步的延时特征。为了验证这个想法,我在每条读取命令S4、S5、S6后都加了延时函数,这样便可模拟出单步的动作,我已经将延时时间设置到了3秒!然后分别测试单步和一起去执行,妈的,还是出现那样的问题,现在看来是只要上trace单步执行就会有问题,只要不用单步手段干涉,程序不管加不加延时,不管上不上trace都能正确执行!这trace的单步到底有什么猫腻?

……

一天以后。。。。

中午老板请客,又蹭了一次。。。呵呵,那干活就要更卖力了。。。。

在我写这个文档的时候我却突然想不起来我是怎么突然就想到了问题的症结的。。。。算了,不想了,直接给吃结论吧 

在用trace调试时我们可以看到所有的寄存器的值,这当然是trace从对应的地址读到的。而这里就包含了那个FIFO寄存器,当我们单步执行时,每走一步,trace都会刷新(读取)所有的寄存器一次,也就是没走一步,trace都会读取fifo寄存器,这就导致fifo计数器减1,并且fifo队首指针后移。也就是单步的时候fifo会被trace读走,我们的程序当然就读不到想要的结果了!

通过这个问题的解决过程我们会对trace这个东西又有了一个新的了解!其实trace真的的是个好东西!呵呵!

 

20110515补充:

昨天在测试中又发现了问题:在start寻址一个不存在的设备时,程序在第一次执行时正确,而第二次执行就会出错,根据log判断应该是第二次执行的时候程序走的逻辑和第一次不一样所致,这是不应该的,仔细排查后发现,程序第二次执行的时候走的逻辑是收到ACK的代码段,而观察寄存器发现此时NACK的标志已经产生,说明硬件方面是没有问题的,因为代码是在start命令发出后就会立刻对包含ack的那个寄存器快照,而在那个时候可能ack或者nack还没有建立,所以程序在后来判断ack状态时用以前的旧照片,当然就会出现错误。所以这个快照应该在ack建立后在做,那么问题是当start波形的前8个时钟脉冲(地址位和R/W位)产生后要等多久才会发生第9个(关于ack的)脉冲呢?考虑到这九个脉冲都是由master来控制的,也就是说master不会刻意延长第9个周期去等slaver!那就意味着第9个脉冲的周期会和前8个的周期一样,就i2c的频率的倒数,那么i2c的最高频率是400kHz,一般的设备都可以工作在至少100kHz以上,那么就按照100kHz来算吧,这样需要等待的最长时间就是10us左右。

在statr命令后设置该延时后,程序正常了!

 

20110519补充:

昨天一直在解决一个问题,在连续多次调用i2c传输函数时,发现两次调用之间总有100ms以上的延时,在仔细检查程序流程后发现代码中设置的延时不足以导致两次数据传输之间的延时现象,每一个完整的传输都是由一个start开头一个start结尾的,要说延时的话再数据传输过程中共有延时倒是可以理解的,但是现在 的延时是发生在上一个stop 和下一个start之间的,这就比较奇怪了,推测应该不是由适配器引起的,因为目前调试的是bsp-test代码,因为使用了中断和fifo模式,而该test是运行在单任务模式下的,因此为了模拟程序调度,我在代码中做了一个死循环来模拟本程序调出cpu,该死循环用了一个全局变量来终止循环,每次start发出后,便会调用这个死循环开始模拟放弃cpu,当i2c来了TX-FIFO为空的中断(或其他中断)后进入中断处理函数,在该函数中最终会有一次机会执行i2c的stop命令用来结束本次传输,此时会在中断回调函数结束前将上述全局变量修改,这样可以导致那个死循环终止,这个死循环是这么写的

 

static wait_flag = 1;

void i2c_wait_event_timeout(u32 count)

{

u32 i = 0;

do

{

mdelay(100);

if(i++ >= count)

break;

}while(wait_flag);

}

 

在中断回调函数中会将wait_flag置0,这样,当中断处理结束的时候会模拟出让cpu,继而该wait函数会继续执行,然后必须等到mdelay完全结束后才有机会退出。这就是问题所在。假如,mdelay要执行100ms,当mdelay在执行到1ms的时候发生了中断,此时会进入中断处理函数,后来中断处理函数结束后继续回到这个wait函数,当然要继续把剩下的99ms执行完了才会执行while,因此这时候就会产生延时,从外部来看就是我的stop波形已经送出了,但是数据处理函数( i2c_do_xfer(...))却还迟迟没有结束!

解决的方法很简单:将mdelay的参数给的小一些,比如1,或者干脆不要mdelay了,像下面那样:

static wait_flag = 1;

void i2c_wait_event_timeout(u32 count)

{

u32 i = 0;

do

{

if(i++ >= count)

break;

}while(wait_flag);

}

 

这种情况下只要把外部的参数(count)给的足够大,也依然可以达到需要的延时,而且能保证当中断退出(这里说的是最后一次中断中会调用stop会修改wait_flag为0)后这个延时函数也能毫无等待的退出!

我猜到今天为止,这个该死的i2c应该没什么大问题了吧。。。。呵呵

//=====================================================================

@201109011609

补充:

我一直很郁闷的一个问题就是为什么i2c-s3c2410.c中在中断处理函数中完成了所有的对i2c的操作。一直以为这样做是不合适的,因为我们一般都要求中断处理函数要尽可能的短小且快速退出,我们会把需要大量耗时的工作都使用类似工作队列的手段推到底半部去完成,以此换来系统较高的响应速度。当我看到2410在中断回调函数中完成了所有的功能后,我就郁闷了,要知道i2c的最大速度才400k,而CPU的速度都是几百兆的级别,如果在中断处理函数中完成i2c的读写操作的话,岂不造成严重的瓶颈?考虑到如果把G-Sensor这样的需要频繁调用i2c驱动的外设加上的话,那整个系统不是会慢成牛吗?就这个问题困扰我N久,因为我的i2c驱动就是参考这个驱动写的,所以这个问题我必须要解决!

今天我又一次开始想这个问题,对照2410的代码仔细研读,看来看去还是那样,的确,所有的读写动作都是在中断回调函数内部完成的。。。。。

突然,一道闪电。。。一下击中我的某个脑细胞。。。

我豁然开朗!中断处理函数中的确完成了所有的读写操作,可是这些读写操作不就是针对控制器的寄存器的读写操作吗?也就是对RX-FIFO和TX-FIFO的操作吗?这些操作不是转瞬就可以完成了吗?所谓的读写操作要么就是把RX-FIFO读空,要么把TX-FIFO写满,然后中断处理函数就结束了。这个FIFO才8级深,11位宽,读写时间都可以忽略不计了。而当中断处理函数退出后,真正耗时的数据发送和接收完全是有i2c控制器完成的。。。和cpu就没有关系了。。。。

终于想明白了,nnd,现在发现原来就是我一时忘了控制器本身是干啥的啦。。。 我竟然以为在中断处理过程中会发生数据的收发动作。。。大意啊 大意啊。。。有时候这脑子就是转不过一个小小的弯儿。。。。惭愧。。。真是岁月不饶哥哥呀。。。


原创粉丝点击