STM32学习--IIC通讯

本文详细介绍了IIC协议的原理,包括开始信号、结束信号、应答信号的产生,以及硬件IIC和软件IIC的区别。同时,文章还深入讲解了24C02EEPROM的工作机制,如地址分配、读写模式,并提供了C语言实现的IIC通信函数示例。此外,还提到了使用HAL库进行I2C操作的方法。
摘要由CSDN通过智能技术生成

什么是IIC?

IIC == Inter Integrated Circuit ==内部集成电路 == I2C;

是Philips公司开发的一种通讯协议

IIC有什么用?

通讯都是用来传数据的,IIC也不例外;

常见的应用有EEPROM读写、mems传感器数据获取、摄像头配置。。。。。。

IIC分类

硬件IIC:芯片里面把IIC的通讯协议通过电路实现了,有专用的IIC引脚,只需要配置下寄存器就能实现IIC通讯;

软件IIC:根据IIC通讯的时序协议,自己找两个引脚,按照IIC协议来比划一下,搞得跟硬件IIC差不多。

IIC物理层

所谓物理层就是硬件构成了

1、得有两根线,一根线是时钟线,一根线是数据线;

2、至少得有1个发送数据的对象,和1个接收数据的对象;

3、这个总线上的每个器件得有一个不同的地址,方便知道是谁发的数据,数据发给谁;

4、数据线和时钟线上一般都搞个上拉电阻,没人用的时候时钟线和数据线上都是高电平。

SCL就是IIC总线的时钟线,这个时钟线上的时钟,只能由主设备(Master)产生,常见的主设备就是各种单片机,处理器了;

SDA计时IIC的数据线,数据可以由主设备发给从设备(Slave),这个过程叫“写”,也可以由从设备发给主设备,这个过程叫“读”;

主机(master),就是老板,上班时间、下班时间、都是他来决定的,指令都是他来发出来的,他要找谁干啥,谁就干啥。

从机(Slave),就是员工,这个slave很形象,就是奴,不能有自己的想法,一切听老板的。

为什么要在总线上加上拉电阻?

有了这个上拉电阻,就可以保证IIC总线没人使用的时候一直是高电平;

IIC接口

漏极开路,Open Drain,也就是高阻态,可独立的输入输出低电平和高阻态,如果要使用高电平,需要外部的上拉电阻。

高阻态,三态门电路的一种,高电平、低电平、高阻态,高阻态可理解为开路,

IIC总线上没开启的设备都是高阻态,相当于开路,只有开启的设备才能正常通信

IIC协议

怎么开始

SCL为高电平的时候,SDA由高电平变为低电平,开始传送数据

怎么结束

SCL为高电平的时候,SDA由低电平变为高电平,停止传输数据

怎么应答

接收者收到8个bit数据后,想发送者发出低电平脉冲,表示收到数据

怎么非应答

IIC通讯流程

1、空闲的时候,也就是没有数据传输的时候,SCL和SDA都是高电平(这是拜上拉电阻所赐);

2、要开始了,就在上图的Start那个点,SCL是高电平,SDA从高电平变成低电平(这是主机干得);

3、数据传输,主设备产生时钟信号,就是SCL上的时钟电平,同时SDA线上就有数据,至于这个数据是主设备发给从设备的,还是从设备发给主设备的,暂时不用理会。每个BIT位的数据是0,还是1,这很重要,所以呢,规定每个BIT的数据都要在时钟处于搞电平的时候采样,采到的是1,那就是1,采到的是0 ,那这一位就是0;

4、只要SDA数据线上发了8个BIT的数据,就要发出一个应答信号ACK,谁接收数据,谁就要应答,应答信号是个低电平。

5、可以结束了,就在上图的Stop点,SCL为高电平,SDA从低电平变为高电平(这也是主机干的);

6、现在总线又恢复到空闲状态了,SCL和SDA都是高电平。

从上面可以看出,

什么时候开始传输数据是主机说的算的,什么时候停止传输数据也是主机说的算的,什么时候对数据进行采样,还是主机说的算的;

从机只有在应答和传输数据的时候,才能改变SDA线的状态,真是个好奴才啊。

上面只是传输一个字节的过程,帮助理解IIC协议是怎么规定起始信号、结束信号、应答信号。

IIC完整的通讯过程

1、总线是空闲状态,SCL=1,SDA =1;

2、要开始传输数据了,此时SCL还是高电平,SCL=1,主机将SDA从1变成0;

3、跟哪个从机通讯,把从机的地址发出去。一般地址是8个bit(也有16个bit的),这8个bit其实真是的地址是7个bit,最后1个bit是用来表示读或者写的。1表示读,0表示写;这个过程相当于主机往SDA上发了8个bit的数据(地址也是数据啊);

4、主机发地址的过程,相当于在找从机,从机是要给应答信号的,就是ACK,就像奴隶主喊奴隶,奴隶得回应一下,你老板喊你,你也得先回答声A吧;

5、应答之后,就是要传输数据了,如果第3步中发的地址是写操作,那就由主机来控制SDA的电平变化,如果第3步中发的地址是读操作,那就由从机来控制SDA的电平变化;

6、每次8bit的数据传输完成,都要有个应答信号,谁接收数据,谁来应答

7、完事之后,SCL就是高电平了,主机把SDA从低电平拉高,表示结束。

主机写的过程

奴隶主(master):都给我站好了(start);

奴隶主(master):奴隶A,吩咐你几件事。(写从设别地址)

奴隶A(slave):好的(应答信号ACK);(从设备应答)

奴隶主(master):事情A是XXXX;(主机写数据)

奴隶A(slave):好的(从机应答信号ACK);

奴隶主(master):事情B是XXXX;(主机继续写)

奴隶A(slave):好的(从机应答信号ACK);

奴隶主(master):行了,就这两件事,解散吧(stop);

主机读的过程(读比写要麻烦一些)

读一个字节

奴隶主(master):都给我站好了(start);

奴隶主(master):奴隶A,我要问你一个问题(写入从设备的地址,后面还要写东西)

奴隶A(slave):好的,你问吧(应答信号ACK);

奴隶主(master):告诉我,你儿子多大了(写入从设备的寄存器地址)

奴隶A(slave):好的(应答信号ACK);

奴隶A(slave):我儿子今年三岁了;(从机返回对应寄存器的数据)

奴隶主(master):知道了,没事了(主设备非应答No ACK)

奴隶主(master):都散了吧(Stop);

IIC的传输过程只需要牢记以下几点就可以了

1、开始信号和结束信号都是主机来控制的;

2、SCL上的时钟信号也是主机控制的;

3、谁接收数据,谁就要发出个应答信号ACK,ACK是低电平;

4、发送数据都是一个字节一个字节的发送的,每个字节都要有应答

起始信号、结束信号、应答信号、非应答信号的C语言实现

二、EEPROM

          EEPROM==Electrically Erasable Programmable Read-Only Memory==电擦除可编程只读存储器。跟U盘差不多,可以存数据,电关掉数据还在。常见的型号24C02 、24C08、24C16

 C02表示2*1024 == 2K bit的存储大小

 C08表示8*1024 == 8K bit 的存储大小

C02每页可以存8个字节的数据;

C04、C08、C16每页可以存16个字节的数据;

大家应该都知道,存储器最小的存储单位是bit,一个字节(Byte)== 8bit;

可以想象EEPROM的一页,跟你写字的方格本是不是很像

24C02的一页

     每页8行8列,一行表示一个字节(Byte),每个小方格表示一个bit(一位)。24C02 EEPROM有32个像上面的页(page)。

24C02存储空间2k bit == 8bit *256;8bit就是上图的1行,2k就表示有256行,一行就是一个数据地址,所以24C02的数据地址分布是0x00 -- 0XFF

        24C02支持字节写模式和页写模式:

        字节写模式:就是一次写一个字节的数据,也就是8个bit的数据,也就是上图的一行。

        页写模式:就是一次写一页的数据,就是8个字节的数据。单不能跨页写数据,比如你一下子写9个字节的数据,那先在一页里面写8个数据,第9个数据就把第一个数据覆盖了,他不会自动跳到下一页去写。

EEPROM器件的设备地址和数据地址

设备地址可以理解为你们小区的地址,比如人民路88号;

数据地址可以理解为你家的门牌号,比如2幢801室;

因此可以把24C02看成一个小区,

一个小区有32幢房子,每个幢有8套房子,每套房子有8个房间。

24C02有32页,每页有8个字节的数据,每个字节有8个bit。

房子交易的最小单元是套吧,开发商应该不会只卖你一个房间吧?

同理24C02读写的最小单位也是1个字节。

24C02也支持整页写,就像你买房子,买一整幢也是可以的,但是不支持跨页写,如果涉及跨页,你可以一个字节一个字节的写

Microchip的24C02B(97年之前的)的IIC时序图

海天芯的24C02 (国产的20年左右的芯片) IIC时序图

开始与结束信号

/**
 * 适用芯片Microchip 24C02B
 * 函数: I2C_Start()
 * 一个_nop_耗时,一个机器周期,针对89C52就是12个时钟周期,晶振为11.0592M的话
 * 一个_nop_就好是12/11.0592=1.085us,5个_nop_,就是5.43us
*/
void I2C_Start()
{
  SDA = 1;  //SDA为高电平
  nops();  //这个延时放着也没关系,从图中可以看出SDA比SCL先高电平
        
  SCL = 1; //把时钟线拉高
  nops(); //SCL拉高之后,要保持一段时间,这个高电平要持续Tsu:STA时间,至少也是4700ns
    
  SDA = 0; //把 SCL 为高电平的时候把SDA拉低
  nops(); //拉低之后需要维持一段时间,T_HD:STA,也是4000ns以上
    
  SCL = 0; //再把SCL拉低
}


/**
 * 适用芯片海天芯 HT24C02
 * 函数: I2C_Start()
 * 一个_nop_耗时,一个机器周期,针对89C52就是12个时钟周期,晶振为11.0592M的话
 * 一个_nop_就好是12/11.0592=1.085us,5个_nop_,就是5.43us
*/
void I2C_Start()
{
  SDA = 1;  //SDA为高电平
  _nop_();  //这个延时放着也没关系,从图中可以看出SDA比SCL先高电平
        
  SCL = 1; //把时钟线拉高
  _nop_(); //SCL拉高之后,要保持一段时间,这个高电平要持续Tsu:STA时间,至少也是4700ns
    
  SDA = 0; //把 SCL 为高电平的时候把SDA拉低
  _nop_(); //拉低之后需要维持一段时间,T_HD:STA,也是4000ns以上
    
  SCL = 0; //再把SCL拉低
}

/**
 * 适用芯片Microchip 24C02B
 * 函数: I2C_Stop()
 * 功能: 停止i2c
 * 一个_nop_耗时,一个机器周期,针对89C52就是12个时钟周期,晶振为11.0592M的话
 * 一个_nop_就好是12/11.0592=1.085us,5个_nop_,就是5.43us
 *
*/
void I2C_Stop()
{
  SCL = 0; //SCL先拉低
  nops(); //这个时间是久了一些,改为一个nop就可以了
    
  SDA = 0; //SDA也要拉低,
  nops(); //这个时间是久了一些,改为一个nop就可以了
    
  SCL = 1;//SCL拉高,
  nops(); //SCL拉高维持时间T_SU:STO,4000ns以上
    
  SDA = 1; //SDA拉高
  nops(); //SDA拉高持续时间T_BUF,4700ns以上
}

/**
 * 适用芯片海天芯 HT24C02
 * 函数: I2C_Stop()
 * 功能: 停止i2c
 * 一个_nop_耗时,一个机器周期,针对89C52就是12个时钟周期,晶振为11.0592M的话
 * 一个_nop_就好是12/11.0592=1.085us,5个_nop_,就是5.43us
 *
*/
void I2C_Stop()
{
  SCL = 0; //SCL先拉低
  _nop_(); 
    
  SDA = 0; //SDA也要拉低,
  _nop_(); 
    
  SCL = 1;//SCL拉高,
  _nop_(); //SCL拉高维持时间T_SU:STO,
    
  SDA = 1; //SDA拉高
  _nop_(); //SDA拉高持续时间T_BUF
}

应答与非应答信号

前面说过,数据发给谁,谁就要给个应答ACK,如果没有发出应答,就表示数据没收到。

这句话本身没毛病,主机给从机发送一个数据,比如MCU给24C02发送一个数据,24C02都会自动发出一个ack信号,表示收到了,如果24C02没有返回ack信号,MCU有理由相信24C02没收到刚才发过去的数据,可以报错,也可以重新发送。

但是,如果从机给主机发数据,比如24C02给MCU发数据,讲道理,24C02每发一个数据,MCU就要发出一个ack信号,但是有个例外,24C02在发最后一个数据的时候(所谓的最后一个,是MCU认为的最后一个),MCU返回的是非应答信号nack(也叫no ack, 叫它非应答和不应答都可以),因为NACK就是让SDA释放总线,就是什么都不做,SDA总线自然就被上拉电阻拉高了。非应答信号之后,紧接着的就是主机发出结束信号。

所以大家要明白这其中的潜规则:

从机只要收到数据,就一定要返回应答信号ack,不返回就是有问题;

主机收到数据也要返回应答信号ack,但主机返回ack信号是为让从机接着发数据的;

主机收到数据后如果没有返回应答信号ack,而是nack,就表示主机要结束通讯了;

好比,你向领导汇报工作:

你:我做了事情A;

领导:嗯;(ack,暗示你接着说)

你:我还做了事情B;

领导:嗯;(ack,暗示你接着说)

你:我还做了事情C;

领导:可以了,你走吧(nack,要结束了)

有效应答ACK的要求是:接收器在第9个脉冲到来之前的低电平期间,将SDA拉低,一直保持到脉冲高电平期间。

产生应答信号(主机给从机的)

void I2C_Ack(void)
{
    SCL = 0;
    SDA = 0;
    nops(); //在第9个脉冲到来之前的低电平期间,把SDA拉低

    SCL = 1; //SCL高电平,表示第9个脉冲
    nops();

    SCL = 0 //SCL拉低,方便后续的读写
}

产生非应答信号(主机给从机的)

非应答和应答的唯一区别就是:

应答SDA是低电平

非应答SDA是高电平

void I2C_Nack(void)
{
    SCL = 0;
    SDA = 1;
    nops(); //在第9个脉冲到来之前的低电平期间,释放SDA总线,让上拉电阻把SDA拉高

    SCL = 1; //SCL高电平,表示第9个脉冲
    nops();

    SCL = 0 //SCL拉低,方便后续的读写
}

检测应答信号(从机给主机的)

从机只要收到数据就要无条件返回应答ack,主机就是通过检查这个ack来判断从机是否收到

unsigned char I2C_Wait_Ack(void)
{
    unsigned char ucErr = 0;
    SDA = 1;
    nops();
    SCL = 1;
    nops();
    while(SDA)  //SDA为高电平,就表示没有检查到ACK,
    {
        ucErr++;
        if(ucErr > 250) //等一段时间,还没有ack,就停止总线
        {
            I2C_Stop();
            return 1;
        }
    }
    SCL = 0;
    return 0; //能到这里就表示检测到应答信号了
}

IIC发送一个字节的数据(主机发给从机的)

这个数据的宽度是有要求的,不同的从设备对有效数据的宽度时间不大一样,Microchip 的AT24C02B,要求T_HIGH 4000ns以上, T_LOW 4700ns以上,所以那些C语言实现的函数都是有适用对象的,不能随便复制就了事

void I2C_Send_Byte(unsigned char dat)
{
    unsigned char i;
    SCL = 0; //拉低时钟,准备数据
    
    for(i=0;i<8;i++)
    {
        SDA = (dat & 0x80) >> 7;先发高位,再发低位,这个逻辑自己分析下
        dat << 1 //数据的高位发出去了,左移一位,接着发送次高位......
        nops();  //数据要保持一段时间,让SCL是高电平的时候,数据已经稳定了
        
        SCL = 1; //SCL 拉高
        nops(); //针对Microchip的24C02B,延时要4700ns以上
        scl = 0; //SCL 拉低
        nops(); //拉低也要持续一段时间
    }
    
}

接收一个字节的数据(从机发给主机)

主机收到数据就要发应答信号,或者非应答信号

unsigned char I2C_Read_Byte(unsigned char ack)
{
    unsigned char i;
    unsigned char receive = 0;
    
    for(i=0; i<8; i++)
    {
        SCL = 0;
        nops();
        
        SCL = 1;//时钟高电平的时候,接收的数据移位
        receive  <<= 1;
        
        if(SDA) //如果SDA是高电平,receive +1
        {
            receive++;
        }
            
        nops(); //
    }
    
    if(!ack)  //非应答信号
    {
        I2C_Nack();
    } else{ //应答信号
        I2C_Ack();
    }
    
    return receive;
}

EEPROM读写

向24C02的某个寄存器,写一个字节

1、主机(MCU)发出起始信号;

2、主机(MCU)发出从机的地址,并表示接下来还要写东西(就是发送写地址);

3、从机应答

4、主机(MCU)发出从机的寄存器地址;

5、从机应答

6、主机(MCU)发出要写的数据;

7、从机应答

8、主机(MCU)发出结束信号

//MCU 向24C02的某个寄存器写一个字节的数据
//很多人不喜欢检查从机的应答信号,其实在工程上是不规范的;
//虽然大部分情况下,不检查应答信号,也能正常使用
//就像国家免检产品一样,默认你不会出问题,一出问题就是大问题
void eeprom_Write_Byte(unsigned char addr, unsigned char dat)
{
    //1、MCU发出起始信号
    I2C_Start();
    
    //2、MCU发送从设备的地址,最后一位是写
    I2C_Send_Byte(DEV_ADDR | WR);
    
    //3、MCU检查从机的应答
    I2C_Wait_Ack();
    
    //4、MCU发出从机的寄存器地址
    I2C_Send_Byte(addr);
    
    //5、MCU检查从机的应答
    I2C_Wait_Ack();
    
    //6、MCU发出要写的数据
    I2C_Send_Byte(dat);
    
    //7、MCU检查从机的应答
    I2C_Wait_Ack();
    
    //8、MCU发出停止信号
    I2C_Stop();
}

写多个字节

//多次调用写一个字节的函数来实现写多个字节
eeprom_Write_NBytes(unsigned char addr, *pdata, size)
{
    unsigned char i ;
    for(i=0;i<size;i++)
    {
        eeprom_Write_Byte(addr, pdata[i]);
        addr++; //寄存器地址自动增加
    }
}

向24C02写多个字节(页写模式)

1、主机(MCU)发出起始信号;

2、主机(MCU)发出从机的地址,并表示接下来还要写东西(就是发送写地址);

3、从机应答

4、主机(MCU)发出从机的寄存器地址;

5、从机应答

6、主机(MCU)发出数据1;

7、从机应答

8、主机(MCU)发出数据2;

9、从机应答

......

10、主机(MCU)发出数据X;

11、从机应答

12、主机(MCU)发出结束信号

数据手册上有说明24C02的写周期是5ms,可以理解为每写完一次,就等5ms.

读当前寄存器的值

随机地址读取

读一个字节

1、MCU发送开始信号

2、MCU发送从设备写地址

3、MCU检测从机应答信号

4、MCU发送从机寄存器地址

5、MCU检测从机应答信号

6、MCU再次发送开始信号

7、MCU发送从设备读地址

8、MCU检测从机应答信号

9、从设备返回寄存器的数据

10、MCU发出非应答信号

11、MCU发出结束信号

//读24C02一个字节
void eeprom_Read_Byte(unsigned char addr, *pdata)
{
    //1、MCU发出起始信号
    I2C_Start();
    
    //2、MCU发送从设备的写地址
    I2C_Send_Byte(DEV_ADDR | WR);
    
    //3、MCU检查从机的应答
    I2C_Wait_Ack();
    
    //4、MCU发出从机的寄存器地址
    I2C_Send_Byte(addr);
    
    //5、MCU检查从机的应答
    I2C_Wait_Ack();
    
    //6、MCU再次发出开始信号
    I2C_Start();
    //7、MCU发送从机的读地址
    I2C_Send_Byte(DEV_ADDR | RD);
    
    //8、MCU检测从机应答
    I2C_Wait_Ack();
    
    //9、从机返回寄存器的数据
    //10、MCU发出非应答NACK信号
    *pdata = I2C_Read_Byte(NACK);
    
    //11、MCU发出结束信号
    I2C_Stop();
}

读多个字节,可以把读一个字节重复几遍

//读eeprom多个字节
//就是把读一个字节重复几遍
void eeprom_Read_NBytes(unsigned char addr, *pdata, size)
{
    unsigned char i;
    for(i=0;i<size;i++)
    {
        eeprom_Read_Byte(addr, &pdata[i]);
        addr++;
    }
}

连续读

以下是用HAL库写的读写EEPROM

#include "i2c.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

I2C_HandleTypeDef hi2c1;

/* I2C1 init function */
void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  /* USER CODE END I2C1_Init 2 */

}

void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(i2cHandle->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspInit 0 */

  /* USER CODE END I2C1_MspInit 0 */

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2C1 GPIO Configuration
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* I2C1 clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
  /* USER CODE BEGIN I2C1_MspInit 1 */

  /* USER CODE END I2C1_MspInit 1 */
  }
}

void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle)
{

  if(i2cHandle->Instance==I2C1)
  {
  /* USER CODE BEGIN I2C1_MspDeInit 0 */

  /* USER CODE END I2C1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_I2C1_CLK_DISABLE();

    /**I2C1 GPIO Configuration
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA
    */
    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_6);

    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_7);

  /* USER CODE BEGIN I2C1_MspDeInit 1 */

  /* USER CODE END I2C1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */
/*
* DevAddress: 24C02器件的地址
*	MemAddress:寄存器地址
* pBuffer:要发送的数据指针
*/
HAL_StatusTypeDef  AT24C02_Write_OneBytes(uint16_t DevAddress, uint16_t MemAddress, uint8_t *pBuffer)
{
	HAL_StatusTypeDef Statu = HAL_OK;
	
	//调用HAL_I2C_Mem_Write函数在指定的寄存器地址写数据
	Statu = HAL_I2C_Mem_Write(&hi2c1, DevAddress, MemAddress, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 10);
	
	//等待发送结束
	while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);
	
	//检查EEPROM是否准备好进行新的操作
	while (HAL_I2C_IsDeviceReady(&hi2c1, DevAddress, 100, 100) != HAL_OK);
	
	//等待器件就绪,为下一次数据传输做准备,其实也不需要,因为HAL_I2C_Mem_Write函数里面有检测
	while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);
	
	return Statu;	
}

//写多个字节,就是把写一个字节的函数,重复几遍
void AT24C02_Write_NBytes(uint16_t DevAddress, uint16_t MemAddress, uint8_t *pBuffer, uint16_t Size)
{
	uint16_t count = 0;
	for(count=0;count<Size;count++)
	{
		AT24C02_Write_OneBytes(DevAddress, MemAddress, pBuffer);
		MemAddress++;
		pBuffer += 1;
	}
	
}


/*
* DevAddress: 24C02器件的地址
*	MemAddress:寄存器地址,
* pBuffer:读取数据存放的地址
* Size:读取的数据个数
*/
HAL_StatusTypeDef AT24C02_Read_Buffer(uint16_t DevAddress, uint8_t* pBuffer, uint8_t MemAddress, uint16_t size)
{
	HAL_StatusTypeDef status = HAL_OK;

	status=HAL_I2C_Mem_Read(&hi2c1, DevAddress, MemAddress, I2C_MEMADD_SIZE_8BIT, pBuffer, size,1000);
 
	return status;
}

main.c里面进行数据读写

#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"


uint8_t Tx_Buffer[20] = "who is your daddy !";
uint8_t Rx_Buffer[30] = {0};

void SystemClock_Config(void);

int main(void)
{
  
  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();

  while (1)
  {
    /* USER CODE END WHILE */
		HAL_GPIO_TogglePin(LED_R_GPIO_Port, LED_R_Pin);
		
		AT24C02_Write_NBytes(EEPROM_ADDR, 0, Tx_Buffer, sizeof(Tx_Buffer));
		HAL_Delay(10);
		
		HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, 0, I2C_MEMADD_SIZE_8BIT, Rx_Buffer, sizeof(Rx_Buffer), 100);
		HAL_Delay(10);
		
		printf("eeprom read: %s\r\n", Rx_Buffer);
		memset(Rx_Buffer, 0 , sizeof(Rx_Buffer));
		Delay_ms(500); 
  }
}

直接调用HAL库,如果不去仔细的研究源代码的话,以后遇到什么问题,都不太好分析,拿来主义还是不利于自身发展的。个人建议在项目中还是多用软件IIC,自己去模拟IIC的通讯时序,方便自己了解IIC,也方便代码移植。24C02的读写时序

24C02两次的写间隔要间隔5ms。

STM32的硬件IIC是指通过STM32芯片中的IIC片上外设来实现IIC通讯协议。该外设负责产生通讯信号,收发数据并缓存起来,从而减轻了CPU的工作负担,使软件设计更加简单。STM32的硬件IIC外设支持通讯的主机和从机模式,支持不同的速率和设备地址,还支持DMA数据传输和数据校验功能。\[1\]虽然硬件IIC外设有时可能会出现问题,需要重新启动才能解决,但学习如何使用硬件实现IIC协议对于以后学习其他协议也会有帮助。\[2\]通过配置对应的寄存器,硬件IIC外设可以方便地控制硬件IIC通讯,而不需要直接控制引脚的电平。\[3\] #### 引用[.reference_title] - *1* [STM32通信---硬件I2C](https://blog.csdn.net/weixin_53762042/article/details/117093698)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [STM32硬件I2C与软件模拟I2C超详解](https://blog.csdn.net/k666499436/article/details/124686559)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值