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

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口的硬件结构:

I/O端口位的基本结构

看输出驱动器部分,可以看到使用了两个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)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【数据分享】2013-2022年我国省市县三级的逐年SO2数据(excel\shp格式\免费获取)
  • websocket状态机
  • 关于正运动学解机器人手臂算法
  • 【学习笔记】无人机系统(UAS)的连接、识别和跟踪(六)-无人机直接C2通信
  • 海外社媒矩阵为何会被关联?如何IP隔离?
  • 学懂C语言(十五):C语言递归函数在实际应用中的要点,关键点
  • Diffusion大模型
  • 生成式 AI 的发展方向:Chat 和 Agent 的有机结合
  • 【Docker】Docker Desktop - WSL update failed
  • 粘包问题、mmap和分片上传
  • spring整合mybatis,junit纯注解开发(包括连接druid报错的所有解决方法)
  • [web]-反序列化-base64
  • 嵌入式C++、STM32、树莓派4B、OpenCV、TensorFlow/Keras深度学习:基于边缘计算的实时异常行为识别
  • 如何使用“Claude Artifact”来生成前端代码
  • 智慧旅游的新引擎:景区客服呼叫中心系统的建设与运营
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • angular组件开发
  • avalon2.2的VM生成过程
  • bootstrap创建登录注册页面
  • go语言学习初探(一)
  • iOS 系统授权开发
  • JavaScript实现分页效果
  • Java反射-动态类加载和重新加载
  • laravel 用artisan创建自己的模板
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • Linux各目录及每个目录的详细介绍
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • Python打包系统简单入门
  • React16时代,该用什么姿势写 React ?
  • Vue2.0 实现互斥
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • vue的全局变量和全局拦截请求器
  • Vue学习第二天
  • 复杂数据处理
  • 构建工具 - 收藏集 - 掘金
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 译自由幺半群
  • 2017年360最后一道编程题
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • Nginx实现动静分离
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • ​14:00面试,14:06就出来了,问的问题有点变态。。。
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • ​力扣解法汇总946-验证栈序列
  • ​油烟净化器电源安全,保障健康餐饮生活
  • # AI产品经理的自我修养:既懂用户,更懂技术!
  • #NOIP 2014#Day.2 T3 解方程
  • $$$$GB2312-80区位编码表$$$$
  • (arch)linux 转换文件编码格式
  • (C++哈希表01)
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析