发新帖本帖赏金 200.00元(功能说明)我要提问
返回列表
[MM32软件]

解析SLCD例程设计思路,加速终端产品应用开发

[复制链接]
14585|6
手机看帖
扫描二维码
随时随地手机跟帖
xld0932|  楼主 | 2024-2-2 14:38 | 显示全部楼层 |阅读模式
本帖最后由 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
2.png

  • 灵动官方推出了EVB-L0130开发板,该开发板搭载了MM32L0136C7P芯片,板载了段码LCD显示屏等外设模块,相应的资料可以到官网去下载:https://www.mindmotion.com.cn/support/development_tools/evaluation_boards/evboard/mm32l0136c7p
1.png

  • 最后我们还需要找一块与EVB-L0130开发板上板载不同的段码LCD显示屏,用来移植,为了能够直接在EVB-L0130开发板上进行移植,我们需要选用段码LCD显示屏的电压与原先板载的段码LCD显示屏的电压保持一致

3.EVB-L0130开发板原理图
我们通过原理图可以看出段码LCD显示屏与MCU连接的引脚对应关系,需要注意的是由于EVB-L0130丰富的板载功能,有些修脚是通过拨动开关来进行功能选择的,所以在做SLCD例程功能演示时,需要将相应的拨动开发拨到相应的位置上,这样才可以正常显示。
3.png

4.png


4.EVB-L0130开发板板载段码LCD显示屏
5.png


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显示屏,真值表和显示段位如下所示:
7.png

我们通过杜绑线将LCD与EVB-L0130开发板连接起来,分配如下所示:
8.png


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)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 200.00 元 2024-02-04
理由:恭喜通过原创审核!期待您更多的原创作品~(蓝v达人奖励已提升)

评论
21小跑堂 2024-2-4 14:19 回复TA
解析MM32L0130单片机的SLCD软硬件使用方法,通过SLCD,可实现在MM32L0130单片机上快速开发段码LCD的显示,作者通过详细的解析,和实例展示,完整的展示了LCD的产品移植过程以及现象的演示,文章质量较优。 
ayb_ice| | 2024-2-6 09:37 | 显示全部楼层
只要COM接任意COM,SEG接任意SEG,通过一个转换程序轻松应对真值表

使用特权

评论回复
xld0932|  楼主 | 2024-2-6 09:41 | 显示全部楼层
ayb_ice 发表于 2024-2-6 09:37
只要COM接任意COM,SEG接任意SEG,通过一个转换程序轻松应对真值表

使用特权

评论回复
[鑫森淼焱垚]| | 2024-2-21 10:15 | 显示全部楼层
好贴,点赞

使用特权

评论回复
储小勇_526| | 2024-2-22 14:24 | 显示全部楼层
大佬代码真工整,刚自主做单片机编程,值得学习。

使用特权

评论回复
xld0932|  楼主 | 2024-2-22 17:59 | 显示全部楼层
储小勇_526 发表于 2024-2-22 14:24
大佬代码真工整,刚自主做单片机编程,值得学习。

使用特权

评论回复
发新帖 本帖赏金 200.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司资深现场应用工程师
简介:诚信·承诺·创新·合作

65

主题

2986

帖子

27

粉丝