arm-day8

一、IIC总线的基本概念:

iic总线是一种带应答的同步的、串行、半双工的通信方式,支持一个主机对应多个从机。

二、IIC总线的通信流程:

当主机向从机发送数据时:

1.主机需要发送一位起始位(时钟线为高电平,数据线产生下降沿),标志着要开始发送数据

2.然后主机发送 7位 的从机地址和 1位 的写标志(0)

3.此时从机需要回应一个应答信号,表示收到了主机发送数据的请求

4.主机发送 8位 从机的寄存器地址,告诉从机发送的数据保存在哪里

5.从机再次回应一个应答信号

6.主机发送 8位 的数据

7.从机回应一个应答信号

8.此时发送完一个字节的数据,如果需要继续发送数据,重复6、7步

9.当不需要发送数据时,主机发起终止信号(时钟线为高电平,数据线产生上升沿),表示停止发送数据

当主机向从机读取数据时:

1.主机需要发送一位起始位,标志着要开始发送数据

2.然后主机发送 7位 的从机地址和 1位 的写标志(0)

3.此时从机需要回应一个应答信号,表示收到了主机发送数据的请求

4.主机发送 8位 从机的寄存器地址,告诉从机发送的数据保存在哪里

5.从机再次回应一个应答信号

6.主机发起一个重复的起始信号

7.然后主机发送 7位 的从机地址和 1位 的读标志(1)

8.从机回应一个应答信号

9.从机可以发送 8位 数据

10.此时主机可以回应一个应答信号或者非应答信号,表示读取多少数据

11.当主机不再读取数据时,上一步回应非应答信号后,再次发起终止信号,表示停止读取数据

main.c:

cpp 复制代码
#include "si7006.h"
 
int main()
{
    //si7006初始化
    si7006_init();
	//风扇/马达的初始化
	led_init();
    unsigned short hum;
    short tem;
    while(1)
    {
        //读取温度和湿度
        hum=si7006_read_hum();
        tem=si7006_read_tem();
        //计算温湿度数据
        hum=hum*125/65536-6;
        tem=tem*175.72/65536-46.85;
        printf("hum:%d\n",hum);
        printf("tem:%d\n",tem);
        delay_ms(1000);
		if(tem>26)
		{
			CH1_CTRL(1);//开启风扇
		}
		else if(tem<=26)
		{
			CH1_CTRL(0);//关闭风扇
		}
		
		if(hum > 65)
		{
			//开启震动马达
			//Motor_CTRL(1);
			GPIOF->ODR |= (0x1<<6);
		}
		else if(hum <= 65)
		{
			//关闭震动马达
			//Motor_CTRL(0);
			GPIOF->ODR &= (~(0x1<<6));
		}
    }
    return 0;
}
si7006.c:
cpp 复制代码
#include "si7006.h"
 
 
//手动封装延时函数
void delay_ms(int ms)
{
    int i,j;
    for(i=0;i<ms;i++)
    {
        for(j=0;j<2000;j++)
        {
 
        }
    }
}
 
void si7006_init()
{
    i2c_init();
    //1.发起起始信号
    i2c_start();
    //2.发送7bit从机地址和写标志位   0X80
    i2c_write_byte(0x80);
    //3.等待从机应答
    i2c_wait_ack();
    //4.发送寄存器地址 0XE6
    i2c_write_byte(0xe6);
    //5.等待从机应答
    i2c_wait_ack();
    //6.向从机发送数据  0X3A
    i2c_write_byte(0x3a);
    //7.等待从机应答
    i2c_wait_ack();
    //8.发送终止信号
    i2c_stop();
}
 
 
unsigned short si7006_read_hum()
{
    unsigned char hum_l,hum_h;
    unsigned short hum;
    
    
    // 1.主机发起起始信号
    i2c_start();
    // 2.主机发送7bit从机地址+1bit写标志
    i2c_write_byte(0x80);
    // 3.等待从机应答
    i2c_wait_ack();
    // 4.主机发送8bit寄存器地址
    i2c_write_byte(0xe5);
    // 5.等待从机应答
    i2c_wait_ack();
    // 6.主机发起重复起始信号
    i2c_start();
    // 7.主机发送7bit从机地址+1bit 读  0X81
    i2c_write_byte(0x81);
    // 8.等待从机应答
    i2c_wait_ack();
    // 9.延时等待从机测量数据
    delay_ms(1000);
    // 10.读取湿度的高8bit数据  hum_h
    // 11.发送应答信号
    hum_h = i2c_read_byte(0);
 
    // 12.读取湿度的低8位数据  hum_l 
    // 13.发送非应答信号
    hum_l = i2c_read_byte(1);
 
    // 14.发起终止信号
    i2c_stop();
    // 15.将读取到的数据的低8位和高8bit合成一个完整的数据
    hum=hum_h<<8|hum_l;
    return hum;
    
}
 
 
short si7006_read_tem()
{
    unsigned char tem_l,tem_h;
    unsigned short tem;
    
    // 1.主机发起起始信号
    i2c_start();
    // 2.主机发送7bit从机地址+1bit写标志
    i2c_write_byte(0x80);
    // 3.等待从机应答
    i2c_wait_ack();
    // 4.主机发送8bit寄存器地址
    i2c_write_byte(0xe3);
    // 5.等待从机应答
    i2c_wait_ack();
    // 6.主机发起重复起始信号
    i2c_start();
    // 7.主机发送7bit从机地址+1bit 读  0X81
    i2c_write_byte(0x81);
    // 8.等待从机应答
    i2c_wait_ack();
    // 9.延时等待从机测量数据
    delay_ms(1000);
    // 10.读取温度的高8bit数据 tem_h
    // 11.发送应答信号
    tem_h = i2c_read_byte(0);
 
    // 12.读取温度的低8位数据 tem_l
    // 13.发送非应答信号
    tem_l = i2c_read_byte(1);
 
    // 14.发起终止信号
    i2c_stop();
    // 15.将读取到的数据的低8位和高8bit合成一个完整的数据
    tem=tem_h<<8|tem_l;
    return tem;
    
}
si7006.h:
cpp 复制代码
#ifndef __SI7006_H__
#define __SI7006_H__
#include"iic.h"
#include "uart4.h"
 
void delay_ms(int ms);
void si7006_init();
unsigned short si7006_read_hum();
short si7006_read_tem();
 
 
 
#endif
i2c.c:
cpp 复制代码
#include "iic.h"
 
extern void printf(const char* fmt, ...);
/*
 * 函数名 : delay_us
 * 函数功能:延时函数
 * 函数参数:无
 * 函数返回值:无
 * */
void delay_us(void)  //微秒级延时
{
    unsigned int i = 2000;
    while(i--);
}
/*
 * 函数名 : i2c_init
 * 函数功能: i2C总线引脚的初始化, 通用输出,推挽输出,输出速度,
 * 函数参数:无
 * 函数返回值:无
 * */
void i2c_init(void)
{
    // 使能GPIOF端口的时钟
    RCC->MP_AHB4ENSETR |= (0x1 << 5);
    // 设置PF14,PF15引脚为通用的输出功能
    GPIOF->MODER &= (~(0xF << 28));
    GPIOF->MODER |= (0x5 << 28);
    // 设置PF14, PF15引脚为推挽输出
    GPIOF->OTYPER &= (~(0x3 << 14));
    // 设置PF14, PF15引脚为高速输出
    GPIOF->OSPEEDR |= (0xF << 28);
    // 设置PF14, PF15引脚的禁止上拉和下拉
    GPIOF->PUPDR &= (~(0xF << 28));
    // 空闲状态SDA和SCL拉高 
    I2C_SCL_H;
    I2C_SDA_H;
}
 
 
 
/*
 * 函数名:i2c_start
 * 函数功能:模拟i2c开始信号的时序
 * 函数参数:无
 * 函数返回值:无
 * */
void i2c_start(void)
{
    /*
     * 开始信号:时钟在高电平期间,数据线从高到低的变化
     *     --------
     * SCL         \
     *              --------
     *     ----
     * SDA     \
     *          --------
     * */   
    //确保SDA是输出状态 PF15输出
    SET_SDA_OUT;
    // 空闲状态SDA和SCL拉高 
    I2C_SCL_H;
    I2C_SDA_H;
    delay_us();//延时等待一段时间
    I2C_SDA_L;//数据线拉低
    delay_us();//延时等待一段时间
    I2C_SCL_L;//时钟线拉低,让总线处于占用状态
}
 
/*
 * 函数名:i2c_stop
 * 函数功能:模拟i2c停止信号的时序
 * 函数参数:无
 * 函数返回值:无
 * */
 
void i2c_stop(void)
{
    /*
     * 停止信号 : 时钟在高电平期间,数据线从低到高的变化 
     *             ----------
     * SCL        /
     *    --------
     *    ---         -------
     * SDA   X       /
     *    --- -------
     * */
    //确保SDA是输出状态 PF15输出
    SET_SDA_OUT;
    //时钟线拉低
    I2C_SCL_L;//为了修改数据线的电平
    delay_us();//延时等待一段时间
    I2C_SDA_L;//数据线拉低
    delay_us();//延时等待一段时间
    //时钟线拉高
    I2C_SCL_H;
    delay_us();//延时等待一段时间
    I2C_SDA_H;//数据线拉高
 
}
 
/*
 * 函数名: i2c_write_byte
 * 函数功能:主机向i2c总线上的从设备写8bits数据
 * 函数参数:dat : 等待发送的字节数据
 * 函数返回值: 无
 * */
 
void i2c_write_byte(unsigned char dat)
{  
    /*
     * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
     *          时钟在高电平期间,接收器从数据线上读取数据 
     *      ----          --------
     *  SCL     \        /        \
     *           --------          --------
     *      -------- ------------------ ---
     *  SDA         X                  X
     *      -------- ------------------ ---
     *
     *      先发送高位在发送低位 
     * */
    //确保SDA是输出状态 PF15输出
    SET_SDA_OUT;
    unsigned int i;
    for(i=0;i<8;i++)
    {
        //时钟线拉低
         I2C_SCL_L;
         delay_us();//延时
         //0X3A->0011 1010   0X80->10000000
         if(dat&0X80)//最高位为1
         {
            //发送1
            I2C_SDA_H;
         }
         else  //最高位为0
         {
            I2C_SDA_L;//发送0
         }
         delay_us();//延时
         //时钟线拉高,接收器接收
         I2C_SCL_H;
        delay_us();//延时,用于等待接收器接收数据
        delay_us();//延时
        //将数据左移一位,让原来第6位变为第7位
        dat = dat<<1;
 
    }
    
 
}
 
/*
 * 函数名:i2c_read_byte
 * 函数功能: 主机从i2c总线上的从设备读8bits数据, 
 *          主机发送一个应答或者非应答信号
 * 函数参数: 0 : 应答信号   1 : 非应答信号
 * 函数返回值:读到的有效数据
 *
 * */
unsigned char i2c_read_byte(unsigned char ack)
{
    /*
     * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
     *          时钟在高电平期间,接收器从数据线上读取数据 
     *      ----          --------
     *  SCL     \        /        \
     *           --------          --------
     *      -------- ------------------ ---
     *  SDA         X                  X
     *      -------- ------------------ ---
     *
     *      先接收高位, 在接收低位 
     * */
    unsigned int i;
    unsigned char dat;//保存接受的数据
    //将数据线设置为输入
    SET_SDA_IN;
    for(i=0;i<8;i++)
    {
        //先把时钟线拉低,等一段时间,保证发送器发送完毕数据
        I2C_SCL_L;
        delay_us();
        delay_us();//保证发送器发送完数据
        //时钟线拉高,读取数据
        I2C_SCL_H;
        delay_us();
        dat=dat<<1;//数值左移 一定要先左移在赋值,不然数据会溢出
        if(I2C_SDA_READ)//pf15管脚得到了一个高电平输入
        {
            dat |=1; //0000 0110
        }
        else
        {
            dat &=(~0X1);
        }
         delay_us();
    }
        if(ack)
        {
            i2c_nack();//发送非应答信号,不再接收下一次数据
        }
        else
        {
           i2c_ack();//发送应答信号 
        }
    return dat;//将读取到的数据返回
}
/*
 * 函数名: i2c_wait_ack
 * 函数功能: 主机作为发送器时,等待接收器返回的应答信号
 * 函数参数:无
 * 函数返回值:
 *                  0:接收到的应答信号
 *                  1:接收到的非应答信号
 * */
unsigned char i2c_wait_ack(void)
{
    /*
     * 主机发送一个字节之后,从机给主机返回一个应答信号
     *
     *                   -----------
     * SCL              /   M:读    \
     *     -------------             --------
     *     --- ---- --------------------
     * SDA    X    X
     *     ---      --------------------
     *     主  释   从机    主机
     *     机  放   向数据  读数据线
     *         总   线写    上的数据
     *         线   数据
     * */   
    //时钟线拉低,接收器可以发送信号
    I2C_SCL_L;
    I2C_SDA_H;//先把数据线拉高,当接收器回应应答信号时,数据线会拉低
    delay_us();
    SET_SDA_IN;//设置数据线为输入
    delay_us();//等待从机响应
    delay_us();
    I2C_SCL_H;//用于读取数据线数据
    if(I2C_SDA_READ)//PF15得到一个高电平输入,收到非应答信号
        return 1;
    I2C_SCL_L;//时钟线拉低,让数据线处于占用状态
    return 0;
    
} 
/*
 * 函数名: iic_ack
 * 函数功能: 主机作为接收器时,给发送器发送应答信号
 * 函数参数:无
 * 函数返回值:无
 * */
void i2c_ack(void)
{
    /*            --------
     * SCL       /        \
     *    -------          ------
     *    ---
     * SDA   X 
     *    --- -------------
     * */
    //保证数据线是输出
    SET_SDA_OUT;
    I2C_SCL_L;//拉低时钟线
    delay_us();
    I2C_SDA_L;//数据线拉低,表示应答信号
    delay_us();
    I2C_SCL_H;//时钟线拉高,等待发送器读取应答信号
    delay_us();//让从机读取我们当前的回应
    delay_us();
    I2C_SCL_L;//数据线处于占用状态,发送器发送下一次数据
 
}
/*
 * 函数名: iic_nack
 * 函数功能: 主机作为接收器时,给发送器发送非应答信号
 * 函数参数:无
 * 函数返回值:无
 * */
void i2c_nack(void)
{
    /*            --------
     * SCL       /        \
     *    -------          ------
     *    --- ---------------
     * SDA   X 
     *    --- 
     * */   
    //保证数据线是输出
    SET_SDA_OUT;
    I2C_SCL_L;//拉低时钟线
    delay_us();
    I2C_SDA_H;//数据线拉高,表示非应答信号
    delay_us();
    I2C_SCL_H;//时钟线拉高,等待发送器读取应答信号
    delay_us();
    delay_us();
    I2C_SCL_L;//数据线处于占用状态,发送器发送下一次数据
}
 
 
相关推荐
松涛和鸣3 小时前
DAY63 IMX6ULL ADC Driver Development
linux·运维·arm开发·单片机·嵌入式硬件·ubuntu
想放学的刺客6 小时前
单片机嵌入式试题(第23期)嵌入式系统电源管理策略设计、嵌入式系统通信协议栈实现要点两个全新主题。
c语言·stm32·单片机·嵌入式硬件·物联网
猫猫的小茶馆6 小时前
【Linux 驱动开发】五. 设备树
linux·arm开发·驱动开发·stm32·嵌入式硬件·mcu·硬件工程
jghhh017 小时前
基于上海钜泉科技HT7017单相计量芯片的参考例程实现
科技·单片机·嵌入式硬件
恶魔泡泡糖8 小时前
51单片机外部中断
c语言·单片机·嵌入式硬件·51单片机
意法半导体STM328 小时前
【官方原创】如何基于DevelopPackage开启安全启动(MP15x) LAT6036
javascript·stm32·单片机·嵌入式硬件·mcu·安全·stm32开发
v_for_van8 小时前
STM32低频函数信号发生器(四通道纯软件生成)
驱动开发·vscode·stm32·单片机·嵌入式硬件·mcu·硬件工程
电化学仪器白超8 小时前
③YT讨论
开发语言·python·单片机·嵌入式硬件
乡野码圣9 小时前
【RK3588 Android12】硬件中断IRQ
单片机·嵌入式硬件
happygrilclh9 小时前
数码管驱动(一):ET6226M -数据手册主要点分析
单片机·嵌入式硬件