STM8L052C6单片机驱动段码屏实例

科技   2024-11-05 12:04   北京  
不得不吐槽一下,现在找点儿技术资料真是挺难的!就拿段码液晶屏来说,这是一种很成熟而古老的技术。但是当你项目里要用,想找点而资料,就会发现,似乎唾手可得,可偏偏又会浪费掉很多时间。

就拿某著名搜索引擎来说,输入段码液晶一搜,首先得欣赏洋洋洒洒的广告,然后一个个链接点进去,一会儿是只言片语,一会儿提醒打开另一个APP,一会儿要注册后继续浏览,有的更直接,要从此处过,留下买路财!唉,都人工智能时代了,怎么感觉有点儿人工智障。

吐槽完毕,跟大家做一些分享。如果用到时希望能节省点儿时间。段码屏是一种广泛使用的低成本,低功耗显示设备,我们留心看一下就会发现不少。比如遥控器,电表,电子时钟,电动汽车仪表盘。特别是在电池供电,而又需要显示的场合,段码液晶屏可以说是不二之选。我们简要介绍一下段码液晶屏的显示原理,并以STM8L052给出一个上手即用的参考实例。

01

段码液晶分类

断码液晶的基本显示原理很简单,就是利用液晶这种物质在电场作用下会扭曲的特性,让光线通过或不通过,从而实现显示。它的技术也在不断地进步,性能越来越好。主要分为下面几大类:

  • 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)液晶屏,具有更高的对比度和更宽广的可视角度,显示效果较为出色。

- 应用场景:常用于一些高端显示设备,不过由于其成本相对较高,应用范围相对较窄。

02

段码液晶重要参数

1、视角

何为视角呢?以一挂时钟来作为参考说明视角的方向,6 点为仰视,3点为左侧视,9点为右侧视,12点为俯视。行业内的名词参数:6H,9H,3H,12H.也就是我们所说的6点钟方向、9点钟方向、3点 钟方向、12点钟方向的意思了。下图为一个6点钟视角的液晶示意图。

2、视角范围

视角范围是指在某个特定视角内,图像保持清晰的最大范围。例如,TN材质的段码液晶屏视角范围是50度,意味着在上下50度以内图像清晰‌。

3、Segment

是组成液晶显示图案的基本单元。比如要显示一个数字“8”,它是由多个段组成的,这些段通过组合可以形成不同的数字、字母或者简单的图形符号。可以把它想象成拼图的小碎片,通过点亮不同的片段组合出想要的形状。

4、Common

公共电极。液晶显示器的工作原理是通过在段电极SEG和公共电极COM之间施加电压,来控制液晶分子的排列,从而实现显示。多个segment会共享一个common电极,通过控制每个“segment”与“common”之间的电压,就能控制各个“segment”是否显示,以此来构成完整的显示内容。

5、Duty

每个COM端扫描时间占一次完整扫描的比例,比如有4个COM,Duty就是1/4。

6、Bias

表明电极的驱动电压有几个电平。如1/3bias表明有VSS,1/3Vlcd,2/3Vlcd,Vlcd这4个电平。两个相邻电平之间的差是1/3Vlcd。这里Vlcd是段码屏的工作电压。

此外,还要注意屏的工作电压,和工作温度范围。

03

段码液晶如何驱动

有的段码屏自身集成驱动芯片,对外接口常用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

写的非常好。

04

断码液晶实例

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官方资料)

END

来源:TopSemic嵌入式


版权归原作者所有,如有侵权,请联系删除


推荐阅读

KEIL MDK v6价格公布......

中国人竟然也能发明靠谱的编程语言!

C/C++大限将至,美国强硬要求2026年前全面剔除!


→点关注,不迷路←

嵌入式微处理器
关注嵌入式相关技术和资讯,你想知道的都在这里。
 最新文章