基于SPI的七针头OLED显示

一、实验目标

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

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

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

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

二、SPI协议以及OLED介绍

2.1 什么是SPI

SPI(Serial Peripheral Interface)是一种通用的串行外围设备接口,最早由Motorola公司在MC68HCXX系列处理器上定义。SPI接口在EEPROM、FLASH、实时时钟、AD转换器以及数字信号处理器和数字信号解码器之间得到广泛应用。

SPI接口使用四根通信线进行数据传输,它们分别是:

SCK(Serial Clock):串行时钟线,由主设备(例如微控制器)提供,用于同步数据传输的时钟信号。

MOSI(Master Output Slave Input):主设备输出从设备输入线,用于主设备向从设备发送数据。

MISO(Master Input Slave Output):主设备输入从设备输出线,用于从设备向主设备发送数据。

SS(Slave Select):从设备选择线,由主设备控制,用于选择与主设备进行通信的从设备。
以下为使用SPI协议常用

SPI主要存在以下特征

  1. 3线全双工同步传输:SPI使用SCK线作为时钟信号,MOSI线进行主设备到从设备的数据传输,MISO线进行从设备到主设备的数据传输,实现全双工通信。

  2. 带或不带第三根双向数据线的双线单工同步传输:SPI还支持只使用两根通信线进行单向数据传输。

  3. 8或16位传输帧格式选择:SPI允许选择8位或16位传输帧格式,根据应用需求进行灵活配置。

  4. 主或从操作:SPI接口可以配置为主设备模式或从设备模式,主设备控制通信进程,从设备响应主设备的请求。

  5. 支持多主模式:SPI支持多个主设备共享同一个总线,通过主设备的选择信号(SS线)选择与之通信的从设备。

  6. 8个主模式波特率预分频系数:SPI提供8个主模式的预分频系数,用于设定主设备的通信速率。

  7. 从模式频率:SPI从设备的工作频率最高可以达到主设备时钟频率的一半。

  8. 主模式和从模式的快速通信:SPI支持高速通信,最大传输速率可达18MHz。

  9. 主模式和从模式下可以由软件或硬件进行NSS管理:SPI接口支持动态切换主/从操作模式,并可以通过软件或硬件进行NSS(Slave Select)信号的管理。

  10. 可编程的时钟极性和相位:SPI接口支持时钟极性和相位的编程,以适应不同设备的通信需求。

  11. 可编程的数据顺序,MSB在前或LSB在前:SPI允许在传输过程中选择数据的顺序,可以按照Most Significant Bit(最高有效位)或Least Significant Bit(最低有效位)进行传输。

  12. 可触发中断的专用发送和接收标志:SPI接口提供特定的发送和接收标志,可以用于触发中断处理。

  13. SPI总线忙状态标志:SPI接口提供忙状态标志,指示当前总线是否正在进行数据传输。

  14. 支持可靠通信的硬件CRC:SPI支持硬件级别的CRC(循环冗余校验),可以在发送模式下将CRC值作为最后一个字节发送,并在全双工模式中对接收到的最后一个字节进行CRC校验。

  15. 可触发中断的主模式故障、过载和CRC错误标志:SPI接口提供故障、过载和CRC错误标志,可用于触发相应的中断处理。

  16. 支持DMA功能的1字节发送和接收缓冲器:SPI接口提供DMA功能,可以通过发送和接收缓冲器触发发送和接收请求,实现高效的数据传输。

2.2 SPI相关架构

SPI的硬件电路如下:
在这里插入图片描述
在这个配置中,所有的SPI设备都通过SCK、MOSI和MISO三个信号线连接在一起。这些信号线将所有设备同步到一个共享的主时钟,并用于在主机和从机之间发送和接收数据。

每个从机都被分配了一条单独的SS(Slave Select)控制线,这是主机用来选择与哪个从机进行通信的线路。当某条SS线被激活时,相应的从机就会被选中,然后主机就可以与其进行数据交换。

在输出引脚配置方面,它们被设置为推挽输出模式。这意味着当引脚被设置为输出模式时,它可以向外部电路提供电力,驱动电流,从而控制外部设备。

对于输入引脚,它们被配置为浮空或上拉输入模式。在浮空输入模式下,引脚不连接到任何特定的电平,因此其状态是由外部电路决定的。而在上拉输入模式下,引脚默认状态为高电平,只有当外部电路将其拉低时,它才会显示外部的电平状态。

这种配置提供了一种有效的方法来扩展主机与多个SPI设备之间的通信能力,同时保证了数据传输的稳定性和可靠性。
以下是SPI主机从机移位寄存器的布局
在这里插入图片描述
SPI移位寄存器是SPI接口通信的核心组成部分。SPI接口是一种高速的、全双工、同步的通信总线,在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间。

SPI传输通常涉及到两个给定了字长的移位寄存器。例如在主机、从机中的8bit的移位寄存器。它们以虚拟环形拓扑连接,数据通常先从最高有效位被移出。在时钟沿,主机和从机都移出1bit数据从传输线上给到对方。在下一个时钟边沿来到时,双方的接收器再对传输线上的该bit进行采样,并将其设置为移位寄存器的新的最低有效位。

2.3 SPI基本时序单元

起始条件与终止条件
在这里插入图片描述

  • 起始条件:SS从高电平切换到低电平
  • 终止条件:SS从低电平切换到i高电平

交换一个字节(模式0)

在这里插入图片描述
在SPI通信模式0下,通过CPOL(Clock Polarity)设置为0和CPHA(Clock Phase)设置为0的组合,可以实现交换一个字节的数据。

在这种模式下,空闲状态时,SCK(Serial Clock)信号线将保持低电平。这意味着在通信过程中,SCK信号将在低电平期间保持稳定,而在高电平期间进行数据传输。

当CPHA设置为0时,数据在SCK信号的第一个边沿被移入(或写入)到移位寄存器中,并在第二个边沿被移出(或读取)到移位寄存器中。这意味着在每个时钟周期内,数据仅在SCK信号的上升沿处被采样和传输,而在下降沿处保持稳定。

通过这种配置,SPI接口可以在每个时钟周期内有效地传输一个字节的数据。主机和从机之间的通信可以同步进行,确保数据的准确传输。这种配置适用于高速、全双工的通信应用,其中数据在每个时钟周期内被精确传输。
交换一个字节(模式1)
在这里插入图片描述

在SPI通信模式1下,通过CPOL(Clock Polarity)设置为0和CPHA(Clock Phase)设置为1的组合,可以实现交换一个字节的数据。

在这种模式下,空闲状态时,SCK(Serial Clock)信号线同样保持低电平。在通信过程中,SCK信号在低电平期间保持稳定,而在高电平期间进行数据传输。

然而,当CPHA设置为1时,数据在SCK信号的第一个边沿被移出(或读取)到移位寄存器中,并在第二个边沿被移入(或写入)到移位寄存器中。这意味着在每个时钟周期内,数据仅在SCK信号的下降沿处被采样和传输,而在上升沿处保持稳定。

通过这种配置,SPI接口可以在每个时钟周期内同样有效地传输一个字节的数据。但请注意,这种配置与模式0下的传输顺序相反。在模式1下,从机在每个时钟周期的开始时刻发送数据,而主机在第二个边沿接收数据。这种配置适用于高速、全双工的通信应用,其中需要实现反向传输或特定的数据采样顺序。

交换一个字节(模式2)
在这里插入图片描述

在SPI通信模式2下,通过CPOL(Clock Polarity)设置为1和CPHA(Clock Phase)设置为0的组合,可以实现交换一个字节的数据。

在这种模式下,空闲状态时,SCK(Serial Clock)信号线将保持高电平。这意味着在通信过程中,SCK信号将在高电平期间保持稳定,而在低电平期间进行数据传输。

当CPHA设置为0时,数据在SCK信号的第一个边沿被移入(或写入)到移位寄存器中,并在第二个边沿被移出(或读取)到移位寄存器中。这意味着在每个时钟周期内,数据仅在SCK信号的下降沿处被采样和传输,而在上升沿处保持稳定。

通过这种配置,SPI接口也可以在每个时钟周期内有效地传输一个字节的数据。在这种模式下,主机和从机之间的通信可以同步进行,确保数据的准确传输。这种配置适用于高速、全双工的通信应用,其中数据在每个时钟周期内被精确传输。

交换一个字节(模式3)
在这里插入图片描述

在SPI通信模式3下,通过CPOL(Clock Polarity)设置为1和CPHA(Clock Phase)设置为1的组合,可以实现交换一个字节的数据。

在这种模式下,空闲状态时,SCK(Serial Clock)信号线同样保持高电平。在通信过程中,SCK信号在每个时钟周期内都保持高电平,只有在数据传输时才发生电平变化。

当CPHA设置为1时,数据在SCK信号的第一个边沿被移出(或读取)到移位寄存器中,并在第二个边沿被移入(或写入)到移位寄存器中。这意味着在每个时钟周期内,数据仅在SCK信号的上升沿处被采样和传输,而在下降沿处保持稳定。

通过这种配置,SPI接口也可以在每个时钟周期内传输一个字节的数据。但请注意,在这种模式下,数据传输的方向与前两种模式相反。在模式3下,从机在每个时钟周期的开始时刻发送数据,而主机在第二个边沿接收数据。这种配置适用于特定的通信应用,其中需要实现反向传输或特定的数据采样顺序。

2.4 OLED元件介绍

OLED介绍:
OLED,也称为有机电激光显示或有机发光半导体(Organic Electroluminesence Display, OLED),是一种电流型的有机发光器件。它通过载流子的注入和复合导致发光现象,其发光强度与注入的电流成正比。在OLED中,当电场作用时,阳极产生的空穴和阴极产生的电子会向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。

基于SPI协议的7排针OLED实物图
在这里插入图片描述
在这里插入图片描述

OLED上点阵编码原理与显示 :
(1)点阵编码:
点阵编码是汉字在OLED显示的一种方式。在汉字的点阵字库中,每个字节的每个位都代表一个汉字的一个点。0代表没有点,1代表有点。将0和1分别用不同颜色画出,就形成了一个汉字。常用的点阵矩阵有1212, 1414, 16*16三种字库。字库根据字节所表示点的不同有分为横向矩阵和纵向矩阵,目前多数的字库都是横向矩阵的存储方式。
(2)点阵显示:
OLED的像素按照128列X64行组织。每一行128个像素单元的阴极是连接在一起作为公共极(COM),每一列64个像素单元的阳极也连接在一起作为一段(SEG)。行列交叉点上的LED就是一个显示单元,即一个像素。要点亮一个像素,只要在该像素所在列电极上加上正电压、行电极接地。同样,要驱动一整行图像,就需要同时把128列信号加载到列电极上,把该行行电极接地。该行显示时,其他63行均不能显示,其行电极应为高电平或悬空。因此,整屏的显示需要分时扫描进行,一行一行的显示,每次显示一行。完成一次全屏扫描称为一帧。一般帧频大于60,人眼观察不到逐行显示。每行扫描显示用时称为占空比,占空比小,为达到相同的显示亮度,驱动电流就大。SSD1306段驱动最大电流为100uA,当整行128个像素全部点亮时,行电极就要流过12.8mA的电流。

三、任务一,显示姓名及学号

3.1 生成字模,找到工程

首先我们需要在网上获取相关OLED的代码,以及汉字取模软件。这些都是现成封装好的,我们直接使用就可以了。
链接: http://www.lcdwiki.com/res/Program/OLED/0.96inch/SPI_SSD1306_MSP096X_V1.0/0.96inch_SPI_OLED_Module_SSD1306_MSP096X_V1.0.zip
这里我就再给一个取模软件的链接
链接:https://pan.baidu.com/s/1ZSrDvhP-mwLQB9F0X_uLlQ
提取码:1234
解压后打开取模软件
在这里插入图片描述
第一个文件里
在这里插入图片描述
在进入主界面后,点击设置
在这里插入图片描述
按照图中选项进行设置
在这里插入图片描述
点击确定好后,在主界面就可以输入你想要的汉字了,文字下方是每个文字对应的C51字模代码,我们将其复制粘贴到代码中
在这里插入图片描述
随后我们打开我们的OLED工程,注意路径
在这里插入图片描述
首先,由于给到的代码用的芯片是RCT6,我们要改为C8T6,执行以下步骤:
打开魔术棒,选择“Device”,将芯片改为STM32F103C8
在这里插入图片描述
然后转到C++,将字符串改为以下格式
在这里插入图片描述

3.2 代码修改

我们点击“gui”这个文件,在头文件声明中找到“oledfont.h”,右边跳转
在这里插入图片描述
找到GB16数组,并将你想输入的文字字模复制并加入到数组中,同时去除原有的大括号,最后输入点阵的内容(符号要用英文输入模式):
在这里插入图片描述
随后打开test.c文件,将代码改为如下:


#include "stdlib.h"
#include "stdio.h"
#include "oled.h"
#include "delay.h"
#include "gui.h"
#include "test.h"
#include "bmp.h"


void TEST_MainPage(void)
{	
  GUI_ShowCHinese(10,20,16,"在一起",1);
	GUI_ShowString(20,40,"63210777777",16,1);
	delay_ms(1500);		
	delay_ms(1500);
}

/****************************************************************************
 * @name       :void Test_Color(void)
 * @date       :2018-08-27 
 * @function   :Color fill test(white,black)
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void Test_Color(void)
{
	 GUI_Fill(0,0,WIDTH-1,HEIGHT-1,0);
	 GUI_ShowString(10,10,"BLACK",16,1);
	 delay_ms(1000);	
	 GUI_Fill(0,0,WIDTH-1,HEIGHT-1,1);
	 delay_ms(1500);
}

/*****************************************************************************
 * @name       :void Test_Rectangular(void))
 * @date       :2018-08-27
 * @function   :Rectangular display and fill test
								Display black,white rectangular boxes in turn,1000 
								milliseconds later,Fill the rectangle in black,white in turn
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void Test_Rectangular(void)
{
	GUI_Fill(0,0,WIDTH/2-1,HEIGHT-1,0);
	GUI_Fill(WIDTH/2,0,WIDTH-1,HEIGHT-1,1);
	GUI_DrawRectangle(5, 5, WIDTH/2-1-5, HEIGHT-1-5,1);
	GUI_DrawRectangle(WIDTH/2-1+5, 5, WIDTH-1-5, HEIGHT-1-5,0);
	delay_ms(1000);
	GUI_FillRectangle(5, 5, WIDTH/2-1-5, HEIGHT-1-5,1);
	GUI_FillRectangle(WIDTH/2-1+5, 5, WIDTH-1-5, HEIGHT-1-5,0);
	delay_ms(1500);
}


/*****************************************************************************
 * @name       :void Test_Circle(void)
 * @date       :2018-08-27 
 * @function   :circular display and fill test
								Display black,white circular boxes in turn,1000 
								milliseconds later,Fill the circular in black,white in turn
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void Test_Circle(void)
{
	GUI_Fill(0,0,WIDTH/2-1,HEIGHT-1,0);
	GUI_Fill(WIDTH/2,0,WIDTH-1,HEIGHT-1,1);
	GUI_DrawCircle(WIDTH/2/2-1, HEIGHT/2-1, 1, 27);
	GUI_DrawCircle(WIDTH/2+WIDTH/2/2-1, HEIGHT/2-1, 0, 27);
	delay_ms(1000);
	GUI_FillCircle(WIDTH/2/2-1, HEIGHT/2-1, 1, 27);
	GUI_FillCircle(WIDTH/2+WIDTH/2/2-1, HEIGHT/2-1, 0, 27);
	delay_ms(1500);
}

/*****************************************************************************
 * @name       :void Test_Triangle(void)
 * @date       :2018-08-27 
 * @function   :triangle display and fill test
								Display black,white triangle boxes in turn,1000 
								milliseconds later,Fill the triangle in black,white in turn
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void Test_Triangle(void)
{
	GUI_Fill(0,0,WIDTH/2-1,HEIGHT-1,0);
	GUI_Fill(WIDTH/2,0,WIDTH-1,HEIGHT-1,1);
	GUI_DrawTriangel(5,HEIGHT-1-5,WIDTH/2/2-1,5,WIDTH/2-1-5,HEIGHT-1-5,1);
	GUI_DrawTriangel(WIDTH/2-1+5,HEIGHT-1-5,WIDTH/2+WIDTH/2/2-1,5,WIDTH-1-5,HEIGHT-1-5,0);
	delay_ms(1000);
	GUI_FillTriangel(5,HEIGHT-1-5,WIDTH/2/2-1,5,WIDTH/2-1-5,HEIGHT-1-5,1);
	GUI_FillTriangel(WIDTH/2-1+5,HEIGHT-1-5,WIDTH/2+WIDTH/2/2-1,5,WIDTH-1-5,HEIGHT-1-5,0);
	delay_ms(1500);
}


/*****************************************************************************
 * @name       :void TEST_English(void)
 * @date       :2018-08-27 
 * @function   :English display test
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void TEST_English(void)
{
	GUI_ShowString(0,5,"6x8:abcdefghijklmnopqrstuvwxyz",8,1);
	GUI_ShowString(0,25,"8x16:abcdefghijklmnopqrstuvwxyz",16,1);
	delay_ms(1000);
	GUI_ShowString(0,5,"6x8:ABCDEFGHIJKLMNOPQRSTUVWXYZ",8,1);
	GUI_ShowString(0,25,"8x16:ABCDEFGHIJKLMNOPQRSTUVWXYZ",16,1);
	delay_ms(1500);
}

/*****************************************************************************
 * @name       :void TEST_Number_Character(void) 
 * @date       :2018-08-27 
 * @function   :number and character display test
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void TEST_Number_Character(void) 
{
	GUI_Fill(0,0,WIDTH-1,HEIGHT/2-1,0);
	GUI_ShowString(0,0,"6x8:!\"#$%&'()*+,-./:;<=>?@[]\\^_`~{}|",8,1);
	GUI_ShowNum(30,16,1234567890,10,8,1);
	delay_ms(1000);
	OLED_Clear(0); 
  GUI_ShowString(0,0,"8x16:!\"#$%&'()*+,-./:;<=>?@[]\\^_`~{}|",16,1);
	GUI_ShowNum(40,32,1234567890,10,16,1);
	delay_ms(1500);
	OLED_Clear(0);
}

/*****************************************************************************
 * @name       :void TEST_Chinese(void)
 * @date       :2018-08-27
 * @function   :chinese display test
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void TEST_Chinese(void)
{	
	GUI_ShowString(45,0,"16x16",8,1);
	GUI_ShowCHinese(16,20,16,"全动电子技术",1);
	delay_ms(1000);
	OLED_Clear(0);
	GUI_ShowString(45,0,"24x24",8,1);
	GUI_ShowCHinese(16,20,24,"全动电子",1);
	delay_ms(1000);
	OLED_Clear(0);
	GUI_ShowString(45,0,"32x32",8,1);
	GUI_ShowCHinese(0,20,32,"全动电子",1);	
  delay_ms(1000);
	OLED_Clear(0);
}

/*****************************************************************************
 * @name       :void TEST_BMP(void)
 * @date       :2018-08-27 
 * @function   :BMP monochromatic picture display test
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void TEST_BMP(void)
{
	GUI_DrawBMP(0,0,128,64, BMP2, 1);
	delay_ms(1000);
	GUI_DrawBMP(0,0,128,64, BMP3, 1);
	delay_ms(1000);
	GUI_DrawBMP(0,0,128,64, BMP4, 1);
	delay_ms(1000);
}

/*****************************************************************************
 * @name       :void TEST_Menu1(void)
 * @date       :2018-08-27 
 * @function   :Chinese selection menu display test
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void TEST_Menu1(void)
{ 
	GUI_Fill(0,0,WIDTH-1,15,1);
	GUI_ShowCHinese(32,0,16,"系统设置",0);
	GUI_DrawCircle(10, 24, 1,6);
	GUI_DrawCircle(10, 24, 1,3);
	GUI_DrawCircle(10, 40, 1,6);
	GUI_DrawCircle(10, 40, 1,3);
	GUI_DrawCircle(10, 56, 1,6);
	GUI_DrawCircle(10, 56, 1,3);
	GUI_ShowString(20,16,"A.",16,1);
	GUI_ShowCHinese(36,16,16,"音量设置",1);
	GUI_ShowString(20,32,"B.",16,1);
	GUI_ShowCHinese(36,32,16,"颜色设置",1);
	GUI_ShowString(20,48,"C.",16,1);
	GUI_ShowCHinese(36,48,16,"网络设置",1);
	GUI_DrawRectangle(0, 0,WIDTH-1,HEIGHT-1,1);
	GUI_DrawLine(WIDTH-1-10, 15, WIDTH-1-10, HEIGHT-1,1);
	GUI_FillTriangel(WIDTH-1-9,20,WIDTH-1-5,16,WIDTH-1-1,20,1);
	GUI_FillTriangel(WIDTH-1-9,HEIGHT-1-5,WIDTH-1-5,HEIGHT-1-1,WIDTH-1-1,HEIGHT-1-5,1);
	GUI_FillCircle(10, 24, 1,3);
	GUI_Fill(20,16,99,31,1);
	GUI_ShowString(20,16,"A.",16,0);
	GUI_ShowCHinese(36,16,16,"音量设置",0);
	GUI_Fill(WIDTH-1-9,23,WIDTH-1-1,28,1);
	delay_ms(1500);
	GUI_FillCircle(10, 24, 0,3);
	GUI_DrawCircle(10, 24, 1,3);
	GUI_Fill(20,16,99,31,0);
	GUI_ShowString(20,16,"A.",16,1);
	GUI_ShowCHinese(36,16,16,"音量设置",1);
	GUI_Fill(WIDTH-1-9,23,WIDTH-1-1,28,0);
	GUI_FillCircle(10, 40, 1,3);
	GUI_Fill(20,32,99,47,1);
	GUI_ShowString(20,32,"B.",16,0);
	GUI_ShowCHinese(36,32,16,"颜色设置",0);
	GUI_Fill(WIDTH-1-9,37,WIDTH-1-1,42,1);
	delay_ms(1500);
	GUI_FillCircle(10, 40, 0,3);
	GUI_DrawCircle(10, 40, 1,3);
	GUI_Fill(20,32,99,47,0);
	GUI_ShowString(20,32,"B.",16,1);
	GUI_ShowCHinese(36,32,16,"颜色设置",1);
	GUI_Fill(WIDTH-1-9,37,WIDTH-1-1,42,0);
	GUI_FillCircle(10, 56, 1,3);
	GUI_Fill(20,48,99,63,1);
	GUI_ShowString(20,48,"C.",16,0);
	GUI_ShowCHinese(36,48,16,"网络设置",0);
	GUI_Fill(WIDTH-1-9,HEIGHT-1-13,WIDTH-1-1,HEIGHT-1-8,1);
	delay_ms(1500);
}

/*****************************************************************************
 * @name       :void TEST_Menu2(void)
 * @date       :2018-08-27 
 * @function   :English weather interface display test
 * @parameters :None
 * @retvalue   :None
******************************************************************************/
void TEST_Menu2(void)
{
	u8 i;
	srand(123456);
	GUI_DrawLine(0, 10, WIDTH-1, 10,1);
	GUI_DrawLine(WIDTH/2-1,11,WIDTH/2-1,HEIGHT-1,1);
	GUI_DrawLine(WIDTH/2-1,10+(HEIGHT-10)/2-1,WIDTH-1,10+(HEIGHT-10)/2-1,1);
	GUI_ShowString(0,1,"2019-08-17",8,1);
	GUI_ShowString(78,1,"Saturday",8,1);
	GUI_ShowString(14,HEIGHT-1-10,"Cloudy",8,1);
	GUI_ShowString(WIDTH/2-1+2,13,"TEMP",8,1);
	GUI_DrawCircle(WIDTH-1-19, 25, 1,2);
	GUI_ShowString(WIDTH-1-14,20,"C",16,1);
	GUI_ShowString(WIDTH/2-1+9,20,"32.5",16,1);
	GUI_ShowString(WIDTH/2-1+2,39,"PM2.5",8,1);
	GUI_ShowString(WIDTH/2-1+5,46,"90ug/m3",16,1);
	GUI_DrawBMP(6,16,51,32, BMP5, 1);
	for(i=0;i<15;i++)
	{
		GUI_ShowNum(WIDTH/2-1+9,20,rand()%4,1,16,1);
		GUI_ShowNum(WIDTH/2-1+9+8,20,rand()%10,1,16,1);
		GUI_ShowNum(WIDTH/2-1+9+8+16,20,rand()%10,1,16,1);
		GUI_ShowNum(WIDTH/2-1+5,46,rand()%10,1,16,1);
		GUI_ShowNum(WIDTH/2-1+5+8,46,rand()%10,1,16,1);
    delay_ms(500);	
	}
}




最后,打开主函数,改为如下:
在这里插入图片描述
至此代码就配置完成了,我们进行编译烧录

3.3 烧录结果

OLED与硬件电路连接如下:
在这里插入图片描述
连接好后上电,显示结果如下:
在这里插入图片描述

四、任务二,滚动字幕

4.1 生成字模,找到工程

生成自摸的过程与上类似,这里给出要显示的文字
在这里插入图片描述

4.2 修改代码

复制一个上一个实验的工程,并对GB16数组进行修改,加入新建立的字模
在这里插入图片描述
随后修改test.c
只需修改一个点就行
在这里插入图片描述
在主函数中修改如下

#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
int main(void)
{	
	delay_init();	    	       //延时函数初始化	  
	NVIC_Configuration(); 	   //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 	
	OLED_Init();			         //初始化OLED  
	OLED_Clear(0);             //清屏(全黑)

	
	OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
	OLED_WR_Byte(0x27,OLED_CMD); //水平向左或者右滚动 26/27
	OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
	OLED_WR_Byte(0x00,OLED_CMD); //起始页 0
	OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔
	OLED_WR_Byte(0x07,OLED_CMD); //终止页 2
	OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
	OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节
	TEST_MainPage();
	OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动

	
}

编译烧录

4.3 烧录结果

硬件连接跟上个实验相同,烧录结果如下:
在这里插入图片描述

五、任务三,显示AHT20参数

AHT20在前面的博客中已经介绍过https://blog.csdn.net/weixin_66084944/article/details/134293110
这里我们就直接使用它。
取模过程不再多说,将取模后的汉字加入到GB16数组中,本次取的文字有以下:
在这里插入图片描述
此外,我们需要导入AHT20的封装配置函数文件。AHT20配置函数链接:

链接:https://pan.baidu.com/s/1846AHdi3J96m_txVhv1ahw?pwd=0231
提取码:0231

解压后,将两个函数导入到项目工程的HARDWARE文件中,并设置头文件路径。
先把本地文件复制粘贴到HARDWARE文件夹里
在这里插入图片描述

再在keil中添加
在这里插入图片描述
在这里插入图片描述
打开工程,修改AHT20的.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]为0
	Send_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]为0
	Send_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();                           //注意: 最后是发送NAK
	Stop_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的初始化命令是0xE1
	Receive_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);//原来是0x70
	Receive_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倍)
//	下一步客户处理显示数据,
//	 }
 
// }

头文件修改为如下:

#ifndef _AHT20_DEMO_
#define _AHT20_DEMO_

#include "stm32f10x.h"  

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的状态寄存器
uint8_t AHT20_Read_Cal_Enable(void);  //查询cal enable位有没有使能
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 AHT20_Init(void);   //初始化AHT20
void JH_Reset_REG(uint8_t addr);///重置寄存器
void AHT20_Start_Init(void);///上电初始化进入正常测量状态

#endif

再将主函数修改为如下:

#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){
	//初始化PC12
	GPIOC13_Init();		
	
	//延时函数初始化	  
	delay_init();	   
	
	//初始化OLED 
	OLED_Init();
 
	//清屏(全黑)	
	OLED_Clear(0);    
	//开机显示信息	
GUI_ShowCHinese(10,0,16,"开启中",1);	
	
	Delay_1ms(1000);
	
	AHT20_Init();
	
	Delay_1ms(1000);
	
	OLED_Clear(0); 
	OLED_WR_Byte(0x2E,OLED_CMD); //关闭滚动
 
	OLED_WR_Byte(0x27,OLED_CMD); //水平向左或者右滚动 26/27
 
	OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
 
	OLED_WR_Byte(0x00,OLED_CMD); //起始页 0
 
	OLED_WR_Byte(0x07,OLED_CMD); //滚动时间间隔
 
	OLED_WR_Byte(0x02,OLED_CMD); //终止页 2
 
	OLED_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(16,24,16,"温度",1);
		GUI_ShowString(47,24,":",16,1);
		GUI_ShowString(62,24,temp,16,1);
		GUI_ShowCHinese(94,24,16,"度",1);
		
 
		//显示湿度
		GUI_ShowCHinese(16,42,16,"湿度",1);
		GUI_ShowString(47,42,":",16,1);
		GUI_ShowString(62,42,hum,16,1);

}

AHT20硬件连接如下:
在这里插入图片描述
在这里插入图片描述
烧录结果如下:
在这里插入图片描述

六、总结在这里插入图片描述

通过这次实验,我深入学习了SPI协议、OLED的使用以及字模的生成,并且通过实际操作,对STM32与OLED的交互操作有了更熟练的掌握。在实现三个要求的过程中,我不仅熟悉了相关的理论知识,还积累了实际的编程经验。

在操作OLED时,我注意到了显示字长的设置问题,如果不进行正确的设置,可能会导致显示不全或者出现其他问题。此外,在进行字模取模时,我也意识到了横向取模、纵向取模以及倒序取模的区别和重要性。如果选择不当,可能会得到模糊不清的显示效果,而不是正常清晰的汉字。

OLED作为一种外设模块,在硬件项目中具有广泛的应用价值。在未来的项目中,我会充分利用OLED进行调试显示,这将会给我带来很大的帮助。因此,我会继续深入学习和练习OLED的使用,不断提高自己的技能水平。

通过这次实验,我不仅掌握了相关的知识和技能,还对硬件编程有了更深入的了解。我相信,这些经验和知识将对我未来的学习和工作产生积极的影响。

七、参考

https://blog.csdn.net/weixin_66084944/article/details/134293110

https://blog.csdn.net/m0_63323712/article/details/134410644

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值