来源 | 极客工作室
前言
为什么 I2C 重要?
I2C 是最常用的低速通信接口:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
特点:
-
✅ 只需 2 根线(SDA、SCL) -
✅ 支持多主多从(最多 127 个设备) -
✅ 设备地址寻址(7 位/10 位地址) -
✅ 硬件简单,成本低 -
❌ 速度较慢(标准 100kHz,快速 400kHz)
真实案例: 某智能硬件公司的产品
一款智能温湿度记录仪,使用 STM32F103C8T6:
-
I2C1:连接 AHT20 温湿度传感器 -
I2C1 复用:连接 AT24C02 存储历史数据 -
单总线:仅用 2 个 GPIO 引脚
I2C 是嵌入式工程师的"万能接口"!
I2C 通信的生活类比
想象两个人用对讲机通话:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
| SCL |
|
|
|
硬件连接:
主设备(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:APB1 总线(36MHz)
GPIO 引脚分配:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
** 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
常用设备地址:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 器件