tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(基础篇)V1.0.2 — 第9章:I2C通信 + 配套代码13~15" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:I2C 就像教室里的传纸条游戏——SCL 是老师拍手的节奏(时钟线),SDA 是传的纸条(数据线)。老师每拍一次手(时钟周期),大家就传递一位。每个同学有唯一的学号(设备地址 7 位),老师喊学号时对应同学才回答。STOP 条件就像下课铃——老师拍手节奏变了,表示传纸条结束。
I2C(Inter-Integrated Circuit,读作 I-squared-C)是一种同步、半双工、多主从的串行通信总线,由 Philips 公司(现 NXP)发明。
| 特性 | 说明 |
|---|---|
| 线数 | SCL(时钟)+ SDA(数据)两根线 |
| 模式 | 半双工(同一时刻只能单向传输) |
| 速率 | 标准 100Kbps / 快速 400Kbps / 高速 3.4MHz |
| 主从 | 多主多从(通过仲裁解决冲突) |
| 地址 | 7 位地址(最多 127 个设备)或 10 位地址 |
| 上拉电阻 | 两根线都需要外部上拉电阻(4.7KΩ 典型) |
参考:参考手册 §16(I2C 寄存器描述) 参考:NXP I2C 总线规范(UM10204)
SCL 和 SDA 都是高电平(由上拉电阻拉高)。
SCL 为高电平时,SDA 从高电平切换到低电平
代码实现:
SCL_HIGH; // SCL = 1
SDA_HIGH; // SDA = 1
I2C_DELAY; // 等待(数据建立时间)
SDA_LOW; // SDA = 0 → 起始条件(在 SCL 高电平时 SDA 变低)
I2C_DELAY; // 等待(保持时间)
SCL 为高电平时,SDA 从低电平切换到高电平
代码实现:
SCL_LOW; // SCL = 0(准备)
SDA_LOW; // SDA = 0
SCL_HIGH; // SCL = 1
I2C_DELAY;
SDA_HIGH; // SDA = 0 → 1 → 终止条件(在 SCL 高电平时 SDA 变高)
I2C_DELAY;
每个字节 8 位,MSB(最高位)先发。SCL 低电平时允许 SDA 变化,SCL 高电平时 SDA 必须稳定(接收方采集数据)。
SCL: ████░░░░████░░░░████░░░░....████░░░░██
SDA: ██░░██░░██░░██░░ ██░░██░░
↑MSB ↑LSB
SCL低时SDA可以变 SCL高时采集
每个字节后,接收方在第 9 个 SCL 周期控制 SDA:
NACK:SDA 释放(高电平,表示"收完"或"不再接收")
主机发送8位数据后:
主机释放SDA → 从机在第9个SCL脉冲拉低SDA → 应答(ACK)
主机写数据到从机:
START + 7位从机地址 + R/W(0=写) + ACK + 数据1 + ACK + 数据2 + ACK + ... + STOP
主机从从机读数据:
START + 7位从机地址 + R/W(1=读) + ACK + 从机发数据1 + 主机ACK + ... + 从机发数据N + 主机NACK + STOP
组合格式(先写地址再读):
START + 从机地址(W) + ACK + 寄存器地址 + ACK + RESTART + 从机地址(R) + ACK + 数据 + NACK + STOP
VCC(3.3V) VCC(3.3V)
├── Rpu(4.7KΩ) ├── Rpu(4.7KΩ)
│ │
├──── SCL ─────────┤
│ PB10 │ 24C02 SCL
├──── SDA ─────────┤
│ PB11 │ 24C02 SDA
│ │
GND ────────────── GND
I2C 总线需要外部上拉电阻(典型值 4.7KΩ),因为 I2C 引脚使用开漏输出:
参考:开发板原理图 I2C 接口部分(24C02 电路) 参考:参考手册 §16(I2C 寄存器)
24C02 是一个 2Kbit(256 字节) 的 I2C 串行 EEPROM。
| 参数 | 值 |
|---|---|
| 容量 | 256 字节(2Kbit) |
| 页大小 | 8 字节(一次页写最多 8 字节) |
| 写周期 | 5ms(内部擦写时间,期间不响应) |
| I2C 地址 | 0xA0(写)/ 0xA1(读) |
| 地址引脚 | A0/A1/A2(可配置,最多 8 个同型号设备共用总线) |
| 工作电压 | 1.8V~5.5V |
| 接口 | I2C(标准/快速模式) |
地址计算:
设备地址 = 1010 + A2 + A1 + A0 + R/W
[7:4] [3] [2] [1] [0]
1010 0 0 0 0/1
写地址 = 1010 0000 = 0xA0
读地址 = 1010 0001 = 0xA1
页写注意事项:24C02 的页大小为 8 字节,一次页写操作只能在同一页内。如果尝试跨页写入(如从地址 0x07 开始写 3 字节),地址会自动卷回到页开头(地址 0x00),覆盖已有数据。跨页时必须手动分页写入。
项目路径:stm32/13_i2c_software_register
文件:stm32/13_i2c_software_register/Hardware/I2C/i2c.h
#ifndef __I2C_H
#define __I2C_H
#include "stm32f10x.h"
#include "usart.h"
#include "delay.h"
// PB10=SCL, PB11=SDA
#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
#define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)
#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)
#define READ_SDA (GPIOB->IDR & GPIO_IDR_IDR11)
#define ACK 0
#define NACK 1
#define I2C_DELAY Delay_us(10)
void I2C_Init(void);
void I2C_Start(void);
void I2C_Stop(void);
void I2C_Ack(void);
void I2C_NAck(void);
uint8_t I2C_Wait4Ack(void);
void I2C_SendByte(uint8_t byte);
uint8_t I2C_ReadByte(void);
#endif
文件:stm32/13_i2c_software_register/Hardware/I2C/i2c.c
#include "i2c.h"
void I2C_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);
GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);
}
void I2C_Start(void)
{
SCL_HIGH;
SDA_HIGH;
I2C_DELAY;
SDA_LOW;
I2C_DELAY;
}
void I2C_Stop(void)
{
SCL_HIGH;
SDA_LOW;
I2C_DELAY;
SDA_HIGH;
I2C_DELAY;
}
void I2C_Ack(void)
{
SCL_LOW;
SDA_HIGH;
I2C_DELAY;
SDA_LOW;
I2C_DELAY;
SCL_HIGH;
I2C_DELAY;
SCL_LOW;
I2C_DELAY;
SDA_HIGH;
I2C_DELAY;
}
void I2C_Nack(void)
{
SCL_LOW;
SDA_HIGH;
I2C_DELAY;
SCL_HIGH;
I2C_DELAY;
SCL_LOW;
I2C_DELAY;
}
uint8_t I2C_Wait4Ack(void)
{
SDA_HIGH;
SCL_LOW;
I2C_DELAY;
SCL_HIGH;
I2C_DELAY;
uint8_t ack = READ_SDA;
SCL_LOW;
I2C_DELAY;
return ack ? NACK : ACK;
}
void I2C_SendByte(uint8_t byte)
{
for (uint8_t i = 0; i < 8; i++)
{
SCL_LOW;
I2C_DELAY;
if (byte & 0x80)
SDA_HIGH;
else
SDA_LOW;
I2C_DELAY;
SCL_HIGH;
I2C_DELAY;
SCL_LOW;
I2C_DELAY;
byte <<= 1;
}
}
uint8_t I2C_ReadByte(void)
{
uint8_t data = 0;
for (uint8_t i = 0; i < 8; i++)
{
SDA_HIGH;
SCL_LOW;
I2C_DELAY;
SCL_HIGH;
I2C_DELAY;
data <<= 1;
if (READ_SDA)
data |= 0x01;
SCL_LOW;
I2C_DELAY;
}
return data;
}
文件:stm32/13_i2c_software_register/User/main.c
#include "usart.h"
#include "m24c02.h"
#include <string.h>
int main(void)
{
USART_Init();
M24C02_Init();
printf("尚硅谷 I2C 软件模拟实验...\n");
M24C02_WriteByte(0x00, 'a');
M24C02_WriteByte(0x01, 'b');
M24C02_WriteByte(0x02, 'c');
uint8_t byte1 = M24C02_ReadByte(0x00);
uint8_t byte2 = M24C02_ReadByte(0x01);
uint8_t byte3 = M24C02_ReadByte(0x02);
printf("%c\n%c\n%c\n", byte1, byte2, byte3);
M24C02_WriteBytes(0x00, "123456", 6);
uint8_t buff[50] = {0};
M24C02_ReadBytes(0x00, buff, 6);
printf("%s\n", buff);
memset(buff, 0, sizeof(buff));
M24C02_WriteBytes(0x05, "1234567890abcdefghijk", 21);
M24C02_ReadBytes(0x00, buff, 21);
printf("%s\n", buff);
while (1) {}
}
项目路径:stm32/14_i2c_hardware_register
文件:stm32/14_i2c_hardware_register/Hardware/I2C/i2c.h
#ifndef __I2C_H
#define __I2C_H
#include "stm32f10x.h"
#include "delay.h"
#define OK 0
#define FAIL 1
void I2C_Init(void);
uint8_t I2C_Start(void);
void I2C_Stop(void);
void I2C_Ack(void);
void I2C_Nack(void);
uint8_t I2C_SendAddr(uint8_t addr);
uint8_t I2C_SendByte(uint8_t byte);
uint8_t I2C_ReadByte(void);
#endif
文件:stm32/14_i2c_hardware_register/Hardware/I2C/i2c.c
#include "i2c.h"
void I2C_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11 |
GPIO_CRH_CNF10 | GPIO_CRH_CNF11);
I2C2->CR1 &= ~I2C_CR1_SMBUS;
I2C2->CCR &= ~I2C_CCR_FS;
I2C2->CR2 |= 36;
I2C2->CCR |= 180;
I2C2->TRISE |= 37;
I2C2->CR1 |= I2C_CR1_PE;
}
uint8_t I2C_Start(void)
{
I2C2->CR1 |= I2C_CR1_START;
uint16_t timeout = 0xffff;
while ((I2C2->SR1 & I2C_SR1_SB) == 0 && timeout)
{
timeout--;
}
return timeout ? OK : FAIL;
}
void I2C_Stop(void)
{
I2C2->CR1 |= I2C_CR1_STOP;
}
void I2C_Ack(void)
{
I2C2->CR1 |= I2C_CR1_ACK;
}
void I2C_Nack(void)
{
I2C2->CR1 &= ~I2C_CR1_ACK;
}
uint8_t I2C_SendAddr(uint8_t addr)
{
I2C2->DR = addr;
uint16_t timeout = 0xffff;
while ((I2C2->SR1 & I2C_SR1_ADDR) == 0 && timeout)
{
timeout--;
}
if (timeout > 0)
{
I2C2->SR2;
}
return timeout ? OK : FAIL;
}
uint8_t I2C_SendByte(uint8_t byte)
{
uint16_t timeout = 0xffff;
while ((I2C2->SR1 & I2C_SR1_TXE) == 0 && timeout)
{
timeout--;
}
I2C2->DR = byte;
timeout = 0xffff;
while ((I2C2->SR1 & I2C_SR1_BTF) == 0 && timeout)
{
timeout--;
}
return timeout ? OK : FAIL;
}
uint8_t I2C_ReadByte(void)
{
uint16_t timeout = 0xffff;
while ((I2C2->SR1 & I2C_SR1_RXNE) == 0 && timeout)
{
timeout--;
}
return timeout ? I2C2->DR : FAIL;
}
文件:stm32/14_i2c_hardware_register/User/main.c
#include "usart.h"
#include "m24c02.h"
#include <string.h>
int main(void)
{
USART_Init();
M24C02_Init();
printf("尚硅谷 I2C 硬件寄存器实验...\n");
M24C02_WriteByte(0x00, 'a');
M24C02_WriteByte(0x01, 'b');
M24C02_WriteByte(0x02, 'c');
uint8_t byte1 = M24C02_ReadByte(0x00);
uint8_t byte2 = M24C02_ReadByte(0x01);
uint8_t byte3 = M24C02_ReadByte(0x02);
printf("byte1 = %c\t byte2 = %c\t byte3 = %c\n", byte1, byte2, byte3);
M24C02_WriteBytes(0x00, "123456", 6);
uint8_t buffer[100] = {0};
M24C02_ReadBytes(0x00, buffer, 6);
printf("buffer = %s\n", buffer);
memset(buffer, 0, sizeof(buffer));
M24C02_WriteBytes(0x00, "1234567890abcdefghijk", 21);
M24C02_ReadBytes(0x00, buffer, 21);
printf("buffer = %s\n", buffer);
while (1)
{
}
}
项目路径:stm32/15_i2c_hardware_hal
文件:stm32/15_i2c_hardware_hal/Core/Src/i2c.c (CubeMX generated I2C init)
#include "i2c.h"
I2C_HandleTypeDef hi2c2;
void MX_I2C2_Init(void)
{
hi2c2.Instance = I2C2;
hi2c2.Init.ClockSpeed = 100000;
hi2c2.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c2.Init.OwnAddress1 = 0;
hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c2.Init.OwnAddress2 = 0;
hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c2) != HAL_OK)
{
Error_Handler();
}
}
void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(i2cHandle->Instance==I2C2)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
__HAL_RCC_I2C2_CLK_ENABLE();
}
}
文件:stm32/15_i2c_hardware_hal/Core/Src/m24c02.c
#include "m24c02.h"
void M24C02_Init(void)
{
MX_I2C2_Init();
}
void M24C02_WriteByte(uint8_t innerAddr, uint8_t byte)
{
HAL_I2C_Mem_Write(&hi2c2, W_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT, &byte, 1, 1000);
HAL_Delay(5);
}
uint8_t M24C02_ReadByte(uint8_t innerAddr)
{
uint8_t byte;
HAL_I2C_Mem_Read(&hi2c2, R_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT, &byte, 1, 1000);
return byte;
}
void M24C02_WriteBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t size)
{
HAL_I2C_Mem_Write(&hi2c2, W_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT, bytes, size, 1000);
HAL_Delay(5);
}
void M24C02_ReadBytes(uint8_t innerAddr, uint8_t *buffer, uint8_t size)
{
HAL_I2C_Mem_Read(&hi2c2, R_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT, buffer, size, 1000);
}
文件:stm32/15_i2c_hardware_hal/Core/Src/main.c
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
#include "m24c02.h"
#include <string.h>
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C2_Init();
MX_USART1_UART_Init();
printf("尚硅谷 I2C HAL 库实验...\n");
M24C02_WriteByte(0x00, 'a');
M24C02_WriteByte(0x01, 'b');
M24C02_WriteByte(0x02, 'c');
uint8_t byte1 = M24C02_ReadByte(0x00);
uint8_t byte2 = M24C02_ReadByte(0x01);
uint8_t byte3 = M24C02_ReadByte(0x02);
printf("byte1 = %c\t byte2 = %c\t byte3 = %c\n", byte1, byte2, byte3);
M24C02_WriteBytes(0x00, "123456", 6);
uint8_t buffer[100] = {0};
M24C02_ReadBytes(0x00, buffer, 6);
printf("buffer = %s\n", buffer);
memset(buffer, 0, sizeof(buffer));
M24C02_WriteBytes(0x00, "1234567890abcdefghijk", 21);
M24C02_ReadBytes(0x00, buffer, 21);
printf("buffer = %s\n", buffer);
while (1)
{
}
}
| I2C 操作 | 软件模拟 | 硬件 I2C(寄存器) | 硬件 I2C(HAL) |
|---|---|---|---|
| 初始化 | I2C_Init() |
I2C_Init() |
HAL_I2C_Init() / MX_I2C2_Init() |
| 写 EEPROM | I2C_Start/SendByte/Stop 组合 |
I2C_SendAddr/SendByte/Stop 组合 |
HAL_I2C_Mem_Write(addr, mem_addr, data, len, timeout) |
| 读 EEPROM | Start+写地址+寄存器地址+RESTART+读地址+数据+NACK+Stop |
I2C_SendAddr/SendByte/Restart/ReadByte 组合 |
HAL_I2C_Mem_Read(addr, mem_addr, data, len, timeout) |
| 发送字节 | I2C_SendByte(byte) |
I2C_SendByte(byte) |
—(底层封装在 HAL 中) |
| 读取字节 | I2C_ReadByte() |
I2C_ReadByte() |
—(底层封装在 HAL 中) |
| 等待应答 | I2C_Wait4Ack() |
—(硬件自动处理) | —(硬件自动处理) |
| 发应答/非应答 | I2C_Ack() / I2C_NAck() |
I2C_Ack() / I2C_Nack() |
—(硬件自动处理) |
| 启动传输 | I2C_Start() |
I2C_Start() |
—(封装在 Mem_Write/Read 中) |
| 停止传输 | I2C_Stop() |
I2C_Stop() |
—(封装在 Mem_Write/Read 中) |
I2C_Start() 但不先发 I2C_Stop()