I2C通信核心原理
-
物理层特性:
-
双线制:仅需SCL(时钟线)和SDA(数据线)两根线
-
开漏输出:所有设备都需配置为开漏模式,依赖外部上拉电阻(通常4.7kΩ)
-
多主从架构:支持多个主设备和从设备
-
地址寻址:7位或10位从机地址机制
-
-
协议层要点:
-
起始条件:SCL高电平时SDA由高→低
-
停止条件:SCL高电平时SDA由低→高
-
数据有效性:SDA在SCL高电平期间需保持稳定
-
应答机制:每个字节传输后接收方必须发送ACK(低电平)
-
时序图(来源江科大)

代码解释(后面有完整的.c和.h文件)
1. 基础GPIO操作
// SCL控制(PB4)
void MyI2C_W_SCL(uint8_t BitValue) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, (GPIO_PinState)BitValue);
Delay_us(5); // 关键时序延时
}
// SDA控制(PB5)
void MyI2C_W_SDA(uint8_t BitValue) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, (GPIO_PinState)BitValue);
Delay_us(5);
}
// SDA读取
uint8_t MyI2C_R_SDA(void) {
Delay_us(5);
return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5);
}
-
精确的微秒级延时保证协议时序
-
所有GPIO操作必须为开漏模式(
GPIO_MODE_OUTPUT_OD) -
读SDA前需先释放总线(输出高电平)
2. 通信控制信号
/* 起始信号 */
void MyI2C_Start(void) {
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0); // 下降沿触发起始
MyI2C_W_SCL(0); // 准备数据传输
}
/* 停止信号 */
void MyI2C_Stop(void) {
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1); // 上升沿触发停止
}
时序要求:
| 信号类型 | SCL状态 | SDA状态 |
| 起始信号 | 高电平 | 高->低 |
| 停止信号 | 高电平 | 低->高 |

3. 数据收发实现
/* 发送单个字节 */
void MyI2C_SendByte(uint8_t Byte) {
for(uint8_t i=0; i<8; i++) {
MyI2C_W_SDA(Byte & (0x80 >> i)); // 高位先行
MyI2C_W_SCL(1); // 上升沿锁存数据
MyI2C_W_SCL(0);
}
MyI2C_W_SDA(1); // 释放总线等待ACK
}
/* 接收单个字节 */
uint8_t MyI2C_ReceiveByte(void) {
uint8_t Byte = 0;
MyI2C_W_SDA(1); // 主机释放总线
for(uint8_t i=0; i<8; i++) {
MyI2C_W_SCL(1);
if(MyI2C_R_SDA()) Byte |= (0x80 >> i);
MyI2C_W_SCL(0);
}
return Byte;
}
发送端: [BIT7] [BIT6] ... [BIT0] [ACK]
接收端:SCL ↑ → 锁存数据 → 检测ACK
4. 应答机制
/* 发送应答 */
void MyI2C_SendAck(uint8_t AckBit) {
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
/* 接收应答 */
uint8_t MyI2C_ReceiveAck(void) {
MyI2C_W_SDA(1); // 主机释放总线
MyI2C_W_SCL(1);
uint8_t ack = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return ack; // 0=ACK, 1=NACK
}
应答规则:
-
ACK:SDA低电平(0)
-
NACK:SDA高电平(1)
完整代码
.h文件
#include "MYIIC.h"
void Delay_us(uint32_t us) {
uint32_t ticks = us * (SystemCoreClock / 1000000) / 5; // 根据主频调整
while(ticks--);
}
// 修改所有GPIO操作函数的引脚定义
void MyI2C_W_SCL(uint8_t BitValue) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, (GPIO_PinState)BitValue); // PB4为SCL
Delay_us(5);
}
void MyI2C_W_SDA(uint8_t BitValue) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, (GPIO_PinState)BitValue); // PB5为SDA
Delay_us(5);
}
uint8_t MyI2C_R_SDA(void) {
Delay_us(5);
uint8_t BitValue = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5); // 读取PB5
return BitValue;
}
/* I2C初始化 */
void MyI2C_Init(void) {
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // 开漏输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 外部上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4 | GPIO_PIN_5, GPIO_PIN_SET);
}
/* 起始信号 */
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
/* 停止信号 */
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
/* 发送一个字节 */
void MyI2C_SendByte(uint8_t Byte) {
for (uint8_t i = 0; i < 8; i++) {
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
// 发送完字节后,释放SDA线
MyI2C_W_SDA(1); // 设置为高电平(开漏模式)
}
/* 接收一个字节 */
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t Byte = 0x00;
MyI2C_W_SDA(1); // 释放SDA总线
for (uint8_t i = 0; i < 8; i++)
{
MyI2C_W_SCL(1);
if (MyI2C_R_SDA()) Byte |= (0x80 >> i);
MyI2C_W_SCL(0);
}
return Byte;
}
/* 发送应答 */
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
/* 接收应答 */
uint8_t MyI2C_ReceiveAck(void) {
MyI2C_W_SDA(1); // 释放SDA线
MyI2C_W_SCL(1);
uint8_t AckBit = MyI2C_R_SDA(); // 读取ACK
MyI2C_W_SCL(0);
return AckBit; // 0=ACK, 1=NACK
}
void IIC_write(uchar addr, uchar dat) {
MyI2C_Start();
MyI2C_SendByte(w_addr); //地址根据从设备修改
MyI2C_ReceiveAck();
MyI2C_SendByte(addr);
MyI2C_ReceiveAck();
MyI2C_SendByte(dat);
MyI2C_ReceiveAck();
MyI2C_Stop();
HAL_Delay(10); // 延长等待时间至10ms
}
uint8_t IIC_read(uchar addr) {
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(w_addr); //地址根据从设备修改
MyI2C_ReceiveAck();
MyI2C_SendByte(addr);
MyI2C_ReceiveAck();
MyI2C_Start(); // 重复起始信号
MyI2C_SendByte(r_addr); //地址根据从设备修改
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1); // 发送NACK(1表示不应答)
MyI2C_Stop();
return Data;
}
.c文件
#ifndef _MYIIC_H_
#define _MYIIC_H_
#include "main.h"
void Delay_us(uint32_t us);
void MyI2C_W_SCL(uint8_t BitValue);
void MyI2C_W_SDA(uint8_t BitValue);
uint8_t MyI2C_R_SDA(void);
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#define w_addr 0xa0 //从机地址线
#define r_addr 0xa1 //从机地址线
void IIC_write(uchar addr,uchar dat);
uint8_t IIC_read(uchar addr);
#endif
这里注意0xa0和0xa1,这个是根据自己传感器的地址线而填的,我的是stm32自带的AT2402
main.c(主函数)
MyI2C_Init();
IIC_write(1,50);
char t[30];
sprintf(t," r:%d ",IIC_read(1));
OLED_ShowString(2,2,t);
现象

8735

被折叠的 条评论
为什么被折叠?



