杰理 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 驱动与字库引擎

本页介绍 AC82N SDK 中 TFT 彩屏的 LCD 驱动框架(lcd_drive)与 Unicode 点阵字库引擎(font_unicode),涵盖面板适配、SPI/PAP 推屏、DMA 中断同步、字库文件格式、页索引查找与文本排版输出的完整机制。

Purpose and Scope

本页覆盖两块紧密协作的显示能力:

  1. LCD 驱动层:sdk/apps/common/lcd/ 下的 lcd_drive.c/h 与各面板驱动(ST7735、ST7789 系列),提供屏幕初始化、画布管理、绘图原语与 SPI DMA 推屏通道。
  2. 字库引擎:sdk/apps/common/font/ 下的 font_textout.h、font_unicode.c,提供从 SD 卡字库文件读取 Unicode 点阵位图、按文本排版输出到 LCD 的能力。

以下相关内容属于兄弟主题,不在本页展开:

  • 段码屏(segment code LCD)驱动:见 sdk/cpu/cd09/segment_code_lcd_drv.c(对应 ui_devices_type 中的 LED_7 / LCD_SEG3X9)。
  • 字库文件的生成工具(FontTool.exe)与字库 XML 工程文件:见 doc/软件工具/unicode字库工具/。
  • 上层 UI 框架与触摸屏处理:由独立 UI 页面负责。

Overview

在 AC82N 这类 MCU 方案中,LCD 显示链路通常为:应用代码 → 字库引擎(将字符串解析为位图)→ LCD 驱动(将位图写入显存/直接推屏)→ SPI/PAP 硬件(DMA 搬运)→ 屏幕面板。

设计动机

  • 面板多样性:同一套 SDK 需支持 128×128、80×160、240×240、240×320 等多种分辨率与 ST7735/ST7789 不同控制 IC。设计上用 struct spi_lcd_init 抽象出面板差异(初始化序列、命令/数据写函数、绘图区域设置、背光控制等),配合 REGISTER_LCD_DRIVE() 宏让每个面板 .c 文件声明一个 lcd_drive 实例即可接入框架。
  • 推屏方式可选:LCD_PUSH_MODE 支持 SPI(硬件 SPI + DMA)与 PAP(并行/专用屏接口)两种通道,编译期选择,避免运行时开销。
  • 资源受限:MCU 内存有限,采用"分块推屏"(lcd_clear_screen 按行分块)而非整屏缓冲;字库数据不常驻内存,按需从 SD 卡随机读取(页索引 + 位图数据两级定位)。
  • 异步不阻塞:SPI 发送走 DMA + 中断(spi_isr),is_lcd_busy 标志协调"设置绘图区域 → 写数据 → DMA 完成"的时序,避免屏幕撕裂与数据覆盖。

关键概念

概念说明
spi_lcd_init面板驱动结构体,函数指针集合(Init/WriteComm/WriteData/WriteMap/SetDrawArea/reset/BackLightCtrl/EnterSleep/ExitSleep)
InitCode面板初始化命令序列(cmd + cnt + dat[64]),REGFLAG_DELAY(0xFF) 表示延时
LCD_PUSH_MODE推屏通道选择:SPI(0) 或 PAP(1)
font_info字库运行上下文:像素字库文件句柄、字号、位图缓冲、排版标志与文本尺寸
Font_PageEntry_t字库文件页索引(charIndex + codesMask 位图),实现 O(1) 级字符定位
FONT_SHOW_PIXEL / FONT_SHOW_MULTI_LINE排版标志:输出位图到屏幕 / 允许多行自动换行

Architecture

下图展示了从应用层到屏幕面板的完整显示链路,以及字库引擎在其中的位置:

flowchart TD
    subgraph sg_App["应用层"]
        App["应用代码 / 测试函数"]
    end

    subgraph sg_Font["字库引擎 (font_unicode.c)"]
        FontAPI["font_unicode_open / TextOutW_Unicode"]
        CharIdx["Font_GetCharIndex()"]
        CharBits["Font_GetCharBits()"]
        PixBuf["font_copy_pixbuf()"]
    end

    subgraph sg_Lcd["LCD 驱动层 (lcd_drive.c)"]
        DrvInit["lcd_driver_init()"]
        DrawArea["lcd_set_draw_area()"]
        LcdDraw["lcd_draw()"]
        Prims["lcd_drawpoint / lcd_draw_line / lcd_fill_rect"]
        Dma["spi_dma_send_map()"]
    end

    subgraph sg_Panel["面板驱动 (lcd_*.c)"]
        Panel["struct spi_lcd_init lcd_drive"]
        InitSeq["InitCode 初始化序列"]
    end

    subgraph sg_Hw["硬件层"]
        Spi["SPI1 + DMA"]
        Screen["LCD 屏幕"]
        FontFile["SD 卡字库文件"]
    end

    App --> DrvInit
    App --> FontAPI
    FontAPI --> CharIdx
    CharIdx --> CharBits
    CharBits --> PixBuf
    PixBuf --> LcdDraw
    LcdDraw --> DrawArea
    LcdDraw --> Dma
    DrvInit --> Panel
    Panel --> InitSeq
    Panel --> Spi
    Spi --> Screen
    CharBits --> FontFile
    Prims --> LcdDraw

各组件职责:

  • 应用层:调用 lcd_driver_init() 完成屏幕初始化,或调用 font_unicode_open() / TextOutW_Unicode() 将字符串渲染到屏幕;push_screen_test() 与 font_unicode_test() 是 SDK 提供的自测入口(见 lcd_drive.h)。
  • 字库引擎:解析 UTF-16 字符串,通过页索引在 SD 卡字库文件中定位每个字符的点阵位图,把位图经 font_copy_pixbuf() 送交 LCD 驱动。
  • LCD 驱动层:屏蔽面板差异,提供 lcd_set_draw_area()、lcd_draw()、lcd_clear_screen() 等高层接口;内部通过 spi_dma_send_map() 走 DMA 通道,由 spi_isr() 中断同步完成状态。
  • 面板驱动:每个面板 .c 文件声明一个 lcd_drive 实例(通过 REGISTER_LCD_DRIVE() 宏),包含初始化命令表(InitCode 数组)与底层读写函数指针。
  • 硬件层:HW_SPI1(默认)承担推屏总线,RES/CS/RS/BL 走普通 GPIO;字库文件存放在 SD 卡(sdfile_open)。

面板驱动接口(类图)

struct spi_lcd_init 是面板适配的核心契约,面板 .c 文件只需填充该结构并导出为全局符号 lcd_drive:

classDiagram
    class spi_lcd_init {
        +char *name
        +u8 spi_pending
        +u8 soft_spi
        +u16 lcd_width
        +u16 lcd_height
        +u8 color_format
        +u8 interface
        +u8 column_addr_align
        +u8 row_addr_align
        +u8 backlight_status
        +char *dispbuf
        +u32 bufsize
        +InitCode *initcode
        +u16 initcode_cnt
        +void Init()
        +void WriteComm(u8 cmd)
        +void WriteData(u8 dat)
        +void WriteMap(char *map, u32 size)
        +void SetDrawArea(int xs, int xe, int ys, int ye)
        +void reset()
        +void BackLightCtrl(u8)
        +void EnterSleep()
        +void ExitSleep()
    }

字段说明:lcd_width/lcd_height 为面板物理分辨率;color_format 取自 LCD_COLOR(RGB565/MONO);interface 取自 LCD_IF(SPI/EMI);initcode/initcode_cnt 为初始化命令表;函数指针是驱动层与面板的唯一交互通道。框架通过 REGISTER_LCD_DRIVE() 宏声明 struct spi_lcd_init lcd_drive,并以 extern 方式引用(见 lcd_drive.h)。

LCD 驱动层深入

编译期配置与通道选择

lcd_drive.h 顶部通过一组宏在编译期完成推屏通道与面板选择(见 lcd_drive.h):

  • LCD_PUSH_MODE:SPI(0) 走硬件 SPI,PAP(1) 走 PAP 专用接口。选择 SPI 时自动启用 TCFG_TFT_LCD_DEV_SPI_HW_NUM(默认 1)与 LCD_SPI_INTERRUPT_ENABLE;选择 PAP 时两者强制为 0。
  • TCFG_LCD_9BIT_SPI_ENABLE:9-bit SPI(数据/命令附加位由硬件 SPI CON1 寄存器 BIT4 控制,用于如 lcd_spi_st7789_BOE1.54_update_240x240 这类屏)。
  • SPI_MODULE_CHOOSE:选择 SPI0/1/2 作为推屏通道,并据此映射中断向量 IRQ_SPI_IDX(IRQ_SPI0_IDX/IRQ_SPI1_IDX/IRQ_SPI2_IDX)。注释明确提示 SPI0 通常被外部 flash 占用,一般不用。
  • 面板使能开关:TCFG_LCD_SPI_ST7735V_128X128_ENABLE、TCFG_LCD_SPI_ST7735V_80X160_ENABLE、TCFG_LCD_SPI_ST7789V_ENABLE、TCFG_LCD_PAP_SPI_ST7789V_240X240_ENABLE、TCFG_LCD_PAP_SPI_ST7789V_240X320_ENABLE,对应 sdk/apps/common/lcd/ 下各面板 .c 文件。

这种"编译期剪裁"的意图:MCU 方案 ROM/RAM 紧张,把不用的面板与中断逻辑直接裁剪掉,避免运行时选择带来的代码体积与功耗开销。

平台数据与硬件绑定

lcd_drive.c 中定义了默认 SPI 通道的硬件信息与引脚绑定(见 lcd_drive.c):

#if (LCD_PUSH_MODE == SPI)
static spi_hardware_info spi1_p_data = {
    .port = {
        IO_PORTC_01, //clk any io
        IO_PORTC_00, //do any io
        -1, //di any io
        -1,
        -1,
        -1,
    },
    .role = SPI_ROLE_MASTER,
    .mode = SPI_MODE_BIDIR_1BIT,//SPI_MODE_UNIDIR_2BIT,//SPI_MODE_BIDIR_1BIT,
    .bit_mode = SPI_FIRST_BIT_MSB,
    .cpol = 0,//clk level in idle state:0:low,  1:high
    .cpha = 0,//sampling edge:0:first,  1:second
    .ie_en = 0, //ie enbale:0:disable,  1:enable
    .spi_isr_callback = NULL,  //spi isr callback
    .clk  = 2000000L,
};

const static struct lcd_spi_platform_data lcd_spi_data = {
    .pin_reset	= IO_PORTC_11,
    .pin_rs	= IO_PORTC_02,
    .pin_cs	= IO_PORTD_02,
    .pin_bl     = IO_PORTA_08,
    .spi_cfg	= HW_SPI1,
    .spi_pdata  = &spi1_p_data,
};
#elif (LCD_PUSH_MODE == PAP)
...
#endif

要点:

  • SPI 采用单线双向模式(SPI_MODE_BIDIR_1BIT)、MSB 优先、CPOL/CPHA 均为 0(空闲低电平、首沿采样),时钟默认 2 MHz —— 这是兼容绝大多数 ST77xx 面板的保守配置,需要提速时可在此调整。
  • lcd_spi_platform_data 将 RES/RS/CS/BL 四个控制脚与 SPI 实例绑定;PAP 模式复用同一结构(引脚不同),并注明"触摸屏和屏公用复位,先初始化触摸"的时序约束。
  • ui_devices_cfg 将整块 LCD 注册为 TFT_LCD 类型设备,private_data 指向 lcd_spi_data,供上层 UI 系统按类型分派。

初始化流程

lcd_driver_init() 的职责链:spi_init() 调用 spi_open() 打开 SPI 外设,并在 LCD_SPI_INTERRUPT_ENABLE 下 request_irq(IRQ_SPI_IDX, 3, spi_isr, 0) 注册中断(见 lcd_drive.c);随后调用面板的 lcd_drive.Init() 依次执行 InitCode 初始化序列(其中 REGFLAG_DELAY 表示插入延时)。初始化完成后,屏幕处于可绘制状态,背光由 lcd_backlight_ctrl() 控制。

SPI 中断与 DMA 同步机制

这是本驱动最关键的并发设计(见 lcd_drive.c):

static void spi_dma_wait_finish()
{
    if (spi_pnd) {
        while (!spi_get_pending(spi_dat->spi_cfg)) {
        }
        spi_clear_pending(spi_dat->spi_cfg);
        spi_pnd = false;
    }
}

static int __spi_dma_send(spi_dev spi, void *buf, u32 len, u8 wait)
{
    int err = 0;

    if (!wait || spi_pnd) {
        spi_dma_wait_finish();
    }
    spi_dma_transmit_for_isr(spi_dat->spi_cfg, buf, len, 0);
    spi_pnd = true;
    asm("csync");

    if (wait) {
        spi_dma_wait_finish();
    }

    return err;
}
  • spi_pnd(静态标志)记录"是否有 DMA 发送尚未完成";spi_dma_wait_finish() 通过轮询 spi_get_pending() 等待上次发送完成,再清标志。asm("csync") 是 CPU 指令屏障,保证 DMA 配置写入在启动发送前可见。
  • 中断路径:spi_isr() 在 DMA 完成中断到来时清除 is_lcd_busy 并关闭 SPI 中断使能(spi_set_ie(..., 0))。注释说明 DMA 模式在发送时内部已清理中断 pending。
  • 这种"轮询等待 + 中断复位忙标志"组合的意图:推屏是周期性大批量操作,DMA 启动后 CPU 可继续执行其他任务,仅在关键切换点同步,从而降低帧推送的 CPU 占用。

绘图原语与画布操作

驱动对外提供统一的绘图 API(声明见 lcd_drive.h):

函数作用
lcd_driver_init / lcd_driver_deinit屏幕初始化 / 反初始化
lcd_set_draw_area(xs, xe, ys, ye)设置绘图窗口,内部设 is_lcd_busy=1 并调用面板 SetDrawArea
lcd_draw(buf, len, wait)将 len 字节写入屏幕;wait=1 时开启 SPI 中断等待,否则立即返回
lcd_clear_screen(color)按行分块清屏(避免一次性大缓冲)
lcd_fill_rect / lcd_drawpoint / lcd_draw_line矩形填充 / 画点 / 画线
lcd_backlight_ctrl(on)背光开关
lcd_get/set_screen_width/height屏幕分辨率读写
lcd_st7735_set_direction(dir)旋转方向(LCD_DIRECTION 枚举:0°/90°/180°/270°)

lcd_draw() 的实现体现了忙状态保护(见 lcd_drive.c):

int lcd_draw(char *buf, u32 len, u8 wait)
{
    if ((is_lcd_busy == 0x11) || lcd_sleep_in) {
        return 0;
    }

    if (wait) {
        __this->WriteMap(buf, len);
#if LCD_SPI_INTERRUPT_ENABLE
        spi_set_ie(spi_dat->spi_cfg, 1);
#else
        is_lcd_busy = 0;
#endif
    } else {
        __this->WriteMap(buf, len);
        is_lcd_busy = 0;
    }
#if (LCD_PUSH_MODE == SPI)
    spi_dma_wait_finish();
#endif
    return 0;
}

设计意图:is_lcd_busy == 0x11(休眠/异常态)或 lcd_sleep_in(睡眠中)时直接丢弃绘制请求,防止向未就绪的控制器写数据;wait 模式依赖中断复位忙标志实现"写完后可被下一帧覆盖"的流水线化。

核心流程:从字符串到屏幕像素

sequenceDiagram
    participant App as 应用层
    participant Font as 字库引擎
    participant SD as SD 卡
    participant Lcd as lcd_drive
    participant SPI as SPI1 DMA
    participant Panel as 面板驱动

    App->>Font: font_unicode_open(info)
    Font->>SD: sdfile_open(字库文件, "r")
    SD-->>Font: FILE*
    Font-->>App: info (含 pixelbuf)
    App->>Font: TextOutW_Unicode(info, str, len)
    loop 每个 UTF-16 字符
        Font->>SD: Font_GetCharIndex(unicode) 页索引定位
        Font->>SD: Font_GetCharBits(unicode) 读取位图
        SD-->>Font: pixelbuf + 宽度
        Font->>Lcd: font_copy_pixbuf → lcd_draw(buf)
        Lcd->>Panel: SetDrawArea / WriteMap
        Panel->>SPI: spi_dma_transmit_for_isr
        SPI-->>Panel: DMA 完成(中断复位 is_lcd_busy)
    end
    Font-->>App: 返回已处理字符数 i

时序要点:

  1. 字库打开阶段只做一次文件打开与 "unic" magic 校验,不加载整库到内存。
  2. 每个字符经历"页索引查询 → 位图读取 → 排版坐标计算 → 推屏"四步;TextOutW_Unicode 以 2 字节为步长解析 UTF-16(受 bigendian 标志控制字节序)。
  3. 推屏阶段 lcd_set_draw_area 先锁定忙标志,lcd_draw 送数据后由 SPI 中断复位,保证下一字符/下一帧不会与未完成的 DMA 竞争。

字库引擎深入

字库文件格式

font_unicode.c 定义了字库文件的三大区域(见 font_unicode.c):

struct font_header {
    u8 magic[4];
    u32 version;
    u32 timestamp;
    u8 fontsize;
    u8 reserved[19];
};

typedef struct {
    u8 width;
    u8 height;
    s8 left;
    s8 top;
    u32 baseaddr;
} __attribute__((packed, aligned(1))) UnicInfo;

#define Font_NumPages 1024
#define Font_PageSize (0x10000 / Font_NumPages)

typedef struct {
    u16 charIndex;
    u8 codesMask[Font_PageSize / 8];
} Font_PageEntry_t;

文件布局:文件头(struct font_header,magic 固定为 "unic")→ 1024 条页索引(Font_PageEntry_t)→ 每字符一条 UnicInfo(含位图基地址 baseaddr)→ 点阵位图数据区。

设计意图:Unicode 码点空间为 0x0000–0xFFFF(0x10000),被平均切成 Font_NumPages = 1024 页,每页覆盖 Font_PageSize = 64 个码点。页索引中的 codesMask 是 8 字节位图,标记本页 64 个码点哪些存在;charIndex 记录本页第一个字符在 UnicInfo 表中的起始下标。这样查找一个字符是否收录、以及其位图位置,都只需要一次 8 字节读 + 少量位运算,无需扫描全表。

页索引查找算法

/* 获取字符索引 */
static int Font_GetCharIndex(struct font_info *info, u16 unicode)
{
    Font_PageEntry_t  pageEntry;

    u16  m = unicode / Font_PageSize;
    u16  n = unicode % Font_PageSize;
    u16  i, j;

    sdfile_seek(info->pixel.file.fd, sizeof(struct font_header) + m * sizeof(Font_PageEntry_t), SEEK_SET);
    sdfile_read(info->pixel.file.fd, &pageEntry, sizeof(Font_PageEntry_t));

    if (!(pageEntry.codesMask[n / 8] & (1 << (n % 8)))) {
        return -1;
    }

    for (i = j = 0; i < n; i++) {
        if (pageEntry.codesMask[i / 8] & (1 << (i % 8))) {
            j++;
        }
    }
    i = pageEntry.charIndex + j;
    return i;
}

(源码见 font_unicode.c)

算法步骤:

  1. 由 unicode 计算页号 m 与页内偏移 n(每页 64 码点)。
  2. 定位并读取该页 Font_PageEntry_t(文件头之后按 m * sizeof(entry) 偏移)。
  3. 检查 codesMask 中 n 对应位;未置位说明字符未收录,返回 -1。
  4. 若收录,统计页内 < n 的已收录字符数 j,则字符索引 = pageEntry.charIndex + j。由于页内字符按码点顺序连续编号,UnicInfo 表无需按页对齐,节省了大量索引空间。

Font_GetCharBits() 随后读取 UnicInfo(宽度/高度/偏移/baseaddr),计算位图字节数 width * ((size+7)/8),并将位图读入 info->pixel.pixelbuf(见 font_unicode.c)。注意它处理了缓冲越界:若本次字符所需字节数超过既有缓冲,会 free 后按需 malloc,避免固定缓冲浪费或溢出。

文本排版与输出

TextOutW_Unicode() 是排版核心(见 font_unicode.c),其行为要点:

  • 字节序:info->bigendian 决定 UTF-16 高低字节顺序,兼容不同字库工具生成的字节序。
  • 换行:遇到 '\n' 重置 x 坐标、y 下移一行;超出 text_height 则停止。
  • 自动换行:xpos > text_width 时,若置 FONT_SHOW_MULTI_LINE 则折行(并把 i 回退 2 字节重新处理该字符),否则截断退出。
  • 缺字回退:width == 0(未收录)时用 '_' 字符替代,避免乱码占位。
  • 像素输出:置 FONT_SHOW_PIXEL 时逐字符调用 font_copy_pixbuf(info, width, pixel_size, xpos-width, ypos) 将位图拷贝到显存/推屏缓冲。
  • 尺寸统计:累计 string_width,最终 string_height = ypos + pixel_size,供调用方做对齐/居中布局。

数据模型

erDiagram
    FONT_FILE ||--o{ PAGE_ENTRY : "1024 条页索引"
    FONT_FILE ||--o{ UNIC_INFO : "每字符一条"
    FONT_FILE ||--o{ PIXEL_DATA : "点阵位图区"
    PAGE_ENTRY {
        uint16 charIndex "本页首个字符索引"
        uint8 codesMask "8 字节收录位图"
    }
    UNIC_INFO {
        uint8 width "字符宽度"
        uint8 height "字符高度"
        int8 left "左偏移"
        int8 top "上偏移"
        uint32 baseaddr "位图基地址"
    }
    FONT_FILE {
        char magic "unic"
        uint32 version "版本"
        uint32 timestamp "生成时间"
        uint8 fontsize "字号"
    }

运行时上下文 struct font_info(见 font_textout.h)将上述文件与排版状态绑定:pixel.file(文件名 + FILE*)、pixel.size(字号)、pixel.pixelbuf(位图缓冲)、pixel.nbytes(缓冲大小)、flags(FONT_SHOW_PIXEL / FONT_SHOW_MULTI_LINE)、text_width/text_height(文本区约束)与 string_width/string_height(排版结果)。LCD_FONT_DRAW_TOP(80)是 SDK 示例中文本在屏幕上的起始 y 坐标。

Usage Examples

示例一:初始化字库并打开

bool InitFont_Unicode(struct font_info *info)
{
    struct font_header header;
    memset(&header, 0, sizeof(header));
    info->pixel.file.fd = sdfile_open((char *)info->pixel.file.name, "r");
    if (!info->pixel.file.fd) {
        log_error("unicode file not open!");
        return FALSE;
    }

    sdfile_seek(info->pixel.file.fd, 0, SEEK_SET);
    sdfile_read(info->pixel.file.fd, &header, sizeof(struct font_header));
    log_info("magic : %s\n", header.magic);
    log_info("version : 0x%x\n", header.version);
    log_info("timestamp : %d\n", header.timestamp);
    log_info("fontsize : %d\n", header.fontsize);

    if (memcmp(header.magic, "unic", 4)) {
        log_error("unicode magic not paired!");
        fclose(info->pixel.file.fd);
        info->pixel.file.fd = NULL;
        return FALSE;
    }
    info->pixel.size = header.fontsize;
    info->pixel.nbytes = info->pixel.size * ((info->pixel.size + 7) / 8);
    log_info("unicode file open succ!\n");
    return TRUE;
}

(源码见 font_unicode.c)

打开时即校验 "unic" magic 并从文件头读取字号,据此计算默认位图缓冲大小 size * ((size+7)/8)(向上取整到字节)。文件校验失败会立即关闭句柄,防止后续对非法文件的随机读取。

示例二:设置绘图区域并推屏

int lcd_set_draw_area(int xs, int xe, int ys, int ye)
{
    if ((is_lcd_busy == 0x11) || lcd_sleep_in) {
        return 0;
    }
    is_lcd_busy = 1;
#if (LCD_PUSH_MODE == SPI)
    spi_set_ie(spi_dat->spi_cfg, 0);
    spi_dma_wait_finish();
#endif
    __this->SetDrawArea(xs, xe, ys, ye);
    return 0;
}

(源码见 lcd_drive.c)

典型调用序列是 lcd_set_draw_area(0, w-1, 0, h-1) + lcd_draw(buf, w*h*2, 1):先锁定忙标志并关闭 SPI 中断、等待前一 DMA 结束,再下发窗口命令,随后 lcd_draw 以 wait=1 模式写完数据并开启中断等待完成。字库引擎产出的每行位图正是通过这一序列逐行/逐块上屏的。

示例三:面板注册宏

#define REGISTER_LCD_DRIVE() \
	struct spi_lcd_init lcd_drive

extern struct spi_lcd_init lcd_drive;

(源码见 lcd_drive.h)

新增面板时(如 lcd_spi_st7735_80x160.c),在该文件顶部执行 REGISTER_LCD_DRIVE(),并填充 name/分辨率/color_format/initcode/initcode_cnt 与各函数指针,框架即可通过 extern 引用统一驱动,无需修改 lcd_drive.c —— 这是典型的"表驱动 + 全局符号注册"扩展模式。

Configuration Options

LCD 驱动与字库引擎的配置集中在 lcd_drive.h 编译期宏与 font_textout.h 运行期标志中:

LCD 驱动编译期宏

选项类型默认值说明
LCD_PUSH_MODEenumSPI(0)推屏通道:SPI=硬件 SPI+DMA,PAP=并行屏接口
TCFG_LCD_SPI_ST7735V_128X128_ENABLEbool0使能 128×128 ST7735 SPI 面板
TCFG_LCD_SPI_ST7735V_80X160_ENABLEbool1使能 80×160 ST7735 SPI 面板(示例默认屏)
TCFG_LCD_SPI_ST7789V_ENABLEbool0使能通用 ST7789 SPI 面板
TCFG_LCD_PAP_SPI_ST7789V_240X240_ENABLEbool0使能 240×240 ST7789 PAP 面板
TCFG_LCD_PAP_SPI_ST7789V_240X320_ENABLEbool0使能 240×320 ST7789 PAP 面板
TCFG_TFT_LCD_DEV_SPI_HW_NUMint1(SPI 模式)推屏 SPI 硬件序号
LCD_SPI_INTERRUPT_ENABLEbool1(SPI 模式)是否使用 SPI 中断同步 DMA 完成
TCFG_LCD_9BIT_SPI_ENABLEbool09-bit SPI 模式(命令/数据附加位走硬件)
SPI_MODULE_CHOOSEintTCFG_TFT_LCD_DEV_SPI_HW_NUM选择 SPI0/1/2;SPI0 通常被外部 flash 占用
IRQ_SPI_IDXirq依 SPI_MODULE_CHOOSE映射到 IRQ_SPI0/1/2_IDX
REGFLAG_DELAYu80xFFInitCode 命令表中表示"延时"的特殊命令

以上均定义于 lcd_drive.h。

硬件绑定(可裁剪)

配置位置默认值说明
spi1_p_data.portlcd_drive.cCLK=IO_PORTC_01, DO=IO_PORTC_00SPI 时钟/数据脚
spi1_p_data.clklcd_drive.c2000000L (2MHz)SPI 时钟频率
lcd_spi_data.pin_reset/rs/cs/bllcd_drive.cPORTC_11 / PORTC_02 / PORTD_02 / PORTA_08面板控制脚(SPI 模式)
lcd_spi_data.spi_cfglcd_drive.cHW_SPI1SPI 外设实例

见 lcd_drive.c。

字库引擎标志与常量

选项类型默认值说明
FONT_SHOW_PIXELu16 flag0x01排版时将位图输出到屏幕
FONT_SHOW_MULTI_LINEu16 flag0x02超过 text_width 时自动换行(否则截断)
FONT_DEFAULTu16FONT_SHOW_PIXEL默认排版标志组合
LCD_FONT_DRAW_TOPint80示例中文本起始 y 坐标
Font_NumPagesint1024字库文件页索引条数
Font_PageSizeint64每页覆盖的 Unicode 码点数(0x10000/1024)

见 font_textout.h 与 font_unicode.c。

API Reference

LCD 驱动

void lcd_driver_init(void) / void lcd_driver_deinit(void)

初始化/反初始化 LCD:spi_open + 注册中断 + 执行面板 Init() 初始化序列;反初始化释放对应资源。

int lcd_draw(char *buf, u32 len, u8 wait)

将缓冲区写入屏幕当前绘图区域。

  • 参数:buf 像素数据(RGB565);len 字节数;wait=1 时等待 DMA 完成(依赖 SPI 中断),=0 时立即返回。
  • 返回:0 成功;当 is_lcd_busy == 0x11 或 lcd_sleep_in 时直接返回 0 并丢弃数据。
  • 抛出/日志:DMA 发送错误时 log_error("spi dma send map timeout")(见 spi_dma_send_map)。

int lcd_set_draw_area(int xs, int xe, int ys, int ye)

设置面板绘图窗口。

  • 参数:窗口的 x/y 起止坐标(含端点)。
  • 行为:置 is_lcd_busy = 1,SPI 模式下先关中断并等待前一 DMA 完成,再调用面板 SetDrawArea。

int lcd_clear_screen(u16 color)

按行分块清屏,避免整屏大缓冲;内部循环调用推屏。

int lcd_backlight_ctrl(u8 on)

背光控制:通过 lcd_bl_h/lcd_bl_l 切换 pin_bl 电平。

void lcd_fill_rect(u16 xs, u16 xe, u16 ys, u16 ye, u16 color) / void lcd_drawpoint(u16 x, u16 y, u16 color) / void lcd_draw_line(u16 xs, u16 ys, u16 xe, u16 ye, u16 color)

基础绘图原语,均以 lcd_set_draw_area + lcd_draw 为基础实现。

u16 lcd_get_screen_width(void) / u16 lcd_get_screen_height(void) / void lcd_set_screen_width(u16) / void lcd_set_screen_height(u16)

屏幕逻辑分辨率读写(旋转后由上层更新)。

void lcd_st7735_set_direction(enum LCD_DIRECTION dir)

设置显示方向:ROTATE_0/90/180/270_CLOCKWISE。

void spi_dma_send_map(char *map, u32 size)

直接经 DMA 发送整块数据(不等待),由 spi_isr 后续复位忙标志。

GPIO 控制函数

lcd_reset_l/h()、lcd_cs_l/h()、lcd_rs_l/h()、lcd_bl_l/h():直接操作 RES/CS/RS/BL 引脚电平(9-bit SPI 模式下 lcd_rs_* 改为操作 JL_SPI1->CON1 BIT4)。声明见 lcd_drive.h。

字库引擎

bool InitFont_Unicode(struct font_info *info)

打开并校验字库文件(magic "unic"),设置字号与默认位图缓冲大小。失败返回 FALSE 并关闭句柄。

struct font_info *font_unicode_open(struct font_info *info)

打开字库(封装 InitFont_Unicode),返回 info 供后续排版使用。

u16 TextOutW_Unicode(struct font_info *info, u8 *str, u16 len)

将 UTF-16 字符串排版到文本区。

  • 参数:info 字库上下文(含 flags、text_width/height);str UTF-16 字符串;len 字节长度(应为偶数)。
  • 返回:已处理的字符位置(字节数)。
  • 行为:支持 '\n' 换行、FONT_SHOW_MULTI_LINE 自动换行、缺字回退 '_';置 FONT_SHOW_PIXEL 时经 font_copy_pixbuf 输出位图;更新 string_width/string_height。

void font_copy_pixbuf(struct font_info *info, u16 width, u16 height, u16 x, u16 y)

将当前 pixelbuf 中单个字符位图按坐标 (x, y) 拷贝至显存/推屏缓冲。

int font_unicode_display(struct font_info *info, u8 *pixbuf, u16 strlen)

封装"打开 → 排版输出"的高层显示接口。

void font_unicode_uninit(struct font_info *info)

释放字库资源(关闭文件、释放位图缓冲)。

以上声明见 font_textout.h。

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

字库文件相关

失败场景处理方式源码位置
字库文件打开失败sdfile_open 返回 NULL,log_error("unicode file not open!"),返回 FALSEfont_unicode.c
magic 校验失败memcmp(header.magic, "unic", 4) 不等则关闭句柄并返回 FALSE,防止对非法文件随机读取font_unicode.c
字符未收录Font_GetCharIndex 返回 -1,Font_GetCharBits 打日志并返回 0;排版层回退为 '_' 字符font_unicode.c
位图缓冲不足nbytes > info->pixel.nbytes 时 free 旧缓冲并按需 malloc;分配失败则 nbytes=0 并返回 0(丢弃该字符)font_unicode.c
换行越界ypos + pixel_size > text_height 时回退并终止排版,避免绘制越出文本区font_unicode.c

推屏时序与并发

  • 忙状态保护:is_lcd_busy 声明为 volatile u8,由 lcd_set_draw_area 置 1、spi_isr 中断清零。lcd_set_draw_area/lcd_draw 入口处检查 is_lcd_busy == 0x11 与 lcd_sleep_in,命中即静默丢弃绘制 —— 这是"宁可丢帧也不撕裂/越序"的取舍(见 lcd_drive.c)。
  • DMA 悬挂:spi_pnd 静态标志记录未完成的 DMA;spi_dma_wait_finish() 以忙等方式轮询 spi_get_pending。若 DMA 异常不完成会导致阻塞,因此关键路径(设置绘图区域前)先 spi_set_ie(...,0) 关中断再等待,避免中断竞争。
  • 内存屏障:asm("csync") 保证 spi_dma_transmit_for_isr 的寄存器配置在启动前对硬件可见,属于 MCU 裸机编程中必须的序列化操作。
  • 中断上下文:spi_isr 标注 __attribute__((interrupt(""))),仅清 pending/复位忙标志/关中断使能,不做任何耗时操作,符合中断处理最小化原则。
  • 文件句柄:字库 FILE* 为全局上下文持有,TextOutW_Unicode 每次字符随机 seek/read;若上层并发操作同一 font_info 会破坏文件偏移,因此字库渲染设计上为单线程使用模型。

性能与运维考虑

  • SPI 时钟:默认 2 MHz 为保守值;实际量产屏(ST7735/ST7789)通常支持更高频率,可在 spi1_p_data.clk 上调,推屏吞吐随之线性提升(见 lcd_drive.c)。
  • DMA 非阻塞:lcd_draw(buf, len, 0) 非等待模式下 DMA 搬运不占 CPU,配合中断复位忙标志可实现双缓冲/流水线推屏;代价是调用方须自行保证缓冲存活期。
  • 字库随机读取:每字符两次 sdfile_seek/read(页索引 + 位图),SD 卡顺序访问友好度一般;对于滚动文本,建议预渲染整行到缓冲再一次性推屏,减少 seek 次数。
  • 内存占用:位图缓冲按需扩容(最大字符宽度 × 每像素字节),不会为整库分配内存;lcd_clear_screen 分块清屏避免整屏 RGB565 缓冲。
  • 调试手段:SDK 提供 push_screen_test() 与 font_unicode_test() 两个自测入口(见 lcd_drive.h);驱动日志 TAG 为 [LCD_DRV],字库为 [UNICODE],可在 debug.h 的 LOG_TAG_CONST 开关控制下查看初始化与错误信息。

扩展点

  1. 新增面板:在 sdk/apps/common/lcd/ 新建 lcd_xxx.c,执行 REGISTER_LCD_DRIVE() 声明 lcd_drive,填充分辨率、color_format、InitCode 初始化序列与全部函数指针;在 lcd_drive.h 增加对应 TCFG_LCD_*_ENABLE 开关。框架无需任何改动。
  2. 更换推屏通道:调整 LCD_PUSH_MODE(SPI↔PAP);PAP 面板(lcd_pap_st7789_240x240.c 等)自带 PAP 平台数据与初始化序列。
  3. 9-bit SPI 屏:置 TCFG_LCD_9BIT_SPI_ENABLE=1,lcd_rs_* 自动切换为操作 JL_SPI1->CON1 BIT4 附加位。
  4. 自定义字库:doc/软件工具/unicode字库工具/ 提供 FontTool.exe 与 font.xml 工程文件,可生成符合上述页索引格式的自定义字号字库文件,替换 SD 卡上的字库即可,代码零改动。
  5. 其他显示设备:ui_devices_type 枚举(LED_7/LCD_SEG3X9/TFT_LCD/DOT_LCD)预留了点阵屏与段码屏的扩展位;段码屏实现见 sdk/cpu/cd09/segment_code_lcd_drv.c,属兄弟主题。

测试

SDK 自带两个显示自测函数(声明见 lcd_drive.h):

  • push_screen_test():向屏幕整幅推送测试图案,验证 lcd_set_draw_area/lcd_draw/DMA 链路是否正常。
  • font_unicode_test():以 font_unicode 接口渲染测试字符串,验证字库打开、页索引查找、位图读取与排版输出(含 LCD_FONT_DRAW_TOP 坐标基线)。

两者组合可快速定位问题在"推屏硬件链路"还是"字库/排版软件链路";配合 [LCD_DRV]/[UNICODE] 日志可进一步缩小到初始化序列、magic 校验或字符索引等具体环节。仓库中 sdk/cpu/demo/segment_code_lcd_demo.c 展示了兄弟主题段码屏的测试方式,可作对比参考。

Related Links

  • lcd_drive.h — 驱动接口与配置宏
  • lcd_drive.c — 驱动实现(DMA/中断/绘图)
  • font_textout.h — 字库上下文与 API
  • font_unicode.c — 字库引擎实现
  • 面板驱动:lcd_spi_st7735_80x160.c、lcd_spi_st7735_128x128.c、lcd_spi_st7789_BOE1.54_update_240x240.c、lcd_pap_st7789_240x240.c、lcd_pap_st7789_240x320.c(均在 sdk/apps/common/lcd/)
  • 兄弟主题:段码屏驱动 sdk/cpu/cd09/segment_code_lcd_drv.c、sdk/cpu/demo/segment_code_lcd_demo.c
  • 字库生成工具:doc/软件工具/unicode字库工具/FontTool.exe 与 font.xml
Next
UI 平台与控件绘制