——Dec.21.2020
目录
本次实验采用正点原子STM32F407探索者开发版和STM32cubeIDE软件开发
- 单片机上LCD屏幕显示原理
- 引脚相关配置
- 调用相关函数画个人
开头说两句
最近学习单片机LCD屏幕的显示,一开始遇到了一些困难和坑,面对复杂的LCD的控制原理、寄存器读写操作以及LCD初始化配置指令着实有点让人不知从何下手。不过都在网上以及正点原子资料的帮助下成功实现LCD的显示,在这里和大家分享一下。
对于一个小萌新而言,我认为应该先学会用,然后再实践中发现问题来寻找原理。如果一开始就卡在复杂的寄存器指令配置,那么学习还是会变得很无聊的(泪
一、单片机上LCD屏幕的显示原理
(1)LCD显示原理
LCD(Liquid Crystal Display)也就是我们平时所说的液晶屏,液晶屏是如何显示的呢?液晶屏大致可以分为三层,最底层是一个背光板——负责发白光;背光板的上面会有一层偏光片,将白光滤成偏光片;再上面是液晶,我们通过电信号来控制液晶和彩色滤光片来控制光的颜色。我们知道光的三原色RGB,通过控制每个像素位置上的RGB三色滤光片来实现像素点彩色的显示。
(2)单片机通信LCD
-
那么单片机是如何与LCD通信的呢?
我们首先介绍一下FSMC(Flexible Static Memory Controller,可变静态存储控制器)FSMC,即灵活的静态存储控制器,能够与同步或异步存储器和16位PC存储器卡连接,STM32的FSMC接口支持包括SRAM、NAND FLASH、NOR FLASH和PSRAM等存储器。可以理解成一个比较万能的存储连接模块,可以用来控制各种存储外设。而TFTLCD在这里相当于一个SRAM,我们通过FSMC调用这个SRAM。通过读写寄存器使LCD显示出我们需要的东西。
所以我们可以把LCD显示的过程理解成用FSMC这个工具往这个LCD的盒子里装东西。因此我们学会根据LCD的数据手册进行读写寄存器的配置。
引脚相关配置
-
首先是经典时钟树配置(参考前面的时钟树配置,结果差不多)
-
Connectivity -> FSMC -> NOR Flash/PSRAM/SRAM/ROM/LCD 1
Chip Select:
NE4
(片选信号,根据原理图我们可以知道LCD片选选择NE4)Memory type:
LCD Interface
(选择为LCD)LCD Register Select:
A6
(原理图中我们可以看到RS对应A6)Data:
16bits
(我们选择16位数据,传输速率会快一倍)Attention: 在上一个文章中矩阵键盘的引脚设置中包含了A6,若要同时使用这两者,需要更改一下矩阵键盘的引脚设置,由于矩阵键盘代码大多数采用宏定义和标签来写,因此修改引脚时要记得加上Label以及函数
HAL_GPIO_ReadPin()
,HAL_GPIO_WritePin()
中需要检查对应GPIO_Port是否正确。 -
然后我们设置NOR/PSRAM 1的内容
-
首先是控制部分(control):
主要设置拓展模式(Extended mode):
Enable
-
时序设置(timing):(这里数字为高频时钟脉冲数,如果按照之前设置的系统时钟为168M,一个脉冲就约为6ns,根据数据手册中我们可以去查询相应的时间,然后换算成相近的脉冲数注意,这里计数是从0开始的 )
「以下是根据正点原子资料所设置」
Address setup time:
15
(大约为16x6=96ns)Data setup time:
60
(对于我们这个TFTLCD而言并不需要这么久,考虑到代码兼容性设置为60)Bus turn around time:
1
(这个随意设置吧,我查了半天没弄明白这个具体是什么,网上有设置为0,也有设置为十几的,应该问题不大)Access mode:
A
(这里选择访问模式A) -
读时序设置:(由于我们选择拓展模式,读写是分开的寄存器,即读写分离。因此我们就需要设置这里的读时序,若extended mode unable,则不需要设置这里)
Extended address setup time:
2
Extended data setup time:
2
Extended bus turn around time:
1
Extended access mode:
A
-
最后,我们需要打开背光源(PB15)若没有打开,LCD是看不到显示的
Maximun output speed:
High
(好像Low和High没太大差别)GPIO Pull-up/Pull-down:
NPND
(选择推挽输出)User Label:
LCD_BL
(这里可以自定义,单纯方便说明这个引脚的用途)
-
都设置好了就可以点击Generate Code
,进入代码部分
Here we go~ ⛵️
驱动代码的引入以及代码修改~~(缝合)~~
作为一个萌新,自己写驱动不仅费时费力消耗精力而且还容易出错,因此这里我采用了正点原子的驱动代码,但是由于我的文件体系和正点原子给的体系有点不同,因此还需要做一点改变~~(缝合)~~。一下简单介绍一下。
1、导入文件
我们找到正点原子相关的工程文件夹,HARDWARE -> LCD
中找到对应的lcd.h
,lcd.c
以及font.h
(字库文件)并拷贝到我们自己的工程文件夹中,这里我设置一个MyFunction
文件夹存放外设文件。因此我将这两个文件拷进去。另外我们还需要System -> sys
文件夹中找到sys.c
以及sys.h
,同样复制到MyFunction
文件夹中,并导入到工程中。
注:如果导入成功后,在工程中打开文件后中文出现乱码大概是因为IDE的文本编码和我们添加的文本编码不大一样,我们随便打开一个文本输入界面将中文复制粘贴到IDE中就好了。
2、修改代码
-
修改代码适配我们的文件体系,我们需要注意几个地方
(1)引入的文件中,是否有原来的工程中没有的宏定义和变量;
(2)引入的代码函数中,是否有没有明确声明过的函数;
(3)引入的文件是否和原工程有冲突(相同的函数,相同的变量,相同功能的模块等)。
(1)头文件包含,宏定义和变量
点开lcd.h
观察头文件包含:
1 | // Include head files |
因为我暂时没有对与printf()
重定向(在HAL库中若要使用**printf()**函数,除了添加头文件,还需要重定向处理。)所以不需要"stdlib.h"
。而最后两个是正点原子编写的串口通信代码以及延迟函数的代码,这里我没有采用因此我也把他们删掉了,最后只保留下lcd.h
以及font.h
。
这里注意的是,点开lcd.h
后,我们可以看到:
1 | //lcd.h |
我们可以打开sys.h
,文件中主要是宏定义了一些变量.sys.c
中主要是系统初始化的配置代码,我们点开sys.h
1 | //定义一些常用的数据类型短关键字 |
由于我之前没有考虑拿正点原子的代码,所以没有设置简化后的变量类型。但是驱动文件中许多变量都是定义为简化后的名称,因此我们需要把这些简化的变量命名添加在lcd.h
头文件中。
然后我们继续检查,在大约60行的地方,我们可以看到这里有一个宏定义:
1 | //-----------------MCU屏 LCD端口定义---------------- |
其中PBout(n)
函数很显然并不出现在我们的HAL库中(读者可以右键打开声明或者对F4xx等配置文件搜索PBout函数,会发现不存在这个函数)我们点开sys.h
文件可以发现:
1 | //PBout() function declaration |
这是通过寄存器控制PB15的输出——是否点亮LCD背光。如果要直接用的话可以直接将对应的宏定义放入,其中GPIOB_BASE``GPIOB_ODR_Addr``GPIO_IDR_Addr
均可以在配置文件中找到。但是BIT_ADDR()
这个函数并没有找到,猜测可能是换了名字,但是我还没找到对应的函数。因此这里我将PBout
的宏定义注释掉,采用HAL_GPIO_WritePin()
函数来开启LCD的背光。(不过还没尝试用PWM输出来控制LCD背光的亮度,以后用到再拿出来说说吧(挖坑)😂 )
然后这个部分算是修改好了,我们接下来第二步。
(2)函数
搞定宏定义和变量后,我们还需要检查有没有我们之前没有声明过的函数出现在下面。
不过一开始看这个庞大的代码大家或多或少会有点害怕,但是如果阅读过lcd文件中相关的代码,我们可以发现大部分代码都是通过读写寄存器来控制的,而这些代码都是底层配置文件中已经有的,所以我们需要改动的地方很少。我们可以通过卡头包含的头文件来猜测。在我们已经删掉了这三个函数:
1 |
其中stdilb.h
以及usart.h
估计是用来定义printf()
的,我这里并没有采用这个函数,因此删掉头文件后,我们还需要在这个文件中查找printf()
函数,看lcd.c
中是否调用了printf()
函数。我们点开搜索,啪的一下很快啊,我们就可以看到在函数LCD_Init()
函数中调用了:
1 | void LCD_Init(void) |
我们直接注释掉就可以了。
然后我们还有一个头文件delay.h
,根据该文件我们可以知道这个文件主要是一些延迟函数的设置。我们点开这个文件可以看到
1 | //delay.h |
我们对这个文件进行检索,可以发现该文件中有delay_ms()
和delay_us()
两个函数的调用。这里有两种处理方法,第一种是讲这些函数的原型拷贝到我们的lcd.c
文件中。第二种方法是替代这些函数,用HAL库自带的HAL_Delay()
函数替代,不过注意这个函数延迟时间的单位是毫秒。但我还是厚着脸皮四舍五入全改成HAL_Delay()
函数。发现好像没多大差别。读者可以自己选择用哪一种方式进行修改。
(3)检查模块冲突
我们已经检查了变量、宏定义以及函数调用,按道理来说应该可以直接用了。但是如果你按下编译,IDE仍然会报错。这是由于STM32cubeIDE自带配置引脚的功能,因此我们最开始的引脚初始化在我们Generate Code
以后就已经生成好了代码。而正点原子的文件中,有一部分函数初始化时候就自带了引脚的配置,这样会产生配置函数之间的冲突。因此我们只需要在IDE中正确配置引脚然后将lcd.c
中的相关引脚配置删掉或者注释掉即可。
观察lcd.c
文件,我们可以发现函数HAL_SRAM_MspInit()内置了引脚的配置,因此我们将这一段注释掉即可。然后我们就可以点击编译了,看到编译成功,说明我们缝合已经可以了。下面开始写点测试代码试试吧!
三、画个小人写点字
上面我们应把相应的驱动代码缝合好了,我们可以写点代码测试一下。这里我打算画个小人然后让小人说两句话。为了实现这个小目标🚩我们需要简单一下驱动文件中自带的绘图函数。读者若之前有用过类似的图形函数,可以直接点开lcd.c
或者lcd.h
看一看绘图函数都有啥,这里简单列几个:(找到需要的函数点开声明可以查看原理)
1 | void LCD_Init(void); //初始化 |
我们在main函数中写到:
1 | HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET); |
然后我们点击编译,0error。如果这时候出现LCD_ShowString()
警告的话是因为显示内容的指针类型为u8,而我们"…"的为字符串类型,因此会报错。可以尝试添加强制修改(uint8_t)
我们来看看最后的效果:
nice!
END