STM32关于驱动段码屏显示

本篇文章主要记录一下我在工作中用STM32单片机驱动段码屏显示内容,不讲解具体的驱动原理,只是单纯记录如何编写驱动屏幕图标显示的代码,以便我日后查看。

单片机:STM32L152RCT6A
IDE:Keil5.25.2.0
代码生成:STM32CUBEMX4.23.0

具体段码屏的驱动代码我直接贴出来:

/* LCD init function */
static void MX_LCD_Init(void)
{
    hlcd.Instance = LCD;
    hlcd.Init.Prescaler = LCD_PRESCALER_1;
    hlcd.Init.Divider = LCD_DIVIDER_31;
    hlcd.Init.Duty = LCD_DUTY_1_4;
    hlcd.Init.Bias = LCD_BIAS_1_3;
    hlcd.Init.VoltageSource = LCD_VOLTAGESOURCE_INTERNAL;
    hlcd.Init.Contrast = LCD_CONTRASTLEVEL_3;
    hlcd.Init.DeadTime = LCD_DEADTIME_0;
    hlcd.Init.PulseOnDuration = LCD_PULSEONDURATION_4;
    hlcd.Init.MuxSegment = LCD_MUXSEGMENT_DISABLE;
    hlcd.Init.BlinkMode = LCD_BLINKMODE_OFF;
    hlcd.Init.BlinkFrequency = LCD_BLINKFREQUENCY_DIV8;
    if (HAL_LCD_Init(&hlcd) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
}


void HAL_LCD_MspInit(LCD_HandleTypeDef* hlcd)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    if(hlcd->Instance==LCD)
    {
        /* USER CODE BEGIN LCD_MspInit 0 */

        /* USER CODE END LCD_MspInit 0 */
        /* Peripheral clock enable */
        __HAL_RCC_LCD_CLK_ENABLE();

        /**LCD GPIO Configuration
        PB8     ------> LCD_SEG16
        PA15    ------> LCD_SEG17
        PC0     ------> LCD_SEG18
        PC1     ------> LCD_SEG19
        PC2     ------> LCD_SEG20
        PC3     ------> LCD_SEG21
        PC4     ------> LCD_SEG22
        PC5     ------> LCD_SEG23
        PC6     ------> LCD_SEG24
        PC7     ------> LCD_SEG25
        PC8     ------> LCD_SEG26
		PC9     ------> LCD_SEG27
        PA8     ------> LCD_COM0
        PA9     ------> LCD_COM1
        PA10    ------> LCD_COM2
        PB9     ------> LCD_COM3
        */
        GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
                              |GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7
                              |GPIO_PIN_8|GPIO_PIN_9;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate = GPIO_AF11_LCD;
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_15;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate = GPIO_AF11_LCD;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_8;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate = GPIO_AF11_LCD;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

        /* USER CODE BEGIN LCD_MspInit 1 */

        /* USER CODE END LCD_MspInit 1 */
    }
}

void HAL_LCD_MspDeInit(LCD_HandleTypeDef* hlcd)
{

    if(hlcd->Instance==LCD)
    {
        /* USER CODE BEGIN LCD_MspDeInit 0 */

        /* USER CODE END LCD_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_LCD_CLK_DISABLE();

        /**LCD GPIO Configuration
        PB8     ------> LCD_SEG16
        PA15    ------> LCD_SEG17
        PC0     ------> LCD_SEG18
        PC1     ------> LCD_SEG19
        PC2     ------> LCD_SEG20
        PC3     ------> LCD_SEG21
        PC4     ------> LCD_SEG22
        PC5     ------> LCD_SEG23
        PC6     ------> LCD_SEG24
        PC7     ------> LCD_SEG25
        PC8     ------> LCD_SEG26
        PC9     ------> LCD_SEG27
        PA8     ------> LCD_COM0
        PA9     ------> LCD_COM1
        PA10     ------> LCD_COM2
        PB9     ------> LCD_COM3
        */
        HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3
                        |GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7
                        |GPIO_PIN_8|GPIO_PIN_9);

        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_15);

        HAL_GPIO_DeInit(GPIOB, GPIO_PIN_9|GPIO_PIN_8);

        /* USER CODE BEGIN LCD_MspDeInit 1 */

        /* USER CODE END LCD_MspDeInit 1 */
    }
}

从上面的初始化代码可以知道,段码屏共有4个 COM 端,12个 SEG 端。下图是某厂商生产的段码屏的资料:
STM32关于驱动段码屏显示
最终程序中驱动段码屏显示图标的代码如下:

#include "main.h"
extern LCD_HandleTypeDef hlcd;

//T1 minus
//T2 gps
//T3 cloud
//T4 bluetooth
//T5 play
//T6 stop
//T7 charge

const u32 DISP_PLAY[2] = {9, LCD_RAM_REGISTER0};
const u32 DISP_STOP[2] = {9, LCD_RAM_REGISTER2};
const u32 DISP_GPS[2] = {4, LCD_RAM_REGISTER0};		
const u32 DISP_CLOUD[2] = {4, LCD_RAM_REGISTER2};	
const u32 DISP_BT[2] = {4, LCD_RAM_REGISTER4};		
const u32 DISP_CHARGE[2] = {9, LCD_RAM_REGISTER4};	
const u32 DISP_BATCASE[2] = {9, LCD_RAM_REGISTER6};
const u32 DISP_NOSIGN[2] = {11, LCD_RAM_REGISTER6};
const u32 DISP_EMAX[2] = {1, LCD_RAM_REGISTER2};
const u32 DISP_EMIN[2] = {1, LCD_RAM_REGISTER6};
const u32 DISP_DGREE[2] = {11, LCD_RAM_REGISTER0};
const u32 DISP_RH[2] = {11, LCD_RAM_REGISTER4};
const u32 DISP_DAY[2] = {11, LCD_RAM_REGISTER2};	
const u32 DISP_DOT[2] = {7, LCD_RAM_REGISTER6};
const u32 DISP_MINUS[2] = {1, LCD_RAM_REGISTER4};
const u32 DISP_BAT[4][2] = {
	{10, LCD_RAM_REGISTER2}, //BAR1
	{10, LCD_RAM_REGISTER4}, //BAR2
	{10, LCD_RAM_REGISTER0}, //BAR3
	{10, LCD_RAM_REGISTER6}, //BAR4
};
const u32 DISP_SIGN[5][2] = {
	{0, LCD_RAM_REGISTER6}, //L1
	{0, LCD_RAM_REGISTER4}, //L2
	{0, LCD_RAM_REGISTER2}, //L3
	{0, LCD_RAM_REGISTER0}, //L4
	{1, LCD_RAM_REGISTER0}, //L5
};

const u8 DISP_NUM[3] = {2, 5, 7};
const u8 DISP_NUM_TAB[10][4] =
{
	{3, 2, 3, 2}, 	//0
	{0, 2, 2, 0}, 	//1
	{2, 3, 1, 2}, 	//2
	{2, 3, 2, 2}, 	//3
	{1, 3, 2, 0},	//4
	{3, 1, 2, 2}, 	//5
	{3, 1, 3, 2}, 	//6
	{2, 2, 2, 0}, 	//7
	{3, 3, 3, 2}, 	//8
	{3, 3, 2, 2},	//9
};

void Set_Lcd_Dot(const u32 *dot)
{
    HAL_LCD_Write(&hlcd, dot[1], (u32)~(1<<(dot[0]+16)), (u32)(1<<(dot[0]+16)));
}

void Clr_Lcd_Dot(const u32 *dot)
{
    HAL_LCD_Write(&hlcd, dot[1], (u32)~(1<<(dot[0]+16)), 0);
}

void Set_Lcd_Num(const u8 c, u8 num)
{
    HAL_LCD_Write(&hlcd, LCD_RAM_REGISTER0, (u32)~(3<<(DISP_NUM[c]+16)), (u32)(DISP_NUM_TAB[num][0]<<(DISP_NUM[c]+16)));
    HAL_LCD_Write(&hlcd, LCD_RAM_REGISTER2, (u32)~(3<<(DISP_NUM[c]+16)), (u32)(DISP_NUM_TAB[num][1]<<(DISP_NUM[c]+16)));
    HAL_LCD_Write(&hlcd, LCD_RAM_REGISTER4, (u32)~(3<<(DISP_NUM[c]+16)), (u32)(DISP_NUM_TAB[num][2]<<(DISP_NUM[c]+16)));
    HAL_LCD_Write(&hlcd, LCD_RAM_REGISTER6, (u32)~(2<<(DISP_NUM[c]+16)), (u32)(DISP_NUM_TAB[num][3]<<(DISP_NUM[c]+16)));
}

void Clr_Lcd_Num(const u8 c)
{
    HAL_LCD_Write(&hlcd, LCD_RAM_REGISTER0, (u32)~(3<<(DISP_NUM[c]+16)), 0);
    HAL_LCD_Write(&hlcd, LCD_RAM_REGISTER2, (u32)~(3<<(DISP_NUM[c]+16)), 0);
    HAL_LCD_Write(&hlcd, LCD_RAM_REGISTER4, (u32)~(3<<(DISP_NUM[c]+16)), 0);
    HAL_LCD_Write(&hlcd, LCD_RAM_REGISTER6, (u32)~(2<<(DISP_NUM[c]+16)), 0);
}

上面的驱动代码中 DISP_PLAY 这样的数组是怎么得来的?根据段码屏资料有如下表(表1):

PIN 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
COM1 - - - COM1 L4 L5 1F 1A T2 2F 2A 3F 3A T5 BAR3 C
COM2 - - COM2 - L3 Max 1G 1B T3 2G 2B 3G 3B T6 BAR1 Day
COM3 - COM3 - - L2 T1 1E 1C T4 2E 2C 3E 3C T7 BAR2 %RH
COM4 COM4 - - - L1 Min - 1D - - 2D DP 3D BAR5 BAR4 X

我们把上表转换一下,方便我们查看(表2):

0 1 2 3 4 5 6 7 8 9 10 11
COM1 L4 L5 1F 1A T2 2F 2A 3F 3A T5 BAR3 C
COM2 L3 Max 1G 1B T3 2G 2B 3G 3B T6 BAR1 Day
COM3 L2 T1 1E 1C T4 2E 2C 3E 3C T7 BAR2 %RH
COM4 L1 Min - 1D - - 2D DP 3D BAR5 BAR4 X

其中 COM1 对应 STM32 的 LCD_RAM_REGISTER 为 LCD_RAM_REGISTER0,COM2 对应 LCD_RAM_REGISTER2,COM3 对应 LCD_RAM_REGISTER4,COM4 对应 LCD_RAM_REGISTER6,如下表:

COM RAM
COM1 LCD_RAM_REGISTER0
COM2 LCD_RAM_REGISTER2
COM3 LCD_RAM_REGISTER4
COM4 LCD_RAM_REGISTER6
COM(n) LCD_RAM_REGISTER 2*(n-1)

所以,我们来看一下 DISP_PLAY[2] = {9, LCD_RAM_REGISTER0},其中第一个值 9 就是 表2 中 T5 所处的列数(SEG端);第二个值 LCD_RAM_REGISTER0 为 T5 所在的行数(COM端)。使用这种方法把段码屏中的所有图标都写下来即可。

接下来看下 DISP_NUM[3] = {2, 5, 7} 这行是怎么来的?
变量 DISP_NUM 是记录段码屏中数字所在列位置的起始位置。例如,段码屏中的第 1 个数字最先出现在 表2 中的第 2 列;第 2 个数字最先出现在第 5 列;第 3 个数字最先出现在第 7 列。所以用一个变量 DISP_NUM 记录这些数字就得到 DISP_NUM[3] = {2, 5, 7} 。

接下来再看下 DISP_NUM_TAB[10][4] 二维数组里面的一堆数字是怎么来的?
注意,DISP_NUM,以及 DISP_NUM_TAB 里面的内容可以按自己的方法写,这里不一定要这样写。我这样写的目的是配合 Set_Lcd_Num() 这个函数的,此函数不同的实现方法,会导致 DISP_NUM、DISP_NUM_TAB 变量里面的内容不同。

由于 Set_Lcd_Num() 函数是一行一行显示段码屏中数字部分内容的,所以 DISP_NUM_TAB 里面的每一个一维数组从 [0] ~ [3] 都完整表达了一个数字。我们就 {3, 2, 3, 2}, //0 数字 0 来说:
STM32关于驱动段码屏显示
因为 3 个数字的段码一样,所以我们只解析一个数字,把 DISP_NUM_TAB 里的内容填满即可。因为段码每个段都是独立的,显示时不能相互干扰,所以有上图中的权值,就相当于左移,避免段码显示时干扰。那我们来先来填 0,数字 0 占用段码为 A,B,C,D,E,F(只看其中一个数字),第一行 F*1+A*2 = 3; 第二行 0*1+B*2 = 2;第三行 E*1+C*2 = 3;第四行 0*1+D*2 = 2,所以最终数字 0 为 {3, 2, 3, 2};我们再写一个数字 1,数字 1 点段码 B,C,第一行没有B,C,所以为 0*1+0*2 = 0;第二行 0*1+B*2 = 2;第三行 0*1+C*2 = 2;第四行 0*0+0*0 = 0,所以数字 1 最终表示为 {0, 2, 2, 0},后面 2 ~ 9 都是这样写的。

接下来看一下 Set_Lcd_Num() 函数中 16 是怎么来的?
STM32关于驱动段码屏显示
这里就要看 STM32L152RC 数据手册了,手册中 LCD 显示缓存如下图:
STM32关于驱动段码屏显示
上图中 LCD_RAM 寄存器中每一位都是一个 SEG,而我们实际硬件中段码屏的 SEG 只接了 12 个,分别为 SEG16 ~ SEG 27,其它没有用到,所以最终的段码屏数据也要写到 SEG16 ~ SEG27 之中,因此,在写的时候需要偏移到第 16位,因为 0 ~ 15硬件中没使用,写入无效。比如你的硬件中段码屏使用的是 SEG18 ~ SEG26,那你写数据的时候就必须要偏移到第18位。

最后我们来看一下 Set_Lcd_Num() 函数中 3,3,3,2 这四个数字怎么来的?
STM32关于驱动段码屏显示
上图中的 3,3,3,2 的意思就是在写数字之前,要先把对应位置的缓存给清除掉,看下图就会明白:
STM32关于驱动段码屏显示

后面还有 Clr_Lcd_Num() 函数,此函数里的内容就很简单了,搞清楚前面文章内容,此函数内容就不成问题了。

上一篇:外部按键中断精准控制步进电机起保停,正反转,加减速Arduino+TB6600驱动器)


下一篇:【Rust日报】2020-11-20 Rust的Pin与Unpin