大家好,我是老耿,高职青椒一枚,一直从事单片机、嵌入式、物联网等课程的教学。对于高职的学生层次,同行应该都懂的,老师在课堂上教学几乎是没什么成就感的。正是如此,才有了借助头条平台寻求认同感和成就感的想法。在这里,我准备陆续把自己花了很多心思设计的教学课件分享出来,如果您正是一名单片机爱好者或是一名同行,欢迎点赞 关注,各位的支持是本人持续输出的动力,多谢多谢!#30天学会STM32##单片机#

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(1)

I2C(Inter-Integrated Circuit,常读作“I方C”)是飞利浦公司最早于1982年开发的一种双向二线制同步串行总线,经过多年的发展和更新,现在已成为很多存储器、传感器、显示屏与处理器之间的通信方式,我们开发板上的SHT20温湿度传感器和AT24C02存储器芯片采用的都是I2C通信接口。可以说,只要某一个器件或模块采用的是I2C通信接口,那就能“以不变的I2C协议应万变的模块”,从而进行学习和开发了。

【学习目标】

  1. 透彻理解I2C的通信时序
  2. 领悟软件模拟时序的思路和方法

本章只聚焦I2C底层的逻辑和时序,暂不涉及具体器件,为了不让篇幅太长,本章打算分三个部分来讲解,本文是第三部分。

五、编写I2C驱动文件5.2 软件模拟I2C代码剖析

2)i2c_sim.c源码剖析

i2c_sim.h的源码已在本文第二部分中呈现,声明了比较多的驱动函数,现在我们就来逐个剖析每个函数的实现。阅读源码时请结合本文第一部分的I2C时序图,效果会更好。

IIC_Init()函数源码

该函数对I2C总线的I/O端口进行了初始化,并将SDA和SCL置于空闲就绪状态,源码见代码清单2。需要注意的是,GPIO被配置成了开漏输出模式(GPIO_Mode_Out_OD),因为SDA线上的数据是双向传输的,配置成该模式可以不用切换I/O口的输入输出方向了。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(2)

代码清单2 IIC_Init()函数源码

IIC_Start()函数源码

这个函数是按照I2C通信起始信号(S)的要求编写的,源码见代码清单3。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(3)

代码清单3 IIC_Start()函数源码

上面函数中的延时有必要解释一下。I2C通信分为低速模式100kbit/s、快速模式400kbit/s和高速模式3.4Mbit/s,所有的I2C器件都支持低速,但却未必支持另外两种速度,所以作为通用的I2C程序我们选择100k这个速率来实现,也就是说实际程序产生的时序必须不高于100k的时序参数,很明显也就是要求SCL的高低电平持续时间都不短于5us,因此我们在时序函数中通过插入delay_us(5)延时函数,来达到这个速度限制。如果以后需要提高速度,那么只需要减小这里的延时时间即可。

IIC_Stop()函数源码

这个函数是按照2C通信结束信号(P)的要求编写的,源码见代码清单4。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(4)

代码清单4 IIC_Stop()函数源码

IIC_SendByte()函数源码

这个函数实现主机发送一个字节的数据到从机,源码见代码清单5。参数byte是待发送的字节,通过循环将byte中的每一位从高到低逐位发出,因此循环内部有对byte的左移操作,保证每次要发的那一位都出现在最高位上。该函数的用意有两方面,一是为了发送“7位从机地址 1位读/写控制”到从机,二是以字节为单位发送有效数据到从机。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(5)

代码清单5 IIC_SendByte()函数源码

IIC_RecvByte()函数源码

该函数用于主机接收来自于从机一个字节的数据,源码见代码清单6。该函数的实现思路与上面的IIC_SendByte()函数类似,不同之处在于数据传输方向变了,逐位接收的数据保存在rec变量中,并作为最后的返回值。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(6)

代码清单6 IIC_RecvByte()函数源码

IIC_WaitAck()函数源码

前面提到,当传输完一个字节数据,接收方会向发送方回复“应答(ACK)”或“非应答(NACK)”信号。作为发送方,则需要等待该信号的到来,该函数就是用于等待期间的处理,源码见代码清单7。如果在规定时间内(参数timeOut,单位us),收到了ACK或NACK信号,返回成功,否则超时返回失败,结束本次通信。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(7)

代码清单7 IIC_WaitAck()函数源码

IIC_Ack()函数源码

根据上面的响应规则,当SDA为低时,SCL一个上升沿代表发送一个应答(ACK)信号。该函数即用于产生应答信号,在读取从机发来的数据的时候会派上用场,源码见代码清单8。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(8)

代码清单8 IIC_Ack()函数源码

IIC_NAck()函数源码

与上同理,当SDA为高时,SCL一个上升沿代表发送一个非应答(NACK)信号。该函数即用于产生非应答信号,源码见代码清单9。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(9)

代码清单9 IIC_NAck()函数源码

I2C_WriteByte()函数源码

该函数将写入一个字节的完整过程封装了起来,它有三个参数:slaveAddr是I2C总线上的从机地址,regAddr是从机存放数据的地址,*byte是缓存写入数据的变量的地址,因为有些寄存器只需要控制下寄存器,并不需要写入值。写入成功返回0,写入失败返回1。详细源码见代码清单10。

注意,三个参数中前两个参数都是地址,但不是一个概念。打个比方,前者好比你的小区在某某路多少号,这是市政道路层面规划的;而后者好比你家在几号楼几零几,这是小区内部规划的,对外不具有通用性。因此,这三个参数就好比外卖员把餐送到你家,先找到小区(slaveAddr),再根据门牌号(regAddr)找到你家,最后把餐(*byte)交到你手上。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(10)

代码清单10 I2C_WriteByte()函数源码

I2C_ReadByte()函数源码

该函数与I2C_WriteByte()函数的操作类似,不同之处在于写完地址参数后需要改变数据方向变为读操作,变换方向之前还要再来一次起始信号(S)。读到的数据存在参数*val指向的缓冲区里。同样,读取成功返回0,读取失败返回1。具体源码见代码清单11。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(11)

代码清单11 I2C_ReadByte()函数源码

I2C_WriteBytes()函数源码

前面的I2C_WriteByte()函数实现的是一个字节的写入,而该函数则完成若干个字节的写入,待写入的数据在参数*buf指向的缓冲区中,数据长度由参数num决定。同样,写入成功返回0,写入失败返回1。具体源码见代码清单12。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(12)

代码清单12 I2C_WriteBytes()函数源码

I2C_ReadBytes()函数源码

前面的I2C_ReadByte()函数实现的是一个字节的读取,而该函数则完成若干个字节的读取,读到的数据在参数*buf指向的缓冲区中,数据长度由参数num决定。同样,读取成功返回0,读取失败返回1。具体源码见代码清单13。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(13)

代码清单13 I2C_ReadBytes()函数源码

至此,我们完成了“软件模拟I2C协议”所有底层驱动代码的解读,后续在学习SHT20温湿度传感器和AT24C02存储器编程时将调用这里的驱动函数。

3)main.c源码剖析

本工程的main.c文件不做具体操作,仅为后面的I2C模块实验做好准备。因此,这里main.c的源码仅作为框架,如代码清单14所示。

使用stm32前需要的电路知识(我在高职教STM32I2C通信入门)(14)

代码清单14 main.c文件源码

(第三部分完,共三部分)

,