欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LCD

程序员文章站 2022-03-05 11:35:41
...

一、显示原理

LCD屏内部采用了一种液晶材料,液晶分子介于固态和液态之间。在自然状态下,液晶具有光学各向异性,但在电磁场作用下会呈现各向同性,LCD屏就是利用了液晶分子的这种物理结构和光学特性制造而成的。

屏的顶部和底部是一对互相垂直的偏振片,中间是液晶分子涂层。当液晶分子两端电压为0时,液晶分子呈现螺旋状态,自然光跟随液晶分子的旋光特性旋转进入到屏另一端的偏振片上,刚好旋转90°后可以穿过。此时,由于光线可以通过,所以人眼看上去屏是白色的。
LCD

图1 LCD显示原理

给屏的两端加上电压时,中间的液晶分子变成了同向排列的结构。此时,由于没有了旋转结构,进入到屏里面的光也就无法旋转90°通过另一端的偏振片。所以,人眼会看到屏幕是黑色的(也就是点亮LCD屏)。

二、控制原理

虽然给屏的两端加上一定电压就可以点亮LCD屏,但是,驱动LCD屏并不是只需要给一个固定电压就可以点亮,这是LCD屏本身的特性决定的。如果给LCD屏固定电压会使得LCD屏出现电泳效应,很快就坏掉,所以要以交流电压的方式驱动LCD。这里有两个参数:

  • 偏压比(Bias):

    驱动LCD屏COM/SEG口的电压并不是一个恒定的电压值,而是会分几个档位,比如分3个档位,就是1/3Bias。
    
  • 占空比(Duty):

    这个参数和COM有关,由于LCD是以动态方式驱动,因此每个COM选通的时间就是整个扫描周期时间的1/COM。比如有4个COM,就是1/4Duty。
    

示例(1/4Duty 1/3Bias):
LCD

图2 1/4Duty 1/3Bias 驱动波形

要选中LCD中的某一段点亮,就要使它的COM和SEG之间的电压差大于LCD显示屏的阈值,如图2所示,COM0-SEG0前1/4周期中的电压差分别为±VDD,即COM0SEG0所对应的区段被点亮。将电压分成几级的目的是防止对比度不均匀,在不点亮的区段上仍加有一定的电压,可以降低点亮区段产生的交叉干扰和防止对比度不均匀。

三、LCD驱动

以华大HC32LFx3x-STK-V2.0开发板为例,该开发板使用的是HC32L136K8TA芯片,最多具有8个公用端子(COM)和40个区段端子(SEG),用以驱动160(4×40)或288(8×36)个LCD图像元素。

华大单片机有两种显示模式,对应的数据存储方式不同,这里采用的是显示模式0、1/4Duty。
LCD

用跳线外接一个4*26的LCD屏,其真值表如下
LCD
LCD

图3 LCD屏真值表

LCD管脚配置(与LCD相关的端口都要配置为模拟端口)

void Lcd_SetPortAnalog(void)
{
    Gpio_SetAnalogMode(GpioPortA, GpioPin9);   ///< COM0
    Gpio_SetAnalogMode(GpioPortA, GpioPin10);  ///< COM1
    Gpio_SetAnalogMode(GpioPortA, GpioPin11);  ///< COM2
    Gpio_SetAnalogMode(GpioPortA, GpioPin12);  ///< COM3
	
    Gpio_SetAnalogMode(GpioPortA, GpioPin8);   ///< SEG0
    Gpio_SetAnalogMode(GpioPortC, GpioPin9);   ///< SEG1
    Gpio_SetAnalogMode(GpioPortC, GpioPin8);   ///< SEG2
    Gpio_SetAnalogMode(GpioPortC, GpioPin7);   ///< SEG3
    Gpio_SetAnalogMode(GpioPortC, GpioPin6);   ///< SEG4
    Gpio_SetAnalogMode(GpioPortB, GpioPin15);  ///< SEG5
    Gpio_SetAnalogMode(GpioPortB, GpioPin14);  ///< SEG6
    Gpio_SetAnalogMode(GpioPortB, GpioPin13);  ///< SEG7
    Gpio_SetAnalogMode(GpioPortB, GpioPin12);  ///< SEG8
    Gpio_SetAnalogMode(GpioPortB, GpioPin11);  ///< SEG9
    Gpio_SetAnalogMode(GpioPortB, GpioPin10);  ///< SEG10
    Gpio_SetAnalogMode(GpioPortB, GpioPin2);   ///< SEG11
    Gpio_SetAnalogMode(GpioPortB, GpioPin1);   ///< SEG12
    Gpio_SetAnalogMode(GpioPortB, GpioPin0);   ///< SEG13
    Gpio_SetAnalogMode(GpioPortC, GpioPin5);   ///< SEG14
    Gpio_SetAnalogMode(GpioPortC, GpioPin4);   ///< SEG15
    Gpio_SetAnalogMode(GpioPortA, GpioPin7);   ///< SEG16
    Gpio_SetAnalogMode(GpioPortA, GpioPin6);   ///< SEG17
    Gpio_SetAnalogMode(GpioPortA, GpioPin5);   ///< SEG18
    Gpio_SetAnalogMode(GpioPortA, GpioPin4);   ///< SEG19
    Gpio_SetAnalogMode(GpioPortA, GpioPin3);   ///< SEG20
    Gpio_SetAnalogMode(GpioPortA, GpioPin2);   ///< SEG21
    Gpio_SetAnalogMode(GpioPortA, GpioPin1);   ///< SEG22
    Gpio_SetAnalogMode(GpioPortA, GpioPin0);   ///< SEG23
    Gpio_SetAnalogMode(GpioPortC, GpioPin3);   ///< SEG24
    Gpio_SetAnalogMode(GpioPortC, GpioPin2);   ///< SEG25
}

LCD参数配置

void LCD_Config(void)
{
	stc_lcd_cfg_t   LcdInitStruct;
	stc_lcd_segcom_t   LcdSegCom;
	
	LcdSegCom.u32Seg0_31 = 0xfc000000;                             ///< 配置LCD_POEN0寄存器 使能SEG0~SEG25
	LcdSegCom.stc_seg32_51_com0_8_t.seg32_51_com0_8 = 0xffffffff;	 ///< 初始化LCD_POEN1寄存器 全部关闭输出端口
	LcdSegCom.stc_seg32_51_com0_8_t.segcom_bit.Com0_3 = 0;         ///< 使能COM0~COM3
	LcdSegCom.stc_seg32_51_com0_8_t.segcom_bit.Mux = 0;            ///< Mux=0,Seg32_35=0,BSEL=1表示:选择外部电容工作模式,内部电阻断路
	LcdSegCom.stc_seg32_51_com0_8_t.segcom_bit.Seg32_35 = 0x0;     
	Lcd_SetSegCom(&LcdSegCom);                                     ///< LCD COMSEG端口配置
	
	LcdInitStruct.LcdBiasSrc = LcdExtCap;           ///< 外部电容分压模式
//	LcdInitStruct.LcdBiasSrc = LcdExtRes;           ///< 外部电阻分压模式
//	LcdInitStruct.LcdBiasSrc = LcdInResHighPower;   ///< 内部电阻分压,大功耗模式
	LcdInitStruct.LcdBias = LcdBias3;               ///< 1/3 Bias
	LcdInitStruct.LcdDuty = LcdDuty4;               ///< 1/4duty
	LcdInitStruct.LcdClkSrc = LcdRCL;               ///< LCD时钟选择RCL
	LcdInitStruct.LcdCpClk = LcdClk2k;              ///< 电压泵时钟频率选择2kHz
	LcdInitStruct.LcdScanClk = LcdClk256hz;         ///< LCD扫描频率选择256Hz
	LcdInitStruct.LcdMode = LcdMode0;               ///< 选择模式0
	LcdInitStruct.LcdEn = LcdEnable;                ///< 使能LCD模块
	Lcd_Init(&LcdInitStruct);
}

建立LCD映射表

  1. 根据屏的真值表完成笔段定义,将显示一个数的八段定义为一个字节,如把表中的PIN4和PIN5定义为一个字节,PIN5为低位。再根据笔段完成数值的定义
#define LCDSEG_A 0x01
#define LCDSEG_B 0x02
#define LCDSEG_C 0x04
#define LCDSEG_D 0x08
#define LCDSEG_E 0x40
#define LCDSEG_F 0x10
#define LCDSEG_G 0x20
#define LCDPOINT_S9 0x08

#define CHAR_0   LCDSEG_A+LCDSEG_B+LCDSEG_C+LCDSEG_D+LCDSEG_E+LCDSEG_F
#define CHAR_1   LCDSEG_B+LCDSEG_C
#define CHAR_2   LCDSEG_A+LCDSEG_B+LCDSEG_D+LCDSEG_E+LCDSEG_G
#define CHAR_3   LCDSEG_A+LCDSEG_B+LCDSEG_C+LCDSEG_D+LCDSEG_G
#define CHAR_4   LCDSEG_B+LCDSEG_C+LCDSEG_F+LCDSEG_G
#define CHAR_5   LCDSEG_A+LCDSEG_C+LCDSEG_D+LCDSEG_F+LCDSEG_G
#define CHAR_6   LCDSEG_A+LCDSEG_C+LCDSEG_D+LCDSEG_E+LCDSEG_F+LCDSEG_G
#define CHAR_7   LCDSEG_A+LCDSEG_B+LCDSEG_C
#define CHAR_8   LCDSEG_A+LCDSEG_B+LCDSEG_C+LCDSEG_D+LCDSEG_E+LCDSEG_F+LCDSEG_G
#define CHAR_9   LCDSEG_A+LCDSEG_B+LCDSEG_C+LCDSEG_D+LCDSEG_F+LCDSEG_G
#define CHAR_A   LCDSEG_A+LCDSEG_B+LCDSEG_C+LCDSEG_E+LCDSEG_F+LCDSEG_G
#define CHAR_b   LCDSEG_C+LCDSEG_D+LCDSEG_E+LCDSEG_F+LCDSEG_G
#define CHAR_C   LCDSEG_A+LCDSEG_D+LCDSEG_E+LCDSEG_F
#define CHAR_d   LCDSEG_B+LCDSEG_C+LCDSEG_D+LCDSEG_E+LCDSEG_G
#define CHAR_E   LCDSEG_A+LCDSEG_D+LCDSEG_E+LCDSEG_F+LCDSEG_G
#define CHAR_F   LCDSEG_A+LCDSEG_E+LCDSEG_F+LCDSEG_G
  1. 定义数据类型,由于LCDRAM是一个32位寄存器,可以装两个位置的数据,直接改变显示数据不方便,使用共用体来定义成我们方便操作的数据类型,并对特别的符号或点定义
typedef union{
	uint8_t u8_dis[4];
	uint16_t u16_dis[2];
	uint32_t u32_dis;
}un_Ram_Data;

typedef union{
	uint32_t LcdSegTotle;
	struct{
        uint8_t  LcdPoint_S1:1;
        uint8_t  LcdPoint_S2:1;
        uint8_t  LcdPoint_S3:1;
        uint8_t  LcdPoint_S4:1;
        uint8_t  LcdPoint_S5:1;
        uint8_t  LcdPoint_S6:1;
        uint8_t  LcdPoint_S8:1;
        uint8_t  LcdPoint_S9:1;
        uint8_t  LcdPoint_TIME:1;
        uint8_t  LcdPoint_RM:1;
        uint8_t  LcdPoint_TRIP:1;
        uint8_t  LcdPoint_VOL:1;
        uint8_t  LcdPoint_OFF:1;
        uint8_t  LcdPoint_KM_H:1;
        uint8_t  LcdPoint_MPH:1;
        uint8_t  LcdPoint_W8:1;
        uint8_t  LcdPoint_W7:1;
        uint8_t  LcdPoint_W6:1;
        uint8_t  LcdPoint_W5:1;
        uint8_t  LcdPoint_W4:1;
        uint8_t  LcdPoint_W3:1;
        uint8_t  LcdPoint_W2:1;
        uint8_t  LcdPoint_W1:1;
        uint8_t  LcdPoint_CUR:1;
        uint8_t  LcdPoint_ODO:1;}stc_Lcd_SP;
}un_Lcd_SP;
  1. 采用显示模式0,需要将装有PIN4和PIN5组成的一个字节数据按高4位和第4位拆成两个字节,分别装入单片机的LCDRAM中才可以显示,因此做出如下的映射数组
const uint16_t u16DisTable[] = {
                                ((CHAR_0 & 0xf0) <<4 | (CHAR_0 & 0x0f)),  ///< 0 
                                ((CHAR_1 & 0xf0) <<4 | (CHAR_1 & 0x0f)),  ///< 1 
                                ((CHAR_2 & 0xf0) <<4 | (CHAR_2 & 0x0f)),  ///< 2
                                ((CHAR_3 & 0xf0) <<4 | (CHAR_3 & 0x0f)),  ///< 3 
                                ((CHAR_4 & 0xf0) <<4 | (CHAR_4 & 0x0f)),  ///< 4 
                                ((CHAR_5 & 0xf0) <<4 | (CHAR_5 & 0x0f)),  ///< 5 
                                ((CHAR_6 & 0xf0) <<4 | (CHAR_6 & 0x0f)),  ///< 6 
                                ((CHAR_7 & 0xf0) <<4 | (CHAR_7 & 0x0f)),  ///< 7 
                                ((CHAR_8 & 0xf0) <<4 | (CHAR_8 & 0x0f)),  ///< 8 
                                ((CHAR_9 & 0xf0) <<4 | (CHAR_9 & 0x0f)),  ///< 9 
                                ((CHAR_A & 0xf0) <<4 | (CHAR_A & 0x0f)),  ///< A 
                                ((CHAR_b & 0xf0) <<4 | (CHAR_b & 0x0f)),  ///< b 
                                ((CHAR_C & 0xf0) <<4 | (CHAR_C & 0x0f)),  ///< C 
                                ((CHAR_d & 0xf0) <<4 | (CHAR_d & 0x0f)),  ///< d 
                                ((CHAR_E & 0xf0) <<4 | (CHAR_E & 0x0f)),  ///< E 
                                ((CHAR_F & 0xf0) <<4 | (CHAR_F & 0x0f)),  ///< F 
	                               };
  1. LCD显示函数
void LcdData_Convert(uint8_t *u8LcdDisData , un_Lcd_SP *punLcdSp)
{
	un_Lcd_SP unLcdSP;
	unLcdSP = *punLcdSp;
	
	unRamData[0].u16_dis[0] = u16BatteryLevel[*u8LcdDisData++];
	unRamData[0].u16_dis[1] = u16DisTable[*u8LcdDisData++];
	unRamData[1].u16_dis[0] = u16DisTable[*u8LcdDisData++];
	unRamData[1].u16_dis[1] = u16DisTable8[*u8LcdDisData++];
	unRamData[2].u16_dis[0] = u16DisTable[*u8LcdDisData++];
	unRamData[2].u16_dis[1] = u16DisTable[*u8LcdDisData++];
	unRamData[3].u16_dis[0] = u16DisTable5[*u8LcdDisData++];
	unRamData[3].u16_dis[1] = u16DisTable4_1[*u8LcdDisData++];
	unRamData[4].u16_dis[0] = u16DisTable4_1[*u8LcdDisData++];
	unRamData[4].u16_dis[1] = u16DisTable4_1[*u8LcdDisData++];
	unRamData[5].u16_dis[0] = u16DisTable4_1[*u8LcdDisData++];
	
	if(unLcdSP.LcdSegTotle)
	{
		if(unLcdSP.stc_Lcd_SP.LcdPoint_S9)
		{
			unRamData[0].u8_dis[3] |= LCDPOINT_S9;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_CUR)
		{
			unRamData[1].u8_dis[1] |= LCDPOINT_CUR;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_S8)
		{
			unRamData[1].u8_dis[3] |= LCDPOINT_S8;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_ODO)
		{
			unRamData[2].u8_dis[1] |= LCDPOINT_ODO;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_S6)
		{
			unRamData[2].u8_dis[3] |= LCDPOINT_S6;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_S5)
		{
			unRamData[3].u8_dis[1] |= LCDPOINT_S5;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_S3)
		{
			unRamData[3].u8_dis[3] |= LCDPOINT_S3;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_S4)
		{
			unRamData[4].u8_dis[1] |= LCDPOINT_S4;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_S1)
		{
			unRamData[4].u8_dis[3] |= LCDPOINT_S1;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_S2)
		{
			unRamData[5].u8_dis[1] |= LCDPOINT_S2;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_W4)
		{
			unRamData[5].u8_dis[2] |= LCDPOINT_W4;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_W3)
		{
			unRamData[5].u8_dis[2] |= LCDPOINT_W3;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_W2)
		{
			unRamData[5].u8_dis[2] |= LCDPOINT_W2;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_W1)
		{
			unRamData[5].u8_dis[2] |= LCDPOINT_W1;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_W5)
		{
			unRamData[5].u8_dis[3] |= LCDPOINT_W5;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_W6)
		{
			unRamData[5].u8_dis[3] |= LCDPOINT_W6;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_W7)
		{
			unRamData[5].u8_dis[3] |= LCDPOINT_W7;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_W8)
		{
			unRamData[5].u8_dis[3] |= LCDPOINT_W8;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_TIME)
		{
			unRamData[6].u8_dis[0] |= LCDPOINT_TIME;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_RM)
		{
			unRamData[6].u8_dis[0] |= LCDPOINT_RM;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_TRIP)
		{
			unRamData[6].u8_dis[0] |= LCDPOINT_TRIP;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_VOL)
		{
			unRamData[6].u8_dis[0] |= LCDPOINT_VOL;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_OFF)
		{
			unRamData[6].u8_dis[1] |= LCDPOINT_OFF;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_KM_H)
		{
			unRamData[6].u8_dis[1] |= LCDPOINT_KM_H;
		}
		if(unLcdSP.stc_Lcd_SP.LcdPoint_MPH)
		{
			unRamData[6].u8_dis[1] |= LCDPOINT_MPH;
		}

	}

}

void Lcd_Dis(uint8_t *u8LcdDisData , un_Lcd_SP *punLcdSp)
{
	LcdData_Convert(u8LcdDisData,punLcdSp);
	Lcd_WriteRam(0,unRamData[0].u32_dis);     ///< 赋值寄存器LCDRAM0
	Lcd_WriteRam(1,unRamData[1].u32_dis);     ///< 赋值寄存器LCDRAM1
	Lcd_WriteRam(2,unRamData[2].u32_dis);     ///< 赋值寄存器LCDRAM2
	Lcd_WriteRam(3,unRamData[3].u32_dis);     ///< 赋值寄存器LCDRAM3
	Lcd_WriteRam(4,unRamData[4].u32_dis);     ///< 赋值寄存器LCDRAM4
	Lcd_WriteRam(5,unRamData[5].u32_dis);     ///< 赋值寄存器LCDRAM5
	Lcd_WriteRam(6,unRamData[6].u32_dis);     ///< 赋值寄存器LCDRAM6
}
  1. 主程序中调用LCD显示函数
int32_t main(void)
{
          uint8_t u8LcdData[11] = {0x00};
          un_Lcd_SP unLcdSP;
	Sysctrl_ClkSourceEnable(SysctrlClkRCL,TRUE);     ///< 使能RCL时钟
	Sysctrl_SetRCLTrim(SysctrlRclFreq32768);         ///< 配置内部低速时钟频率为32.768kHz
	
	Sysctrl_SetPeripheralGate(SysctrlPeripheralLcd,TRUE);   ///< 开启LCD时钟
          Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE);  ///< 开启GPIO时钟
	
	DDL_ZERO_STRUCT(unLcdSP);
	
	Lcd_SetPortAnalog();         ///< LCD端口配置
	LCD_Config();                ///< LCD模块配置
	Lcd_ClearDisp();             ///< 清屏
	
	u8LcdData[0] =5;         ///< 电量
	u8LcdData[1] =3;         ///< 八段10
	u8LcdData[2] =2;         ///< 八段9
	u8LcdData[3] =1;         ///< 八段8
	u8LcdData[4] =9;         ///< 八段7
	u8LcdData[5] =8;         ///< 八段6
	u8LcdData[6] =2;         ///< 八段5  
	u8LcdData[7] =11;        ///< 八段3
	u8LcdData[8] =12;        ///< 八段4
	u8LcdData[9] =2;        ///< 八段1
	u8LcdData[10] =5;       ///< 八段2
	unLcdSP.stc_Lcd_SP.LcdPoint_CUR = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_KM_H = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_MPH = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_ODO = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_OFF = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_RM = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_S1 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_S2 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_S3 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_S4 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_S5 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_S6 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_S8 = 0;  
	unLcdSP.stc_Lcd_SP.LcdPoint_S9 = 0;
	unLcdSP.stc_Lcd_SP.LcdPoint_TIME = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_TRIP = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_VOL = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_W1 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_W2 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_W3 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_W4 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_W5 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_W6 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_W7 = 1;
	unLcdSP.stc_Lcd_SP.LcdPoint_W8 = 1;
	Lcd_Dis(u8LcdData,&unLcdSP);
//  Lcd_FullDisp();         ///< 全显
    while(1);
}

四、结果和问题

程序运行结果如下
LCD

出现的问题

  1. 点亮OFF、Km/h、mph者三个符号,笔段就呈现如上图的状况 ,笔段亮暗不均

    用示波器抓波形,点亮的一个段的COM-SEG如下图
    LCD
    可能是硬件上的问题,因为是用跳线引到开发板上,可能有COM或SEG接触不良或者短路。也可能是软件设置问题,有大佬可以解救一下吗

文章借鉴:https://blog.csdn.net/willOkay/article/details/106733759