本帖最后由 xld0932 于 2024-2-2 14:42 编辑
#申请原创# @21小跑堂
- 解析SLCD例程设计思路
- 基于SLCD例程设计框架,移植产品LCD,实现显示功能
0.前因
段码LCD显示时,不管是通过驱动芯片驱动段码LCD显示的,还是使用带有驱动段码LCD能力的MCU,在实际应用中都需要实现硬件原理图、段码LCD真值表、显示缓存这三者之间的对应关系,只有这些对应一致了,应用程序才能够随心所欲的显示需求内容,而这三者的对应关系一直是应用设计中一个很头疼的问题,有些时候可能会因为PCB布线的方便,使得连接段码LCD的COM/SEG顺序变得错乱,这样在找对应关系的时候,会更加的复杂!
BUT……不用慌,今天我们找到一个解决这一老大难问题的程序设计思路/框架,就是隐藏在MM32L0160的官方示例程序中的SLCD例程,有些小伙伴在看到这段程序的时候会感觉看不懂,不明白官方这么设计的思路/意图是啥,为什么要这么设计,这么设计的用意/优点体现在哪里?简单来说就是看不懂,也不知道怎么应用到自己的产品中来。下面我们会结合官方的SLCD示例程序分两大类进行讲解,理解SLCD例程的设计思路,掌握SLCD例程的移植,加速终端产品的应用开发。
1.概述
早在2022年7月,灵动微电子就发布了低功耗MM32L0130系列MCU产品,它搭载Arm Cortex-M0+处理器,集成段码LCD驱动,功耗可低至100nA,适用于更多低功耗的应用场景。MM32L0130系列MCU集成的段码LCD驱动(SLCD),从用户实际产品应用设计出发,优化驱动功能,最大程度的满足客户应用需求。支持待机状态下显示,最低功耗可以做到1.5uA;支持多达40*4或者36*8个显示段,支持硬件配置刷新帧率、显示对比度、段码的闪烁功能;支持1.8V~5.5V的段码LCD显示屏,内置电荷泵,在MCU电源电压下降时可依然保证LCD的清晰显示;支持多种占空比和偏压模式的配置,适用于更多类型的段码LCD;最最最厉害的,还应属于可通过内部的重映射矩阵,实现COM和SEG的任意映射,极大的方便了硬件设计和PCB布线。
2.准备工作
- 通过灵动官网下载MM32L0130系列MCU的库函数和例程:https://www.mindmotion.com.cn/products/mm32mcu/mm32l/new_mm32l0/mm32l0130
- 准备一个调试/下载工具,我们这边使用官方的MM32-LINK MINI:https://www.mindmotion.com.cn/support/development_tools/debug_and_programming_tools/mm32_link_mini
- 灵动官方推出了EVB-L0130开发板,该开发板搭载了MM32L0136C7P芯片,板载了段码LCD显示屏等外设模块,相应的资料可以到官网去下载:https://www.mindmotion.com.cn/support/development_tools/evaluation_boards/evboard/mm32l0136c7p
- 最后我们还需要找一块与EVB-L0130开发板上板载不同的段码LCD显示屏,用来移植,为了能够直接在EVB-L0130开发板上进行移植,我们需要选用段码LCD显示屏的电压与原先板载的段码LCD显示屏的电压保持一致
3.EVB-L0130开发板原理图
我们通过原理图可以看出段码LCD显示屏与MCU连接的引脚对应关系,需要注意的是由于EVB-L0130丰富的板载功能,有些修脚是通过拨动开关来进行功能选择的,所以在做SLCD例程功能演示时,需要将相应的拨动开发拨到相应的位置上,这样才可以正常显示。
4.EVB-L0130开发板板载段码LCD显示屏
5.解析SLCD例程设计思路
我们将对官方示例程序LibSamples_MM32L0130_V0.9.3_SLCD\Samples\LibSamples\SLCD目录下的SLCD_Basic和SLCD_Blink这两个例程进行解析,这两个例程的区别在于SLCD_Basic是通过软件操作的方式实现图标闪烁的,SLCD_Blink则是通过配置寄存器功能的方式实现图标闪烁的,这个后面我们会具体展开说明。
5.1.在段码LCD显示应用中,所有显示的内容都是一个个段位组成的,有单独一个段位组成的图标、单位,有多个段位组成的数字、进度条等等;而由7段/8段组成的8字型显示组合,我们可以显示数字、部分字符、定制某一段位显示,这个我们在程序中定义了个结构体,其中ch表示显示的字符,Data数值的每一位表示对应的显示段是显示状态还是熄灭状态,然后我们根据常用的一些字符,列举了最常用的38个对应列表,如下所示:
typedef struct
{
char ch;
uint8_t Data;
} SLCD_Code_TypeDef;
const SLCD_Code_TypeDef SLCD_CodeTable[38] =
{
{ ' ', 0x00 },
{ '0', 0x3F },
{ '1', 0x06 },
{ '2', 0x5B },
{ '3', 0x4F },
{ '4', 0x66 },
{ '5', 0x6D },
{ '6', 0x7D },
{ '7', 0x07 },
{ '8', 0x7F },
{ '9', 0x6F },
{ 'A', 0x77 },
{ 'b', 0x7C },
{ 'c', 0x58 },
{ 'C', 0x39 },
{ 'd', 0x5E },
{ 'E', 0x79 },
{ 'F', 0x71 },
{ 'g', 0x6F },
{ 'H', 0x76 },
{ 'h', 0x74 },
{ 'i', 0x04 },
{ 'I', 0x30 },
{ 'J', 0x1E },
{ 'l', 0x30 },
{ 'L', 0x38 },
{ 'n', 0x54 },
{ 'o', 0x5C },
{ 'O', 0x3F },
{ 'P', 0x73 },
{ 'q', 0x67 },
{ 'r', 0x50 },
{ 'S', 0x6D },
{ 't', 0x78 },
{ 'u', 0x1C },
{ 'U', 0x3E },
{ 'y', 0x6E },
{ '-', 0x40 },
};
这些显示字符和段位显示的对应关系都是固定,如果你还需要其它的定制某一段位的显示,是可以自行进行修改添加的,但需要注意数组的大小定义和使用到该数组时个遍历的个数,也需要同步修改一致。
5.2.MM32L0130系列MCU引脚与LCD功能引脚的对应关系,我们可以通过芯片的数据手册来获悉,在数据手册4.2引脚定义表中,Name列是MCU引脚名称,LCD Function列是LCD功能引脚下标;通过这个表格的整理,最大LCD Function是L63,但在L0~L63之间有不存在的项,所以为了我们定义数组的完整,我们在程序设计时,将不存在的Lx下标都定义在一个没有LCD功能的PH0引脚上,这么定义只是为了数组的完整和顺序性。
typedef struct
{
GPIO_TypeDef *GPIOn;
uint16_t PINn;
uint32_t Line;
} SLCD_Line_TypeDef;
const SLCD_Line_TypeDef SLCD_LineTable[] =
{
{ GPIOC, GPIO_Pin_13, SLCD_L0 },
{ GPIOD, GPIO_Pin_7, SLCD_L1 },
{ GPIOB, GPIO_Pin_9, SLCD_L2 },
{ GPIOB, GPIO_Pin_8, SLCD_L3 },
{ GPIOC, GPIO_Pin_12, SLCD_L4 },
{ GPIOC, GPIO_Pin_11, SLCD_L5 },
{ GPIOC, GPIO_Pin_10, SLCD_L6 },
{ GPIOA, GPIO_Pin_15, SLCD_L7 },
{ GPIOD, GPIO_Pin_3, SLCD_L8 },
{ GPIOD, GPIO_Pin_2, SLCD_L9 },
{ GPIOA, GPIO_Pin_12, SLCD_L10 },
{ GPIOA, GPIO_Pin_11, SLCD_L11 },
{ GPIOA, GPIO_Pin_10, SLCD_L12 },
{ GPIOA, GPIO_Pin_9, SLCD_L13 },
{ GPIOA, GPIO_Pin_8, SLCD_L14 },
{ GPIOC, GPIO_Pin_9, SLCD_L15 },
{ GPIOC, GPIO_Pin_8, SLCD_L16 },
{ GPIOC, GPIO_Pin_7, SLCD_L17 },
{ GPIOC, GPIO_Pin_6, SLCD_L18 },
{ GPIOB, GPIO_Pin_15, SLCD_L19 },
{ GPIOB, GPIO_Pin_14, SLCD_L20 },
{ GPIOB, GPIO_Pin_13, SLCD_L21 },
{ GPIOB, GPIO_Pin_12, SLCD_L22 },
{ GPIOB, GPIO_Pin_11, SLCD_L23 },
{ GPIOB, GPIO_Pin_10, SLCD_L24 },
{ GPIOB, GPIO_Pin_2, SLCD_L25 },
{ GPIOB, GPIO_Pin_1, SLCD_L26 },
{ GPIOB, GPIO_Pin_0, SLCD_L27 },
{ GPIOC, GPIO_Pin_5, SLCD_L28 },
{ GPIOC, GPIO_Pin_4, SLCD_L29 },
{ GPIOA, GPIO_Pin_7, SLCD_L30 },
{ GPIOA, GPIO_Pin_6, SLCD_L31 },
{ GPIOA, GPIO_Pin_5, SLCD_L32 },
{ GPIOA, GPIO_Pin_4, SLCD_L33 },
{ GPIOD, GPIO_Pin_5, SLCD_L34 },
{ GPIOD, GPIO_Pin_4, SLCD_L35 },
{ GPIOA, GPIO_Pin_3, SLCD_L36 },
{ GPIOA, GPIO_Pin_2, SLCD_L37 },
{ GPIOA, GPIO_Pin_1, SLCD_L38 },
{ GPIOA, GPIO_Pin_0, SLCD_L39 },
{ GPIOC, GPIO_Pin_3, SLCD_L40 },
{ GPIOC, GPIO_Pin_2, SLCD_L41 },
{ GPIOC, GPIO_Pin_1, SLCD_L42 },
{ GPIOC, GPIO_Pin_0, SLCD_L43 },
{ GPIOH, GPIO_Pin_0, SLCD_L44 },
{ GPIOH, GPIO_Pin_0, SLCD_L45 },
{ GPIOH, GPIO_Pin_0, SLCD_L46 },
{ GPIOH, GPIO_Pin_0, SLCD_L47 },
{ GPIOH, GPIO_Pin_0, SLCD_L48 },
{ GPIOH, GPIO_Pin_0, SLCD_L49 },
{ GPIOH, GPIO_Pin_0, SLCD_L50 },
{ GPIOH, GPIO_Pin_0, SLCD_L51 },
{ GPIOH, GPIO_Pin_0, SLCD_L52 },
{ GPIOH, GPIO_Pin_0, SLCD_L53 },
{ GPIOH, GPIO_Pin_0, SLCD_L54 },
{ GPIOH, GPIO_Pin_0, SLCD_L55 },
{ GPIOH, GPIO_Pin_0, SLCD_L56 },
{ GPIOH, GPIO_Pin_0, SLCD_L57 },
{ GPIOD, GPIO_Pin_6, SLCD_L58 },
{ GPIOB, GPIO_Pin_3, SLCD_L59 },
{ GPIOB, GPIO_Pin_4, SLCD_L60 },
{ GPIOB, GPIO_Pin_5, SLCD_L61 },
{ GPIOB, GPIO_Pin_6, SLCD_L62 },
{ GPIOB, GPIO_Pin_7, SLCD_L63 },
};
如上定义的结构体中,GPIOn是端口号,PINn是引脚,Line是LCD Function的下标号;如上定义的结构体数组是依据MM32L0160系列MCU引脚定义表进行列举的,属于固定内容,无需修改,也不能够修改,切记!!!定义此结构体、数组是为了在后面程序中可以通过查表的方式来配置的MCU引脚功能,不需要再通过数据手册一个个翻查对应关系了。
5.3.段码LCD显示屏真值表数组定义,这个可以直接参照段码LCD显示屏真值表的段码名,在后面显示的时候通过真值表中的段码名可以直接进行查询,既方便又直观,快速的定位到当前段码名对应真值表当中的哪个COM和SEG:
const char SLCD_NAME_Table[SLCD_COM_NUMBER][SLCD_SEG_NUMBER][4] =
{
{ "1D ", "DP1", "2D ", "DP2", "3D ", "DP3", "4D ", "C1 ", "C2 ", "W5 ", "L1 ", "5F ", "5A ", "6F ", "6A ", "7F ", "7A ", "S4 ", "S5 ", "8F ", "8A ", "9F ", "9A ", "10F", "10A" },
{ "1E ", "1C ", "2E ", "2C ", "3E ", "3C ", "4E ", "4C ", "C3 ", "W4 ", "L2 ", "5G ", "5B ", "6G ", "6B ", "7G ", "7B ", "S3 ", "S6 ", "8G ", "8B ", "9G ", "9B ", "10G", "10B" },
{ "1G ", "1B ", "2G ", "2B ", "3G ", "3B ", "4G ", "4B ", "T1 ", "W3 ", "L3 ", "5E ", "5C ", "6E ", "6C ", "7E ", "7C ", "S2 ", "S7 ", "8E ", "8C ", "9E ", "9C ", "10E", "10C" },
{ "1F ", "1A ", "2F ", "2A ", "3F ", "3A ", "4F ", "4A ", "W1 ", "W2 ", "L4 ", "5D ", "DP5", "6D ", "DP6", "7D ", "DP7", "S1 ", "S8 ", "8D ", "DP8", "9D ", "DP9", "10D", "S9 " },
};
5.4.段码LCD显示屏硬件连接映射表,这也是最关键的一步;我们通过定义如下结构体,来说明MCU引脚对应的LCD功能,MCU引脚是作为SEG功能使用,还是作为COM功能使用,在结构体中GPIOn是端口号,PINn是引脚,Line是LCD Function的下标号,LineGroup是分组,Mode是引脚配置;其中Line和LineGroup在定义结构体数组时,初始值都是0,后面程序设计中会自动根据5.2小节的功能进行自动匹配,定义LineGroup是因为在操作寄存器时有区分,所以后面程序中将L0~L31划分为LineGroup1,将L31~L63划分到LineGroup2,以此来操作不同的显示缓存;最后Mode就是用来区分是MCU是作SEG功能使用的,还是作为COM功能使用的了:
typedef struct
{
GPIO_TypeDef *GPIOn;
uint16_t PINn;
uint32_t Line;
uint8_t LineGroup;
uint8_t Mode;
} SLCD_IO_TypeDef;
SLCD_IO_TypeDef SLCD_SCH[SLCD_PIN_NUMBER] =
{
{ GPIOB, GPIO_Pin_8, 0, 0, SLCD_IOConfigSEG }, /* PB8 : SLCD_D0 */
{ GPIOA, GPIO_Pin_15, 0, 0, SLCD_IOConfigSEG }, /* PA15 : SLCD_D1 */
{ GPIOC, GPIO_Pin_10, 0, 0, SLCD_IOConfigSEG }, /* PC10 : SLCD_D2 */
{ GPIOC, GPIO_Pin_11, 0, 0, SLCD_IOConfigSEG }, /* PC11 : SLCD_D3 */
{ GPIOC, GPIO_Pin_12, 0, 0, SLCD_IOConfigSEG }, /* PC12 : SLCD_D4 */
{ GPIOD, GPIO_Pin_6, 0, 0, SLCD_IOConfigSEG }, /* PD6 : SLCD_D5 */
{ GPIOB, GPIO_Pin_1, 0, 0, SLCD_IOConfigSEG }, /* PB1 : SLCD_D6 */
{ GPIOB, GPIO_Pin_0, 0, 0, SLCD_IOConfigSEG }, /* PB0 : SLCD_D7 */
{ GPIOA, GPIO_Pin_7, 0, 0, SLCD_IOConfigSEG }, /* PA7 : SLCD_D8 */
{ GPIOA, GPIO_Pin_6, 0, 0, SLCD_IOConfigSEG }, /* PA6 : SLCD_D9 */
{ GPIOD, GPIO_Pin_5, 0, 0, SLCD_IOConfigSEG }, /* PD5 : SLCD_D10 */
{ GPIOD, GPIO_Pin_3, 0, 0, SLCD_IOConfigSEG }, /* PD3 : SLCD_D11 */
{ GPIOD, GPIO_Pin_2, 0, 0, SLCD_IOConfigSEG }, /* PD2 : SLCD_D12 */
{ GPIOA, GPIO_Pin_12, 0, 0, SLCD_IOConfigSEG }, /* PA12 : SLCD_D13 */
{ GPIOA, GPIO_Pin_11, 0, 0, SLCD_IOConfigSEG }, /* PA11 : SLCD_D14 */
{ GPIOC, GPIO_Pin_9, 0, 0, SLCD_IOConfigSEG }, /* PC9 : SLCD_D15 */
{ GPIOC, GPIO_Pin_8, 0, 0, SLCD_IOConfigSEG }, /* PC8 : SLCD_D16 */
{ GPIOC, GPIO_Pin_7, 0, 0, SLCD_IOConfigSEG }, /* PC7 : SLCD_D17 */
{ GPIOC, GPIO_Pin_6, 0, 0, SLCD_IOConfigSEG }, /* PC6 : SLCD_D18 */
{ GPIOB, GPIO_Pin_14, 0, 0, SLCD_IOConfigSEG }, /* PB14 : SLCD_D19 */
{ GPIOB, GPIO_Pin_15, 0, 0, SLCD_IOConfigSEG }, /* PB15 : SLCD_D20 */
{ GPIOB, GPIO_Pin_12, 0, 0, SLCD_IOConfigSEG }, /* PB12 : SLCD_D21 */
{ GPIOB, GPIO_Pin_13, 0, 0, SLCD_IOConfigSEG }, /* PB13 : SLCD_D22 */
{ GPIOC, GPIO_Pin_5, 0, 0, SLCD_IOConfigSEG }, /* PC5 : SLCD_D23 */
{ GPIOC, GPIO_Pin_4, 0, 0, SLCD_IOConfigSEG }, /* PC4 : SLCD_D24 */
{ GPIOD, GPIO_Pin_4, 0, 0, SLCD_IOConfigCOM }, /* PD4 : SLCD_COM0 */
{ GPIOC, GPIO_Pin_3, 0, 0, SLCD_IOConfigCOM }, /* PC3 : SLCD_COM1 */
{ GPIOC, GPIO_Pin_13, 0, 0, SLCD_IOConfigCOM }, /* PC13 : SLCD_COM2 */
{ GPIOD, GPIO_Pin_7, 0, 0, SLCD_IOConfigCOM }, /* PD7 : SLCD_COM3 */
};
如上的结构体数组,需要根据硬件原理图设计中的MCU引脚和LCD引脚的实际连接来定义,并且需要按照顺序依次来定义,参照LCD_SEG0、LCD_SEG1、LCD_SEG2……LCD_SEGn、LCD_COM0、LCD_COM1……LCD_COMm这样的顺序编写。
5.5.底层函数实现,这些都是固定的,不需要客户修改,如下所示:
void SLCD_Clear(uint8_t Mode)
{
uint8_t i = 0;
if (0 != Mode)
{
for (i = 0; i < 16; i++)
{
SLCD->DR[i] = 0xFFFFFFFF;
}
}
else
{
for (i = 0; i < 16; i++)
{
SLCD->DR[i] = 0x00000000;
}
}
}
void SLCD_WriteBit(uint8_t COMn, uint32_t SEGn, uint32_t Group, uint8_t State)
{
uint8_t Index = COMn * 2 + Group;
if (State)
{
SLCD->DR[Index] |= SEGn;
}
else
{
SLCD->DR[Index] &= ~SEGn;
}
}
uint8_t SLCD_SearchCode(char ch)
{
uint8_t i = 0;
for (i = 0; i < 38; i++)
{
if (ch == SLCD_CodeTable[i].ch)
{
return (SLCD_CodeTable[i].Data);
}
}
return (0xFF);
}
void SLCD_SearchName(char *str, uint8_t *COMn, uint8_t *SEGn)
{
uint8_t i = 0, j = 0;
for (i = 0; i < SLCD_COM_NUMBER; i++)
{
for (j = 0; j < SLCD_SEG_NUMBER; j++)
{
if (strcmp(str, SLCD_NAME_Table[i][j]) == 0)
{
*COMn = i;
*SEGn = j;
return;
}
}
}
*COMn = 0xFF;
*SEGn = 0xFF;
}
void SLCD_Clear(uint8_t Mode)函数是清除所有LCD段位的显示,通过Mode来控制是全显示,还是全不显示
void SLCD_WriteBit(uint8_t COMn, uint32_t SEGn, uint32_t Group, uint8_t State)函数是操作显示RAM的某一位,通过COM、SEG和Group确定操作SLCD显示RAM的位置,通过State来控制显示还是不显示
uint8_t SLCD_SearchCode(char ch)函数是通过显示字符ch查找与之对应的段码数值
void SLCD_SearchName(char *str, uint8_t *COMn, uint8_t *SEGn)函数是通过真值表段名查找对应的COM和SEG下标
通过上述这些函数就可以组合实现具体的显示应用啦!
5.6.SLCD的配置,需要开启使用到GPIO的时钟、SLCD的时钟,当然对于输入到SLCD的时钟源是可以选择的,可以根据需求进行配置,另外就是LCD功能的配置了,具体参考哪下几个函数:
void SLCD_Configure(void)函数是SLCD的总配置入口,如下所示:
void SLCD_Configure(void)
{
SLCD_InitTypeDef SLCD_InitStructure;
uint32_t SLCD_ClockFreq = 0;
uint32_t SLCD_Prescaler = SLCD_Prescaler_16;
uint32_t SLCD_Divider = SLCD_Divider_16;
uint8_t SLCD_ClockSource = 0;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);
switch (SLCD_ClockSource)
{
case 0: /* LSI */
RCC_LSICmd(ENABLE);
RCC_LSICLKConfig(RCC_LSICLKSource_40KHz);
while (RESET == RCC_GetFlagStatus(RCC_FLAG_LSIRDY))
{
__NOP();
}
RCC_APB1PeriphClockCmd(RCC_APB1Periph_LCD, ENABLE);
SLCD_DeInit();
RCC_SLCDCLKConfig(RCC_SLCDCLKSource_LSI);
break;
case 1: /* LSE */
RCC_LSEConfig(RCC_LSE_ON);
while (RESET == RCC_GetFlagStatus(RCC_FLAG_LSERDY))
{
__NOP();
}
RCC_APB1PeriphClockCmd(RCC_APB1Periph_LCD, ENABLE);
SLCD_DeInit();
RCC_SLCDCLKConfig(RCC_SLCDCLKSource_LSE);
break;
case 2: /* HSI(1MHz)/DIV1 -> 1MHz */
RCC_HSIConfig(RCC_HSI_1M);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_LCD, ENABLE);
SLCD_DeInit();
RCC_SLCDCLKConfig(RCC_SLCDCLKSource_HSI_Div1);
break;
case 3: /* HSI(8MHz)/DIV8 -> 1MHz */
RCC_HSIConfig(RCC_HSI_8M);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_LCD, ENABLE);
SLCD_DeInit();
RCC_SLCDCLKConfig(RCC_SLCDCLKSource_HSI_Div8);
break;
default:
break;
}
SLCD_ClockFreq = RCC_GetSlcdClockFreq();
printf("\r\nSLCD_ClockFreq : %dHz\r\n", SLCD_ClockFreq);
if (SLCD_ClockFreq == 0)
{
return;
}
else if (SLCD_ClockFreq == 32768UL)
{
SLCD_Prescaler = SLCD_Prescaler_2;
SLCD_Divider = SLCD_Divider_16;
}
else if (SLCD_ClockFreq == 40000UL)
{
SLCD_Prescaler = SLCD_Prescaler_2;
SLCD_Divider = SLCD_Divider_20;
}
else if (SLCD_ClockFreq == 1000000UL)
{
SLCD_Prescaler = SLCD_Prescaler_64;
SLCD_Divider = SLCD_Divider_16;
}
SLCD_StructInit(&SLCD_InitStructure);
SLCD_InitStructure.SLCD_Prescaler = SLCD_Prescaler;
SLCD_InitStructure.SLCD_Divider = SLCD_Divider;
SLCD_InitStructure.SLCD_Duty = SLCD_Duty_1_4;
SLCD_InitStructure.SLCD_Bias = SLCD_Bias_1_3;
SLCD_InitStructure.SLCD_VoltageSource = SLCD_VoltSrcCapCharggDownVdd;
SLCD_Init(&SLCD_InitStructure);
SLCD_ConfigureSEGoCOM();
SLCD_ConfigureCOMnIDX();
SLCD_ChargePumpClockDivConfig(SLCD_ChargePumpClock_Div1024);
SLCD_LowPowerDriveCmd(DISABLE);
SLCD_DeadTimeConfig(SLCD_DeadTime_0);
SLCD_Cmd(ENABLE);
SLCD_Clear(1);
PLATFORM_DelayMS(1000);
SLCD_Clear(0);
SLCD_ConfigureBlinkEN();
}
void SLCD_ConfigureSEGoCOM(void)函数是用来配置MCU引脚是作LCD功能使用的
void SLCD_ConfigureCOMnIDX(void)函数是用来配置MCU引脚作为COM功能使用的
void SLCD_ConfigureSEGoCOM(void)
{
uint8_t i = 0, j = 0;
uint8_t SLCD_IOConfigTable[MAX_SLCD_PIN_NUMBER];
memset(SLCD_IOConfigTable, SLCD_IOConfigNone, MAX_SLCD_PIN_NUMBER);
for (i = 0; i < MAX_SLCD_PIN_NUMBER; i++)
{
for (j = 0; j < SLCD_PIN_NUMBER; j++)
{
if ((SLCD_LineTable[i].GPIOn == SLCD_SCH[j].GPIOn) && (SLCD_LineTable[i].PINn == SLCD_SCH[j].PINn))
{
SLCD_IOConfigTable[i] = SLCD_SCH[j].Mode;
SLCD_SCH[j].Line = SLCD_LineTable[i].Line;
if (i > 31)
{
SLCD_SCH[j].LineGroup = 1;
}
else
{
SLCD_SCH[j].LineGroup = 0;
}
}
}
}
SLCD_IO_Config(SLCD_IOConfigTable);
}
void SLCD_ConfigureCOMnIDX(void)
{
uint8_t i = 0, j = 0, k = 0;
uint8_t SCLD_COM_IndexTable[COM_INDEX_MAX];
for (i = 0; i < COM_INDEX_MAX; i++)
{
SCLD_COM_IndexTable[i] = 50 + i;
}
i = 0;
for (j = 0; j < SLCD_PIN_NUMBER; j++)
{
if (SLCD_IOConfigCOM == SLCD_SCH[j].Mode)
{
for (k = 0; k < MAX_SLCD_PIN_NUMBER; k++)
{
if ((SLCD_LineTable[k].GPIOn == SLCD_SCH[j].GPIOn) && (SLCD_LineTable[k].PINn == SLCD_SCH[j].PINn))
{
SCLD_COM_IndexTable[i++] = k;
}
}
}
}
SLCD_COM_IndexInit(SCLD_COM_IndexTable);
}
这2个函数都是固定的,不需要客户修改!!
void SLCD_ConfigureBlinkEN(void)
{
uint8_t SCLD_BLINK_IndexTable[COM_INDEX_MAX * BLINK_INDEX_STEP] =
{
1, 0, 0, /* BLINK_Index0 is Enable, BLINK_Index0 point to COM0 and SEG0 */
1, 1, 1, /* BLINK_Index1 is Enable, BLINK_Index1 point to COM1 and SEG1 */
1, 2, 2, /* BLINK_Index2 is Enable, BLINK_Index2 point to COM2 and SEG2 */
1, 3, 3, /* BLINK_Index3 is Enable, BLINK_Index3 point to COM3 and SEG3 */
1, 4, 4, /* BLINK_Index4 is Enable, BLINK_Index4 point to COM4 and SEG4 */
1, 5, 5, /* BLINK_Index5 is Enable, BLINK_Index5 point to COM5 and SEG5 */
1, 6, 6, /* BLINK_Index6 is Enable, BLINK_Index6 point to COM6 and SEG6 */
1, 7, 7, /* BLINK_Index7 is Enable, BLINK_Index7 point to COM7 and SEG7 */
};
SLCD_BlinkConfig(SLCD_BlinkMode_Off, SLCD_BlinkFrequency_Div512);
SLCD_BLINK_IndexInit(SCLD_BLINK_IndexTable);
}
void SLCD_ConfigureBlinkEN(void)函数是用来配置硬件闪烁功能的,这个函数可以根据应用需示进行修改,如上所示,最多也只能配置8个段位进行闪烁,如果多余8个段位,就需要使用软件来实现闪烁功能了。
5.7.在进行上述的配置之后,我们就可以来编写应用显示功能函数了,我们拿例程中的SLCD_DisplayNumber1函数来说明:
void SLCD_DisplayNumber1(uint8_t Index, char ch, uint8_t Point)
{
char TAB[4][8][4] =
{
{ "1A ", "1B ", "1C ", "1D ", "1E ", "1F ", "1G ", "DP1" },
{ "2A ", "2B ", "2C ", "2D ", "2E ", "2F ", "2G ", "DP2" },
{ "3A ", "3B ", "3C ", "3D ", "3E ", "3F ", "3G ", "DP3" },
{ "4A ", "4B ", "4C ", "4D ", "4E ", "4F ", "4G ", " " },
};
uint8_t COMn = 0xFF, SEGn = 0xFF;
uint8_t Code = SLCD_SearchCode(ch);
uint8_t i = 0;
if (Code != 0xFF)
{
for (i = 0; i < 7; i++)
{
SLCD_SearchName(TAB[Index][i], &COMn, &SEGn);
if ((COMn != 0xFF) && (SEGn != 0xFF))
{
SLCD_WriteBit(COMn, SLCD_SCH[SEGn].Line, SLCD_SCH[SEGn].LineGroup, (Code >> i) & 0x01);
}
}
SLCD_SearchName(TAB[Index][7], &COMn, &SEGn);
if ((COMn != 0xFF) && (SEGn != 0xFF))
{
SLCD_WriteBit(COMn, SLCD_SCH[SEGn].Line, SLCD_SCH[SEGn].LineGroup, Point);
}
}
}
我们先通过段码LCD显示屏的真值表,构建显示组,就比如我要4个小数字区域(左上角)第一个数字的位置显示字符,首先就需要定义第一个数字位置是由哪几个段位组成的,然后在代码中定义出来;然后通过SLCD_SearchCode函数获取要显示字符对应段码值,然后将这个段码值依次写入到组成这个显示区域的各个段位中,每一个段位对应的COM和SEG则是通过SLCD_SearchName函数来获取的,最后调用SLCD_WriteBit将状态写入到显示RAM中去。
我们再拿SLCD_DisplayTool函数为例:
void SLCD_DisplayTool(void)
{
static uint8_t State = 0;
char TAB[4] = "T1 ";
uint8_t COMn = 0xFF, SEGn = 0xFF;
SLCD_SearchName(TAB, &COMn, &SEGn);
if ((COMn != 0xFF) && (SEGn != 0xFF))
{
SLCD_WriteBit(COMn, SLCD_SCH[SEGn].Line, SLCD_SCH[SEGn].LineGroup, State);
}
State = (State + 1) % 2;
}
它是一个独立的段位,我们只需要通过SLCD_SearchName函数查找真值表对应的COM和SEG,然后通过SLCD_WriteBit将状态写入到显示RAM中去,即可操作这个图标的显示状态。
5.8.官方示例程序显示效果
6.基于SLCD例程设计框架,移植产品LCD,实现显示功能
我们准备了另外一块段码LCD显示屏,真值表和显示段位如下所示:
我们通过杜绑线将LCD与EVB-L0130开发板连接起来,分配如下所示:
6.1.依据段码LCD显示屏,修改如下宏定义,他们分别指示了段码LCD显示屏有几个COM口,几个SEG口:
#define SLCD_COM_NUMBER (4)
#define SLCD_SEG_NUMBER (8)
6.2.根据段码LCD显示屏的真值表,修改SLCD_NAME_Table数组内容:
const char SLCD_NAME_Table[SLCD_COM_NUMBER][SLCD_SEG_NUMBER][3] =
{
{ "1F", "1A", "2F", "2A", "3F", "3A", "4F", "4A" },
{ "1G", "1B", "2G", "2B", "3G", "3B", "4G", "4B" },
{ "1E", "1C", "2E", "2C", "3E", "3C", "4E", "4C" },
{ "P1", "1D", "P2", "2D", "P3", "3D", "P4", "4D" },
};
6.3.根据段码LCD显示屏与MCU引脚的分配,修改SLCD_SCH数组内容:
SLCD_IO_TypeDef SLCD_SCH[SLCD_PIN_NUMBER] =
{
{ GPIOC, GPIO_Pin_4, 0, 0, SLCD_IOConfigSEG }, /* PC4 : LCD_SEG1 */
{ GPIOC, GPIO_Pin_5, 0, 0, SLCD_IOConfigSEG }, /* PC5 : LCD_SEG2 */
{ GPIOC, GPIO_Pin_6, 0, 0, SLCD_IOConfigSEG }, /* PC6 : LCD_SEG3 */
{ GPIOC, GPIO_Pin_7, 0, 0, SLCD_IOConfigSEG }, /* PC7 : LCD_SEG4 */
{ GPIOB, GPIO_Pin_12, 0, 0, SLCD_IOConfigSEG }, /* PB12 : LCD_SEG5 */
{ GPIOB, GPIO_Pin_13, 0, 0, SLCD_IOConfigSEG }, /* PB13 : LCD_SEG6 */
{ GPIOB, GPIO_Pin_14, 0, 0, SLCD_IOConfigSEG }, /* PB14 : LCD_SEG7 */
{ GPIOB, GPIO_Pin_15, 0, 0, SLCD_IOConfigSEG }, /* PB15 : LCD_SEG8 */
{ GPIOC, GPIO_Pin_3, 0, 0, SLCD_IOConfigCOM }, /* PC3 : LCD_COM1 */
{ GPIOC, GPIO_Pin_13, 0, 0, SLCD_IOConfigCOM }, /* PC13 : LCD_COM2 */
{ GPIOD, GPIO_Pin_4, 0, 0, SLCD_IOConfigCOM }, /* PD4 : LCD_COM3 */
{ GPIOD, GPIO_Pin_7, 0, 0, SLCD_IOConfigCOM }, /* PD7 : LCD_COM4 */
};
6.4.根据段码LCD显示屏的功能,重构应用显示函数:
void SLCD_DisplayNumber1(uint8_t Index, char ch, uint8_t Point)
{
char TAB[4][8][3] =
{
{ "1A", "1B", "1C", "1D", "1E", "1F", "1G", "P1" },
{ "2A", "2B", "2C", "2D", "2E", "2F", "2G", "P2" },
{ "3A", "3B", "3C", "3D", "3E", "3F", "3G", "P3" },
{ "4A", "4B", "4C", "4D", "4E", "4F", "4G", "P4" },
};
uint8_t COMn = 0xFF, SEGn = 0xFF;
uint8_t Code = SLCD_SearchCode(ch);
uint8_t i = 0;
if (Code != 0xFF)
{
for (i = 0; i < 7; i++)
{
SLCD_SearchName(TAB[Index][i], &COMn, &SEGn);
if ((COMn != 0xFF) && (SEGn != 0xFF))
{
SLCD_WriteBit(COMn, SLCD_SCH[SEGn].Line, SLCD_SCH[SEGn].LineGroup, (Code >> i) & 0x01);
}
}
SLCD_SearchName(TAB[Index][7], &COMn, &SEGn);
if ((COMn != 0xFF) && (SEGn != 0xFF))
{
SLCD_WriteBit(COMn, SLCD_SCH[SEGn].Line, SLCD_SCH[SEGn].LineGroup, Point);
}
}
}
6.5.移植后的显示效果
7.附件程序
LibSamples_MM32L0130_V0.9.3_SLCD.zip
(5.42 MB)
|
此文章已获得独家原创/原创奖标签,著作权归21ic所有,未经允许禁止转载。
打赏榜单
21小跑堂 打赏了 200.00 元 2024-02-04 理由:恭喜通过原创审核!期待您更多的原创作品~(蓝v达人奖励已提升)
共1人点赞
|
解析MM32L0130单片机的SLCD软硬件使用方法,通过SLCD,可实现在MM32L0130单片机上快速开发段码LCD的显示,作者通过详细的解析,和实例展示,完整的展示了LCD的产品移植过程以及现象的演示,文章质量较优。