IIC

IIC

1. 理论知识

1.1 IIC基础概念

I2C:Inter-Intergrated Circuit ,Phiilps公司80年代初期开发的,引脚少,硬件实现简单,具有可拓展性,广泛使用在系统内多个集成电路间的低速通信

双向两线制总线协议标准,支持同步串行半双工通讯。

标准模式传输速率:100 kbit/s 快速模式传输速率: 400 kbit/s 高速模式传输速率:3.4Mbit/s,大多数设备不支持高速。

术语定义:
image-20250126153041912

1.2 硬件构成

通信的实现通过SCL串行时钟总线和SDA串行数据总线实现。

SCL:用于数据的收发同步;
SDA:用于传输数据,高低电平表示:
可同时连接多个IIC通信设备,支持一主多从也支持多主多从。每个设备都有唯一的地址,主机通过地址与从机通信。总线通过上拉电阻连接到电源。设备空闲时,都输出高阻态,由上拉电阻把总线拉成高电平。

image-20250126113002463

1.3 工作原理

  1. 位传输机制:IIC为串行通信,按位进行数据传输,高位先行

  2. 起始信号:当SCL为高电平,SDA由高电平变化为低电平。

  3. 停止信号:当SCL为高电平,SDA由低电平变化为高电平。
    image-20250126224503448

  4. 传输地址:主机通过SDA信号线发送设备地址(SLAVE_ADDRESS)来查找从机。
    IIC协议规定设备地址可以是7位或10位,实际中7位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第8位或第11位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。

    image-20250205213929578

  5. 数据的有效性:
    SDA高或低电平状态只有在SCL时钟信号是低电平是才能才能改变。
    SDA线上的数据必须在时钟的高电平周期保持稳定。
    image-20250205214116281

  6. 响应:接收方接收到数据后要给发送方响应,有2种响应
    应答响应,给发送方一个低电平。
    非应答响应,给发送方一个高电平。
    image-20250205214257461

1.4 时序图

  1. 写入一个字节时序
    image-20250205233832588
  2. 读出一个字节时序
    image-20250205233921367
  3. 单次写入多个字节时序
    image-20250205233929183
    一次性写入多个字节,也叫页写入(Page Write)。AT24C02每页只有16个字节,每次只能写入单独的一个页中,所以一次性最多只能写入16个字节。当一次性写入超过16个字节的时候,则超过的部分会重新从这页的首地址重新写入。
  4. 单次读出多个字节时序
    image-20250205233959077
    读出多个字节的时候没有限制,可以读出任意多个。

1.5 补充:24C02简介

image-20250205215358082
image-20250205215440683
image-20250205215827068

2. 实践验证

2.1 寄存器方式

验证一:软件模拟IIC通信

实现逻辑:

配置IO端口为通用开漏输出(总线接上拉电阻,IO开漏输出为低电平或高阻态),软件定义端口的电平输出状态和输入状态。根据IIC通信的时序图完成通信。
效果:
image-20250207152643741

如图,页写入超出16字节后会从头写入
代码如下:

mian.c
#include "USART.h"
#include "led.h"
#include "m24c02.h"
#include "string.h"

int main()
{
    // 1. 初始化
    Driver_USART1_Init();
    M24C02_Init();


    printf("******实践软件模拟IIC通信*******\r\n");
    printf("写入单个字符\r\n");
    // 2. 向EEPROM写入字符
    M24C02_WriteByte(0x00, 'a');
    M24C02_WriteByte(0x01, 'b');
    M24C02_WriteByte(0x02, 'c');

    printf("读取单个字符\r\n");
    // 3. 读取字符
    uint8_t byte1 = M24C02_ReadByte(0x00);
    uint8_t byte2 = M24C02_ReadByte(0x01);
    uint8_t byte3 = M24C02_ReadByte(0x02);

    // 4. 串口输出打印
    printf("byte1 = %c\t byte2 = %c\t byte3 = %c\r\n", byte1, byte2, byte3);
    

    printf("写入多个字符\r\n"); 
    // 5. 写入多个字符
    M24C02_WriteBytes(0x00, "yangabc123", 10);

    printf("读取多个字符\r\n");
    // 6. 读取多个字符
    uint8_t buffer[100] = {0};

    M24C02_ReadBytes(0x00, buffer, 10);

    // 7. 串口输出打印
    printf("buffer = %s\r\n", buffer);
    
    // 8. 测试超出16个字节的写入
    printf("测试写入超过16个字符:1234567890abcdefghijk\r\n"); 
    // 清零缓冲区
    memset(buffer, 0, sizeof(buffer));

    M24C02_WriteBytes(0x00, "1234567890abcdefghijk", 21);
    M24C02_ReadBytes(0x00, buffer, 21);
    printf("buffer = %s\r\n", buffer);

    
    while (1)
    {
    }
}
iic.c
#include "iic.h"


// 初始化
void IIC_Init(void)
{
    // 1. 配置时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

    // 2. GPIO工作模式配置,通用开漏输出CNF-01,MODE-11
    GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);
    GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
    GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);
}

// 发出起始信号
void IIC_Start(void)
{
    // 1. SCL拉高,SDA拉高
    SCL_HIGH;
    SDA_HIGH;
    IIC_DELAY;

    // 2. SCL保持不变,SDA拉低
    SDA_LOW;
    IIC_DELAY;
}

// 发出停止信号
void IIC_Stop(void)
{
    // 1. SCL拉高,SDA拉低
    SCL_HIGH;
    SDA_LOW;
    IIC_DELAY;

    // 2. SCL保持不变,SDA拉高
    SDA_HIGH;
    IIC_DELAY;
}

// 发送一个字节数据
void IIC_SendByte(uint8_t byte)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        // 1. SCL拉低,SDA拉低,等待数据上传
        SCL_LOW;
        SDA_LOW;
        IIC_DELAY;

        // 2. 取字节的最高位,向SDA写入数据
        if (byte & 0x80)
        {
            SDA_HIGH;
        }
        else
        {
            SDA_LOW;
        }
        IIC_DELAY;

        // 3. SCL拉高,进行数据采样
        SCL_HIGH;
        IIC_DELAY;

        // 4. SCL拉低,采样结束
        SCL_LOW;
        IIC_DELAY;

        // 5. 左移一位
        byte <<= 1;
    }
}

// 接收一个字节数据
uint8_t IIC_ReadByte(void)
{
    // 1. 定义一个变量,用来保存接收的数据
    uint8_t data = 0;

    // 2. 循环处理每一位
    for (uint8_t i = 0; i < 8; i++)
    {
        // 1. SCL拉低,SDA拉高,释放数据总线,等待数据翻转
        SCL_LOW;
        SDA_HIGH;
        IIC_DELAY;

        // 2. SCL拉高,进行数据采样
        SCL_HIGH;
        IIC_DELAY;

        // 3. SCL保持不变,读取SDA上的电平

        data <<= 1; // 先做左移,新存入的位永远在最低位
        if (READ_SDA)
        {
            data |= 0x01; // 先存入最低位,然后每次都左移1位
        }

        // 4. SCL拉低,结束数据线上信号采样
        SCL_LOW;
        IIC_DELAY;
    }
    return	data;
}

// 发送ACK信号
void IIC_SendACK(void)
{
    // 1. SCL拉低,SDA拉高,准备发出信号
    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;

    // 2. SCL保持不变,SDA拉低,发送ACK信号
    SDA_LOW;
    IIC_DELAY;

    // 3. SDA保持不变,SCL拉高,开始数据线上信号采样
    SCL_HIGH;
    IIC_DELAY;

    // 4. SDA保持不变,SCL拉低,结束数据线上信号采样
    SCL_LOW;
    IIC_DELAY;

    // 5. SCL保持不变,SDA拉高,释放数据总线
    SDA_HIGH;
    IIC_DELAY;
}

// 发送NACK信号
void IIC_SendNACK(void)
{
    // 1. SCL拉低,SDA拉高,准备发出信号
    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;

    // 2. SCL保持不变,SDA拉高(即不变),发送ACK信号
    //SDA_LOW;
    //IIC_DELAY;

    // 3. SDA保持不变,SCL拉高,开始数据线上信号采样
    SCL_HIGH;
    IIC_DELAY;

    // 4. SDA保持不变,SCL拉低,结束数据线上信号采样
    SCL_LOW;
    IIC_DELAY;

    // 5. SCL保持不变,释放数据总线
    //SDA_HIGH;
    //IIC_DELAY;
}
// 读取ACK信号
uint8_t IIC_WaitACK(void)
{
    // 1. SCL拉低,SDA拉高,释放数据总线
    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;

    // 2. SCL保持不变,SDA拉低,开始数据线上信号采样
    SCL_HIGH;
    IIC_DELAY;

    // 3. 读取SDA上的数据
    uint16_t ack = READ_SDA;

    // 4. SCL拉低,结束数据线上信号采样
    SCL_LOW;
    IIC_DELAY;

    return ack ? NACK : ACK;
}
iic.h
#ifndef __IIC_H__
#define __IIC_H__   

#include "stm32f10x.h"
#include "delay.h"

// 宏定义
#define ACK 0
#define NACK 1

// 控制SCL、SDA输出高低电平 
#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
#define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)
#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)

// 读取SDA
#define READ_SDA (GPIOB->IDR &= GPIO_IDR_IDR11)

// 定义操作的基本延迟
#define IIC_DELAY Delay_us(10)

// 初始化
void IIC_Init(void);

// 发出起始信号
void IIC_Start(void);

// 发出停止信号
void IIC_Stop(void);

// 发送一个字节数据
void IIC_SendByte(uint8_t byte);

// 接收一个字节数据
uint8_t IIC_ReadByte(void);

// 发送ACK信号
void IIC_SendACK(void);

// 发送NACK信号
void IIC_SendNACK(void);

// 读取ACK信号
uint8_t IIC_WaitACK(void);

#endif

m24c02.c
#include "m24c02.h"


// 初始化
void M24C02_Init(void)
{
    IIC_Init();
}

// 向EEPROM写入一个字节
void M24C02_WriteByte(uint8_t innerAddr, uint8_t byte)
{
    // 1. 发出开始信号
    IIC_Start();

    // 2. 发送设备地址及写入命令
    IIC_SendByte(M24C02_WRITE_ADDR);

    // 3. 等待m24c02应答ACK
    uint8_t ack = IIC_WaitACK();

    if (ack == ACK)
    {
        // 4. 发送内部地址
        IIC_SendByte(innerAddr);

        // 5. 等待m24c02应答ACK
        IIC_WaitACK();

        // 6. 发送数据
        IIC_SendByte(byte);

        // 7. 等待应答
        IIC_WaitACK();

        // 8. 发送停止信号
        IIC_Stop();
    }

    // 延迟等待写入周期结束
    Delay_ms(5);

}

// 从EEPROM读取一个字节
uint8_t M24C02_ReadByte(uint8_t innerAddr)
{
    // 1. 发出开始信号
    IIC_Start();

    // 2. 发送设备地址及写入命令
    IIC_SendByte(M24C02_WRITE_ADDR);

    // 3. 等待m24c02应答ACK
    IIC_WaitACK();

    // 4. 发送内部地址
    IIC_SendByte(innerAddr);

    // 5. 等待m24c02应答ACK
    IIC_WaitACK();

    // 6. 发出开始信号
    IIC_Start();

    // 7. 发送设备地址及读取命令
    IIC_SendByte(M24C02_READ_ADDR);

    // 8. 等待m24c02应答ACK
    IIC_WaitACK();

    // 9. 读取一个字节数据
    uint8_t byte = IIC_ReadByte();

    // 10. 发送非应答NACK
    IIC_SendNACK();

    // 11. 发送停止信号
    IIC_Stop();

    return byte;
}

// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t size)
{
        // 1. 发出开始信号
    IIC_Start();

    // 2. 发送设备地址及写入命令
    IIC_SendByte(M24C02_WRITE_ADDR);

    // 3. 等待m24c02应答ACK
    uint8_t ack = IIC_WaitACK();

    if (ack == ACK)
    {
        // 4. 发送内部地址
        IIC_SendByte(innerAddr);

        // 5. 等待m24c02应答ACK
        IIC_WaitACK();

        // 利用循环不停发送数据
        for (uint8_t i = 0; i < size; i++)
        {
            // 6. 发送数据
            IIC_SendByte(bytes[i]);

            // 7. 等待应答
            IIC_WaitACK(); 
        }
        
        // 8. 发送停止信号
        IIC_Stop();
    }

    // 延迟等待写入周期结束
    Delay_ms(5);
}

// 连续读取多个字节
void M24C02_ReadBytes(uint8_t innerAddr, uint8_t *buffer, uint8_t size)
{
        // 1. 发出开始信号
    IIC_Start();

    // 2. 发送设备地址及写入命令
    IIC_SendByte(M24C02_WRITE_ADDR);

    // 3. 等待m24c02应答ACK
    IIC_WaitACK();

    // 4. 发送内部地址
    IIC_SendByte(innerAddr);

    // 5. 等待m24c02应答ACK
    IIC_WaitACK();

    // 6. 发出开始信号
    IIC_Start();

    // 7. 发送设备地址及读取命令
    IIC_SendByte(M24C02_READ_ADDR);

    // 8. 等待m24c02应答ACK
    IIC_WaitACK();

    // 利用循环读取数据
    for (uint8_t i = 0; i < size; i++)
    {
        // 9. 读取一个字节数据
        buffer[i] = IIC_ReadByte();

        // 发送应ACK应答
        IIC_SendACK();
    }

    // 10. 发送非应答NACK
    IIC_SendNACK();

    // 11. 发送停止信号
    IIC_Stop();

    return;
}

m24c02.h
#ifndef __M24C02_H__
#define __M24C02_H__

#include "iic.h"

// 宏定义
#define M24C02_WRITE_ADDR 0xA0
#define M24C02_READ_ADDR 0xA1

// 初始化
void M24C02_Init(void);

// 向EEPROM写入一个字节
void M24C02_WriteByte(uint8_t innerAddr, uint8_t byte);

// 从EEPROM读取一个字节
uint8_t M24C02_ReadByte(uint8_t innerAddr);

// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t size);

// 连续读取多个字节
void M24C02_ReadBytes(uint8_t innerAddr, uint8_t *buffer, uint8_t size);

#endif

验证二:硬件IIC通信

实现逻辑:

配置IO端口为复用开漏输出,根据IIC通信的时序图完成通信驱动文件的编程。
效果:
image-20250207223451499

main.c
#include "USART.h"
#include "led.h"
#include "m24c02.h"
#include "string.h"

int main()
{
    // 1. 初始化
    Driver_USART1_Init();
    M24C02_Init();


    printf("******实践寄存器硬件IIC通信*******\r\n");
    printf("写入单个字符\r\n");
    // 2. 向EEPROM写入字符
    M24C02_WriteByte(0x00, 'a');
    M24C02_WriteByte(0x01, 'b');
    M24C02_WriteByte(0x02, 'c');

    printf("读取单个字符\r\n");
    // 3. 读取字符
    uint8_t byte1 = M24C02_ReadByte(0x00);
    uint8_t byte2 = M24C02_ReadByte(0x01);
    uint8_t byte3 = M24C02_ReadByte(0x02);

    // 4. 串口输出打印
    printf("byte1 = %c\t byte2 = %c\t byte3 = %c\r\n", byte1, byte2, byte3);
    

    printf("写入多个字符\r\n"); 
    // 5. 写入多个字符
    M24C02_WriteBytes(0x00, "yangabc123", 10);

    printf("读取多个字符\r\n");
    // 6. 读取多个字符
    uint8_t buffer[100] = {0};

    M24C02_ReadBytes(0x00, buffer, 10);

    // 7. 串口输出打印
    printf("buffer = %s\r\n", buffer);
    
    // 8. 测试超出16个字节的写入
    printf("测试写入超过16个字符:1234567890abcdefghijk\r\n"); 
    // 清零缓冲区
    memset(buffer, 0, sizeof(buffer));

    M24C02_WriteBytes(0x00, "1234567890abcdefghijk", 21);
    M24C02_ReadBytes(0x00, buffer, 21);
    printf("buffer = %s\r\n", buffer);

    
    while (1)
    {
    }
}


iic.c
#include "iic.h"

// 初始化
void I2C_Init(void)
{
    // 1. 配置时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;

    // 2. GPIO工作模式配置:复用开漏输出 CNF-11,MODE-11
    GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11 |
                   GPIO_CRH_CNF10 | GPIO_CRH_CNF11);

    // 3. I2C2配置
    // 3.1 硬件工作模式
    I2C2->CR1 &= ~I2C_CR1_SMBUS;
    I2C2->CCR &= ~I2C_CCR_FS;

    // 3.2 选择输入的时钟频率
    I2C2->CR2 |= 36;

    // 3.3 配置CCR,对应数据传输速率100kb/s,SCL高电平时间为 5us
    I2C2->CCR |= 180;

    // 3.4 配置TRISE,SCL上升沿最大时钟周期数 + 1
    I2C2->TRISE |= 37;

    // 3.5 使能I2C2模块
    I2C2->CR1 |= I2C_CR1_PE;
}

// 发出起始信号
uint8_t I2C_Start(void)
{
    // 产生一个起始信号
    I2C2->CR1 |= I2C_CR1_START;

    // 引入一个超时时间
    uint16_t timeout = 0xffff;

    // 等待起始信号发出
    while ((I2C2->SR1 & I2C_SR1_SB) == 0 && timeout)
    {
        timeout--;
    }
    return timeout ? OK : FAIL;
}

// 设置接收完成之后发出停止信号
void I2C_Stop(void)
{
    I2C2->CR1 |= I2C_CR1_STOP;
}

// 主机设置使能应答信号
void I2C_Ack(void)
{
    I2C2->CR1 |= I2C_CR1_ACK;
}

// 主机设置使能非应答信号
void I2C_Nack(void)
{
    I2C2->CR1 &= ~I2C_CR1_ACK;
}

// 主机发送设备地址,并等待应答
uint8_t I2C_SendAddr(uint8_t addr)
{
    // 直接将要发送的地址给到DR
    I2C2->DR = addr;

    // 等待应答
    uint16_t timeout = 0xffff;
    while ((I2C2->SR1 & I2C_SR1_ADDR) == 0 && timeout)
    {
        timeout--;
    }
    // 访问SR2,清除ADDR标志位
    if (timeout > 0)
    {
        I2C2->SR2;
    }

    return timeout ? OK : FAIL;
}

// 主机发送一个字节的数据(写入),并等待应答
uint8_t I2C_SendByte(uint8_t byte)
{
    // 1. 先等待DR为空,上一个字节数据已经发送完毕
    uint16_t timeout = 0xffff;
    while ((I2C2->SR1 & I2C_SR1_TXE) == 0 && timeout)
    {
        timeout--;
    }

    // 2. 将要发送的字节放入DR中
    I2C2->DR = byte;

    // 3. 等待应答
    timeout = 0xffff;
    while ((I2C2->SR1 & I2C_SR1_BTF) == 0 && timeout)
    {
        timeout--;
    }
    return timeout ? OK : FAIL;
}

// 主机从EEPROM接收一个字节的数据(读取)
uint8_t I2C_ReadByte(void)
{
    // 1. 先等待DR为满
    uint16_t timeout = 0xffff;
    while ((I2C2->SR1 & I2C_SR1_RXNE) == 0 && timeout)
    {
        timeout--;
    }

    // 2. 将收到的字节数据返回
    return timeout ? I2C2->DR : FAIL;
}

iic.h
#ifndef __IIC_H
#define __IIC_H

#include "stm32f10x.h"
#include "delay.h"

// 宏定义
#define OK 0
#define FAIL 1

// 初始化
void I2C_Init(void);

// 发出起始信号
uint8_t I2C_Start(void);

// 设置发出停止信号
void I2C_Stop(void);

// 主机设置使能应答信号
void I2C_Ack(void);

// 主机设置使能非应答信号
void I2C_Nack(void);

// 主机发送设备地址,并等待应答
uint8_t I2C_SendAddr(uint8_t addr);

// 主机发送一个字节的数据(写入),并等待应答
uint8_t I2C_SendByte(uint8_t byte);

// 主机从EEPROM接收一个字节的数据(读取)
uint8_t I2C_ReadByte(void);

#endif

m24c02.c
#include "m24c02.h"

// 初始化
void M24C02_Init(void)
{
    I2C_Init();
}

// 向EEPROM写入一个字节
void M24C02_WriteByte(uint8_t innerAddr, uint8_t byte)
{
    // 1. 发出开始信号
    I2C_Start();

    // 2. 发送写地址
    I2C_SendAddr(W_ADDR);

    // 3. 发送内部地址
    I2C_SendByte(innerAddr);

    // 4. 发送具体数据
    I2C_SendByte(byte);

    // 5. 发出一个停止信号
    I2C_Stop();

    // 延迟等待写入周期结束
    Delay_ms(5);
}

// 读取EEPROM的一个字节
uint8_t M24C02_ReadByte(uint8_t innerAddr)
{
    // 1. 发出开始信号
    I2C_Start();

    // 2. 发送写地址(假写)
    I2C_SendAddr(W_ADDR);

    // 3. 发送内部地址
    I2C_SendByte(innerAddr);

    // 4. 发出开始信号
    I2C_Start();

    // 5. 发送读地址(真读)
    I2C_SendAddr(R_ADDR);

    // 6. 设置非应答
    I2C_Nack();

    // 7. 设置在接收下一个字节后发出停止信号
    I2C_Stop();

    // 8. 读取一个字节
    uint8_t byte = I2C_ReadByte();

    return byte;
}

// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t size)
{
    // 1. 发出开始信号
    I2C_Start();

    // 2. 发送写地址
    I2C_SendAddr(W_ADDR);

    // 3. 发送内部地址
    I2C_SendByte(innerAddr);

    // 利用循环不停发送数据
    for (uint8_t i = 0; i < size; i++)
    {
        // 4. 发送具体数据
        I2C_SendByte(bytes[i]);
    }

    // 5. 发出一个停止信号
    I2C_Stop();

    // 延迟等待写入周期结束
    Delay_ms(5);
}

// 连续读取多个字节
void M24C02_ReadBytes(uint8_t innerAddr, uint8_t *buffer, uint8_t size)
{
    // 1. 发出开始信号
    I2C_Start();

    // 2. 发送写地址(假写)
    I2C_SendAddr(W_ADDR);

    // 3. 发送内部地址
    I2C_SendByte(innerAddr);

    // 4. 发出开始信号
    I2C_Start();

    // 5. 发送读地址(真读)
    I2C_SendAddr(R_ADDR);

    // 利用循环连续读取多个字节
    for (uint8_t i = 0; i < size; i++)
    {
        // 6. 设置应答或非应答
        if (i < size - 1)
        {
            I2C_Ack();
        }
        else
        {
            I2C_Nack();

            // 7. 设置发出停止信号
            I2C_Stop();
        }
        // 8. 读取一个字节
        buffer[i] = I2C_ReadByte();
    }
}

m24c02.h
#ifndef __M24C02_H
#define __M24C02_H

#include "iic.h"

// 宏定义
#define W_ADDR 0xA0
#define R_ADDR 0xA1

// 初始化
void M24C02_Init(void);

// 向EEPROM写入一个字节
void M24C02_WriteByte(uint8_t innerAddr, uint8_t byte);

// 读取EEPROM的一个字节
uint8_t M24C02_ReadByte(uint8_t innerAddr);

// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t innerAddr, uint8_t * bytes, uint8_t size);

// 连续读取多个字节
void M24C02_ReadBytes(uint8_t innerAddr, uint8_t * buffer, uint8_t size);

#endif


2.2 库函数方式

验证二:硬件IIC通信

实现逻辑:

在cubemx配置IIC通信,然后再根据M24C02通信时序图,添加对应驱动文件。

image-20250207222445154

效果:

image-20250207221139490

main.c
 /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("******实践cubemx硬件IIC通信*******\r\n");
  printf("写入单个字符\r\n");
  // 2. 向EEPROM写入字符
  M24C02_WriteByte(0x00, 'a');
  M24C02_WriteByte(0x01, 'b');
  M24C02_WriteByte(0x02, 'c');

  printf("读取单个字符\r\n");
  // 3. 读取字符
  uint8_t byte1 = M24C02_ReadByte(0x00);
  uint8_t byte2 = M24C02_ReadByte(0x01);
  uint8_t byte3 = M24C02_ReadByte(0x02);

  // 4. 串口输出打印
  printf("byte1 = %c\t byte2 = %c\t byte3 = %c\r\n", byte1, byte2, byte3);
  

  printf("写入多个字符\r\n"); 
  // 5. 写入多个字符
  M24C02_WriteBytes(0x00, "yangabc123", 10);

  printf("读取多个字符\r\n");
  // 6. 读取多个字符
  uint8_t buffer[100] = {0};

  M24C02_ReadBytes(0x00, buffer, 10);

  // 7. 串口输出打印
  printf("buffer = %s\r\n", buffer);
  
  // 8. 测试超出16个字节的写入
  printf("测试写入超过16个字:1234567890abcdefghijk\r\n"); 
  // 清零缓冲区
  memset(buffer, 0, sizeof(buffer));

  M24C02_WriteBytes(0x00, "1234567890abcdefghijk", 21);
  M24C02_ReadBytes(0x00, buffer, 21);
  printf("buffer = %s\r\n", buffer);
  /* USER CODE END 2 */
m24c02.c
#include "m24c02.h"

// 初始化
void M24C02_Init(void)
{
    MX_I2C2_Init();
}

// 向EEPROM写入一个字节
void M24C02_WriteByte(uint8_t innerAddr, uint8_t byte)
{
    HAL_I2C_Mem_Write(&hi2c2, W_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT, &byte, 1, 1000);

    // 延迟等待写入周期结束
    HAL_Delay(5);
}

// 读取EEPROM的一个字节
uint8_t M24C02_ReadByte(uint8_t innerAddr)
{
    uint8_t byte;
    HAL_I2C_Mem_Read(&hi2c2, R_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT, &byte, 1, 1000);
    return byte;
}

// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t size)
{
    HAL_I2C_Mem_Write(&hi2c2, W_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT, bytes, size, 1000);

    // 延迟等待写入周期结束
    HAL_Delay(5);
}

// 连续读取多个字节
void M24C02_ReadBytes(uint8_t innerAddr, uint8_t *buffer, uint8_t size)
{
    HAL_I2C_Mem_Read(&hi2c2, R_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT, buffer, size, 1000);
}

m24c02.h
#ifndef __M24C02_H
#define __M24C02_H

#include "i2c.h"

// 宏定义
#define W_ADDR 0xA0
#define R_ADDR 0xA1

// 初始化
void M24C02_Init(void);

// 向EEPROM写入一个字节
void M24C02_WriteByte(uint8_t innerAddr, uint8_t byte);

// 读取EEPROM的一个字节
uint8_t M24C02_ReadByte(uint8_t innerAddr);

// 连续写入多个字节(页写)
void M24C02_WriteBytes(uint8_t innerAddr, uint8_t * bytes, uint8_t size);

// 连续读取多个字节
void M24C02_ReadBytes(uint8_t innerAddr, uint8_t * buffer, uint8_t size);

#endif
posted @ 2025-01-23 22:45  Arsun  阅读(58)  评论(0)    收藏  举报