杰理 SDK 文档中心
首页
首页
  • 概述与入门

    • 项目概述与芯片平台
    • 环境搭建与工具链安装
    • 编译与烧录指南
    • 工程结构总览
  • 应用层与公共模块

    • GP MCU 主应用入口
    • AT 指令与调试模块
    • 电池检测与电源管理
    • EEPROM 与参数存储
    • 按键与 USB 设备驱动
    • 音频解码与 APA 语音播报
  • 外设驱动与示例

    • 高精度 ADC(HADC)
    • 通用 ADC 与定时器
    • UART / SPI / IIC 通信外设
    • MCPWM 与电机控制
    • RTC 与低功耗唤醒
    • 段码 LCD 驱动
    • NOR Flash 与红外编解码组件
  • 显示与 UI 系统

    • LCD 驱动与字库引擎
    • UI 平台与控件绘制
    • UI 工程与资源生成工具
  • 系统底层与芯片平台

    • cd09 芯片平台与预编译库
    • GPIO 与 IIC 底层驱动
    • 系统文件系统与设备模型
  • 启动引导与固件升级

    • UBOOT 引导工程
    • 固件升级机制
  • 开发工具与资源

    • 编译脚本与命令行工具
    • 音频文件转换工具
    • 硬件资料与文档资源

段码 LCD 驱动

段码 LCD(断码屏)驱动是 AC82N(CD09 CPU)上控制段码液晶屏的底层外设驱动,负责通过 P33 电源域协处理器寄存器驱动 LCD 电荷泵、配置 COM/SEG 波形并维护显示缓存,最大支持 4×27 或 6×25 的段点阵。

Purpose and Scope

本文档介绍 sdk/cpu/cd09/segment_code_lcd/ 目录下的段码 LCD 驱动实现,涵盖:

  • 驱动与 P33 协处理器寄存器之间的控制通路(电荷泵、VLCD 分压、BIAS、推屏模式等)
  • 平台数据(segment_code_lcd_platform_data)的字段含义与枚举取值
  • 初始化、点/数字/字符填充、缓存刷新、帧中断与 IO 复用的完整机制
  • Demo 程序展示的典型使用方式

不包含以下内容(属于兄弟页面):

  • 点阵 LCD(SPI/并口屏)驱动,如 sdk/apps/common/lcd/lcd_drive.c、lcd_spi_st7735_*.c、lcd_pap_st7789_*.c,请参考「点阵 LCD 驱动」相关页面
  • GPIO、时钟、看门狗等通用外设驱动

概述

段码 LCD(Segment LCD,又称断码屏)本身不含控制器,需要 MCU 侧的 LCD 驱动模块在 COM 与 SEG 引脚上产生交流驱动波形,利用液晶分子的电光效应点亮/熄灭交叉点。驱动内部将每个交叉点抽象为 (com, seg) 坐标,通过 LCD_POINT(com, seg) 宏编码为一个字节(高 5 位为 SEG 号,低 3 位为 COM 号)。

该驱动设计上遵循 "CPU 只维护显存、硬件自动推屏" 的思路:

  1. CPU 在内存中的 disp_buffer[6](每 COM 一行,u32 位图)上"画"点;
  2. 调用 segment_code_lcd_disp_update() 时仅把发生变化的 COM 行写入 P33 寄存器;
  3. P33 协处理器中的 LCD 硬件按 COM×SEG 扫描产生波形,无需 CPU 逐帧干预;
  4. 支持连续推屏(硬件自动循环)与断续推屏(每帧结束产生 PND/中断,供 IO 复用场景释放 SEG 引脚)。

底层所有寄存器操作通过 p33_tx_1byte / p33_rx_1byte / p33_tx_nbyte 访问 P33 电源域寄存器空间(详见头文件中的 P33_CON_SET 等宏),这些寄存器位于 asm/power/p33_sfr.h 定义的地址空间中。

架构

flowchart TD
    subgraph sg_App["应用层"]
        App["应用程序 / segment_code_lcd_demo"]
    end

    subgraph sg_Drv["驱动 API 层 (segment_code_lcd_drv.c)"]
        Init["segment_code_lcd_init"]
        Fill["fill_point / fill_number / fill_char"]
        Update["segment_code_lcd_disp_update"]
        ISR["segment_code_lcd_isr (帧中断)"]
        Pump["segment_code_lcd_pump_init (电荷泵)"]
    end

    subgraph sg_P33["P33 协处理器寄存器域"]
        CHG0["P3_LCD_CHG_CON0/1/2<br/>电荷泵 + LDOB + 电压档位"]
        CLK0["P3_CLK_CON0<br/>LCD 时钟选择"]
        LCDC0["P3_LCDC_CON0~CON7<br/>推屏模式/BIAS/COM使能/状态"]
        SEGDAT["P3_SEG0_DAT0~<br/>像素数据寄存器"]
    end

    subgraph sg_IO["引脚层"]
        COMIOS["COM0~COM5 (IO_PORTC_00~05)"]
        SEGIOS["SEG0~SEG26 (IO_PORTC_04~IO_PORTD_14)"]
    end

    App --> Init
    App --> Fill
    Fill --> Update
    App --> ISR
    Init --> Pump
    Init --> CHG0
    Init --> CLK0
    Init --> LCDC0
    Update --> SEGDAT
    LCDC0 -->|"驱动波形"| COMIOS
    SEGDAT -->|"段点亮数据"| SEGIOS
    COMIOS --> Panel["段码 LCD 面板 (6COM×25SEG)"]
    SEGIOS --> Panel
    Panel -->|"帧结束 PND 置位"| LCDC0
    LCDC0 -->|"中断"| ISR
    ISR -->|"isr_cb 回调"| App

架构说明:应用层通过四个入口与驱动交互——初始化、填充显存、刷新显存、接收帧中断。驱动层将平台数据翻译成 P33 寄存器的位域配置(电荷泵电压、VLCD 系数、BIAS、时钟源、COM 使能),而像素数据则通过 p33_tx_nbyte 批量写入 P3_SEG*_DAT* 数据寄存器。LCD 硬件扫描完成一帧后置位 PND 标志,在断续推屏模式下可触发中断回调 isr_cb,这是实现 IO 复用的关键钩子。

核心实现

硬件资源与引脚映射

驱动用两张静态映射表把逻辑 COM/SEG 索引翻译为 GPIO 引脚号。SEG 共 27 个索引,从 IO_PORTC_04 一直到 IO_PORTD_14;COM 共 6 个索引,位于 IO_PORTC_00 ~ IO_PORTC_05:

//seg index 到 IO的映射表
static const u8 hw_seg2pin_mapping[27] = {
    IO_PORTC_04, IO_PORTC_05, IO_PORTC_06, IO_PORTC_07,
    IO_PORTC_08, IO_PORTC_09, IO_PORTC_10, IO_PORTC_11,
    IO_PORTC_12, IO_PORTC_13, IO_PORTC_14, IO_PORTC_15,
    IO_PORTD_00, IO_PORTD_01, IO_PORTD_02, IO_PORTD_03,
    IO_PORTD_04, IO_PORTD_05, IO_PORTD_06, IO_PORTD_07,
    IO_PORTD_08, IO_PORTD_09, IO_PORTD_10, IO_PORTD_11,
    IO_PORTD_12, IO_PORTD_13, IO_PORTD_14,
};

//com index 到 IO的映射表
static const u8 hw_com2pin_mapping[6] = {
    IO_PORTC_00, IO_PORTC_01, IO_PORTC_02, IO_PORTC_03, IO_PORTC_04, IO_PORTC_05,
};

Source: segment_code_lcd_drv.c

映射表是固定不变的硬件事实:例如 SEG0 固定为 IO_PORTC_04。应用只需关心 (com, seg) 逻辑坐标,LCD_POINT(com, seg) 宏将其编码为 (seg << 3 | com),其中 INVALID_COM (0x07) 表示该段位不使用(如数字 "1" 不需要 F 段):

//描述一个点
#define LCD_POINT(com, seg)     (seg << 3 | com)
//把某个点的com设置成INVALID_COM,则该点无效
#define INVALID_COM             0x07

Source: segment_code_lcd_drv.h

P33 寄存器控制层

驱动不直接操作 GPIO 产生波形,而是把全部控制字通过 P33_CON_SET / p33_tx_1byte 写入 P33 电源域寄存器。寄存器按功能分组:

寄存器组关键位域宏作用
P3_CHG_PUMPLCDCP_WVBG_EN内部带隙基准(VBG)使能,电荷泵电压校准基准
P3_LCD_CHG_CON0LCDCP_PUMP_EN / LCDCP_LDOB_EN / LCDCP_HFREQ_EN / LCDCP_COMP_EN / LCDCP_PUMPCLK_EN / LCDCP_ADC_DET_EN / LCDCP_STANDBY_SET_BIT / LCDCP_X2MODE_SET_BIT电荷泵与 LDOB 主控;X2MODE=1 时 VLCD=2×VDDIO 工作模式
P3_LCD_CHG_CON1LCDCP_OSCCAP_SEL / LCDCP_PUMPVOL_SEL泵振荡电容档位(默认 1.2M)与泵输出电压档(3.0V~4.5V,步进约 100mV)
P3_LCD_CHG_CON2LCDCP_LDOB_SELLDOB 输出电压档(2.7V/3.0V/3.3V/3.6V)
P3_CLK_CON0LCD_CLK_SEL / LCD_CLK_ENLCD 扫描时钟源:OSL 外挂 32K 晶振、WCLK 32K、LRC 200K/6、PAT
P3_LCDC_CON0LCD_VOL_SEL / LCD_BIAS_SEL / LCD_DOT_EN / LCD_ENABLEVLCD 分压系数(0.886~1.000×LCDCP)、BIAS(1/2、1/3、1/4)、跳秒脚使能、LCD 模块总使能
P3_LCDC_CON1LCD_SET_CHGDUTY / LCD_CHG_CURR / LCD_SET_CHGMODE强充电占空比/电流/充电模式(弱充、强充、半周期、多周期)
P3_LCDC_CON2LCD_DEADBAND_EN / LCD_TEST_EN / LCD_CLK_DIV死区控制、测试模式(IO 直出静态电平)、模块时钟预分频
P3_LCDC_CON3LCD_GET_PENDING / LCD_CLR_PENDING / LCD_SW_KST / LCD_IE_EN / LCD_CTU_EN推屏控制核心:帧结束标志、软件 KST 启动位、断续模式帧中断使能、连续推屏使能
P3_LCDC_CON4LCD_COM_IO_ENbit0~bit5 对应 COM0~COM5 是否使能
P3_LCDC_CON5LCD_DOT_SEG_SEL / LCD_DOT_COM_SEL跳秒脚(DOT)的 SEG/COM 位置选择
P3_LCDC_CON6/7LCD_SEG_TEST_FLAG / LCD_COM_TEST_FLAG测试模式通道选择,SEG/COM 口直出 VLCD 各分压电平

Source: segment_code_lcd_drv.h

其中 P3_LCDC_CON3 的语义决定了整个推屏模型(注释即规范):

#define LCD_SW_KST(en)              P33_CON_SET(P3_LCDC_CON3, 4, 1, en)     //开启测试模式,IO直接输出LCD静态电平
#define LCD_IE_EN(en)               P33_CON_SET(P3_LCDC_CON3, 1, 1, en)     //断续推屏模式下,完成一帧后,LCD帧中断使能位
#define LCD_CTU_EN(en)              P33_CON_SET(P3_LCDC_CON3, 0, 1, en)     //连续推屏使能位:
//0:断续推屏模式(完成一帧后起帧pending,同时停止推屏)
//1:连续推屏模式(完成一帧后不会起帧pending,硬件会自动继续进行下一帧的推屏)

Source: segment_code_lcd_drv.h

平台数据结构与配置

驱动通过 struct segment_code_lcd_platform_data 接收面板配置(Demo 中的完整实例见"使用示例"一节),字段语义如下:

字段类型说明
clkenum lcd_clkLCD 扫描时钟源:LCD_CLK_SEL_OSL(外挂 32K 晶振,跳秒功能可用)、LCD_CLK_SEL_WCLK、LCD_CLK_SEL_LRC、LCD_CLK_SEL_PAT
chgpump_volenum lcdcp_pmup_vol电荷泵输出电压档,LCDCP_PUMPVOL_SEL_3P0V~_4P5V(默认 3P6V)
ldob_volenum lcdcp_ldob_volLDOB 电压档:2.7V/3.0V/3.3V/3.6V(默认 3P3V)
vlcdenum lcd_volVLCD 分压系数:LCD_VOL_SEL_0P886XLCDCP~_1P000XLCDCP(8 档,步进约 1.6%)
biasenum lcd_bias偏压比:LCD_BIAS_DISABLE/_1DIV2/_1DIV3/_1DIV4
chgmodeenum lcd_chgmode充电模式:弱充、强充、半周期强驱、多周期强驱(CHGDUTY+1 个 32K 时钟后撤强驱)
chgdutyu8多周期充电模式的强驱占空比
dot_com / dot_segu8跳秒脚(DOT)的 COM/SEG 坐标
lcd_com_enu32位图,bit0~5 使能 COM0~COM5
lcd_seg_enu32位图,bit0~26 使能 SEG0~SEG26
ctu_enu80:断续推屏(使能帧中断);1:连续推屏
isr_cb函数指针断续推屏帧中断回调,用于 IO 复用等场景;为 NULL 时 ISR 自动重新推屏

初始化流程

segment_code_lcd_init() 是唯一入口,内部按固定顺序完成"清寄存器 → 电荷泵 → 时钟 → IO 接管 → 启动推屏":

void segment_code_lcd_init(const struct segment_code_lcd_platform_data *lcd_dat)
{
    u8 temp = 0;
    if ((__this->init) || (lcd_dat == NULL)) {
        return;
    }

    __this->usr_data = lcd_dat;

    for (temp = P3_LCDC_CON0; temp <= P3_LCDC_CON7; temp++) {
        p33_tx_1byte(temp, 0);
    }
    for (temp = P3_SEG_IO_EN0; temp <= P3_SEG_IO_EN3; temp++) {
        p33_tx_1byte(temp, 0);
    }

    //LCDCP初始化
    segment_code_lcd_pump_init();

    //配置LCD时钟
    LCD_CLK_SEL(__this->usr_data->clk);
    if (__this->usr_data->clk == LCD_CLK_SEL_LRC) {
        ...

Source: segment_code_lcd_drv.c

初始化要点:

  1. 防重入保护:__this->init 与空指针双重检查,重复调用直接返回;
  2. 寄存器归零:先清空 P3_LCDC_CON0~CON7 与 P3_SEG_IO_EN0~3,保证从确定状态开始;
  3. 电荷泵上电时序(segment_code_lcd_pump_init):先把 IO_PORTD_15 置为输出高电平(模拟模块供电控制),延时 500µs 后使能 VBG;写入 LCD_CHG_CON0_INIT(使能 HFREQ/COMP/PUMPCLK,STANDBY/X2MODE 置位),配置振荡电容 1.2M、泵压档、LDOB 档;再把 PD15 恢复高阻,依次打开 PUMP_EN(延时 1.5ms)与 LDOB_EN——严格的时序保证泵稳定建立后再点亮 LDOB:
static void segment_code_lcd_pump_init(void)
{
    gpio_set_mode(IO_PORT_SPILT(IO_PORTD_15), PORT_OUTPUT_HIGH);
    delay_us(500);

    LCDCP_WVBG_EN(1);//LCDCP_WVBG_EN
    delay_us(500);

    p33_tx_1byte(P3_LCD_CHG_CON0, LCD_CHG_CON0_INIT);
    LCDCP_OSCCAP_SEL(LCDCP_OSCCAP_SEL_1P2M);
#if SEGMENT_CODE_LCD_AUTO_TRIM_ENABLE
    LCDCP_PUMPVOL_SEL(0);
#else
    LCDCP_PUMPVOL_SEL(__this->usr_data->chgpump_vol);
#endif
    LCDCP_LDOB_SEL(__this->usr_data->ldob_vol);
    delay_us(500);

    gpio_set_mode(IO_PORT_SPILT(IO_PORTD_15), PORT_HIGHZ);
    LCDCP_PUMP_EN(1);
    delay_us(1500);

    LCDCP_LDOB_EN(1);
}

Source: segment_code_lcd_drv.c

  1. 后续步骤(与头文件使用流程一致):配置时钟源、VLCD 分压与 BIAS、COM/SEG IO 高阻输入(DIE=0、DIEH=0)、写入像素寄存器、配置模式(连续/断续)、开启 LCD_ENABLE、写入软件 KST 启动位,此后 LCD 模块接管 IO 自动推屏。

显示数据路径:双缓存与差分刷新

驱动内部维护两组缓存:

struct segment_code_lcd_var {
    u8 init;
    u32 disp_buffer[6];//显示缓存,最大6com
    u32 save_buffer[6];//记忆当前显示的内容
    const struct segment_code_lcd_platform_data *usr_data;
};

static struct segment_code_lcd_var _lcd_var = {0};
#define __this      (&_lcd_var)

Source: segment_code_lcd_drv.c

  • disp_buffer[6]:工作缓存,fill_* 系列 API 只改这里,不直接写硬件;
  • save_buffer[6]:镜像"当前已推送到硬件"的内容,用于差分比较。

所有填充 API 最终都收敛到 segment_code_lcd_fill_point()——把 icon 拆成 com/seg,越界直接返回,然后对 disp_buffer[com] 的对应 bit 置位/清零:

void segment_code_lcd_fill_point(u8 icon, u8 isfill)
{
    u8 com;
    u8 seg;
    com = icon & 0x07;
    if (com >= LCD_COM_MAX) {
        return;
    }
    seg = icon >> 3;
    if (seg >= LCD_SEG_MAX) {
        return;
    }
    if (isfill) {
        __this->disp_buffer[com] |= BIT(seg);
    } else {
        __this->disp_buffer[com] &= ~BIT(seg);
    }
}

Source: segment_code_lcd_drv.c

segment_code_lcd_disp_update() 是唯一把缓存同步到硬件的入口,采用逐 COM 差分刷新:只对使能的 COM 行比较 save_buffer 与 disp_buffer,内容相同则跳过(避免无谓的 P33 总线写入,降低功耗);不同则先更新镜像再按 4 字节批量写像素寄存器(前 4 个 COM 映射到 P3_SEG0_DAT0 段,后 2 个映射到 P3_SEG4_DAT0 段):

void segment_code_lcd_disp_update(void)
{
    u8 i;
    if (__this->usr_data  == NULL) {
        return;
    }
    for (i = 0; i < LCD_COM_MAX; i++) {
        if (__this->usr_data->lcd_com_en & BIT(i)) {
            //缓存和显示不一致时才刷新
            if (__this->save_buffer[i] == __this->disp_buffer[i]) {
                continue;
            }
            __this->save_buffer[i] = __this->disp_buffer[i];
            if (i < 4) {
                p33_tx_nbyte(P3_SEG0_DAT0 + (i - 0) * 4, (u8 *)&__this->disp_buffer[i], 4);
            } else {
                p33_tx_nbyte(P3_SEG4_DAT0 + (i - 4) * 4, (u8 *)&__this->disp_buffer[i], 4);
            }
        }
    }
}

Source: segment_code_lcd_drv.c

数字与字符段码表

驱动内置 7 段码映射表,避免应用自己拼段:

  • lcd_seg_number_2_seg[12]:'0'~'9'、'-'、以及下标 11 作为"清除"哨兵(segment_code_lcd_fill_number 中 num >= 数组大小 时按清除处理);
  • lcd_seg_large_letter_2_seg[26] / lcd_seg_small_letter_2_seg[26]:大写/小写字母(0x40 表示该字母无法用 7 段表达,只点亮 G 段)。
#define LED_A   BIT(0)
#define LED_B   BIT(1)
#define LED_C   BIT(2)
#define LED_D   BIT(3)
#define LED_E   BIT(4)
#define LED_F   BIT(5)
#define LED_G   BIT(6)
//数字'0' ~ '9' '-'显示段码表
static const u8 lcd_seg_number_2_seg[12] = {
    (u8)(LED_A | LED_B | LED_C | LED_D | LED_E | LED_F),        //'0'
    (u8)(LED_B | LED_C),                                        //'1'
    ...

Source: segment_code_lcd_drv.c

segment_code_lcd_fill_number(ptr, num) 要求 ptr 指向 7 个 LCD_POINT 编码(对应 A~G 段),驱动逐段与 LED_x 做位与后调用 fill_point;fill_char 同理,先按大小写换算成段码表下标。

推屏模式、中断与 IO 复用

连续推屏 vs 断续推屏

segment_code_lcd_set_ctu_en() 可在运行中切换推屏模式,切换逻辑即寄存器语义的直接体现:

void segment_code_lcd_set_ctu_en(u8 ctu_en)
{
    if (__this->init == 0) {
        return;
    }
    if (ctu_en) {
        LCD_IE_EN(0);//IE=0
        LCD_CTU_EN(1);//连续推屏
    } else {
        LCD_IE_EN(1);//IE=1
        LCD_CTU_EN(0);//断续推屏
    }
}

Source: segment_code_lcd_drv.c

设计意图:连续推屏(CTU_EN=1)适合独占 LCD 的场景,功耗最优、无中断开销;断续推屏(CTU_EN=0, IE_EN=1)每完成一帧即置位 PND 并停止推屏,让出 SEG 引脚给其他外设(如按键扫描、LED 呼吸),是"LCD 与其他功能共享 IO"的唯一手段。

帧中断处理

中断服务函数 segment_code_lcd_isr() 是断续推屏的核心:检测到 PND 后先清标志、关 LCD_ENABLE(让 COM 保持高阻),然后二选一——存在 isr_cb 则回调应用(应用可安全复用 SEG IO),否则硬件自动重新使能并写 KST 继续推屏:

___interrupt
static void segment_code_lcd_isr(void)
{
    //帧中断
    if (LCD_GET_PENDING()) {
        LCD_CLR_PENDING();
        LCD_ENABLE(0);//en=0
        if (__this->usr_data->isr_cb) {
            __this->usr_data->isr_cb();
        } else {
            LCD_ENABLE(1);
            LCD_SW_KST(1);
        }
    }
}

Source: segment_code_lcd_drv.c

注意该 ISR 默认关断 LCD 使能——这意味着凡提供 isr_cb 的应用,回调内完成复用功能后必须显式调用 segment_code_lcd_start() 恢复推屏,否则屏幕会停在最后一帧并永久停止刷新。

IO 复用工作流

头文件注释给出了三种场景的标准流程(不复用、复用切换、复用后恢复),核心约束有两条:

  1. LCD IO 复用其他功能期间,COM 口必须保持高阻输入(由 LCD_ENABLE(0) + IO 高阻保证);
  2. LCD 驱动过程中引脚电平会周期性翻转,复用的功能协议必须能容忍这种干扰。

驱动为此提供 segment_code_lcd_set_all_io_highz()(按 lcd_com_en/lcd_seg_en 位图把全部 COM/SEG 引脚置高阻)和 segment_code_lcd_start()(重新 LCD_ENABLE(1) + LCD_SW_KST(1) 启动推屏):

void segment_code_lcd_start(void)
{
    if (__this->init == 0) {
        return;
    }
    LCD_ENABLE(1);
    LCD_SW_KST(1);
}

Source: segment_code_lcd_drv.c

Demo 中的 isr_cb 展示了完整复用模式:帧中断到来 → 用 IO_PORTC_07 模拟一段输出(按键/点灯等)→ 恢复高阻 → segment_code_lcd_start() 继续推屏:

//复用测试
static void segment_code_lcd_isr_cb(void)
{
    u8 i;
    gpio_set_mode(PORTC, PORT_PIN_7, PORT_OUTPUT_HIGH);
    for (i = 0; i < 10; i++) {
        gpio_write(IO_PORTC_07, 1);
        udelay(5);
        gpio_write(IO_PORTC_07, 0);
        udelay(5);
    }
    gpio_set_mode(PORTC, PORT_PIN_7, PORT_HIGHZ);
    segment_code_lcd_start();
}

Source: segment_code_lcd_demo.c

跳秒功能

segment_code_lcd_set_dot_en() 控制跳秒脚(DOT)。驱动特意限制:仅当时钟源为 LCD_CLK_SEL_OSL(外挂 32K 晶振)时才允许使能,因为跳秒的精度依赖外部晶振:

void segment_code_lcd_set_dot_en(u8 dot_en)
{
    if (__this->init == 0) {
        return;
    }
    if (__this->usr_data->clk != LCD_CLK_SEL_OSL) {
        return;
    }
    LCD_DOT_EN(dot_en);
}

Source: segment_code_lcd_drv.c

核心流程

sequenceDiagram
    participant App as 应用
    participant Drv as segment_code_lcd_drv
    participant P33 as P33 寄存器
    participant Panel as LCD 面板

    App->>Drv: segment_code_lcd_init(&cfg)
    Drv->>P33: 清 LCDC_CON0~7 / SEG_IO_EN0~3
    Drv->>Drv: segment_code_lcd_pump_init() 上电时序
    Drv->>P33: 时钟/VLCD/BIAS/COM 使能/模式配置
    Drv->>P33: LCD_ENABLE(1) + SW_KST(1)
    P33-->>Panel: 开始输出 COM/SEG 扫描波形

    App->>Drv: fill_number / fill_char / fill_point
    App->>Drv: segment_code_lcd_disp_update()
    Drv->>P33: 仅刷新 disp_buffer 变化的 COM 行<br/>(p33_tx_nbyte 写 P3_SEG*_DAT*)
    P33-->>Panel: 新波形生效

    alt 断续推屏模式 (ctu_en = 0)
        P33-->>Drv: 完成一帧, PND 置位并触发中断
        Drv->>Drv: 清 PND, LCD_ENABLE(0)
        alt isr_cb 存在
            Drv->>App: isr_cb() 执行复用功能
            App->>Drv: segment_code_lcd_start()
        else isr_cb 为空
            Drv->>P33: LCD_ENABLE(1) + SW_KST(1)
        end
        P33-->>Panel: 继续下一帧
    else 连续推屏模式 (ctu_en = 1)
        Note over P33,Panel: 硬件自动循环推屏, 无中断
    end

流程解读:初始化完成后硬件即开始推屏,此后应用的所有"画图"操作都只在 RAM 缓存中进行,只有 disp_update() 才触碰 P33 总线;断续模式下每帧结束都经历"停屏 → 复用 → 重启"的循环,isr_cb 的存在与否决定了重启动作由应用还是驱动完成。

推屏模式的状态机可概括为:

flowchart TD
    Start([init 完成]) --> Mode{"ctu_en 配置"}
    Mode -->|"1 连续推屏"| Cont["硬件自动循环推屏"]
    Mode -->|"0 断续推屏"| Disc["完成一帧 -> PND 置位 -> 中断"]
    Disc --> CB{"isr_cb 非空?"}
    CB -->|"否"| Auto["ISR 自动 LCD_ENABLE(1)+SW_KST(1)"]
    CB -->|"是"| CbRun["isr_cb: 复用 SEG IO<br/>执行其他功能"]
    CbRun --> ReStart["应用调用 segment_code_lcd_start()"]
    ReStart --> Disc
    Auto --> Disc
    Cont --> Cont

使用示例

配置平台数据

Demo 展示了 6COM×21SEG(lcd_com_en=0x3F、lcd_seg_en=0x7FFFFC)断码屏的完整配置:WCLK 时钟、3.8V 泵压、1/3 bias、强充电模式、断续推屏并使能帧中断:

const struct segment_code_lcd_platform_data lcd_test_data = {
    .clk = LCD_CLK_SEL_WCLK,
    .chgpump_vol = LCDCP_PUMPVOL_SEL_3P8V,
    .ldob_vol = LCDCP_LDOB_SEL_3P3V,
    .vlcd = LCD_VOL_SEL_1P000XLCDCP,
    .bias = LCD_BIAS_1DIV3,
    .chgmode = LCD_CHGMODE_STRONG,
    .chgduty = 0,
    .dot_com = LCD_COM0,
    .dot_seg = LCD_SEG18,
    .lcd_com_en = 0x3F,     //6com
    .lcd_seg_en = 0x7FFFFC, //21seg
    .ctu_en = 0, //0:断续推屏(en ie) 1:连续推屏
    .isr_cb = segment_code_lcd_isr_cb,
};

Source: segment_code_lcd_demo.c

数字显示与全流程测试

先用 LCD_POINT 定义每个 7 段数字的 A~G 段坐标(INVALID_COM 表示该段不用,如数字 "1" 的 F 段),组织成数组后交给 fill_number;测试流程为"全显 → 清屏 → 显示两组数字 → 更新",最后喂狗死循环:

#define  NUM_SYS_HUN_A    (LCD_POINT(0, 9))
#define  NUM_SYS_HUN_B    (LCD_POINT(0, 8))
...
#define  NUM_SYS_HUN_G    (LCD_POINT(1, 8))

static const u8 NUM_SYS_HUN[7] = {NUM_SYS_HUN_A, NUM_SYS_HUN_B, NUM_SYS_HUN_C, NUM_SYS_HUN_D, NUM_SYS_HUN_E, NUM_SYS_HUN_F, NUM_SYS_HUN_G};

void segment_code_lcd_test(void)
{
    segment_code_lcd_init(&lcd_test_data);

    segment_code_lcd_disp_all();
    segment_code_lcd_disp_update();
    mdelay(500);

    segment_code_lcd_disp_clear();

    segment_code_lcd_fill_number(NUM_SYS_HUN, 1);
    segment_code_lcd_fill_number(NUM_SYS_TEN, 2);
    segment_code_lcd_fill_number(NUM_SYS_ONE, 3);

    segment_code_lcd_fill_number(NUM_DIA_HUN, 1);
    segment_code_lcd_fill_number(NUM_DIA_TEN, 5);
    segment_code_lcd_fill_number(NUM_DIA_ONE, 7);

    segment_code_lcd_disp_update();

    while (1) {
        wdt_clear();
    }
}

Source: segment_code_lcd_demo.c

该示例揭示了两个典型用法约定:填充 API 与更新 API 分离(先批量画点、再一次同步,减少 P33 总线写入);disp_update 后屏幕即由硬件接管,应用只需维持缓存即可。

配置选项

选项类型默认/推荐值说明
SEGMENT_CODE_LCD_AUTO_TRIM_ENABLE(编译宏,drv.c L13)int0是否在每次开机时用 ADC(VBG 基准)自动校准 VLCD 电压;开启后初始化会遍历 PUMPVOL±2 档与 8 档 VOL_SEL 寻找最优组合
clkenumLCD_CLK_SEL_WCLK扫描时钟源;跳秒功能要求 LCD_CLK_SEL_OSL
chgpump_volenumLCDCP_PUMPVOL_SEL_3P6V泵输出目标电压(3.0V~4.5V,步进 ~100mV)
ldob_volenumLCDCP_LDOB_SEL_3P3VLDOB 稳压输出档位
vlcdenumLCD_VOL_SEL_1P000XLCDCP面板实际工作电压 = VLCDCP × 系数(0.886~1.000)
biasenumLCD_BIAS_1DIV3偏压比,1/2、1/3、1/4 按面板规格选择
chgmodeenumLCD_CHGMODE_STRONG充电策略:弱充(省电)/强充/半周期/多周期
chgdutyu80多周期充电模式下强驱持续 chgduty+1 个 32K 时钟
dot_com / dot_segu8LCD_COM0 / LCD_SEG18跳秒脚位置
lcd_com_enu320x3FCOM0~COM5 使能位图
lcd_seg_enu320x7FFFFCSEG0~SEG26 使能位图
ctu_enu80(断续)1 为连续推屏,0 为断续推屏(配合 isr_cb)
isr_cb函数指针NULL帧中断回调;NULL 时驱动自动重启推屏

API 参考

void segment_code_lcd_init(const struct segment_code_lcd_platform_data *lcd_dat)

驱动初始化入口,必须先调用。内部完成寄存器清零、电荷泵上电、时钟/电压/BIAS/模式配置并启动推屏。

  • 参数 lcd_dat:平台配置指针,不可为 NULL;重复调用被忽略(init 标志保护)。
  • 注意:若使用外部晶振(LCD_CLK_SEL_OSL),须在 rtc_dev_init 之后初始化本驱动。

void segment_code_lcd_fill_point(u8 icon, u8 isfill)

填充或清除一个交叉点。

  • 参数 icon:LCD_POINT(com, seg) 编码,高 5 位 SEG、低 3 位 COM;isfill:1 点亮 / 0 熄灭。
  • 行为:仅修改 disp_buffer,不写硬件;com/seg 越界时静默返回。

void segment_code_lcd_fill_number(const u8 *ptr, u8 num)

按 7 段码表填充一个数字。

  • 参数 ptr:7 个 LCD_POINT 编码数组(A~G 段顺序);num:0~9 显示对应数字,'-' 显示负号,>= 11(数组大小)时清除该位全部段。
  • 行为:仅修改 disp_buffer。

void segment_code_lcd_fill_char(const u8 *ptr, u8 cc)

按 7 段码表填充一个字母('a'~'z' 或 'A'~'Z',大小写分别查表)。

void segment_code_lcd_disp_clear(void) / void segment_code_lcd_disp_all(void)

清空整个 disp_buffer / 按 lcd_com_en、lcd_seg_en 位图点亮全部可用点。仅改缓存。

void segment_code_lcd_disp_update(void)

把 disp_buffer 与 save_buffer 的差异逐 COM 写入 P33 像素寄存器(P3_SEG0_DAT0/P3_SEG4_DAT0),完成"缓存 → 硬件"同步。

void segment_code_lcd_set_ctu_en(u8 ctu_en)

运行时切换推屏模式:1 连续推屏(关 IE),0 断续推屏(开 IE)。未初始化时忽略。

void segment_code_lcd_set_dot_en(u8 dot_en)

使能/禁能跳秒脚;仅 clk == LCD_CLK_SEL_OSL 时生效,其余时钟源下静默忽略。

void segment_code_lcd_start(void)

重新启动推屏:LCD_ENABLE(1) + LCD_SW_KST(1)。断续推屏且使用 isr_cb 时,复用功能完成后必须调用,否则屏幕停更。

void segment_code_lcd_set_all_io_highz(void)

把 lcd_com_en/lcd_seg_en 位图对应的全部 COM/SEG 引脚配置为高阻输入,用于复用切换前的释放。

中断服务 static void segment_code_lcd_isr(void)

P33 LCD 帧中断处理:清 PND、关 LCD_ENABLE,然后按 isr_cb 是否注册决定回调应用或自动重启推屏。

失败模式、边界情况与并发

初始化保护

segment_code_lcd_init 同时检查 __this->init 与 lcd_dat == NULL,防止重复初始化覆盖运行中的显示配置,也防止空指针解引用。但驱动不提供反初始化接口——一旦初始化,P33 寄存器配置会保持到掉电。

越界与非法输入

  • fill_point 对 com >= LCD_COM_MAX (6)、seg >= LCD_SEG_MAX (27) 静默返回,避免写坏 disp_buffer 内存;
  • fill_number/fill_char 对 ptr == NULL 直接返回;数字超过 9 会按数组边界钳制到"清除"档;
  • disp_update/disp_all/set_all_io_highz 均检查 usr_data 是否已初始化(NULL 直接返回),保证未 init 时调用不崩溃。

中断上下文约束

segment_code_lcd_isr 在中断上下文执行,且 isr_cb 被调用前 LCD_ENABLE 已被关闭。因此:

  • isr_cb 内不得长时间阻塞(Demo 中仅做 10 次 5µs 级 GPIO 翻转);若复用功能耗时较长,应记录标志位后在主循环处理,并在主循环中调用 segment_code_lcd_start();
  • 若 isr_cb 内再次触发 LCD 相关寄存器操作,需注意与硬件推屏状态的时序;
  • 忘记调用 segment_code_lcd_start() 是最常见的故障模式:表现为屏幕刷新一次后永久定格。

时钟与功能耦合

跳秒功能依赖 LCD_CLK_SEL_OSL,使用 WCLK/LRC 时钟源时 set_dot_en 静默失效——这是硬件精度约束,而非软件缺陷。

并发与原子性

驱动是单实例(静态 _lcd_var)且所有 API 均为非可重入设计:

  • 若应用在中断/任务中同时调用填充与更新 API,需要自行保证临界区(例如关中断)——disp_buffer 的读-改-写不是原子的;
  • disp_update 的差分比较(读 save_buffer、写 save_buffer、写 P33)之间同样存在竞态窗口,多任务场景建议串行化调用。

电压校准(可选路径)

SEGMENT_CODE_LCD_AUTO_TRIM_ENABLE=1 时,初始化会通过 ADC 采样 AD_ANA_PMU_CH_VBG 与 AD_ANA_PMU_CH_VLCD_DIV_4 遍历泵压档位寻找最优组合。该路径在关 LCDCP_ADC_DET_EN 前完成,且依赖 adc_get_value_by_blocking——开机阶段会阻塞较长执行时间,对启动时延敏感的产品默认关闭(宏默认 0)。

性能与运维

  • 差分刷新:disp_update 只写内容变化的 COM 行,静态画面下 P33 总线零写入,显著降低功耗——这是 save_buffer 存在的根本原因;
  • 批量写:每个 COM 行按 4 字节 p33_tx_nbyte 一次传输,而非逐 bit 操作,缩短总线占用;
  • 连续推屏免中断:无 IO 复用需求时用 ctu_en=1,硬件自主刷新,CPU 零中断开销;
  • 推屏时序:电荷泵建立需要固定延时(PD15 输出 500µs → VBG 500µs → PUMP 1.5ms),初始化期间不可抢占关键延时(delay_us 忙等);
  • 调试手段:寄存器组提供测试模式(LCD_TEST_EN + LCD_SEG_TEST_FLAG/LCD_COM_TEST_FLAG),可将 SEG/COM 引脚直接输出 VLCD 各分压电平(VDD/VL2/VL1/VL0/PD),用于硬件排障。

扩展点

  1. isr_cb 回调:IO 复用的标准扩展钩子,可在每帧间隙执行 GPIO 复用功能;
  2. 平台数据结构:通过 lcd_com_en/lcd_seg_en/dot_com/dot_seg 可适配任意段数、任意跳秒位置的断码屏,无需改驱动;
  3. 段码表:lcd_seg_number_2_seg、lcd_seg_large_letter_2_seg、lcd_seg_small_letter_2_seg 为 static const,如需自定义字形(如箭头、特殊符号)可直接修改或按相同模式新增查表 API;
  4. SEGMENT_CODE_LCD_AUTO_TRIM_ENABLE:量产屏幕离散性大时可开启开机自动校准,也可移植 segment_code_lcd_pump_trim 的逻辑做产线校准流程;
  5. 硬件映射:hw_seg2pin_mapping/hw_com2pin_mapping 为芯片固定引脚,换芯片型号时需同步更新这两张表。

测试

segment_code_lcd_demo.c(segment_code_lcd_test)覆盖了以下路径:

  • 初始化与电荷泵上电(segment_code_lcd_init);
  • 全显/清屏(disp_all → disp_update → disp_clear);
  • 6 位数字显示(系统区 123 + 日期区 157),验证 LCD_POINT 坐标定义与 7 段码表;
  • 断续推屏 + isr_cb IO 复用(GPIO 翻转后 segment_code_lcd_start 恢复);
  • 看门狗喂狗(wdt_clear)保证测试循环不触发复位。

未覆盖(源码中未见):连续推屏模式下的完整回归、set_dot_en 跳秒实际波形验证、自动校准(AUTO_TRIM 默认关闭)路径的板级验证。

相关链接

  • 驱动实现:segment_code_lcd_drv.c
  • 驱动头文件(寄存器宏与使用流程):segment_code_lcd_drv.h
  • Demo 测试程序:segment_code_lcd_demo.c
  • 点阵 LCD 驱动(兄弟页面):lcd_drive.c 与 lcd_drive.h
Prev
RTC 与低功耗唤醒
Next
NOR Flash 与红外编解码组件