基于ARM的LCD显示(附硬件电路和源码)
扫描二维码
随时随地手机看文章
1.项目介绍
不管是LED数码管,还是段码式LCD液晶显示屏,在显示上都是由各个段位组合显示成我们想要的字符的。本项目中采用的是4位段码式LCD显示屏,每一位由A~G共7个段位组成,另外还有P1~P4共4个点段位。为了实现在显示程序上的可移植性和通用性,本项目通过数据结构的设计形式结合驱动LCD显示芯片内部RAM的操作方法,给出一种既适合LED数码管,也适合段码式LCD液晶显示屏的程序设计方法。
2.开发环境
软件开发环境:Keil MDK 5.25,HC32005_SDK
硬件设计软件:立创EDA
3.硬件设计
3.1.原理图设计
硬件设计上使用了华大的Cortex-M0+内核的HC32F005C6PA作为主控芯片,整个系统电压工作在3.3V~5V之间,简单的电源设计和复位电路,使用内部24MHz晶振,通用的SWD在线调试和下载接口,在硬件外围电路上省了不少的空间。4个用户自定义按键、2个串口全部引出,一个连接SP232芯片进行RS-232通讯,另一个直接引出TTL作调试监控用、还有一个4位的LCD段码式液晶屏,使用TM1621驱动芯片,作为显示接口。
3.2.PCB设计
3.3.焊接调试
3.4.华大MCU初次使用,调试体会
所有的芯片,我都喜欢从新建工程开始研究,华大的MCU也不例外,官方也给出了MCU开发工具用户手册,可以对照手册一步一步的新建工程。但是让我困惑的第一件事是我调用delay函数进行GPIO口翻转的实验与官网给的DEMO例程实际的效果不一样,很明显是我的代码有问题,GPIO口通过示波器测出来的速度明显快相当多,因为第一次用,真是无从下手,一步步的从startup_hc32f005.s文件分析起,后来看到system_hc32f005.c,发现KEIL创建自动生成的这两个文件和官方的DEMO例程不一样,也是因为system_hc32f005.c文件中的一些初始化和调用导致了我芯片启动后时钟配置不正确,导致了GPIO口翻转过快的问题。后面也是因为这边的两个文件其它部分的不一致导致了串口异常的问题,所以个人建议是:如果你要自建工程,还是用官方DEMO中的startup_hc32f005.s、system_hc32f005.c和system_hc32f005.h这3个文件覆盖KEIL自动生成的这几个文件。第2个需要注意的地方就是如果你要使用定时器、串口功能模块之前,一定要开启相应模块对应的时钟,这个大家都清楚;但别忘了,华大的MCU还需要配置PCLK等一些初始化操作,具体一可以参考代码board.c文件中的BSP_InitSysClock函数。第3个就是串口模式的选择和波特率的设定,这个跟其它的MCU,比如说ST之类的,有点不太一样,具体一可以把这一块的用户手册看一下,有助于加深理解,我也是想当然跳了不少坑……
当然华大半导体作为国产芯片,个人感觉还是不错的,通过这一个项目的前期调试,耐下心来,一两天就能上手了;性价比也是相当的高,值得推荐!
4.程序设计
4.1.TM1621驱动设计
TM1621芯片内部带有静态显示内存(RAM),RAM以32*4位格式存储所显示的数据。RAM的数据直接映像到LCD驱动器,也就是说可以通过修改RAM的数据内容,来修改液晶屏显示的内容。对应的COM和SEG所对应的RAM映像图可以参考TM1621的数据手册。对于TM1621我们有4个基础的主要函数,分别如下所述:
4.1.1.void TM1621_Write(uint16_t value, uint8_t length),这是TM1621最底层的函数, 是将value这个数据的高length个bit位写入到TM1621芯片内部,例如:
TM1621_Write(0x8020/*0b 1000 0000 0010 0000*/, 0x0C); // SYS_EN,将0x8020这个数值的高12位写入到TM1621芯片
4.1.2.void TM1621_UpdateRAM(void),更新TM1621显示RAM,将全局数组暂存储的RAM数据统一一次性的更新到TM1621芯片内,使用的是TM1621的WRITE命令,命令概述详见TM1621数据手册。
4.1.3.void TM1621_ModifyRAM(uint8_t index, uint8_t bit, uint8_t flag),修改TM1621显示RAM,根据数据结构及算法确认修改的TM1621显示RAM的下标和位下标,flag是置位和清除的操作标志。统一修改完成后,需要调用TM1621_UpdateRAM函数更新TM1621的显示RAM,更新液晶屏显示。
4.1.4.void TM1621_Init(void),TM1621芯片初始化配置操作,详见程序设计说明。
4.2.显示设计
4.2.1.定义显示字符和段位的映射表,如下代码给出了38个常用的显示字符:
typedef struct
{
char ch; /*字符索引*/
uint8_t segment[8]; /*字符对应的段编码*/
} DIGITRON_STRUCT;
const DIGITRON_STRUCT DIGITRON_TABLE[38] =
{
{' ', {0, 0, 0, 0, 0, 0, 0, 0}},
{'0', {1, 1, 1, 1, 1, 1, 0, 0}},
{'1', {0, 1, 1, 0, 0, 0, 0, 0}},
{'2', {1, 1, 0, 1, 1, 0, 1, 0}},
{'3', {1, 1, 1, 1, 0, 0, 1, 0}},
{'4', {0, 1, 1, 0, 0, 1, 1, 0}},
{'5', {1, 0, 1, 1, 0, 1, 1, 0}},
{'6', {1, 0, 1, 1, 1, 1, 1, 0}},
{'7', {1, 1, 1, 0, 0, 0, 0, 0}},
{'8', {1, 1, 1, 1, 1, 1, 1, 0}},
{'9', {1, 1, 1, 1, 0, 1, 1, 0}},
{'A', {1, 1, 1, 0, 1, 1, 1, 0}},
{'b', {0, 0, 1, 1, 1, 1, 1, 0}},
{'c', {0, 0, 0, 1, 1, 0, 1, 0}},
{'C', {1, 0, 0, 1, 1, 1, 0, 0}},
{'d', {0, 1, 1, 1, 1, 0, 1, 0}},
{'E', {1, 0, 0, 1, 1, 1, 1, 0}},
{'F', {1, 0, 0, 0, 1, 1, 1, 0}},
{'g', {1, 1, 1, 1, 0, 1, 1, 0}},
{'H', {0, 1, 1, 0, 1, 1, 1, 0}},
{'h', {0, 0, 1, 0, 1, 1, 1, 0}},
{'i', {0, 0, 1, 0, 0, 0, 0, 0}},
{'I', {0, 0, 0, 0, 1, 1, 0, 0}},
{'J', {0, 1, 1, 1, 1, 0, 0, 0}},
{'l', {0, 0, 0, 0, 1, 1, 0, 0}},
{'L', {0, 0, 0, 1, 1, 1, 0, 0}},
{'n', {0, 0, 1, 0, 1, 0, 1, 0}},
{'o', {0, 0, 1, 1, 1, 0, 1, 0}},
{'O', {1, 1, 1, 1, 1, 1, 0, 0}},
{'P', {1, 1, 0, 0, 1, 1, 1, 0}},
{'q', {1, 1, 1, 0, 0, 1, 1, 0}},
{'r', {0, 0, 0, 0, 1, 0, 1, 0}},
{'S', {1, 0, 1, 1, 0, 1, 1, 0}},
{'t', {0, 0, 0, 1, 1, 1, 1, 0}},
{'u', {0, 0, 1, 1, 1, 0, 0, 0}},
{'U', {0, 1, 1, 1, 1, 1, 0, 0}},
{'y', {0, 1, 1, 1, 0, 1, 1, 0}},
{'-', {0, 0, 0, 0, 0, 0, 1, 0}},
};
4.2.2.定义段位的组成关系,如下给出了显示一位字符所需要的一个段组合,这些定义可以参照段码式LCD液晶屏的PIN引脚关系定义:
const char DISPLAY_DIGIT_TABLE[4][7][3] =
{
{"1A", "1B", "1C", "1D", "1E", "1F", "1G"},
{"2A", "2B", "2C", "2D", "2E", "2F", "2G"},
{"3A", "3B", "3C", "3D", "3E", "3F", "3G"},
{"4A", "4B", "4C", "4D", "4E", "4F", "4G"},
};
const char DISPLAY_POINT_TABLE[4][3] =
{
"P1", "P2", "P3", "P4"
};
直白了说,就是我要在第一位上显示一个‘8’这个字符,那这个字符肯定是由"1A", "1B", "1C", "1D", "1E", "1F", "1G"这7个段位组合显示而成的。
4.2.3.定义段码式LCD液晶屏PIN引脚关系与TM1621驱动芯片显示RAM的对应关系表,如下代码所示:
const char LCD_CS_TABLE[4][32][3] =
{
{"1F","1A","2F","2A","3F","3A","4F","4A"," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "},
{"1G","1B","2G","2B","3G","3B","4G","4B"," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "},
{"1E","1C","2E","2C","3E","3C","4E","4C"," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "},
{"P1","1D","P2","2D","P3","3D","P4","4D"," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "},
};
这个数组的每一行对应着一个COM,每一列对应着一个SEG,这样一个数据就直接映射成TM1621显示RAM中的每一个BIT位;但需要注意的是,这个数据是需要根据原理图的COM和SEG引脚的设计而定的;本项目在硬件设计上段码式LCD液晶的COM0~COM3分别对应的也是TM1621驱动芯片的COM0~COM3,所以在行的对应关系上与段码式LCD液晶屏PIN引脚关系保持一致,如果顺序不一致,那在数组的行顺序要也要做相应的对应排序;SEG段的数组对应关系设计和COM段的思想是一样的。
4.2.4.void DISPLAY_SearchCS(const char *str, uint8_t *com, uint8_t *seg),这是最关键的一个函数,结合硬件电路设计,查找某个显示字符对应的每一个段编码在TM1621显示RAM的下标和位下标。
4.2.5.void DISPLAY_Digit(uint8_t index, char ch, uint8_t blink, uint8_t flag),这是位显示函数,index是位下标,ch是需要显示字符,blink是闪烁标志,flag是刷新标志;通过这个函数,我们可以调用DISPLAY_Digit(0, '8', 0, 1);在第一个位置上显一个不闪烁的8字。
5.程序移植性和通用性
在相同硬件驱动的情况下,对应不同设计的段码式LCD,我们只需要根据段码式LCD液晶屏PIN引脚关系,再结合硬件原理图电路设计,修改LCD_CS_TABLE数组即可实现如上显示功能。
有人说,我驱动LED数码写没有用驱动芯片,那怎么办呢?只是用了一个74HC595,或者是单片机引脚直接控制的,怎么办呢?对于这种情况,我们的数据结构和程序设计思想是一样的,我们可以定义一个显示缓存,在显示的时候,将这个显示缓存通过定时器的方式不断的进行刷新到LED数码管,进行显示;当然这边不需要TM1621驱动程序了,应该替换成驱动数码管显示的驱动程序。