不得不吐槽一下,現(xiàn)在找點兒技術(shù)資料真是挺難的!就拿段碼液晶屏來說,這是一種很成熟而古老的技術(shù)。但是當(dāng)你項目里要用,想找點而資料,就會發(fā)現(xiàn),似乎唾手可得,可偏偏又會浪費掉很多時間。
就拿某著名搜索引擎來說,輸入段碼液晶一搜,首先得欣賞洋洋灑灑的廣告,然后一個個鏈接點進去,一會兒是只言片語,一會兒提醒打開另一個APP,一會兒要注冊后繼續(xù)瀏覽,有的更直接,要從此處過,留下買路財!唉,都人工智能時代了,怎么感覺有點兒人工智障。
吐槽完畢,跟大家做一些分享。如果用到時希望能節(jié)省點兒時間。段碼屏是一種廣泛使用的低成本,低功耗顯示設(shè)備,我們留心看一下就會發(fā)現(xiàn)不少。比如遙控器,電表,電子時鐘,電動汽車儀表盤。特別是在電池供電,而又需要顯示的場合,段碼液晶屏可以說是不二之選。我們簡要介紹一下段碼液晶屏的顯示原理,并以STM8L052給出一個上手即用的參考實例。
段碼液晶分類
斷碼液晶的基本顯示原理很簡單,就是利用液晶這種物質(zhì)在電場作用下會扭曲的特性,讓光線通過或不通過,從而實現(xiàn)顯示。它的技術(shù)也在不斷地進步,性能越來越好。主要分為下面幾大類:
TN(Twisted Nematic):
- 特點:TN扭轉(zhuǎn)式向列場效應(yīng)是將入射光旋轉(zhuǎn)90度。
這是最為常見的一種液晶顯示器件,制造成本相對較低,液晶分子偏轉(zhuǎn)速度快,響應(yīng)時間較快;但視角表現(xiàn)相對較弱,一般視角范圍在50度左右。
- 應(yīng)用場景:廣泛應(yīng)用于計算器、遙控器、電子鬧鐘等對視角要求不是特別高、顯示內(nèi)容較為簡單的小型電子設(shè)備中。
HTN(High Twisted Nematic):
- 特點:在TN液晶屏的基礎(chǔ)上進行了改進,液晶分子的取向扭轉(zhuǎn)角度更大,通常在110至130度。它的對比度有所提高,視角范圍也比TN更寬,可達到70度左右,并且具有功耗低、驅(qū)動電壓低等特點,但動態(tài)驅(qū)動性能較差。
- 應(yīng)用場景:適用于對視角范圍有一定要求,但對顯示效果要求不是特別高的設(shè)備,比如一些簡單的儀器儀表等。
STN(Super Twisted Nematic):
- 特點:顯示原理與TN相類似,但扭轉(zhuǎn)角度更大,可以實現(xiàn)更高的對比度和更豐富的色彩顯示。普通的STN液晶屏,液晶分子可以旋轉(zhuǎn)180-270度。不過,其響應(yīng)時間相對較長,在快速變化的畫面顯示上可能會有一定的滯后。
- 應(yīng)用場景:能夠滿足需要顯示較為復(fù)雜圖形或文字的需求,常用于一些中低端的電子設(shè)備,如電子詞典、早期的手機顯示屏等。
FSTN(Film Compensated Super Twisted Nematic):
- 特點:在STN的基礎(chǔ)上進一步優(yōu)化,通過在偏光片上增加補償膜來消除色散,實現(xiàn)更清晰的黑白顯示。其視角更寬,顯示效果更佳,對比度也較高。
- 應(yīng)用場景:適用于對顯示效果有較高要求的設(shè)備,如多功能電表、電力檢測儀器等。
VATN(Vertical Alignment Twisted Nematic):
- 特點:也稱為VA(BTN)液晶屏,具有更高的對比度和更寬廣的可視角度,顯示效果較為出色。
- 應(yīng)用場景:常用于一些高端顯示設(shè)備,不過由于其成本相對較高,應(yīng)用范圍相對較窄。
段碼液晶重要參數(shù)
視角
何為視角呢?以一掛時鐘來作為參考說明視角的方向,6 點為仰視,3點為左側(cè)視,9點為右側(cè)視,12點為俯視。行業(yè)內(nèi)的名詞參數(shù):6H,9H,3H,12H.也就是我們所說的6點鐘方向、9點鐘方向、3點 鐘方向、12點鐘方向的意思了。下圖為一個6點鐘視角的液晶示意圖。
視角范圍
視角范圍是指在某個特定視角內(nèi),圖像保持清晰的最大范圍。例如,TN材質(zhì)的段碼液晶屏視角范圍是50度,意味著在上下50度以內(nèi)圖像清晰?。
Segment
是組成液晶顯示圖案的基本單元。比如要顯示一個數(shù)字“8”,它是由多個段組成的,這些段通過組合可以形成不同的數(shù)字、字母或者簡單的圖形符號??梢园阉胂蟪善磮D的小碎片,通過點亮不同的片段組合出想要的形狀。
Common
公共電極。液晶顯示器的工作原理是通過在段電極SEG和公共電極COM之間施加電壓,來控制液晶分子的排列,從而實現(xiàn)顯示。多個segment會共享一個common電極,通過控制每個“segment”與“common”之間的電壓,就能控制各個“segment”是否顯示,以此來構(gòu)成完整的顯示內(nèi)容。
Duty
每個COM端掃描時間占一次完整掃描的比例,比如有4個COM,Duty就是1/4。
Bias
表明電極的驅(qū)動電壓有幾個電平。如1/3bias表明有VSS,1/3Vlcd,2/3Vlcd,Vlcd這4個電平。兩個相鄰電平之間的差是1/3Vlcd。這里Vlcd是段碼屏的工作電壓。
還要注意屏的工作電壓,和工作溫度范圍。
段碼液晶如何驅(qū)動
有的段碼屏自身集成驅(qū)動芯片,對外接口常用IIC,SPI等串行總線,這種屏成本稍高一些。有的屏不帶驅(qū)動芯片,對外接口為COM,和SEG引腳。驅(qū)動這種屏,需要用驅(qū)動芯片,如HT1621,CNV1792S等。段碼屏的每個段,點亮的條件是在COM和SEG引腳之間施加一個壓差,壓差超過一定門限就會點亮。但是要注意給液晶不能施加直流信號,比如COM加0V,SEG加3.3V,雖然這樣能顯示,但時間長了后會由于電泳現(xiàn)象導(dǎo)致液晶損壞,顯示模糊。正確的做法是,隔一小段時間就把電壓翻轉(zhuǎn)一次,COM加3.3V,SEG變?yōu)?V,這樣交替進行。
如果想更深入的了解原理和驅(qū)動方法,可以參考ST的一篇官方文檔:
AN3114 How to use the STM8AL3Lxx, STM8L152xx and STM8L162xx LCD controllers
寫的非常好。
- 斷碼液晶實例
STM8L052C6是一款帶段碼液晶驅(qū)動模塊的單片機,提供了驅(qū)動庫,可以通過調(diào)用庫函數(shù),設(shè)置DUTY和BIAS,段碼顯示與否,調(diào)節(jié)對比度,閃爍頻率等。
我們用的這個段碼屏,是大連奇耘的QYT13264S(T)P10V3T,TN型,1/4Duty,1/3Bias,工作電壓3V。淘寶上可以買到,5塊錢左右。管腳圖,1-4管腳是COM0-COM3,5-13管腳是SEG0-SEG8。
下圖是屏和單片機的接線圖,stm8L052和stm8L152管腳兼容。注意VLCD這個引腳,如果配置段碼屏使用內(nèi)部電壓時,斷碼屏的工作電壓VLCD由內(nèi)部的升壓電路從MCU的電源VDD產(chǎn)生,VLCD外接一個0.1-2uF的小電容,可以使電壓更穩(wěn)定。此引腳的電壓可以在一定范圍內(nèi)通過程序調(diào)節(jié),從而改變屏的對比度。
下面是初始化的子程序
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);
}
//設(shè)置實例使用LCD_Duty_1_4, LCD_Bias_1_3代表1/4DUTY,1/3BIAS
//LCD_PortMaskRegister_0 設(shè)置LCD_PM0寄存器,啟用的SEG對應(yīng)位置1。
如何點亮一個液晶的段?比如小數(shù)點P。下面是實現(xiàn)的子程序
void LCD_GLASS_SetP(bool P ) // P "." 小數(shù)點
{
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) 內(nèi)容的每一位的值對應(yīng)著圖5 COM0和SEG0-SEG7交叉位置的元素被點亮還是熄滅。
小數(shù)點P是COM0和PIN9交叉位置。PIN9 對應(yīng)的SEG為SEG4
LCD_RAM0=0x10;代表P(小數(shù)點) 元素被點亮。
其它元素的點亮原理與此相同
下面是電池電量顯示部分的子程序
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個數(shù)字的顯示需要一些技巧,下面是數(shù)字部分顯示的子程序
/* =========================================================================
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: //十位數(shù)字
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: //個位數(shù)字
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: //小數(shù)點后1位數(shù)字
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;
}
}
//在主函數(shù)里調(diào)用上面子函數(shù)測試一下效果
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) ;
}
下面是實際效果
參考:
記一次段碼屏調(diào)試總結(jié)
AN3114 How to use the STM8AL3Lxx, STM8L152xx and STM8L162xx LCD controllers(ST官方資料)
AN1447 SOFTWARE DRIVER FOR 4-MULTIPLEXED LCD WITH A STANDARD ST62(ST官方資料)