[开源/进行中直播] gpio模拟的i2c at24cxx读写源码

辛昕   2015-9-6 23:24 楼主
这是针对atmel官方最新的at24cxx系列数据手册中的时序和延时写的一个函数。 基于stm32f407,但因为使用模拟i2c,而不是硬件i2c,所以稍作简单的io寄存器操作和延时修改,源码的移植非常简单。 几点说明: 1.之所以不使用硬件i2c,是因为stm32几个系列的硬件i2c都做得不太好,硬件上据说不太稳定,但最主要的是,ST库的实现非常复杂,用起来非常麻烦。 故而,很多人称之为鸡肋,而弃之不用。 我曾有过在stm8s上的使用经验确实也发现如此,故而我也不用。 PS:据称stm32f0系列的硬件i2c有了进步,有机会试试再说。 2.这个at24cxx的函数没有考虑多主机冲突和仲裁的情形,只是处理了简单的一主一从 的情形,当然,对于 一主多从的情形也是一样的; 3.这个模块本身不提供 读写操作的线程保护。需要的话,需要另行在调用层面 使用信号锁 等措施。 下面逐个楼层贴出代码,主要是为了避免一大段代码,而采用分部分贴。 .c / .h 源文件下载
at24cxx_eeworld.rar (3.2 KB)
(下载次数: 36, 2015-9-6 23:33 上传)
本帖最后由 辛昕 于 2015-9-6 23:33 编辑
强者为尊,弱者,死无葬身之地

回复评论 (22)

源文件头部说明 和 微妙级延时部分 io寄存器操作 等可移植部分

  1. /*
  2.     参考各类文档以及 飞利浦官方2014 i2c文档,对i2c有以下简单几点说明:
  3.     1.时钟线、数据线皆为高时,意味着总线空闲;
  4.     2.这个源文件实现的i2c总线读写操作不考虑多主机冲突和仲裁的情形,仅提供一主多从的处理;
  5. 所以,在开始信号之后,时钟线为低,以占住总线,并且只在结束信号之后,才恢复 总线空闲;
  6. 初始状态:(合适的话,作为每个动作后的结束状态)
  7.     3.因为 开始信号和结束信号 都以 时钟线高时的 脉冲沿来标识,故此,需要通过代码上来避免
  8. 时钟线在不同的操作后可能的处于高电平时,避免此时数据线的电平变化误触发 开始、结束信号,
  9. 而改变过去默认让每个动作过后,时钟线都保持在低作为默认状态;
  10.     4.在一主多从的情况下,不需要担心同一个i2c设备被不同进程同时操作——即使当前i2c设备
  11. 处在一个读写过程中,即总线忙的状态下,也可以通过重新发起一次开始信号重新开始一次新的操作;
  12.     至于前面的数据读写失败,作废,这已经不能通过i2c本身来仲裁了,这种情形为了避免进程读写
  13. 操作冲突,只能通过信号锁等其他上层机制来保证了。

  14. */
  15. #include "stm32f4xx_gpio.h"
  16. #include "stm32f4xx_rcc.h"


  17. #include "at24cxx.h"

  18. // 微妙级延时:此处有一些奇怪,我曾发现,我把i循环调到几百次几千次后,发现延时时间的变化
  19. // 不符合数值变化(不是线性变化),原因未明,恐怕要查到汇编级,但看了也看不出太大毛病

  20. void delay(void)
  21. {
  22.     int i;
  23.    
  24.     for(i = 0;i < 25;i++)
  25.         __ASM("NOP");
  26. }

  27. #define _1us {delay();}

  28. #define _5us {_1us;_1us;_1us;_1us;_1us;}
  29. #define _10us {_5us;_5us;}

  30. // 时序延时   
  31. // 限于测试工具,所以这里没有到1us以下的测试;另外,由于硬件上没有其他at24cxx器件,所以也
  32. // 没测试其他型号。只是这里用宏方便管理和修改,待用到的时候可仔细测试;
  33.                                            //512 pdf (1.7V/2.5V)
  34. #define SETUP_START  _1us                  //0.6us/0.25us(min)
  35. #define HOLD_START   _1us                  //0.6us/0.25us(min)

  36. #define SETUP_STOP   _1us;                 //0.6us/0.25us(min)

  37. #define SETUP_DATA   _1us;                 //0.6us/0.25us(min)
  38. #define HOLD_DATA    {}                    //0

  39. #define CLOCK_LOW_DATA_VALID _1us;         //0.05~0.9us/0.05~0.55us
  40. #define DATA_HOLD    _1us;                 //50ns(min)
  41. #define HIGH_TIME    _1us;                 //0.6us/0.4us
  42. #define LOW_TIME     {_1us;_1us;}          //1.3us/0.4us

  43. // tBuf min between start and stop 1.3us/0.5us


  44. // end of 微妙级延时 ----------------------------

  45. // BSRRL和BSRRH反过来了??
  46. #define I2C1_SCK_OUT    {GPIOB->MODER &= 0xfffcffff;GPIOB->MODER |= 0x00010000;}
  47. #define I2C1_SCK_HIGH   GPIOB->BSRRL |= 0x0100
  48. #define I2C1_SCK_LOW    GPIOB->BSRRH |= 0x0100

  49. #define I2C1_SDA_OUT    {GPIOB->MODER &= 0xffff3fff;GPIOB->MODER |= 0x00004000;}
  50. #define I2C1_SDA_IN      GPIOB->MODER &= 0xffff3fff
  51. #define I2C1_SDA_HIGH    GPIOB->BSRRL |= 0x0080
  52. #define I2C1_SDA_LOW     GPIOB->BSRRH |= 0x0080
  53. #define I2C1_SDA_STATUS (GPIOB->IDR & 0x0080)

  54. // 对接收不到的应答进行计数,以避免让简单的写字节函数直接返回这个信息,可在发现了错误操作后
  55. // 通过这个函数查询,这是在字节层面上做检查;
  56. // 可简化写字节过程的检查 也利于高速;
  57. static int i2c1_ack_lack_times = 0;

  58. int is_i2c1_ack_not_found(void)
  59. {
  60.     return i2c1_ack_lack_times;
  61. }


  62. void i2c1_gpio_init(void)
  63. {
  64.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);

  65.     I2C1_SCK_OUT;
  66.     I2C1_SDA_OUT;

  67.     GPIOB->OSPEEDR |= 0x003c000;     // pin 8 7,设为 0x11;100Mhz
  68.     GPIOB->OTYPER &= 0xfffffe7f;    // pin 8 7,设为 0;    默认输出模式
  69.     GPIOB->PUPDR &= 0xfffc3fff;     // pin 8 7,设为 0x00; 无上拉下拉
  70. }

  71. //------------------------------------------------------------------------------




强者为尊,弱者,死无葬身之地
点赞  2015-9-6 23:26
妈的,这代码功能一直有毛病。
我添加了N次愣是不行,最后不得已弄了两个代码框,结果格式全毁了

而且,这个代码框内也没有不同颜色和高亮功能,简直是鸡肋
强者为尊,弱者,死无葬身之地
点赞  2015-9-6 23:30

i2c读写操作部分

  1. void i2c1_init(void)
  2. {                                             
  3.     i2c1_gpio_init();
  4.     I2C1_SCK_HIGH;  
  5.     I2C1_SDA_HIGH;
  6. }

  7. // 应确保所有涉及改SDA为输入状态的操作完后都恢复成默认输出状态
  8. void i2c1_start(void)
  9. {
  10.     I2C1_SDA_HIGH;

  11.     I2C1_SCK_HIGH;   
  12.     SETUP_START;
  13.    
  14.     I2C1_SDA_LOW;
  15.     HOLD_START;
  16. }         

  17. //产生IIC停止信号
  18. void i2c1_stop(void)
  19. {
  20.     I2C1_SDA_LOW;

  21.     I2C1_SCK_HIGH;
  22.     SETUP_STOP;
  23.     I2C1_SDA_HIGH;        
  24. }

  25. void i2c1_wait_ack(void)
  26. {
  27.         char time=0;

  28.     I2C1_SDA_IN;        //切换回数据输入
  29.     I2C1_SCK_HIGH;      //拉高总线 等待ACK

  30.         while(I2C1_SDA_STATUS)
  31.         {
  32.                 time++;
  33.                 if(time>250)
  34.                 {
  35.             if(i2c1_ack_lack_times < i2c1_ack_lack_times+1)
  36.                 i2c1_ack_lack_times++;
  37.             return;
  38.                 }
  39.         }
  40.    
  41.     i2c1_ack_lack_times = 0;    // 一旦有一次应答,则清除该标志
  42.         I2C1_SCK_LOW;          //成功收到slave回应的ACK,拉低时钟结束
  43.     I2C1_SDA_OUT;
  44. }

  45. //产生ACK应答
  46. void i2c1_ack(void)
  47. {
  48.     I2C1_SCK_LOW;  
  49.     SETUP_DATA;   
  50.       
  51.     I2C1_SDA_OUT;
  52.     I2C1_SDA_LOW;   // 应答
  53.     HOLD_DATA;
  54.    
  55.         I2C1_SCK_HIGH;  // 拉高时钟线数据有效
  56.     HIGH_TIME;
  57.         I2C1_SCK_LOW;   //应该与 结束信号配合有关
  58. }

  59. //不产生ACK应答                    
  60. void i2c1_nack(void)
  61. {
  62.     I2C1_SCK_LOW;   
  63.     SETUP_DATA;   
  64.       
  65.     I2C1_SDA_OUT;
  66.     I2C1_SDA_HIGH;  // 不应答
  67.     HOLD_DATA;
  68.    
  69.         I2C1_SCK_HIGH;  // 拉高时钟线数据有效
  70.     HIGH_TIME;
  71.         I2C1_SCK_LOW;  //应该与 结束信号配合有关
  72. }                                                                              
  73.          
  74. void i2c1_send_byte(char txd)
  75. {                        
  76.     char t;   
  77.    
  78.         I2C1_SCK_LOW;
  79.     I2C1_SDA_OUT;
  80.    
  81.     // 时钟低电平时间
  82.    
  83.     for(t=0;t<8;t++)
  84.     {      
  85.         if( (txd&0x80)>>7 == 1)
  86.           I2C1_SDA_HIGH;
  87.         else
  88.           I2C1_SDA_LOW;
  89.         
  90.         txd<<=1;
  91.         
  92.         SETUP_DATA;
  93.         
  94.                 I2C1_SCK_HIGH;      // 拉高数据有效
  95.         HIGH_TIME;
  96.         I2C1_SCK_LOW;       // 允许下一位变
  97.         HOLD_DATA;        
  98.     }        
  99.    
  100.     i2c1_wait_ack();
  101. }            
  102. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
  103. char i2c1_read_byte(char ack)
  104. {
  105.         char i,receive=0;
  106.    
  107.     I2C1_SDA_IN;

  108.     for(i=0;i<8;i++ )
  109.         {
  110.         I2C1_SCK_LOW;
  111.         CLOCK_LOW_DATA_VALID;
  112.                 I2C1_SCK_HIGH;
  113.         
  114.         receive<<=1;            //数据接收操作
  115.         if(I2C1_SDA_STATUS)
  116.             receive++;   
  117.         
  118.         DATA_HOLD;
  119.     }        
  120.    
  121.     if (!ack)
  122.         i2c1_nack();//发送nACK
  123.     else
  124.         i2c1_ack(); //发送ACK   
  125.     return receive;
  126. }

强者为尊,弱者,死无葬身之地
点赞  2015-9-6 23:30

at24cxx读写操作 和 简单测试函数 (完)

  1. void at24cxx_byte_write(short Add,char Byte)
  2. {
  3.      i2c1_start();
  4.      
  5.      i2c1_send_byte(0xAE);

  6.      i2c1_send_byte((char)((Add>>8)&0xff));
  7.      i2c1_send_byte((char)((Add>>0)&0xff));   
  8.      i2c1_send_byte(Byte);

  9.      i2c1_stop();
  10. }


  11. void at24cxx_page_write(short Add,char *data,int len)
  12. {
  13.      int i;
  14.      
  15.      i2c1_start();
  16.      
  17.      i2c1_send_byte(0xAE);

  18.      i2c1_send_byte((char)((Add>>8)&0xff));
  19.      i2c1_send_byte((char)((Add>>0)&0xff));
  20.      
  21.      // 缺一个最大页写限制
  22.      
  23.      for(i = 0;i < len;i++)
  24.         i2c1_send_byte(data[i]);

  25.      i2c1_stop();   
  26. }


  27. char at24cxx_byte_read(short Add)
  28. {
  29.      char Recv = 0;
  30.      
  31.      i2c1_start();
  32.      
  33.      i2c1_send_byte(0xAE);
  34.      
  35.      i2c1_send_byte((char)((Add>>8)&0xff));
  36.      i2c1_send_byte((char)((Add>>0)&0xff));
  37.      
  38.      i2c1_start();
  39.      i2c1_send_byte(0xAF);
  40.      
  41.      Recv = i2c1_read_byte(0);
  42.      
  43.      i2c1_stop();
  44.      
  45.      return Recv;
  46. }

  47. int at24cxx_sequence_read(short Add,char *buff,int len)
  48. {
  49.      char Recv = 0;
  50.      int i;
  51.      
  52.      i2c1_start();
  53.      
  54.      i2c1_send_byte(0xAE);
  55.      
  56.      i2c1_send_byte((char)((Add>>8)&0xff));
  57.      i2c1_send_byte((char)((Add>>0)&0xff));
  58.      
  59.      i2c1_start();
  60.      i2c1_send_byte(0xAF);
  61.      
  62.      // 限制只有存储器大小范围
  63.      
  64.      do
  65.      {
  66.         buff[i] = i2c1_read_byte(1);
  67.         i++;
  68.      }while(i<len - 1);
  69.      
  70.      buff[i] = i2c1_read_byte(0);
  71.      
  72.      i2c1_stop();
  73.      
  74.      return i;
  75. }


  76. //------------------------------------------------------------------------------
  77. void delay_ms(void)
  78. {
  79.     int i;
  80.    
  81.     for(i = 0;i < 1000000;i++)
  82.         __ASM("NOP");
  83. }

  84. void i2c1_gpio_test(void)
  85. {
  86.     char u;
  87.     char t[3] = {0x04,0x33,0x14};
  88.     char r[3];
  89.   
  90.     at24cxx_page_write(0x0001,t,3);
  91.     delay_ms();
  92.     at24cxx_byte_write(0x0004,0x34);
  93.     delay_ms();
  94.     at24cxx_byte_write(0x0005,0x03);
  95.       
  96.     delay_ms();
  97.    
  98.     u = at24cxx_byte_read(0x0033);
  99.     delay_ms();
  100.     u = at24cxx_byte_read(0x0001);
  101.     delay_ms();
  102.     at24cxx_sequence_read(0x0002,r,4);
  103.     delay_ms();
  104.     u = at24cxx_byte_read(0x0004);
  105. }

  106. // end of file -----------------------
强者为尊,弱者,死无葬身之地
点赞  2015-9-6 23:31
分享铸就美好未来。。。
点赞  2015-9-6 23:38

别光顾着竖拇指。
漏了很多东西没处理好。

说说看,能想到多少
强者为尊,弱者,死无葬身之地
点赞  2015-9-6 23:51
发完后,洗澡时才想到遗漏了很多要补充的:

    at24cxx 系列不同容量的片子 在很多操作的地方存在差异,比如
    1.设备地址 A0 A1 A2;
    2.存储地址的字节数;
    3.不同容量的地址范围,页写最大长度;
    另外,还需要多增加一些错误标志位,既然采用 no-ack这个写法就要继续,比如至少目前想到的
超出读写地址范围,这些应以位记录;
   
    这些,基本上都和不同容量 型号 有关,而这个源码中只考虑到不同容量的时序延时上的差别,其他
都还没考虑到,待后续完善;
    同时,初始化函数应增加一个和型号有关的形参;
强者为尊,弱者,死无葬身之地
点赞  2015-9-6 23:58
STM32出货量这么大,况且IIC不是什么复杂模块,还能不稳定?
training
点赞  2015-9-7 08:15
主机怎么的也稳定吧,官方有个appnote还有代码
从机不太容易,基本上问题多多,主要还是用户不能理解设计者的意图,对于那些个标志/状态的理解
电工
点赞  2015-9-7 08:54
按偶的理解,他那个标志啥的就是查地太细,从位上查ack之类的,活活累死,连发个开始信号都要检查状态,真的被它玩死,这是以前看stm8的库的时候看到的。
点赞  2015-9-7 08:57
后来,发现正点原子也吐槽它,我就有点奇怪查了一下百度,果然问题多多。
点赞  2015-9-7 08:58
时序那个实现的傻逼方案先不说。就说,这玩意一旦开了中断就必须最高优先级,否则来几个死几个
点赞  2015-9-7 08:59
好像都说stm32的硬件iic不好用
点赞  2015-9-8 10:04
STM的IIC确实坑人
do while有时能解决
So TM what......?
点赞  2015-9-8 18:58
引用: ljj3166 发表于 2015-9-8 18:58
STM的IIC确实坑人
do while有时能解决

那样也太他妈傻逼了。那我还不如不用
强者为尊,弱者,死无葬身之地
点赞  2015-9-10 08:33
引用: 白丁 发表于 2015-9-7 08:15
STM32出货量这么大,况且IIC不是什么复杂模块,还能不稳定?

据说,它是为了绕开philips在i2c上的专利,才这么蛋疼。
强者为尊,弱者,死无葬身之地
点赞  2015-9-10 08:34
引用: 辛昕 发表于 2015-9-10 08:34
据说,它是为了绕开philips在i2c上的专利,才这么蛋疼。

绕开专利也不至于这样吧,IIC这么普遍别家也没这样干啊
training
点赞  2015-9-10 08:39
引用: 白丁 发表于 2015-9-10 08:39
绕开专利也不至于这样吧,IIC这么普遍别家也没这样干啊

嗯,有道理,所以我觉得这只能说明stm32这个做的确实挺操蛋,然后就赖....
怪飞利浦咯.......
强者为尊,弱者,死无葬身之地
点赞  2015-9-10 09:40
呃,最标准的处理是,把die抠出来,在iic模块区域中断产生电路输出端光罩一个同步锁存器,注意工艺匹配和器件的一致性,并完成管脚ESD,然后再封装起来,焊接上板。嗯,这样可以基本修正硬件八哥。 本帖最后由 ljj3166 于 2015-9-10 11:05 编辑
点赞  2015-9-10 11:03
12下一页
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复