HT合泰单片机入门教程(第七章 LCD)

本文主要介绍了HT单片机驱动LCD的工作原理,包括LCD的交流电压、扫描频率、占空比和偏置概念,以及如何通过设置单片机的LCD控制寄存器来实现显示。还提到了代码实现LCD显示,以滚动显示0-200为例,帮助理解LCD驱动过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


系列文章目录

第一章 HT单片机环境搭建

第二章 点亮第一个LED灯

第三章 按键

第四章 定时器

第五章 PWM

第六章 时基中断

第七章 LCD


前言

前一段时间有小伙伴问我关于LCD的问题,看了一下LCD的原理看完头大。但是再看看MCU的手册,其实发现也没有那么难。下面就跟我一起来看看吧。

一、新建工程

请参照第二章第一节新建工程
——》第二章 点亮第一个LED灯

二、LCD了解

1.LCD原理

在实际的液晶模拟驱动电压中,有几个参数非常关键:
交流电压,液晶分子是需要交流信号来驱动的,长时间的直流电压加在液晶分子两端, 会影响液晶分子的电气化学特性,引起显示模糊,寿命的减少,其破坏性为不可恢复。扫描频率,直接驱动液晶分子的交流电压的频率一般在 60~100Hz 之间,具体是依据 LCD Panel 的面积和设计而定,频率过高,会导致驱动功耗的增加,频率过低,会导致显示闪烁,同时如果扫描频率同光源的频率之间有倍数关系,
则显示也会有闪烁现象出现。
LCD
液晶分子是一种电压积分型材料,它的扭曲程度(透光性)仅仅和极板间电压的有效值有关, 和充电波形无关。电压的有效值用 COM/SEG 之间的电压差值的均方根 VRMS 表示:
LCD
LCD 类单片机内嵌的 LCD driver(液晶驱动器),正是通过系统的控制,按照用户定义的显示图案,产生点亮 LCD(Liquid Crystal Display,液晶)所需的仿真驱动波形,接到 LCD Panel(液晶显示屏)上点亮对应的像素而达到显示的效果。

占空比(Duty)
该项参数一般也称为 Duty 数或 COM 数。由于 STN/TN 的 LCD 一般是采用时分动态扫描的驱动模式,在此模式下,每个 COM 的有效选通时间与整个扫描周期的比值即占空比(Duty)是固定的,等于 1/COM 数。

偏置(Bias)
LCD 的 SEG/COM 的驱动波形为模拟信号,而各档模拟电压相对于 LCD 输出的最高电压的比例称为偏置,而一般来讲,Bias 是以最低一档与输出最高电压的比值来表示,如下图所示(1/4 Duty,1/3 Bias):
LCD
该图对应的是 1/4 duty,1/3 bias 的液晶驱动波形,COM 数为 4,每个COM 的有效选通时间与整个扫描周期的比值(Duty)=1/4,驱动波形的模拟电压共分 3 档,V3 位输出最高电压,V2、V1 为输出中间电压,并且 V1/V3=1/3,所以上述波形图对应的 Duty=1/4,Bias=1/3。
一般而言,Bias 和 Duty 之间是有一定关系的,Duty 数越多,每根 COM 对应的扫描时间变短,而要达到同样的显示亮度和显示对比度,Von 的电压就要提高,选准位和非选准位的差异需要加大,即 Bias 需要加大,Duty 和 Bias 间有一经验公式:在这里插入图片描述
带 SCOM 和 SSEG 功能的 LCD
该 系 列 单 片 机 具 有 驱 动 外 部 LCD 面 板 的 能 力。LCD 驱 动 的 COM 脚 SCOM0~SCOM5 和 SEG 脚 SSEG0~SSEG19 或 SSEG0~SSEG23 与 I/O 口共用。 LCD 信号 COM 和 SEG 由应用程序实现。
LCD 操作
该系列单片机通过设置相关 I/O 引脚为 COM 引脚和 SEG 引脚,以驱动外部 LCD 面板。LCD 驱动功能是由几个 LCD 控制寄存器一起控制的,另外,这些 寄存器还可设置 LCD 的开启和关闭以及 SCOM 和 SSEG 引脚的 R-type 偏压电流,使得 LCD 驱动器 COM 和 SEG 引脚输出 VSS、(1/3)VDD、(2/3)VDD 和 VDD 的电压,从而实现 1/3 bias LCD 的显示。
SLCDC0 寄存器中的 LCDEN 位是 LCD 驱动的主控制位,它与 COMnEN 和 SEGnEN 位搭配共同选择哪些输入 / 输出引脚用于 LCD 驱动。需注意的是,输 入 / 输出端口控制寄存器不需要设置为输出以使能 LCD 驱动操作。
LCD
LCD Frames 一个完整的 LCD 波形周期包含两个 Frame, 即 Frame 0 和 Frame 1。下面将做出 详细解释:
Frame 0 :
当要输出 Frame 0 的波形,需将 SLCDC0 寄存器中的 FRAME 位设为 0。 在 Frame 0, COM 信号输出可以是 VDD, 或是 VBIAS = (1/3 )×VDD。SEG 信号输出 可以是 VSS, 或是 VBIAS = (2/3 )×VDD。
Frame 1 :
当要输出 Frame 1 的波形,需将 SLCDC0 寄存器中的 FRAME 位设为 1。 在 Frame 1, COM 信号输出可以是 VSS, 或是 VBIAS =(2/3 )×VDD。SEG 信号输出可 以是 VDD, 或是 VBIAS = (1/3 )×VDD。 COMn 的波形,由应用程序设定 SLCDC0 寄存器中的 FRAME 位控制,及相应 的 I/O 共用引脚数据位决定 COMn 引脚目前输出是 VDD, VSS 或 VBIAS。SEGm 的 波形,由应用程序设定 FRAME 位控制,及相应的 I/O 共用引脚数据位决定 SEGm 引脚目前输出是 VDD, VSS 或 VBIAS。 典型的1/3 bias LCD波形由应用程序以及LCD电压选择电路产生,波形图如下。 请注意,图中“1”代表点亮 LCD 像素。SCOM0~SCOM5 引脚的 COM 信号极 性为“0”或“1”,由相应的 I/O 共用引脚数据位产生。
LCD

2.寄存器了解

LCD 控制寄存器
LCD 驱动器 COM 和 SEG 口可以提供多种驱动电流选择以适应不同 LCD 面板 的需求。通过设置 SLCDC0 寄存器中 ISEL0 位和 ISEL1 位可以配置不同的偏压 电阻。所有 COM 和 SEG 引脚和 I/O 引脚共用,可分别通过 SLCDCn 寄存器的 相应引脚功能选择位选择 COM 和 SEG 引脚。
LCD

三、LCD显示实现

1.代码实现

以上的内容是不是让你很懵逼?看不懂没关系,跟着我的代码来一起了解吧。
我们实现的功能:点亮LCD屏从0-200滚动显示,我这里用的是TN数码显示屏。
LCD
假如我要在一号位点亮数值:1,那我们就需要选中COM1和1B、CO3和1C。在二号位点亮数值:2,那我们就需要选中COM1和2A、COM1和2B、COM3和2C、COM4和2D。
当数值需要滚动时,我们就要依次选中COM1、COM2、COM3、COM4。然后在选中的COM下,使能ABCD等。这个原理类似于点亮LDE数码管或者是矩阵式的LED灯。
LCD

man.c

#include "HT66F0185.h"
#include "LCD.h"

	
/*******************************************************************************
 * @fn		delayMs	
 * @brief	延时函数
 * @param	延时时间 单位为ms
 * @return	无
 *******************************************************************************/
void delayMs(unsigned long int ms){
	
	while(ms--)
		GCC_DELAY(2000);//主频8Mhz,执行一条指令为0.5us。一条指令周期等于四条机器周期——》 1/8Mhz * 4 = 0.5us
}

/*******************************************************************************
 * @fn		timeBaseInit	
 * @brief	timebase 初始化
 * @param	无
 * @return	无
 *******************************************************************************/
void timeBaseInit(void){
	 
	_tbc=0xc4;//时基信号  tbc = fsys / 4 = 8MHz / 2 =  4MHz 溢出周期: t = 2^12 / tbc = 2.048ms
	_tb0e=1;//允许时基中断
	_emi=1;//打开总中断
	
}

/*******************************************************************************
 * @fn		tb0	
 * @brief	tb0 为函数名(自己设定) 0x1c为TimeBase0中断向量 2.048ms中断一次
 * @param	无
 * @return	无
 *******************************************************************************/
DEFINE_ISR(tb0,0x1c){
	lcdScan();
}

/*******************************************************************************
 * @fn		main	
 * @brief	主函数
 * @param	无
 * @return	无
 *******************************************************************************/
void main(){
	unsigned char i;
	_wdtc = 0b10101000;//关闭看门狗。直接配置看门狗寄存器,0b代表二进制。
	
	/*关闭复用功能*/
	_csel=0;//比较器共用脚PB5(C+)和PB6(C-)用作I/O
	_cos=1;//设置PA3管脚为IO,而不是比较器输出
	_acerl=0;//引脚(PB3、PA7、PA6、PA5、PA4、PB2、PB1、PB0)不用做A/D输入
	
	/*LCD初始化*/
	lcdInit();
	/*时基初始化*/
	timeBaseInit();
		
	
	while(1){
		
		setLcd(0xff);//lcd全显示
		delayMs(500);//延迟500ms 为了让人眼看见变化
		
		setLcd(0x00);//清空lcd
		delayMs(500);//延迟500ms 为了让人眼看见变化	
		
		/*依次显示小数点等符号*/
		for(i = 0; i < 4; i++){		
			dispMark(i,1);
			delayMs(200);
		}
		
		/*依次消除小数点等符号*/
		for(i = 0; i < 4; i++){
			dispMark(i,0);
			delayMs(200);
		}
		
		/*从0显示到200*/
		for(i = 0; i < 200; i++){		
			dispNum(i);
			delayMs(50);	
		}
	}
}

lcd.h

#ifndef __LCD__H__
#define __LCD__H__

/*
*	此结构采用位域,目的是节省空间。总共使用1个字节
*	假如没有用位域,一共使用8个字节
*/
typedef struct {
	volatile unsigned char b0 : 1;	//对应B段
	volatile unsigned char b1 : 1;	//对应G段	
	volatile unsigned char b2 : 1;	//对应C段
	volatile unsigned char b3 : 1;	//对应D段
	volatile unsigned char b4 : 1;	//对应A段
	volatile unsigned char b5 : 1;	//对应F段
	volatile unsigned char b6 : 1;	//对应E段
	volatile unsigned char b7 : 1;	//对应特殊段(P1或2P或3P或4P)
}bits;

/*
*	使用共用体,目的是省略赋值操作
*/
typedef union{
	bits b;
	unsigned char data;		
}LCD;

//定义lcd的com口对应的io
#define COM1 _pc2
#define COM2 _pa1
#define COM3 _pa3
#define COM4 _pb6

//定义lcd的seg口对应的io
#define SEG1 _pb5
#define SEG2 _pb4
#define SEG3 _pb3
#define SEG4 _pa6
#define SEG5 _pa5
#define SEG6 _pd0
#define SEG7 _pd3
#define SEG8 _pa4

//定义特殊符号是否显示
#define SECPOINT 0  



void dispIndex(unsigned char index,unsigned char num);

void dispNum(unsigned int data);

void dispMark(unsigned char obj,unsigned char on);

void setLcd(unsigned char data);

void lcdInit();

void lcdScan();

#endif

lcd.c

#include "HT66F0185.h"
#include "lcd.h"


/*
*================================================================================				
* HEX:0x7D 	    * HEX:0x05 	   	* HEX:0x5B 		* HEX:0x1F 		* HEX:0x27 		=
*																				=
* BIN:0111 1101 * BIN:0000 0101 * BIN:0101 1011 * BIN:0001 1111 * BIN:0010 0111 =
*================================================================================
* HEX:0x3E 	    * HEX:0x7E 	   	* HEX:0x15 		* HEX:0x7F 		* HEX:0x3F 		=
*																				=	
* BIN:0011 1110 * BIN:0111 1110 * BIN:0001 0101 * BIN:0111 1111 * BIN:0011 1111 =
*================================================================================
*/
							//LCD显示 0	  1	   2	3	 4	  5   6     7	8	 9	依次与下面对应	
const unsigned char dispcode[10] = {0x7D,0x5,0x5B,0x1F,0x27,0x3E,0x7E,0x15,0x7F,0x3F};

LCD LCDBUFF[4];//定义显示缓存,运用共用体省略赋值操作。


/*******************************************************************************
 * @fn		dispIndex	
 * @brief	根据数字查找数组的元素
 * @param	index:显示第几位
 			num:显示的数字
 * @return	无
 *******************************************************************************/
void dispIndex(unsigned char index,unsigned char num){
	if(LCDBUFF[index].b.b7)
		LCDBUFF[index].data = dispcode[num] | 0x80;
	else
		LCDBUFF[index].data = dispcode[num];
}


/*******************************************************************************
 * @fn		dispNum	
 * @brief	显示数字
 * @param	data:0-9999
 * @return	无
 *******************************************************************************/
void dispNum(unsigned int data){
	unsigned char time = 3;
	/*输出个位*/
	dispIndex(time,data % 10);
	/*判断输入数字是第几位数,然后输出*/
	while((data / 10) > 0){
		time -= 1;
		data /= 10;
		dispIndex(time,data % 10);
	}
	

}

/*******************************************************************************
 * @fn		dispMark	
 * @brief	显示标点
 * @param	obj:显示的对象(开头定义)
			on: 0-不显示  1-显示
 * @return	无
 *******************************************************************************/
void dispMark(unsigned char obj,unsigned char on){
	LCDBUFF[obj].b.b7 = on;			
}


/*******************************************************************************
 * @fn		setLcd	
 * @brief	设置LCD显示段
 * @param	data: 0-不显示  0xff-全显示
 * @return	无
 *******************************************************************************/
void setLcd(unsigned char data){
	unsigned char i;
	for(i = 0; i < 4; i++)
		LCDBUFF[i].data = data;	
	
}

/*******************************************************************************
 * @fn		lcdInit	
 * @brief	LCD初始化
 * @param	无
 * @return	无
 *******************************************************************************/
void lcdInit(void){

	
	/*SLCDC0和SLCDC1 寄存器操作 使能SCOM和SEEG功能*/
	_com0en=1;
	_com2en=1;
	_com3en=1;
	_com4en=1;
	_com5en=1;
	/*
	*以上可以简写为:
	*		_slcdc0 &= 0b00001111
	*		_slcdc1 &= 0b11000000
	*/
	
	
	/*SLCDC1 寄存器操作 SCOM或SSEEG引脚功能选择 */
	_comsegs0=0;//选择为com
	_comsegs2=0;//选择为com
	_comsegs3=0;//选择为com
	_comsegs4=0;//选择为com
	_comsegs5=1;//选择为SSEG	
	/*
	*以上可以简写为:
	*		_slcdc1 &= 0b00100000
	*/
	
	
	/*SLCDC2和SLCDC3 寄存器操作 SSEG引脚功能选择*/
	_seg6en=1;
	_seg7en=1;
	_seg9en=1;
	_seg10en=1;
	_seg11en=1;
	_seg14en=1;
	_seg15en=1;
	/*
	*以上可以简写为:
	*		_slcdc2 &= 0b00111011
	*		_slcdc3 &= 0b00000011
	*/	
	
	/*LCD对应引脚设置为输出 
	*以下注释COM和SEG是硬件的名称
	*实际使用的COM和SEG需要根据数据手册查看
	*/
	_pcc2=0;//COM1
	_pac1=0;//COM2
	_pac3=0;//COM3
	_pbc6=0;//COM4
	
	_pbc5=0;//SEG1
	_pbc4=0;//SEG2
	_pbc3=0;//SEG3
	_pac6=0;//SEG4
	_pac5=0;//SEG5
	_pdc0=0;//SEG6
	_pdc3=0;//SEG7
	_pac4=0;//SEG8
	

	_lcden = 1;//LCD使能
	_isel0 = 1;//电流选择最小驱动
	_isel1 = 1;
	_frame = 0;//默认frame = 0	
	
}

/*******************************************************************************
 * @fn		lcdScan	
 * @brief	LCD扫描
 * @param	无
 * @return	无
 *******************************************************************************/
void lcdScan(void){
	
	static unsigned char scanstep=0;
	
	if(_frame){//正反帧输出
	
		/*扫描阶段*/
		switch(scanstep){
			case 0:			
				/*选中第一行*/
				COM1 = 1;
				COM2 = 0;
				COM3 = 0;
				COM4 = 0;
				
				/*选中1的A、B段*/
				if(LCDBUFF[0].b.b4==0)SEG1 = 0;else SEG1 = 1;
				if(LCDBUFF[0].b.b0==0)SEG2 = 0;else SEG2 = 1;
				/*选中2的A、B段*/
				if(LCDBUFF[1].b.b4==0)SEG3 = 0;else SEG3 = 1;
				if(LCDBUFF[1].b.b0==0)SEG4 = 0;else SEG4 = 1;
				/*选中3的A、B段*/
				if(LCDBUFF[2].b.b4==0)SEG5 = 0;else SEG5 = 1;
				if(LCDBUFF[2].b.b0==0)SEG6 = 0;else SEG6 = 1;
				/*选中4的A、B段*/
				if(LCDBUFF[3].b.b4==0)SEG7 = 0;else SEG7 = 1;
				if(LCDBUFF[3].b.b0==0)SEG8 = 0;else SEG8 = 1;
				 
				/*进行下一行的扫描*/
				scanstep = 1;	
				break;
				
			case 1:				
				/*选中第二行*/
				COM1 = 0;
				COM2 = 1;
				COM3 = 0;
				COM4 = 0;
				
				/*选中1的F、G段*/
				if(LCDBUFF[0].b.b5 == 0)SEG1 = 0;else SEG1 = 1;
				if(LCDBUFF[0].b.b1 == 0)SEG2 = 0;else SEG2 = 1;
				/*选中2的F、G段*/
				if(LCDBUFF[1].b.b5 == 0)SEG3 = 0;else SEG3 = 1;
				if(LCDBUFF[1].b.b1 == 0)SEG4 = 0;else SEG4 = 1;
				/*选中3的F、G段*/
				if(LCDBUFF[2].b.b5 == 0)SEG5 = 0;else SEG5 = 1;
				if(LCDBUFF[2].b.b1 == 0)SEG6 = 0;else SEG6 = 1;
				/*选中4的F、G段*/
				if(LCDBUFF[3].b.b5 == 0)SEG7 = 0;else SEG7 = 1;
				if(LCDBUFF[3].b.b1 == 0)SEG8 = 0;else SEG8 = 1;
				 
				/*进行下一行的扫描*/
				scanstep = 2;
				break;
				
			case 2:
				/*选中第三行*/
				COM1 = 0;
				COM2 = 0;
				COM3 = 1;
				COM4 = 0;
				
				/*选中1的E、C段*/
				if(LCDBUFF[0].b.b6 == 0)SEG1 = 0;else SEG1 = 1;
				if(LCDBUFF[0].b.b2 == 0)SEG2 = 0;else SEG2 = 1;
				/*选中2的E、C段*/
				if(LCDBUFF[1].b.b6 == 0)SEG3 = 0;else SEG3 = 1;
				if(LCDBUFF[1].b.b2 == 0)SEG4 = 0;else SEG4 = 1;
				/*选中3的E、C段*/
				if(LCDBUFF[2].b.b6 == 0)SEG5 = 0;else SEG5 = 1;
				if(LCDBUFF[2].b.b2 == 0)SEG6 = 0;else SEG6 = 1;
				/*选中4的E、C段*/
				if(LCDBUFF[3].b.b6 == 0)SEG7 = 0;else SEG7 = 1;
				if(LCDBUFF[3].b.b2 == 0)SEG8 = 0;else SEG8 = 1;
				
				/*进行下一行的扫描*/
				scanstep = 3;
				break;
				
			case 3:
				/*选中第四行*/
				COM1 = 0;
				COM2 = 0;
				COM3 = 0;
				COM4 = 1;
				
				/*选中1的D、P1段*/
				if(LCDBUFF[0].b.b7 == 0)SEG1 = 0;else SEG1 = 1;
				if(LCDBUFF[0].b.b3 == 0)SEG2 = 0;else SEG2 = 1;
				/*选中2的D、2P段*/
				if(LCDBUFF[1].b.b7 == 0)SEG3 = 0;else SEG3 = 1;
				if(LCDBUFF[1].b.b3 == 0)SEG4 = 0;else SEG4 = 1;
				/*选中3的D、3P段*/
				if(LCDBUFF[2].b.b7 == 0)SEG5 = 0;else SEG5 = 1;
				if(LCDBUFF[2].b.b3 == 0)SEG6 = 0;else SEG6 = 1;
				/*选中4的D、4P段*/
				if(LCDBUFF[3].b.b7 == 0)SEG7 = 0;else SEG7 = 1;
				if(LCDBUFF[3].b.b3 == 0)SEG8 = 0;else SEG8 = 1;
				/*回到第一行扫描*/				
				scanstep = 0;
				break;
				
			default:	
				;
		}
		_frame = 0;//下一次反向输出
	}
	else{
		_frame = 1;
	}	
	
}

工程链接—》LCD


总结

其中需要注意的是: 1.LCD的扫描时间(60HZ~100HZ) 2.LCD是交流电驱动,所以有使用_frame进行反向。 3.代码中共用体和位寻址的使用 4.原理其实和矩阵LED灯是差不多的
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值