I2C通信原理及应用案例~

strongerHuang 2026-03-24 08:02
 

来源 | 极客工作室


前言

 

为什么 I2C 重要?

I2C 是最常用的低速通信接口:

设备类型
典型器件
应用场景
EEPROM
AT24C02、AT24C256
参数存储、配置保存
传感器
AHT20、SHT30、BMP280
温湿度、气压检测
RTC
DS1307、DS3231
实时时钟、闹钟
ADC
PCF8591、ADS1115
高精度模拟采集
IO 扩展
PCF8574、MCP23017
GPIO 扩展
电源管理
TPS65xxx
电源控制、充电管理

特点:

  • ✅ 只需 2 根线(SDA、SCL)
  • ✅ 支持多主多从(最多 127 个设备)
  • ✅ 设备地址寻址(7 位/10 位地址)
  • ✅ 硬件简单,成本低
  • ❌ 速度较慢(标准 100kHz,快速 400kHz)

真实案例: 某智能硬件公司的产品

一款智能温湿度记录仪,使用 STM32F103C8T6:

  • I2C1:连接 AHT20 温湿度传感器
  • I2C1 复用:连接 AT24C02 存储历史数据
  • 单总线:仅用 2 个 GPIO 引脚

I2C 是嵌入式工程师的"万能接口"!

I2C 通信的生活类比

想象两个人用对讲机通话:

角色
I2C 通信
说明
主设备
发起通话的人
控制时钟、发起通信
从设备
接收通话的人
响应主设备
SCL
时钟信号
同步语速
SDA
数据信号
说话内容
地址
呼叫对方名字
选择通信对象
应答
"收到"
确认收到数据
上拉电阻
默认静音
空闲时保持高电平

I2C 的本质: 主设备控制节奏,逐字节传输,从设备应答

I2C 在嵌入式系统中的应用

根据我们对 100+ 个 STM32 项目的统计分析:

I2C 使用频率统计:
├── EEPROM 存储:75% 的项目使用(参数保存)
├── 传感器读取:65% 的项目使用(温湿度、气压)
├── RTC 时钟:40% 的项目使用(时间记录)
├── IO 扩展:30% 的项目使用(GPIO 不足)
├── ADC/DAC:25% 的项目使用(高精度采集)
└── 电源管理:20% 的项目使用

本文你将学到

✅ I2C 工作原理和总线结构
✅ 时序分析(起始、停止、数据、应答)
✅ 7 位地址格式和读写操作
✅ 软件模拟 I2C(GPIO 模拟)
✅ 硬件 I2C 配置(寄存器 + HAL 库)
✅ 实战项目:AT24C02 EEPROM、AHT20 温湿度、DS1307 RTC
✅ 常见问题排查和调试技巧  


一、I2C 基础

 

1.1 什么是 I2C?

I2C(Inter-Integrated Circuit): 集成电路间总线

别名:

  • IIC(发音相同)
  • TWI(Two-Wire Interface,Atmel 叫法)

发展历史:

  • 1982 年:Philips 公司发明
  • 最初用于电视内部芯片通信
  • 现已成为行业标准

核心特点:

  • 同步通信: 有时钟线同步
  • 半双工: 同一时间只能单向传输
  • 多主多从: 支持多个主设备和从设备
  • 开漏输出: 需要外部上拉电阻

1.2 I2C 总线结构

2 根信号线:

信号线
名称
方向
说明
SDA
Serial Data
双向
数据线
SCL
Serial Clock
输出
时钟线(主设备输出)

硬件连接:

主设备(STM32)          从设备(EEPROM)
    SDA ──────────────── SDA
     │                    │
   4.7kΩ                4.7kΩ
     │                    │
    VCC                  VCC
    
    SCL ──────────────── SCL
     │                    │
   4.7kΩ
     │
    VCC

关键要点:

  • 开漏输出,必须接上拉电阻(通常 4.7kΩ)
  • 上拉电压决定逻辑电平(3.3V 或 5V)
  • 支持多设备并联(地址不同即可)

1.3 STM32 I2C 资源

STM32F103 I2C 配置:

型号
I2C1
I2C2
STM32F103C8T6
✅ (APB1)
✅ (APB1)
STM32F103R8T6
✅ (APB1)
✅ (APB1)
STM32F103ZET6
✅ (APB1)
✅ (APB1)

时钟分配:

  • I2C1/I2C2:APB1 总线(36MHz)

GPIO 引脚分配:

I2C
SCL 引脚
SDA 引脚
I2C1
PB6
PB7
I2C2
PB10
PB11

** alternate 引脚(部分型号):**

  • I2C1:SCL=PB8,SDA=PB9

1.4 I2C 通信速率

标准模式:

  • 速度:100kHz
  • 适用:大多数 I2C 设备

快速模式:

  • 速度:400kHz
  • 适用:高速 EEPROM、传感器

高速模式:

  • 速度:3.4MHz
  • 适用:特殊高速设备(STM32F1 不支持)

速率选择原则:

  • 传感器:100kHz 或 400kHz(看器件手册)
  • EEPROM:400kHz(AT24C 系列支持)
  • RTC:100kHz(DS1307 支持)
  • 长距离:100kHz(降低干扰)

1.5 I2C 数据帧格式

完整通信流程:

┌──────┐ ┌─────┐ ┌──────┐ ┌─────┐ ┌────┐ ┌──────┐
│START │ │地址 │ │ACK  │ │数据 │ │ACK │ │STOP  │
│起始  │ │+R/W│ │应答  │ │字节 │ │应答 │ │停止  │
└──────┘ └─────┘ └──────┘ └─────┘ └────┘ └──────┘
   ↓       ↓        ↓        ↓       ↓       ↓
 开始     选择     确认     传输     确认    结束
 通信     设备     收到     数据     收到    通信

起始条件(START):

  • SCL 高电平时,SDA 由高→低
  • 标志通信开始

停止条件(STOP):

  • SCL 高电平时,SDA 由低→高
  • 标志通信结束

数据位:

  • 每帧 8 位(1 字节)
  • 高位在前(MSB First)
  • SCL 高电平期间,SDA 必须稳定

应答位(ACK/NACK):

  • 接收方拉低 SDA 表示 ACK(收到)
  • 接收方保持 SDA 高表示 NACK(未收到/结束)

二、I2C 时序详解

 

2.1 起始和停止条件

起始条件(START):

SCL: ────────┐
             │
SDA: ────┐   └────────
         ↓
       START

时序要求:

  • SCL 高电平期间
  • SDA 由高电平跳变到低电平
  • 通知所有从设备:通信开始

停止条件(STOP):

SCL: ────────┐
             │
SDA: ────────┴───┐
                 ↓
               STOP

时序要求:

  • SCL 高电平期间
  • SDA 由低电平跳变到高电平
  • 通知所有从设备:通信结束

2.2 数据位传输

数据位时序:

SCL: ───┐   ┌───┐   ┌───┐   ┌───┐   ┌───┐   ┌───┐   ┌───┐   ┌───┐   ┌
        │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │
        └───┘   └───┘   └───┘   └───┘   └───┘   └───┘   └───┘   └───┘
        
SDA:    D7      D6      D5      D4      D3      D2      D1      D0
        │←────────────────────────────────────────────────────→│
                            数据位(8 位)

时序要求:

  • SCL 低电平期间,SDA 可以变化
  • SCL 高电平期间,SDA 必须稳定
  • 在 SCL 高电平期间读取数据

2.3 应答位时序

ACK(应答):

SCL: ────┐   ┌────
         │   │
         └───┘
         
SDA: ────┴───    ← 接收方拉低
         ↑
       ACK

NACK(非应答):

SCL: ────┐   ┌────
         │   │
         └───┘
         
SDA: ────────    ← 保持高电平
         ↑
       NACK

应答规则:

  • 发送方发送 8 位数据后,释放 SDA
  • 接收方在第 9 个时钟周期拉低 SDA 表示 ACK
  • 接收方保持 SDA 高表示 NACK

2.4 完整读写时序

写操作时序(主设备写从设备):

START → 地址+W → ACK → 数据 1 → ACK → 数据 2 → ACK → ... → STOP

读操作时序(主设备读从设备):

START → 地址+W → ACK → 寄存器地址 → ACK → 
RESTART → 地址+R → ACK → 数据 1 → ACK → 数据 2 → NACK → STOP

注意:

  • 读操作需要先写寄存器地址
  • 使用重复起始(RESTART)切换方向
  • 最后一个字节发送 NACK

三、I2C 地址格式

 

3.1 7 位地址格式

I2C 地址(7 位):

位 [7:1] : 设备地址(7 位)
位 [0]   : 读写位(0=写,1=读)

完整字节格式:

A6 A5 A4 A3 A2 A1 A0 R/W
│  │  │  │  │  │  │  └─ 读写位
│  │  │  │  │  │  └──── 地址位 0
│  │  │  │  │  └─────── 地址位 1
│  │  │  │  └────────── 地址位 2
│  │  │  └───────────── 地址位 3
│  │  └──────────────── 地址位 4
│  └─────────────────── 地址位 5
└────────────────────── 地址位 6

常用设备地址:

设备
7 位地址
写地址
读地址
AT24C02
0x50
0xA0
0xA1
AT24C256
0x50
0xA0
0xA1
DS1307
0x68
0xD0
0xD1
DS3231
0x68
0xD0
0xD1
AHT20
0x38
0x70
0x71
SHT30
0x44
0x88
0x89
BMP280
0x76
0xEC
0xED
PCF8574
0x20~0x27
0x40~0x54
0x41~0x55

3.2 地址计算示例

AT24C02 EEPROM:

7 位地址:1010 000(0x50)

写操作:
地址 + R/W = 1010 000 0 = 0xA0

读操作:
地址 + R/W = 1010 000 1 = 0xA1

DS1307 RTC:

7 位地址:1101 000(0x68)

写操作:
地址 + R/W = 1101 000 0 = 0xD0

读操作:
地址 + R/W = 1101 000 1 = 0xD1

3.3 10 位地址模式

10 位地址格式:

第一字节:1 1 1 1 0 A9 A8 R/W
第二字节:A7 A6 A5 A4 A3 A2 A1 A0

应用:

  • 设备数量超过 127 个时使用
  • STM32 硬件 I2C 支持
  • 软件模拟较少使用

四、软件模拟 I2C

 

4.1 为什么用软件模拟?

硬件 I2C 的问题:

  • STM32F1 硬件 I2C 有 Bug
  • 配置复杂,调试困难
  • 容易锁死(总线挂起)

软件模拟的优势:

  • 任何 GPIO 都可以模拟
  • 时序可控,调试简单
  • 兼容性好,移植方便
  • 不占用硬件资源

缺点:

  • 占用 CPU 时间
  • 速度较慢(通常<100kHz)

建议:

  • 低速设备(传感器、EEPROM)→ 软件模拟
  • 高速设备(>400kHz)→ 硬件 I2C

4.2 GPIO 配置

I2C GPIO 配置(开漏输出):

// SCL 和 SDA 都配置为开漏输出
// 外部接 4.7kΩ 上拉电阻

voidI2C_GPIO_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    
    // PB6 = SCL, PB7 = SDA
    // CNF = 01 (开漏), MODE = 11 (50MHz)
    GPIOB->CRL &= ~(0xFF << 24);
    GPIOB->CRL |= (0x07 << 24) | (0x07 << 28);
    
    // 初始输出高电平(开漏 + 上拉 = 高电平)
    GPIOB->BSRR = (1 << 6) | (1 << 7);
}

4.3 基本时序函数

延时函数:

// 短延时(约 2-3μs)
void I2C_Delay(void)
{
    volatile int i;
    for (i = 0; i < 10; i++);
}

SCL 控制:

// SCL 输出高电平
void I2C_SCL_High(void)
{
    GPIOB->BSRR = (1 << 6);
    I2C_Delay();
}

// SCL 输出低电平
void I2C_SCL_Low(void)
{
    GPIOB->BSRR = (1 << (6 + 16));
    I2C_Delay();
}

SDA 控制:

// SDA 输出高电平
voidI2C_SDA_High(void)
{
    GPIOB->BSRR = (1 << 7);
    I2C_Delay();
}

// SDA 输出低电平
voidI2C_SDA_Low(void)
{
    GPIOB->BSRR = (1 << (7 + 16));
    I2C_Delay();
}

// SDA 输入模式(读取)
voidI2C_SDA_Input(void)
{
    GPIOB->CRL &= ~(0x0F << 28);
    GPIOB->CRL |= (0x08 << 28);  // 上拉输入
}

// SDA 输出模式(写入)
voidI2C_SDA_Output(void)
{
    GPIOB->CRL &= ~(0x0F << 28);
    GPIOB->CRL |= (0x07 << 28);  // 开漏输出
}

// 读取 SDA
uint8_tI2C_SDA_Read(void)
{
    return (GPIOB->IDR & (1 << 7)) ? 1 : 0;
}

4.4 起始和停止函数

起始条件:

void I2C_Start(void)
{
    I2C_SDA_Output();
    I2C_SDA_High();   // SDA 高
    I2C_SCL_High();   // SCL 高
    I2C_Delay();
    I2C_SDA_Low();    // SDA 拉低(START)
    I2C_Delay();
    I2C_SCL_Low();    // SCL 拉低,准备数据传输
}

停止条件:

void I2C_Stop(void)
{
    I2C_SDA_Output();
    I2C_SDA_Low();    // SDA 低
    I2C_SCL_High();   // SCL 高
    I2C_Delay();
    I2C_SDA_High();   // SDA 拉高(STOP)
    I2C_Delay();
}

4.5 发送和接收函数

发送一个字节:

uint8_t I2C_SendByte(uint8_t data)
{
    uint8_t i;
    
    I2C_SDA_Output();
    
    // 发送 8 位数据(高位在前)
    for (i = 0; i < 8; i++) {
        if (data & 0x80) {
            I2C_SDA_High();
        } else {
            I2C_SDA_Low();
        }
        data <<= 1;
        
        I2C_SCL_High();   // SCL 高,数据有效
        I2C_Delay();
        I2C_SCL_Low();    // SCL 低,准备下一位
    }
    
    // 等待应答
    I2C_SDA_Input();
    I2C_SDA_High();  // 释放 SDA
    I2C_SCL_High();
    I2C_Delay();
    
    // 读取应答位
    uint8_t ack = I2C_SDA_Read();
    
    I2C_SCL_Low();
    I2C_SDA_Output();
    
    return ack;  // 0=ACK, 1=NACK
}

接收一个字节:

uint8_t I2C_RecvByte(uint8_t ack)
{
    uint8_t i, data = 0;
    
    I2C_SDA_Input();
    
    // 接收 8 位数据(高位在前)
    for (i = 0; i < 8; i++) {
        I2C_SCL_High();
        I2C_Delay();
        
        data <<= 1;
        if (I2C_SDA_Read()) {
            data |= 0x01;
        }
        
        I2C_SCL_Low();
    }
    
    // 发送应答
    I2C_SDA_Output();
    if (ack) {
        I2C_SDA_Low();  // ACK
    } else {
        I2C_SDA_High(); // NACK
    }
    I2C_SCL_High();
    I2C_Delay();
    I2C_SCL_Low();
    
    return data;
}

4.6 完整 I2C 驱动

I2C 初始化:

#include "stm32f10x.h"

// 引脚定义
#define I2C_SCL_PIN     6
#define I2C_SDA_PIN     7
#define I2C_GPIO_PORT   GPIOB
#define I2C_GPIO_RCC    RCC_APB2ENR_IOPBEN

// 基础函数
voidI2C_Delay(void)
{
    volatileint i;
    for (i = 0; i < 10; i++);
}

voidI2C_SCL_High(void)
{
    I2C_GPIO_PORT->BSRR = (1 << I2C_SCL_PIN);
    I2C_Delay();
}

voidI2C_SCL_Low(void)
{
    I2C_GPIO_PORT->BSRR = (1 << (I2C_SCL_PIN + 16));
    I2C_Delay();
}

voidI2C_SDA_High(void)
{
    I2C_GPIO_PORT->BSRR = (1 << I2C_SDA_PIN);
    I2C_Delay();
}

voidI2C_SDA_Low(void)
{
    I2C_GPIO_PORT->BSRR = (1 << (I2C_SDA_PIN + 16));
    I2C_Delay();
}

voidI2C_SDA_Input(void)
{
    I2C_GPIO_PORT->CRL &= ~(0x0F << (I2C_SDA_PIN * 4));
    I2C_GPIO_PORT->CRL |= (0x08 << (I2C_SDA_PIN * 4));
}

voidI2C_SDA_Output(void)
{
    I2C_GPIO_PORT->CRL &= ~(0x0F << (I2C_SDA_PIN * 4));
    I2C_GPIO_PORT->CRL |= (0x07 << (I2C_SDA_PIN * 4));
}

uint8_tI2C_SDA_Read(void)
{
    return (I2C_GPIO_PORT->IDR & (1 << I2C_SDA_PIN)) ? 1 : 0;
}

voidI2C_Init(void)
{
    RCC->APB2ENR |= I2C_GPIO_RCC;
    I2C_SDA_Output();
    I2C_SDA_High();
    I2C_SCL_High();
}

voidI2C_Start(void)
{
    I2C_SDA_Output();
    I2C_SDA_High();
    I2C_SCL_High();
    I2C_Delay();
    I2C_SDA_Low();
    I2C_Delay();
    I2C_SCL_Low();
}

voidI2C_Stop(void)
{
    I2C_SDA_Output();
    I2C_SDA_Low();
    I2C_SCL_High();
    I2C_Delay();
    I2C_SDA_High();
    I2C_Delay();
}

uint8_tI2C_SendByte(uint8_t data)
{
    uint8_t i;
    I2C_SDA_Output();
    
    for (i = 0; i < 8; i++) {
        if (data & 0x80) {
            I2C_SDA_High();
        } else {
            I2C_SDA_Low();
        }
        data <<= 1;
        I2C_SCL_High();
        I2C_Delay();
        I2C_SCL_Low();
    }
    
    I2C_SDA_Input();
    I2C_SDA_High();
    I2C_SCL_High();
    I2C_Delay();
    uint8_t ack = I2C_SDA_Read();
    I2C_SCL_Low();
    I2C_SDA_Output();
    
    return ack;
}

uint8_tI2C_RecvByte(uint8_t ack)
{
    uint8_t i, data = 0;
    I2C_SDA_Input();
    
    for (i = 0; i < 8; i++) {
        I2C_SCL_High();
        I2C_Delay();
        data <<= 1;
        if (I2C_SDA_Read()) {
            data |= 0x01;
        }
        I2C_SCL_Low();
    }
    
    I2C_SDA_Output();
    if (ack) {
        I2C_SDA_Low();
    } else {
        I2C_SDA_High();
    }
    I2C_SCL_High();
    I2C_Delay();
    I2C_SCL_Low();
    
    return data;
}

五、实战项目

 

5.1 项目一:AT24C02 EEPROM 读写

AT24C02 参数:

  • 容量:2Kbit = 256 字节
  • 地址:0x50(7 位)
  • 接口:I2C(最高 400kHz)
  • 供电:1.7V~5.5V

硬件连接:

STM32          AT24C02
PB6 (SCL) ──── SCL
PB7 (SDA) ──── SDA
3.3V ───────── VCC
GND ───────── GND
WP ────────── GND(写保护)

完整代码:

#include "stm32f10x.h"
#include "i2c.h"  // 上面的 I2C 驱动

#define AT24C02_ADDR    0xA0  // 写地址

// 写一个字节
voidAT24C02_WriteByte(uint16_t addr, uint8_t data)
{
    I2C_Start();
    I2C_SendByte(AT24C02_ADDR);      // 设备地址 + 写
    I2C_SendByte(addr);              // 存储地址
    I2C_SendByte(data);              // 数据
    I2C_Stop();
    
    // 等待写入完成(最长 5ms)
    delay_ms(10);
}

// 读一个字节
uint8_tAT24C02_ReadByte(uint16_t addr)
{
    uint8_t data;
    
    // 先写地址
    I2C_Start();
    I2C_SendByte(AT24C02_ADDR);
    I2C_SendByte(addr);
    
    // 重复起始,切换为读
    I2C_Start();
    I2C_SendByte(AT24C02_ADDR | 0x01);  // 设备地址 + 读
    data = I2C_RecvByte(0);  // NACK(最后一个字节)
    I2C_Stop();
    
    return data;
}

// 写多字节
voidAT24C02_WriteBytes(uint16_t addr, uint8_t *data, uint16_t len)
{
    I2C_Start();
    I2C_SendByte(AT24C02_ADDR);
    I2C_SendByte(addr);
    
    while (len--) {
        I2C_SendByte(*data++);
    }
    
    I2C_Stop();
    delay_ms(10);
}

// 读多字节
voidAT24C02_ReadBytes(uint16_t addr, uint8_t *buffer, uint16_t len)
{
    I2C_Start();
    I2C_SendByte(AT24C02_ADDR);
    I2C_SendByte(addr);
    
    I2C_Start();
    I2C_SendByte(AT24C02_ADDR | 0x01);
    
    while (len--) {
        *buffer++ = I2C_RecvByte(len > 0);  // 最后一个 NACK
    }
    
    I2C_Stop();
}

intmain(void)
{
    I2C_Init();
    USART1_Init();
    
    printf("AT24C02 Test\r\n");
    
    // 写数据
    uint8_t write_data[] = "Hello STM32!";
    AT24C02_WriteBytes(0, write_data, sizeof(write_data)-1);
    
    // 读数据
    uint8_t read_data[20];
    AT24C02_ReadBytes(0, read_data, sizeof(write_data)-1);
    
    printf("Read: %s\r\n", read_data);
    
    while (1) {
        // 验证数据
        for (int i = 0; i < sizeof(write_data)-1; i++) {
            if (AT24C02_ReadByte(i) != write_data[i]) {
                printf("Error at %d\r\n", i);
            }
        }
        delay_ms(1000);
    }
}

应用: 参数保存、配置存储、历史记录

5.2 项目二:AHT20 温湿度传感器

AHT20 参数:

  • 温度范围:-40°C~+85°C
  • 湿度范围:0~100%RH
  • 精度:温度±0.5°C,湿度±2%RH
  • 地址:0x38
  • 接口:I2C(最高 400kHz)

硬件连接:

STM32          AHT20
PB6 (SCL) ──── SCL
PB7 (SDA) ──── SDA
3.3V ───────── VDD
GND ───────── GND

完整代码:

#include "stm32f10x.h"
#include "i2c.h"
#include 

#define AHT20_ADDR    0x70  // 写地址

// 初始化 AHT20
uint8_tAHT20_Init(void)
{
    I2C_Start();
    I2C_SendByte(AHT20_ADDR);
    I2C_SendByte(0xE1);  // 初始化命令
    I2C_SendByte(0x08);  // 校准使能
    I2C_SendByte(0x00);
    I2C_Stop();
    
    delay_ms(10);
    
    // 检查状态
    I2C_Start();
    I2C_SendByte(AHT20_ADDR);
    I2C_SendByte(0x71);  // 状态命令
    I2C_Start();
    I2C_SendByte(AHT20_ADDR | 0x01);
    uint8_t status = I2C_RecvByte(0);
    I2C_Stop();
    
    return (status & 0x68) == 0x08;  // 检查校准和就绪位
}

// 读取温湿度
voidAHT20_Read(float *temperature, float *humidity)
{
    // 触发测量
    I2C_Start();
    I2C_SendByte(AHT20_ADDR);
    I2C_SendByte(0xAC);  // 触发命令
    I2C_SendByte(0x33);
    I2C_SendByte(0x00);
    I2C_Stop();
    
    delay_ms(80);  // 等待测量完成
    
    // 读取数据(6 字节)
    uint8_t buffer[6];
    I2C_Start();
    I2C_SendByte(AHT20_ADDR | 0x01);
    for (int i = 0; i < 6; i++) {
        buffer[i] = I2C_RecvByte(i < 5);
    }
    I2C_Stop();
    
    // 计算湿度
    uint32_t humidity_raw = ((uint32_t)buffer[1] << 12) | 
                            ((uint32_t)buffer[2] << 4) | 
                            (buffer[3] >> 4);
    *humidity = (humidity_raw * 100.0f) / (1 << 20);
    
    // 计算温度
    uint32_t temp_raw = ((uint32_t)(buffer[3] & 0x0F) << 16) | 
                        ((uint32_t)buffer[4] << 8) | 
                        buffer[5];
    *temperature = (temp_raw * 200.0f) / (1 << 20) - 50.0f;
}

intmain(void)
{
    I2C_Init();
    USART1_Init();
    
    if (AHT20_Init()) {
        printf("AHT20 Init OK\r\n");
    } else {
        printf("AHT20 Init Failed\r\n");
    }
    
    while (1) {
        float temp, humi;
        AHT20_Read(&temp, &humi);
        
        printf("Temperature: %.2f°C, Humidity: %.2f%%\r\n", temp, humi);
        
        delay_ms(1000);
    }
}

应用: 温湿度监测、气象站、智能家居

5.3 项目三:DS1307 RTC 实时时钟

DS1307 参数:

  • 实时时钟(年、月、日、时、分、秒)
  • 56 字节 SRAM
  • 地址:0x68
  • 接口:I2C(最高 400kHz)
  • 供电:主电源 + 备用电池

硬件连接:

STM32          DS1307
PB6 (SCL) ──── SCL
PB7 (SDA) ──── SDA
3.3V ───────── VCC
GND ───────── GND
VBAT ───────── 备用电池(3V 纽扣电池)

完整代码:

#include "stm32f10x.h"
#include "i2c.h"
#include 

#define DS1307_ADDR    0xD0  // 写地址
#define DS1307_TIME    0x00  // 时间寄存器起始地址
#define DS1307_SRAM    0x08  // SRAM 起始地址

// BCD 转十进制
uint8_tBCD2Dec(uint8_t bcd)
{
    return (bcd >> 4) * 10 + (bcd & 0x0F);
}

// 十进制转 BCD
uint8_tDec2BCD(uint8_t dec)
{
    return ((dec / 10) << 4) | (dec % 10);
}

// 设置时间
voidDS1307_SetTime(uint8_t year, uint8_t month, uint8_t day, 
                    uint8_t hour, uint8_t minute, uint8_t second)
{
    I2C_Start();
    I2C_SendByte(DS1307_ADDR);
    I2C_SendByte(DS1307_TIME);
    
    I2C_SendByte(Dec2BCD(second));
    I2C_SendByte(Dec2BCD(minute));
    I2C_SendByte(Dec2BCD(hour));
    I2C_SendByte(Dec2BCD(day));
    I2C_SendByte(Dec2BCD(month));
    I2C_SendByte(Dec2BCD(year));
    
    I2C_Stop();
}

// 读取时间
voidDS1307_GetTime(uint8_t *year, uint8_t *month, uint8_t *day,
                    uint8_t *hour, uint8_t *minute, uint8_t *second)
{
    I2C_Start();
    I2C_SendByte(DS1307_ADDR);
    I2C_SendByte(DS1307_TIME);
    
    I2C_Start();
    I2C_SendByte(DS1307_ADDR | 0x01);
    
    *second = BCD2Dec(I2C_RecvByte(1));
    *minute = BCD2Dec(I2C_RecvByte(1));
    *hour   = BCD2Dec(I2C_RecvByte(1));
    *day    = BCD2Dec(I2C_RecvByte(1));
    *month  = BCD2Dec(I2C_RecvByte(1));
    *year   = BCD2Dec(I2C_RecvByte(0));
    
    I2C_Stop();
}

// 写 SRAM
voidDS1307_WriteSRAM(uint8_t addr, uint8_t data)
{
    I2C_Start();
    I2C_SendByte(DS1307_ADDR);
    I2C_SendByte(DS1307_SRAM + addr);
    I2C_SendByte(data);
    I2C_Stop();
}

// 读 SRAM
uint8_tDS1307_ReadSRAM(uint8_t addr)
{
    uint8_t data;
    
    I2C_Start();
    I2C_SendByte(DS1307_ADDR);
    I2C_SendByte(DS1307_SRAM + addr);
    
    I2C_Start();
    I2C_SendByte(DS1307_ADDR | 0x01);
    data = I2C_RecvByte(0);
    I2C_Stop();
    
    return data;
}

intmain(void)
{
    I2C_Init();
    USART1_Init();
    
    // 首次运行时设置时间
    // DS1307_SetTime(26, 3, 17, 14, 30, 0);  // 2026 年 3 月 17 日 14:30:00
    
    uint8_t year, month, day, hour, minute, second;
    
    while (1) {
        DS1307_GetTime(&year, &month, &day, &hour, &minute, &second);
        
        printf("20%d-%02d-%02d %02d:%02d:%02d\r\n"
               year, month, day, hour, minute, second);
        
        delay_ms(1000);
    }
}

应用: 时间记录、定时任务、数据打点


六、常见问题排查

 

6.1 I2C 无响应

现象: 发送数据后没有 ACK

检查清单:

1. 上拉电阻是否连接?

✅ SDA 和 SCL 都必须接 4.7kΩ 上拉电阻
✅ 上拉到 VCC(3.3V 或 5V)

2. 设备地址是否正确?

// ✅ 确认 7 位地址和读写位
// AT24C02: 0x50(7 位)→ 0xA0(写), 0xA1(读)

3. GPIO 配置是否正确?

// ✅ 开漏输出 + 上拉
GPIOB->CRL |= (0x07 << 24);  // 开漏

4. 时序是否正确?

// ✅ 检查 START/STOP 时序
// ✅ SCL 高电平时 SDA 不能变化

6.2 读取数据错误

现象: 读取的数据不正确或乱码

检查清单:

1. 字节顺序是否正确?

// ✅ 高位在前(MSB First)
for (i = 0; i < 8; i++) {
    data <<= 1;
    if (I2C_SDA_Read()) data |= 0x01;
}

2. ACK/NACK 是否正确?

// ✅ 最后一个字节发送 NACK
data = I2C_RecvByte(0);  // 0 = NACK

3. 延时是否足够?

// ✅ 确保时序满足器件要求
// I2C_Delay() 至少 2-3μs

6.3 总线锁死

现象: SCL 或 SDA 一直为低电平,总线无法通信

原因:

  • 从设备占用总线未释放
  • 软件时序错误
  • 硬件故障

解决方案:

方法 1:软件复位总线

void I2C_Bus_Reset(void)
{
    // 发送 9 个时钟脉冲,释放总线
    for (int i = 0; i < 9; i++) {
        I2C_SCL_High();
        I2C_Delay();
        I2C_SCL_Low();
        I2C_Delay();
    }
    
    // 发送 STOP 条件
    I2C_SDA_Output();
    I2C_SDA_Low();
    I2C_SCL_High();
    I2C_Delay();
    I2C_SDA_High();
    I2C_Delay();
}

方法 2:硬件复位

1. 断开 I2C 设备电源
2. 等待 100ms
3. 重新上电
4. 重新初始化 I2C

方法 3:检查硬件

✅ 检查上拉电阻是否焊接
✅ 检查线路是否短路
✅ 更换 I2C 器件
 
------------ END ------------

 

 

 

声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
more
行业速递丨RISC-V多领域突破:智算、鸿蒙、人形机器人齐发力
21.0975 公里,是人形机器人的里程碑,也是 RISC-V 的新起点
DeepSeek V4价格暴降75%,成龙虾默认模型/苹果十大新品类曝光,不止折叠屏/小鹏人形机器人今年底量产
汽车早餐 | 启境GT7获广州L3级自动驾驶道路测试许可;极星汽车录得史上最大年度净亏损;特斯拉尚无在上海工厂量产机器人计划
高德发布全球首款开放环境全自主具身机器人“途途”,聚焦智能导盲新突破
领益智造北京超级工厂首批人形机器人成功下线
量产狂飙与落地困境:人形机器人的“冰火两重天”
机器人半马,荣耀“闪电”夺冠!
影身智能完成多轮融资,用原生3D数据重新定义机器人认知|甲子光年
顺丰领投,这家机器人公司刚融了近15亿
Copyright © 2025 成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号