系列文章目录
前言
前一段时间有小伙伴问我关于LCD的问题,看了一下LCD的原理看完头大。但是再看看MCU的手册,其实发现也没有那么难。下面就跟我一起来看看吧。一、新建工程
请参照第二章第一节新建工程
——》第二章 点亮第一个LED灯
二、LCD了解
1.LCD原理
在实际的液晶模拟驱动电压中,有几个参数非常关键:
交流电压,液晶分子是需要交流信号来驱动的,长时间的直流电压加在液晶分子两端, 会影响液晶分子的电气化学特性,引起显示模糊,寿命的减少,其破坏性为不可恢复。扫描频率,直接驱动液晶分子的交流电压的频率一般在 60~100Hz 之间,具体是依据 LCD Panel 的面积和设计而定,频率过高,会导致驱动功耗的增加,频率过低,会导致显示闪烁,同时如果扫描频率同光源的频率之间有倍数关系,
则显示也会有闪烁现象出现。
液晶分子是一种电压积分型材料,它的扭曲程度(透光性)仅仅和极板间电压的有效值有关, 和充电波形无关。电压的有效值用 COM/SEG 之间的电压差值的均方根 VRMS 表示:
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):
该图对应的是 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 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 共用引脚数据位产生。
2.寄存器了解
LCD 控制寄存器
LCD 驱动器 COM 和 SEG 口可以提供多种驱动电流选择以适应不同 LCD 面板 的需求。通过设置 SLCDC0 寄存器中 ISEL0 位和 ISEL1 位可以配置不同的偏压 电阻。所有 COM 和 SEG 引脚和 I/O 引脚共用,可分别通过 SLCDCn 寄存器的 相应引脚功能选择位选择 COM 和 SEG 引脚。
三、LCD显示实现
1.代码实现
以上的内容是不是让你很懵逼?看不懂没关系,跟着我的代码来一起了解吧。
我们实现的功能:点亮LCD屏从0-200滚动显示,我这里用的是TN数码显示屏。
假如我要在一号位点亮数值:1,那我们就需要选中COM1和1B、CO3和1C。在二号位点亮数值:2,那我们就需要选中COM1和2A、COM1和2B、COM3和2C、COM4和2D。
当数值需要滚动时,我们就要依次选中COM1、COM2、COM3、COM4。然后在选中的COM下,使能ABCD等。这个原理类似于点亮LDE数码管或者是矩阵式的LED灯。
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