目录

1. IIC协议概述

1.1 IIC协议定义

IIC(Inter-Integrated Circuit,也写作I²C或I2C)是一种串行通信总线,由飞利浦公司在1980年代开发,用于连接微控制器和各种外围设备。IIC协议具有以下特点:

  • 双线制:仅使用两条线(SCL和SDA)实现双向通信
  • 主从架构:一个或多个主设备控制总线,从设备响应主设备的命令
  • 寻址机制:每个从设备都有唯一的地址,支持多设备共享总线
  • 低速率:标准模式为100kbit/s,快速模式为400kbit/s,高速模式可达3.4Mbit/s
  • 开放集电极:允许多个设备共享总线而不会发生冲突

1.2 物理特性

IIC总线由两条信号线组成:

  • SCL(Serial Clock Line):时钟线,由主设备产生
  • SDA(Serial Data Line):数据线,可由主设备或从设备控制

这两条线都需要上拉电阻(通常为4.7kΩ)连接到电源,形成开漏(或开集电极)输出结构。这种结构允许多个设备共享总线,并实现"线与"逻辑。

1.3 通信时序

IIC协议定义了几种特殊的总线条件和基本时序:

起始条件(START)

当SCL为高电平时,SDA从高电平切换到低电平,表示通信开始。

    ____
SDA     \___
    _______
SCL      
停止条件(STOP)

当SCL为高电平时,SDA从低电平切换到高电平,表示通信结束。

         ____
SDA ____/    
    _______
SCL      
数据传输

数据按字节(8位)传输,高位先传。每个字节后跟一个应答位(ACK/NACK)。

  • 数据在SCL低电平期间准备
  • 数据在SCL高电平期间有效并被采样
  • 每个字节传输后,接收方发送一个应答位(ACK=0表示应答,NACK=1表示不应答)
     _   _   _   _   _   _   _   _   _
SCL _| |_| |_| |_| |_| |_| |_| |_| |_| |_
    ___     ___     ___     ___     ___
SDA    |___|   |___|   |___|   |___|   |___
      D7  D6  D5  D4  D3  D2  D1  D0  ACK
完整通信流程

一个典型的IIC通信序列包括:

  1. 主设备发送START条件
  2. 主设备发送从设备地址(7位)和读/写位(1位,0=写,1=读)
  3. 从设备发送ACK
  4. 数据传输(一个或多个字节,每个字节后跟ACK/NACK)
  5. 主设备发送STOP条件

1.4 寻址方式

IIC设备使用7位或10位地址。对于7位寻址(最常用):

  • 地址占用7位(可寻址128个设备)
  • 第8位表示读/写操作(0=写,1=读)

2. AT24CXX EEPROM概述

2.1 产品简介

AT24CXX是Atmel(现为Microchip)生产的一系列IIC接口EEPROM(电可擦除可编程只读存储器)。该系列包括多种型号,如AT24C01、AT24C02、AT24C04、AT24C08、AT24C16、AT24C32、AT24C64等,容量从1Kbit到512Kbit不等。

主要特点:

  • IIC兼容接口
  • 低功耗
  • 页写缓冲
  • 高可靠性(100,000次写入周期,数据保存超过40年)
  • 宽工作电压范围(通常为1.8V至5.5V)

2.2 工作原理

AT24CXX EEPROM通过IIC总线与微控制器通信,使用电可擦除可编程技术存储数据。其工作原理包括:

  1. 读操作:主控制器发送设备地址和存储器地址,然后读取数据
  2. 写操作:主控制器发送设备地址、存储器地址和要写入的数据
  3. 页写操作:可以一次写入多个字节(一页,通常为8或16字节)
  4. 写周期:写入操作后,EEPROM需要一段时间(通常为5ms)完成内部写入周期

2.3 存储组织

不同型号的AT24CXX有不同的存储容量和组织方式:

型号 容量 组织方式 页大小 地址字节数
AT24C01 1Kbit 128×8 8字节 1
AT24C02 2Kbit 256×8 8字节 1
AT24C04 4Kbit 512×8 16字节 1
AT24C08 8Kbit 1024×8 16字节 1
AT24C16 16Kbit 2048×8 16字节 1
AT24C32 32Kbit 4096×8 32字节 2
AT24C64 64Kbit 8192×8 32字节 2

2.4 寻址模式

AT24CXX的寻址方式根据容量不同而有所差异:

  1. AT24C01/C02(小容量):

    • 器件地址:1010 + A2 + A1 + A0 + R/W
    • 使用1字节内部地址(0-127或0-255)
  2. AT24C04/C08/C16(中容量):

    • 器件地址:1010 + A2 + (A1) + (A0) + R/W
    • 其中A1、A0可能被高位地址位替代
    • AT24C04:1010 + A2 + A1 + a8 + R/W
    • AT24C08:1010 + A2 + a9 + a8 + R/W
    • AT24C16:1010 + a10 + a9 + a8 + R/W
  3. AT24C32/C64及以上(大容量):

    • 器件地址:1010 + A2 + A1 + A0 + R/W
    • 使用2字节内部地址

其中:

  • A0-A2:硬件地址引脚
  • a8-a10:存储器寻址位
  • R/W:读/写控制位(0=写,1=读)

3. 软件实现

3.1 IIC驱动实现

IIC驱动通过软件模拟方式实现,主要包括以下功能:

初始化
void iic_init(void)
{
    rcu_periph_clock_enable(RCU_GPIOB);  /* 使能GPIOB时钟 */

    /* 配置SCL引脚:推挽输出模式,上拉,高速 */
    gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_8);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);

    /* 配置SDA引脚:开漏输出模式,上拉,高速
     * 开漏输出可以实现双向IO功能,上拉电阻(=1)时也可以读取外部信号的高低电平 */
    gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_9);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_9);

    iic_stop();     /* 发送停止信号,复位所有设备 */
}
起始信号
void iic_start(void)
{
    IIC_SDA(1);     /* 确保SDA初始为高 */
    IIC_SCL(1);     /* 确保SCL初始为高 */
    iic_delay();
    IIC_SDA(0);     /* START信号:当SCL为高时,SDA由高变低,表示起始信号 */
    iic_delay();
    IIC_SCL(0);     /* 钳制I2C总线,准备发送或接收数据 */
    iic_delay();
}
停止信号
void iic_stop(void)
{
    IIC_SDA(0);     /* STOP信号:当SCL为高时,SDA由低变高,表示停止信号 */
    iic_delay();
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(1);     /* 释放I2C总线,结束信号 */
    iic_delay();
}
发送字节
void iic_send_byte(uint8_t data)
{
    uint8_t t;
    
    for (t = 0; t < 8; t++)
    {
        IIC_SDA((data & 0x80) >> 7);    /* 高位先发送 */
        iic_delay();
        IIC_SCL(1);                      /* 产生SCL时钟 */
        iic_delay();
        IIC_SCL(0);                      /* 时钟输出0 */
        data <<= 1;                      /* 左移一位,准备下一次发送 */
    }
    IIC_SDA(1);                         /* 发送完成,主机释放SDA线 */
}
接收字节
uint8_t iic_read_byte(uint8_t ack)
{
    uint8_t i;
    uint8_t receive = 0;

    for (i = 0; i < 8; i++)             /* 接收1个字节数据 */
    {
        receive <<= 1;                  /* 左移一位,接收到的数据位要放到最低位 */
        IIC_SCL(1);
        iic_delay();

        if (IIC_READ_SDA)
        {
            receive++;
        }
        
        IIC_SCL(0);
        iic_delay();
    }

    if (!ack)
    {
        iic_nack();                     /* 发送nACK */
    }
    else
    {
        iic_ack();                      /* 发送ACK */
    }

    return receive;                      /* 返回接收到的数据 */
}
应答和非应答
void iic_ack(void)
{
    IIC_SDA(0);     /* SCL 0 -> 1 时 SDA = 0,表示应答 */
    iic_delay();
    IIC_SCL(1);     /* 产生一个时钟 */
    iic_delay();
    IIC_SCL(0);
    iic_delay();
    IIC_SDA(1);     /* 主机释放SDA线 */
    iic_delay();
}

void iic_nack(void)
{
    IIC_SDA(1);     /* SCL 0 -> 1 时 SDA = 1,表示不应答 */
    iic_delay();
    IIC_SCL(1);     /* 产生一个时钟 */
    iic_delay();
    IIC_SCL(0);
    iic_delay();
}
等待应答
uint8_t iic_wait_ack(void)
{
    uint8_t waitTime = 0;
    uint8_t rAck = 0;

    IIC_SDA(1);     /* 主机释放SDA线(此时外部器件可以拉低SDA线) */
    iic_delay();
    IIC_SCL(1);     /* SCL=1,此时从机可以发送ACK */
    iic_delay();

    while (IIC_READ_SDA)    /* 等待应答 */
    {
        waitTime++;

        if (waitTime > 250) /* 超时未收到应答信号 */
        {
            iic_stop();     
            rAck = 1;
            break;     
        }
    }

    IIC_SCL(0);     /* SCL=0,结束ACK检测 */
    iic_delay();
    return rAck;
}

3.2 AT24CXX驱动实现

AT24CXX驱动基于IIC驱动,实现了EEPROM的读写操作:

初始化
void at24cxx_init(void)
{
    iic_init();  /* 初始化IIC接口 */
}
读取单字节
uint8_t at24cxx_read_one_byte(uint16_t addr)
{
    uint8_t temp = 0;
    iic_start();                /* 发送起始信号 */

    /* 根据不同的24CXX型号,发送设备地址和寻址方式 */
    if (EE_TYPE > AT24C16)        /* 24C16以上型号,需要发送2字节地址 */
    {
        iic_send_byte(0XA0);      /* 发送写命令,IIC规定最低位为0表示写入 */
        iic_wait_ack();           /* 等待从机应答 */
        iic_send_byte(addr >> 8); /* 发送高字节地址 */
    }
    else 
    {
        iic_send_byte(0XA0 + ((addr >> 8) << 1));   /* 器件地址 0XA0 + 高位地址 */
    }
    
    iic_wait_ack();             /* 等待从机应答 */
    iic_send_byte(addr % 256);  /* 发送低字节地址 */
    iic_wait_ack();             /* 等待从机应答,此时地址设置完成 */
    
    iic_start();                /* 重新发送起始信号,准备读取数据 */ 
    iic_send_byte(0XA1);        /* 进入读取模式,IIC规定最低位为1表示读取 */
    iic_wait_ack();             /* 等待从机应答 */
    temp = iic_read_byte(0);    /* 读取一个字节数据,发送NACK */
    iic_stop();                 /* 发送停止信号 */
    return temp;
}
写入单字节
void at24cxx_write_one_byte(uint16_t addr, uint8_t data)
{
    iic_start();                  /* 发送起始信号 */

    if (EE_TYPE > AT24C16)        /* 24C16以上型号,需要发送2字节地址 */
    {
        iic_send_byte(0XA0);      /* 发送写命令,IIC规定最低位为0表示写入 */
        iic_wait_ack();           /* 等待从机应答 */
        iic_send_byte(addr >> 8); /* 发送高字节地址 */
    }
    else 
    {
        iic_send_byte(0XA0 + ((addr >> 8) << 1));   /* 器件地址 0XA0 + 高位地址 */
    }
    
    iic_wait_ack();             /* 等待从机应答 */
    iic_send_byte(addr % 256);  /* 发送低字节地址 */
    iic_wait_ack();             /* 等待从机应答,此时地址设置完成 */
    
    /* 写数据时不需要重新发送起始信号,直接发送数据即可 */
    iic_send_byte(data);        /* 发送要写入的数据 */
    iic_wait_ack();             /* 等待从机应答 */
    iic_stop();                 /* 发送停止信号 */
    delay_ms(10);               /* EEPROM写操作需要时间,等待10ms以上才能进行下一次写入 */
}
读取多字节
void at24cxx_read(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{
    while (datalen--)
    {
        *pbuf++ = at24cxx_read_one_byte(addr++);    /* 循环读取每个字节 */
    }
}
写入多字节
void at24cxx_write(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{
    while (datalen--)
    {
        at24cxx_write_one_byte(addr, *pbuf);    /* 写入一个字节数据 */
        addr++;                                  /* 地址递增 */
        pbuf++;                                 /* 数据指针递增 */
    }
}
设备检测
uint8_t at24cxx_check(void)
{
    uint8_t temp;
    uint16_t addr = EE_TYPE;    /* 使用最大地址进行测试 */
    
    /* 首先读取当前值,避免不必要的写操作 */
    temp = at24cxx_read_one_byte(addr);

    if (temp == 0X55)   /* 如果当前值已经是0x55,说明器件正常 */
    {
        return 0;
    }
    else    /* 当前值不是0x55,需要进行写入测试 */
    {
        at24cxx_write_one_byte(addr, 0X55); /* 写入测试数据 */
        temp = at24cxx_read_one_byte(addr);  /* 读取写入的数据 */

        if (temp == 0X55) return 0;          /* 读取值正确,设备正常 */
    }

    return 1;   /* 读取值错误,设备异常 */
}

4. 应用示例

4.1 基本读写操作

以下是AT24CXX EEPROM基本读写操作的示例代码:

/* 初始化 */
at24cxx_init();

/* 检查设备是否正常 */
if (at24cxx_check() == 0)
{
    printf("AT24CXX检测正常!\r\n");
    
    /* 写入测试数据 */
    uint8_t write_buf[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    at24cxx_write(0, write_buf, 10);
    
    /* 读取测试数据 */
    uint8_t read_buf[10] = {0};
    at24cxx_read(0, read_buf, 10);
    
    /* 验证数据 */
    uint8_t i;
    for (i = 0; i < 10; i++)
    {
        printf("读取数据[%d]: %d\r\n", i, read_buf[i]);
    }
}
else
{
    printf("AT24CXX检测失败!\r\n");
}

4.2 数据存储应用

AT24CXX EEPROM常用于存储系统配置、校准数据、日志信息等。以下是一个存储系统配置的示例:

/* 定义系统配置结构体 */
typedef struct
{
    uint8_t device_id;
    uint16_t sample_rate;
    uint8_t channel_num;
    uint8_t filter_mode;
    uint16_t alarm_threshold;
    uint8_t checksum;  /* 校验和,用于验证数据完整性 */
} SystemConfig_t;

/* 计算校验和 */
uint8_t calculate_checksum(SystemConfig_t *config)
{
    uint8_t *ptr = (uint8_t *)config;
    uint8_t sum = 0;
    uint16_t i;
    
    /* 计算除校验和外所有字节的和 */
    for (i = 0; i < sizeof(SystemConfig_t) - 1; i++)
    {
        sum += ptr[i];
    }
    
    return sum;
}

/* 保存系统配置 */
void save_system_config(SystemConfig_t *config)
{
    /* 计算校验和 */
    config->checksum = calculate_checksum(config);
    
    /* 写入EEPROM */
    at24cxx_write(0, (uint8_t *)config, sizeof(SystemConfig_t));
}

/* 加载系统配置 */
uint8_t load_system_config(SystemConfig_t *config)
{
    /* 从EEPROM读取配置 */
    at24cxx_read(0, (uint8_t *)config, sizeof(SystemConfig_t));
    
    /* 验证校验和 */
    uint8_t checksum = calculate_checksum(config);
    if (checksum == config->checksum)
    {
        return 0;  /* 校验成功 */
    }
    else
    {
        return 1;  /* 校验失败 */
    }
}

/* 使用示例 */
void config_example(void)
{
    SystemConfig_t config;
    
    /* 初始化AT24CXX */
    at24cxx_init();
    
    /* 尝试加载配置 */
    if (load_system_config(&config) == 0)
    {
        printf("加载配置成功!\r\n");
        printf("设备ID: %d\r\n", config.device_id);
        printf("采样率: %d\r\n", config.sample_rate);
    }
    else
    {
        printf("配置无效或首次使用,使用默认配置\r\n");
        
        /* 设置默认配置 */
        config.device_id = 1;
        config.sample_rate = 1000;
        config.channel_num = 4;
        config.filter_mode = 2;
        config.alarm_threshold = 500;
        
        /* 保存默认配置 */
        save_system_config(&config);
    }
}

在OTA(空中升级)应用中,AT24CXX也可用于存储固件版本信息、升级状态等关键数据:

/* 读取OTA标志 */
void Read_OTA_Flag(void)
{
    memset(&ota_info, 0, OTA_INFO_SIZE);
    at24cxx_read(0, (uint8_t *)&ota_info, OTA_INFO_SIZE);
}

/* 写入OTA标志 */
void Write_OTA_Flag(void)
{
    uint8_t *wptr = (uint8_t *)&ota_info;
    at24cxx_write(0, wptr, OTA_INFO_SIZE);
}

通过这种方式,系统可以在断电或重启后恢复之前的状态,确保OTA过程的可靠性和连续性。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐