前言

环境

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接口实现以下功能:

  1. 显示自己的学号和姓名;

  2. 显示AHT20的温度和湿度;

  3. 上下或左右的滑动显示长字符,比如“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的SDA
void SDA_Pin_Output_Low(void);  //将P15配置为输出  并设置为低电平
void SDA_Pin_IN_FLOATING(void);  //SDA配置为浮空输入
void SCL_Pin_Output_High(void); //SCL输出高电平,P14作为I2C的SCL
void 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是否有回复ACK
void Send_ACK(void) ;     //主机回复ACK信号
void Send_NOT_ACK(void);    //主机不回复ACK
void 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的传输大概可以分为以下几个过程:

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

一主多从模式

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

第一种方法:多NSS

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

第二种方法:菊花链

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

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

具体的连接如下图所示;

最终的数据流向为:

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显示相关推荐

  1. 【嵌入式基础】基于IIC和SPI协议的温湿度采集与OLED显示

    本文主要介绍IIC总线通信协议和SPI协议,并使用STM32系列芯片基于IIC协议实现AHT20温湿度传感器上位机数据采集,基于SPI协议实现OLED显示. 目录 一.IIC总线通信协议 1.IIC协 ...

  2. 【嵌入式08】基于IIC和SPI协议的温湿度采集与OLED显示

    文章目录 一.温湿度采集 1.I2C总线协议 2.电路连接 3.代码编写 4.烧录输出 二.OLED显示 1.SPI和OLED介绍 2.电路连接 3.汉字取模 4.代码编写 5.烧录输出 三.总结 四 ...

  3. 基于I2C/SPI总线的温湿度采集与OLED显示

    实验一 实验目的 学习I2C总线通信协议,使用STM32F103完成基于I2C协议的AHT20温湿度传感器的数据采集,并将采集的温度-湿度值通过串口输出.具体任务: 1)解释什么是"软件I2 ...

  4. 基于 SPI 协议的0.96 寸OLED显示

    目录 一. SPI 协议 1.简介 2.SPI的连接方式 3.通信过程​ 二.OLED 1.OLED原理 2.点阵编码原理与显示 三.OLED显示实验 Demo 程序 1.要求 2. OLED 显示汉 ...

  5. 基于I2C/SPI的温湿度采集与OLED显示

    目录 一.温湿度采集 1.了解I2C总线协议 2.实现AHT20采集程序 3.温湿度的OLED(4SPI)显示 二.OLED显示 1.了解SPI(串行外设接口) 2.使用0.96寸OLED显示屏显示数 ...

  6. 基于IIC和SPI协议的OLED显示(STM32)

    文章目录 一.介绍SPI 二.使用OLED显示屏显示数据 1.介绍OLED 2.连接开发板和显示屏 3.运行结果 三.修改程序实现显示 四.结果展示 五.参考 一.介绍SPI SPI(Serial P ...

  7. STM32通过I2C接口实现温湿度(AHT20)的采集与OLED显示及显示姓名学号

    文章目录 一.了解I2C总线协议 二.实现AHT20采集程序 三.温湿度采集--OLED显示 三.总结 四.参考链接 一.了解I2C总线协议 1.什么是I2C协议 I2C 通讯协议(Inter-Int ...

  8. 基于stm32、spi协议的Fatfs文件系统移植(附完整代码下载)

    开发环境:Window 7 32bit 开发工具:Keil uVision4 硬件:stm32f103vct6 目录 1.硬件设计: 2.软件设计 1.SPI收发数据 2.向SD卡发送的命令格式: 3 ...

  9. 用STM32F103达成基于I2C协议的AHT20温湿度传感器和OLED屏显示汉字

    目录 一 I2C协议简介 硬件I2C与软件I2C 二 代码和ATH20芯片实现温湿度的串口显示 三 用stm32f103芯片的SPI和IIC接口接上OLED屏显示中文姓名温湿度 1 先用文字字模生成器 ...

最新文章

  1. 云计算机运行内存,电脑内存,云服务器内存最深刻的解读!
  2. Vista OS 中添加网络中的非Vista OS共享的打印机
  3. WINCE下SOS驱动开发
  4. c++11 移动语义move semantics
  5. 基于用户投票的排名算法(一):Delicious和Hacker News
  6. 各大网站猪年新春应景LOGO秀
  7. mongo php 自增,PHP7下MongoDB自增或自减一个字段的值
  8. 请使用webdav_介绍下phpdav的使用功能价值
  9. 一个优质软件测试工程师简历的范文(答应我一定要收藏起来)
  10. ppc手机用蓝牙和电脑同步上网设置教程
  11. 聊聊 Xcode 编译 ToolChain
  12. Macmini 2012Late硬盘异响和Mac下设置apm
  13. 如何注册域名邮箱?个人域名邮箱怎么弄?域名邮箱登录入口?
  14. 【经典产品思维】引领“用户消费”的产品怎么做?
  15. 福特汉姆大学计算机科学专业,福特汉姆大学计算机科学专业
  16. 服务器没有显示器能接笔记本吗,笔记本能连显示器吗,笔记本怎么才能接显示器(图文)...
  17. 1481: 考试排名(一)(结构体专题)
  18. 重装系统打开Unity编辑器弹窗:sentinel key not found(h0007)
  19. 看过《非你莫属》那期,因为刘俐俐,说说陈鸥
  20. 华为手机如何设置微信来消息自动亮屏?

热门文章

  1. 【计算机毕业设计】java jsp+ssm大学校园宿舍零食便利店系统
  2. Python 数据分析概述
  3. matlab 中conv2、filter2、imfilter的区别
  4. javax.accessibility
  5. 树莓派vga转hdmi显示黑屏,闪烁
  6. 【前端三剑客】JavaScript 网页脚本语言(速览)
  7. windows下快速实现labelImg环境配置
  8. 【高速接口-RapidIO】2、RapidIO串行物理层的包与控制符号
  9. IBM IEEE 1394黄色感叹号
  10. 一个密码本(ACodebook)使用说明