文章目录[隐藏]
01 什么是段码屏
段码液晶屏(段码屏)是非点阵类的液晶屏,主要是用来替代LED数码管的,比如温度计、计算器、钟表等等,显示的内容基本都是数字,也有些是符号。
02 段码屏驱动原理
段码液晶由若干段区(单像素或完整符号)组成,每个区段都包含一层在两根电极之间对齐的液晶分子。液晶在电场的影响下,晶体的排序方向会产生扭曲,因而改变其透光性,从而能够看到显示的內容。要使晶体产生扭曲现象,必须使电极两端的压差大于一定的阈值,才能显示內容。
一般段码屏有两个极,一个为段电极,一个为公共极,如下图所示。SEG1 ~ SEGn为段电极,COM为公共极。上述电场的电压就是施加在段电极和公共极两端的。
但是段码液晶显示屏不像数码管那样,施以一定大小的直流正向电压就能显示,如果在段码屏SEG和COM两端加直流电压,将会导致液态晶体不可逆的损坏。
需要某段液晶显示的,那么就在某段液晶的SEG和COM两端施加一定压差的交流电,如果不需要液晶显示的话,那么也需要在SEG和COM两端施加电压,不过所施加电压的压差很小或者为零即可。
03 段码屏驱动实现
- LCD段码屏三个主要参数
-
工作电压
段码液晶屏的操作电压。 -
占空比(Duty)
该参数也称作COM数,定义为1 / (给定LCD上的公共端子数)。一般LCD的驱动方式是采用时分动态扫描的方式。所以每个 COM 的有效选通时间与整个扫描周期的比值,即占空比(Duty)是固定的,等于 1/COM 。 -
偏置(BIAS)
驱动LCD时使用的电压等级。定义为1 / (用于驱动LCD显示器的电压等级数 - 1)
- 基本驱动方法
如液晶屏的工作电压约为3.3V,只要给液晶两个电机间施加约3.3V的电压差(如COM端为3.3V,SEG端为0V),并间隔适当的时间,将两个电极的电压反转(如COM端为0V,SEG端为3.3V)即可让液晶显示。
而液晶不显示时,保证两个电极间的电压差为0V(如COM为3.3V,SEG为3.3V),并且间隔适当的时间反转两极的电压(如COM为0V,SEG为0V)。
- 驱动方案
- MCU没有LCD驱动外设
使用IO口直接驱动的方式,偏置比只能选择1/2,这种方式需要在COM口加上拉、下拉各1个电阻,阻值约为100KΩ - 200KΩ。 - MCU+专用的LCD显示驱动芯片
常用的驱动芯片有HT1621、HT1622,对应的偏压比为1/3、1/4。 - MCU带有LCD外设
如STM32L073系列芯片。
硬件设计
以带有LCD外设的MCU驱动YR1433段码屏为例。
- 主要特性
- 帧速率可调节。
- 占空比支持静态、1/2、1/3、1/4和1/8。
- 偏置支持静态、1/2、1/3和1/4。
- 双缓冲存储器,允许应用程序随时更新LCD_RAM寄存器的数据
- 对比度可调节
- 支持闪烁功能
- LCD控制器框图
- LCD外设的时钟源
- 32.768KHz低速外部时钟(LSE)
- 32.768KHz低速内部时钟(LSI)
- 高速外部时钟(HSE)的分频,最大支持1MHz
YR1433段码液晶屏
- YR1433的参数
工作电压 | DUTY | BIAS |
---|---|---|
3.2V | 1/4 | 1/3 |
- 段码屏MAP图
PIN | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
COM1 | COM1 | 1F | 1A | 2F | 2A | 3F | 3A | 4F | 4A | 5F | 5A | 6F | 6A | 7F | 7A | 8F | 8A | K2 | S | S1 | T2 | |||
COM2 | COM2 | 1G | 1B | 2G | 2B | 3G | 3B | 4G | 4B | 5G | 5B | 6G | 6B | 7G | 7B | 8G | 8B | K3 | T8 | S2 | T1 | |||
COM3 | COM3 | 1E | 1C | 2E | 2C | 3E | 3C | 4E | 4C | 5C | 5C | 6E | 6C | 7E | 7C | 8E | 8C | K4 | T7 | S6 | T0 | |||
COM4 | COM4 | 1D | T3 | 2D | T4 | 3D | T5 | 4D | T6 | 5D | S7 | 6D | S8 | 7D | S4 | 8D | S5 | K1 | K5 | S3 | T |
固件设计
- 固件配置流程
- 定义端口
根据MAP图,我们将STM32L073RBT6的引脚功能分配如下。
#define LCD_SEG0_PIN GPIO_PIN_1
#define LCD_SEG1_PIN GPIO_PIN_2
#define LCD_SEG2_PIN GPIO_PIN_3
#define LCD_SEG3_PIN GPIO_PIN_6
#define LCD_SEG4_PIN GPIO_PIN_7
#define LCD_SEG5_PIN GPIO_PIN_0
#define LCD_SEG6_PIN GPIO_PIN_1
#define LCD_SEG7_PIN GPIO_PIN_3
#define LCD_SEG8_PIN GPIO_PIN_4
#define LCD_SEG9_PIN GPIO_PIN_5
#define LCD_SEG10_PIN GPIO_PIN_10
#define LCD_SEG11_PIN GPIO_PIN_11
#define LCD_SEG12_PIN GPIO_PIN_12
#define LCD_SEG13_PIN GPIO_PIN_13
#define LCD_SEG14_PIN GPIO_PIN_14
#define LCD_SEG15_PIN GPIO_PIN_15
#define LCD_SEG16_PIN GPIO_PIN_8
#define LCD_SEG17_PIN GPIO_PIN_15
#define LCD_SEG18_PIN GPIO_PIN_0
#define LCD_SEG19_PIN GPIO_PIN_1
#define LCD_SEG20_PIN GPIO_PIN_2
#define LCD_SEG_GPIOA_PORT GPIOA
#define LCD_SEG_GPIOB_PORT GPIOB
#define LCD_SEG_GPIOC_PORT GPIOC
#define LCD_SEG_AF GPIO_AF1_LCD
#define LCD_COM0_PIN GPIO_PIN_8
#define LCD_COM1_PIN GPIO_PIN_9
#define LCD_COM2_PIN GPIO_PIN_10
#define LCD_COM3_PIN GPIO_PIN_9
#define LCD_COM0_2_GPIO_PORT GPIOA
#define LCD_COM3_GPIO_PORT GPIOB
#define LCD_COM_AF GPIO_AF1_LCD
- 初始化寄存器
void LCD_GlassInit(void)
{
LCDHandle.Instance = LCD;
LCDHandle.Init.Prescaler = LCD_PRESCALER_4;
LCDHandle.Init.Divider = LCD_DIVIDER_16;
LCDHandle.Init.Duty = LCD_DUTY_1_4;
LCDHandle.Init.Bias = LCD_BIAS_1_3;
LCDHandle.Init.VoltageSource = LCD_VOLTAGESOURCE_INTERNAL;
LCDHandle.Init.Contrast = LCD_CONTRASTLEVEL_5;
LCDHandle.Init.DeadTime = LCD_DEADTIME_0;
LCDHandle.Init.PulseOnDuration = LCD_PULSEONDURATION_7;
LCDHandle.Init.BlinkMode = LCD_BLINKMODE_OFF;
LCDHandle.Init.BlinkFrequency = LCD_BLINKFREQUENCY_DIV8;
LCDHandle.Init.MuxSegment = LCD_MUXSEGMENT_DISABLE;
/* YR1433 LCD glass need open high drive*/
LCDHandle.Init.HighDrive = LCD_HIGHDRIVE_1;
__HAL_LCD_RESET_HANDLE_STATE(&LCDHandle);
HAL_LCD_MspInit(&LCDHandle);
HAL_LCD_Init(&LCDHandle);
HAL_LCD_Clear(&LCDHandle);
}
- 用LSE作为LCD时钟源并初始化端口
void HAL_LCD_MspInit(LCD_HandleTypeDef *hlcd)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_OscInitTypeDef RCC_OscInitStruct;
if(hlcd->Instance == LCD)
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_BACKUPRESET_FORCE();
__HAL_RCC_BACKUPRESET_RELEASE();
/** Enable LCD LSE Clock*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
LCD_SEG_GPIOA_CLK_ENABLE();
LCD_SEG_GPIOB_CLK_ENABLE();
LCD_SEG_GPIOC_CLK_ENABLE();
LCD_COM0_2_GPIO_CLK_ENABLE();
LCD_COM3_GPIO_CLK_ENABLE();
/** Configure peripheral GPIO*/
GPIO_InitStruct.Pin = LCD_SEG0_PIN | LCD_SEG1_PIN | LCD_SEG2_PIN \
| LCD_SEG3_PIN | LCD_SEG4_PIN | LCD_SEG17_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = LCD_SEG_AF;
HAL_GPIO_Init(LCD_SEG_GPIOA_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_SEG5_PIN | LCD_SEG6_PIN | LCD_SEG7_PIN \
| LCD_SEG8_PIN | LCD_SEG9_PIN | LCD_SEG10_PIN \
| LCD_SEG11_PIN | LCD_SEG12_PIN | LCD_SEG13_PIN \
| LCD_SEG14_PIN | LCD_SEG15_PIN | LCD_SEG16_PIN;
HAL_GPIO_Init(LCD_SEG_GPIOB_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_SEG18_PIN | LCD_SEG19_PIN;
HAL_GPIO_Init(LCD_SEG_GPIOC_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_COM0_PIN | LCD_COM1_PIN | LCD_COM2_PIN;
GPIO_InitStruct.Alternate = LCD_COM_AF;
HAL_GPIO_Init(LCD_COM0_2_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_COM3_PIN;
GPIO_InitStruct.Alternate = LCD_COM_AF;
HAL_GPIO_Init(LCD_COM3_GPIO_PORT, &GPIO_InitStruct);
LCD_CLK_ENABLE();
}
}
- 固件应用实例
限于篇幅,以下只列举简单的数字显示应用,其他部分的应用,同学可以自行类推研究下。
在上述配置完成后,接下来编写应用层的函数。
- 根据段码屏的定义我们可以得到以下MAP信息。
/*!
* @brief GLASS LCD MAPPING(YR1433)
* LCD allows to display informations on GAS Meter 8-segment digits:
*
* 单价 阀开 阀关 透支 异常 *{[][][][]}请换电池 余额不足 WIFI 灯
1 2 3 4 5 6 7 8
剩余 --- --- --- --- --- --- --- --- 日期
| | | | | | | | | | | | | | | |
累计 --- --- --- --- --- --- --- --- 时间
| | | | | | | | | | | | | | | |
环境 --- --- --- --- --- 。--- 。--- --- 元/m3
*/
- 其中数字字符段(暂时排除符号段)的信号关系如下(参考前述的段码屏MAP图)。
/*!
* @brief An LCD character coding is based on the following matrix:
* COM 0 1 2 3
* SEG(n) { F , G , E , D }
* SEG(n+1) { A , B , C , T/S}
* n <= 14
*
* @brief The character '8' for example is:
------------------------------------
* LSB { 1 , 1 , 1 , 1 }
* MSB { 1 , 1 , 1 , 0 }
------------------------------------
* '8' = 0xEF hex
*/
- 定义数字列表
const uint16_t numberMap[16] =
{
/** 0 1 2 3 4 5 6 7 8 9 a b c d e f*/
0xEB, 0x60, 0xC7, 0xE5, 0x6C, 0xAD, 0xAF, 0xE0, 0xEF, 0xED, 0xEE, 0x2F, 0x8B, 0x67, 0x8F, 0x8E
};
- 定义寄存器和偏移量
#define fastAbs(n) n >= 0 ? n : -n
#define ASCII_CHAR_0 0x30
#define LCD_COM0 LCD_RAM_REGISTER0
#define LCD_COM1 LCD_RAM_REGISTER2
#define LCD_COM2 LCD_RAM_REGISTER4
#define LCD_COM3 LCD_RAM_REGISTER6
#define LCD_COM4 LCD_RAM_REGISTER8
#define LCD_SEG0_SHIFT 0
#define LCD_SEG1_SHIFT 1
#define LCD_SEG2_SHIFT 2
#define LCD_SEG3_SHIFT 3
#define LCD_SEG4_SHIFT 4
#define LCD_SEG5_SHIFT 5
#define LCD_SEG6_SHIFT 6
#define LCD_SEG7_SHIFT 7
#define LCD_SEG8_SHIFT 8
#define LCD_SEG9_SHIFT 9
#define LCD_SEG10_SHIFT 10
#define LCD_SEG11_SHIFT 11
#define LCD_SEG12_SHIFT 12
#define LCD_SEG13_SHIFT 13
#define LCD_SEG14_SHIFT 14
#define LCD_SEG15_SHIFT 15
#define LCD_SEG16_SHIFT 16
#define LCD_SEG17_SHIFT 17
#define LCD_SEG18_SHIFT 18
#define LCD_SEG19_SHIFT 19
#define LCD_COM_NUM 4
#define LCD_SEG_NUM 20
typedef struct
{
uint32_t lcdRegData[LCD_COM_NUM];
} LCD_GLASS_T;
- 浮点数字应用程序
/** public variables */
LCD_GLASS_T lcdGlassInfo;
/*!
* @brief LCD data convert
*
* @param data
*
* @retval None
*
*/
static void LCD_GlassConvert(uint8_t data)
{
uint16_t character = 0;
uint8_t i = 0;
uint8_t j = 0;
switch(data)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
character = numberMap[data - ASCII_CHAR_0];
break;
default:
character = 0x00;
break;
}
/** 分离出单个字符的MSB 4bit和LSB 4bit*/
for(i = 4, j = 0; j < 2; i -= 4, j++)
{
digit[j] = (character >> i) & 0x0F;
}
}
/*!
* @brief LCD glass write float number
*
* @param number
*
* @param dotType
*
* @retval None
*
*/
void LCD_GlassWriteFloat(float data, LCD_DOT_T dotType)
{
char character[20];
int i, j = 0;
sprintf(character, "%09.3f", fastAbs(data));
for(i = 0; i < strlen(character); i++)
{
if(data < 0)
{
if(i == 0)
{
LCD_GlassConvert((uint8_t)character[i]);
}
else if(character[i] == '.')
{
LCD_GlassConvert((uint8_t)character[i + 1]);
i += 1;
}
else
{
LCD_GlassConvert((uint8_t)character[i]);
}
}
else if(character[i] == '.')
{
LCD_GlassConvert((uint8_t)character[i + 1]);
i += 1;
}
else
{
LCD_GlassConvert((uint8_t)character[i]);
}
digitData[j] = digit[0];
digitData[j + 1] = digit[1];
j += 2;
}
/** 转换COM0对应的数据, 每个LSB和MSB的最高位*/
lcdGlassInfo.lcdRegData[LCD_COM0_TYPE] |= (((digitData[1] & 0x8) >> 3) << LCD_SEG0_SHIFT) | (((digitData[0] & 0x8) >> 3) << LCD_SEG1_SHIFT) |\
(((digitData[3] & 0x8) >> 3) << LCD_SEG2_SHIFT) | (((digitData[2] & 0x8) >> 3) << LCD_SEG3_SHIFT) |\
(((digitData[5] & 0x8) >> 3) << LCD_SEG4_SHIFT) | (((digitData[4] & 0x8) >> 3) << LCD_SEG5_SHIFT) |\
(((digitData[7] & 0x8) >> 3) << LCD_SEG6_SHIFT) | (((digitData[6] & 0x8) >> 3) << LCD_SEG7_SHIFT) |\
(((digitData[9] & 0x8) >> 3) << LCD_SEG8_SHIFT) | (((digitData[8] & 0x8) >> 3) << LCD_SEG9_SHIFT) |\
(((digitData[11] & 0x8) >> 3) << LCD_SEG10_SHIFT) | (((digitData[10] & 0x8) >> 3) << LCD_SEG11_SHIFT) |\
(((digitData[13] & 0x8) >> 3) << LCD_SEG12_SHIFT) | (((digitData[12] & 0x8) >> 3) << LCD_SEG13_SHIFT) |\
(((digitData[15] & 0x8) >> 3) << LCD_SEG14_SHIFT) | (((digitData[14] & 0x8) >> 3) << LCD_SEG15_SHIFT);
/** 转换COM1对应的数据*/
lcdGlassInfo.lcdRegData[LCD_COM1_TYPE] |= (((digitData[1] & 0x4) >> 2) << LCD_SEG0_SHIFT) | (((digitData[0] & 0x4) >> 2) << LCD_SEG1_SHIFT) |\
(((digitData[3] & 0x4) >> 2) << LCD_SEG2_SHIFT) | (((digitData[2] & 0x4) >> 2) << LCD_SEG3_SHIFT) |\
(((digitData[5] & 0x4) >> 2) << LCD_SEG4_SHIFT) | (((digitData[4] & 0x4) >> 2) << LCD_SEG5_SHIFT) |\
(((digitData[7] & 0x4) >> 2) << LCD_SEG6_SHIFT) | (((digitData[6] & 0x4) >> 2) << LCD_SEG7_SHIFT) |\
(((digitData[9] & 0x4) >> 2) << LCD_SEG8_SHIFT) | (((digitData[8] & 0x4) >> 2) << LCD_SEG9_SHIFT) |\
(((digitData[11] & 0x4) >> 2) << LCD_SEG10_SHIFT) | (((digitData[10] & 0x4) >> 2) << LCD_SEG11_SHIFT) |\
(((digitData[13] & 0x4) >> 2) << LCD_SEG12_SHIFT) | (((digitData[12] & 0x4) >> 2) << LCD_SEG13_SHIFT) |\
(((digitData[15] & 0x4) >> 2) << LCD_SEG14_SHIFT) | (((digitData[14] & 0x4) >> 2) << LCD_SEG15_SHIFT);
/** 转换COM2对应的数据*/
lcdGlassInfo.lcdRegData[LCD_COM2_TYPE] |= (((digitData[1] & 0x2) >> 1) << LCD_SEG0_SHIFT) | (((digitData[0] & 0x2) >> 1) << LCD_SEG1_SHIFT) |\
(((digitData[3] & 0x2) >> 1) << LCD_SEG2_SHIFT) | (((digitData[2] & 0x2) >> 1) << LCD_SEG3_SHIFT) |\
(((digitData[5] & 0x2) >> 1) << LCD_SEG4_SHIFT) | (((digitData[4] & 0x2) >> 1) << LCD_SEG5_SHIFT) |\
(((digitData[7] & 0x2) >> 1) << LCD_SEG6_SHIFT) | (((digitData[6] & 0x2) >> 1) << LCD_SEG7_SHIFT) |\
(((digitData[9] & 0x2) >> 1) << LCD_SEG8_SHIFT) | (((digitData[8] & 0x2) >> 1) << LCD_SEG9_SHIFT) |\
(((digitData[11] & 0x2) >> 1) << LCD_SEG10_SHIFT) | (((digitData[10] & 0x2) >> 1) << LCD_SEG11_SHIFT) |\
(((digitData[13] & 0x2) >> 1) << LCD_SEG12_SHIFT) | (((digitData[12] & 0x2) >> 1) << LCD_SEG13_SHIFT) |\
(((digitData[15] & 0x2) >> 1) << LCD_SEG14_SHIFT) | (((digitData[14] & 0x2) >> 1) << LCD_SEG15_SHIFT);
/** 转换COM3对应的数据*/
lcdGlassInfo.lcdRegData[LCD_COM3_TYPE] |= ((digitData[1] & 0x1) << LCD_SEG0_SHIFT) | ((digitData[0] & 0x1) << LCD_SEG1_SHIFT) |\
((digitData[3] & 0x1) << LCD_SEG2_SHIFT) | ((digitData[2] & 0x1) << LCD_SEG3_SHIFT) |\
((digitData[5] & 0x1) << LCD_SEG4_SHIFT) | ((digitData[4] & 0x1) << LCD_SEG5_SHIFT) |\
((digitData[7] & 0x1) << LCD_SEG6_SHIFT) | ((digitData[6] & 0x1) << LCD_SEG7_SHIFT) |\
((digitData[9] & 0x1) << LCD_SEG8_SHIFT) | ((digitData[8] & 0x1) << LCD_SEG9_SHIFT) |\
((digitData[11] & 0x1) << LCD_SEG10_SHIFT) | ((digitData[10] & 0x1) << LCD_SEG11_SHIFT) |\
((digitData[13] & 0x1) << LCD_SEG12_SHIFT) | ((digitData[12] & 0x1) << LCD_SEG13_SHIFT) |\
((digitData[15] & 0x1) << LCD_SEG14_SHIFT) | ((digitData[14] & 0x1) << LCD_SEG15_SHIFT);
/** dot type*/
if(dotType == LCD_DOT_TYPE1)
{
LCD_DisSignal(LCD_DOT1_SG);
}
else if(dotType == LCD_DOT_TYPE2)
{
LCD_DisSignal(LCD_DOT2_SG);
}
}
- 实时刷新LCD显示寄存器
/*!
* @brief LCD display
*
* @param data
*
* @retval None
*
*/
void LCD_GlassDisplay(void)
{
HAL_LCD_Write(&LCDHandle, LCD_DIGIT_COM0, LCD_DIGIT_COM0_SEG_MASK, lcdGlassInfo.lcdRegData[LCD_COM0_TYPE]);
HAL_LCD_Write(&LCDHandle, LCD_DIGIT_COM1, LCD_DIGIT_COM1_SEG_MASK, lcdGlassInfo.lcdRegData[LCD_COM1_TYPE]);
HAL_LCD_Write(&LCDHandle, LCD_DIGIT_COM2, LCD_DIGIT_COM2_SEG_MASK, lcdGlassInfo.lcdRegData[LCD_COM2_TYPE]);
HAL_LCD_Write(&LCDHandle, LCD_DIGIT_COM3, LCD_DIGIT_COM3_SEG_MASK, lcdGlassInfo.lcdRegData[LCD_COM3_TYPE]);
/** Update the LCD display*/
HAL_LCD_UpdateDisplayRequest(&LCDHandle);
}
- 注意事项
- 每个COM有两个RAM寄存器(2 x 32bit),分别对应每个像素点
- LCD接口的时钟使用LSI或者LSE,使用时记得要开启其中一个
- 有些内阻较高的显示器,需要更长时间驱动才能达到令人满意的对比度,显示不正常时,可开启high drive模式
版权声明:本文为CSDN博主「weiDev101」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_40749320/article/details/122869925
01 什么是段码屏
段码液晶屏(段码屏)是非点阵类的液晶屏,主要是用来替代LED数码管的,比如温度计、计算器、钟表等等,显示的内容基本都是数字,也有些是符号。
02 段码屏驱动原理
段码液晶由若干段区(单像素或完整符号)组成,每个区段都包含一层在两根电极之间对齐的液晶分子。液晶在电场的影响下,晶体的排序方向会产生扭曲,因而改变其透光性,从而能够看到显示的內容。要使晶体产生扭曲现象,必须使电极两端的压差大于一定的阈值,才能显示內容。
一般段码屏有两个极,一个为段电极,一个为公共极,如下图所示。SEG1 ~ SEGn为段电极,COM为公共极。上述电场的电压就是施加在段电极和公共极两端的。
但是段码液晶显示屏不像数码管那样,施以一定大小的直流正向电压就能显示,如果在段码屏SEG和COM两端加直流电压,将会导致液态晶体不可逆的损坏。
需要某段液晶显示的,那么就在某段液晶的SEG和COM两端施加一定压差的交流电,如果不需要液晶显示的话,那么也需要在SEG和COM两端施加电压,不过所施加电压的压差很小或者为零即可。
03 段码屏驱动实现
- LCD段码屏三个主要参数
-
工作电压
段码液晶屏的操作电压。 -
占空比(Duty)
该参数也称作COM数,定义为1 / (给定LCD上的公共端子数)。一般LCD的驱动方式是采用时分动态扫描的方式。所以每个 COM 的有效选通时间与整个扫描周期的比值,即占空比(Duty)是固定的,等于 1/COM 。 -
偏置(BIAS)
驱动LCD时使用的电压等级。定义为1 / (用于驱动LCD显示器的电压等级数 - 1)
- 基本驱动方法
如液晶屏的工作电压约为3.3V,只要给液晶两个电机间施加约3.3V的电压差(如COM端为3.3V,SEG端为0V),并间隔适当的时间,将两个电极的电压反转(如COM端为0V,SEG端为3.3V)即可让液晶显示。
而液晶不显示时,保证两个电极间的电压差为0V(如COM为3.3V,SEG为3.3V),并且间隔适当的时间反转两极的电压(如COM为0V,SEG为0V)。
- 驱动方案
- MCU没有LCD驱动外设
使用IO口直接驱动的方式,偏置比只能选择1/2,这种方式需要在COM口加上拉、下拉各1个电阻,阻值约为100KΩ - 200KΩ。 - MCU+专用的LCD显示驱动芯片
常用的驱动芯片有HT1621、HT1622,对应的偏压比为1/3、1/4。 - MCU带有LCD外设
如STM32L073系列芯片。
硬件设计
以带有LCD外设的MCU驱动YR1433段码屏为例。
- 主要特性
- 帧速率可调节。
- 占空比支持静态、1/2、1/3、1/4和1/8。
- 偏置支持静态、1/2、1/3和1/4。
- 双缓冲存储器,允许应用程序随时更新LCD_RAM寄存器的数据
- 对比度可调节
- 支持闪烁功能
- LCD控制器框图
- LCD外设的时钟源
- 32.768KHz低速外部时钟(LSE)
- 32.768KHz低速内部时钟(LSI)
- 高速外部时钟(HSE)的分频,最大支持1MHz
YR1433段码液晶屏
- YR1433的参数
工作电压 | DUTY | BIAS |
---|---|---|
3.2V | 1/4 | 1/3 |
- 段码屏MAP图
PIN | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
COM1 | COM1 | 1F | 1A | 2F | 2A | 3F | 3A | 4F | 4A | 5F | 5A | 6F | 6A | 7F | 7A | 8F | 8A | K2 | S | S1 | T2 | |||
COM2 | COM2 | 1G | 1B | 2G | 2B | 3G | 3B | 4G | 4B | 5G | 5B | 6G | 6B | 7G | 7B | 8G | 8B | K3 | T8 | S2 | T1 | |||
COM3 | COM3 | 1E | 1C | 2E | 2C | 3E | 3C | 4E | 4C | 5C | 5C | 6E | 6C | 7E | 7C | 8E | 8C | K4 | T7 | S6 | T0 | |||
COM4 | COM4 | 1D | T3 | 2D | T4 | 3D | T5 | 4D | T6 | 5D | S7 | 6D | S8 | 7D | S4 | 8D | S5 | K1 | K5 | S3 | T |
固件设计
- 固件配置流程
- 定义端口
根据MAP图,我们将STM32L073RBT6的引脚功能分配如下。
#define LCD_SEG0_PIN GPIO_PIN_1
#define LCD_SEG1_PIN GPIO_PIN_2
#define LCD_SEG2_PIN GPIO_PIN_3
#define LCD_SEG3_PIN GPIO_PIN_6
#define LCD_SEG4_PIN GPIO_PIN_7
#define LCD_SEG5_PIN GPIO_PIN_0
#define LCD_SEG6_PIN GPIO_PIN_1
#define LCD_SEG7_PIN GPIO_PIN_3
#define LCD_SEG8_PIN GPIO_PIN_4
#define LCD_SEG9_PIN GPIO_PIN_5
#define LCD_SEG10_PIN GPIO_PIN_10
#define LCD_SEG11_PIN GPIO_PIN_11
#define LCD_SEG12_PIN GPIO_PIN_12
#define LCD_SEG13_PIN GPIO_PIN_13
#define LCD_SEG14_PIN GPIO_PIN_14
#define LCD_SEG15_PIN GPIO_PIN_15
#define LCD_SEG16_PIN GPIO_PIN_8
#define LCD_SEG17_PIN GPIO_PIN_15
#define LCD_SEG18_PIN GPIO_PIN_0
#define LCD_SEG19_PIN GPIO_PIN_1
#define LCD_SEG20_PIN GPIO_PIN_2
#define LCD_SEG_GPIOA_PORT GPIOA
#define LCD_SEG_GPIOB_PORT GPIOB
#define LCD_SEG_GPIOC_PORT GPIOC
#define LCD_SEG_AF GPIO_AF1_LCD
#define LCD_COM0_PIN GPIO_PIN_8
#define LCD_COM1_PIN GPIO_PIN_9
#define LCD_COM2_PIN GPIO_PIN_10
#define LCD_COM3_PIN GPIO_PIN_9
#define LCD_COM0_2_GPIO_PORT GPIOA
#define LCD_COM3_GPIO_PORT GPIOB
#define LCD_COM_AF GPIO_AF1_LCD
- 初始化寄存器
void LCD_GlassInit(void)
{
LCDHandle.Instance = LCD;
LCDHandle.Init.Prescaler = LCD_PRESCALER_4;
LCDHandle.Init.Divider = LCD_DIVIDER_16;
LCDHandle.Init.Duty = LCD_DUTY_1_4;
LCDHandle.Init.Bias = LCD_BIAS_1_3;
LCDHandle.Init.VoltageSource = LCD_VOLTAGESOURCE_INTERNAL;
LCDHandle.Init.Contrast = LCD_CONTRASTLEVEL_5;
LCDHandle.Init.DeadTime = LCD_DEADTIME_0;
LCDHandle.Init.PulseOnDuration = LCD_PULSEONDURATION_7;
LCDHandle.Init.BlinkMode = LCD_BLINKMODE_OFF;
LCDHandle.Init.BlinkFrequency = LCD_BLINKFREQUENCY_DIV8;
LCDHandle.Init.MuxSegment = LCD_MUXSEGMENT_DISABLE;
/* YR1433 LCD glass need open high drive*/
LCDHandle.Init.HighDrive = LCD_HIGHDRIVE_1;
__HAL_LCD_RESET_HANDLE_STATE(&LCDHandle);
HAL_LCD_MspInit(&LCDHandle);
HAL_LCD_Init(&LCDHandle);
HAL_LCD_Clear(&LCDHandle);
}
- 用LSE作为LCD时钟源并初始化端口
void HAL_LCD_MspInit(LCD_HandleTypeDef *hlcd)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_OscInitTypeDef RCC_OscInitStruct;
if(hlcd->Instance == LCD)
{
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
__HAL_RCC_BACKUPRESET_FORCE();
__HAL_RCC_BACKUPRESET_RELEASE();
/** Enable LCD LSE Clock*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
LCD_SEG_GPIOA_CLK_ENABLE();
LCD_SEG_GPIOB_CLK_ENABLE();
LCD_SEG_GPIOC_CLK_ENABLE();
LCD_COM0_2_GPIO_CLK_ENABLE();
LCD_COM3_GPIO_CLK_ENABLE();
/** Configure peripheral GPIO*/
GPIO_InitStruct.Pin = LCD_SEG0_PIN | LCD_SEG1_PIN | LCD_SEG2_PIN \
| LCD_SEG3_PIN | LCD_SEG4_PIN | LCD_SEG17_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = LCD_SEG_AF;
HAL_GPIO_Init(LCD_SEG_GPIOA_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_SEG5_PIN | LCD_SEG6_PIN | LCD_SEG7_PIN \
| LCD_SEG8_PIN | LCD_SEG9_PIN | LCD_SEG10_PIN \
| LCD_SEG11_PIN | LCD_SEG12_PIN | LCD_SEG13_PIN \
| LCD_SEG14_PIN | LCD_SEG15_PIN | LCD_SEG16_PIN;
HAL_GPIO_Init(LCD_SEG_GPIOB_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_SEG18_PIN | LCD_SEG19_PIN;
HAL_GPIO_Init(LCD_SEG_GPIOC_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_COM0_PIN | LCD_COM1_PIN | LCD_COM2_PIN;
GPIO_InitStruct.Alternate = LCD_COM_AF;
HAL_GPIO_Init(LCD_COM0_2_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LCD_COM3_PIN;
GPIO_InitStruct.Alternate = LCD_COM_AF;
HAL_GPIO_Init(LCD_COM3_GPIO_PORT, &GPIO_InitStruct);
LCD_CLK_ENABLE();
}
}
- 固件应用实例
限于篇幅,以下只列举简单的数字显示应用,其他部分的应用,同学可以自行类推研究下。
在上述配置完成后,接下来编写应用层的函数。
- 根据段码屏的定义我们可以得到以下MAP信息。
/*!
* @brief GLASS LCD MAPPING(YR1433)
* LCD allows to display informations on GAS Meter 8-segment digits:
*
* 单价 阀开 阀关 透支 异常 *{[][][][]}请换电池 余额不足 WIFI 灯
1 2 3 4 5 6 7 8
剩余 --- --- --- --- --- --- --- --- 日期
| | | | | | | | | | | | | | | |
累计 --- --- --- --- --- --- --- --- 时间
| | | | | | | | | | | | | | | |
环境 --- --- --- --- --- 。--- 。--- --- 元/m3
*/
- 其中数字字符段(暂时排除符号段)的信号关系如下(参考前述的段码屏MAP图)。
/*!
* @brief An LCD character coding is based on the following matrix:
* COM 0 1 2 3
* SEG(n) { F , G , E , D }
* SEG(n+1) { A , B , C , T/S}
* n <= 14
*
* @brief The character '8' for example is:
------------------------------------
* LSB { 1 , 1 , 1 , 1 }
* MSB { 1 , 1 , 1 , 0 }
------------------------------------
* '8' = 0xEF hex
*/
- 定义数字列表
const uint16_t numberMap[16] =
{
/** 0 1 2 3 4 5 6 7 8 9 a b c d e f*/
0xEB, 0x60, 0xC7, 0xE5, 0x6C, 0xAD, 0xAF, 0xE0, 0xEF, 0xED, 0xEE, 0x2F, 0x8B, 0x67, 0x8F, 0x8E
};
- 定义寄存器和偏移量
#define fastAbs(n) n >= 0 ? n : -n
#define ASCII_CHAR_0 0x30
#define LCD_COM0 LCD_RAM_REGISTER0
#define LCD_COM1 LCD_RAM_REGISTER2
#define LCD_COM2 LCD_RAM_REGISTER4
#define LCD_COM3 LCD_RAM_REGISTER6
#define LCD_COM4 LCD_RAM_REGISTER8
#define LCD_SEG0_SHIFT 0
#define LCD_SEG1_SHIFT 1
#define LCD_SEG2_SHIFT 2
#define LCD_SEG3_SHIFT 3
#define LCD_SEG4_SHIFT 4
#define LCD_SEG5_SHIFT 5
#define LCD_SEG6_SHIFT 6
#define LCD_SEG7_SHIFT 7
#define LCD_SEG8_SHIFT 8
#define LCD_SEG9_SHIFT 9
#define LCD_SEG10_SHIFT 10
#define LCD_SEG11_SHIFT 11
#define LCD_SEG12_SHIFT 12
#define LCD_SEG13_SHIFT 13
#define LCD_SEG14_SHIFT 14
#define LCD_SEG15_SHIFT 15
#define LCD_SEG16_SHIFT 16
#define LCD_SEG17_SHIFT 17
#define LCD_SEG18_SHIFT 18
#define LCD_SEG19_SHIFT 19
#define LCD_COM_NUM 4
#define LCD_SEG_NUM 20
typedef struct
{
uint32_t lcdRegData[LCD_COM_NUM];
} LCD_GLASS_T;
- 浮点数字应用程序
/** public variables */
LCD_GLASS_T lcdGlassInfo;
/*!
* @brief LCD data convert
*
* @param data
*
* @retval None
*
*/
static void LCD_GlassConvert(uint8_t data)
{
uint16_t character = 0;
uint8_t i = 0;
uint8_t j = 0;
switch(data)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
character = numberMap[data - ASCII_CHAR_0];
break;
default:
character = 0x00;
break;
}
/** 分离出单个字符的MSB 4bit和LSB 4bit*/
for(i = 4, j = 0; j < 2; i -= 4, j++)
{
digit[j] = (character >> i) & 0x0F;
}
}
/*!
* @brief LCD glass write float number
*
* @param number
*
* @param dotType
*
* @retval None
*
*/
void LCD_GlassWriteFloat(float data, LCD_DOT_T dotType)
{
char character[20];
int i, j = 0;
sprintf(character, "%09.3f", fastAbs(data));
for(i = 0; i < strlen(character); i++)
{
if(data < 0)
{
if(i == 0)
{
LCD_GlassConvert((uint8_t)character[i]);
}
else if(character[i] == '.')
{
LCD_GlassConvert((uint8_t)character[i + 1]);
i += 1;
}
else
{
LCD_GlassConvert((uint8_t)character[i]);
}
}
else if(character[i] == '.')
{
LCD_GlassConvert((uint8_t)character[i + 1]);
i += 1;
}
else
{
LCD_GlassConvert((uint8_t)character[i]);
}
digitData[j] = digit[0];
digitData[j + 1] = digit[1];
j += 2;
}
/** 转换COM0对应的数据, 每个LSB和MSB的最高位*/
lcdGlassInfo.lcdRegData[LCD_COM0_TYPE] |= (((digitData[1] & 0x8) >> 3) << LCD_SEG0_SHIFT) | (((digitData[0] & 0x8) >> 3) << LCD_SEG1_SHIFT) |\
(((digitData[3] & 0x8) >> 3) << LCD_SEG2_SHIFT) | (((digitData[2] & 0x8) >> 3) << LCD_SEG3_SHIFT) |\
(((digitData[5] & 0x8) >> 3) << LCD_SEG4_SHIFT) | (((digitData[4] & 0x8) >> 3) << LCD_SEG5_SHIFT) |\
(((digitData[7] & 0x8) >> 3) << LCD_SEG6_SHIFT) | (((digitData[6] & 0x8) >> 3) << LCD_SEG7_SHIFT) |\
(((digitData[9] & 0x8) >> 3) << LCD_SEG8_SHIFT) | (((digitData[8] & 0x8) >> 3) << LCD_SEG9_SHIFT) |\
(((digitData[11] & 0x8) >> 3) << LCD_SEG10_SHIFT) | (((digitData[10] & 0x8) >> 3) << LCD_SEG11_SHIFT) |\
(((digitData[13] & 0x8) >> 3) << LCD_SEG12_SHIFT) | (((digitData[12] & 0x8) >> 3) << LCD_SEG13_SHIFT) |\
(((digitData[15] & 0x8) >> 3) << LCD_SEG14_SHIFT) | (((digitData[14] & 0x8) >> 3) << LCD_SEG15_SHIFT);
/** 转换COM1对应的数据*/
lcdGlassInfo.lcdRegData[LCD_COM1_TYPE] |= (((digitData[1] & 0x4) >> 2) << LCD_SEG0_SHIFT) | (((digitData[0] & 0x4) >> 2) << LCD_SEG1_SHIFT) |\
(((digitData[3] & 0x4) >> 2) << LCD_SEG2_SHIFT) | (((digitData[2] & 0x4) >> 2) << LCD_SEG3_SHIFT) |\
(((digitData[5] & 0x4) >> 2) << LCD_SEG4_SHIFT) | (((digitData[4] & 0x4) >> 2) << LCD_SEG5_SHIFT) |\
(((digitData[7] & 0x4) >> 2) << LCD_SEG6_SHIFT) | (((digitData[6] & 0x4) >> 2) << LCD_SEG7_SHIFT) |\
(((digitData[9] & 0x4) >> 2) << LCD_SEG8_SHIFT) | (((digitData[8] & 0x4) >> 2) << LCD_SEG9_SHIFT) |\
(((digitData[11] & 0x4) >> 2) << LCD_SEG10_SHIFT) | (((digitData[10] & 0x4) >> 2) << LCD_SEG11_SHIFT) |\
(((digitData[13] & 0x4) >> 2) << LCD_SEG12_SHIFT) | (((digitData[12] & 0x4) >> 2) << LCD_SEG13_SHIFT) |\
(((digitData[15] & 0x4) >> 2) << LCD_SEG14_SHIFT) | (((digitData[14] & 0x4) >> 2) << LCD_SEG15_SHIFT);
/** 转换COM2对应的数据*/
lcdGlassInfo.lcdRegData[LCD_COM2_TYPE] |= (((digitData[1] & 0x2) >> 1) << LCD_SEG0_SHIFT) | (((digitData[0] & 0x2) >> 1) << LCD_SEG1_SHIFT) |\
(((digitData[3] & 0x2) >> 1) << LCD_SEG2_SHIFT) | (((digitData[2] & 0x2) >> 1) << LCD_SEG3_SHIFT) |\
(((digitData[5] & 0x2) >> 1) << LCD_SEG4_SHIFT) | (((digitData[4] & 0x2) >> 1) << LCD_SEG5_SHIFT) |\
(((digitData[7] & 0x2) >> 1) << LCD_SEG6_SHIFT) | (((digitData[6] & 0x2) >> 1) << LCD_SEG7_SHIFT) |\
(((digitData[9] & 0x2) >> 1) << LCD_SEG8_SHIFT) | (((digitData[8] & 0x2) >> 1) << LCD_SEG9_SHIFT) |\
(((digitData[11] & 0x2) >> 1) << LCD_SEG10_SHIFT) | (((digitData[10] & 0x2) >> 1) << LCD_SEG11_SHIFT) |\
(((digitData[13] & 0x2) >> 1) << LCD_SEG12_SHIFT) | (((digitData[12] & 0x2) >> 1) << LCD_SEG13_SHIFT) |\
(((digitData[15] & 0x2) >> 1) << LCD_SEG14_SHIFT) | (((digitData[14] & 0x2) >> 1) << LCD_SEG15_SHIFT);
/** 转换COM3对应的数据*/
lcdGlassInfo.lcdRegData[LCD_COM3_TYPE] |= ((digitData[1] & 0x1) << LCD_SEG0_SHIFT) | ((digitData[0] & 0x1) << LCD_SEG1_SHIFT) |\
((digitData[3] & 0x1) << LCD_SEG2_SHIFT) | ((digitData[2] & 0x1) << LCD_SEG3_SHIFT) |\
((digitData[5] & 0x1) << LCD_SEG4_SHIFT) | ((digitData[4] & 0x1) << LCD_SEG5_SHIFT) |\
((digitData[7] & 0x1) << LCD_SEG6_SHIFT) | ((digitData[6] & 0x1) << LCD_SEG7_SHIFT) |\
((digitData[9] & 0x1) << LCD_SEG8_SHIFT) | ((digitData[8] & 0x1) << LCD_SEG9_SHIFT) |\
((digitData[11] & 0x1) << LCD_SEG10_SHIFT) | ((digitData[10] & 0x1) << LCD_SEG11_SHIFT) |\
((digitData[13] & 0x1) << LCD_SEG12_SHIFT) | ((digitData[12] & 0x1) << LCD_SEG13_SHIFT) |\
((digitData[15] & 0x1) << LCD_SEG14_SHIFT) | ((digitData[14] & 0x1) << LCD_SEG15_SHIFT);
/** dot type*/
if(dotType == LCD_DOT_TYPE1)
{
LCD_DisSignal(LCD_DOT1_SG);
}
else if(dotType == LCD_DOT_TYPE2)
{
LCD_DisSignal(LCD_DOT2_SG);
}
}
- 实时刷新LCD显示寄存器
/*!
* @brief LCD display
*
* @param data
*
* @retval None
*
*/
void LCD_GlassDisplay(void)
{
HAL_LCD_Write(&LCDHandle, LCD_DIGIT_COM0, LCD_DIGIT_COM0_SEG_MASK, lcdGlassInfo.lcdRegData[LCD_COM0_TYPE]);
HAL_LCD_Write(&LCDHandle, LCD_DIGIT_COM1, LCD_DIGIT_COM1_SEG_MASK, lcdGlassInfo.lcdRegData[LCD_COM1_TYPE]);
HAL_LCD_Write(&LCDHandle, LCD_DIGIT_COM2, LCD_DIGIT_COM2_SEG_MASK, lcdGlassInfo.lcdRegData[LCD_COM2_TYPE]);
HAL_LCD_Write(&LCDHandle, LCD_DIGIT_COM3, LCD_DIGIT_COM3_SEG_MASK, lcdGlassInfo.lcdRegData[LCD_COM3_TYPE]);
/** Update the LCD display*/
HAL_LCD_UpdateDisplayRequest(&LCDHandle);
}
- 注意事项
- 每个COM有两个RAM寄存器(2 x 32bit),分别对应每个像素点
- LCD接口的时钟使用LSI或者LSE,使用时记得要开启其中一个
- 有些内阻较高的显示器,需要更长时间驱动才能达到令人满意的对比度,显示不正常时,可开启high drive模式
版权声明:本文为CSDN博主「weiDev101」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_40749320/article/details/122869925
暂无评论