打印
[应用相关]

【转】stm32模拟iic-引脚配置、代码

[复制链接]
1083|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
联通移不动|  楼主 | 2017-2-8 12:28 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

我的工程里要用到iic总线扩展rom,stm32是有硬件iic的,但是,网上有很多人说这个硬件iic有漏洞,甚至于有bug。https://bbs.21ic.com/icview-184741-1-1.html http://blog.gkong.com/more.asp?name=zjcsharp&id=112878。《例说stm32》的表述是:“非常复杂,不太好用”。那么我判断这个硬件iic可能确实有不足,因此选择直接用软件模拟出iic。

在做的过程中,遇到几个问题,记录下来。

1、引脚的模式与配置

iic的两个引脚SDA与SCL都要求既能输出又能输入。这对stm32来说问题不大,由参考手册给出的图来看,引脚是始终连着IDR寄存器的,另外“输出配置”一节还特意讲到,“在开漏模式时,对输入数据寄存器的读访问可得到I/O状态”。所以,模式的问题很好解决。

SDA线是由不同的器件分时控制的,这就造成一个问题:当一个器件主动置高或者置低时,若另一个器件若发出相反的电平,会短路。

这就决定将引脚配置成推挽,有很多麻烦事。alientek就是这么做的,他在主机(单片机)控制SDA线时,将其SDA引脚配置成推挽输出;从机(EEPROM)控制SDA线时,将单片机的引脚配置成上拉/下拉输入,用频繁的配置切换来避免这个问题。

我觉得这么做太麻烦,stm32有一个开漏的配置,它与推挽有点像,但也不完全一样,手册里这么说:

开漏模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将端口置于高阻状态(P-MOS从不被激活)。
推挽模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将激活P-MOS。

这样一来,问题就很好解决了:当单片机的SDA引脚置低时,SDA线被拉低,当单片机的SDA引脚置高时,实际上引脚是浮空的,SDA线通过上拉电阻被VCC拉高(iic的两条线都要通过上拉电阻接到VCC,典型接法),这样就不会出现短路的状况。很巧妙。

2、逻辑与时序

逻辑与时序的问题iic相关手册,尤其是EEPROM的手册上都有详细的介绍,这里主要从编程的角度来讨论这个问题。

先附上代码:

  • void IicStart()  
  • {  
  •   
  •     SDA_H;  
  •     SCL_H;  
  •     delay_us(5);  
  •     SDA_L;  
  •     delay_us(5);  
  •     SCL_L;            //­  
  •   
  •     delay_us(2);      //  
  •     SDA_H;  
  •     delay_us(2);  
  • }  
  •   
  • void IicStop()  
  • {  
  •     SDA_L;  
  •     SCL_L;  
  •     delay_us(2);  
  •   
  •     SCL_H;  
  •     delay_us(2);  
  •     SDA_H;            //  
  •     delay_us(2);  
  •     SCL_L;               
  •       
  •     delay_us(2);        //  
  • }  
  • void IicAck()  //  
  • {  
  •     SCL_L;  
  •     SDA_L;  
  •     delay_us(5);  
  •   
  •     SCL_H;  
  •     delay_us(5);   //  
  •     SCL_L;  
  •   
  •     delay_us(2);  
  •     SDA_H;  
  •     delay_us(2);  
  • }  
  •   
  • void IicNack()  //  
  • {  
  •     SCL_L;  
  •     SDA_H;  
  •     delay_us(5);  
  •   
  •     SCL_H;  
  •     delay_us(5);   //  
  •     SCL_L;  
  •   
  •     delay_us(2);  
  •     SDA_H;  
  •     delay_us(2);  
  • }  
  • void IicWaiteAck()  
  • {     
  •     SCL_L;  
  •     SDA_H;  
  •     delay_us(5);  
  •   
  •     SCL_H;  
  •     delay_us(5);  
  •     SCL_L;  
  •   
  •     delay_us(2);  
  • }  
  • void IicSendByte(u8 temp)  
  • {  
  •     u8 i;  
  •   
  •     for(i=0;i<8;i++)  
  •     {  
  •         SCL_L;  
  •         delay_us(5);  
  •         if(temp&0x80)           //MSB在前  
  •             SDA_H;  
  •         else  
  •             SDA_L;  
  •         SCL_H;  
  •         delay_us(5);  
  •         SCL_L;  
  •         temp<<=1;  
  •     }  
  •       
  •     delay_us(2);  
  •     SDA_H;  
  •     delay_us(2);  
  •          
  • }  
  •   
  • u8 IicReceiveByte()  
  • {  
  •     u8 i,temp=0;  
  •   
  •     delay_us(2);  
  •     SDA_H;                  //  
  •     delay_us(2);            
  •   
  •     for(i=0;i<8;i++)  
  •     {  
  •         temp<<=1;  
  •         SCL_L;  
  •         delay_us(5);  
  •         SCL_H;  
  •         delay_us(2);  
  •   
  •         if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7))  
  •             temp=temp|0x01;  
  •         else  
  •             temp=temp&0xFE;   
  •     }  
  •   
  •     SCL_L;  
  •     delay_us(2);  
  •     return temp;      
  • }  


发送8个字节:

  • IicStart();  
  • IicSendByte(0xA0);            //寻址,发送写命令  
  •     IicWaiteAck();  
  • IicSendByte(0x00);         //字节地址,24LC02里首先写入的是指针,指向0x00字节单元  
  • IicWaiteAck();  
  • for(i=0;i<8;i++)  
  • {  
  •     IicSendByte(0xa0+i);            //写完了指针就是按指针的指向写数据  
  •     IicWaiteAck();  
  • }  
  • IicStop();  
  • delay_ms(4);  


接收字节:

  • IicStart();  
  • IicSendByte(0xA0);        //寻址,发送写命令(先要写指针)            
  • IicWaiteAck();  
  • IicSendByte(0x00);       //写指针  
  • IicWaiteAck();  
  • IicStart();     //再次发出起始条件         
  • IicSendByte(0xA1);  //这次是寻址和读命令  
  • for(i=0;i<2;i++)        //24LC会根据写好的指针,将相应数据发送在SDA上  
  • {  
  •     IicAck();  
  •     *((u8 *)(&lxj)+i)=IicReceiveByte();  
  • }  
  • IicNack();  
  • IicStop();  


在发送START,ACK,NACK,STOP,发送一个字节时,以及接收ACK,接收字节时,每个函数返回,都保证SCL为低,即一个信号位结束;并且SDA为高(浮空),以保证SDA线可以被其他器件控制。

另外还要注意电平高低之间要有适当的延时。我这里微秒级的延时是为了产生有效的iic信号,这些延时是我随意加进去的,可以用,但不知道能不能减少一点;毫秒级的延时是为了等待24LC02将数据从缓冲区写入ROM,这个时间与写入的字节数无关,至少要3ms,否则出错。

24LC02每次最多写入8个字节,读出没有限制。

3、EEPROM的页

24LC02有页的概念,在写时,先写入缓冲区,缓冲区大小是一页(8字节,64位),在页写时,先写入缓冲区,待总线上出现STOP信号时,将缓冲区写到相应的ROM中。这就要求:不能跨页写。以下是网上摘录的一点,我觉得自己对这个东西的理解还不全面,网上的资料不多。

AT24CXX系列的EEPROM为了提高写效率,提供了页写功能,内部有个一页大小的写缓冲RAM,地址范围当然就是从00到一页大小,发生写操作时,开始送入的地址对应的页被选中,并将其内容映像到缓冲RAM,数据从低端地址对应的缓冲RAM地址开始修改,超过这个地址范围就回到00,写完后,就会把开始确定的EEPROM页擦除,再把一整页RAM数据写入。所有写数据都发生在开始写地址时确定的页上。
如页容量为128,一页都是从00开始按128字节分成一个个的页,0页就是0~7F,1页就是80~FF,类推,边界就是128字节的整数倍地址。页RAM的地址范围为7位00~7F,写入时高端地址就是页号。发生写操作,开始送入的地址对应的页被锁存,后续不论写多少,都在这个页中,只是一个页内的地址进行加一,超过就归零开始。从F0开始写32个字节,那么开始送入的地址为F0,就会锁定在1号页(第2个页)上,底端7位页内部地址开始从70H开始写,到达7F时回到00再到10H,也就是写在了F0~FF,80~8F。也就是,从01开始写也只能到7F,再往80写就跑到00上去了,这就是写操作的翻卷,datasheet上都有说明。就是从边界前写两个字节也要分两次写。页是绝对的,按整页大小排列,不是从开始写入的地址开始算。
读没有页的问题,可以从任意地址开始读取任意大小数据,只是超过整个存储器容量时地址才回卷。但一次性访问的数据长度也不要太大。
所以分页的存储器要做好存储器管理,尽量同时读写的数据放在一个页上。

4、其他

在我这个项目里,iic是不常用的,当电网发生故障时,才做记录。所以,引脚无需长期配置在开漏输出,根据st的官方推荐,在不使用iic时,将引脚配置成浮空输入。


沙发
lengruobing| | 2017-2-10 09:36 | 只看该作者
需要加上拉10K么?

使用特权

评论回复
板凳
sonicll| | 2017-2-10 09:45 | 只看该作者
lengruobing 发表于 2017-2-10 09:36
需要加上拉10K么?

需要加上拉电阻,STM32模拟SCL和SDA的管脚,设置为开漏模式,我们公司一直是这样用的,没出现过问题

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

67

主题

127

帖子

0

粉丝