糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > 基于IIC和SPI协议的温湿度采集与OLED显示

基于IIC和SPI协议的温湿度采集与OLED显示

时间:2019-01-19 14:59:00

相关推荐

基于IIC和SPI协议的温湿度采集与OLED显示

前言

环境

KEIL:5.0

FIyMcu:V0.993

开发板:STM32F103C8T6

器件

AHT-20 温湿度传感器

0.96英寸OLED屏幕

任务1内容

学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出。具体任务:

1)解释什么是“软件I2C”和“硬件I2C”?

2)阅读AHT20数据手册,编程实现:每隔2秒钟采集一次温湿度数据,并通过串口发送到上位机(win10)。

任务2内容

理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:

显示自己的学号和姓名;

显示AHT20的温度和湿度;

上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(使用硬件刷屏模式)。

环境

MDK-KEIL 5

FIyMcu:V0.993

开发板:STM32F103C8T6

任务1

I2C通讯

I2C通讯协议(Inter-Integrated Circuit)引脚少,硬件实现简单,可扩展性强,不需要USART、CAN等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路(IC)间的通讯实现I2C需要两根信号线完成信息交换,SCL(Serial Clock)时钟信号线,SDA(Serial Data)数据输入/输出线。它属于同步通信,由于输入输出数据均使用一根线,因此通信方向为半双工。I2C最少只需要两根线,和异步串口类似,但可以支持多个slave设备。一个I2C理论上最多可挂载127个设备,但除去保留地址,最多可挂载112个设备。·多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。和SPI不同的是,I2C可以支持multi-master系统,允许有多个master并且每个master都可以与所有的slaves通信(master之间不可通过I2C通信,并且每个master只能轮流使用I2C总线)。I2C的数据传输速率位于串口和SPI之间,大部分I2C设备支持100KHz和400KHz模式。

硬件I2C

下图中是一组多从模型中协议的规定:

左边的CPU是单片机,作为总线的主机。主机能够对SCL线的完全控制。在空闲状态下,主机可以主动发起对SDA的控制。只有在从机发送数据和从机应答的时候,主机才会转交SDA控制权给从机。挂在在SCL线上的被控设备就是挂在在I2C总线上的从机。这些从机可以是姿态传感器、OLED、存储器、时钟模块等。从机的权利比较小,对于SCL时钟线,在任何时候都只能被动的读取,不被允许控制SCL线。对于SDA数据线,从机不允许主动发起对SDA的控制,只有在主机发送读取从机命令后或者从机应答的时候,从机才能短暂地取得SDA的控制权

硬件电路:

所有I2C设备的SCL线连在一起,SDA连在一起。所有I2C设备的SCL和SDA均要配置成开漏输出模式。这是为了防止两个引脚同时处于输出状态。导致电源短路。所以I2C禁止所有设备输出强上拉的高电平,采用外置弱上拉电阻的电路结构如上图。在SDA和SCL上各添加一个上拉电阻,阻值一般为4.7k欧左右。每一个设备(从机和从机)的引脚内部结构如下图,左边部分为SCL(即图中SCLK)的结构,右边部分为SDA(即图中DATA)结构,都采用了开漏输出的方式。开漏输出时,输出低电平时导通,是强下拉;输出高电平时下管断开,但是没有上管,此时引脚处于浮空状态。

软件I2C

I2C时序基本单元

在了解几个时序基本单元之前需要了解一下几点:

在I2C总线处于空闲状态时,SCL和SDA都是处于高电平状态

起始条件和终止条件

一个完整的数据帧都是以起始条件开始、终止条件结束。且起始条件和终止条件都是由主机产生的。在一主多从模型中,从机不允许产生起始条件和终止条件,所以在总线空闲状态时,从机不可以改变总线状态。

起始条件是指:SCL高电平期间,SDA从高电平切换到低电平。当主机捕获到SDA下降沿和SCL高电平状态时,会在SDA变为低电平后将SCL高电平转化为低电平。(这也保证了除起始条件和终止条件之外,每个时序单元的SCL都是以低电平开始低电平结束)

终止条件是指:SCL高电平期间,SDA从低电平切换到高电平。这个过程中SCL会先恢复到高电平,之后SDA在恢复到高电平产生的上升沿过程就是终止条件。(终止条件后SDA和SCL都是高电平,最终回到初始的空闲状态)

发送一个字节时序单元

发送一个字节:

SCL低电平期间,主机将数据位依次放到SDA线上(高位先行,串口是地位先行),然后释放SCL,从主机将SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程八次即可发送一个字节。

注意:

1、在SCL低电平期间SDA的数据是可以改变的。

2、如果主机想发送1,那么将SDA置为高电平;如果想发送0,那么将SDA置为低电平。

3、在SCL为高电平期间,就是从机读取数据的时候,此时SDA的状态不能够改变。

4、一般在上升沿过程中从机就已经完成了数据读取,因为从机不能判断下降沿,只能尽快读取。

5、主机需要在SCL下降沿之后(即SCL处于低电平的时候),下一个上升沿来临之前尽快把下一位数据放到SDA上准备好下一次传输。

6、若发生中断,SCL的时序会不断拉长,直到中断处理完成之后再回来继续进行处理。

7、该过程中,SDA和SCL全程都由主机掌控,从机只能被动读取。

接收一个字节

接收一个字节:在SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。

1、图中SDA为虚线表示从机在控制SDA

2、SCL还是由主机控制

发送应答和接收应答

发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

:若主机没有接收到数据,从机还是需要交出对SDA的控制权

接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接受之前需要释放SDA)

数据的发送结构

图中S表示上文提到的起始条件SLAVE ADDRESS 与R/W共同构成第一个字节,前七位是SLAVE ADDRESS(从机设备地址),用于寻找从机。最后一位是R/W位表示读或者写操作。

1、从机设备地址在I2C标准协议中分为7位地址和10位地址。本文讲解的是7位地址的模式。七位地址相对比较简单,且应用范围广。、

2、每个设备在出厂时,厂商都会为它分配一个7位地址,这个地址具体是什么应该在相应的设备手册中查找。

3、一般同一厂家生产的同一设备的设备地址相同。如AHT20温湿度传感器的设备地址是0111000。

4、如果有相同的设备挂载在同一总线时,就需要使用地址中可变部分了。一般器件地址的最后几位是可以在电路中改变的,通过改变器件上的引脚接入可以改变器件地址的最后几位,在不接任何地址引脚的情况下HT20温湿度传感器器件地址就是0111000。

图中A表示上文提到的接收应答。DATA表示按照上文发送一个字节或者接收一个字节方式进行数据的传输。P表示终止条件。

stm32通过I2C接口实现温湿度(AHT20)的采集

创建标准库

因为现有资源所提供的AHT20项目必须在标准库中进行,所以需要先创建一个标准库。

标准库的创建参考:江科大stm32新建工程

引入AHT20的资源文件

DHT20温湿度采集资源文件:点击下载

提取码:8888

将资源文件中的AHT20-21_DEMO_V1_3.h和AHT20-21_DEMO_V1_3.c两个文件复制到工程下的User目录下

注意:一定要根据创建标准库流程将User文件夹添加到资源文件路径中,否则引入的资源不可用

点击打开AHT20-21_DEMO_V1_3.h文件可以看到一些对AHT20进行操作的函数及函数说明。

void Delay_N10us(uint32_t t);//延时函数void SensorDelay_us(uint32_t t);//延时函数void Delay_4us(void);//延时函数void Delay_5us(void);//延时函数void Delay_1ms(uint32_t t);void AHT20_Clock_Init(void);//延时函数void SDA_Pin_Output_High(void) ; //将PB15配置为输出 , 并设置为高电平, PB15作为I2C的SDAvoid SDA_Pin_Output_Low(void); //将P15配置为输出 并设置为低电平void SDA_Pin_IN_FLOATING(void); //SDA配置为浮空输入void SCL_Pin_Output_High(void); //SCL输出高电平,P14作为I2C的SCLvoid SCL_Pin_Output_Low(void); //SCL输出低电平void Init_I2C_Sensor_Port(void); //初始化I2C接口,输出为高电平void I2C_Start(void); //I2C主机发送START信号void AHT20_WR_Byte(uint8_t Byte); //往AHT20写一个字节uint8_t AHT20_RD_Byte(void);//从AHT20读取一个字节uint8_t Receive_ACK(void); //看AHT20是否有回复ACKvoid Send_ACK(void); //主机回复ACK信号void Send_NOT_ACK(void);//主机不回复ACKvoid Stop_I2C(void); //一条协议结束uint8_t AHT20_Read_Status(void);//读取AHT20的状态寄存器void AHT20_SendAC(void); //向AHT20发送AC命令uint8_t Calc_CRC8(uint8_t *message,uint8_t Num);void AHT20_Read_CTdata(uint32_t *ct); //没有CRC校验,直接读取AHT20的温度和湿度数据void AHT20_Read_CTdata_crc(uint32_t *ct); //CRC校验后,读取AHT20的温度和湿度数据void JH_Reset_REG(uint8_t addr);///重置寄存器void AHT20_Start_Init(void);///上电初始化进入正常测量状态

编写程序

需要注意AHT20芯片的使用过程如下:

void read_AHT20_once(void){delay_ms(10);reset_AHT20();//重置AHT20芯片delay_ms(10);init_AHT20();//初始化AHT20芯片delay_ms(10);startMeasure_AHT20();//开始测试AHT20芯片delay_ms(80);read_AHT20();//读取AHT20采集的到的数据delay_ms(5);}

其中read_AHT20()代码如下

void read_AHT20(void){uint8_t i;for(i=0; i<6; i++){readByte[i]=0;}I2C_Start();//I2C启动I2C_WriteByte(0x71);//I2C写数据ack_status = Receive_ACK();//收到的应答信息readByte[0]= I2C_ReadByte();//I2C读取数据Send_ACK();//发送应答信息readByte[1]= I2C_ReadByte();Send_ACK();readByte[2]= I2C_ReadByte();Send_ACK();readByte[3]= I2C_ReadByte();Send_ACK();readByte[4]= I2C_ReadByte();Send_ACK();readByte[5]= I2C_ReadByte();SendNot_Ack();//Send_ACK();I2C_Stop();//I2C停止函数//判断读取到的第一个字节是不是0x08,0x08是该芯片读取流程中规定的,如果读取过程没有问题,就对读到的数据进行相应的处理if( (readByte[0] & 0x68) == 0x08 ){H1 = readByte[1];H1 = (H1<<8) | readByte[2];H1 = (H1<<8) | readByte[3];H1 = H1>>4;H1 = (H1*1000)/1024/1024;T1 = readByte[3];T1 = T1 & 0x0000000F;T1 = (T1<<8) | readByte[4];T1 = (T1<<8) | readByte[5];T1 = (T1*2000)/1024/1024 - 500;AHT20_OutData[0] = (H1>>8) & 0x000000FF;AHT20_OutData[1] = H1 & 0x000000FF;AHT20_OutData[2] = (T1>>8) & 0x000000FF;AHT20_OutData[3] = T1 & 0x000000FF;}else{AHT20_OutData[0] = 0xFF;AHT20_OutData[1] = 0xFF;AHT20_OutData[2] = 0xFF;AHT20_OutData[3] = 0xFF;printf("读取失败!!!");}//根据AHT20芯片中,温度和湿度的计算公式,得到最终的结果,通过串口显示printf("温度:%d%d.%d%d%d\n",T1/100,(T1/10)%10,T1%10,T1%100,T1%1000);printf("湿度:%d%d.%d%d%d\n\r",H1/100,(H1/10)%10,H1%10,H1%100,H1%1000);}

最后在main()函数中添加代码

int main(void){while(1){read_AHT20_once();}}

注意:需要重写printf

运行无误后进行烧录运行

接线方式如下:

运行结果

任务2

SPI协议介绍

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是由 Motorola 公司提出的一种高速的,全双工,同步的通信总线,被广泛地使用在 ADC、LCD 等设备与 MCU间要求通讯速率较高的场合。SPI总线系统可直接与各个厂家生产的多种标准外围器件连接,该接口一般使用4条线:串行时钟线(SCK)、主机输入/从机输出数据线MISO、主机输出/从机输入数据线MOST和低电平有效的从机选择线C/S(有的SPI接口芯片带有中断信号线INT或INT、有的SPI接口芯片没有主机输出/从机输入数据线MOSI)。

SPI特性

SPI总线包括4条逻辑线,定义如下:

MISO:Master input slave output 主机输入,从机输出(数据来自从机);

MOSI:Master output slave input 主机输出,从机输入(数据来自主机);

SCLK :Serial Clock 串行时钟信号,由主机产生发送给从机;

SS:Slave Select 片选信号,由主机发送,以控制与哪个从机通信,通常是低电平有效信号。

其他制造商可能会遵循其他命名规则,但是最终他们指的相同的含义。以下是一些常用术语;

MISO也可以是SIMO,DOUT,DO,SDO或SO(在主机端);MOSI也可以是SOMI,DIN,DI,SDI或SI(在主机端);NSS也可以是CE,CS或SSEL;SCLK也可以是SCK;

下图显示了单个主机和单个从机之间的典型SPI连接。产生时钟的一侧称为主机,另一侧称为从机,这就是一主一从模式。

注意:SPI是“全双工”(具有单独的发送和接收线路),因此可以在同一时间发送和接收数据,另外SPI的接收硬件可以是一个简单的移位寄存器。这比异步串行通信所需的完整UART要简单得多,并且更加便宜;

SPI的传输大概可以分为以下几个过程:

主机先将NSS信号拉低,这样保证开始接收数据;当接收端检测到时钟的边沿信号时,它将立即读取数据线上的信号,这样就得到了一位数据(1bit);由于时钟是随数据一起发送的,因此指定数据的传输速度并不重要,尽管设备将具有可以运行的最高速度(稍后我们将讨论选择合适的时钟边沿和速度)。主机发送到从机时:主机产生相应的时钟信号,然后数据一位一位地将从MOSI信号线上进行发送到从机;主机接收从机数据:如果从机需要将数据发送回主机,则主机将继续生成预定数量的时钟信号,并且从机会将数据通过MISO信号线发送;

一主多从模式

前面说到SPI总线必须有一个主机,可以有多个从机,那么具体连接到SPI总线的方法有以下两种:

第一种方法:多NSS

通常,每个从机都需要一条单独的SS线。

如果要和特定的从机进行通讯,可以将相应的NSS信号线拉低,并保持其他NSS信号线的状态为高电平;如果同时将两个NSS信号线拉低,则可能会出现乱码,因为从机可能都试图在同一条MISO线上传输数据,最终导致接收数据乱码。

具体连接方式如下图所示:

第二种方法:菊花链

在数字通信世界中,在设备信号(总线信号或中断信号)以串行的方式从一 个设备依次传到下一个设备,不断循环直到数据到达目标设备的方式被称为菊花链。

菊花链的最大缺点是因为是信号串行传输,所以一旦数据链路中的某设备发生故障的时候,它下面优先级较低的设备就不可能得到服务了;另一方面,距离主机越远的从机,获得服务的优先级越低,所以需要安排好从机的优先级,并且设置总线检测器,如果某个从机超时,则对该从机进行短路,防止单个从机损坏造成整个链路崩溃的情况;

具体的连接如下图所示;

最终的数据流向为:

SCK为时钟信号,8clks表示8个边沿信号; 其中D为数据,X为无效数据;

SPI通讯的优势:

全双工串行通信;高速数据传输速率。简单的软件配置;极其灵活的数据传输,不限于8位,它可以是任意大小的字;非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同)

SPI的缺点:没有硬件从机应答信号(主机可能在不知情的情况下无处发送);通常仅支持一个主设备;需要更多的引脚(与I2C不同);没有定义硬件级别的错误检查协议;与RS-232和CAN总线相比,只能支持非常短的距离;

OLED屏幕

OLED原理

OLED(OrganicLight-Emitting Diode),又称为有机电激光显示、有机发光半导体(OrganicElectroluminesence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。

点阵编码原理与显示

汉字点阵编码:

在汉字的点阵字库中,每个字节的每个位都代表一个汉字的一个点,每个汉字都是由一个矩形的点阵组成,0 代表没有点,1 代表有点,将 0 和 1 分别用不同颜色画出,就形成了一个汉字,常用的点阵矩阵有 1212, 1414, 16*16 三 种字库。

字库根据字节所表示点的不同有分为横向矩阵和纵向矩阵,目前多数的字库都是横向矩阵的存储方式(用得最多的应该是早期 UCDOS 字库),纵向矩阵一 般是因为有某些液晶是采用纵向扫描显示法,为了提高显示速度,于是便把字库 矩阵做成纵向,省得在显示时还要做矩阵转换。

**OLED点阵显示:**点阵屏像素按128列X64行组织,每一行128个像素单元的阴极是连接在一起,作为公共极(COM),每一列64个像素单元的阳极也连接在一起,作为一段(SEG)。行列交叉点上的LED就是一个显示单元,即一个像素。要点亮一个像素,只要在该像素所在列电极上加上正电压、行电极接地。同样,要驱动一整行图像,就需要同时把128列信号加载到列电极上,把该行行电极接地。该行显示时,其他63行均不能显示,其行电极应为高电平或悬空。

可见,整屏的显示,只能分时扫描进行,一行一行的显示,每次显示一行。行驱依次产生低电平扫描各行,列驱动读取显示数据依次加载到列电极上。扫描一行的时间称为行周期,完成一次全屏扫描,就叫做一帧。一般帧频大于60,人眼观察不到逐行显示。每行扫描显示用时叫占空比,占空比小,为达到相同的显示亮度,驱动电流就大。SSD1306段驱动最大电流为100uA,当整行128个像素全部点亮时,行电极就要流过12.8mA的电流。

OLED引脚功能如下:

OLED显示程序

需要下载 0.96 寸 OLED 显示屏厂家给出的 Demo 程序。 程序下载链接: 0.96 寸 SPI_OLED模块配套资料包。

下载资料包之后打开其中的0.96inch_SPI_OLED_Module_SSD1306_MSP096X_V1.0\1-Demo\Demo_STM32\0.96inch_OLED_Demo_STM32F103RCT6_Software_4-wire_SPI

汉字取模

打开取字模软件

点击新建图像,宽度高度都设置为16

输入需要驱魔的汉字按CTRL+Enter

取模方式选择C51取模,得到取模结果

显示自己的学号和姓名

打开 gui.c 下的 oledfont.h 头文件,将 cfont16[] 数组内的内容修改成自己的中文文字点阵

将 test.c 里 void TEST_MainPage(void) 函数中的语句注释掉,添加自己的执行语句

修改mian函数中的while循环

OLED屏幕接线如下:

编译烧录程序,运行情况如下:

左右滑动显示一段诗歌

跟前面一样,获取字模后,向 gui.c 下的 oledfont.h 头文件里的 cfont16[] 数组内的添加中文文字点阵,我这里添加了“大漠孤烟直、长河落日圆。”这 几个点阵

修改test.c 里 void TEST_MainPage(void) 函数如下

修改mian函数中的代码

编译烧录程序,显示结果为

显示AHT20的温度和湿度

先完成串口显示温湿度采集,详细操作看本文前面部分。

添加AHT相关文件到工程的HARDWARE目录下

打开工程,将AHT相关文件所在目录添加到工程中

成功后

修改AHT20-21_DEMO_V1_3.c中代码

#include "AHT20-21_DEMO_V1_3.h" void Delay_N10us(uint32_t t)//延时函数{uint32_t k;while(t--){for (k = 0; k < 2; k++);//110}}void SensorDelay_us(uint32_t t)//延时函数{for(t = t-2; t>0; t--){Delay_N10us(1);}}void Delay_4us(void)//延时函数{Delay_N10us(1);Delay_N10us(1);Delay_N10us(1);Delay_N10us(1);}void Delay_5us(void)//延时函数{Delay_N10us(1);Delay_N10us(1);Delay_N10us(1);Delay_N10us(1);Delay_N10us(1);}void Delay_1ms(uint32_t t)//延时函数{while(t--){SensorDelay_us(1000);//延时1ms}}//void AHT20_Clock_Init(void)//延时函数//{//RCC_APB2PeriphClockCmd(CC_APB2Periph_GPIOB,ENABLE);//}void SDA_Pin_Output_High(void) //将PB7配置为输出 , 并设置为高电平, PB7作为I2C的SDA{GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,& GPIO_InitStruct);GPIO_SetBits(GPIOB,GPIO_Pin_7);}void SDA_Pin_Output_Low(void) //将P7配置为输出 并设置为低电平{GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,& GPIO_InitStruct);GPIO_ResetBits(GPIOB,GPIO_Pin_7);}void SDA_Pin_IN_FLOATING(void) //SDA配置为浮空输入{GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init( GPIOB,&GPIO_InitStruct);}void SCL_Pin_Output_High(void) //SCL输出高电平,PB6作为I2C的SCL{GPIO_SetBits(GPIOB,GPIO_Pin_6);}void SCL_Pin_Output_Low(void) //SCL输出低电平{GPIO_ResetBits(GPIOB,GPIO_Pin_6);}void Init_I2C_Sensor_Port(void) //初始化I2C接口,输出为高电平{GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,& GPIO_InitStruct);GPIO_SetBits(GPIOB,GPIO_Pin_15);//输出高电平GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,& GPIO_InitStruct);GPIO_SetBits(GPIOB,GPIO_Pin_15);//输出高电平}void I2C_Start(void) //I2C主机发送START信号{SDA_Pin_Output_High();SensorDelay_us(8);SCL_Pin_Output_High();SensorDelay_us(8);SDA_Pin_Output_Low();SensorDelay_us(8);SCL_Pin_Output_Low();SensorDelay_us(8); }void AHT20_WR_Byte(uint8_t Byte) //往AHT20写一个字节{uint8_t Data,N,i;Data=Byte;i = 0x80;for(N=0;N<8;N++){SCL_Pin_Output_Low(); Delay_4us();if(i&Data){SDA_Pin_Output_High();}else{SDA_Pin_Output_Low();}SCL_Pin_Output_High();Delay_4us();Data <<= 1;}SCL_Pin_Output_Low();SensorDelay_us(8); SDA_Pin_IN_FLOATING();SensorDelay_us(8);}uint8_t AHT20_RD_Byte(void)//从AHT20读取一个字节{uint8_t Byte,i,a;Byte = 0;SCL_Pin_Output_Low();SDA_Pin_IN_FLOATING();SensorDelay_us(8);for(i=0;i<8;i++){SCL_Pin_Output_High();Delay_5us();a=0;if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)) a=1;Byte = (Byte<<1)|a;SCL_Pin_Output_Low();Delay_5us();}SDA_Pin_IN_FLOATING();SensorDelay_us(8);return Byte;}uint8_t Receive_ACK(void) //看AHT20是否有回复ACK{uint16_t CNT;CNT = 0;SCL_Pin_Output_Low();SDA_Pin_IN_FLOATING();SensorDelay_us(8);SCL_Pin_Output_High();SensorDelay_us(8);while((GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)) && CNT < 100) CNT++;if(CNT == 100){return 0;}SCL_Pin_Output_Low();SensorDelay_us(8);return 1;}void Send_ACK(void) //主机回复ACK信号{SCL_Pin_Output_Low();SensorDelay_us(8);SDA_Pin_Output_Low();SensorDelay_us(8);SCL_Pin_Output_High();SensorDelay_us(8);SCL_Pin_Output_Low();SensorDelay_us(8);SDA_Pin_IN_FLOATING();SensorDelay_us(8);}void Send_NOT_ACK(void)//主机不回复ACK{SCL_Pin_Output_Low();SensorDelay_us(8);SDA_Pin_Output_High();SensorDelay_us(8);SCL_Pin_Output_High();SensorDelay_us(8);SCL_Pin_Output_Low();SensorDelay_us(8);SDA_Pin_Output_Low();SensorDelay_us(8);}void Stop_I2C(void) //一条协议结束{SDA_Pin_Output_Low();SensorDelay_us(8);SCL_Pin_Output_High();SensorDelay_us(8);SDA_Pin_Output_High();SensorDelay_us(8);}uint8_t AHT20_Read_Status(void)//读取AHT20的状态寄存器{uint8_t Byte_first;I2C_Start();AHT20_WR_Byte(0x71);Receive_ACK();Byte_first = AHT20_RD_Byte();Send_NOT_ACK();Stop_I2C();return Byte_first;}uint8_t AHT20_Read_Cal_Enable(void) //查询cal enable位有没有使能{uint8_t val = 0;//ret = 0,val = AHT20_Read_Status();if((val & 0x68)==0x08)return 1;else return 0;}void AHT20_SendAC(void) //向AHT20发送AC命令{I2C_Start();AHT20_WR_Byte(0x70);Receive_ACK();AHT20_WR_Byte(0xac);//0xAC采集命令Receive_ACK();AHT20_WR_Byte(0x33);Receive_ACK();AHT20_WR_Byte(0x00);Receive_ACK();Stop_I2C();}//CRC校验类型:CRC8/MAXIM//多项式:X8+X5+X4+1//Poly:0011 0001 0x31//高位放到后面就变成 1000 1100 0x8c//C现实代码:uint8_t Calc_CRC8(uint8_t *message,uint8_t Num){uint8_t i;uint8_t byte;uint8_t crc=0xFF;for(byte=0; byte<Num; byte++){crc^=(message[byte]);for(i=8;i>0;--i){if(crc&0x80) crc=(crc<<1)^0x31;else crc=(crc<<1);}}return crc;}void AHT20_Read_CTdata(uint32_t *ct) //没有CRC校验,直接读取AHT20的温度和湿度数据{volatile uint8_t Byte_1th=0;volatile uint8_t Byte_2th=0;volatile uint8_t Byte_3th=0;volatile uint8_t Byte_4th=0;volatile uint8_t Byte_5th=0;volatile uint8_t Byte_6th=0;uint32_t RetuData = 0;uint16_t cnt = 0;AHT20_SendAC();//向AHT10发送AC命令Delay_1ms(80);//延时80ms左右cnt = 0;while(((AHT20_Read_Status()&0x80)==0x80))//直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态{SensorDelay_us(1508);if(cnt++>=100){break;}}I2C_Start();AHT20_WR_Byte(0x71);Receive_ACK();Byte_1th = AHT20_RD_Byte();//状态字,查询到状态为0x98,表示为忙状态,bit[7]为1;状态为0x1C,或者0x0C,或者0x08表示为空闲状态,bit[7]为0Send_ACK();Byte_2th = AHT20_RD_Byte();//湿度Send_ACK();Byte_3th = AHT20_RD_Byte();//湿度Send_ACK();Byte_4th = AHT20_RD_Byte();//湿度/温度Send_ACK();Byte_5th = AHT20_RD_Byte();//温度Send_ACK();Byte_6th = AHT20_RD_Byte();//温度Send_NOT_ACK();Stop_I2C();RetuData = (RetuData|Byte_2th)<<8;RetuData = (RetuData|Byte_3th)<<8;RetuData = (RetuData|Byte_4th);RetuData =RetuData >>4;ct[0] = RetuData;//湿度RetuData = 0;RetuData = (RetuData|Byte_4th)<<8;RetuData = (RetuData|Byte_5th)<<8;RetuData = (RetuData|Byte_6th);RetuData = RetuData&0xfffff;ct[1] =RetuData; //温度}void AHT20_Read_CTdata_crc(uint32_t *ct) //CRC校验后,读取AHT20的温度和湿度数据{volatile uint8_t Byte_1th=0;volatile uint8_t Byte_2th=0;volatile uint8_t Byte_3th=0;volatile uint8_t Byte_4th=0;volatile uint8_t Byte_5th=0;volatile uint8_t Byte_6th=0;volatile uint8_t Byte_7th=0;uint32_t RetuData = 0;uint16_t cnt = 0;// uint8_t CRCDATA=0;uint8_t CTDATA[6]={0};//用于CRC传递数组AHT20_SendAC();//向AHT10发送AC命令Delay_1ms(80);//延时80ms左右cnt = 0;while(((AHT20_Read_Status()&0x80)==0x80))//直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态{SensorDelay_us(1508);if(cnt++>=100){break;}}I2C_Start();AHT20_WR_Byte(0x71);Receive_ACK();CTDATA[0]=Byte_1th = AHT20_RD_Byte();//状态字,查询到状态为0x98,表示为忙状态,bit[7]为1;状态为0x1C,或者0x0C,或者0x08表示为空闲状态,bit[7]为0Send_ACK();CTDATA[1]=Byte_2th = AHT20_RD_Byte();//湿度Send_ACK();CTDATA[2]=Byte_3th = AHT20_RD_Byte();//湿度Send_ACK();CTDATA[3]=Byte_4th = AHT20_RD_Byte();//湿度/温度Send_ACK();CTDATA[4]=Byte_5th = AHT20_RD_Byte();//温度Send_ACK();CTDATA[5]=Byte_6th = AHT20_RD_Byte();//温度Send_ACK();Byte_7th = AHT20_RD_Byte();//CRC数据Send_NOT_ACK(); //注意: 最后是发送NAKStop_I2C();if(Calc_CRC8(CTDATA,6)==Byte_7th){RetuData = (RetuData|Byte_2th)<<8;RetuData = (RetuData|Byte_3th)<<8;RetuData = (RetuData|Byte_4th);RetuData =RetuData >>4;ct[0] = RetuData;//湿度RetuData = 0;RetuData = (RetuData|Byte_4th)<<8;RetuData = (RetuData|Byte_5th)<<8;RetuData = (RetuData|Byte_6th);RetuData = RetuData&0xfffff;ct[1] =RetuData; //温度}else{ct[0]=0x00;ct[1]=0x00;//校验错误返回值,客户可以根据自己需要更改}//CRC数据}void AHT20_Init(void) //初始化AHT20{Init_I2C_Sensor_Port();I2C_Start();AHT20_WR_Byte(0x70);Receive_ACK();AHT20_WR_Byte(0xa8);//0xA8进入NOR工作模式Receive_ACK();AHT20_WR_Byte(0x00);Receive_ACK();AHT20_WR_Byte(0x00);Receive_ACK();Stop_I2C();Delay_1ms(10);//延时10ms左右I2C_Start();AHT20_WR_Byte(0x70);Receive_ACK();AHT20_WR_Byte(0xbe);//0xBE初始化命令,AHT20的初始化命令是0xBE, AHT10的初始化命令是0xE1Receive_ACK();AHT20_WR_Byte(0x08);//相关寄存器bit[3]置1,为校准输出Receive_ACK();AHT20_WR_Byte(0x00);Receive_ACK();Stop_I2C();Delay_1ms(10);//延时10ms左右}void JH_Reset_REG(uint8_t addr){uint8_t Byte_first,Byte_second,Byte_third;I2C_Start();AHT20_WR_Byte(0x70);//原来是0x70Receive_ACK();AHT20_WR_Byte(addr);Receive_ACK();AHT20_WR_Byte(0x00);Receive_ACK();AHT20_WR_Byte(0x00);Receive_ACK();Stop_I2C();Delay_1ms(5);//延时5ms左右I2C_Start();AHT20_WR_Byte(0x71);//Receive_ACK();Byte_first = AHT20_RD_Byte();Send_ACK();Byte_second = AHT20_RD_Byte();Send_ACK();Byte_third = AHT20_RD_Byte();Send_NOT_ACK();Stop_I2C();Delay_1ms(10);//延时10ms左右I2C_Start();AHT20_WR_Byte(0x70);///Receive_ACK();AHT20_WR_Byte(0xB0|addr);寄存器命令Receive_ACK();AHT20_WR_Byte(Byte_second);Receive_ACK();AHT20_WR_Byte(Byte_third);Receive_ACK();Stop_I2C();Byte_second=0x00;Byte_third =0x00;}void AHT20_Start_Init(void){JH_Reset_REG(0x1b);JH_Reset_REG(0x1c);JH_Reset_REG(0x1e);}//int32_t main(void)//{// uint32_t CT_data[2];//volatile int c1,t1;///***********************************************************************************////**///①刚上电,产品芯片内部就绪需要时间,延时100~500ms,建议500ms///***********************************************************************************///Delay_1ms(500);///***********************************************************************************////**///②上电第一次发0x71读取状态字,判断状态字是否为0x18,如果不是0x18,进行寄存器初始化///***********************************************************************************///if((AHT20_Read_Status()&0x18)!=0x18)//{//AHT20_Start_Init(); //重新初始化寄存器//Delay_1ms(10);//}/////***********************************************************************************////**///③根据客户自己需求发测量命令读取温湿度数据,当前while(1)循环发测量命令读取温湿度数据,仅供参考///***********************************************************************************///while(1)//{// AHT20_Read_CTdata(CT_data); //不经过CRC校验,直接读取AHT20的温度和湿度数据 推荐每隔大于1S读一次// //AHT20_Read_CTdata_crc(CT_data); //crc校验后,读取AHT20的温度和湿度数据 //// c1 = CT_data[0]*100*10/1024/1024; //计算得到湿度值c1(放大了10倍)// t1 = CT_data[1]*200*10/1024/1024-500;//计算得到温度值t1(放大了10倍)//下一步客户处理显示数据,// }// }

修改main.c中代码

#include "delay.h"#include "sys.h"#include "oled.h"#include "gui.h"#include "test.h"#include "AHT20-21_DEMO_V1_3.h" //存放温度和湿度uint32_t CT_data[2]={0,0};//湿度和温度volatile int c1,t1;//用于LED显示的温度和湿度u8 temp[10]; u8 hum[10];//初始化PC13用于测试void GPIOC13_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_ResetBits(GPIOC,GPIO_Pin_13);}//初始化以及前期准备void Init(void);//读取温湿度void getData(void);//显示温湿度void showData(void);int main(void){//初始化Init();while(1){//获取数据getData();//显示数据showData();//开启滚动OLED_WR_Byte(0x2F,OLED_CMD);//延时Delay_1ms(3100);//OLED_Clear(0); }}//初始化以及前期准备void Init(void){//初始化PC12GPIOC13_Init();//延时函数初始化 delay_init(); //初始化OLED OLED_Init();//清屏(全黑)OLED_Clear(0); //开机显示信息GUI_ShowCHinese(10,0,16,"杨海东",1);GUI_ShowString(10,24,"631807060431",16,1);Delay_1ms(1000);AHT20_Init();/***********************************************************************************//**///①刚上电,产品芯片内部就绪需要时间,延时100~500ms,建议500ms/***********************************************************************************/Delay_1ms(1000);OLED_Clear(0); OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动OLED_WR_Byte(0x27,OLED_CMD); //水平向左或者右滚动 26/27OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节OLED_WR_Byte(0x00,OLED_CMD); //起始页 0OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔OLED_WR_Byte(0x02,OLED_CMD); //终止页 2OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节GUI_ShowCHinese(10,0,16,"王卓、",1);}//读取温湿度void getData(){//AHT20_Read_CTdata(CT_data); //不经过CRC校验,直接读取AHT20的温度和湿度数据 推荐每隔大于1S读一次AHT20_Read_CTdata_crc(CT_data);; //crc校验后,读取AHT20的温度和湿度数据 c1 = CT_data[0]*1000/1024/1024; //计算得到湿度值c1(放大了10倍)t1 = CT_data[1]*2000/1024/1024-500;//计算得到温度值t1(放大了10倍)//转为字符串易于显示temp[0]=t1/100+'0';temp[1]=(t1/10)%10+'0';temp[2]='.';temp[3]=t1%10+'0';temp[4]='\0';hum[0]=c1/100+'0';hum[1]=(c1/10)%10+'0';hum[2]='.';hum[3]=c1%10+'0';hum[4]=32;hum[5]='%';hum[6]='\0';}//显示温湿度void showData(){//显示温度GUI_ShowCHinese(15,28,16,"温度",1);GUI_ShowString(47,28,":",16,1);GUI_ShowString(62,28,temp,16,1);//显示湿度GUI_ShowCHinese(15,48,16,"湿度",1);GUI_ShowString(47,48,":",16,1);GUI_ShowString(62,48,hum,16,1);}

最后编译烧录,运行结果如下:

总结

参考文章

如果觉得《基于IIC和SPI协议的温湿度采集与OLED显示》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。