数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 42|回复: 0

《STM32H7R7开发指南》第三十六章 IIC实验

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
第三十六章 IIC实验



本章,我们将介绍如何使用STM32H7R7的普通IO口模拟IIC时序,并实现和24C02之间的双向通信,并把结果显示在TFTLCD模块上
本章分为如下几个小节:
36.1 IIC及24C02简介
36.2 硬件设计
36.3 程序设计
36.4 下载验证


36.1 IIC及24C02简介36.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总线挂载多个器件的示意图,如下图所示:
图36.1.1.1 IIC总线挂载多个器件
下面来学习IIC总线协议,IIC总线时序图如下所示:
图36.1.1.2 IIC总线时序图
为了便于大家更好的了解IIC协议,我们从起始信号、停止信号、应答信号、数据有效性、数据传输以及空闲状态等6个方面讲解,大家需要对应图36.1.1.2的标号来理解。
① 起始信号
当SCL为高电平期间,SDA由高到低的跳变。起始信号是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在起始信号产生后,总线就会处于被占用状态,准备数据传输。
② 停止信号
当SCL为高电平期间,SDA由低到高的跳变。停止信号也是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在停止信号发出后,总线就会处于空闲状态。
③ 应答信号
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节。应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
观察上图标号③就可以发现,有效应答的要求是从机在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主机,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主机接收器发送一个停止信号。
④ 数据有效性
IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。数据在SCL的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。
⑤ 数据传输
在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。
⑥ 空闲状态
IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
了解这些知识后,下面介绍一下IIC的基本的读写通讯过程,包括主机写数据到从机即写操作,主机到从机读取数据即读操作。下面先看一下写操作通讯过程图,见图36.1.1.3所示:
图36.1.1.3 写操作通讯过程图
主机首先在IIC总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地址+0(写操作)组成的8bit数据,所有从机接收到该8bit数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
接着讲解一下IIC总线的读操作过程,先看一下读操作通讯过程图,见图36.1.1.4所示。
图36.1.1.4 读操作通讯过程图
主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,都是由主机发出起始信号,接着发送从机地址+1(读操作)组成的8bit数据,从机接收到数据验证是否是自身的地址。 那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返回8bit数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从机可以一直发送数据,也就是图中的(n byte + 应答信号)情况,直到主机发出非应答信号,从机才会停止发送数据。
24C02的数据传输时序是基于IIC总线传输时序,下面讲解一下24C02的数据传输时序。
36.1.2 24C02简介
24C02是一个2K bit的串行EEPROM存储器,内部含有256个字节。在24C02里面还有一个8字节的页写缓冲器。该设备的通信方式IIC,通过其SCL和SDA与其他设备通信,芯片的引脚图如图36.1.2.1所示。
图36.1.2.1 24C02引脚图
上图中有一个WP,这个是写保护引脚,接高电平只读,接地允许读和写,我们的板子设计是把该引脚接地。每一个设备都有自己的设备地址,24C02也不例外,但是24C02的设备地址是包括不可编程部分和可编程部分,可编程部分是根据上图的硬件引脚A0、A1和A2所决定。设备地址最后一位用于设置数据的传输方向,即读操作/写操作,0是写操作,1是读操作,具体格式如下图36.1.2.2所示:
图36.1.2.2 24C02设备地址格式图
根据我们的板子设计,A0、A1和A2均接地处理,所以24C02设备的读操作地址为:0xA1;写操作地址为:0xA0。
在前面已经说过IIC总线的基本读写操作,那么我们就可以基于IIC总线的时序的上,理解24C02的数据传输时序。
下面把实验中到的数据传输时序讲解一下,分别是对24C02的写时序和读时序。24C02写时序图见图36.1.2.3所示:
图36.1.2.3 24C02写时序图
上图展示的主机向24C02写操作时序图,主机在IIC总线发送第1个字节的数据为24C02的设备地址0xA0,用于寻找总线上找到24C02,在获得24C02的应答信号之后,继续发送第2个字节数据,该字节数据是24C02的内存地址,再等到24C02的应答信号,主机继续发送第3字节数据,这里的数据即是写入在第2字节内存地址的数据。主机完成写操作后,可以发出停止信号,终止数据传输。
上面的写操作只能单字节写入到24C02,效率比较低,所以24C02有页写入时序,大大提高了写入效率,下面看一下24C02页写时序图,图36.1.2.4所示:
图36.1.2.4 24C02页写时序
在单字节写时序时,每次写入数据时都需要先写入设备的内存地址才能实现,在页写时序中,只需要告诉24C02第一个内存地址1,后面数据会按照顺序写入到内存地址2,内存地址3等,大大节省了通信时间,提高了时效性。因为24C02每次只能写8bit数据,所以它的页大小也就是1字节。页写时序的操作方式跟上面的单字节写时序差不多,所以不作过多解释了。参考以上说明去理解页写时序。
说完两种写入方式之后,下面看一下图36.1.2.5关于24C02的读时序:
图36.1.2.5 24C02读时序图
24C02读取数据的过程是一个复合的时序,其中包含写时序和读时序。先看第一个通信过程,这里是写时序,起始信号产生后,主机发送24C02设备地址0xA0,获取从机应答信号后,接着发送需要读取的内存地址;在读时序中,起始信号产生后,主机发送24C02设备地址0xA1,获取从机应答信号后,接着从机返回刚刚在写时序中内存地址的数据,以字节为单位传输在总线上,假如主机获取数据后返回的是应答信号,那么从机会一直传输数据,当主机发出的是非应答信号并以停止信号发出为结束,从机就会结束传输。
以上的时序的发生基于软件IIC的实现,不用硬件IIC实现,虽然STM32H7R7带有IIC总线接口,但是ST把硬件IIC设计得非常复杂,所以使用起来很不方便,所以我们采用软件模拟。
36.2 硬件设计1. 例程功能
每按下KEY_UP,MCU通过IIC总线向24C02写入数据,通过按下KEY1来控制24C02读取数据。同时在LCD上面显示相关信息。LED0闪烁用于提示程序正在运行。
2. 硬件资源
1)LED灯
     LED0 : LED0 – PD14
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)独立按键
KEY0  - PE9
        KEY1  - PE8
5)24C02
IIC_SCL – PF1
        IIC_SDA – PF0
3. 原理图
我们主要来看看24C02和开发板的连接,如下图所示:
图36.2.1 24C02与开发板连接示意图
24C02的SCL和SDA分别连接在STM32的PF1和PF0上。本实验通过软件模拟IIC信号建立起与24C02的通信,进行数据发送与接收,使用按键KEY0和KEY1去触发,LCD屏幕进行显示。
36.3 程序设计
IIC实验中使用的是软件模拟IIC,所以用到的是HAL中GPIO相关函数,前面也有介绍到,这里就不做展开了。下面介绍一下使用IIC传输数据的配置步骤:
使用IIC传输数据的配置步骤
1)使能IIC的SCL和SDA对应的GPIO时钟。
本实验中IIC使用的SCL和SDA分别是PF1和PF0,因此需要先使能GPIOF的时钟,代码如下:
__HAL_RCC_GPIOF_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数据。读写函数均以一字节数据进行操作。
有了读和写函数,我们就可以对外设进行驱动了。
36.3.1 程序解析
本实验中,我们通过GPIO使用软件来模拟IIC,所以不需要用到HAL库的IIC驱动源码。在工程文件中,我们新增了myiic.c存放iic底层驱动代码,24cxx.c文件夹存放24C02驱动。
1. IIC底层驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。IIC驱动源码包括两个文件:myiic.c和myiic.h。
下面我们直接介绍IIC相关的程序,首先先介绍 myiic.h文件,其定义如下:
/* 引脚 定义 */
#define IIC_SCL_GPIO_PORT               GPIOF
#define IIC_SCL_GPIO_PIN                GPIO_PIN_1
#define IIC_SCL_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)   /* PF口时钟使能 */

#define IIC_SDA_GPIO_PORT               GPIOF
#define IIC_SDA_GPIO_PIN                GPIO_PIN_0
#define IIC_SDA_GPIO_CLK_ENABLE()       do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)   /* PF口时钟使能 */

/* 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 */

#define IIC_READ_SDA     HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN)        /* 读取SDA */
我们通过宏定义标识符的方式去定义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 = {0};

    /* 时钟使能 */
    IIC_SCL_GPIO_CLK_ENABLE();
    IIC_SDA_GPIO_CLK_ENABLE();

    /* 配置SCL引脚 */
    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_HIGH;
    HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct);

    /* 配置SDA引脚 */
    gpio_init_struct.Pin = IIC_SDA_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct);

    iic_stop();
}
在iic_init函数中主要工作就是对于GPIO的初始化,用于iic通信,不过这里需要注意的一点是SDA线的GPIO模式要使用开漏模式,特别注意:假如是STM32H7R7必须外接上拉电阻!
接下来介绍在上面已经在文字上说明过的IIC模拟信号:起始信号、停止信号、应答信号,下面以代码方法实现,大家可以对着图去看代码,有利于理解。
/**
* @brief       IIC延时函数,用于控制IIC读写速度
* @param       无
* @retval      无
*/
static void iic_delay(void)
{
    delay_us(2);  
}

/**
* @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总线时序图中抽取出来,如图36.3.2.1所示:
图36.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 < 8; t++)
    {
        IIC_SDA((data & 0x80) >> 7);     /* 高位先发送 */
        iic_delay();
        IIC_SCL(1);
        iic_delay();
        IIC_SCL(0);
        data <<= 1;           /* 左移1位,用于下一次发送 */
    }
    IIC_SDA(1);               /* 发送完成, 主机释放SDA线 */
}
在iic的发送函数iic_send_byte中,我们把需要发送的数据作为形参,形参大小为1个字节。在iic总线传输中,一个时钟信号就发送一个bit,所以该函数需要循环八次,模拟八个时钟信号,才能把形参的8个位数据都发送出去。这里使用的是形参data和0x80与运算的方式,判断其最高位的逻辑值,假如为1即需要控制SDA输出高电平,否则为0控制SDA输出低电平。为了更好说明,数据发送的过程,单独拿出数据传输时序图,见图36.3.2.2。
图36.3.2.2数据传输时序图

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|数码之家-技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2026-11-10 11:38 , Processed in 0.218400 second(s), 10 queries , Gzip On, Redis On.

Powered by Discuz!

© MyDigit.Net Since 2006

快速回复 返回顶部 返回列表