STM32的IIC实在太难用了,一个很简单的东西,ST的人把它弄得很复杂,不得不说STM32的IIC很鸡肋。
首先请大家不要吃惊,本文没有发错版块。上面这句话不是我说的,是《stm32不完全手册》中在讲述I2C时说的一句话。
同时也请STM的Fans不要鄙视我,我这里也不是在贬低STM32来衬托Stellaris。因为起初我也是不怎么同意他的这句话的(我还没有用过STM32的芯片,起初是准备从STM32入门的,但是申请到了8962的开发板,就转到LM3S下了),但是当我开始使用8962的IIC总线时,我也有了同样的感受。
这一切还要从一次不规范的引用示例程序说起。
前段日子准备山寨一个USB-Blaster。网络上有一个开源的项目叫做ixo-jtag,里面有使用CY7C68013做的USB-Blaster资料。我以前也买过一个同样的下载线,这次我懒得去分析编译里面的程序了,于是准备将我的USB-Blaster中EEPROM(24C64)中的程序读取出来。
本来这也不算什么事情,我以前也写过基于51的I2C总线程序,只不过很久不玩51了,电脑里51的编译器都没有安装。8962带有现成的I2C接口,站在巨人的肩膀上,这事还不是三五分钟的事吗。
好了,马上打开文件夹,找范例。呵呵,反正已经占到人家肩膀头上了,索性就叠个罗汉,也可以省下些调试时间。
很快就找到了,周立功的EasyARM8962光盘里面的实验例程4.12。该例程演示了对I2C存储器24C02进行读写操作,跟我的要求很对口,就从它上面改吧。原示例代码
这段代码我就不再详细解读了,其工作原理是读取进入中断时所处的操作状态,然后进行相应的操作,并判断下一步要进行的操作并设置下一状态。
其实刚一开始我也没有细细的读这段程序。因为如果只是应用的话,这些应该算是封装起来的操作。大家不需要关心,真正关心的是接口的操作。其操作接口则是在下面两个函数中:
void EEPROMWrite (unsigned char *pucData, unsigned long ulOffset, unsigned long ulCount);
void EEPROMRead (unsigned char *pucData, unsigned long ulOffset, unsigned long ulCount);
我们看一下其中的一个函数:
这段程序中所进行的操作非常少,只有两个,一是判断写入的是否只有一个字节,然后发送第一个字节。
在这里,我用的是24C64,其内部地址长度为两个字节,而示例程序用的是24C02,内部地址长度只有一个字节。这样问题就来了,我要多发送一个地址数据。怎么发呢?当时也是为了省事,直接在其发送第一字节后面又增加了发送一个字节的代码。结果可想而知,因为是采用的中断操作,所以在发送完第一字节后立即进入中断,后面的事情都由中断来完成了,如果此时主程序和中也有相应的操作的话,就会和中断中的操作相冲突,造成不可预料的后果。不过还好当时没有发现,才有了后来对I2C的进一步分析。因祸得福?
由于该程序是用库函数API写的,所以出现问题后首先想到的就是去查库函数,看看它都做了什么。
I2CMasterSlaveAddrSet和I2CMasterDataPut都没有什么问题,和预料中的操作一样,只有这个I2CMasterControl了。而该函数核心还是其命令常量的设置,如下:
#define I2C_MASTER_CMD_SINGLE_SEND 0x00000007
#define I2C_MASTER_CMD_SINGLE_RECEIVE 0x00000007
#define I2C_MASTER_CMD_BURST_SEND_START 0x00000003
#define I2C_MASTER_CMD_BURST_SEND_CONT 0x00000001
#define I2C_MASTER_CMD_BURST_SEND_FINISH 0x00000005
#define I2C_MASTER_CMD_BURST_SEND_ERROR_STOP 0x00000004
#define I2C_MASTER_CMD_BURST_RECEIVE_START 0x0000000b
#define I2C_MASTER_CMD_BURST_RECEIVE_CONT 0x00000009
#define I2C_MASTER_CMD_BURST_RECEIVE_FINISH 0x00000005
#define I2C_MASTER_CMD_BURST_RECEIVE_ERROR_STOP 0x00000005
解读这个命令编码还需要一个表格的帮助,那就是I2CMCS寄存器的说明
从这个表格中,可以看到,起作用的四位每位代表一种操作。本来是一种很普通的控制方式,每次设置一位来完成一个操作,简单明了。但是复杂就复杂在有些操作组合是必然的,处理器就替你封装了,而且不允许你独立进行操作。这些操作是一种怎样的组合方式呢,我们还是先看一下这个寄存器位的定义。
I2CMCS寄存器其实是两个寄存器,一个只读的状态寄存器和一个只写的控制寄存器共用同一个地址空间。在控制寄存器中,有四位可用,分别是:ACK、STOP、START、RUN。关于这四位的描述在Datasheet中是这么写的:
ACK:数据应答使能(Data Acknowledge Enable);当该位置位时,主机自动应答已接收的数据字节。
STOP:产生停止条件(Generate STOP);当该位置位时,产生停止条件。
START:产生起始条件(Generate START);当该位置位时,产生起始或重复起始条件。
RUN:I2C 主机使能(I2C Master Enable);当该位置位时,允许主机发送或接收数据。
不知道大家有没有注意到,上述四个位的描述中,有两个采用了使能(Enable)来作为其描述动词。使能在控制中意义是将一设备设置为活动状态,等到条件满足即执行,并非立即执行的意思。因此,这里的I2C总线的操作则是以命令序列的方式来执行的(当然序列里也可以只有一个命令),用一句PC里面的术语来说,采用的是批处理,以此来提高效率。
这样我们再来看前面这个表格的组合就不难发现,为什么没有产生起始条件的命令,因为起始之后肯定要发送或接收数据的,所以所以在所有的Start位设置时RUN位也必须设置。其他诸如ACK与STOP位互斥也是这个原理,表格中有些ACK位与STOP位不是互斥,那是因为在发送时ACK位是无效的。
此外对于表格中的ACK位是该I2C控制器管理中最智能化的,对于需要设置ACK的场合都是在I2C协议中没有明确规定是否使用ACK的场合,而在协议中明确规定有或者没有ACK的场合,ACK是可以自动产生的,这样也最大化的减小了错误的发生。比如I2C_MASTER_CMD_SINGLE_RECEIVE这个命令。在该表格中没有定义独立产生ACK的命令,这是因为ACK的产生是需要一定条件的,即接收完一个完整的数据。但是在接收结束时的最后一个数据接收完成时,是不产生ACK的,所以在命令表格中ACK与STOP位是互斥的,但是在单字节接收时又有一个特例,此时要产生ACK。所以在I2C_MASTER_CMD_SINGLE_RECEIVE这个命令中,虽然ACK位为0,但是依然会产生ACK,这个就是由处理器自动完成的了。
那么如何手动产生ACK呢?前面说了,ACK是在接收完一个字节后产生的,如果你要手动产生的ACK位置无法设置自动产生ACK,那么你就将该ACK前的字节用I2C_MASTER_CMD_SINGLE_RECEIVE命令来接收。
那么上面一段又产生了一个新的问题,如果这是在一个序列中的一个过程而不是读取单个字节呢?那么就要研究一下“重复起始条件”这个过程了。其实在I2C中对于起始条件和结束条件的要求很不严格,起始条件就是通知从器件占据总线,而结束条件则是要求从器件释放总线,在这中间的起始条件则没有限制。I2C的写操作总是要先指定写入地址然后写入数据,则是一种完全的随机写入方式。而读取则只有一种,当前地址读取,所有的读取操作都是建立在这一基础之上的,这就是为什么在读取之前先要有一个虚拟写入操作。而在读取过程中可以随时中断然后继续读取,都不会有影响。
当然上面的手动发送ACK情况大家应该是找不到特例的,因为I2C总线协议已经很全面了,基本上需要考虑的情况已经完全考虑了,但谁也无法保证滴水不漏,所以才有了上面那些特殊的操作方式。
好了,关于I2C的操作今天就先说这些吧,接下来将对ZLG的程序做一些修改以扩展其应用范围。
本来想对ZLG的4.12例程进行一些扩展,不过后来在EasyARM8962光盘里发现ZLG早已写好了一个完整的软件包,我这里就不再献丑了,函数的用法在头文件里面有说明。
不过对4.12进行改造也有一定的必要,那就是其中断程序比较简单,中断运行时间也较短。
对其改造的方法是在主程序中不要打开I2C中断,在读写子程序中,在执行完最后一条地址写入命令后打开中断。同时子程序退出时关闭中断。
我在应用中用的就是这种方法,想知道我山寨USB-Blaster的成果吗?
我在空白24C64芯片上调试完成读写程序后(串口回报数据完全正确),把USB-Blaster上的24C64连上开发板,准备改成只读程序后读出程序。插上电后看到程序运行指示灯闪烁(我增加的读写指示灯)心里大叫糟糕,芯片中原来的写入程序还在
就这样,我的USB-Blaster挂掉了
我在连接有程序的24C64的时候为了防止误操作,还把WP通过一个电阻上拉了,但是还是擦除了程序,后来查看了芯片手册,原来其保护功能只是保护的最高四分之一的存储区域。手册中用了quandrant这个词,查了一下,这个词意为“象限”,不知道怎么跟四分之一联系上的,难道仅仅是从一个平面有四个象限引申出来的意思。