实验7 - IIC/I2C接口实验

1、IIC总线的介绍

1. I2C总线是PHLIPS公司在八十年代初推出的一种"同步串行半双工"总线,
 	主要用于连接整体电路。
 	
 	SOC和各自传感器外设之间的通信,一般使用的都是IIC或者SPI
 	两个设备之间的通信,一般使用UART,485,CAN,USB
 
2. I2C总线为"两线制",只有两根双向信号线。
 	一根是"数据线SDA",另一根是"时钟线SCL"。

3. I2C硬件结构简单,接口连接方便,成本较低。因此在各个领域得到了广泛的应用。

4. I2C总线的通信的速度一般为100k-400kbps之间。

5. 一根I2C总线上可以外接多个I2C的器件,I2C总线上需要外接两个"上拉电阻"。

image-20230721134027759

1. I2C是具备多主机系统所需的包括总线裁决功能的高性能串行总线。

2. 每个接到I2C总线上的器件都有"唯一的地址"。
	主机与其它器件进行数据传送时总线上发送数据的器件为"发送器",
	总线上接收数据的器件则为"接收器"。

3. 主机:可以主动发起通信的器件称为主机,一般指SOC。
		时钟信号一般由主机产生,作用给从机。
	从机:只可以被动的进行数据收发的器件,程序从机,一般指传感器。

image-20230721134634977

2、I2C总线的时序

2.1 起始信号时序/终止信号时序

image-20230721135030513

1. SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号

2. SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号

3. 起始和终止信号都是由主机发出,起始信号产生后,总线就处于占用的态;
	终止信号产生后,总线就处于空闲态。

2.2 数据传输时序

image-20230721135242995

1. I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,
	只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

2. 在时钟为低电平期间,发送器可以向数据线上写入数据,
	因此数据线上的数据可以发生改变。
	在时钟为高电平期间,接收器从数据线上读取数据,
	因此要求数据线上的数据必须保持稳定。
	
	一个时钟周期完成了一个bit位数据的收发。

2.3 应答信号

image-20230721135817038

1. 每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),
	每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

2. 发送器给接收器发送完成1个字节的数据之后,接收器需要在第九个时钟周期内,
	给发送器返回一个应答信号或者非应答信号。

3. 在第九个时钟周期的低电平期间,接收器向数据线上写入数据,
	在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
	如果读到的是低电平信号,表示接收到应答信号,
	如果读到的是高电平信号,表示接收到非应答信号。

2.4 I2C的寻址

1. I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。

2. 主机在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/W),
	用“0”表示主机发送数据(W),“1”表示主机接收数据(R)。
	总线上的每个从机都将这7位地址码与自己的地址进行比较,
	如果相同,则认为自己被主机寻址,根据R/W位将自己定为发送器或接收器。

image-20230721140653335

3、I2C总线的通信协议

image-20230721143854863

image-20230721144009231

image-20230721145019843

3.1 主机给从机发生一个字节的通信协议

image-20230721145027025

3.2 主机给从机发生连续的多个字节的通信协议

image-20230721145247701

3.3 从机给主机发送一个字节的通信协议

image-20230721145829807

3.4 从机给主机发送连续的多个字节的通信协议

image-20230721150035375

从机给主机发送数据时,为什么主机在读最后一个字节数据时,返回的是非应答信号?

	从机只能被动的进行数据的收发,当从机给主机发送数据时,
主机收到1个字节的数据之后,需要给从机返回一个应答信号或者非应答信号,
如果主机返回的是应答信号,则从机会发送下一个字节的数据,
如果主机返回的是非应答信号,则从机不会发送下一个字节的数据。

4、I2C总线实验实现

4.1 分析温湿度传感器电路图

image-20230721154403580

image-20230721154416856

image-20230721154437515

4.2 分析芯片手册

4.2.1 分析2.5.2章节,确定GPIOF外设寄存器的基地址以及接到哪根总线上

4.2.2 分析RCC章节,使用GPIOF外设的时钟源

4.2.3 分析GPIO章节,设置PF14,PF15引脚为输入或者输出,输出高低电平

4.2.4 分析SI7006温湿度传感器的芯片手册

SI7006温湿度传感器的框图:

image-20230721155133369

转换时间:跟测量的精度有关

image-20230721155552271

I2C总线的时序图:

image-20230721155659244

湿度的测量精度和误差

image-20230721155810693

image-20230721155844282

温度测量的精度和误差:

image-20230721160125463

image-20230721160148789

SI7006典型设计电路图:

image-20230721160236405

SI7006的从机地址:

image-20230721160529682

SI7006芯片的命令码:

命令码就是这个寄存器地址

image-20230721160709472

测量温湿度保持主模式的通信协议:

image-20230721160935781

数字量的湿度转换为模拟量的公式:

image-20230721161045881

数字量的温度转换为模拟量的公式:

image-20230721161110061

读写用户寄存器的通信协议:

image-20230721161255819

用户寄存器的介绍:

image-20230721161429178

image-20230721161444855

 

4.3 编写驱动代码

4.3.1 I2C驱动代码

1. 开始信号函数
2. 停止信号函数
3. 主机给从机,写一个字节数据的函数
4. 主机从从机,读一个字节数据的函数
5. 主机给从机发送应答信号函数
6. 主机给从机发送非应答信号函数
7. 主机等待从机发送应答信号的函数

iic.h文件

将前面协议中的每一个小方块都封装成函数

#ifndef _IIC_H_
#define _IIC_H_

#include "../common/include/stm32mp1xx_i2c.h"
#include "gpio.h"

//宏定义
//时钟  SCL  PF14
#define IIC_SCL_H               \
    do{                         \
        GPIOF->BSRR |= 1<<14;    \
    }while(0)

#define IIC_SCL_L               \
    do{                         \
        GPIOF->BRR |= 1<<14;     \
    }while(0)

//数据线   SDA  PF15
//GPIO输出模式
#define SET_SDA_OUT                 \
    do{                             \
        GPIOF->MODER &=~(0x3<<30);   \
        GPIOF->MODER |=(0x1<<30);    \
        }while(0)

//输入模式
#define SET_SDA_IN                  \
    do{                             \
        GPIOF->MODER &=~(0x3<<30);   \
        }while(0)
//高低电平
#define IIC_SDA_H               \
    do{                         \
        GPIOF->BSRR |= 1<<15;    \
    }while(0)

#define IIC_SDA_L               \
    do{                         \
        GPIOF->BRR |= 1<<15;     \
    }while(0)

//读取输入内容
#define IIC_SDA_READ (GPIOF->IDR & (1<<15))

void delay_us();
void iic_init();
void iic_start();
void iic_stop();
void iic_write_1byte(uint8_t data);
uint8_t iic_read_1byte(uint8_t ack);
uint8_t iic_wait_ack();
void iic_send_ack();
void iic_send_nack();


#endif /*_IIC_H_*/

iic.c

根据每个函数中画好的时序图写,将图转换成代码语言

#include "../include/iic.h"

void delay_us(){
    unsigned int i = 2000;
	while (i--)
		;
}
void iic_init(){
    //使能GPIOF时钟
    RCC->MP_AHB4ENSETR |=(1<<5);
    //将PF14 PF15设置为输出
    GPIOF->MODER &=~(0xF<<28);
    GPIOF->MODER |= (0x5<<28);
    //推挽
    GPIOF->OTYPER &=~(0b11<<14);
    //高速
    GPIOF->OSPEEDR |= (0xF<<28);
    //禁止上下拉
    GPIOF->PUPDR &=~(0xF<<28);
    //拉高时钟线和数据线
    IIC_SCL_H;
    IIC_SDA_H;
}
void iic_start(){
    /*
	 * 开始信号:时钟在高电平期间,数据线从高到低的变化
	 *     --------
	 * SCL         \
	 *              --------
	 *     ----
	 * SDA     \
	 *          --------
	 * */
    SET_SDA_OUT;//先将SDA设置为输出模式
    IIC_SCL_H;
    IIC_SDA_H;
    delay_us();
    IIC_SDA_L;
    delay_us();
    IIC_SCL_L;
}
void iic_stop(){
    /*
	 * 停止信号 : 时钟在高电平期间,数据线从低到高的变化
	 *             ----------
	 * SCL        /
	 *    --------
	 *    ---         -------
	 * SDA   X       /
	 *    --- -------
	 *    为了确保停止信号是一个上升沿,因此在时钟为低电平期间
	 *    将数据线拉低,确保可以产生上升沿。
	 * */
    SET_SDA_OUT;//先将SDA设置为输出模式
    IIC_SCL_L;
    delay_us();
    IIC_SDA_L;//确保上升沿
    delay_us();
    IIC_SCL_H;
    delay_us();
    IIC_SDA_H;
    delay_us();
}
uint8_t iic_wait_ack(){
    /*
	 * 主机发送一个字节之后,从机给主机返回一个应答信号,主机接收应答信号
	 *                       -----------
	 * SCL                  /   M:读    \
	 *     -----------------             --------
	 *     --- -------- --------------------
	 * SDA    X        X
	 *     ---          --------------------
	 *     主  释 设  从机    主机
	 *     机  放 置  向数据  读数据线
	 *         总 SDA 线写    上的数据
	 *         线 输  数据
	 *            入
	 * */
    uint8_t ack;
    IIC_SCL_L;
    delay_us();
    IIC_SDA_H;//释放总线
    SET_SDA_IN;
    delay_us();
	delay_us();		// 等待从机向数据线上写入应答信号或者非应答信号
    IIC_SCL_H;
    delay_us();
    if(IIC_SDA_READ){
        ack=1;
    }else{
        ack=0;
    }
    delay_us();
    IIC_SCL_L;
    delay_us();

    return ack;
}
void iic_send_ack(){
    /*            --------
	 * SCL       /        \
	 *    -------          ------
	 *    ---
	 * SDA   X
	 *    --- -------------
	 *    在第九个时钟周期的低电平期间,接收器向数据线写入数据,
	 *    在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
	 *    如果读到低电平表示应答信号
	 * */
    SET_SDA_OUT;
    IIC_SCL_L;
    delay_us();
    IIC_SDA_L;
    delay_us();
    IIC_SCL_H;
    delay_us();
    delay_us();//等待接收器读走数据
    IIC_SCL_L;//释放,可以继续写入数据
    delay_us();
}
void iic_send_nack(){
    /*            --------
	 * SCL       /        \
	 *    -------          ------
	 *    --- ---------------
	 * SDA   X
	 *    ---
	 *    在第九个时钟周期的低电平期间,接收器向数据线写入数据,
	 *    在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
	 *    如果读到高电平表示非应答信号
	 * */
    SET_SDA_OUT;
    IIC_SCL_L;
    delay_us();
    IIC_SDA_H;//非应答
    IIC_SCL_H;
    delay_us();
    delay_us();
    IIC_SCL_L;
    delay_us();
}
void iic_write_1byte(uint8_t data){
    /*
	 * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
	 * 			时钟在高电平期间,接收器从数据线上读取数据
	 *      ----          --------
	 * 	SCL     \        /        \
	 *           --------          --------
	 *      -------- ------------------ ---
	 * 	SDA         X                  X
	 *      -------- ------------------ ---
	 *      先发送高位在发送低位
	 * */
    int i=0;
    SET_SDA_OUT;
    //传数  先传数据高位再传低位
    for(;i<8;i++){
        IIC_SCL_L;
        delay_us();
        if(data & 0x80){//取出数据并为高电平
            IIC_SDA_H;
        }else{
            IIC_SDA_L;
        }
        delay_us();
		delay_us(); // 等待从机从数据线上读取数据
        data=data<<1;
    }
}
uint8_t iic_read_1byte(uint8_t ack){
    uint8_t data=0;
    int i=0;
    SET_SDA_IN;
    for(;i<8;i++){
        IIC_SCL_L; // SCL拉低
		delay_us();
		delay_us(); // 等待从机向数据线上写入数据
        IIC_SCL_H;
        delay_us();
        data <<=1;
        if(IIC_SDA_READ){//读并为高电平
            data |=1;
        }else{
            data |=0;
        }
        delay_us();
    }
    //主机根据情况发送应答或非应答
    if(!ack){
        iic_send_ack();
    }else{
        iic_send_nack();
    }
    return data;
}

建立芯片的文件,单独完成芯片的功能

4.3.2 SI7006的驱动代码

1. SI7006初始化的函数
2. 测量湿度并读取湿度的函数
3. 测量温度并读取温度的函数

si7006.h

 将要用的命令码定义为宏,

精度按照寄存器里写的配置

#ifndef _SI7006_H
#define _SI7006_H

#define HULMI 0xE4         //测量湿度
#define TEMPRATURE 0xE5   //测量温度
#define WRTIE 0xE6        //向si7006写
#define READ 0xE7         //从si7006读
#define WRITE_USER_REG_VALUE  0x3A//设置精度,也是初始化用户寄存器需要向芯片中写入的值
#define slaveaddr 0x40

void si7006_init();
unsigned char si7006_read_hul(unsigned char Slaveaddr,unsigned char CMD);
unsigned char si7006_read_tem(unsigned char Slaveaddr,unsigned char CMD);


#endif /*_SI7006_H*/

si7006.c

芯片手册里已经写好了如何读取温湿度,按照手册里给的时序图写即可

向寄存器中写入

从寄存器读 

 

#include "../include/si7006.h"
#include "../include/iic.h"
extern void delay_ms(unsigned int ms);
void si7006_init(){
    //要设置精度,12位的温度,14位湿度,禁止加热器
    //按照si7006芯片手册里的过程写
    //不同模式代码不一样
    iic_init();//初始化iic
    iic_start();
    iic_write_1byte(slaveaddr<<1);
    iic_wait_ack();
    iic_write_1byte(WRTIE);
    iic_wait_ack();
    iic_write_1byte(WRITE_USER_REG_VALUE);
    iic_send_ack();
    iic_stop();
}
unsigned char si7006_read_hul(unsigned char Slaveaddr,unsigned char CMD){
    unsigned char dat_h,dat_l;
    unsigned short data;
    iic_start();
    iic_write_1byte(slaveaddr<<1);
    iic_wait_ack();
    iic_write_1byte(CMD);
    iic_wait_ack();
    iic_start();
    iic_write_1byte((slaveaddr<<1)|1);
    iic_wait_ack();
    delay_ms(100);
    dat_h=iic_read_1byte(0);
    dat_l=iic_read_1byte(1);
    iic_stop();

    data = dat_h;
	data = data << 8 | dat_l;

	return data;
}
unsigned char si7006_read_tem(unsigned char Slaveaddr,unsigned char CMD){
    unsigned char dat_h,dat_l;
    unsigned short data;
    iic_start();
    iic_write_1byte(slaveaddr<<1);
    iic_wait_ack();
    iic_write_1byte(CMD);
    iic_wait_ack();
    iic_start();
    iic_write_1byte((slaveaddr<<1)|1);
    iic_wait_ack();
    delay_ms(100);
    dat_h=iic_read_1byte(0);
    dat_l=iic_read_1byte(1);
    iic_stop();

    data = dat_h;
	data = data << 8 | dat_l;

	return data;
}

main.c

main中用定时器实现一秒打印一次温湿度,

还需将芯片测得的模拟量通过攻是转换为数据量

#include "include/led.h"
#include "include/key.h"
#include "include/uart4.h"
#include "include/beep.h"
#include "include/fan.h"
#include "include/motor.h"
#include "include/interrupt.h"
#include "include/timer_it.h"
#include "include/si7006.h"

extern void printf(const char *fmt, ...);
extern int tim_flag;
void delay_ms(unsigned int ms){
        int i, j;
        for (i = 0; i < ms; i++)
            for (j = 0; j < 1800; j++)
                ;
    }

int main(){
    short temp;
	unsigned short hum;
	
	hal_timer_IT_init();
	hal_gic_init(61, 10);
	si7006_init();

    while (1){
		if(tim_flag==1){
			tim_flag=0;
			temp = si7006_read_tem(slaveaddr, TEMPRATURE);
			hum = si7006_read_hul(slaveaddr,HULMI);
			temp = (temp * 175.72 / 65536.0 - 46.85) * 100;
			hum = (hum * 125.0 / 65536.0 - 6.0) * 100;
			printf("temp = %d.%d\n", temp/100, temp%100);
			printf("hum = %d.%d\n", hum/100, hum%100);
		}
    }
        
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值