Skip to content

概述

I2C(Inter-Integrated Circuit)是两根线(SCL 时钟 + SDA 数据)即可挂载多个设备的串行总线,每个从设备有唯一 7 位地址,是传感器最常见的接口之一。本教程通过 I2C 读取 SHT30 温湿度传感器,并带 CRC 校验解析温度与湿度。

用大白话讲:I2C 就像一条「走廊」里铺的两根线:一根喊号(SCL 时钟),一根传话(SDA 数据)。走廊里每个设备都有自己的「门牌号」(7 位地址),喊到谁的号,谁就出来应答。本教程就是主控通过这两根线,向 SHT30 传感器要温度和湿度数据。

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/peripherals/demo_i2c 编写,代码可在本地 SDK 中直接找到。

🎯本页目标通过 I2C 总线读取 SHT30 温湿度传感器,串口打印温度与湿度,掌握 I2C 初始化、主机发送/接收与数据解析。
🧰前置条件① Ai-WB2 开发板、SHT30 温湿度传感器模块 ② 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建,并完成 [GPIO输出(点亮LED)](./gpio_led)。
🔗相关章节其他 I2C 传感器应用见 [最佳实践-BH1750](../best/bh1750);SPI 总线见 [SPI 协议](./spi)。

硬件接线

按官方示例接线(见 SDK applications/peripherals/demo_i2c/README.md):

Ai-WB2 引脚 SHT30 引脚
IO12 SCL
IO3 SDA
3V3 VCC
GND GND

💡 I2C 的 SCL/SDA 是开漏信号(引脚只能主动拉低、不能主动拉高),需要上拉电阻(把线路默认拉到高电平的小电阻)配合。多数 SHT30 模块板载上拉电阻;裸芯片需在 SCL/SDA 上各接 4.7kΩ 上拉到 3V3。

进入示例工程

打开终端,进入官方 demo_i2c 示例工程目录:

cd ~/Ai-Thinker-WB2/applications/peripherals/demo_i2c

说明:cd 是「进入目录」命令,这里进入 demo_i2c 示例工程目录;后续的 make 编译、make flash 烧录命令都必须先在这个目录里执行。

编写代码

打开 demo_i2c/main.c,本步完整代码已移至文末,见:

📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(applications/peripherals/demo_i2c/demo_i2c/main.c)完全一致。

代码要点:

代码 作用
.scl = 12.sda = 3.freq = 100000 I2C 引脚 IO12/IO3,时钟 100kHz(标准模式);引脚接错就通信不上
.mode = HOSAL_I2C_MODE_MASTER 主机模式(开发板当「发话人」主动问,传感器当「应答者」),配成从机就没人发起通信
SHT31_DEFAULT_ADDR 0x0044 SHT30 的 7 位地址(门牌号,ADDR 悬空/接低为 0x44,接高为 0x45),地址错就找不到传感器
hosal_i2c_master_send(&i2c0, addr, command, 2, 100) 发送 2 字节测量命令 0x2400(让传感器开工测量),不发命令传感器不干活
hosal_i2c_master_recv(&i2c0, addr, &data, 6, 100) 接收 6 字节数据:温度 2B+CRC、湿度 2B+CRC,收不到就读不出温湿度
crc8(...) 多项式 0x31 的 CRC8 校验,防止传输错误,没校验会把坏数据当真值

温湿度换算公式(SHT30 数据手册):

数据 换算公式
温度 T = 温度原始值 × 17500 ÷ 0xFFFF - 4500,单位 0.01°C
湿度 RH = 湿度原始值 × 10000 ÷ 0xFFFF,单位 0.01%RH
编译工程

在工程目录执行编译:

make -j8

说明:make 是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8 表示用 8 个核并行编译,速度更快。

编译成功后生成固件 build_out/demo_i2c.bin

烧录固件

开发板保持 USB 连接,确认串口设备号后执行烧录:

make flash p=/dev/ttyUSB0 b=921600

说明:make flash 是「烧录」命令,把编译好的固件写进开发板芯片。p= 后面是串口设备号(改成你电脑上实际的串口,可用 ls /dev/ttyUSB* 查看),b= 是烧录波特率(传输速度)。

⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。

运行验证

烧录完成后开发板自动重启运行,打开串口助手(波特率 921600),每 1 秒打印一次温湿度:

temperature: 26.54 C	humidity: 45 %
temperature: 26.53 C	humidity: 45 %
...

对着传感器哈气,湿度数值应明显上升;用手捏住传感器,温度数值应缓慢上升。CRC 校验失败时打印 N/A

看到串口每 1 秒打印一次实时温湿度即为成功;如果一直打印 N/A 或数值固定不变,说明还没成功,对照文末「常见问题与踩坑提示」排查。

💡 用逻辑分析仪观察 IO12/IO3 波形,可看到 I2C 的 START、地址、数据与 STOP 时序(见官方示例 img/logic_analyzer.jpg)。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

hosal_i2c_init(i2c)

dev 结构体中的引脚、频率、主从模式配置 I2C 控制器(本教程初始化主机模式读取 SHT30)。

参数

  • i2chosal_i2c_dev_t 结构体指针,必填。关键字段:config.mode(可选值:HOSAL_I2C_MODE_MASTER 主机 / HOSAL_I2C_MODE_SLAVE 从机)、config.scl/config.sda(SCL/SDA 引脚号,本教程 IO12/IO3)、config.freq(可选值:100000 标准 100kHz / 400000 快速 400kHz)、config.address_width(可选值:HOSAL_I2C_ADDRESS_WIDTH_7BIT / HOSAL_I2C_ADDRESS_WIDTH_10BIT

返回值:成功返回 0;失败返回负值错误码

hosal_i2c_master_send(i2c, dev_addr, data, size, timeout)

以主机身份向指定地址的从机发送一帧数据(含起始位/地址/停止位,本教程发送 SHT30 测量命令)。

参数

  • i2chosal_i2c_dev_t 结构体指针
  • dev_addr:从机设备地址(7 位地址左移 1 位后的字节),如 0x5c << 1
  • data:发送数据缓冲区指针,必填
  • size:发送字节数,可选值:1~256
  • timeout:等待超时(单位 ms),可选值:如 100

返回值:成功返回 0;失败(无 ACK/超时)返回负值错误码

hosal_i2c_master_recv(i2c, dev_addr, data, size, timeout)

以主机身份从指定从机读取一帧数据(本教程接收 SHT30 的 6 字节温湿度数据)。

参数

  • i2chosal_i2c_dev_t 结构体指针
  • dev_addr:从机设备地址(同 master_send 规则)
  • data:接收数据缓冲区指针,必填
  • size:期望接收字节数
  • timeout:等待超时(单位 ms)

返回值:成功返回 0;失败返回负值错误码

hosal_i2c_mem_write(i2c, dev_addr, mem_addr, data, size, timeout)

写从机内部寄存器:先发送寄存器地址,再发送数据(传感器配置常用)。

参数

  • i2chosal_i2c_dev_t 结构体指针
  • dev_addr:从机设备地址
  • mem_addr:从机寄存器地址(8 位寄存器地址,可选值:0x00~0xFF,具体见芯片手册)
  • data:待写数据缓冲区指针
  • size:写入字节数
  • timeout:等待超时(单位 ms)

返回值:成功返回 0;失败返回负值错误码

hosal_i2c_mem_read(i2c, dev_addr, mem_addr, data, size, timeout)

先向从机发送寄存器地址,再读取该寄存器内容(读传感器数据常用)。

参数

  • i2chosal_i2c_dev_t 结构体指针
  • dev_addr:从机设备地址
  • mem_addr:从机寄存器地址(可选值:0x00~0xFF
  • data:接收缓冲区指针,必填
  • size:期望读取字节数
  • timeout:等待超时(单位 ms)

返回值:成功返回 0;失败返回负值错误码

hosal_i2c_finalize(i2c)

停止 I2C 控制器并释放占用引脚(不再使用前调用)。

参数

  • i2chosal_i2c_dev_t 结构体指针

返回值:成功返回 0;失败返回负值错误码

blog_info(fmt, ...)

输出一条 INFO 级日志(UART0,受级别过滤)。

参数

  • fmt:格式化字符串,同 printf 用法,必填
  • ...:变参,与 fmt 占位符对应,可省略

返回值:无


完整代码

以下为 demo_i2c/main.c 完整源码,与官方示例(applications/peripherals/demo_i2c/demo_i2c/main.c)完全一致:

📜 点击展开 demo_i2c/main.c 完整代码
c
#include <stdio.h>

#include <FreeRTOS.h>
#include <task.h>

#include <hosal_i2c.h>
#include <bl_gpio.h>
#include <blog.h>

#define SHT31_DEFAULT_ADDR 0x0044
#define SHT31_MEAS_HIGHREP 0x2400

#pragma pack(1)
struct sht3x_data
{
    uint8_t st_high;
    uint8_t st_low;
    uint8_t st_crc8;
    uint8_t srh_high;
    uint8_t srh_low;
    uint8_t srh_crc8;
};
#pragma pack()

static uint8_t crc8(uint8_t *data, int len)
{
    const uint8_t POLYNOMIAL = 0x31;
    uint8_t crc = 0xFF;
    for (int j = len; j; --j)
    {
        crc ^= *data++;
        for (int i = 8; i; --i)
        {
            crc = (crc & 0x80)
                      ? (crc << 1) ^ POLYNOMIAL
                      : (crc << 1);
        }
    }
    return crc;
}

int main(void)
{
    static hosal_i2c_dev_t i2c0 = {
        .config = {
            .address_width = HOSAL_I2C_ADDRESS_WIDTH_7BIT,
            .freq = 100000,
            .mode = HOSAL_I2C_MODE_MASTER,
            .scl = 12,
            .sda = 3,
        },
        .port = 0,
    };

    hosal_i2c_init(&i2c0);

    for (;;) {
        
        struct sht3x_data data;

        uint8_t command[2] = { SHT31_MEAS_HIGHREP >> 8, SHT31_MEAS_HIGHREP & 0xff };
        hosal_i2c_master_send(&i2c0, SHT31_DEFAULT_ADDR, command, sizeof command, 100);
        hosal_i2c_master_recv(&i2c0, SHT31_DEFAULT_ADDR, (uint8_t*)&data, sizeof data, 100);

        char temperature_str[8];
        char humidity_str[8];

        if (crc8(&data.st_high, 2) == data.st_crc8) {
            uint16_t st = data.st_high;
            st <<= 8;
            st |= data.st_low;

            int temp = st;
            temp *= 17500;
            temp /= 0xffff;
            temp = -4500 + temp;

            int temperature_integer = temp / 100;
            
            if (temp < 0) {
                temp = -temp;
            }

            unsigned temperature_decimal = temp % 100;

            sprintf(temperature_str, "%d.%02u C", temperature_integer, temperature_decimal);
        }
        else {
            sprintf(temperature_str, "%s", "N/A C");
        }
        
        if (crc8(&data.srh_high, 2) == data.srh_crc8) {
            uint16_t srh = data.srh_high;
            srh <<= 8;
            srh |= data.srh_low;

            unsigned humidity = srh;
            humidity *= 10000;
            humidity /= 0xFFFF;

            unsigned humidity_integer = humidity / 100;

            sprintf(humidity_str, "%u %%", humidity_integer);
        }
        else {
            sprintf(humidity_str, "N/A %%");
        }

        blog_info("temperature: %s\thumidity: %s\r\n", temperature_str, humidity_str);

        vTaskDelay(portTICK_RATE_MS * 1000);
    }

    return 0;
}

常见问题与踩坑提示

⚠️ 打印 N/A(CRC 校验失败)
原因:接线接触不良、SDA/SCL 无上拉电阻、或模块供电不足
解决:确认 SDA/SCL 有 4.7kΩ 上拉;缩短杜邦线;确认模块 VCC 接 3V3 且共地

⚠️ 数据为 0 或固定不变
原因:从机地址错误(SHT30 的 ADDR 引脚接 3V3 时地址变为 0x45)
解决:确认模块 ADDR 状态,SHT31_DEFAULT_ADDR 改为 0x0045 或对应地址

⚠️ 更换传感器后读取失败
原因:不同 I2C 传感器的命令格式与寄存器地址不同
解决:先查目标传感器数据手册,使用 hosal_i2c_mem_read/mem_write 按寄存器地址读写(demo_i2c 用的是不带寄存器地址的 SHT3x 私有协议)

⚠️ IO12/IO3 与其他功能冲突
原因:IO12/IO3 同时被 SPI/ADC 等复用功能占用
解决:确认工程中无其他模块占用这两个引脚;或修改 .scl/.sda 换用其他可用引脚

⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试

⚠️ 找不到串口设备或提示无权限
原因:Linux 下 /dev/ttyUSB0 不存在或权限不足,Windows 下未安装 USB 转串口驱动
解决:Linux 用 ls /dev/ttyUSB* 确认设备号,权限不足执行 sudo usermod -aG dialout $USER 后重新登录;Windows 在设备管理器安装驱动并确认 COM 口号

运行自检

串口每秒打印一次温湿度,数值随环境变化而更新,即 I2C 通信验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23