ARM_day8:基于iic总线的通信

ops/2024/9/25 13:10:42/

一、IIC总线的基本概念:

        iic总线是一种带应答的同步的、串行、半双工的通信方式,支持一个主机对应多个从机。它有一根SCL(时钟线)和一根SDA(数据线)组成,由于只有一根数据线,所以它是半双工的通信方式。

首先需要了解 主机和从机之间是如何通过 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.当主机不再读取数据时,上一步回应非应答信号后,再次发起终止信号,表示停止读取数据

三、GPIO模拟IIC实现温湿度传感器数据的读取

1.首先需要查看原理图找到对应的引脚,进行相关初始化的设置。

(1)查询RCC寄存器,使能GPIOF外设时钟

(2)查询GPIO数据手册,根据需要将相关引脚进行初始化设置

2.了解SI7006温湿度传感器的内部结构以及工作原理

查询SI7006数据手册,需要了解到以下几个方面的内容:

(1)如何初始化芯片

        通过数据手册了解到对应的寄存器地址,向寄存器地址中写入初始化的数值。

(2)芯片内部的工作原理

        SI7006内部有两个传感器,分别是温度传感器和湿度传感器,通过模数转换器,可以将二者采集到的模拟信号转换为数字信号,再由相关的寄存器存储,通过控制单元交给IIC接口,再通过IIC总线与外界进行交互

(3)查询作为IIC从机时的从机地址

        SI7006数据手册中已经写明,但要注意它是原本的从机地址,还是加上了R/W标志位后的数值

(4)采集温度和湿度的芯片寄存器

        根据数据手册查询到相关的寄存器命令码,主IIC设备可以使用命令结构与Si7006通信

(5)查询传感器采集的数据如何进行计算

        根据给定的公式可以将采集到的温湿度转换成通用的温湿度打印出来

相关代码如下:

main.c:
#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:
#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从机地址和写标志位   0X80i2c_write_byte(0x80);//3.等待从机应答i2c_wait_ack();//4.发送寄存器地址 0XE6i2c_write_byte(0xe6);//5.等待从机应答i2c_wait_ack();//6.向从机发送数据  0X3Ai2c_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 读  0X81i2c_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 读  0X81i2c_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:
#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:
#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->10000000if(dat&0X80)//最高位为1{//发送1I2C_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;//数据线处于占用状态,发送器发送下一次数据
}
i2c.h:
#ifndef __IIC_H__
#define __IIC_H__
#include "stm32mp1xx_gpio.h"
#include "stm32mp1xx_rcc.h"/* 通过程序模拟实现I2C总线的时序和协议* GPIOF ---> AHB4* I2C1_SCL ---> PF14* I2C1_SDA ---> PF15** */#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 I2C_SCL_H       do{GPIOF->BSRR |= (0x1 << 14);}while(0)
#define I2C_SCL_L       do{GPIOF->BRR |= (0x1 << 14);}while(0)#define I2C_SDA_H       do{GPIOF->BSRR |= (0x1 << 15);}while(0)
#define I2C_SDA_L       do{GPIOF->BRR |= (0x1 << 15);}while(0)#define I2C_SDA_READ    (GPIOF->IDR & (0x1 << 15))void delay_us(void);//微秒延时
void delay(int ms);
void i2c_init(void);//初始化
void i2c_start(void);//起始信号
void i2c_stop(void);//终止信号
void i2c_write_byte(unsigned char  dat);//写一个字节数据
unsigned char i2c_read_byte(unsigned char ack);//读取一个字节数据
unsigned char i2c_wait_ack(void);       //等待应答信号
void i2c_ack(void);//发送应答信号
void i2c_nack(void);//发送非应答信号#endif 


http://www.ppmy.cn/ops/5153.html

相关文章

【C++】vector的模拟实现

在这篇博客中&#xff0c;作者将会带领是简单的实现STL库中的vector。 一.vector的基本结构 vector是一个顺序存储的容器&#xff0c;也可以说是一个数组&#xff0c;那么对于它的结构&#xff0c;我们可以用三个迭代器来组成&#xff0c;如下图&#xff1a; 在vector当中&…

轻松上手MYSQL:MYSQL初识(下)

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《MYSQL入门》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;轻松上手MYSQL&#xff1a;MYSQL初识&a…

设计模式在芯片验证中的应用——策略

1. 策略模式 策略模式是一种行为设计模式&#xff0c; 它能让你定义一系列算法&#xff0c; 并将每种算法分别放入独立的类中&#xff0c; 以使算法的对象能够相互替换。 在RTL设计中可能包含了复杂的多个访问仲裁逻辑&#xff0c;使用了多种算法来确定访问内存优先级顺序&am…

Python爬虫:urllib库的基本使用

文章目录 一、urllib简介二、请求的发送和响应&#xff08;一&#xff09;发送请求&#xff08;二&#xff09;获取相应内容&#xff08;三&#xff09;下载所需文件 三、URL请求对象的定制四、常见请求方式&#xff08;一&#xff09;get请求方式&#xff08;二&#xff09;po…

打开电脑底部导航栏的任务

from pywinauto import Application app Application("uia").connect(path"explorer") app["任务栏"].print_control_identifiers() task app["任务栏"].child_window(title"酷狗音乐", auto_id"D:\soft\kugou\KGMus…

immutable variables, constants的区别(rust)

let immutable_variable 1590865; //不可变变量 const constant_variable 0096; //常量常量、不可变变量一样&#xff0c;都无法更改变量的值&#xff0c;但是常量和不可变变量之间存在以下区别&#xff1a; 不可变变量默认不可变&#xff0c;编译器会进行检查。常量不仅在默…

面向对象——类与对象

文章目录 类与对象构造函数、析构函数get/set方法函数&#xff1a;类内声明、类外定义static 类与对象 #include<iostream> #include<string> using namespace std; /* 类与对象 */ class Person{public:string name;// 固有属性&#xff0c;成员变量 int age;pu…

第一章:系统架构概述之系统架构的定义以及发展

什么是系统架构 系统架构 (System Architecture )是系统的一种整体的高层次的结构表示&#xff0c; 是系统的骨架和根基&#xff0c;也决定 了系统的健壮性和生命周期的长短。 什么系统架构设计师 系统架构设计师在整个项目研制中的主导地位愈加重要。可以说&#xff0c;系统架…