当前位置: 首页 > news >正文

IIC—读写EEPROM(1)

IIC—读写EEPROM(1)

1.1 I2C协议简介

I2C 通讯协议(Inter-Integrated Circuit)是由Phiilps公司开发的,由于它引脚少,硬件实现简单,可扩展性强,不需要USART、CAN等通讯协议的外部收发设备, 现在被广泛地使用在系统内多个集成电路(IC)间的通讯。

1.2 I2C物理层&协议层

(1)物理层

image-20260429152306377

它的物理层有如下特点:

(1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个I2C通讯总线中,可连接多个I2C通讯设备,支持多个通讯主机及多个通讯从机。

(2) 一个I2C总线只使用两条总线线路,一条双向串行数据线(SDA),一条串行时钟线 (SCL)。数据线即用来表示数据,时钟线用于数据收发同步。

(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。

(4) 总线通过上拉电阻接到电源。当I2C设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。

(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。

(6) 具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s ,高速模式下可达1Mbit/s,但目前大多I2C设备尚不支持高速模式。

(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。

(2)协议层

I2C的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。

1.3 I2C基本读写顺序

I2C通讯过程的基本结构,它的通讯过程如下图所示:

  1. 主机写数据到从机

image-20260429152752536

  1. 主机由从机中读数据

    image-20260429152819870

  2. I2C通讯复合格式

    image-20260429152838371

image-20260429152859982

这些图表示的是主机和从机通讯时,SDA线的数据包序列。

其中S表示由主机的I2C接口产生的传输起始信号(S),这时连接到I2C总线上的所有从机都会接收到这个信号。

起始信号产生后,所有从机就开始等待主机紧接下来广播的从机地址信号(SLAVE_ADDRESS)。在I2C总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没有选中的设备将会忽略之后的数据信号。根据I2C协议,这个从机地址可以是7位或10位。

在地址位之后,是传输方向的选择位,该位为0时,表示后面的数据传输方向是由主机传输至从机,即主机向从机写数据。该位为1时,则相反,即主机由从机读数据。

从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,只有接收到应答信号后,主机才能继续发送或接收数据。

若配置的方向传输位为“写数据”方向,即第一幅图的情况,广播完地址,接收到应答信号后,主机开始正式向从机传输数据(DATA),数据包的大小为8位,主机每发送完一个字节数据[1 字节 (Byte) = 8 位 (bit)],都要等待从机的应答信号(ACK),重复这个过程,可以向从机传输N个数据,这个N没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输数据。

若配置的方向传输位为“读数据”方向,即第二幅图的情况,广播完地址,接收到应答信号后,从机开始向主机返回数据(DATA), 数据包大小也为8位,从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,可以返回N个数据,这个N也没有大小限制。 当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。

除了基本的读写,I2C通讯更常用的是复合格式,即第三幅图的情况,该传输过程有两次起始信号(S)。一般在第一次传输中,主机通过SLAVE_ADDRESS寻找到从设备后, 发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与SLAVE_ADDRESS的区别);在第二次的传输中,对该地址的内容进行读或写。 也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。

以上通讯流程中包含的各个信号分解如下:

通讯的起始和停止信号

前文提到的起始(S)和停止(P)信号是两种特殊的状态,如下图所示。当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。 当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

image-20260429154913894

数据有效性

I2C使用SDA信号线来传输数据,使用SCL信号线进行数据同步,如下图所示。SDA数据线在SCL的每个时钟周期传输一位数据。传输时, SCL为高电平的时候SDA表示的数据有效,即此时的SDA为高电平时表示数据“1”,为低电平时表示数据“0”。当SCL为低电平时,SDA的数据无效, 一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。

image-20260429155056057

每次数据传输都以字节为单位,每次传输的字节数不受限制。

地址及数据方向

I2C总线上的每个设备都有自己的独立地址,主机发起通讯时,通过SDA信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C协议规定设备地址可以是7位或10位, 实际中7位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第8位或第11位。 数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。

image-20260429155200148

读数据方向时,主机会释放对SDA信号线的控制,由从机控制SDA信号线,主机接收信号,写数据方向时,SDA由主机控制,从机接收信号。

响应

I2C的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到I2C传输的一个字节数据或地址后, 若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号, 发送方接收到该信号后会产生一个停止信号,结束信号传输。

image-20260429155324345

传输时主机产生时钟,在第9个时钟时,数据发送端会释放SDA的控制权,由数据接收端控制SDA,若SDA为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。

2. STM32的I2C特性及架构

如果直接控制STM32的两个GPIO引脚,分别用作SCL及SDA,按照上述信号的时序要求,直接像控制LED灯那样控制引脚的输出(若是接收数据时则读取SDA电平), 就可以实现I2C通讯。同样,假如我们按照USART的要求去控制引脚,也能实现USART通讯。所以只要遵守协议,就是标准的通讯,不管如何实现它, 不管是ST生产的控制器还是ATMEL生产的存储器, 都能按通讯标准交互。

由于直接控制GPIO引脚电平产生通讯时序时,需要由CPU控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。

相对地,还有“硬件协议”方式,STM32的I2C片上外设专门负责实现I2C通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号, 收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理I2C协议的方式减轻了CPU的工作,且使软件设计更加简单。

2.1 STM32的I2C外设简介

STM32的I2C外设可用作通讯的主机及从机,支持标准速度模式(高达100Kbit/s)、快速模式(高达400Kbit/s)、超快速模式(高达1Mbit/s), 支持7位、10位设备地址,支持DMA数据传输,并具有数据校验功能。它的I2C外设还支持SMBus2.0协议和PMBus1.1协议。SMBus协议与I2C类似, 主要应用于笔记本电脑的电池管理中,本教程不展开,感兴趣的读者可参考《SMBus2.0》文档了解。

2.2 STM32的I2C架构剖析

image-20260429160200603

通讯引脚

I2C的所有硬件架构都是根据图中左侧SCL线和SDA线展开的(其中的SMBA线用于SMBUS的警告信号,I2C通讯没有使用)。STM32芯片有多个I2C外设, 它们的I2C通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚。

I2C1(STM32F103C8T6最常用)

  • 默认引脚

    • SCL:PB6
    • SDA:PB7
  • 重映射(AFIO)

    • SCL:PB8
    • SDA:PB9
    • HAL 库开启:
      __HAL_AFIO_REMAP_I2C1_ENABLE();
      

I2C2

  • 固定引脚(无重映射)
    • SCL:PB10
    • SDA:PB11

硬件要点

  • 所有 I2C 引脚都在 GPIOB,为 5V 耐受(FT)
  • 配置模式:复用开漏 AF_OD,必须外接 4.7kΩ 上拉电阻到 3.3V
  • 最高速率:400kHz(Fast-mode),常用 100kHz

引脚冲突提示

  • PB10/PB11 与 USART3(TX/RX) 复用。
  • PB6/PB7 与 USART1(TX/RX) 复用(重映射后可避开)。

3. 标准库版本的I2C模块设计

STM32F103C8T6 标准库 I2C1 通用驱动模块

I2C1 默认:PB6=SCL、PB7=SDA

i2c.h

#ifndef __I2C_H
#define __I2C_H#include "stm32f10x.h"// I2C 引脚定义
#define I2C_SCL_PIN    GPIO_Pin_6
#define I2C_SDA_PIN    GPIO_Pin_7
#define I2C_GPIO_PORT  GPIOB
#define I2C_RCC_CLK    RCC_APB2Periph_GPIOB// I2C 初始化
void I2C_Init_Config(void);// I2C 读写函数
uint8_t I2C_Write_Byte(uint8_t addr, uint8_t reg, uint8_t data);
uint8_t I2C_Read_Byte(uint8_t addr, uint8_t reg);
uint8_t I2C_Read_Buf(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t len);#endif

i2c.c

#include "i2c.h"/*** @brief  I2C1 硬件初始化*/
void I2C_Init_Config(void)
{GPIO_InitTypeDef GPIO_InitStruct;I2C_InitTypeDef  I2C_InitStruct;// 开启时钟RCC_APB2PeriphClockCmd(I2C_RCC_CLK, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);// PB6 PB7 复用开漏GPIO_InitStruct.GPIO_Pin   = I2C_SCL_PIN | I2C_SDA_PIN;GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_OD;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct);// I2C 参数配置I2C_InitStruct.I2C_ClockSpeed    = 400000;  // 400K 快速模式I2C_InitStruct.I2C_Mode          = I2C_Mode_I2C;I2C_InitStruct.I2C_DutyCycle     = I2C_DutyCycle_2;I2C_InitStruct.I2C_OwnAddress1   = 0x30;I2C_InitStruct.I2C_Ack          = I2C_Ack_Enable;I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;I2C_Init(I2C1, &I2C_InitStruct);I2C_Cmd(I2C1, ENABLE);
}/*** @brief  I2C 写入1字节* @param  addr: 设备7位地址* @param  reg:  寄存器地址* @param  data: 要写入的数据* @retval 0成功 1失败*/
uint8_t I2C_Write_Byte(uint8_t addr, uint8_t reg, uint8_t data)
{while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));// 起始信号I2C_GenerateSTART(I2C1, ENABLE);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));// 发送设备地址+写I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));// 发送寄存器地址I2C_SendData(I2C1, reg);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));// 发送数据I2C_SendData(I2C1, data);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));// 停止信号I2C_GenerateSTOP(I2C1, ENABLE);return 0;
}/*** @brief  I2C 读取1字节* @param  addr: 设备7位地址* @param  reg:  寄存器地址* @retval 读到的数据*/
uint8_t I2C_Read_Byte(uint8_t addr, uint8_t reg)
{uint8_t val;while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));I2C_GenerateSTART(I2C1, ENABLE);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));I2C_SendData(I2C1, reg);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));// 重复起始I2C_GenerateSTART(I2C1, ENABLE);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));// 设备地址+读I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));// 关闭应答I2C_AcknowledgeConfig(I2C1, DISABLE);// 等待接收完成while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));val = I2C_ReceiveData(I2C1);I2C_GenerateSTOP(I2C1, ENABLE);I2C_AcknowledgeConfig(I2C1, ENABLE);return val;
}/*** @brief  I2C 连续读取多个字节* @param  addr: 设备地址* @param  reg:  起始寄存器* @param  buf:  存放数据缓存* @param  len:  读取长度*/
uint8_t I2C_Read_Buf(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t len)
{uint8_t i;while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));I2C_GenerateSTART(I2C1, ENABLE);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));I2C_SendData(I2C1, reg);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));I2C_GenerateSTART(I2C1, ENABLE);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));for(i=0; i<len; i++){if(i == len-1){I2C_AcknowledgeConfig(I2C1, DISABLE);}else{I2C_AcknowledgeConfig(I2C1, ENABLE);}while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));buf[i] = I2C_ReceiveData(I2C1);}I2C_GenerateSTOP(I2C1, ENABLE);I2C_AcknowledgeConfig(I2C1, ENABLE);return 0;
}

使用方法

  1. 工程加入 i2c.ci2c.h
  2. 在 main 初始化:
I2C_Init_Config();
  1. 读写示例:
// 写数据
I2C_Write_Byte(0x68, 0x00, 0x55);
// 读数据
uint8_t dat = I2C_Read_Byte(0x68, 0x00);

硬件注意事项

  1. PB6、PB7 必须外接 4.7K 上拉电阻到3.3V
  2. 标准库固件库要完整,包含 stm32f10x_i2c.cstm32f10x_gpio.c
http://www.jsqmd.com/news/719997/

相关文章:

  • WASM容器化部署实战(从树莓派到Jetson AGX):7步完成低延迟边缘AI服务上线
  • STM32 + MODBUS RTU + RS485 实现方案
  • 2026热门室内地图建模工具推荐:SLAM与矢量绘制全收录 - 品牌2025
  • 大语言模型(LLM)入门学习路线图
  • 2026最新避暑攻略/景点/景区/打卡地推荐!贵州优质避暑目的地榜单发布,高口碑值得去贵阳安顺等地避暑打卡地推荐 - 十大品牌榜
  • 2025届学术党必备的六大AI论文方案横评
  • RK3399开发环境搭建实录:在Ubuntu 22.04上配置Arm GNU Toolchain 12.2交叉编译器的完整流程
  • 退休金的本质的庖丁解牛
  • 2026年温州黄金回收六家机构实测对比 避坑指南与优选推荐 - 福正美黄金回收
  • 漫画翻译工具完全指南:5分钟快速上手,轻松翻译日漫
  • 2026年全国沥青筑路设备采购指南:德州霖垚与山东五大厂商深度横评 - 企业名录优选推荐
  • 建站公司哪家安全性最高?良心推荐以下4家平台! - FaiscoJeff
  • Android手把手编写儿童手机远程监控App之UUID
  • 2026年温州视频制作:从技术赋能到全品类定制的行业进阶路径 - GrowthUME
  • 智能制造行业海外营销代运营公司有哪些?涵盖海外营销代运营服务商+外贸AI营销平台推荐,高效拓客不踩坑(附带联系方式) - 品牌2026
  • 3步掌握飞书文档转Markdown:告别手动复制的完整指南
  • 千问3.5-9B运维知识库构建:智能故障诊断与解决方案推荐
  • 别再死记硬背了!用COCA和BNC语料库,像母语者一样地道学英语
  • UGUI源码剖析 (24):常用插件扩展介绍
  • 洛谷官方题单[Java版题解]--【入门3】循环结构
  • 如何通过 NoETL 指标平台构建企业唯一指标计算中心
  • 3个关键步骤彻底解决电脑风扇噪音!Fan Control完全指南
  • 5G应用下的网络延迟测试专业方案
  • 2025届学术党必备的十大AI辅助论文神器实际效果
  • 15分钟构建专业级流程图:Flowchart-Vue组件实战指南
  • 从房价预测到信贷评分:岭回归在真实业务场景中的落地实践与避坑指南
  • 【花雕动手做】当设备学会“思考”:ESP-Claw如何用AI重塑物联网的未来
  • 2026年3月评价好的工业厂房搭建公司口碑推荐,工业厂房搭建工程,专业电气布局,厂房用电安全便捷 - 品牌推荐师
  • **发散创新:基于Python的渗透测试自动化框架设计与实战**在网络安全日益
  • 如何快速构建微信智能助手:实用高效的自动化工具指南