汉字点阵与OLE屏显

汉字点阵与OLE屏显

一、串口传输文件

这里我们需要将两台笔记本电脑,借助 usb转rs232 模块和杜邦线,建立起串口连接。然后用串口助手等工具软件(带文件传输功能)将一台笔记本上的一个大文件(图片、视频和压缩包软件)传输到另外一台电脑,预算文件大小、波特率和传输时间三者之间的关系,并对比实际传输时间。

我们这里使用的是野火串口助手,我们连接的时候需要注意rx口连接tx口,一台电脑负责发送,一台电脑负责接收。

首先将线路连接好,打开串口助手,注意波特率需要选择一致,可以调试不同的波特率以测试传输速度。

以下为再460800波特率下的结果,传输速度几乎瞬间到达,采用115200的波特率延时大概在几秒左右。同时还有的原因是传输的内容简单,所以很快,我们尝试过传输图片,这时所需的时间需要几分钟,并且由于软件问题接收到的数据全为乱码,需要更换软件才行。
在这里插入图片描述

二、使用opencv显示图片

汉字点阵介绍

(一)国标码(交换码)
1.简介
国标码
国标码是一个四位十六进制数,它将一个汉字用两个字节表示,每个字节只有7位,与ASCII码相似。

为了避开ASCII字符中的不可显示字符0000 0000 ~ 0001 1111(十六进制为0 ~ 1F,十进制为0 ~ 31)及空格字符0010 0000(十六进制为20,十进制为32)(至于为什么要避开、又为什么只避开ASCII中0~32的不可显示字符和空格字符,后文有解释),国标码(又称为交换码)规定表示汉字的范围为(0010 0001,0010 0001) ~ (0111 1110,0111 1110),十六进制为(21,21) ~ (7E,7E),十进制为(33,33) ~ (126,126)(注意,GB类汉字编码为双字节编码)。

因此,必须将“区码”和“位码”分别加上32(十六进制为20H,后缀H表示十六进制),作为国标码。也就是说,国标码相当于将区位码向后偏移了32,以避免与ASCII字符中0~32的不可显示字符和空格字符相冲突。

2.举例
注意,
标码中是分别将区位码中的“区”和“位”各自加上32(20H)的,因为GB2312是DBCS双字节字符集,国标码属于双字节码,“区”和“位”各作为一个单独的字节。

“万”字的国标码十进制为:(45+32,82+32) = (77,114),
十六进制为:(4D,72H),
二进制为:(0100 1101,0111 0010)。

(二)汉字机内码
1.汉字机内码简介
机内码:为了避免ASCII码和国标码同时使用时产生二义性问题,大部分汉字系统都采用将国标码每个字节高位置1作为汉字机内码。这样既解决了汉字机内码与西文机内码之间的二义性,又使汉字机内码与国标码具有极简单的对应关系。
在这里插入图片描述
2.例子
国标码的机内码为二字节长的代码,它是在相应国标码的每个字节最高位上加“1”,即
汉字机内码=汉字国标码+8080H
例如,上述“啊”字的国标码是3021H,其汉字机内码则是B0A1H。

在这里插入图片描述

(三)汉字区位码
1.简介
区位码一个四位的十进制数,它将GB2312—80的全部字符集组成一个94×94的方阵,每一行称为一个“区”,编号为01~94;每一列称为一个“位”,编号为01~94,这样得到GB2312—80的区位图,用区位图的位置来表示的汉字编码,称为区位码。
在这里插入图片描述
2.GB2312字符集中区位码位置
(1)01~09区(682个):特殊符号、数字、英文字符、制表符等,包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母等在内的682个全角字符;

(2)10~15区:空区,留待扩展;

(3)16~55区(3755个):常用汉字(也称一级汉字),按拼音排序;

(4)56~87区(3008个):非常用汉字(也称二级汉字),按部首/笔画排序;

(5)88~94区:空区,留待扩展。

(四)汉字机内码、国标码和区位码三者关系
1.三者的关系
国标码 = 区位码 + 2020H;
机内码 = 国标码 + 8080H;

2020H解释

因为ASCLL码中分为控制型编码和有形字符编码,前32位是控制码(如回车,退格等),沿用前32个,覆盖后面的。故国标码规定在区位码的基础上每个字节分别加上20H(32的十六进制表示)。
8080H解释

为避免与ASCLL编码冲突,从而规定把每个字节的最高位都从 0 换成 1(这之前它们都是 0),或者说把每个字节(区和位)都再加上 80H(128的十六进制表示)。
2. 运算规则
(1)将区位码中的区码和位码分别转换为十六进制数;
(2)区位码的十六进制数+2020H = 国标码;
(3)国标码+8080H = 机内码

从区位码(国家标准定义) —> 区码和位码分别+32(即+20H)得到国标码 —> 再分+128(即+80H)得到机内码(与ACSII码不再冲突)
区位码(区码,位码) + (20H,20H) + (80H,80H) =区位码(区码,位码) + (A0H,A0H) = 内码(高字节,低字节)。

在这里插入图片描述

(五)汉字字形储存格式

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

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

  1. 16*16点阵字库
    对于1616的矩阵来说,它所需要的位数共是1616=256个位,每个字节为8位,因此,每个汉字都需要用256/8=32个字节来表示。

即每两个字节代表一行的16个点,共需要16行,显示汉字时,只需一次性读取32个字节,并将每两个字节为一行打印出来,即可形成一个汉字。
点阵结构如下图所示:
在这里插入图片描述

  1. 1414与1212点阵字库
    对于1414和1212的字库,理论上计算,它们所需要的点阵分别为(1414/8)=25, (1212/8)=18个字节,但是,如果按这种方式来存储,那么取点阵和显示时,由于它们每一行都不是8的整位数,因此,就会涉到点阵的计算处理问题,会增加程序的复杂度,降低程序的效率。

为了解决这个问题,有些点阵字库会将1414和1212的字库按1614和1612来存储,即,每行还是按两个字节来存储,但是1414的字库,每两个字节的最后两位是没有使用,1212的字节,每两字节的最后4位是没有使用,这个根据不同的字库会有不同的处理方式,所以在使用字库时要注意这个问题,特别是14*14的字库。

4.汉字点阵获取
(1)利用区位码获取汉字

汉字点阵字库是根据区位码的顺序进行存储的,因此,我们可以根据区位来获取一个字库的点阵,它的计算公式如下:

点阵起始位置 = ((区码- 1)*94 + (位码 – 1)) * 汉字点阵字节数

获取点阵起始位置后,我们就可以从这个位置开始,读取出一个汉字的点阵。

(2.) 利用汉字机内码获取汉字

前面我们己经讲过,汉字的区位码和机内码的关系如下:

机内码高位字节 = 区码 + 20H + 80H(或区码 + A0H)
机内码低位字节 = 位码 + 20H + 80H(或位码 + AOH)

反过来说,我们也可以根据机内码来获得区位码:

区码 = 机内码高位字节 - A0H
位码 = 机内码低位字节 - AOH

将这个公式与获取汉字点阵的公式进行合并计就可以得到汉字的点阵位置。

配置环境:

显示图片前我们需要学习理解汉字的机内码、区位码编码规则和字形数据存储格式。在Ubuntu下用C/C++(或python) 调用opencv库编程显示一张图片,并打开一个名为"logo.txt"的文本文件(其中只有一行文本文件,包括你自己的名字和学号),按照名字和学号去读取汉字24*24点阵字形字库(压缩包中的文件HZKf2424.hz)中对应字符的字形数据,将名字和学号叠加显示在此图片中。

这里我为了方便直接使用的双系统,在双系统里使用Ubuntu进行操作。如果是刚下载才初始化的话,那就需要先配置基本环境。

在终端输入以下命令

sudo apt-get install build-essential

sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev

sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev

安装:

在opencv目录下先创建build文件

cd opencv
mkdir build

在build下进行编译:

cd build

sudo cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local …

再输入以下命令:

sudo make -j8
或者
sudo make -j4

开始安装:

sudo make install

添加路径:
(1)打开文件:

sudo gedit /etc/ld.so.conf

(2)在文件中添加如下代码:

/usr/loacal/lib

(3)保存关闭,运行下面代码:

sudo ldconfig

配置环境:
(1)打开.bashrc文件:

sudo gedit /etc/bash.bashrc
(2)添加下面两行代码,放到最后面即可:

PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
(3)保存退出,终端输入:

source /etc/bash.bashrc
输入以下命令,可以查看所安装opencv的版本

pkg-config opencv --modversion

到这里基本工作已经准备完成,若是觉得过程不详细或者缺少cmake的环境,可以在csdn博客上进行资料查找。

正式进程:

首先我们下载本次实验必要的两个文件。

再创建logo.txt文件,包含名字和学号。这里注意再windows上面创建文本文件,然后点击另存为,在下方有一个编码,选择ANSI,以免文字显示在图片上面时出现乱码
在这里插入图片描述

然后打开Ubuntu,创建本次实验的作业文件,再创建cpp文件,文件具体代码如下:

#include

#include<opencv/cv.h>

#include"opencv2/opencv.hpp"

#include<opencv/cxcore.h>

#include<opencv/highgui.h>

#include<math.h>

using namespace cv;

using namespace std;

void paint_chinese(Mat& image,int x_offset,int y_offset,unsigned long offset);

void paint_ascii(Mat& image,int x_offset,int y_offset,unsigned long offset);

void put_text_to_image(int x_offset,int y_offset,String image_path,char* logo_path);

int main(){

String image_path="/home/lxj/qianrushi/week10/yuanshen.png";

char* logo_path=(char*)"/home/lxj/qianrushi/week10/logo.txt";

put_text_to_image(20,300,image_path,logo_path);

return 0;

}

void paint_ascii(Mat& image,int x_offset,int y_offset,unsigned long offset){

//绘制的起点坐标
Point p;
p.x = x_offset;
p.y = y_offset;
 //存放ascii字膜
char buff[16];           
//打开ascii字库文件
FILE *ASCII;
if ((ASCII = fopen("/home/lxj/qianrushi/week10/Asci0816.zf", "rb")) == NULL){
	printf("Can't open ascii.zf,Please check the path!");
	//getch();
	exit(0);
}

fseek(ASCII, offset, SEEK_SET);
fread(buff, 16, 1, ASCII);
int i, j;
Point p1 = p;
for (i = 0; i<16; i++)                  //十六个char
{
	p.x = x_offset;
	for (j = 0; j < 8; j++)              //一个char八个bit
	{
		p1 = p;
		if (buff[i] & (0x80 >> j))    /*测试当前位是否为1*/
		{
			/*
				由于原本ascii字膜是8*16的,不够大,
				所以原本的一个像素点用4个像素点替换,
				替换后就有16*32个像素点
				ps:感觉这样写代码多余了,但目前暂时只想到了这种方法
			*/
			circle(image, p1, 0, Scalar(0, 0, 255), -1);
			p1.x++;
			circle(image, p1, 0, Scalar(0, 0, 255), -1);
			p1.y++;
			circle(image, p1, 0, Scalar(0, 0, 255), -1);
			p1.x--;
			circle(image, p1, 0, Scalar(0, 0, 255), -1);

		}						
		p.x+=2;            //原来的一个像素点变为四个像素点,所以x和y都应该+2
	}
	p.y+=2;
}

}

void paint_chinese(Mat& image,int x_offset,int y_offset,unsigned long offset){//在图片上画汉字

Point p;
p.x=x_offset;
p.y=y_offset;
FILE *HZK;
char buff[72];//72个字节,用来存放汉字的
if((HZK=fopen("/home/lxj/qianrushi/week10/HZKs2424.hz","rb"))==NULL){
    printf("Can't open HZKf2424.hz,Please check the path!");
    exit(0);//退出
}

fseek(HZK, offset, SEEK_SET);/*将文件指针移动到偏移量的位置*/
fread(buff, 72, 1, HZK);/*从偏移量的位置读取72个字节,每个汉字占72个字节*/
bool mat[24][24];//定义一个新的矩阵存放转置后的文字字膜

int i,j,k;
for (i = 0; i<24; i++)                 /*24x24点阵汉字,一共有24行*/
{
	for (j = 0; j<3; j++)                /*横向有3个字节,循环判断每个字节的*/
		for (k = 0; k<8; k++)              /*每个字节有8位,循环判断每位是否为1*/
			if (buff[i * 3 + j] & (0x80 >> k))    /*测试当前位是否为1*/
			{
				mat[j * 8 + k][i] = true;          /*为1的存入新的字膜中*/
			}
			else {
				mat[j * 8 + k][i] = false;
			}
}

for (i = 0; i < 24; i++)
{
	p.x = x_offset;
	for (j = 0; j < 24; j++)
	{		
		if (mat[i][j])
			circle(image, p, 1, Scalar(255, 0, 0), -1);		  //写(替换)像素点
		p.x++;                                                //右移一个像素点
	}
	p.y++;                                                    //下移一个像素点
}

}

void put_text_to_image(int x_offset,int y_offset,String image_path,char* logo_path){//将汉字弄上图片
//x和y就是第一个字在图片上的起始坐标
//通过图片路径获取图片

Mat image=imread(image_path);
int length=18;//要打印的字符长度
unsigned char qh,wh;//定义区号,位号
unsigned long offset;//偏移量
unsigned char hexcode[30];//用于存放记事本读取的十六进制,记得要用无符号
FILE* file_logo;
if ((file_logo = fopen(logo_path, "rb")) == NULL){
	printf("Can't open txtfile,Please check the path!");
	//getch();
	exit(0);
}

fseek(file_logo, 0, SEEK_SET);
fread(hexcode, length, 1, file_logo);
int x =x_offset,y = y_offset;//x,y:在图片上绘制文字的起始坐标

for(int m=0;m<length;){
    if(hexcode[m]==0x23){
        break;//读到#号时结束
    }
    else if(hexcode[m]>0xaf){
        qh=hexcode[m]-0xaf;//使用的字库里是以汉字啊开头,而不是以汉字符号开头
        wh=hexcode[m+1] - 0xa0;//计算位码
        offset=(94*(qh-1)+(wh-1))*72L;
        paint_chinese(image,x,y,offset);
        /*
        计算在汉字库中的偏移量
        对于每个汉字,使用24*24的点阵来表示的
        一行有三个字节,一共24行,所以需要72个字节来表示
        如赵字
        区位码是5352
        十六进制位3534
        机内码就是d5d4
        d5-af=38(十进制),因为是从汉字啊开始的,所以减去的是af而不是a0,38+15等于53与区码相对应
        d4-a0=52
        */
        m=m+2;//一个汉字的机内码占两个字节,
        x+=24;//一个汉字为24*24个像素点,由于是水平放置,所以是向右移动24个像素点
    }
    else{//当读取的字符为ASCII码时
    wh=hexcode[m];
    offset=wh*16l;//计算英文字符的偏移量
    paint_ascii(image,x,y,offset);
    m++;//英文字符在文件里表示只占一个字节,所以往后移一位就行了
    x+=16;

}

}
cv::imshow("image", image);
cv::waitKey();

}

将代码写入cpp文件后,需要注意修改路径,图片的路径一定要具体,一定要对,其他文件的路径也要核对。前面我们下载了Ascii6.zf文件和HZKs2424.hz文件,传到Ubuntu后将文件复制到作业文件下,这里用cp命令,我们用cp命令将图片文件复制到目的地,logo.txt文件也需要复制下来,具体命令如下:
在这里插入图片描述

这里我们再输入以下命令进行编译:

g++ word.cpp -o word pkg-config --cflags --libs opencv

再输入:

./word

得到结果:
在这里插入图片描述

这里我们需要注意设置显示字符的长度,如果设置的数值不够,那么内容不会被完全显示出来,我们这里一般长度设置为18即可。

三、OLED屏显

简单介绍

一.SPI(串行外设接口)
  1. 什么是SPI?
    SPI协议是一种高速全双工同步串行通信协议,由一个主设备(Master)和一个或多个从设备(Slave)组成。它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,一般需要4根线,也可以使用三根线(单向传输)。SPI协议被广泛应用于ADC、LCD等设备与MCU间,要求通讯速率较高的场合。在物理层上,SPI通讯使用3条总线及片选线,3条总线分别为SCK、MOSI、MISO,片选线为SS。SPI时钟频率相比IIC要高很多,最高可以工作在上百MHZ。

2.SPI接口
如图所示,一个标准的SPI连接涉及到一个主机master使用串行时钟(SCK)、主输出从输入(MOSI)、主输出从输出(MISO)和从选择(SS)线连接到一个或几个从机slave。SCK、MOSI和MISO信号可以由从机slave共享,而每个从机slave都有一条唯一的SS线。
在这里插入图片描述

2.1 SPI模式:极性和时钟相位
SPI接口没有定义数据交换协议,限制了开销并允许高速数据流。时钟极性(CPOL)和时钟相位(CPHA)可以指定为“0”或“1”,形成四种独特的模式,以提供主从通信的灵活性,如图所示。
在这里插入图片描述

如果CPOL和CPHA都为’ 0 ‘(定义为模式0),则在时钟的前上升沿采样数据。目前,模式0是SPI总线通信最常见的模式。如果CPOL为’ 1 ‘,CPHA为’ 0 '(模式2),则在时钟的前降边缘采样数据。同样,CPOL = ’ 0 '和CPHA = ’ 1 ’ (Mode 1)在尾降边缘采样,CPOL = ’ 1 '和CPHA = ’ 1 ’ (Mode 3)在尾升边缘采样。下面的表1总结了可用的模式

CPOL:时钟极性,表示时钟线空闲时是高电平1还是低电平0。
CPHA:时钟相位,表示是在时钟的前沿0还是尾沿1采样数据。
在这里插入图片描述

2.2 SPI三线总线和多IO配置
除了标准的4线配置外,SPI接口还扩展到包括各种IO标准,包括用于减少引脚数的3线和用于更高吞吐量的双或四I/O。

在3线模式下,MOSI和MISO线路组合成单个双向数据线,如图3所示。事务是半双工的,以允许双向通信。减少数据线的数量并以半双工模式运行也会降低最大可能的吞吐量; 许多3线设备具有低性能要求,而设计时考虑到低引脚数。
在这里插入图片描述

多I/O变体(如双I/O和四I/O)在标准外添加了额外的数据线,以提高吞吐量。利用多I/O模式的组件可以与并行器件的读取速度相媲美,同时仍然可以减少引脚数量。这种性能提升使得能够从闪存中随机访问和直接执行程序(XIP)。

例如,四路I/O设备在与高速设备通信时可提供四倍于标准4线SPI接口的性能。下图显示了单个四通道IO从站配置的示例。
在这里插入图片描述

  1. SPI总线事务
    SPI协议没有定义数据流的结构; 数据的组成完全取决于组件设计者。但是,许多设备遵循相同的基本格式来发送和接收数据,从而允许来自不同供应商的部件之间的互操作性。

3.1 简单SPI写事务
大多数SPI闪存都有一个写状态寄存器命令,用于写入一个或两个字节的数据,如图5所示。要写入状态寄存器,SPI主机首先启用当前器件的从选择线。然后,主设备输出适当的指令,后跟两个数据字节,用于定义预期的状态寄存器内容。由于事务不需要返回任何数据,因此从设备将MISO线保持在高阻抗状态,并且主设备屏蔽任何输入数据。最后,从机选择信号被取消以结束事务。
在这里插入图片描述

3.2 简单SPI读事务
状态寄存器读取事务与写入事务类似,但现在利用从器件返回的数据,如图所示。在发送读取状态寄存器指令后,从器件开始以MISO线路传输数据,数率为每八个时钟周期一个字节。主机接收比特流并通过取消SS信号来完成事务。
在这里插入图片描述

3.3 四线IO事务
由于其性能的提高,四线IO在闪存中越来越受欢迎。四线IO没有使用单输出和单输入接口,而是使用4条独立的半双工数据线来传输和接收数据,其性能是标准四线SPI的四倍。

图中显示了Spansion S25FL016K串行NorFLASH器件的读取示例命令。要从器件读取,主器件首先在第一个IO线上发送快速读取命令(EBh),而其他所有命令都处于三态。接下来,主机发送地址; 由于接口现在有4条双向数据线,因此它可以利用它们在8个时钟周期内发送一个完整的24位地址和8个模式位。然后,该地址跟随2个虚拟字节(4个时钟周期),以允许器件有额外的时间来设置初始地址。
在这里插入图片描述

在主机发送地址周期和虚拟字节之后,组件开始发送数据字节; 每个时钟周期由分布在4个IO线上的数据半字节组成,每个数据字节总共有两个时钟周期。

二.OLED显示数据

OLED显示汉字是通过汉字取摸软件生成的,将汉字在16*16的像素区域内进行识别并排列,生成对应的c51代码。所以我们这里需要下载一个汉字取摸软件。

运行软件,点击设置,设置以下内容
在这里插入图片描述

再输入汉字,点击生成字模即可
在这里插入图片描述

开始任务

任务一:显示自己的学号和姓名

这里我们可以在网络查找相关资料,获取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

打开文件,找到工程,路径如下:
在这里插入图片描述

打开工程,点击魔术棒,点击Device,将芯片换为STM32F103C8
在这里插入图片描述

再点击C/C++,将以下位置更改为以下内容
在这里插入图片描述

然后进入到gui.c,找到头文件oledfont.h,右键跳转
在这里插入图片描述

在取模软件里面复制之前生成的数组,找到GB16函数,复制进去,在前面加上中文,注意,在驱魔软件里面复制的内容是带有大括号的,需要将大括号去掉。
在这里插入图片描述

打开test.c文件,找到TEST_MainPage函数,修改内容
在这里插入图片描述

最后再打开主函数,修改为以下内容
在这里插入图片描述

然后就可以进行编译,你可能会遇到以下问题,若没遇见则跳过
在这里插入图片描述

然后这样改可以解决问题,将图中位置的勾去掉,就不会报错
在这里插入图片描述

连线如下:

然后在进行烧录,得到结果如下:
请添加图片描述

任务二:显示AHT20的温度和湿度

为了将几个任务分清楚,并保存好每个任务的文件,这里可以复制上个工程,然后再进行操作
获取字模
在这里插入图片描述
“正”,0x00,0x00,0x7F,0xFC,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x11,0x00,0x11,0xF8,
0x11,0x00,0x11,0x00,0x11,0x00,0x11,0x00,0x11,0x00,0x11,0x00,0xFF,0xFE,0x00,0x00,/“正”,0/

    "在",0x02,0x00,0x02,0x00,0x04,0x00,0xFF,0xFE,0x08,0x00,0x08,0x40,0x10,0x40,0x30,0x40,
    0x57,0xFC,0x90,0x40,0x10,0x40,0x10,0x40,0x10,0x40,0x10,0x40,0x1F,0xFE,0x10,0x00,/*"在",1*/
	
    "检",0x10,0x40,0x10,0x40,0x10,0xA0,0x10,0xA0,0xFD,0x10,0x12,0x08,0x35,0xF6,0x38,0x00,
    0x54,0x88,0x50,0x48,0x92,0x48,0x11,0x50,0x11,0x10,0x10,0x20,0x17,0xFE,0x10,0x00,/*"检",2*/
	
    "测",0x00,0x04,0x27,0xC4,0x14,0x44,0x14,0x54,0x85,0x54,0x45,0x54,0x45,0x54,0x15,0x54,
    0x15,0x54,0x25,0x54,0xE5,0x54,0x21,0x04,0x22,0x84,0x22,0x44,0x24,0x14,0x08,0x08,/*"测",3*/
	
    "温",0x00,0x00,0x23,0xF8,0x12,0x08,0x12,0x08,0x83,0xF8,0x42,0x08,0x42,0x08,0x13,0xF8,
    0x10,0x00,0x27,0xFC,0xE4,0xA4,0x24,0xA4,0x24,0xA4,0x24,0xA4,0x2F,0xFE,0x00,0x00,/*"温",4*/
	
    "度",0x01,0x00,0x00,0x80,0x3F,0xFE,0x22,0x20,0x22,0x20,0x3F,0xFC,0x22,0x20,0x22,0x20,
    0x23,0xE0,0x20,0x00,0x2F,0xF0,0x24,0x10,0x42,0x20,0x41,0xC0,0x86,0x30,0x38,0x0E,/*"度",5*/

我们需要导入AHT20的封装配置函数文件。AHT20配置函数链接(前面的博客也发过):

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

下载好文件后将文件添加到工程里面,位置如下:
在这里插入图片描述

然后在keil里面添加(前面博客有过程,这里不细讲):
在这里插入图片描述

同时还需要添加头文件路径
在这里插入图片描述
在这里插入图片描述

点开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]为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]10010/1024/1024; //计算得到湿度值c1(放大了10倍)
// t1 = CT_data[1]20010/1024/1024-500;//计算得到温度值t1(放大了10倍)
// 下一步客户处理显示数据,
// }

// }

再修改主函数为以下内容:

#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_DEMO_V1_3.h文件里面的内容是不全的,打开文件,在文件末尾添加以下内容:
#endif
编译烧录程序,得到结果:
请添加图片描述

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

重新设置一段话,尽量长一些,用取模软件生成字模
在这里插入图片描述
“欢”,0x00,0x80,0x00,0x80,0xFC,0x80,0x04,0xFC,0x05,0x04,0x49,0x08,0x2A,0x40,0x14,0x40,
0x10,0x40,0x28,0xA0,0x24,0xA0,0x45,0x10,0x81,0x10,0x02,0x08,0x04,0x04,0x08,0x02,/“欢”,0/

    "迎",0x00,0x00,0x20,0x80,0x13,0x3C,0x12,0x24,0x02,0x24,0x02,0x24,0xF2,0x24,0x12,0x24,
    0x12,0x24,0x12,0xB4,0x13,0x28,0x12,0x20,0x10,0x20,0x28,0x20,0x47,0xFE,0x00,0x00,/*"迎",1*/
	
    "来",0x01,0x00,0x01,0x00,0x01,0x00,0x7F,0xFC,0x01,0x00,0x11,0x10,0x09,0x10,0x09,0x20,
    0xFF,0xFE,0x03,0x80,0x05,0x40,0x09,0x20,0x31,0x18,0xC1,0x06,0x01,0x00,0x01,0x00,/*"来",2*/
	
    "到",0x00,0x04,0xFF,0x84,0x08,0x04,0x10,0x24,0x22,0x24,0x41,0x24,0xFF,0xA4,0x08,0xA4,
    0x08,0x24,0x08,0x24,0x7F,0x24,0x08,0x24,0x08,0x04,0x0F,0x84,0xF8,0x14,0x40,0x08,/*"到",3*/
	
    "交",0x02,0x00,0x01,0x00,0x01,0x00,0xFF,0xFE,0x00,0x00,0x10,0x10,0x10,0x08,0x20,0x24,
    0x48,0x24,0x04,0x40,0x02,0x80,0x01,0x00,0x02,0x80,0x0C,0x40,0x30,0x30,0xC0,0x0E,/*"交",4*/
	
    "大",0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,
    0x02,0x80,0x02,0x80,0x04,0x40,0x04,0x40,0x08,0x20,0x10,0x10,0x20,0x08,0xC0,0x06,/*"大",5*/

按照任务一修改对应的内容即可,以下为修改内容:
在这里插入图片描述
在这里插入图片描述

将main函数里面的while循环删去,添加以下代码:

​ 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); //开启滚动

进行烧录,得到以下结果:
请添加图片描述

四、总结

本次实验一共要做三个大任务,相对而言,第一个任务是最简单的,稍微了解一下很快便可以做完,主要是体会这一过程。任务二需要用到Ubuntu,由于选修了轨道信号这门课,所以做起来也比较得心应手,稍微需要注意的是一些细节性的问题,显示图片的实验需要注意路径的问题,总体做出来难度不大。最耗费时间的便是任务三,因为任务三有三个小任务,但是也不算特别难,多查阅资料便可。过程中肯定会遇到问题,最主要的就是能够解决问题,在这篇博客里我已经写出我遇到的几个问题并且提供了解决方法。
总的来说,本次实验做得还是比较成功的,也深切地体会了测试温度,显示字幕的过程,让我对于这门课程的了解也更多,当然未来还需要继续进步、学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值