第十九章 IIC_EXIO实验
1)实验平台:正点原子DNESP32S3开发板
2)章节摘自【正点原子】ESP32-S3使用指南—IDF版 V1.6
3)购买链接:https://detail.tmall.com/item.htm?&id=768499342659
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32S3.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章,我们将继续使用ESP32-S3的硬件IIC接口去驱动IO扩展芯片XL9555。在本章中,实现和XL9555之间的双向通信,将使用其IO的输入输出功能。
本章分为如下几个小节:
19.1 IIC简介
19.2 硬件设计
19.3 程序设计
19.4 下载验证
19.1 IIC简介
IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器以及其外围设备。它是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据,在CPU与被控IC之间、IC与IC之间进行双向传送。
IIC总线有如下特点:
①总线由数据线SDA和时钟线SCL构成的串行总线,数据线用来传输数据,时钟线用来同步数据收发。
②总线上每一个器件都有一个唯一的地址识别,所以我们只需要知道器件的地址,根据时序就可以实现微控制器与器件之间的通信。
③数据线SDA和时钟线SCL都是双向线路,都通过一个电流源或上拉电阻连接到正的电压,所以当总线空闲的时候,这两条线路都是高电平。
④总线上数据的传输速率在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可达3.4Mbit/s。
⑤总线支持设备连接。在使用IIC通信总线时,可以有多个具备IIC通信能力的设备挂载在上面,同时支持多个主机和多个从机,连接到总线的接口数量只由总线电容400pF的限制决定。IIC总线挂载多个器件的示意图,如下图所示。
图19.1.1 IIC总线挂载多个器件
下面来学习IIC总线协议,IIC总线时序图如下所示:
图19.1.2 IIC总线时序图
为了便于大家更好的了解IIC协议,我们从起始信号、停止信号、应答信号、数据有效性、数据传输以及空闲状态等6个方面讲解,大家需要对应图14.1.1.2的标号来理解。
① 起始信号
当SCL为高电平期间,SDA由高到低的跳变。起始信号是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在起始信号产生后,总线就处于被占用状态,准备数据传输。
② 停止信号
当SCL为高电平期间,SDA由低到高的跳变。停止信号也是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在停止信号发出后,总线就处于空闲状态。
③ 应答信号
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
观察上图标号③就可以发现,有效应答的要求是从机在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主机,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主机接收器发送一个停止信号。
④ 数据有效性
IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。数据在SCL的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。
⑤ 数据传输
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。
⑥ 空闲状态
IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
了解前面的知识后,下面介绍一下IIC的基本的读写通讯过程,包括主机写数据到从机即写操作,主机到从机读取数据即读操作。下面先看一下写操作通讯过程图,如下图所示。
图19.1.3 写操作通讯过程图
主机首先在IIC总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地址+0(写操作)组成的8bit数据,所有从机接收到该8bit数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
接着讲解一下IIC总线的读操作过程,先看一下读操作通讯过程图,如下图所示。
图19.1.4 读操作通讯过程图
主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,都是由主机发出起始信号,接着发送从机地址+1(读操作)组成的8bit数据,从机接收到数据验证是否是自身的地址。那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返回8bit数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从机可以一直发送数据,也就是图中的(n byte + 应答信号)情况,直到主机发出非应答信号,从机才会停止发送数据。
19.1.1 IIC基本参数
(1)速率:I2C总线有标准模式(100kbit/s)和快速模式(400kbit/s)两种传输模式,还有更快的扩展模式和高速模式可供选择。
(2)器件地址:每个设备都有唯一的7位或10位地址,可以通过地址选择来确定与谁进行通信。
(3)总线状态:I2C总线有五种状态,分别是空闲状态、起始信号、结束信号、响应信号、数据传输。
(4)数据格式:I2C总线有两种数据格式,标准格式和快速格式。标准格式是8位数据字节加上1位ack/nack(应答/非应答)位,快速格式允许两个字节同时传输。
由于SCL和SDA线是双向的,它们也可能会由于外部原因(比如线路中的电容等)出现电平误差,而从而导致通信出错。因此,在IIC总线中,通常使用上拉电阻来保证信号线在空闲状态下的电平为高电平。
19.1.2 软件IIC与硬件IIC
IIC协议可以通过软件实现或者硬件实现。这两种方式的区别在于实现的方法和所需的硬件资源。
1,软件IIC
软件IIC是一种通过编程模拟IIC通信协议的技术。它巧妙利用微控制器的GPIO引脚来模拟IIC的数据线(SDA)和时钟线(SCL),通过软件精确控制引脚的电平变化,从而完成数据的传输和时序信号的生成。与硬件IIC相比,软件IIC的最大优势在于其普遍适用性,几乎任何支持GPIO功能的微控制器都可以实现。
在软件IIC的实现中,我们能够通过编程方式模拟IIC的主机和从机设备。通过精确控制GPIO引脚的状态,并严格按照IIC协议的时序要求进行操作,从而能够实现高效的数据传输和通信。软件IIC不仅灵活性极高,可以根据应用需求进行定制和扩展,还支持多从机设备和多主机环境,极大提升了通信的多样性和便捷性。
尽管软件IIC在性能上可能稍逊于硬件IIC,但在低速通信和简单通信需求的场景中,它无疑是一种经济且实用的解决方案。因此,软件IIC在资源受限的MCU系统中应用广泛,尤其适用于需要与多个外部设备进行通信的应用场景。
2,硬件IIC
硬件IIC指的是利用专门的硬件模块来执行IIC通信协议。现代微控制器及许多外部设备均集成了硬件IIC模块,这些模块负责处理IIC通信的所有复杂细节,如生成精确的时序信号、自动处理信号冲突、数据传输及错误检测等。采用硬件IIC,开发者可以直接通过硬件引脚与外部设备相连,无需编写繁琐的时序控制代码,从而简化了开发流程。
硬件IIC的使用通常更加便捷,开发者无需深入处理通信协议的内部机制,只需关注数据的传输与接收。硬件模块能够直接与外部设备通信,通过专用引脚进行数据和时钟信号的传输,确保通信的高效性与可靠性。
在决定使用软件IIC还是硬件IIC时,应综合考虑应用需求和硬件资源。软件IIC适用于资源有限的系统,其灵活性使其能在任何支持GPIO的微控制器上运行,但性能可能相对较低。而硬件IIC则以其出色的性能为特点,但通常需要特定的硬件支持,并可能占用部分引脚资源。因此,在选择时需要根据实际应用场景进行权衡。
19.1.3 IIC控制器介绍
ESP32-S3有两个IIC总线接口,根据用户的配置,总线接口可以用作IIC主机或从机模式。IIC接口特点:
l 可支持标准模式(100Kbit/s)、快速模式(400Kbit/s),速度最高可达800Kbit/s,但受限于SCL和SDA上拉强度。
l 可支持7位寻址模式和10位寻址模式
l 可支持双地址(从机地址和从机寄存器地址)寻址模式
下面介绍一下ESP32S3的IIC主机写入从机,7位寻址,单次命令序列的场景,如下图所示。
图19.1.3.1 IIC主机写7位寻址的从机
在ESP32-S3硬件IIC控制器中,都有相对应的空间存放相对应的内容。比如上图中,在cmd内存区中存放的是就是命令序列,就比如前面提及到的起始信号、写过程、读过程、停止信号;在RAM内存区中存放的就是某些命令序列携带的内容。
当主机在软件配置好命令序列和RAM数据后,操作寄存器启动数据传输时。控制器的行为可分为以下四步:
(1)等待SCL线位高电平,以避免SCL线被其他主机或者从机占用。
(2)执行RSTART命令发送START位。即发送起始信号。
(3)执行WRITE命令从RAM的首地址开始取出N+1个字节并一次发送给从机,其中第一个字节为地址。这个过程中会产生对应的时序,携带数据进行发送。
(4)发送STOP命令,即发送停止信号。
19.1.4 XL9555介绍
XL9555是一款24引脚的CMOS器件,支持IIC总线或SMBus接口进行驱动。XL9555器件是一个16位通用并行输入/输出(GPIO)扩展器,可用其GPIO连接按键、LED、传感器等,解决需要额外的I/O的需求。
l XL9555有如下特性:
l IIC总线至16位GPIO扩展器
l 工作电源电压范围为2.3 V至5.5 V
l 低待机电流消耗
l 5 V容错I/O端口
l 400 kHz快速模式IIC总线时钟频率
l SCL/SDA输入上的噪声滤波器
l 内部通电复位
l 器件地址由3个硬件地址引脚决定,最多可在总线上挂载8个器件
l 中断脚为开漏输出模式(低电平有效)
l 16个I/O引脚,默认为16个输入
简单概括一下,XL9555可使用400kHz速率的IIC通信接口与微控制器进行连接,也就是用2根通信线可扩展使用16个IO。XL9555器件地址会由三个硬件地址引脚决定,理论上在这个IIC总线上可挂载8个XL9555器件,足以满足IO引脚需求。XL9555上电进行复位,16个I/O口默认为输入模式,当输入模式的IO口状态发生变化时,即发生从高电平变低电平或者从低电平变高电平,中断脚会拉低。当中断有效后,必须对XL9555进行一次读取/写入操作,复位中断,才可以输出下一次中断,否则中断将一直保持。
XL9555引脚图如下图所示:
图19.1.4.1 XL9555器件引脚图
XL9555器件总共有24个管脚,分别为电源线VCC、地线GND、GPIO口、通信线、地址线,上图用不同底色标注出来了。16个IO分为了2组,一组是8个,分为是P0x和P1x,这些IO都可通过器件寄存器进行配置作为输出或者输出使用。通信线就是SDA和SCL,中断线INT也划分过来通信线。而地址线就是A0、A1和A2,用来决定器件地址。
1,XL9555寻址
要进行IIC通信,首先得知道器件地址,XL9555器件地址是7位的,具体格式如下图。
图19.1.4.2 XL9555器件地址
从上图可以知道,XL9555器件地址由两部分组成,一部分就是“Fixed bits”即固定的4位“0100”;另一部分就是“Programmable bits”即可编程的3位“A2 A1 A0”,在硬件上,都把这三个引脚接地处理,所以这三位为“000”。最终可得到,XL9555器件地址为“0100000”即0x20。读操作地址就为0x41,即0100 0001;写操作地址就为0x40,即0100 0000。
2, XL9555寄存器介绍
接下来,介绍一下XL9555器件的八个寄存器,如下图所示。
图19.1.4.3 XL9555寄存器
由于在IIC通信中,数据都是以字节作为单位,表示寄存器地址的数据也是1个字节。由于XL9555器件只有八个寄存器,所以这里1个字节用3个位表示,即Table 5中的B2、B1和B0。这8个寄存器都是XL9555器件的16个GPIO进行配置,其实分为4种:输入查询、输出设置、极性翻转和端口配置,每种都有两个寄存器对应的就是P0端口和P1端口。
地址0x00和0x01的寄存器是“Input Port0”和“Input Port1”寄存器,主要用于获取P0和P1的IO输入状态。寄存器如下图所示。
图19.1.4.4 XL9555的Input Port Register详情
该寄存器只反应引脚输入逻辑电平情况,不管IO是设置成输入还是输出模式。打个比方,从0x00地址处(Input Port 0 Register)读出的数据是0x55,以二进制展开为01010101,从高位到低位对应的就是P07~P00的IO状态,P00的输入电平状态就为高电平。
地址0x02和0x03的寄存器是“Output Port0”和“Output Port1”寄存器,主要用于设置P0和P1的IO输出电平。寄存器如下图所示。
图19.1.4.5 XL9555的Output Port Register详情
该寄存器设置的是已经配置成输出模式的IO口的IO输出状态,1代表的是高电平,0代表的都是低电平,配置IO为输出模式的寄存器为Configuration Port寄存器。寄存器的一些位值对已经设置成输入模式的IO口是没有影响的。该寄存器还支持读取,读取到的值只是设置值,并不是实际引脚电平值,实际电平值通过Input Port寄存器查询即可。
地址0x04和0x05的寄存器是“Polarity Inversion Port0”和“PolarityInversion Port1”寄存器,用于对端口0和端口1进行极性翻转。该寄存器值默认为0,所以对IO电平翻转功能并没有启用,且在本实验也没有用到,所以不做讲解,详细说明可看《XL9555数据手册》P13。
地址0x06和0x07的寄存器是“Configuration Port1”和“Configuration Port0”寄存器,用于配置P0和P1的IO输入/输出模式。寄存器如下图所示。
图19.1.4.6 XL9555的Configuration Port Register详情
该寄存器某一个位设置成1即作为输入模式,设置成0即作为输入模式。打个比方,要向0x06地址处(Configuration Port 0 Register)写入的数据是0x55,以二进制展开为01010101,从高位到低位对应的就是P07~P00的IO配置模式,P00、P02、P04、P06这四个IO口即配置为输入模式,而P01、P03、P05、P07这四个IO口配置为输出模式。XL9555上电复位后,所有IO口默认都是输入状态,即上图这两个寄存器读出来的值都是0xFF。
3, XL9555时序介绍
ESP32-S3是通过IIC总线跟XL9555进行通信的,对XL9555相关寄存器进行写入配置,对其16个IO进行使用。这里的时序主要就是写寄存器时序和读寄存器时序,我们一一介绍。
写寄存器时序
图19.1.4.7 单字节写入到寄存器时序图
上图中展示的是主机将单字节写入到寄存器的时序,主机在IIC总线发送第1个字节的数据为XL9555的写操作地址0x40(设备地址0x20 len != 0) { i2c_master_write(cmd, bufs->buf, bufs->len, ACK_CHECK_EN); /* len个数据 */ } } data_len += bufs->len; } if (flags & I2C_FLAG_STOP) { i2c_master_stop(cmd); /*停止位 */ }ret = i2c_master_cmd_begin(self->port, cmd, 100 * (1 + data_len) / portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd); /* 释放命令链接使用的资源 */ return ret;}[/code]与STM32不同,ESP32在IIC的配置上提供了两个IIC端口,但是在开发过程中会用到使用不同IIC端口的外设,为了保持代码的最大兼容性与减少两个IIC端口在使用过程中的冲突,我们在iic.h文件里定义了包含IIC端口的结构体参数,同时在IIC初始化函数中实现对这两个IIC端口的判断,再激活IIC控制器驱动函数完成对IIC的初始化。
在i2c_transfer()函数中,我们创建一个命令链接,将一系列待发送给从机的数据填充命令链接,接下来器件通信时序去决定flags参数,进而选择代码不同的执行情况。在最后释放命令链接使用资源前触发I2C控制器执行命令链接,并赋值于ret用作函数返回值。
IIC驱动中对IIC的各种操作,例如产生IIC起始信号、产生IIC停止信号等,请读者结合IIC的时序规定查看本实验的配套实验源码。
3,xl9555.h文件
通过前面的介绍可知,XL9555器件有8个寄存器,而且16个IO口在寄存器的位置都是固定的,基于单个IO操作单位的考虑,故这里我们也定义了对应的宏,如下所示。4,xl9555.c文件
[code]/*** @brief 初始化XL9555* @param 无* @retval 无*/void xl9555_init(i2c_obj_t self){ uint8_t r_data[2]; if (self.init_flag == ESP_FAIL) { iic_init(I2C_NUM_0); /* 初始化IIC */ } xl9555_i2c_master = self; gpio_config_tgpio_init_struct = {0}; gpio_init_struct.intr_type = GPIO_INTR_DISABLE; gpio_init_struct.mode = GPIO_MODE_INPUT; gpio_init_struct.pin_bit_mask = (1ull > 8)); do { err = xl9555_write_byte(XL9555_CONFIG_PORT0_REG, data, 2); if (err != ESP_OK) { retry--; vTaskDelay(100); ESP_LOGE("IIC", "%s configure %X failed, ret: %d", __func__, config_value, err); xl9555_failed = 1; if ((retry |