一、I2C协议简介

1、I2C物理层

1、在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。

2、一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。

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

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

2、I2C协议层

1、I2C读写过程

主机写数据到从机:

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

主机由从机中读数据:

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

复合通讯:

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

2、通讯的起始和停止信号

当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

3、地址及数据方向

I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向数据方向位,第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据。读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时, SDA 由主机控制,从机接收信号。

4、响应

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

二、 I2C架构

引脚:

时钟控制逻辑:

SCL 线的时钟信号,由 I2C 接口根据时钟控制寄存器(CCR)控制。

1、可选择 I2C 通讯的“标准/快速”模式,这两个模式分别 I2C 对应 100/400Kbit/s 的通讯速率。

2、在快速模式下可选择 SCL 时钟的占空比,可选 Tlow/Thigh=2 或 Tlow/Thigh=16/9模式。

3、STM32的I2C外设挂载在APB1总线上,使用APB1的时钟源PCLK1。

SCL信号线输出时钟计算:

通讯过程 :

1、主发送器

2、主接收器

三、I2C初始化结构体

四、读写EEPROM

EEPROM芯片的设备地址一共有 7 位,其中高 4 位固定为:1010 b,低 3 位则由 A0/A1/A2 信号线的电平决定,R/W 是读写方向位,与地址无关。

按照我们此处的连接, A0/A1/A2均为 0,所以 EEPROM的7位设备地址是: 101 0000b ,即 0x50。由于 I2C 通讯时常常是地址跟读写方向连在一起构成一个 8 位数,且当 R/W 位为0 时,表示写方向,所以加上 7 位地址,其值为“0xA0”,常称该值为 I2C 设备的“写地址”;当 R/W 位为 1 时,表示读方向,加上 7 位地址,其值为“0xA1”,常称该值为“读地址”。

EEPROM 芯片中还有一个 WP 引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时,可写入数据,我们直接接地,不使用写保护功能。

1、 初始化I2C的GPIO

static void I2C_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;/* 使能与 I2C 有关的时钟 */RCC_APB1PeriphClockCmd ( RCC_APB1Periph_I2C1, ENABLE );RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOB, ENABLE );/* I2C_SCL、 I2C_SDA*/GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出GPIO_Init(GPIOB, &GPIO_InitStructure);
}

2 、配置I2C模式

static void I2C_Mode_Configu(void)
{I2C_InitTypeDef  I2C_InitStructure;/* I2C 配置 */I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;/* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//I2C主机地址,随机I2C_InitStructure.I2C_OwnAddress1 =0x5F;I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;/* I2C 的寻址模式 */I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;/* 通信速率 */I2C_InitStructure.I2C_ClockSpeed = 400000;/* I2C 初始化 */I2C_Init(I2C1, &I2C_InitStructure);/* 使能 I2C */I2C_Cmd(I2C1, ENABLE);
}

3、向EEPROM写入一个字节的数据

EEPROM 单字节写入时序:

EPROM 的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。

//EEPROM内存256K,内存地址设置为8位uint8_t
void EEPROM_Byte_Write(uint8_t addr,uint8_t data)
{//产生起始信号I2C_GenerateSTART(I2C1, ENABLE);while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//EV5时间被检测到,发送设备地址I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);//EV6事件被检测到,发送要操作的存储单元地址I2C_SendData(I2C1, addr);while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);//EV8事件被检测到,发送要存储的数据I2C_SendData(I2C1, data);while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);//数据传输完成I2C_GenerateSTOP(I2C1, ENABLE);
}

4、EEPROM的页写入

EEPROM 页写入时序:

向连续地址写入多个数据的时候,只要告诉 EEPROM 第一个内存地址 address1,后面的数据按次序写入到 address2、 address3… 这样可以节省通讯的时间,加快速度。根据页写入时序,第一个数据被解释为要写入的内存地址 address1,后续可连续发送 n个数据,这些数据会依次写入到内存中。其中 AT24C02 型号的芯片页写入时序最多可以一次发送 8 个数据(即 n = 8 ),该值也称为页大小。

//向EEPROM写入多个字节(页写入),每次写入不能超过8个
//EEPROM内存256K,内存地址设置为8位uint8_t
void EEPROM_Page_Write(uint8_t addr,uint8_t *data,uint8_t numByteToWrite){//产生起始信号I2C_GenerateSTART(I2C1, ENABLE);//检测EV5事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//发送设备地址I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);//检测EV6事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);//发送要操作的存储单元地址I2C_SendData(I2C1, addr);//检测EV8事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);while(numByteToWrite){//发送要存储的数据I2C_SendData(I2C1, *data);//检测EV8事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);data++;numByteToWrite--;}//数据传输完成I2C_GenerateSTOP(I2C1, ENABLE);
}

5、利用页写入的方式快速写入多字节

首地址对齐到页时的情况:

首地址未对齐到页时的情况

#define I2C_PageSize 8/**
* @brief 将缓冲区中的数据写到 I2C EEPROM 中
* @param
* @arg pBuffer:缓冲区指针
* @arg WriteAddr:写地址
* @arg NumByteToWrite:写的字节数
* @retval 无
*/
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr,u16 NumByteToWrite)
{uint8_t NumOfPage=0,NumOfSingle=0,Addr =0,count=0;/*mod 运算求余,若 writeAddr 是 I2C_PageSize 整数倍,运算结果 Addr 值为 0*/Addr = WriteAddr % I2C_PageSize;/*差 count 个数据值,刚好可以对齐到页地址*/count = I2C_PageSize - Addr;/*计算出要写多少整数页*/NumOfPage = NumByteToWrite / I2C_PageSize;/*mod 运算求余,计算出剩余不满一页的字节数*/NumOfSingle = NumByteToWrite % I2C_PageSize;// Addr=0,则 WriteAddr 刚好按页对齐 aligned// 这样就很简单了,直接写就可以,写完整页后// 把剩下的不满一页的写完即可if (Addr == 0) {/* 如果 NumByteToWrite < I2C_PageSize */if (NumOfPage == 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitForWritingEnd();}/* 如果 NumByteToWrite > I2C_PageSize */else {/*先把整数页都写了*/while (NumOfPage--) {I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);I2C_EE_WaitForWritingEnd();WriteAddr += I2C_PageSize;pBuffer += I2C_PageSize;}/*若有多余的不满一页的数据,把它写完*/if (NumOfSingle!=0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitForWritingEnd();}}}// 如果 WriteAddr 不是按 I2C_PageSize 对齐// 那就算出对齐到页地址还需要多少个数据,然后// 先把这几个数据写完,剩下开始的地址就已经对齐// 到页地址了,代码重复上面的即可else {/* 如果 NumByteToWrite < I2C_PageSize */if (NumOfPage== 0) {if (NumOfSingle > count) {I2C_EE_PageWrite(pBuffer, WriteAddr,NumOfSingle);I2C_EE_WaitForWritingEnd();WriteAddr += count;pBuffer += count;} }/* 如果 NumByteToWrite > I2C_PageSize */else {/*地址不对齐多出的 count 分开处理,不加入这个运算*/NumByteToWrite -= count;NumOfPage = NumByteToWrite / I2C_PageSize;NumOfSingle = NumByteToWrite % I2C_PageSize;/*先把 WriteAddr 所在页的剩余字节写了*/if (count != 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, count);I2C_EE_WaitForWritingEnd();/*WriteAddr 加上 count 后,地址就对齐到页了*/WriteAddr += count;pBuffer += count;}/*把整数页都写了*/while (NumOfPage--) {I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);I2C_EE_WaitForWritingEnd();WriteAddr += I2C_PageSize;pBuffer += I2C_PageSize;}/*若有多余的不满一页的数据,把它写完*/if (NumOfSingle != 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitForWritingEnd();}}}
}

6、从EEPROM读取数据

EEPROM 读取时序:

从 EEPROM 读取数据是一个复合的 I2C 时序,它实际上包含一个写过程和一个读过程。读时序的第一个通讯过程中,使用 I2C 发送设备地址寻址(写方向),接着发送要读取的“内存地址”;第二个通讯过程中,再次使用 I2C 发送设备地址寻址,但这个时候的数据方向是读方向;在这个过程之后, EEPROM 会向主机返回从“内存地址”开始的数据,一个字节一个字节地传输,只要主机的响应为“应答信号”,它就会一直传输下去,主机想结束传输时,就发送“非应答信号”,并以“停止信号”结束通讯,作为从机的 EEPROM也会停止传输。

/*** @brief 从EEPROM 里面读取一块数据* @param data:存放从 EEPROM 读取的数据的缓冲区指针* @param addr:接收数据的 EEPROM 的地址* @param numByteToRead:要从 EEPROM 读取的字节数*/
void EEPROM_Read(uint8_t addr,uint8_t *data,uint8_t numByteToRead)
{//产生起始信号I2C_GenerateSTART(I2C1, ENABLE);//检测EV5事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//发送设备地址I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);//检测EV6事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);//发送要操作的存储单元地址I2C_SendData(I2C1, addr);//检测EV8事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING)==ERROR);//第二次起始信号I2C_GenerateSTART(I2C1, ENABLE);//检测EV5事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)==ERROR);//发送设备地址   最后一位为读   方向为接收I2C_Send7bitAddress(I2C1, 0xA1, I2C_Direction_Receiver);//检测EV6事件while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)==ERROR);while(numByteToRead){if(numByteToRead==1){//如果为最后一个字节I2C_AcknowledgeConfig(I2C1, DISABLE);}//EV7事件被检测到,即数据寄存器有新的有效数据while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)==ERROR);//接收数据*data=I2C_ReceiveData(I2C1);data++;numByteToRead--;}//数据传输完成I2C_GenerateSTOP(I2C1, ENABLE);//使能应答,方便下次I2C传输I2C_AcknowledgeConfig(I2C1, DISABLE);
}

7、状态等待函数

void I2C_EE_WaitForWritingEnd()
{do{I2C_GenerateSTART(I2C1, ENABLE);//检查EV5事件while(I2C_GetFlagStatus(I2C1, I2C_FLAG_SB)==RESET);I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);}//检查EV6事件while(I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR)==RESET);I2C_AcknowledgeConfig(I2C1, DISABLE);
}

STM32学习笔记9(I2C)相关推荐

  1. STM32学习笔记(9)——(I2C续)读写EEPROM

    STM32学习笔记(9)--(I2C续)读写EEPROM 一.概述 1. 背景介绍 2. EEPROM简介 二.AT24C02--常用的EEPROM 1. 电路原理图 2. 写操作 (1)按字节写操作 ...

  2. 《STM32学习笔记》4——核心功能电路与编程(下)

    接上文,文中的图片,大多数来自视频的截图(来自洋桃电子). 欢迎大家批评指正! STM32学习笔记-专栏 文章目录 一.蜂鸣器驱动 1.蜂鸣器介绍 2.蜂鸣器电路 3.蜂鸣器程序 二. MIDI 音乐 ...

  3. STM32学习笔记(15)——SPI协议

    STM32学习笔记(15)--SPI协议 一.SPI协议简介 1. 物理层 2. 协议层 (1) 通讯的开始与停止 (2)时钟极性CPOL.时钟相位CPHA 二.STM32的SPI外设 1. 通讯引脚 ...

  4. STM32学习笔记(三)丨中断系统丨EXTI外部中断(对射式红外传感器计次、旋转编码器计次)

    本篇文章包含的内容 一.中断系统 1.1 中断的定义 1.2 中断优先级 1.3 中断的嵌套 1.4 STM32中的中断系统 1.4.1 STM32的中断资源 1.4.2 嵌套中断向量控制器 NVIC ...

  5. STM32学习笔记 | CAN总线收发数据常见问题分析

    关注+星标公众号,不错过精彩内容 CAN,Controller Area Network(控制器局域网络),在汽车电子.工业控制领域的应用比较多,通常用于局域组网. CAN总线和UART.I2C.SP ...

  6. stm32学习笔记----双串口同时打开时的printf()问题

    stm32学习笔记----双串口同时打开时的printf()问题 最近因为要使用串口2外接PN532芯片实现通信,另一方面,要使用串口1来将一些提示信息输出到上位机,于是重定义了printf(),使其 ...

  7. STM32学习笔记 | 引起电源和系统异常复位的原因

    关注+星标公众号,不错过精彩内容 每一块处理器都有复位的功能,不同处理器复位的类型可能有差异,引起复位的原因也可能有多种. STM32的复位功能非常强大,可通过软件.硬件和一些事件触发系统复位,而且通 ...

  8. 【STM32学习笔记-点亮LED灯】

    STM32学习笔记-点亮LED灯 文章目录 STM32学习笔记-点亮LED灯 一.原理图分析 二.代码分析 1.mian函数 2.led.c函数 3.led.h函数 4.函数文件整理 5.LED_In ...

  9. STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)

    本篇文章包含的内容 一.TIM 定时器 1.1 TIM 定时器简介 1.2 TIM 定时器类型及其工作原理简介 1.2.1 基本定时器工作原理及其结构 1.2.2 通用定时器工作原理及其结构 1.2. ...

  10. STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)

    本篇文章包含的内容 一.输入捕获 1.1 输入捕获简介 1.2 输入捕获通道的工作原理 1.3 输入捕获的主从触发模式 1.4 输入捕获和PWMI结构 二.频率的测量方法 2.1 测频法 2.2 测周 ...

最新文章

  1. ubuntu下pytorch
  2. 数字治理转型与公共卫生治理能力现代化调查项目
  3. csu 1976: 搬运工小明
  4. 用java开发一个Hello Word系统内核
  5. 一个不错的报表工具 open flash chart 2
  6. 联想拯救者Y7000P 2021H deepin v20.2.4设置双屏显示:切记要用集显,NAVIDA独显不生效
  7. 本地提交spark_Spark 数据本地化级别
  8. 最新升学e网通JS逆向分析
  9. antd vue关闭模态对话框_我不能没有的5个Vue.js库
  10. 中柏平板触摸驱动_要成绩也要玩乐,聊聊学生买平板那些事儿
  11. template.js 模板引擎
  12. TP5.0 Redis(单例模式)(原)
  13. mysql如何查询前几天_sql语句查询mysql怎么取前几天的数据
  14. Python Crash Course读书笔记 - 第18章:GETTING STARTED WITH DJANGO
  15. 高德地图marker标点数据量太大造成卡顿的解决方案
  16. 运营商大数据的发展现状和趋势
  17. 新年上班第一天生产环境分布式文件系统崩了!!
  18. 常见数据库对象和数据库存储
  19. 大屏数据可视化(dataV看看官网自己练练,在与echarts一结合完美!)
  20. linux awk nginx日志分析,awk分析nginx日志中的网页响应时间

热门文章

  1. 从键盘输入一个二进制非负整数,屏幕上打印输出对应的十进制、八进制和十六进制数,要求输出的十六进制数中的英文字母为大写字母。
  2. 计算机应用word单元测试,[高职统考}计算机应用基础word2003单元测试题(3)
  3. safeNet sentinel 加密狗远程更改简单配置(时间和使用次数等)
  4. iOS机型 iPhone X/XS/XR 判断的5种方式总结
  5. 生产制造企业如何建立适合自身的数字化工厂,实现数字化转型?
  6. 实训九:三层交换机VLAN划分及VLAN间通信
  7. 手把手教学:如何用低代码平台开发一个软件?
  8. 深入浅出,camera v4l2理解(2)v4l2注册
  9. 世界顶级计算机专家排名,世界前1000名顶级计算机科学家名单出炉,美国616人,中国呢?...
  10. [全程建模]全程建模方法被乱介绍的高校培训