历史上的今天
返回首页

历史上的今天

今天是:2025年08月10日(星期日)

正在发生

2019年08月10日 | stm32模拟iic——引脚配置、代码

2019-08-10 来源:eefocus

我的工程里要用到iic总线扩展rom,stm32是有硬件iic的,但是,网上有很多人说这个硬件iic有漏洞,甚至于有bug。http://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的手册上都有详细的介绍,这里主要从编程的角度来讨论这个问题。

先附上代码:

  1. void IicStart()  

  2. {  

  3.   

  4.     SDA_H;  

  5.     SCL_H;  

  6.     delay_us(5);  

  7.     SDA_L;  

  8.     delay_us(5);  

  9.     SCL_L;            //­  

  10.   

  11.     delay_us(2);      //  

  12.     SDA_H;  

  13.     delay_us(2);  

  14. }  

  15.   

  16. void IicStop()  

  17. {  

  18.     SDA_L;  

  19.     SCL_L;  

  20.     delay_us(2);  

  21.   

  22.     SCL_H;  

  23.     delay_us(2);  

  24.     SDA_H;            //  

  25.     delay_us(2);  

  26.     SCL_L;               

  27.       

  28.     delay_us(2);        //  

  29. }  

  30. void IicAck()  //  

  31. {  

  32.     SCL_L;  

  33.     SDA_L;  

  34.     delay_us(5);  

  35.   

  36.     SCL_H;  

  37.     delay_us(5);   //  

  38.     SCL_L;  

  39.   

  40.     delay_us(2);  

  41.     SDA_H;  

  42.     delay_us(2);  

  43. }  

  44.   

  45. void IicNack()  //  

  46. {  

  47.     SCL_L;  

  48.     SDA_H;  

  49.     delay_us(5);  

  50.   

  51.     SCL_H;  

  52.     delay_us(5);   //  

  53.     SCL_L;  

  54.   

  55.     delay_us(2);  

  56.     SDA_H;  

  57.     delay_us(2);  

  58. }  

  59. void IicWaiteAck()  

  60. {     

  61.     SCL_L;  

  62.     SDA_H;  

  63.     delay_us(5);  

  64.   

  65.     SCL_H;  

  66.     delay_us(5);  

  67.     SCL_L;  

  68.   

  69.     delay_us(2);  

  70. }  

  71. void IicSendByte(u8 temp)  

  72. {  

  73.     u8 i;  

  74.   

  75.     for(i=0;i<8;i++)  

  76.     {  

  77.         SCL_L;  

  78.         delay_us(5);  

  79.         if(temp&0x80)           //MSB在前  

  80.             SDA_H;  

  81.         else  

  82.             SDA_L;  

  83.         SCL_H;  

  84.         delay_us(5);  

  85.         SCL_L;  

  86.         temp<<=1;  

  87.     }  

  88.       

  89.     delay_us(2);  

  90.     SDA_H;  

  91.     delay_us(2);  

  92.           

  93. }  

  94.   

  95. u8 IicReceiveByte()  

  96. {  

  97.     u8 i,temp=0;  

  98.   

  99.     delay_us(2);  

  100.     SDA_H;                  //  

  101.     delay_us(2);             

  102.   

  103.     for(i=0;i<8;i++)  

  104.     {  

  105.         temp<<=1;  

  106.         SCL_L;  

  107.         delay_us(5);  

  108.         SCL_H;  

  109.         delay_us(2);  

  110.   

  111.         if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7))  

  112.             temp=temp|0x01;  

  113.         else  

  114.             temp=temp&0xFE;   

  115.     }  

  116.   

  117.     SCL_L;  

  118.     delay_us(2);  

  119.     return temp;      

  120. }  


发送8个字节:

  1. IicStart();  

  2. IicSendByte(0xA0);            //寻址,发送写命令  

  3.     IicWaiteAck();  

  4. IicSendByte(0x00);         //字节地址,24LC02里首先写入的是指针,指向0x00字节单元  

  5. IicWaiteAck();  

  6. for(i=0;i<8;i++)  

  7. {  

  8.     IicSendByte(0xa0+i);            //写完了指针就是按指针的指向写数据  

  9.     IicWaiteAck();  

  10. }  

  11. IicStop();  

  12. delay_ms(4);  


接收字节:

  1. IicStart();  

  2. IicSendByte(0xA0);        //寻址,发送写命令(先要写指针)            

  3. IicWaiteAck();  

  4. IicSendByte(0x00);       //写指针  

  5. IicWaiteAck();  

  6. IicStart();     //再次发出起始条件         

  7. IicSendByte(0xA1);  //这次是寻址和读命令  

  8. for(i=0;i<2;i++)        //24LC会根据写好的指针,将相应数据发送在SDA上  

  9. {  

  10.     IicAck();  

  11.     *((u8 *)(&lxj)+i)=IicReceiveByte();  

  12. }  

  13. IicNack();  

  14. 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时,将引脚配置成浮空输入。


推荐阅读

史海拾趣

佰鸿(BrtLed)公司的发展小趣事

近年来,佰鸿公司开始将业务触角延伸至再生医学领域。通过多年的努力,公司成功建立了再生医学医疗与健康科技生态圈,并逐步实现了产业集群化。在再生医学领域,佰鸿不仅建立了产业化场地和研发设备,还计划在未来几年内打造国际领先的再生医学产业集群。这一多元化的发展战略,使得佰鸿在电子行业之外,也找到了新的增长点。

General Electric Company公司的发展小趣事
在实际应用前进行充分的测试验证,确保电路的稳定性和可靠性。
Ambersil公司的发展小趣事

在电子行业的初期,Ambersil公司以其创新的技术和产品迅速崭露头角。公司研发团队成功开发出一种高效的电子清洁剂,能够去除电路板上的顽固污垢,提高电子设备的性能和稳定性。这一创新产品迅速获得了市场的认可,Ambersil公司因此获得了大量的订单,销售额逐年攀升。

Frequency Sources公司的发展小趣事

在20世纪90年代初,Frequency Sources公司(或类似名称的公司)凭借其在频率源技术领域的深厚积累,成功研发出了一种新型高精度晶体振荡器。这种振荡器在稳定性、相位噪声和温度特性等方面均达到了当时业界的顶尖水平,为无线通信、卫星导航等领域提供了关键的技术支持。这一技术创新不仅巩固了公司在频率源技术领域的领先地位,还为公司赢得了广泛的市场认可和大量订单。

Cables To Go公司的发展小趣事

随着电子设备的普及和互联网的发展,Cables To Go公司看到了巨大的市场潜力。公司积极拓展销售渠道,通过线上电商平台和线下实体店相结合的方式,将产品销往全国各地。此外,公司还积极开拓国际市场,与多个国家和地区的代理商建立了合作关系,进一步扩大了市场份额。这种跨越式的市场拓展策略为公司的快速发展奠定了坚实的基础。

Amulet Technologies公司的发展小趣事

随着电子设备的普及和互联网的发展,Cables To Go公司看到了巨大的市场潜力。公司积极拓展销售渠道,通过线上电商平台和线下实体店相结合的方式,将产品销往全国各地。此外,公司还积极开拓国际市场,与多个国家和地区的代理商建立了合作关系,进一步扩大了市场份额。这种跨越式的市场拓展策略为公司的快速发展奠定了坚实的基础。

问答坊 | AI 解惑

如何给.Net软件加密

.Net是架构于操作系统上的平台,它是一套虚拟机,其核心功能由一系列运行在用户层的Dll文件实现。它的出现,大大减少了软件开发的工作量,但是也同时带来了版权保护难的新问题。不过,.Net最突出的跨平台优势使得它不能被编译成本地代码,而要以中 ...…

查看全部问答>

STM32f10x。。数组初始化有错误。。只能初始化前64个字节

STM32f10x。。数组初始化有错误。。只能初始化前64个字节…

查看全部问答>

傻瓜式无线传输模块PT2262/2294

今年电赛做智能小车 要求两辆小车需要无线通信 想用PT2262发送4位同步数据 但是PT2294接收不回数据 2262我用了4位发送位 1个VCC    1个GND 2294我也用上述6个引脚 空着1个VT 求解!  …

查看全部问答>

安装了aStudio4.13却无法启动,谁能帮帮我?

安装了aStudio4.13却无法启动,谁能帮帮我? 提示“应用程序配置错误,不能启动” 我没有配置过什么啊?都是按默认的路径和状态来装的,一直“下一步”到最后的。…

查看全部问答>

EPM240 奇怪现象求助

module test2(  input [15:0] xinput,  output reg [15:0] xoutput); reg  [15:0] routput ; always @ (xinput) begin case (xinput)  16\'b???????????????0 :  routput <=  16\'b111111111 ...…

查看全部问答>

MSP430

各位大侠,正弦信号通过AD637的峰值检验电路后输出直流电压(例如6V),怎样用MSP430G2553显示到LCD12864显示屏上?急啊!!…

查看全部问答>

做一个简单的ASK解调

新手上路,想做一个ASK的解调电路,感觉分立元件搭建检波电路性能不会很好,,听说用MC3361可以实现,但没看懂怎么用,MC3361引脚的10到14引脚用得上吗?是不是直接第9脚就可以输出了?  请问还有什么其他的推荐芯片?(最好外围器件少一 ...…

查看全部问答>

TMS320VC5502调试SDRAM时, 出问题

\' TMS320VC5502调试SDRAM时, , 两个问题, 求解 1,写一个字节,结果所以内存全部变掉; 2,0x1e0000到0x200000之间的内存变成----了 …

查看全部问答>

【LPC54100】在M0上跑事件驱动构架

初次接触非对称双核MCU,由于之前好多知识不熟悉,这三天遇到不少问题。现在终于把事件驱动在M0核上跑起来了,就发一贴简单介绍下这个事件驱动构架和这几天的5410x芯片学习所得。 关于事件驱动架构,其主要用于低功耗设计,可以很方便的进入休眠模 ...…

查看全部问答>

【TI C2000的使用经验】+地址引导的小小心得

本帖最后由 led2015 于 2015-4-24 01:37 编辑 在初次使用C2000的时候也遇到过不懂的地方,但都是慢慢自己琢磨出来的,有幸跟着大师,学到点皮毛,有时师傅叫我独立去设计就头大了。一次自己独立设计了一段程序,自知可能不行,实验了下果然不行, ...…

查看全部问答>