STM32学习笔记9(I2C)
一、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)相关推荐
- STM32学习笔记(9)——(I2C续)读写EEPROM
STM32学习笔记(9)--(I2C续)读写EEPROM 一.概述 1. 背景介绍 2. EEPROM简介 二.AT24C02--常用的EEPROM 1. 电路原理图 2. 写操作 (1)按字节写操作 ...
- 《STM32学习笔记》4——核心功能电路与编程(下)
接上文,文中的图片,大多数来自视频的截图(来自洋桃电子). 欢迎大家批评指正! STM32学习笔记-专栏 文章目录 一.蜂鸣器驱动 1.蜂鸣器介绍 2.蜂鸣器电路 3.蜂鸣器程序 二. MIDI 音乐 ...
- STM32学习笔记(15)——SPI协议
STM32学习笔记(15)--SPI协议 一.SPI协议简介 1. 物理层 2. 协议层 (1) 通讯的开始与停止 (2)时钟极性CPOL.时钟相位CPHA 二.STM32的SPI外设 1. 通讯引脚 ...
- STM32学习笔记(三)丨中断系统丨EXTI外部中断(对射式红外传感器计次、旋转编码器计次)
本篇文章包含的内容 一.中断系统 1.1 中断的定义 1.2 中断优先级 1.3 中断的嵌套 1.4 STM32中的中断系统 1.4.1 STM32的中断资源 1.4.2 嵌套中断向量控制器 NVIC ...
- STM32学习笔记 | CAN总线收发数据常见问题分析
关注+星标公众号,不错过精彩内容 CAN,Controller Area Network(控制器局域网络),在汽车电子.工业控制领域的应用比较多,通常用于局域组网. CAN总线和UART.I2C.SP ...
- stm32学习笔记----双串口同时打开时的printf()问题
stm32学习笔记----双串口同时打开时的printf()问题 最近因为要使用串口2外接PN532芯片实现通信,另一方面,要使用串口1来将一些提示信息输出到上位机,于是重定义了printf(),使其 ...
- STM32学习笔记 | 引起电源和系统异常复位的原因
关注+星标公众号,不错过精彩内容 每一块处理器都有复位的功能,不同处理器复位的类型可能有差异,引起复位的原因也可能有多种. STM32的复位功能非常强大,可通过软件.硬件和一些事件触发系统复位,而且通 ...
- 【STM32学习笔记-点亮LED灯】
STM32学习笔记-点亮LED灯 文章目录 STM32学习笔记-点亮LED灯 一.原理图分析 二.代码分析 1.mian函数 2.led.c函数 3.led.h函数 4.函数文件整理 5.LED_In ...
- STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)
本篇文章包含的内容 一.TIM 定时器 1.1 TIM 定时器简介 1.2 TIM 定时器类型及其工作原理简介 1.2.1 基本定时器工作原理及其结构 1.2.2 通用定时器工作原理及其结构 1.2. ...
- STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)
本篇文章包含的内容 一.输入捕获 1.1 输入捕获简介 1.2 输入捕获通道的工作原理 1.3 输入捕获的主从触发模式 1.4 输入捕获和PWMI结构 二.频率的测量方法 2.1 测频法 2.2 测周 ...
最新文章
- ubuntu下pytorch
- 数字治理转型与公共卫生治理能力现代化调查项目
- csu 1976: 搬运工小明
- 用java开发一个Hello Word系统内核
- 一个不错的报表工具 open flash chart 2
- 联想拯救者Y7000P 2021H deepin v20.2.4设置双屏显示:切记要用集显,NAVIDA独显不生效
- 本地提交spark_Spark 数据本地化级别
- 最新升学e网通JS逆向分析
- antd vue关闭模态对话框_我不能没有的5个Vue.js库
- 中柏平板触摸驱动_要成绩也要玩乐,聊聊学生买平板那些事儿
- template.js 模板引擎
- TP5.0 Redis(单例模式)(原)
- mysql如何查询前几天_sql语句查询mysql怎么取前几天的数据
- Python Crash Course读书笔记 - 第18章:GETTING STARTED WITH DJANGO
- 高德地图marker标点数据量太大造成卡顿的解决方案
- 运营商大数据的发展现状和趋势
- 新年上班第一天生产环境分布式文件系统崩了!!
- 常见数据库对象和数据库存储
- 大屏数据可视化(dataV看看官网自己练练,在与echarts一结合完美!)
- linux awk nginx日志分析,awk分析nginx日志中的网页响应时间
热门文章
- 从键盘输入一个二进制非负整数,屏幕上打印输出对应的十进制、八进制和十六进制数,要求输出的十六进制数中的英文字母为大写字母。
- 计算机应用word单元测试,[高职统考}计算机应用基础word2003单元测试题(3)
- safeNet sentinel 加密狗远程更改简单配置(时间和使用次数等)
- iOS机型 iPhone X/XS/XR 判断的5种方式总结
- 生产制造企业如何建立适合自身的数字化工厂,实现数字化转型?
- 实训九:三层交换机VLAN划分及VLAN间通信
- 手把手教学:如何用低代码平台开发一个软件?
- 深入浅出,camera v4l2理解(2)v4l2注册
- 世界顶级计算机专家排名,世界前1000名顶级计算机科学家名单出炉,美国616人,中国呢?...
- [全程建模]全程建模方法被乱介绍的高校培训