社区导航

 

搜索
查看: 11661|回复: 17

[原创] 【TI原创】LM3S使用笔记之I2C总线(一)

[复制链接]

208

TA的帖子

0

TA的资源

纯净的硅(高级)

Rank: 6Rank: 6

发表于 2011-10-6 15:09 | 显示全部楼层 |阅读模式

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进行读写操作,跟我的要求很对口,就从它上面改吧。原示例代码 eeprom.rar (3.45 KB, 下载次数: 312)

评分

3

查看全部评分


回复

使用道具 举报

208

TA的帖子

0

TA的资源

纯净的硅(高级)

Rank: 6Rank: 6

 楼主| 发表于 2011-10-6 15:11 | 显示全部楼层

其实刚一开始我也没有细细的读这段程序。因为如果只是应用的话,这些应该算是封装起来的操作。大家不需要关心,真正关心的是接口的操作。其操作接口则是在下面两个函数中:
void EEPROMWrite (unsigned char *pucData, unsigned long ulOffset, unsigned long ulCount);
void EEPROMRead  (unsigned char *pucData, unsigned long ulOffset, unsigned long ulCount);
我们看一下其中的一个函数:

  1. void EEPROMWrite (unsigned char *pucData,
    unsigned long ulOffset,
    unsigned long ulCount)
    {
    g_pucData = pucData; /* 将要写入的数据存入缓冲区 */
    g_ulCount = ulCount;
    if (ulCount != 1) { /* 根据将要写的字节数设定中断
    状态机的下一状态 */
    g_ulState = STATE_WRITE_NEXT;
    }
    else {
    g_ulState = STATE_WRITE_FINAL;
    }
    I2CMasterSlaveAddrSet (I2C_MASTER_BASE, CSI24c02 , false); /* 设置从地址,准备发送数据 */
    I2CMasterDataPut (I2C_MASTER_BASE, ulOffset); /* 将写地址发送到数据寄存器 */
    I2CMasterControl (I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);/* 开始循环写字节操作,写该地址
    作为第一个地址 */
    while ( g_ulState != STATE_IDLE ) { /* 等待I2C为空闲状态 */
    }
    }
复制代码

这段程序中所进行的操作非常少,只有两个,一是判断写入的是否只有一个字节,然后发送第一个字节。
在这里,我用的是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寄存器的说明
  未命名.JPG
从这个表格中,可以看到,起作用的四位每位代表一种操作。本来是一种很普通的控制方式,每次设置一位来完成一个操作,简单明了。但是复杂就复杂在有些操作组合是必然的,处理器就替你封装了,而且不允许你独立进行操作。这些操作是一种怎样的组合方式呢,我们还是先看一下这个寄存器位的定义。
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的程序做一些修改以扩展其应用范围。


回复

使用道具 举报

208

TA的帖子

0

TA的资源

纯净的硅(高级)

Rank: 6Rank: 6

 楼主| 发表于 2011-10-9 00:55 | 显示全部楼层

本来想对ZLG的4.12例程进行一些扩展,不过后来在EasyARM8962光盘里发现ZLG早已写好了一个完整的软件包,我这里就不再献丑了,函数的用法在头文件里面有说明。 lm硬件I2C软件包.rar (4.13 KB, 下载次数: 316)


回复

使用道具 举报

1791

TA的帖子

0

TA的资源

五彩晶圆(初级)

Rank: 7Rank: 7Rank: 7

荣誉会员勋章

发表于 2011-10-9 13:23 | 显示全部楼层
讲的很好,谢谢楼主的东东。

回复

使用道具 举报

396

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2011-10-11 17:19 | 显示全部楼层
讲的很好

回复

使用道具 举报

1886

TA的帖子

0

TA的资源

五彩晶圆(高级)

Rank: 9Rank: 9Rank: 9

荣誉会员勋章

发表于 2011-11-11 14:56 | 显示全部楼层

回复 沙发 柳叶舟 的帖子

楼主是否知道,为什么主发送后,从的 CLK 一直是低电平?

点评

不好意思,很少关注时间比较久的帖子,也一直没有发现你的回复。 是这样的,在I2C 协议中,当一个传输过程没有结束之前(指停止位)器件处于工作状态,无法响应时,就会拉低CLK 电平,其他器件靠检测CLK 电平来确定  详情 回复 发表于 2012-9-21 15:46

回复

使用道具 举报

2803

TA的帖子

0

TA的资源

裸片初长成(初级)

Rank: 10Rank: 10Rank: 10

发表于 2011-11-16 16:21 | 显示全部楼层
拿去看看,给楼主顶一个帖子,算是对于楼主给大家做贡献的一个鼓励吧,嗯嗯,值得值得,希望楼主继续努力啊!加油
我爱电子!

回复

使用道具 举报

54

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2012-7-18 10:57 | 显示全部楼层
找了好久那硬件的软件包。。。

回复

使用道具 举报

208

TA的帖子

0

TA的资源

纯净的硅(高级)

Rank: 6Rank: 6

 楼主| 发表于 2012-9-21 15:46 | 显示全部楼层
原帖由 Study_Stellaris 于 2011-11-11 14:56 发表
楼主是否知道,为什么主发送后,从的 CLK 一直是低电平?
不好意思,很少关注时间比较久的帖子,也一直没有发现你的回复。
是这样的,在I2C 协议中,当一个传输过程没有结束之前(指停止位)器件处于工作状态,无法响应时,就会拉低CLK 电平,其他器件靠检测CLK 电平来确定当前总线是否空闲。所以,对于发送之后从器件需要响应时间的,从器件会拉低CLK。而在发送过程中,当发送完一个字节后(指已经完成了ACK 应答),如果没有发送停止位,主器件会拉低CLK 电平从而避免总线被其他器件抢占而无法完成一次完整的数据传送。当你发送完停止位后,CLK 会恢复高电平。

回复

使用道具 举报

8

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2012-9-23 08:01 | 显示全部楼层
TI的I2C可以既做主设备又做从设备吗

回复

使用道具 举报

1

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2012-11-30 16:43 | 显示全部楼层

附件中的程序包有问题

看了lm硬件I2C软件包.rar里的代码,如果我没理解错的话,里面带器件子地址任意字节写的函数ISendStr()不能只写单字节数据。因为在中断里面对STATE_WRITE_NEXT状态的发送数据操作是先发送数据,然后字节长度减一,最后才判断字节长度是否为1(最后一个字节)。这样的话,若是发送字节长度为1,则会由于被减1后,长度变成0,接着在后面的最后字节判断中得不到正确结果,从而导致发送出错。

回复

使用道具 举报

54

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2013-1-11 17:33 | 显示全部楼层
我想用不进入中断,用轮询方式发送数据,为什么发的是三个字节,而从机接收到的是四个字节呢
很是郁闷

回复

使用道具 举报

54

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2013-1-11 17:33 | 显示全部楼层
能不能请哪位大侠帮一下忙呀,很急,纠结了两天的东西了

回复

使用道具 举报

54

TA的帖子

0

TA的资源

一粒金砂(中级)

Rank: 2

发表于 2013-1-15 09:39 | 显示全部楼层
如果用轮询方式读从机数据,程序应该怎么写,希望指点一下,可以发到我邮箱
,谢谢

回复

使用道具 举报

175

TA的帖子

0

TA的资源

纯净的硅(初级)

Rank: 4

发表于 2013-3-1 16:04 | 显示全部楼层
博主,想问一下,初始化需要具体指明是I2C0还是I2C1吗?还是笼统的初始化就可以了?特别是设备使能的时候!

回复

使用道具 举报

23

TA的帖子

0

TA的资源

一粒金砂(初级)

Rank: 1

发表于 2014-8-23 20:10 | 显示全部楼层
好东西

回复

使用道具 举报

1

TA的帖子

0

TA的资源

一粒金砂(初级)

Rank: 1

发表于 2015-3-22 14:27 | 显示全部楼层
我用LM3S 9B81,ARM做主机,且只有一个主机读取一个丛机。有时会发生I2C仲裁丢失的情况,至今原因未知,然后只能断电,重启。

回复

使用道具 举报

161

TA的帖子

12

TA的资源

一粒金砂(高级)

Rank: 3Rank: 3

发表于 2015-4-4 13:53 | 显示全部楼层
顶楼主对I2C的解读,不过,周立功给的例程也非原创,
而是TI的例程,其所用芯片不是24c02而是atmel的
AT2408,应该可以解决你提到的问题---仅猜想,
TI的例程命---i2c_atmel
good

回复

使用道具 举报

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

关闭

站长推荐上一条 /5 下一条

  • 论坛活动 E手掌握

    扫码关注
    EEWORLD 官方微信

  • EE福利  唾手可得

    扫码关注
    EE福利 唾手可得

Archiver|手机版|小黑屋|电子工程世界 ( 京ICP证 060456 )

GMT+8, 2020-2-22 04:43 , Processed in 0.500279 second(s), 19 queries , Gzip On, MemCache On.

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