1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-336836-1-1.html 4)对正点原子STM32感兴趣的同学可以加群讨论:879133275
第三十五章 IIC实验本章,我们将介绍如何使用STM32H750的普通IO口模拟IIC时序,并实现和24C02之间的双向通信,并把结果显示在TFTLCD模块上 本章分为如下几个小节: 35.1 IIC及24C02简介 35.2 硬件设计 35.3 程序设计 35.4 下载验证
35.1 IIC及24C02简介 35.1.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总线挂载多个器件的示意图,如下图所示:
图35.1.1.1 IIC总线挂载多个器件 下面来学习IIC总线协议,IIC总线时序图如下所示:
图35.1.1.2 IIC总线时序图 为了便于大家更好的了解IIC协议,我们从起始信号、停止信号、应答信号、数据有效性、数据传输以及空闲状态等6个方面讲解,大家需要对应图35.1.1.2的标号来理解。 ① 起始信号 当SCL为高电平期间,SDA由高到低的跳变。起始信号是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在起始信号产生后,总线就会处于被占用状态,准备数据传输。 ② 停止信号 当SCL为高电平期间,SDA由低到高的跳变。停止信号也是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在停止信号发出后,总线就会处于空闲状态。 ③ 应答信号 发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节。应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 观察上图标号③就可以发现,有效应答的要求是从机在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主机,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主机接收器发送一个停止信号。 ④ 数据有效性 IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。数据在SCL的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。 ⑤ 数据传输 在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。 ⑥ 空闲状态 IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。 了解这些知识后,下面介绍一下IIC的基本的读写通讯过程,包括主机写数据到从机即写操作,主机到从机读取数据即读操作。下面先看一下写操作通讯过程图,见图35.1.1.3所示:
图35.1.1.3 写操作通讯过程图 主机首先在IIC总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地址+0(写操作)组成的8bit数据,所有从机接收到该8bit数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。 接着讲解一下IIC总线的读操作过程,先看一下读操作通讯过程图,见图35.1.1.4所示。
图35.1.1.4 读操作通讯过程图 主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,都是由主机发出起始信号,接着发送从机地址+1(读操作)组成的8bit数据,从机接收到数据验证是否是自身的地址。 那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返回8bit数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从机可以一直发送数据,也就是图中的(n byte + 应答信号)情况,直到主机发出非应答信号,从机才会停止发送数据。 24C02的数据传输时序是基于IIC总线传输时序,下面讲解一下24C02的数据传输时序。 35.1.2 24C02简介 24C02是一个2K bit的串行EEPROM存储器,内部含有256个字节。在24C02里面还有一个8字节的页写缓冲器。该设备的通信方式IIC,通过其SCL和SDA与其他设备通信,芯片的引脚图如图35.1.2.1所示。
图35.1.2.1 24C02引脚图 上图中有一个WP,这个是写保护引脚,接高电平只读,接地允许读和写,我们的板子设计是把该引脚接地。每一个设备都有自己的设备地址,24C02也不例外,但是24C02的设备地址是包括不可编程部分和可编程部分,可编程部分是根据上图的硬件引脚A0、A1和A2所决定。设备地址最后一位用于设置数据的传输方向,即读操作/写操作,0是写操作,1是读操作,具体格式如下图35.1.2.2所示:
图35.1.2.2 24C02设备地址格式图 根据我们的板子设计,A0、A1和A2均接地处理,所以24C02设备的读操作地址为:0xA1;写操作地址为:0xA0。 在前面已经说过IIC总线的基本读写操作,那么我们就可以基于IIC总线的时序的上,理解24C02的数据传输时序。 下面把实验中到的数据传输时序讲解一下,分别是对24C02的写时序和读时序。24C02写时序图见图35.1.2.3所示。
图35.1.2.3 24C02写时序图 上图展示的主机向24C02写操作时序图,主机在IIC总线发送第1个字节的数据为24C02的设备地址0xA0,用于寻找总线上找到24C02,在获得24C02的应答信号之后,继续发送第2个字节数据,该字节数据是24C02的内存地址,再等到24C02的应答信号,主机继续发送第3字节数据,这里的数据即是写入在第2字节内存地址的数据。主机完成写操作后,可以发出停止信号,终止数据传输。 上面的写操作只能单字节写入到24C02,效率比较低,所以24C02有页写入时序,大大提高了写入效率,下面看一下24C02页写时序图,图35.1.2.4所示。
图35.1.2.4 24C02页写时序 在单字节写时序时,每次写入数据时窦需要先写入设备的内存地址才能实现,在页写时序中,只需要告诉24C02第一个内存地址1,后面数据会按照顺序写入到内存地址2,内存地址3等,大大节省了通信时间,提高了时效性。因为24C02每次只能写8bit数据,所以它的页大小也就是1字节。页写时序的操作方式跟上面的单字节写时序差不多,所以不作过多解释了。参考以上说明去理解页写时序。 说完两种写入方式之后,下面看一下图35.1.2.5关于24C02的读时序。
图35.1.2.5 24C02读时序图 24C02读取数据的过程是一个复合的时序,其中包含写时序和读时序。先看第一个通信过程,这里是写时序,起始信号产生后,主机发送24C02设备地址0xA0,获取从机应答信号后,接着发送需要读取的内存地址;在读时序中,起始信号产生后,主机发送24C02设备地址0xA1,获取从机应答信号后,接着从机返回刚刚在写时序中内存地址的数据,以字节为单位传输在总线上,假如主机获取数据后返回的是应答信号,那么从机会一直传输数据,当主机发出的是非应答信号并以停止信号发出为结束,从机就会结束传输。 以上的时序的发生基于软件IIC的实现,不用硬件IIC实现,虽然STM32H750带有IIC总线接口,但是ST把硬件IIC设计得非常复杂,所以使用起来很不方便,所以我们采用软件模拟。 35.2 硬件设计
- 例程功能 每按下KEY1,MCU通过IIC总线向24C02写入数据,通过按下KEY0来控制24C02读取数据。同时在LCD上面显示相关信息。LED0闪烁用于提示程序正在运行。
- 硬件资源 1)RGB灯 RED : LED0 - PB4 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)独立按键 KEY0 - PA1 KEY1 - PA15 5)24C02 IIC_SCL - PB10 IIC_SDA - PB11
- 原理图 我们主要来看看24C02和开发板的连接,如下图所示:
图35.2.1 24C02与开发板连接示意图 24C02的SCL和SDA分别连接在STM32的PB10和PB11上。本实验通过软件模拟IIC信号建立起与24C02的通信,进行数据发送与接收,使用按键KEY0和KEY1去触发,LCD屏幕进行显示。 35.3 程序设计 IIC实验中使用的是软件模拟IIC,所以用到的是HAL中GPIO相关函数,前面也有介绍到,这里就不做展开了。下面介绍一下使用IIC传输数据的配置步骤: 使用IIC传输数据的配置步骤 1)使能IIC的SCL和SDA对应的GPIO时钟。 本实验中IIC使用的SCL和SDA分别是PB10和PB11,因此需要先使能GPIOB的时钟,代码如下: __HAL_RCC_GPIOB_CLK_ENABLE(); 2)设置对应GPIO工作模式(SCL推挽输出 SDA开漏输出) SDA线的GPIO模式使用开漏输出模式(硬件已接外部上拉电阻,也可以用内部的上拉电阻),而SCL线的GPIO模式使用推挽输出模式,通过函数HAL_GPIO_Init设置实现。 3)参考IIC总线协议,编写信号函数(起始信号,停止信号,应答信号) 起始信号:SCL为高电平时,SDA由高电平向低电平跳变。 停止信号:SCL为高电平时,SDA由低电平向高电平跳变。 应答信号:接收到IC数据后,向IC发出特定的低电平脉冲表示已接收到数据。 4)编写IIC的读写函数 通过参考时序图,在一个时钟周期内发送1bit数据或者读取1bit数据。读写函数均以一字节数据进行操作。 有了读和写函数,我们就可以对外设进行驱动了。 35.3.1 程序流程图
图35.3.1.1 IIC实验程序流程图 35.3.2 程序解析 本实验中,我们通过GPIO使用软件来模拟IIC,所以不需要用到HAL库的IIC驱动源码。在工程文件中,我们新增了myiic.c存放iic底层驱动代码,24cxx.c文件夹存放24C02驱动。
- IIC底层驱动代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。IIC驱动源码包括两个文件:myiic.c和myiic.h。 下面我们直接介绍IIC相关的程序,首先先介绍 myiic.h文件,其定义如下:
/* 引脚 定义 */
#define IIC_SCL_GPIO_PORT GPIOB
#define IIC_SCL_GPIO_PIN GPIO_PIN_10
#define IIC_SCL_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)
#define IIC_SDA_GPIO_PORT GPIOB
#define IIC_SDA_GPIO_PIN GPIO_PIN_11
#define IIC_SDA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)
/* IO操作 */
#define IIC_SCL(x) do{ x ? \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* SCL */
#define IIC_SDA(x) do{ x ? \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* SDA */
/* 读取SDA */
#define IIC_READ_SDA HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN)
我们通过宏定义标识符的方式去定义SCL和SDA两个引脚,同时通过宏定义的方式定义了IIC_SCL() 和IIC_SDA()设置这两个管脚可以输出0或者1,主要还是通过HAL库的GPIO操作函数实现的。另外方便在iic操作函数中调用读取SDA管脚的数据,这里直接宏定义IIC_READ_SDA实现,在后面iic模拟信号实现中会频繁调用。
接下来我们看一下myiic.c代码中的初始化函数,代码如下:
/**
* @brief 初始化IIC
* @param 无
* @retval 无
*/
void iic_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
IIC_SCL_GPIO_CLK_ENABLE(); /* SCL引脚时钟使能 */
IIC_SDA_GPIO_CLK_ENABLE(); /* SDA引脚时钟使能 */
gpio_init_struct.Pin = IIC_SCL_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 快速 */
HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct); /* SCL */
gpio_init_struct.Pin = IIC_SDA_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */
HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct); /* SDA */
/* SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1),
也可以读取外部信号的高低电平 */
iic_stop(); /* 停止总线上所有设备 */
}
在iic_init函数中主要工作就是对于GPIO的初始化,用于iic通信,不过这里需要注意的一点是SDA线的GPIO模式要使用开漏模式,特别注意:假如是STM32F103必须外接上拉电阻! 接下来介绍在上面已经在文字上说明过的IIC模拟信号:起始信号、停止信号、应答信号,下面以代码方法实现,大家可以对着图去看代码,有利于理解。
/**
* @brief IIC延时函数,用于控制IIC读写速度
* @param 无
* @retval 无
*/
static void iic_delay(void)
{
delay_us(2); /* 2us的延时, 读写速度在250Khz以内 */
}
/**
* @brief 产生IIC起始信号
* @param 无
* @retval 无
*/
void iic_start(void)
{
IIC_SDA(1);
IIC_SCL(1);
iic_delay();
IIC_SDA(0); /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
iic_delay();
IIC_SCL(0); /* 钳住I2C总线,准备发送或接收数据 */
iic_delay();
}
/**
* @brief 产生IIC停止信号
* @param 无
* @retval 无
*/
void iic_stop(void)
{
IIC_SDA(0); /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
iic_delay();
IIC_SCL(1);
iic_delay();
IIC_SDA(1); /* 发送I2C总线结束信号 */
iic_delay();
}
在这里首先定义一个iic_delay函数,目的就是控制IIC的读写速度,通过示波器检测读写速度在250KHz内,所以一秒钟传送500Kb数据,换算一下即一个bit位需要2us,在这个延时时间内可以让器件进行获得一个稳定性的数据采集。 为了大家更加清晰了解代码实现的过程,下面单独把起始信号和停止信号从iic总线时序图中抽取出来,如图35.3.2.1所示:
图35.3.2.1 起始信号与停止信号图 iic_start函数中,通过调用myiic.h中通过宏定义好的可以输出高低电平的SCL和SDA来模拟iic总线中起始信号的发送,在SCL时钟线为高电平的时候,SDA数据线从高电平状态转化到低电平状态,最后拉低时钟线,准备发送或者接收数据。 iic_stop函数中,也是按着模拟iic总线中停止信号的逻辑,在SCL时钟线为高电平的时候,SDA数据线从低电平状态转化到高电平状态。 接下来讲解一下iic的发送函数,其定义如下:
/**
* @brief IIC发送一个字节
* @param data: 要发送的数据
* @retval 无
*/
void iic_send_byte(uint8_t data)
{
uint8_t t;
for (t = 0; t > 7); /* 高位先发送 */
iic_delay();
IIC_SCL(1);
iic_delay();
IIC_SCL(0);
data > 8); /* 发送高字节地址 */
}
else
{ /* 发送器件 0XA0 + 高位a8/a9/a10地址,写数据 */
iic_send_byte(0XA0 + ((addr >> 8) 8)> 8);/* 发送高字节地址 */
}
else
{
/* 发送器件 0XA0 + 高位a8/a9/a10地址,写数据 */
iic_send_byte(0XA0 + ((addr >> 8)
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?