LCD
一、显示原理
LCD屏内部采用了一种液晶材料,液晶分子介于固态和液态之间。在自然状态下,液晶具有光学各向异性,但在电磁场作用下会呈现各向同性,LCD屏就是利用了液晶分子的这种物理结构和光学特性制造而成的。
屏的顶部和底部是一对互相垂直的偏振片,中间是液晶分子涂层。当液晶分子两端电压为0时,液晶分子呈现螺旋状态,自然光跟随液晶分子的旋光特性旋转进入到屏另一端的偏振片上,刚好旋转90°后可以穿过。此时,由于光线可以通过,所以人眼看上去屏是白色的。
给屏的两端加上电压时,中间的液晶分子变成了同向排列的结构。此时,由于没有了旋转结构,进入到屏里面的光也就无法旋转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中的某一段点亮,就要使它的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。
用跳线外接一个4*26的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映射表
- 根据屏的真值表完成笔段定义,将显示一个数的八段定义为一个字节,如把表中的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
- 定义数据类型,由于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;
- 采用显示模式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
};
- 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
}
- 主程序中调用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);
}
四、结果和问题
程序运行结果如下
出现的问题
-
点亮OFF、Km/h、mph者三个符号,笔段就呈现如上图的状况 ,笔段亮暗不均
用示波器抓波形,点亮的一个段的COM-SEG如下图
可能是硬件上的问题,因为是用跳线引到开发板上,可能有COM或SEG接触不良或者短路。也可能是软件设置问题,有大佬可以解救一下吗
文章借鉴:https://blog.csdn.net/willOkay/article/details/106733759