STM32之八:IIC通信协议
目录
1. IIC协议简介
1.1 主从模式
1.2 2根通信线
2. IIC协议时序
2.1 起始条件和终止条件
2.2 应答信号
2.3 发送一个字节
2.4 接收一个字节
3. IIC读写操作
3.1 写操作
3.2 读操作
1. IIC协议简介
IIC协议是一个半双工、同步、一主多从、多主多从的串行通用数据总线。该通信模式需要2根线:SCL、SDA,即时钟线和数据线。
1.1 主从模式
IIC协议支持一主多从和多主多从,每个设备都有唯一的地址。
1.2 2根通信线
SDA:数据线,用于传输数据
SCL:时钟线,用于同步数据传输
接线时所有IIC设备的SCL连在一起,SDA连在一起,不同的设备,都是并联接在这两条线上(设备之间“线与”关系),I2C总线上的每个设备都自己一个唯一的地址,来确保不同设备之间访问的准确性。设备的SCL的SDA均要配置成开漏输出模式,SCL和SDA需要各添加一个上拉电阻,阻值一般为4.7KΩ左右。
这里补充一下,因为有看到过一个主机一个从机的情况下,可以设置为推挽输出模式,但是在一主多从,或者多主多从的情况下,推荐开漏输出,原因请看下文。

为了能理解为什么在IIC协议总线上,IO口模式需要设置为开漏输出模式,而不能使用推挽输出模式,可以看下GPIO口的硬件结构:
看输出驱动器部分,可以看到使用了两个MOS管,分别是P-MOS和N-MOS管,这些是STM32 GPIO输出模式的关键元件。在推挽输出模式下,P-MOS管和N-MOS管都工作,通过控制这些管的开关状态来实现高电平和低电平的输出。在开漏输出模式下,只有N-MOS管工作,用于输出低电平,而高电平的输出则需要通过外部上拉电阻实现。
推挽输出模式:
推挽输出结构是由两个MOS收到互补控制的信号控制。推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。推挽输出模式中,N-MOS管和P-MOS管都工作,如果我们控制输出为0(低电平),则P-MOS管关闭,N-MOS管导通,输出低电平;若控制输出为1(高电平),则P-MOS管导通,N-MOS管关闭,输出高电平。外部上拉和下拉的作用是控制在没有输出时的IO口电平。
优点:驱动能力强,电平切换能力快(根据GPIO的波特率可用作模拟其他协议)。
缺点:多个推挽输出端口相连时,由于通路上阻抗较小电流会从IO的VDD流向另一个IO的GND,会发生短路进而对端口造成伤害。(这里就解释了为什么IIC不能使用推挽输出模式,如果多个从机都接到SDA线上,一个机器发送数据0,另一个机器发送数据1,则可能会发生短路进而毁坏IIC器件)。
开漏输出模式:
开漏输出时只有N-MOS管工作,只能输出低电平。当其输出高电平时没有驱动能力(电压会被外部阻抗拉低),需要借助外部上拉电阻完成对外驱动(通断N-MOS实现对路径上的电压控制),驱动能力取决于上拉电阻阻值。
如果我们控制输出为0(低电平),则P-MOS管关闭,N-MOS管导通,输出低电平;若控制输出为1(高电平),则P-MOS管和N-MOS管都关闭,输出指令就不会起到作用,此时I/O端口的电平就不由输出的高低电平决定,而是由I/O端口外部的上拉或者下拉决定。如果没有上拉或者下拉 IO口就处于悬空状态。
半双工:一根数据线,这根数据线既可以发送数据,也可以接收数据,但是不能同时发送和接收,所以叫做半双工通信。
代码如下:注意开漏输出模式
// PB11-->SDA
// PB10-->CLKvoid MyI2C_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出/*设置默认电平*/GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}
2. IIC协议时序
IIC 协议基本形式:

可以看到,IIC时序在进行一次传输的时候,会有开始信号和停止信号,期间发送1byte数据,每一byte数据之后都会有一个ACK信号,下面依次讲解这些信号时序。
2.1 起始条件和终止条件

IIC需要起始信号和终止信号,SCL在高电平期间,SDA从高电平跳转到低电平表示开始信号;SDA从低电平跳转高电平表示结束信号。
/*** 函 数:I2C起始* 参 数:无* 返 回 值:无*/
void MyI2C_Start(void)
{MyI2C_W_SDA(1); //释放SDA,确保SDA为高电平MyI2C_W_SCL(1); //释放SCL,确保SCL为高电平MyI2C_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号MyI2C_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
/*** 函 数:I2C终止* 参 数:无* 返 回 值:无*/
void MyI2C_Stop(void)
{MyI2C_W_SDA(0); //拉低SDA,确保SDA为低电平MyI2C_W_SCL(1); //释放SCL,使SCL呈现高电平MyI2C_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}//---------------
/*** 函 数:I2C写SCL引脚电平* 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平*/
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平Delay_us(10); //延时10us,防止时序频率超过要求
}/*** 函 数:I2C写SDA引脚电平* 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平*/
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性Delay_us(10); //延时10us,防止时序频率超过要求
}
2.2 应答信号

下面展示一个传输数据时候发送应答信号(ACK)和非应答信号(NACK)的示意 :


/*** 函 数:I2C发送应答位* 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答* 返 回 值:无*/
void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit); //主机把应答位数据放到SDA线MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间,读取应答位MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
}/*** 函 数:I2C接收应答位* 参 数:无* 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答*/
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit; //定义应答位变量MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDAAckBit = MyI2C_R_SDA(); //将应答位存储到变量里MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块return AckBit; //返回定义应答位变量
}
2.3 发送一个字节

注意,当时钟信号SCL为高电平的时候,数据线SDA上的信号需要保持不变。因为在SCL高电平期间发生SDA的跳变会误认为产生了起始或停止信号,从而导致数据数据传输错误。
/*** 函 数:I2C发送一个字节* 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF* 返 回 值:无*/
void MyI2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i ++) //循环8次,主机依次发送数据的每一位{MyI2C_W_SDA(Byte & (0x80 >> i)); //使用掩码的方式取出Byte的指定一位数据并写入到SDA线MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间读取SDAMyI2C_W_SCL(0); //拉低SCL,主机开始发送下一位数据}
}
2.4 接收一个字节

/*** 函 数:I2C接收一个字节* 参 数:无* 返 回 值:接收到的一个字节数据,范围:0x00~0xFF*/
uint8_t MyI2C_ReceiveByte(void)
{uint8_t i, Byte = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送for (i = 0; i < 8; i ++) //循环8次,主机依次接收数据的每一位{MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDAif (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);} //读取SDA数据,并存储到Byte变量//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA}return Byte; //返回接收到的一个字节数据
}/*** 函 数:I2C读SDA引脚电平* 参 数:无* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1*/
uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //读取SDA电平Delay_us(10); //延时10us,防止时序频率超过要求return BitValue; //返回SDA电平
}
如果IIC总线上传输1Byte数据0X49(01001001),其时序图如下:

3. IIC读写操作
3.1 写操作
使用IIC进行数据传输时,写操作流程如下:
1. 主机确定从机地址。IIC总线上每个设备都有唯一的地址码,用于标识和寻址。主机寻址过程是在起始信号之后进行的,起始信号之后,主机会发送7bit的从机地址+1bit的读写位(0表示写操作,1表示读操作),这也是为什么有时候看到一个器件有读地址和写地址的原因。这种寻址方式理论上最多能挂载127个从设备。目前为了制造更多的可使用从机地址数量,IIC有标准模式、快速模式和高速模式,支持10bit寻址,允许1024个从机地址。
2. 主机发送开始信号,表示开始IIC传输
3. 进行寻址,即发送从机设备地址+1bit的读写位,写操作则读写位0,例如一个从机设备地址为0x13,则发送的信号为:00011010。
4.从机回复应答信号ACK。IIC总线的从机接受到主机的信号后,会将该地址和自己的地址码进行匹配,如果匹配成功后,则相应从机会回复给主机一个应答信号ACK。
5.主机接受到应答信号,发送数据,从机应答。
6.主机发送结束信号,IIC传输结束。
整个时序图如下所示:

3.2 读操作
读操作和写操作类似,主要更改的点在于:
1. 寻址时读写标志位为1
2. 从机应答后会主动向主机发送其所要读取的数据,主机接受到信号后会向从机发送一个应答信号
3. 当主机想要结束读操作时,主机会回复一下非应答信号,生成停止信号来结束数据读取操作。
整个时序图如下:

参考:
1. 江协科技STM32
2. IIC协议 (dreamsourcelab.cn)
相关文章:
STM32之八:IIC通信协议
目录 1. IIC协议简介 1.1 主从模式 1.2 2根通信线 2. IIC协议时序 2.1 起始条件和终止条件 2.2 应答信号 2.3 发送一个字节 2.4 接收一个字节 3. IIC读写操作 3.1 写操作 3.2 读操作 1. IIC协议简介 IIC协议是一个半双工、同步、一主多从、多主多从的串行通用数据总…...
mysql的数据往hive进行上报时怎么保证数据的准确性和一致性
在将MySQL的数据往Hive进行上报时,确保数据的准确性和一致性可以通过下面一系列步骤来实现 一、准备工作 环境配置: 确保MySQL和Hive环境已经安装并配置好,且都处于可运行状态。检查Hadoop集群(Hive通常运行在Hadoop之上&#x…...
问题:4、商业保险与政策性保险的主要不同之处是:经营主体不同、经营目标不同、承保机制不同。 #学习方法#其他#学习方法
问题:4、商业保险与政策性保险的主要不同之处是:经营主体不同、经营目标不同、承保机制不同。 参考答案如图所示...
Getx学习笔记之中间件鉴权
目录 前言 一、实现步骤 1.添加依赖 2.创建鉴权中间件 3.定义路由 4.设置初始路由 5.模拟登陆状态 二、Getx鉴权步骤总结 三、本文demo示例 四、参考文章 前言 在 Flutter 中,使用 GetX 可以很方便地实现中间件鉴权(Authentication)…...
介绍 Elasticsearch 中的 Learning to Tank - 学习排名
作者:来自 Elastic Aurlien Foucret 从 Elasticsearch 8.13 开始,我们提供了原生集成到 Elasticsearch 中的学习排名 (learning to rank - LTR) 实现。LTR 使用经过训练的机器学习 (ML) 模型为你的搜索引擎构建排名功能。通常,该模型用作第二…...
2024年计算机软考中级【硬件工程师】面试题目汇总(附答案)
硬件工程师面试题汇总分析 1、解释一下同步电路和异步电路 解题思路 同步电路和异步电路是指同步时序电路和异步时序电路。由于存储电路中触发器的动作特点不同,因此可以把时序电路分为同步时序电路和异步时序电路两种。同步时序电路所有的触发器状态的变化都是在同…...
ThinkPad改安装Windows7系统的操作步骤
ThinkPad:改安装Windows7系统的操作步骤 一、BIOS设置 1、先重新启动计算机,并按下笔记本键盘上“F1”键进入笔记本的BIOS设置界面。 2、进入BIOS设置界面后,按下键盘上“→”键将菜单移动至“Restart“项目,按下键盘上“↓”按键…...
微软Edge浏览器全解析教程
微软Edge浏览器全解析教程 微软Edge浏览器,作为微软公司精心打造的一款现代化网页浏览器,自其首次发布以来,凭借其卓越的性能、出色的用户体验和不断迭代的功能,赢得了广大用户的青睐。本文将全面解析微软Edge浏览器的各个方面&a…...
【过题记录】7.20
前两题一直在打模拟赛,有点忙,就没更 Red Playing Cards 算法:动态规划 其实这就是一个线段覆盖问题,只不过大线段能够包含小线段。 这就启发我们,对于每个大线段分别跑一个dp,合并在他内部的小线段。而后…...
Linux系统学习日记——vim操作手册
Vim编辑器是linux下的一个命令行编辑器,类似于我们windows下的记事本。 目录 打开文件 编辑 保存退出 打开文件 打开 hello.c不存在也可以打开,保存时vim会自动创建。 效果 Vim打开时,处于命令模式,即执行命令的模式&#x…...
【深度学习图片】图片清洗,只留下图像中只有一张人脸的,而且人脸是全的
环境: conda install pytorch torchvision torchaudio pytorch-cuda11.8 -c pytorch -c nvidia -ypip install onnx1.15 onnxruntime-gpu1.17pip install insightface0.7.3pip install opencv-pythonpip install gradio图片清洗,只留下图像中只有一张人脸…...
如何在 PostgreSQL 中处理海量数据的存储和检索?
🍅关注博主🎗️ 带你畅游技术世界,不错过每一次成长机会!📚领书:PostgreSQL 入门到精通.pdf 文章目录 如何在 PostgreSQL 中处理海量数据的存储和检索?一、优化表结构设计二、分区技术三、数据压…...
【中项】系统集成项目管理工程师-第2章 信息技术发展-2.2新一代信息技术及应用-2.2.1物联网与2.2.2云计算
前言:系统集成项目管理工程师专业,现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试,全称为“全国计算机与软件专业技术资格(水平)考试”&…...
Redis集群的主从复制原理-全量复制和增量复制-哨兵机制
Redis集群的主从复制原理-全量复制和增量复制-哨兵机制 作用 数据备份 这一点直观,因为现在有很多节点,每个节点都保存了原始数据的备份. 读写分离 这一点主要是当发生读写的时候,读数据的操作大部分都会进入到从节点,而写数据的操作都会进入到主节点&…...
23年阿里淘天笔试题 | 卡码网模拟
第一题 字典序最小的 01 字符串 解题思路: 模拟,统计遇到的连续的1的个数记为num,直到遇到0,如果k>num,直接将第一个1置为0,将遇到的0置为1,否则将第一个1偏置num-k个位置置为0࿰…...
【SpringBoot】单元测试之测试Service方法
测试Service方法 SpringBootTest public class UserServiceTest{ Autowired private UserService userService; Test public void findOne () throws Exception{ Assert.assertEquals("1002",userService.findOne()); } } 测试Controller接口方法 Runwith(S…...
剪辑师和小白都能用的AI解说神器,一键把短剧变解说视频-手把手教程-2024
为什么短剧、综艺、电影和电视剧需要以解说形式在抖音、快手和TikTok推广? 此类专业影视内容由于时间过长、平台用户的习惯、算法去重需求和版权问题,专业的影视综节目通常需要用解说类型的视频来不断重复的宣发剧集。具体的原因如下: 1. 视…...
我去,怎么http全变https了
项目场景: 在公司做的一个某地可视化项目。 部署采用的是前后端分离部署,图片等静态资源请求一台minio服务器。 项目平台用的是http 图片资源的服务器用的是https 问题描述 在以https请求图片资源时,图片请求成功报200。 【现象1】: 继图…...
IDEA的详细设置
《IDEA破解、配置、使用技巧与实战教程》系列文章目录 第一章 IDEA破解与HelloWorld的实战编写 第二章 IDEA的详细设置 第三章 IDEA的工程与模块管理 第四章 IDEA的常见代码模板的使用 第五章 IDEA中常用的快捷键 第六章 IDEA的断点调试(Debug) 第七章 …...
为什么Spring选择使用容器来管理对象,而不是直接使用new
为什么Spring选择使用容器来管理对象,而不是直接使用new 在Java应用程序开发中,对象的创建和管理是一项基础且关键的任务。传统上,开发者习惯于使用new关键字直接在代码中实例化对象。然而,随着应用程序规模的扩大和复杂度的增加…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
字符串哈希+KMP
P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...
