一、IIC简介

左边位串口,右边位IIC


SDL数据传输主机可以向从机传送数据,从机也可以向主机传送,SCL作为时钟线,仅仅可以由主机发出,核心作用是 "同步通信节奏



时钟信号(比如 I2C 的 SCL)和数据信号(比如 I2C 的 SDA)是通信总线里的 "分工搭档",核心区别在作用、特性、内容这三点:
-
作用不同
-
时钟信号:是 "节拍器",负责同步通信节奏,让收发双方按统一的时间规则操作(比如 "什么时候读数据、什么时候发数据")。
-
数据信号:是 "内容载体",负责传递实际要通信的信息(比如地址、指令、数值)。
-
-
特性不同
-
时钟信号:通常是固定频率的脉冲(比如 I2C 的 SCL 是周期性高低电平),节奏由主设备控制。
-
数据信号:电平随传输的内容变化(比如要发 "1" 就输出高电平,发 "0" 就输出低电平),内容由通信需求决定。
-
-
内容属性不同
-
时钟信号:本身不包含业务信息,只是 "控制时序的工具"。
-
数据信号:承载的是实际要交互的业务信息(比如传感器的测量值、芯片的配置指令)。
-
简单说:时钟是 "通信节奏的指挥",数据是 "要传递的消息本身"。


- 实现双向通信 + 线与逻辑
SDA 需要同时支持 "主设备发数据" 和 "从设备回数据 / 应答",开漏输出的高阻态特性(输出 1 时 = 断开引脚),让 SDA 既能输出低电平,又能直接检测总线上的电平(相当于输入模式),不用频繁切换 IO 模式。
而线与逻辑(多个设备共享总线时,只要有一个拉低 SDA,总线就是低电平;所有设备释放时,上拉电阻拉成高电平)是 I2C "多主设备仲裁""从设备应答" 的基础。
- 避免短路 / 硬件损坏
如果 SDA 用推挽输出(同时能主动输出高 / 低电平),当主设备输出高电平、从设备拉低 SDA 应答时,会形成 "电源→引脚→地" 的直流通路,导致电流过大、烧毁芯片。
- 兼容不同电压设备
开漏输出的高电平由外部上拉电阻提供,能同时连接 3.3V 和 5V 设备(比如主设备是 3.3V MCU,从设备是 5V 传感器),不用额外加电平转换电路。
而 SCL 可以用推挽输出(因为主设备完全控制 SCL,从设备不主动拉低),但实际工程中也常和 SDA 一样用开漏 + 上拉(兼容多主设备场景)。
二、IIC协议




低电平采集,高电平读取






发送0xA1表示需要主机读取从机的数据
三、EEPROM










1、程序移植
将LED文件复制,重命名为IIC

添加myiic.c文件

先将头文件修改

2、修改头文件(maiic.h)
使用宏定义方便移植
#ifndef _MYIIC_H
#define _MYIIC_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define IIC_SCL_GPIO_PORT GPIOA
#define IIC_SCL_GPIO_PIN GPIO_PIN_2
#define IIC_SCL_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define IIC_SDA_GPIO_PORT GPIOA
#define IIC_SDA_GPIO_PIN GPIO_PIN_3
#define IIC_SDA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
/******************************************************************************************/
/* LED端口定义 */
#define IIC_SCL(x) do{ x ? \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN , GPIO_PIN_SET) : \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN , GPIO_PIN_RESET); \
}while(0) /* LED0翻转 */
#define IIC_SDA(x) do{ x ? \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN , GPIO_PIN_SET) : \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN , GPIO_PIN_RESET); \
}while(0) /* LED1翻转 */
#define IIC_READ__SDA(x) HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN) //读取SDA电平
#endif
3、IIC引脚初始化及程序编写(maiic.c)
#include "myiic.h"
#include "./SYSTEM/delay/delay.h"
/**
* @brief I2C总线初始化函数
* @note 初始化SCL和SDA引脚为GPIO输出模式,SCL推挽输出,SDA开漏输出,默认上拉
* 初始化完成后,SCL和SDA均置为高电平,进入I2C空闲状态
* @param 无
* @retval 无
*/
void iic_init(void)
{
GPIO_InitTypeDef gpio_init_struct; // GPIO初始化结构体
IIC_SCL_GPIO_CLK_ENABLE(); /* 使能SCL引脚对应的GPIO端口时钟 */
IIC_SDA_GPIO_CLK_ENABLE(); /* 使能SDA引脚对应的GPIO端口时钟 */
/* 配置SCL引脚:推挽输出 + 上拉 */
gpio_init_struct.Pin = IIC_SCL_GPIO_PIN; /* 指定SCL引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出模式(I2C时钟线为主机控制,可用推挽) */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉电阻使能,保证空闲时为高电平 */
HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct); /* 初始化SCL引脚 */
/* 配置SDA引脚:开漏输出 + 上拉 */
gpio_init_struct.Pin = IIC_SDA_GPIO_PIN; /* 指定SDA引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出模式(I2C数据线需要双向通信,必须开漏) */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉电阻使能,保证空闲时为高电平 */
HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct); /* 初始化SDA引脚 */
IIC_SCL(1); /* SCL置高,空闲状态 */
IIC_SDA(1); /* SDA置高,空闲状态 */
}
/**
* @brief I2C总线延时函数
* @note 用于保证I2C通信时序的稳定性,延时2us(根据实际系统主频调整)
* @param 无
* @retval 无
*/
void iic_delay(void)
{
delay_us(2); /* 调用微秒级延时函数,延时2us */
}
/**
* @brief 发送I2C起始信号
* @note 起始信号时序:SCL高电平时,SDA从高电平拉低
* @param 无
* @retval 无
*/
void iic_start(void)
{
IIC_SCL(1); /* 先将SCL置高 */
IIC_SDA(1); /* 先将SDA置高,保证总线处于空闲状态 */
iic_delay(); /* 延时稳定电平 */
IIC_SDA(0); /* SCL高电平时,SDA拉低,产生起始信号 */
iic_delay(); /* 延时保证信号稳定 */
IIC_SCL(0); /* 拉低SCL,准备发送/接收数据 */
iic_delay(); /* 延时稳定 */
}
/**
* @brief 发送I2C停止信号
* @note 停止信号时序:SCL高电平时,SDA从低电平拉高
* @param 无
* @retval 无
*/
void iic_stop(void)
{
IIC_SDA(0); /* 先将SDA置低 */
iic_delay(); /* 延时稳定电平 */
IIC_SCL(1); /* 拉高SCL */
iic_delay(); /* 延时保证信号稳定 */
IIC_SDA(1); /* SCL高电平时,SDA拉高,产生停止信号 */
iic_delay(); /* 延时稳定 */
}
/**
* @brief 发送I2C应答信号(ACK)
* @note 应答信号时序:SCL高电平时,SDA保持低电平
* @param 无
* @retval 无
*/
void iic_ack(void)
{
IIC_SDA(0); /* SDA置低,表示应答 */
iic_delay(); /* 延时稳定电平 */
IIC_SCL(1); /* 拉高SCL,让从机检测应答信号 */
iic_delay(); /* 延时保证从机检测到 */
IIC_SCL(0); /* 拉低SCL,结束应答 */
iic_delay(); /* 延时稳定 */
IIC_SDA(1); /* 释放SDA总线 */
iic_delay(); /* 延时稳定 */
}
/**
* @brief 发送I2C非应答信号(NACK)
* @note 非应答信号时序:SCL高电平时,SDA保持高电平
* @param 无
* @retval 无
*/
void iic_nack(void)
{
IIC_SDA(1); /* SDA置高,表示非应答 */
iic_delay(); /* 延时稳定电平 */
IIC_SCL(1); /* 拉高SCL,让从机检测非应答信号 */
iic_delay(); /* 延时保证从机检测到 */
IIC_SCL(0); /* 拉低SCL,结束非应答 */
iic_delay(); /* 延时稳定 */
}
/**
* @brief 等待从机的应答信号
* @note 主机释放SDA,等待从机拉低SDA产生应答,超时则返回错误
* @param 无
* @retval 0: 接收到应答(ACK) 1: 未接收到应答/超时(NACK)
*/
uint8_t iic_wait_ack()
{
uint8_t rack = 0; /* 应答状态标志:0=成功,1=失败 */
uint8_t waittime = 0; /* 超时计数变量 */
IIC_SDA(1); /* 主机释放SDA总线,由从机控制 */
iic_delay(); /* 延时稳定电平 */
IIC_SCL(1); /* 拉高SCL,等待从机应答 */
iic_delay(); /* 延时稳定 */
/* 循环检测SDA电平,判断是否收到应答 */
while(IIC_READ_SDA) /* 若SDA为高,表示未收到应答,进入循环等待 */
{
waittime++; /* 超时计数+1 */
if(waittime > 250)/* 超时判断(可根据实际系统调整) */
{
iic_stop(); /* 超时后发送停止信号,终止通信 */
rack = 1; /* 标记应答失败 */
}
break; /* 注:此处break会导致仅检测1次,建议移到if内部 */
}
IIC_SCL(0); /* 拉低SCL,结束应答检测 */
iic_delay(); /* 延时稳定 */
return rack; /* 返回应答状态 */
}
/**
* @brief 向I2C总线发送1个字节数据
* @note 数据高位先行,逐位发送,SCL时钟同步
* @param data: 要发送的8位数据
* @retval 无
*/
void iic_send_byte(uint8_t data)
{
/* 循环发送8位数据(I2C为8位通信) */
for(uint8_t i = 0; i < 8; i++)
{
/* 发送当前最高位:(data & 0X80)取出最高位,右移7位转为0/1 */
IIC_SDA((data & 0X80) >> 7);
iic_delay(); /* 延时稳定电平 */
IIC_SCL(1); /* 拉高SCL,让从机读取当前位 */
iic_delay(); /* 延时保证从机读取到 */
IIC_SCL(0); /* 拉低SCL,准备发送下一位 */
iic_delay(); /* 延时稳定 */
data <<= 1; /* 数据左移1位,将次高位变为最高位 */
}
IIC_SDA(1); /* 发送完成后释放SDA总线 */
iic_delay(); /* 延时稳定 */
}
/**
* @brief 从I2C总线读取1个字节数据
* @note 数据高位先行,逐位读取,读取完成后发送ACK/NACK
* @param ack: 读取完成后是否发送应答 0: 发送NACK 1: 发送ACK
* @retval 读取到的8位数据
*/
uint8_t iic_read_byte(uint8_t ack)
{
uint8_t receive = 0; /* 存储读取到的字节数据,初始化为0 */
/* 循环读取8位数据(I2C为8位通信) */
for(uint8_t i = 0; i < 8; i++)
{
receive <<= 1; /* 数据左移1位,空出最低位,准备接收新位 */
IIC_SDA(1); /* 主机释放SDA总线,由从机控制 */
iic_delay(); /* 延时稳定电平 */
IIC_SCL(1); /* 拉高SCL,从机输出当前位数据到SDA */
iic_delay(); /* 延时保证数据稳定 */
/* 读取SDA当前电平:
* - 若SDA为高电平(1),将receive最低位设为1
* - 若SDA为低电平(0),receive最低位保持0(左移后默认0)
*/
if(IIC_READ_SDA)
{
receive |= 1;
}
IIC_SCL(0); /* 拉低SCL,从机准备下一位数据 */
iic_delay(); /* 延时稳定 */
}
/* 根据参数决定发送ACK或NACK */
if(!ack)
{
iic_nack(); /* 发送非应答,表示后续无数据读取 */
}
else
{
iic_ack(); /* 发送应答,表示继续读取下一个字节 */
}
return receive; /* 返回读取到的完整字节数据 */
}
注释:上述代码由IIC协议编写,注释用AI生成

在头文件中进行声明
四、实现24C02读写函数

头文件"atc_24c02.h"
#ifndef _ATC_24C02_H
#define _ATC_24C02_H
#include "./SYSTEM/sys/sys.h"
#include "myiic.h"
void atk_24c02_init(void);
void atk_24cxx_write_one_byte(uint16_t addr, uint8_t data);
uint8_t atk_24cxx_read_one_byte(uint16_t addr);
#endif
源文件"atc_24c02.c"
/**
****************************************************************************************************
* @file led.c
* @author 正点原子团队(ALIENTEK)
* @version V1.0
* @date 2020-04-17
* @brief LED 驱动代码
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 MiniSTM32 V4开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
* 修改说明
* V1.0 20200417
* 第一次发布
*
****************************************************************************************************
*/
#include "myiic.h"
#include "atc_24c02.h"
#include "./SYSTEM/delay/delay.h"
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void atk_24c02_init(void)
{
iic_init();
}
void atk_24cxx_write_one_byte(uint16_t addr, uint8_t data)
{
iic_start(); /* 发送起始信号 */
iic_send_byte(0XA0);/* 发送器件 0XA0 */
iic_wait_ack();
iic_send_byte(addr);/* 发送地址 */
iic_wait_ack();
iic_send_byte(data);/* 发送1字节 */
iic_wait_ack();
iic_stop(); /* 产生一个停止条件 */
delay_ms(10); /* EEPROM 写入比较慢 */
}
uint8_t atk_24cxx_read_one_byte(uint16_t addr)
{
uint8_t temp = 0;
iic_start(); /* 发送起始信号 */
iic_send_byte(0XA0); /* 发送器件 0XA0 */
iic_wait_ack(); /* 每次发送完一个字节,都要等待ACK */
iic_send_byte(addr); /* 发送低位地址 */
iic_wait_ack(); /* 等待ACK,此时地址发送完成了 */
iic_start(); /* 重新发送起始信号 */
iic_send_byte(0XA1); /* 进入接收模式,IIC规定最低位是0,表示读取 */
iic_wait_ack(); /* 每次发送完一个字节,都要等待ACK */
temp = iic_read_byte(0); /* 接收一个字节数据 */
iic_stop(); /* 产生一个停止条件 */
return temp;
}
