STM32外设LCD的配置和应用

以STM32L0为例。

01 外设简介

  1. 主要特性
  • 帧速率可调节。
  • 占空比支持静态、1/2、1/3、1/4和1/8。
  • 偏置支持静态、1/2、1/3和1/4。
  • 双缓冲存储器,允许应用程序随时更新LCD_RAM寄存器的数据
  • 对比度可调节
  • 支持闪烁功能
  1. LCD控制器框图
    Snipaste_2022-02-14_22-39-10
  2. LCD外设的时钟源
  • 32.768KHz低速外部时钟(LSE)
  • 32.768KHz低速内部时钟(LSI)
  • 高速外部时钟(HSE)的分频,最大支持1MHz

02 外设配置

以驱动YR1433段码液晶屏为例。

  1. YR1433段码屏
    Snipaste_2022-02-15_20-46-51
  • YR1433的参数
工作电压DUTYBIAS
3.2V1/41/3
  • 段码屏MAP图
PIN123456789101112131415161718192021222324
COM1COM11F1A2F2A3F3A4F4A5F5A6F6A7F7A8F8AK2SS1T2
COM2COM21G1B2G2B3G3B4G4B5G5B6G6B7G7B8G8BK3T8S2T1
COM3COM31E1C2E2C3E3C4E4C5C5C6E6C7E7C8E8CK4T7S6T0
COM4COM41DT32DT43DT54DT65DS76DS87DS48DS5K1K5S3T
  1. 外设配置流程
    外设文章素材
  • 定义端口
    根据MAP图,我们将STM32L073RBT6的引脚功能分配如下。

Snipaste_2022-02-15_20-49-26

#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();
    }
}

03 外设应用

限于篇幅,以下只列举简单的数字显示应用,其他部分的应用,同学可以自行类推研究下。
在上述配置完成后,接下来编写应用层的函数。

  • 根据段码屏的定义我们可以得到以下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);
}
  1. 注意事项
  • 每个COM有两个RAM寄存器(2 x 32bit),分别对应每个像素点
  • LCD接口的时钟使用LSI或者LSE,使用时记得要开启其中一个
  • 有些内阻较高的显示器,需要更长时间驱动才能达到令人满意的对比度,显示不正常时,可开启high drive模式
  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MorroMaker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值