不得不吐槽一下,现在找点儿技术资料真是挺难的!就拿段码液晶屏来说,这是一种很成熟而古老的技术。但是当你项目里要用,想找点而资料,就会发现,似乎唾手可得,可偏偏又会浪费掉很多时间。
就拿某著名搜索引擎来说,输入段码液晶一搜,首先得欣赏洋洋洒洒的广告,然后一个个链接点进去,一会儿是只言片语,一会儿提醒打开另一个APP,一会儿要注册后继续浏览,有的更直接,要从此处过,留下买路财!唉,都人工智能时代了,怎么感觉有点儿人工智障。
吐槽完毕,跟大家做一些分享。如果用到时希望能节省点儿时间。段码屏是一种广泛使用的低成本,低功耗显示设备,我们留心看一下就会发现不少。比如遥控器,电表,电子时钟,电动汽车仪表盘。特别是在电池供电,而又需要显示的场合,段码液晶屏可以说是不二之选。我们简要介绍一下段码液晶屏的显示原理,并以STM8L052给出一个上手即用的参考实例。
段码液晶分类
断码液晶的基本显示原理很简单,就是利用液晶这种物质在电场作用下会扭曲的特性,让光线通过或不通过,从而实现显示。它的技术也在不断地进步,性能越来越好。主要分为下面几大类:
TN(Twisted Nematic):
- 特点:TN扭转式向列场效应是将入射光旋转90度。
这是最为常见的一种液晶显示器件,制造成本相对较低,液晶分子偏转速度快,响应时间较快;但视角表现相对较弱,一般视角范围在50度左右。
- 应用场景:广泛应用于计算器、遥控器、电子闹钟等对视角要求不是特别高、显示内容较为简单的小型电子设备中。
HTN(High Twisted Nematic):
- 特点:在TN液晶屏的基础上进行了改进,液晶分子的取向扭转角度更大,通常在110至130度。它的对比度有所提高,视角范围也比TN更宽,可达到70度左右,并且具有功耗低、驱动电压低等特点,但动态驱动性能较差。
- 应用场景:适用于对视角范围有一定要求,但对显示效果要求不是特别高的设备,比如一些简单的仪器仪表等。
STN(Super Twisted Nematic):
- 特点:显示原理与TN相类似,但扭转角度更大,可以实现更高的对比度和更丰富的色彩显示。普通的STN液晶屏,液晶分子可以旋转180-270度。不过,其响应时间相对较长,在快速变化的画面显示上可能会有一定的滞后。
- 应用场景:能够满足需要显示较为复杂图形或文字的需求,常用于一些中低端的电子设备,如电子词典、早期的手机显示屏等。
FSTN(Film Compensated Super Twisted Nematic):
- 特点:在STN的基础上进一步优化,通过在偏光片上增加补偿膜来消除色散,实现更清晰的黑白显示。其视角更宽,显示效果更佳,对比度也较高。
- 应用场景:适用于对显示效果有较高要求的设备,如多功能电表、电力检测仪器等。
VATN(Vertical Alignment Twisted Nematic):
- 特点:也称为VA(BTN)液晶屏,具有更高的对比度和更宽广的可视角度,显示效果较为出色。
- 应用场景:常用于一些高端显示设备,不过由于其成本相对较高,应用范围相对较窄。
段码液晶重要参数
视角
何为视角呢?以一挂时钟来作为参考说明视角的方向,6 点为仰视,3点为左侧视,9点为右侧视,12点为俯视。行业内的名词参数:6H,9H,3H,12H.也就是我们所说的6点钟方向、9点钟方向、3点 钟方向、12点钟方向的意思了。下图为一个6点钟视角的液晶示意图。
视角范围
视角范围是指在某个特定视角内,图像保持清晰的最大范围。例如,TN材质的段码液晶屏视角范围是50度,意味着在上下50度以内图像清晰。
Segment
是组成液晶显示图案的基本单元。比如要显示一个数字“8”,它是由多个段组成的,这些段通过组合可以形成不同的数字、字母或者简单的图形符号。可以把它想象成拼图的小碎片,通过点亮不同的片段组合出想要的形状。
Common
公共电极。液晶显示器的工作原理是通过在段电极SEG和公共电极COM之间施加电压,来控制液晶分子的排列,从而实现显示。多个segment会共享一个common电极,通过控制每个“segment”与“common”之间的电压,就能控制各个“segment”是否显示,以此来构成完整的显示内容。
Duty
每个COM端扫描时间占一次完整扫描的比例,比如有4个COM,Duty就是1/4。
Bias
表明电极的驱动电压有几个电平。如1/3bias表明有VSS,1/3Vlcd,2/3Vlcd,Vlcd这4个电平。两个相邻电平之间的差是1/3Vlcd。这里Vlcd是段码屏的工作电压。
还要注意屏的工作电压,和工作温度范围。
段码液晶如何驱动
有的段码屏自身集成驱动芯片,对外接口常用IIC,SPI等串行总线,这种屏成本稍高一些。有的屏不带驱动芯片,对外接口为COM,和SEG引脚。驱动这种屏,需要用驱动芯片,如HT1621,CNV1792S等。段码屏的每个段,点亮的条件是在COM和SEG引脚之间施加一个压差,压差超过一定门限就会点亮。但是要注意给液晶不能施加直流信号,比如COM加0V,SEG加3.3V,虽然这样能显示,但时间长了后会由于电泳现象导致液晶损坏,显示模糊。正确的做法是,隔一小段时间就把电压翻转一次,COM加3.3V,SEG变为0V,这样交替进行。
如果想更深入的了解原理和驱动方法,可以参考ST的一篇官方文档:
AN3114 How to use the STM8AL3Lxx, STM8L152xx and STM8L162xx LCD controllers
写的非常好。
- 断码液晶实例
STM8L052C6是一款带段码液晶驱动模块的单片机,提供了驱动库,可以通过调用库函数,设置DUTY和BIAS,段码显示与否,调节对比度,闪烁频率等。
我们用的这个段码屏,是大连奇耘的QYT13264S(T)P10V3T,TN型,1/4Duty,1/3Bias,工作电压3V。淘宝上可以买到,5块钱左右。管脚图,1-4管脚是COM0-COM3,5-13管脚是SEG0-SEG8。
下图是屏和单片机的接线图,stm8L052和stm8L152管脚兼容。注意VLCD这个引脚,如果配置段码屏使用内部电压时,断码屏的工作电压VLCD由内部的升压电路从MCU的电源VDD产生,VLCD外接一个0.1-2uF的小电容,可以使电压更稳定。此引脚的电压可以在一定范围内通过程序调节,从而改变屏的对比度。
下面是初始化的子程序
void LCD_GLASS_Init(void)
{
/* Enable LCD/RTC clock */
CLK_PeripheralClockConfig(CLK_Peripheral_RTC, ENABLE);
CLK_PeripheralClockConfig(CLK_Peripheral_LCD, ENABLE);
#ifdef USE_LSE
CLK_RTCClockConfig(CLK_RTCCLKSource_LSE, CLK_RTCCLKDiv_1);
#else
CLK_RTCClockConfig(CLK_RTCCLKSource_LSI, CLK_RTCCLKDiv_1);
#endif
/* Initialize the LCD */
LCD_Init(LCD_Prescaler_1, LCD_Divider_31, LCD_Duty_1_4,
LCD_Bias_1_3, LCD_VoltageSource_Internal);
/* Mask register
For declare the segements used.
in the Discovery we use 0 to 8 segments. */
// 使用0-8 segments
LCD_PortMaskConfig(LCD_PortMaskRegister_0, 0xFF);
LCD_PortMaskConfig(LCD_PortMaskRegister_1, 0x01);
LCD_PortMaskConfig(LCD_PortMaskRegister_2, 0x00);
/* To set contrast to mean value */
LCD_ContrastConfig(LCD_Contrast_3V0);
LCD_DeadTimeConfig(LCD_DeadTime_0);
LCD_PulseOnDurationConfig(LCD_PulseOnDuration_1);
/* Enable LCD peripheral */
LCD_Cmd(ENABLE);
}
//设置实例使用LCD_Duty_1_4, LCD_Bias_1_3代表1/4DUTY,1/3BIAS
//LCD_PortMaskRegister_0 设置LCD_PM0寄存器,启用的SEG对应位置1。
如何点亮一个液晶的段?比如小数点P。下面是实现的子程序
void LCD_GLASS_SetP(bool P ) // P "." 小数点
{
if(P)
LCD->RAM[LCD_RAMRegister_0] |= 0x10; // 亮 (7:0) LCD_RAM0第4位
else
LCD->RAM[LCD_RAMRegister_0] &= 0xEF; // 灭 (7:0) LCD_RAM0第4位
}
下图LCD_RAM0寄存器 S0[7:0] (COM0 or COM4) 内容的每一位的值对应着图5 COM0和SEG0-SEG7交叉位置的元素被点亮还是熄灭。
小数点P是COM0和PIN9交叉位置。PIN9 对应的SEG为SEG4
LCD_RAM0=0x10;代表P(小数点) 元素被点亮。
其它元素的点亮原理与此相同
下面是电池电量显示部分的子程序
void LCD_GLASS_SetT(uint8_t T) //电池电量显示
{
switch (T)
{
case 0: //PD2_LCDSEG8
LCD->RAM[LCD_RAMRegister_1] &= 0xFE; //S9清零 S0(15:8) 外框
LCD->RAM[LCD_RAMRegister_4] &= 0xEF; //S10清零 S1(11:4)
LCD->RAM[LCD_RAMRegister_8] &= 0xFE; //S12清零 S2(15:8)
LCD->RAM[LCD_RAMRegister_11] &= 0xEF; //S11清零 S3(11:4)
break;
case 1: //PD2_LCDSEG8
LCD->RAM[LCD_RAMRegister_1] |= 0x01; //S9亮 S0(15:8) 外框
LCD->RAM[LCD_RAMRegister_4] |= 0x10; //S10亮 S1(11:4)
LCD->RAM[LCD_RAMRegister_8] &= 0xFE; //S12清零 S2(15:8)
LCD->RAM[LCD_RAMRegister_11] &= 0xEF; //S11清零 S3(11:4)
break;
case 2: //PD2_LCDSEG8
LCD->RAM[LCD_RAMRegister_1] |= 0x01; //S9亮 S0(15:8) 外框
LCD->RAM[LCD_RAMRegister_4] |= 0x10; //S10亮 S1(11:4)
LCD->RAM[LCD_RAMRegister_8] &= 0xFE; //S12清零 S2(15:8)
LCD->RAM[LCD_RAMRegister_11] |= 0x10; //S11亮 S3(11:4)
break;
case 3: //PD2_LCDSEG8
LCD->RAM[LCD_RAMRegister_1] |= 0x01; //S9亮 S0(15:8) 外框
LCD->RAM[LCD_RAMRegister_4] |= 0x10; //S10亮 S1(11:4)
LCD->RAM[LCD_RAMRegister_8] |= 0x01; //S12亮 S2(15:8)
LCD->RAM[LCD_RAMRegister_11] |= 0x10; //S11亮 S3(11:4)
break;
}
}
下面是字段S1 S2 S3 S4 显示的子程序
void LCD_GLASS_SetS(bool S1,bool S2,bool S3,bool S4) // S1 S2 S3 "%"S4 " ℃"
{
if(S1)
LCD->RAM[LCD_RAMRegister_0] |= 0x04; // 亮 (7:0) 第2位
else
LCD->RAM[LCD_RAMRegister_0] &= 0xfB; // 灭 (7:0) 第2位
if(S2)
LCD->RAM[LCD_RAMRegister_0] |= 0x01; // 亮 S0(7:0) 第1位
else
LCD->RAM[LCD_RAMRegister_0] &= 0xfE; // 灭 S0(7:0) 第1位
if(S3)
LCD->RAM[LCD_RAMRegister_11] |= 0x04; // 亮 S3(11:4) 第1位
else
LCD->RAM[LCD_RAMRegister_11] &= 0xFB; // 灭 S3(11:4) 第1位
if(S4)
LCD->RAM[LCD_RAMRegister_7] |= 0x40; // 亮 S2(7:0) 第6位
else
LCD->RAM[LCD_RAMRegister_7] &= 0xBF; // 灭 S2(7:0) 第6位
}
3个数字的显示需要一些技巧,下面是数字部分显示的子程序
/* =========================================================================
LCD MAPPING
A
D
An LCD character coding is based on the following matrix:
{ S , G }
{ D , B }
{ E , F }
{ C , A }
The character '9' for example is:
LSB { 0 , 1 }
{ 1 , 1 }
{ 0 , 1 }
MSB { 1 , 1 }
The character '8' for example is:
LSB { 0 , 1 }
{ 1 , 1 }
{ 1 , 1 }
MSB { 1 , 1 }
The character '7' for example is:
LSB { 0 , 0 }
{ 0 , 1 }
{ 0 , 0 }
MSB { 1 , 1 }
The character '6' for example is:
LSB { 1 , 1 }
{ 0 , 0 }
{ 1 , 1 }
MSB { 1 , 1 }
The character '5' for example is:
LSB { 0 , 1 }
{ 1 , 0 }
{ 0 , 1 }
MSB { 1 , 1 }
The character '4' for example is:
LSB { 0 , 1 }
{ 0 , 1 }
{ 0 , 1 }
MSB { 1 , 0 }
The character '3' for example is:
LSB { 0 , 1 }
{ 1 , 1 }
{ 0 , 0 }
MSB { 1 , 1 }
The character '2' for example is:
LSB { 0 , 1 }
{ 1 , 1 }
{ 1 , 0 }
MSB { 0 , 1 }
The character '1' for example is:
LSB { 0 , 0 }
{ 0 , 0}
{ 1, 1 }
MSB { 0, 0 }
The character '0' for example is:
LSB { 0 , 0 }
{ 1 , 1}
{ 1 , 1 }
MSB { 1 , 1 }
*/
const uint8_t NumberMap[10]=
{
0xEE ,0x44 ,0xB6, 0xBA, 0x78, 0xDA, 0xDD, 0xA8, 0xFE, 0xFA
};
static void LCD_Conv_Char_Seg(uint8_t* c, uint8_t* digit)
{
uint16_t ch = 0 ;
uint8_t i,j;
switch (*c)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
ch = NumberMap[*c];
break;
default:
break;
}
//下面是大连奇耘段码屏的顺序
digit[0] = (ch ) & 0x03;//D S 每个digit[ ]用最低两位
digit[1] = (ch >> 2) & 0x03;//C E
digit[2] = (ch >> 4) & 0x03;//B G
digit[3] = (ch >> 6 ) & 0x03;//A F
}
//在第position位置 显示1个* ch 字符
void LCD_GLASS_WriteChar(uint8_t* ch, uint8_t position) {
uint8_t digit[4]; /* Digit frame buffer */
LCD_Conv_Char_Seg(ch, digit);
switch (position)
{
case 1: //十位数字
LCD->RAM[LCD_RAMRegister_0] &= 0x0fd;
LCD->RAM[LCD_RAMRegister_0] |= (uint8_t)(digit[0]& 0x02); // 1D
LCD->RAM[LCD_RAMRegister_3] &= 0x0cf;
LCD->RAM[LCD_RAMRegister_3] |= (uint8_t)(digit[1]<<4 & 0x30); // 1C 1E
LCD->RAM[LCD_RAMRegister_7] &= 0x0fc;
LCD->RAM[LCD_RAMRegister_7] |= (uint8_t)(digit[2]&0x03); // 1B 1G
LCD->RAM[LCD_RAMRegister_10] &= 0xcf;
LCD->RAM[LCD_RAMRegister_10] |= (uint8_t)((digit[3]<<4)& 0x30); // 1A 1F
break;
/* Position 2 on LCD (Digit2)*/
case 2: //个位数字
LCD->RAM[LCD_RAMRegister_0] &= 0x0f7;
LCD->RAM[LCD_RAMRegister_0] |= (uint8_t)((digit[0]<<2)&0x08); // 2D
LCD->RAM[LCD_RAMRegister_3] &= 0x3f;
LCD->RAM[LCD_RAMRegister_3] |= (uint8_t)((digit[1]<<6) & 0xc0); // 2C 2E
LCD->RAM[LCD_RAMRegister_7] &= 0xf3;
LCD->RAM[LCD_RAMRegister_7] |= (uint8_t)((digit[2]<<2)& 0x0c); // 2B 2G
LCD->RAM[LCD_RAMRegister_10] &= 0x3f;
LCD->RAM[LCD_RAMRegister_10] |= (uint8_t)((digit[3]<<6)& 0xC0); // 2A 2F
break;
/* Position 3 on LCD (Digit3)*/
case 3: //小数点后1位数字
LCD->RAM[LCD_RAMRegister_0] &= 0xdf;
LCD->RAM[LCD_RAMRegister_0] |= (uint8_t)(digit[0]<<4) & 0x20; // 3D
LCD->RAM[LCD_RAMRegister_4] &= 0xfc;
LCD->RAM[LCD_RAMRegister_4] |= (uint8_t)(digit[1]) & 0x03; // 3C 3E
LCD->RAM[LCD_RAMRegister_7] &= 0xcf;
LCD->RAM[LCD_RAMRegister_7] |= (uint8_t)(digit[2]<<4)&0x30; // 3B 3G
LCD->RAM[LCD_RAMRegister_11] &= 0xfc;
LCD->RAM[LCD_RAMRegister_11] |= (uint8_t)(digit[3]) & 0x03 ; // 3A 3F
break;
default:
break;
}
}
//在主函数里调用上面子函数测试一下效果
void main(void)
{
uint8_tchar_tmp;
char_tmp = 1;
LCD_GLASS_WriteChar(&char_tmp, 1);
char_tmp = 2;
LCD_GLASS_WriteChar(&char_tmp, 2);
char_tmp = 3;
LCD_GLASS_WriteChar(&char_tmp, 3);
LCD_GLASS_SetT(2);
LCD_GLASS_SetS(1,1,1,1);
//LCD_GLASS_SetS(0,0,1,1);
LCD_GLASS_SetP(1) ;
}
下面是实际效果
参考:
记一次段码屏调试总结
AN3114 How to use the STM8AL3Lxx, STM8L152xx and STM8L162xx LCD controllers(ST官方资料)
AN1447 SOFTWARE DRIVER FOR 4-MULTIPLEXED LCD WITH A STANDARD ST62(ST官方资料)