全部例程

I2C通信测试

W55MH32

更新于 2026年8月13日

本篇总结

使用硬件 I2C1 驱动板载 AHT20 温湿度传感器和 AT24C16C EEPROM,读取温湿度并验证读写。

🔑 KEIL MDK、UART调试、I2C、AHT20、EEPROM

整改说明

原例程为自发自收测试,未使用板载 AHT20。本次整改改为驱动实际板载器件 AHT20 + AT24C16C,代码已重写。

本示例演示 W55MH32L 的硬件 I2C1 外设,驱动板载两颗 I2C 器件:AHT20(温湿度传感器,7位地址 0x38)和 AT24C16C(EEPROM,7位地址 0x50)。两者共用 I2C1 总线(PB6=SCL / PB7=SDA)。程序先读取 AHT20 温湿度并打印,再对 EEPROM 写入一字节并读回验证。

前置工具准备

开始本例程前,请确认以下工具已安装:

  • WIZ UartTool V1.0:串口收发测试工具:点击下载

硬件连接

本例程使用 I2C1 的 SCL(PB6) 和 SDA(PB7) 引脚连接板载两颗 I2C 器件:AHT20 温湿度传感器(7位地址 0x38)和 AT24C16C EEPROM(7位地址 0x50),两者共用同一条 I2C 总线。PB6/PB7 配置为复用开漏输出并使能内部上拉。USART1 用于输出调试信息。

引脚功能方向模式
PB6I2C1_SCL(时钟线)开发板 ↔ AHT20/EEPROM复用开漏输出 (AF_OD)
PB7I2C1_SDA(数据线)开发板 ↔ AHT20/EEPROM复用开漏输出 (AF_OD)
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)

板载器件地址表:

器件7位地址库调用基础地址说明
AHT20 温湿度传感器0x380x70 (0x38<<1)触发测量 0xAC/0x33/0x00,读6字节
AT24C16C EEPROM0x500xA0 (0x50<<1)2KB容量,11位地址,高3位入设备字节

地址参数注意

标准库 I2C_Send7bitAddress() 的 Address 参数为「7位地址<<1」,R/W 位由 Direction 决定。读写均传基础地址(0x70/0xA0),靠 Direction(Receiver/Transmitter) 区分方向,不要传读写地址(0x71/0xA1)。

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\I2C\,核心文件如下:

I2C/
├── USER/
│   ├── main.c            ← I2C配置 + AHT20驱动 + AT24C16C读写
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:I2C 初始化与 AHT20 温湿度读取

文件:USER/main.c — 配置 PB6(SCL)/PB7(SDA) 为复用开漏输出并使能内部上拉,I2C1 为主机模式、100kHz 时钟、7 位地址、使能 ACK。编写 AHT20 初始化函数(等待 40ms、读状态字检查校准位 bit3、发 0xBE 0x08 0x00)和温湿度读取函数(触发 0xAC 0x33 0x00、等 80ms、读 6 字节换算)。


/* AHT20 7位地址 0x38 << 1 */
#define AHT20_ADDR        0x70
/* AT24C16C 7位地址 0x50 << 1 */
#define AT24C_ADDR_BASE   0xA0

/* I2C1 配置:PB6=SCL / PB7=SDA,100kHz 标准模式 */
void IIC_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    I2C_InitTypeDef  I2C_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    /* PB6/PB7: 复用开漏输出 */
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_OD;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* 使能内部上拉 */
    GPIO_ForcePuPdCmd(GPIOB, ENABLE);
    GPIO_ForcePullUpConfig(GPIOB, GPIO_Pin_6);
    GPIO_ForcePullUpConfig(GPIOB, GPIO_Pin_7);

    I2C_DeInit(I2C1);
    I2C_InitStructure.I2C_Mode                = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle           = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_OwnAddress1         = 0xA0;
    I2C_InitStructure.I2C_ClockSpeed          = 100000;   /* 100kHz */
    I2C_InitStructure.I2C_Ack                 = I2C_Ack_Enable;

    I2C_Init(I2C1, &I2C_InitStructure);
    I2C_Cmd(I2C1, ENABLE);
    I2C_AcknowledgeConfig(I2C1, I2C_Ack_Enable);
}

/* AHT20 初始化:等待40ms,读状态字检查校准位bit3,发0xBE 0x08 0x00 */
uint8_t AHT20_Init(void)
{
    uint8_t status;

    delay_ms(40);  /* AHT20 上电后需等待 40ms 才能通信 */

    /* 读取状态字,检查校准使能位(bit3) */
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, AHT20_ADDR, I2C_Direction_Receiver);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    /* 单字节读:进入接收模式后立即 NACK + STOP,再读 DR */
    I2C_AcknowledgeConfig(I2C1, I2C_Ack_Disable);
    I2C_GenerateSTOP(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
    status = I2C_ReceiveData(I2C1);

    /* 若未校准,发送初始化命令 0xBE 0x08 0x00 */
    if ((status & 0x08) == 0)
    {
        I2C_GenerateSTART(I2C1, ENABLE);
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
        I2C_Send7bitAddress(I2C1, AHT20_ADDR, I2C_Direction_Transmitter);
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

        I2C_SendData(I2C1, 0xBE);
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
        I2C_SendData(I2C1, 0x08);
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
        I2C_SendData(I2C1, 0x00);
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
        I2C_GenerateSTOP(I2C1, ENABLE);
        delay_ms(10);
    }
    return 0;
}

/* AHT20 温湿度读取:触发0xAC 0x33 0x00,等80ms,读6字节换算 */
uint8_t AHT20_Read(float *temperature, float *humidity)
{
    uint8_t  data[6] = {0};
    uint8_t  i;
    uint32_t raw_h, raw_t;

    /* 触发测量:0xAC 0x33 0x00 */
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, AHT20_ADDR, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, 0xAC);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    I2C_SendData(I2C1, 0x33);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    I2C_SendData(I2C1, 0x00);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    I2C_GenerateSTOP(I2C1, ENABLE);

    delay_ms(80);  /* 测量等待 */

    /* 读 6 字节:状态 + 湿度20bit + 温度20bit */
    I2C_AcknowledgeConfig(I2C1, I2C_Ack_Enable);
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, AHT20_ADDR, I2C_Direction_Receiver);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    for (i = 0; i < 6; i++)
    {
        if (i == 5)  /* 最后一字节:NACK + STOP */
        {
            I2C_AcknowledgeConfig(I2C1, I2C_Ack_Disable);
            I2C_GenerateSTOP(I2C1, ENABLE);
        }
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
        data[i] = I2C_ReceiveData(I2C1);
    }

    if (data[0] & 0x80)  /* bit7=1 表示忙 */
        return 1;

    raw_h = ((uint32_t)data[1] << 12) | ((uint32_t)data[2] << 4) | (data[3] >> 4);
    raw_t = (((uint32_t)data[3] & 0x0F) << 16) | ((uint32_t)data[4] << 8) | data[5];

    *humidity    = (float)raw_h * 100.0f / 1048576.0f;
    *temperature = (float)raw_t * 200.0f / 1048576.0f - 50.0f;

    return 0;
}
  

IIC_Configuration:使能 GPIOB 和 I2C1 时钟,配置 PB6/PB7 为复用开漏输出(GPIO_Mode_AF_OD)并启用内部上拉,初始化 I2C1 为 100kHz 标准模式、7位地址、使能 ACK。

AHT20_Init:上电等待 40ms 后读取状态字,检查校准使能位(bit3)。若未校准则发送初始化命令 0xBE 0x08 0x00。单字节读时序:EV6 后立即 NACK+STOP 再读 DR。

AHT20_Read:发触发测量命令 0xAC 0x33 0x00,等待 80ms,读 6 字节(状态+湿度20bit+温度20bit)。换算公式:湿度 = raw_h × 100 / 2²⁰;温度 = raw_t × 200 / 2²⁰ − 50。多字节读最后一字节前必须 NACK+STOP。

如果你想自定义

  • 改通信速率 → 修改 I2C_ClockSpeed,如 400000 为快速模式 400kHz
  • 换 I2C 器件 → 修改设备地址宏定义,按目标器件 datasheet 调整读写时序
  • 连续读写 → 在发送地址后连续发送/接收多个字节,最后发送 STOP/NACK
  • 换引脚 → 使用 I2C2 的 PB10(SCL)/PB11(SDA)

步骤2:AT24C16C 读写与主测试

函数:AT24C_WriteByte() / AT24C_ReadByte() / main() — AT24C16C 容量 2KB,11 位地址,高 3 位放入设备地址字节 bit3-1,低 8 位作为 word address。主函数先读 AHT20 温湿度,再对 EEPROM 写入 0x5A 并读回验证。


/* AT24C16C 写字节:高3位地址放入设备地址 bit3-1 */
void AT24C_WriteByte(uint16_t addr, uint8_t data)
{
    uint8_t dev = AT24C_ADDR_BASE | (uint8_t)((addr & 0x0700) >> 7);

    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, dev, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, (uint8_t)(addr & 0xFF));   /* word address 低8位 */
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_SendData(I2C1, data);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    I2C_GenerateSTOP(I2C1, ENABLE);
}

/* AT24C16C 读字节:dummy write 设地址后 restart 读 */
uint8_t AT24C_ReadByte(uint16_t addr)
{
    uint8_t dev = AT24C_ADDR_BASE | (uint8_t)((addr & 0x0700) >> 7);
    uint8_t val;

    /* dummy write: 设置待读地址 */
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, dev, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, (uint8_t)(addr & 0xFF));
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    /* restart read */
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, dev, I2C_Direction_Receiver);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    /* 单字节读:NACK + STOP 后读 DR */
    I2C_AcknowledgeConfig(I2C1, I2C_Ack_Disable);
    I2C_GenerateSTOP(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
    val = I2C_ReceiveData(I2C1);

    return val;
}

int main(void)
{
    RCC_ClocksTypeDef clocks;
    float             temperature = 0.0f, humidity = 0.0f;
    uint8_t           write_val = 0x5A, read_val = 0;

    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000, (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000, (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);
    printf("I2C Test: AHT20 (0x38) + AT24C16C (0x50), I2C1 PB6/PB7.\n");

    IIC_Configuration();

    /* 1. AHT20 温湿度读取 */
    if (AHT20_Init() == 0)
    {
        printf("AHT20 init OK.\n");
        if (AHT20_Read(&temperature, &humidity) == 0)
        {
            printf("AHT20: Temperature = %.2f C, Humidity = %.2f %%RH\n", temperature, humidity);
        }
        else
        {
            printf("AHT20 read failed.\n");
        }
    }
    else
    {
        printf("AHT20 init failed.\n");
    }

    /* 2. AT24C16C EEPROM 写读验证 */
    printf("\nAT24C16C write 0x%02X to addr 0x0000 ...\n", write_val);
    AT24C_WriteByte(0x0000, write_val);
    delay_ms(5);  /* 页写周期 tWR */
    read_val = AT24C_ReadByte(0x0000);
    printf("AT24C16C read back from addr 0x0000: 0x%02X\n", read_val);
    if (read_val == write_val)
    {
        printf("AT24C16C write/read verify SUCCESS.\n");
    }
    else
    {
        printf("AT24C16C write/read verify FAILED.\n");
    }

    while (1);
}
  

AT24C16C 地址处理:16C 容量 2KB,地址 11 位,高 3 位放入设备地址字节 bit3-1(AT24C_ADDR_BASE | (addr & 0x0700) >> 7),低 8 位作为 word address 发送。

读时序:先 dummy write 写入待读地址,再 Restart 切换为读方向。单字节读:EV6 后 NACK+STOP 再读 DR。写后需 delay_ms(5) 等页写周期 tWR。

主函数流程:初始化延时和 UART → 打印时钟频率 → 配置 I2C1 → 读取 AHT20 温湿度 → 对 AT24C16C 写入 0x5A 并读回验证。

编译与下载

Keil MDK 中打开 I2C.uvprojxF7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。

运行验证

打开 WIZ UartTool V1.0,波特率 115200 / 8N1。下载固件并按 RESET 后,串口首先打印系统时钟信息,然后依次输出 AHT20 温湿度读取结果和 AT24C16C EEPROM 写读验证结果。预期串口输出:

SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 72.0Mhz
I2C Test: AHT20 (0x38) + AT24C16C (0x50), I2C1 PB6/PB7.
AHT20 init OK.
AHT20: Temperature = 25.34 C, Humidity = 45.67 %RH

AT24C16C write 0x5A to addr 0x0000 ...
AT24C16C read back from addr 0x0000: 0x5A
AT24C16C write/read verify SUCCESS.

可看到 AHT20 温湿度读取成功,AT24C16C EEPROM 写读验证通过(写入 0x5A,读回 0x5A)。

I2C AHT20温湿度与EEPROM读写测试串口输出
WIZ UartTool V1.0 显示 I2C 测试结果:AHT20 温湿度读取成功,AT24C16C EEPROM 写读验证通过

常见问题

Q: I2C 通信卡在 I2C_CheckEvent 等待循环

检查 SCL/SDA 引脚(PB6/PB7)是否正确连接,内部上拉是否已使能(GPIO_ForcePullUpConfig)。确认设备地址正确(AHT20 为 0x70,AT24C16C 为 0xA0)。用示波器或逻辑分析仪观察 SCL/SDA 波形。

Q: AHT20 读取失败或数据异常

确认 AHT20 上电后等待了 40ms 再通信。检查初始化命令 0xBE 0x08 0x00 是否正确发送。读取时确认等待了 80ms 测量完成。检查状态字 bit7 是否为 1(忙),若忙需延长等待时间。

Q: AT24C16C 读回数据与写入不一致

EEPROM 写入后需延时 5ms 等待页写周期 tWR 再读取。确认 11 位地址处理正确:高 3 位放入设备地址字节 bit3-1,低 8 位作为 word address。确认读时序中的 Repeated START 和 NACK 时序正确。

Q: I2C 速率无法达到 400kHz

确认 I2C_ClockSpeed 设为 400000,I2C_DutyCycle 设为 I2C_DutyCycle_16_9(快速模式占空比)。检查 APB1 时钟频率是否足够(I2C 时钟来自 APB1)。

总结

本文基于 W55MH32L 的硬件 I2C1 控制器实现了板载 AHT20 温湿度传感器和 AT24C16C EEPROM 的驱动:

  • I2C1 主机模式:100kHz 标准速率,7 位地址,使能 ACK,PB6(SCL)/PB7(SDA) 复用开漏输出并使能内部上拉
  • AHT20 驱动:等待 40ms 上电 → 读状态字检查校准位 → 发 0xBE 0x08 0x00 初始化 → 触发 0xAC 0x33 0x00 测量 → 等 80ms 读 6 字节换算温湿度
  • AT24C16C 读写:2KB 容量,11 位地址(高 3 位入设备地址字节),写入 0x5A 并读回验证通过
  • 地址参数注意:I2C_Send7bitAddress() 传基础地址(0x70/0xA0),R/W 位由 Direction 决定

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家