本篇文章主要记录一下我在工作中用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 端。下图是某厂商生产的段码屏的资料:
最终程序中驱动段码屏显示图标的代码如下:
#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 来说:
因为 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 是怎么来的?
这里就要看 STM32L152RC 数据手册了,手册中 LCD 显示缓存如下图:
上图中 LCD_RAM 寄存器中每一位都是一个 SEG,而我们实际硬件中段码屏的 SEG 只接了 12 个,分别为 SEG16 ~ SEG 27,其它没有用到,所以最终的段码屏数据也要写到 SEG16 ~ SEG27 之中,因此,在写的时候需要偏移到第 16位,因为 0 ~ 15硬件中没使用,写入无效。比如你的硬件中段码屏使用的是 SEG18 ~ SEG26,那你写数据的时候就必须要偏移到第18位。
最后我们来看一下 Set_Lcd_Num() 函数中 3,3,3,2 这四个数字怎么来的?
上图中的 3,3,3,2 的意思就是在写数字之前,要先把对应位置的缓存给清除掉,看下图就会明白:
后面还有 Clr_Lcd_Num() 函数,此函数里的内容就很简单了,搞清楚前面文章内容,此函数内容就不成问题了。