你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32Cube HAL IIC

[复制链接]
STMCU小助手 发布时间:2022-4-7 15:15
一、原理图

MFP5}ZMDG56)O0KGS3_6K95.png

—— —— —— —— —— —— —— —— 软件IIC—— —— —— —— —— —— —— ——

二、 CubeMX配置

Step1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。

M@689[00TAE~R73KG~PJQTU.png

Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。

QTLDH1P5O$FX0EVMBGKP_XC.png

QSQI`M$I}P]YBGY]KI6I)_4.png

Step3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。)

5)KJ}19@Y{PF`%IU20PTMDJ.png

  Step4.串口配置(主要为了在串口调试助手显示读写数据),因为没有用到中断和DMA所以我们就不过多讲解。




Step5.配置软件IIC所需要的的GPIO口,需要注意的是这边要设置成开漏输出模式,上拉模式。这里选用PB6,PB7作为SCL,SDA,因为是软件IIC,所以这里的选择接口是不受限制的。

(05F88{5QTDXV%@)[L(F`KK.png

到这里关于软件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。

三、添加功能代码

1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。重定向代码如下(usart.c)

  1. //重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
  2. int fputc(int ch, FILE *f)
  3. {
  4.         /* 发送一个字节数据到串口DEBUG_USART */
  5.         HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);        
  6.         
  7.         return (ch);
  8. }

  9. //重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
  10. int fgetc(FILE *f)
  11. {               
  12.         int ch;
  13.         HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);        
  14.         return (ch);
  15. }
复制代码


2、为了把IIC和EEPROM的相关接口区分开,方便以后移植到其他IIC设备。在cubemx自动生成的gpio.c/gpio.h IIC的相关接口,同时新建了i2c_ee.c/i2c_ee.c中写读写EEPROM的接口。

(1)IIC相关接口的书写。

首先是在gpio.h中,对SCL/SDA相关IO口的写0/1和读取进行宏定义,提高代码可读性。以及等会涉及到读写位进行宏定义。最后就是函数的声明。


  1. #define I2C_SCL_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
  2. #define I2C_SCL_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);

  3. #define I2C_SDA_1() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
  4. #define I2C_SDA_0() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);

  5. #define I2C_SDA_READ() HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7)
  6. #define I2C_WR 0 //写控制bit
  7. #define I2C_RD 1 //读控制bit

  8. void MX_GPIO_Init(void);

  9. void i2c_Start(void);
  10. void i2c_Stop(void);
  11. void i2c_SendByte(uint8_t HS_Byte);
  12. uint8_t i2c_ReadByte(void);
  13. uint8_t i2c_WaitAck(void);
  14. void i2c_Ack(void);
  15. void i2c_NAck(void);
  16. uint8_t i2c_CheckDevice(uint8_t _Address);
复制代码

然后就是在gpio.c中,把我们刚才声明的函数进行完善。

  1. /*延时函数*/
  2. static void i2c_Delay(void)
  3. {
  4.         uint8_t i;

  5.         /* 
  6.                  下面的时间是通过逻辑分析仪测试得到的。
  7.     工作条件:CPU主频72MHz ,MDK编译环境,1级优化

  8.                 循环次数为10时,SCL频率 = 205KHz
  9.                 循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
  10.                  循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us
  11.         */
  12.         for (i = 0; i < 10; i++);
  13. }
  14. /* CPU发起I2C总线启动信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换*/
  15. void i2c_Start(void)
  16. {
  17.         I2C_SCL_1();
  18.         I2C_SDA_1();
  19.         i2c_Delay();
  20.         I2C_SDA_0();
  21.         i2c_Delay();
  22.         I2C_SCL_0();
  23. }
  24. /* CPU发起I2C总线停止信号:当 SCL 是高电平时 SDA 线由低电平向高电平切换*/
  25. void i2c_Stop(void)
  26. {
  27.         I2C_SCL_1();
  28.         I2C_SDA_0();
  29.         i2c_Delay();
  30.         I2C_SDA_1();
  31.         i2c_Delay();
  32.         I2C_SCL_0();
  33. }
  34. /*CPU向I2C总线设备发送8bit数据*/
  35. void i2c_SendByte(uint8_t HS_Byte)
  36. {
  37.         uint8_t i;
  38.         for(i=0;i<8;i++)
  39.         {
  40.                
  41.                 if(HS_Byte&0X80)//先发送高位
  42.                 {
  43.                         I2C_SDA_1();
  44.                 }
  45.                 else
  46.                 {
  47.                         I2C_SDA_0();
  48.                 }
  49.                 i2c_Delay();
  50.                 I2C_SCL_1();
  51.                 i2c_Delay();
  52.                 I2C_SCL_0();
  53.                 if(i==7)//发送完成一个字节,释放数据总线,接收应答/非应答信号
  54.                 {
  55.                         I2C_SDA_1();
  56.                 }
  57.                 HS_Byte<<=1;//左移一位
  58.                 i2c_Delay();
  59.         }
  60. }
  61. /*CPU从I2C总线设备读取8bit数据*/
  62. uint8_t i2c_ReadByte(void)
  63. {
  64.         uint8_t SH_Byte, i;
  65.         
  66.         SH_Byte=0;
  67.         for(i=0;i<8;i++)//先读取高位
  68.         {
  69.                 SH_Byte <<= 1;
  70.                 I2C_SCL_1();
  71.                 i2c_Delay();
  72.                 if(I2C_SDA_READ())
  73.                 {
  74.                         SH_Byte++;
  75.                 }
  76.                 I2C_SCL_0();
  77.           i2c_Delay();
  78.         }
  79.         return SH_Byte;
  80. }
  81. /*CPU产生一个时钟,并读取器件的ACK应答信号*/
  82. uint8_t i2c_WaitAck(void)
  83. {
  84.         uint8_t re;

  85.         I2C_SDA_1();        // CPU释放SDA总线
  86.         i2c_Delay();
  87.         I2C_SCL_1();        //CPU驱动SCL = 1, 此时器件会返回ACK应答
  88.         i2c_Delay();
  89.         if (I2C_SDA_READ())        // CPU读取SDA口线状态
  90.         {
  91.                 re = 1;
  92.         }
  93.         else
  94.         {
  95.                 re = 0;
  96.         }
  97.         I2C_SCL_0();
  98.         i2c_Delay();
  99.         return re;
  100. }
  101. /*CPU产生1个ACK信号*/
  102. void i2c_Ack(void)
  103. {
  104.         I2C_SDA_0();        // CPU驱动SDA = 0 ,应答信号
  105.         i2c_Delay();
  106.         I2C_SCL_1();        // CPU产生1个时钟
  107.         i2c_Delay();
  108.         I2C_SCL_0();
  109.         i2c_Delay();
  110.         I2C_SDA_1();        // CPU释放SDA总线
  111. }
  112. /*CPU产生1个NACK信号*/
  113. void i2c_NAck(void)
  114. {
  115.         I2C_SDA_1();        // CPU驱动SDA = 1 ,非应答信号
  116.         i2c_Delay();
  117.         I2C_SCL_1();        // CPU产生1个时钟
  118.         i2c_Delay();
  119.         I2C_SCL_0();
  120.         i2c_Delay();
  121. }
  122. /*检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在*/
  123. uint8_t i2c_CheckDevice(uint8_t Addr)
  124. {
  125.         uint8_t ucAck;

  126.         i2c_Start();                //发送启动信号

  127.         /* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
  128.         i2c_SendByte(Addr|I2C_WR);
  129.         ucAck = i2c_WaitAck();        // 检测设备的ACK应答

  130.         i2c_Stop();                        // 发送停止信号

  131.         return ucAck;
  132. }
复制代码


(2)使用刚才写好的IIC的接口编写对EEPROM的读写、擦除等程序。

还是一样在i2c_ee.h中,进行相关代码宏定义,提高代码可读性。还有就是函数的声明。

  1. * AT24C02 2kb = 2048bit = 2048/8 B = 256 B
  2. * 32 pages of 8 bytes each
  3. *
  4. * Device Address
  5. * 1 0 1 0 A2 A1 A0 R/W
  6. * 1 0 1 0 0  0  0  0 = 0XA0
  7. * 1 0 1 0 0  0  0  1 = 0XA1
  8. */

  9. /* AT24C01/02每页有8个字节
  10. * AT24C04/08A/16A每页有16个字节
  11. */
  12.         

  13. #define EEPROM_DEV_ADDR                        0xA0                // 24xx02的设备地址
  14. #define EEPROM_PAGE_SIZE                  8                          // 24xx02的页面大小
  15. #define EEPROM_SIZE                                  256                          // 24xx02总容量


  16. uint8_t ee_CheckOk(void);
  17. uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize);
  18. uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize);
  19. void ee_Erase(void);
  20. uint8_t ee_Test(void);
复制代码


在i2c_ee.c完善相关函数

  1. /* 判断串行EERPOM是否正常 */
  2. uint8_t ee_CheckOk(void)
  3. {
  4.         if(i2c_CheckDevice(EEPROM_DEV_ADDR))
  5.         {
  6.                 i2c_Stop();//失败后,切记发送I2C总线停止信号
  7.                 return 0;//设备异常
  8.         }
  9.         else
  10.         {
  11.                 return 1;//设备正常
  12.         }
  13. }

  14. /*从串行EEPROM指定地址处开始读取若干数据*/
  15. uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
  16. {
  17.         uint16_t i;
  18.         i2c_Start();//第1步:启动I2C总线
  19.         
  20.         i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发送地址+写指令
  21.         if(i2c_WaitAck()!=0)//第3步:判断设备是否响应
  22.         {
  23.                 goto res_error;
  24.         }

  25.         i2c_SendByte(_usAddress);//第4步:发送字节地址
  26.         if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
  27.         {
  28.                 goto res_error;
  29.         }
  30.         
  31.         i2c_Start();//第6步:重启I2C总线,开始读数据
  32.         
  33.         i2c_SendByte(EEPROM_DEV_ADDR|I2C_RD);//第7步:发送地址+读指令
  34.         if(i2c_WaitAck()!=0)//第8步:判断设备是否响应
  35.         {
  36.                 goto res_error;
  37.         }
  38.         
  39.         for(i=0;i<_usSize;i++)
  40.         {
  41.                 _pReadBuf=i2c_ReadByte();//第9步:开始读取数据
  42.                 if(i!=_usSize-1)//第10步:在接收_usSize-1个字节前都发送应答信号,最后一个字节发送非应答信号
  43.                 {
  44.                         i2c_Ack();
  45.                 }
  46.                 else
  47.                 {
  48.                         i2c_NAck();
  49.                 }
  50.         }
  51.         
  52.         i2c_Stop();//第11步:停止I2C总线
  53.         return 1;//读取成功

  54.         res_error:
  55.                         printf("\r\n响应超时\r\n");
  56.                         return 0;//读取失败
  57. }

  58. /*向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率*/
  59. uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
  60. {
  61.         uint16_t i,m,Addr;
  62.         Addr=_usAddress;

  63.         for(i=0;i<_usSize;i++)
  64.         {
  65.                
  66.                 if((i==0)||(Addr&(EEPROM_PAGE_SIZE - 1))==0)//当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址
  67.                 {
  68.                         i2c_Stop();//发停止信号,启动内部写操作
  69.                         for(m=0;m<1000;m++)//通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms,CLK频率为200KHz时,查询次数为30次左右
  70.                         {
  71.                                 i2c_Start();//第1步:发起I2C总线启动信号
  72.                                 
  73.                                 i2c_SendByte(EEPROM_DEV_ADDR|I2C_WR);//第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
  74.                                 
  75.                                 if(i2c_WaitAck()==0)//第3步:判断设备是否响应
  76.                                 {
  77.                                         break;
  78.                                 }
  79.                         }
  80.                         if(m==1000)//响应超时
  81.                         {
  82.                                 goto res_error;
  83.                         }
  84.                         
  85.                         i2c_SendByte(Addr);// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址
  86.                         if(i2c_WaitAck()!=0)//第5步:判断设备是否响应
  87.                         {
  88.                                 goto res_error;
  89.                         }
  90.                 }
  91.                
  92. <i>                i2c_SendByte(_pWriteBuf);//第6步:开始写入数据
  93.                 if(i2c_WaitAck()!=0)//第7步:判断设备是否响应
  94.           {
  95.                   goto res_error;
  96.           }
  97.                 Addr++;
  98.         }
  99.         
  100.         i2c_Stop();//第8步:停止I2C总线
  101.         return 1;//写入成功
  102.         
  103.         res_error:
  104.                         printf("\r\n响应超时\r\n");
  105.                         return 0;//写入失败
  106. }

  107. /* 擦除EEPROM */
  108. void ee_Erase(void)
  109. {
  110.         uint16_t i;
  111.         uint8_t buf[EEPROM_SIZE];
  112.         
  113.         for (i = 0; i < EEPROM_SIZE; i++)// 填充缓冲区
  114.         {
  115.                 buf = 0xFF;
  116.         }
  117.         
  118.         if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)//写EEPROM, 起始地址 = 0,数据长度为 256
  119.         {
  120.                 printf("擦除eeprom出错!\r\n");
  121.         }
  122.         else
  123.         {
  124.                 printf("擦除eeprom成功!\r\n");
  125.         }
  126. }

  127. /* 测试程序 */
  128. uint8_t ee_Test(void)
  129. {
  130.         uint16_t i;
  131.         uint8_t write_buf[EEPROM_SIZE];
  132.   uint8_t read_buf[EEPROM_SIZE];

  133.   if (ee_CheckOk() == 0)// 没有检测到EEPROM
  134.         {
  135.                 printf("没有检测到串行EEPROM!\r\n");               
  136.                 return 0;
  137.         }

  138.         for (i = 0; i < EEPROM_SIZE; i++)//填充测试缓冲区
  139.         {               
  140.                 write_buf = i;
  141.         }
  142.         
  143.   if (ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)//向EEPROM写入数据
  144.         {
  145.                 printf("写eeprom出错!\r\n");
  146.                 return 0;
  147.         }
  148.         else
  149.         {               
  150.                 printf("写eeprom成功!\r\n");
  151.         }

  152.   HAL_Delay(1);//写完之后需要适当的延时再去读,不然会出错

  153.   if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)//从EEPROM读取数据
  154.         {
  155.                 printf("读eeprom出错!\r\n");
  156.                 return 0;
  157.         }
  158.         else
  159.         {               
  160.                 printf("读eeprom成功,数据如下:\r\n");
  161.         }

  162.   for (i = 0; i < EEPROM_SIZE; i++)//比较读取和写入的数据是否一致
  163.         {
  164.                 if(read_buf != write_buf)
  165.                 {
  166.                         printf("0x%02X ", read_buf);
  167.                         printf("错误:EEPROM读出与写入的数据不一致");
  168.                         return 0;
  169.                 }
  170.     printf(" %02X", read_buf);
  171.                
  172.                 if ((i & 15) == 15)
  173.                 {
  174.                         printf("\r\n");        
  175.                 }               
  176.         }
  177.   printf("eeprom读写测试成功\r\n");
  178.   return 1;
  179. }</i>
复制代码


3、在主函数中调用i2c_ee.c中的测试函数即可。

  1. ee_Test();
复制代码

关于软件IIC有几点需要注意的:

1、我们传输速率的设置:硬件IIC,标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。这里使用软件IIC的设置SCL频率200kbit/s左右,大家可以根据需要设置gpio.c中i2c_Delay(void)的延时时间来修改SCL频率。

2、在i2c_ee.c 写ee_ReadBytes、ee_WriteBytes,可以发现步骤有一点区别,ee_WriteBytes在多了一个for循环来重复判断设备内的写操作是否完成,而不是直接采用判断一次。虽然后者的写法在实验结果上也没有出错,但是为了代码严谨性,避免不必要的错误,选用前者。

3、在写入数据到EEPROM的时候,根据数据的大小,如果需要读写的话,需要进行一定的延时,否则会错。

—— —— —— —— —— —— —— ——硬件IIC—— —— —— —— —— —— —— ——

二、 CubeMX配置

step1-step4与上面软件IIC模式一致

step5.IIC外设配置,这里选用的I2C1,因为使用的主机模式,所以从机参数默认即可。

1Z_]S~I87CLM4TKQG3HOB.png

到这里关于硬件IIC参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。

三、添加功能代码

1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。(代码同上: 软件IIC)

2、在i2c.h文件中添加相关的宏定义以及函数声明。

  1. #define EEPROM_ADDRESS                        0xA0                // 24xx02的设备地址
  2. #define EEPROM_PAGESIZE                  8                          // 24xx02的页面大小
  3. #define  DATA_Size                        7//256   
  4. #define  EEP_Firstpage  0x02   // 0x00               

  5. void MX_I2C1_Init(void);
  6. uint8_t I2C_Test(void);
  7. void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite);
  8. uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr);
  9. uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite);
  10. uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead);
复制代码

3、在i2c.c实现EEPROM的读写函数,以及用于测试的测试函数。

  1. /*将缓冲区中的数据写到I2C EEPROM中*/
  2. void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
  3. {
  4.   uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;

  5.   Addr = WriteAddr % EEPROM_PAGESIZE;//判断写入的首地址是否与EEPROM页的首地址对齐,0为对齐
  6.   count = EEPROM_PAGESIZE - Addr;//计算从写入的首地址需要写多少数据才能填满当前页
  7.   NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页(地址对齐的情况)
  8.   NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;//计算写完完整页剩下的数据个数(地址对齐的情况)

  9.   if(Addr == 0) //判断写入的首地址是否与页地址对齐
  10.   {
  11.     if(NumOfPage == 0) //如果页对齐,判断数据是否不满一页
  12.     {
  13.       I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不满一页,直接写入数据
  14.     }
  15.     else  //在数据满一页的情况下,通过地址自增方式,循环写入数据(页写入的形式)
  16.     {
  17.       while(NumOfPage--)//循环写入数据:先写入完整页
  18.       {
  19.         I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
  20.         WriteAddr +=  EEPROM_PAGESIZE;
  21.         pBuffer += EEPROM_PAGESIZE;
  22.       }

  23.       if(NumOfSingle!=0)//循环写入数据:再写入不满一页的数据
  24.       {
  25.         I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  26.       }
  27.     }
  28.   }
  29.   else
  30.   {
  31.     if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
  32.     {
  33.                         if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页
  34.                         {
  35.                                 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
  36.                         }
  37.                         else
  38.                         {
  39.                                 I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
  40.                                 I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
  41.                         }
  42.     }

  43.     else
  44.     {
  45.                         /*如果数据满一页,对数据进行分离*/
  46.       NumByteToWrite -= count;//扣除第一页数据个数
  47.       NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;//计算写入数据需要写几个完整页
  48.       NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;        //计算写完完整页剩下的数据个数

  49.       if(count != 0)
  50.       {  
  51.         I2C_EE_PageWrite(pBuffer, WriteAddr, count);//写入首页数据
  52.         WriteAddr += count;//写地址自增
  53.         pBuffer += count;//缓冲区指针自增
  54.       }

  55.       while(NumOfPage--)//依次写入完整页的数据
  56.       {
  57.         I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
  58.         WriteAddr +=  EEPROM_PAGESIZE;//写地址自增
  59.         pBuffer += EEPROM_PAGESIZE; //缓冲区指针自增
  60.       }
  61.       if(NumOfSingle != 0)//判断最后一页的数据是否是填满完整一页的
  62.       {
  63.         I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); //写入最后一页的数据
  64.       }
  65.     }
  66.   }  
  67. }

  68. /*写一个字节到I2C EEPROM中*/
  69. uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
  70. {
  71.         HAL_StatusTypeDef status = HAL_OK;

  72.         status = HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100);

  73.         /* 检测IIC外设是否传输完成,并准备进行下一次通信 */
  74.         if(status != HAL_OK)
  75.         {
  76.         /* Execute user timeout callback */
  77.         }
  78.         while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
  79.         {
  80.                
  81.         }
  82.         /* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/
  83.         while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);

  84. //        while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
  85. //        {
  86. //               
  87. //        }
  88.         return status;
  89. }

  90. /*在EEPROM的一个写循环中可以写多个字节,但一次写入
  91. 的字节数不能超过EEPROM页的大小,AT24C02每页有8个字节*/
  92. uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
  93. {
  94.         HAL_StatusTypeDef status = HAL_OK;
  95.         status=HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);
  96.         /* 检测IIC外设是否传输完成,并准备进行下一次通信 */
  97.         while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
  98.         {
  99.                
  100.         }
  101.         /* 检测目标设备是否准备好进行通信(即目标设备是否在进行读写操作)*/
  102.         while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDRESS, 300, 300) == HAL_TIMEOUT);

  103. //        while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY)
  104. //        {
  105. //               
  106. //        }
  107.         return status;
  108. }

  109. /*从EEPROM里面读取一块数据 */
  110. uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead)
  111. {
  112.         HAL_StatusTypeDef status = HAL_OK;
  113.         
  114.         status=HAL_I2C_Mem_Read(&hi2c1,EEPROM_ADDRESS,ReadAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t *)pBuffer, NumByteToRead,1000);

  115.         return status;
  116. }

  117. uint8_t I2C_Test(void)
  118. {
  119.         uint16_t i;

  120.         printf("写入的数据");

  121.         for ( i=0; i<DATA_Size; i++ ) //填充缓冲
  122.         {   
  123.                 I2c_Buf_Write =i;
  124.                 printf("0x%02X ", I2c_Buf_Write);
  125.                 if(i%16 == 15)   
  126.                 printf("\n\r");   
  127.         }

  128.         //将I2c_Buf_Write中顺序递增的数据写入EERPOM中
  129.         I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, DATA_Size);

  130.         printf("读出的数据");
  131.         //将EEPROM读出数据顺序保持到I2c_Buf_Read中
  132.         I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, DATA_Size);
  133.         //将I2c_Buf_Read中的数据通过串口打印
  134.         for (i=0; i<DATA_Size; i++)
  135.         {        
  136.                 if(I2c_Buf_Read != I2c_Buf_Write)
  137.                 {
  138.                         printf("0x%02X ", I2c_Buf_Read);
  139.                         printf("错误:I2C EEPROM写入与读出的数据不一致");
  140.                         return 0;
  141.                 }
  142.                 printf("0x%02X ", I2c_Buf_Read);
  143.                 if(i%16 == 15)   
  144.                 printf("\n\r");

  145.         }
  146.         printf("I2C(AT24C02)读写测试成功");
  147.         return 1;
  148. }
复制代码


4、在主函数中添加测试代码验证功能是否正常。

  1. I2C_Test();
复制代码

以上程序主要参考野火的硬件IIC程序,但是在页写入程序中添加了一个判断,用于判断在地址不对齐,且数据不满一页的情况下,数据是否跨页。数据不跨页直接写入数据,跨页分两次写入数据。代码如下:

  1. if(NumOfPage== 0) //如果页不对齐,判断数据是否不满一页
  2.     {
  3.                         if(NumOfSingle<=count)//如果不满一页,判断数据是否跨页
  4.                         {
  5.                                 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);//如果不跨页,直接写入数据
  6.                         }
  7.                         else
  8.                         {
  9.                                 I2C_EE_PageWrite(pBuffer, WriteAddr, count);//如果跨页,先写首页数据,再写次页数据
  10.                                 I2C_EE_PageWrite(pBuffer+count, WriteAddr+count, NumOfSingle-count);
  11.                         }
  12.     }
复制代码

同时需要注意的EEPROM的几个关键特性:(AT24C02)

在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。

还有需要有一个页的概念,页写入的时候,写入的数据有跨页现象,需要进行数据分离处理,否则数据会重复覆盖当前页(发送的内存地址所在页),而不会到下一页,这也是我们代码中进行页写入的时候提前处理数据的原因。

[133MADOI6VDUZL618T(WUC.png

写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms。

这里总结软件IIC和硬件IIC的优缺点:

1、软件IIC可灵活选用引脚;硬件IIC引脚固定。                                                                        

2、软件IIC需要通过代码模拟时序以及读写;硬件IIC只需调用库函数。                                    

3、硬件IIC效率更高,可以使用DMA ,中断。                                                                           

4、软件IIC代码相对较多,但是流程更清晰;硬件IIC由于使用很多封装函数,处理的过程不够直观,不易理解。   


收藏 评论0 发布时间:2022-4-7 15:15

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版