ARM(14) - LCD(1)清屏和画图形

一、学习目标

  1. 理解 LCD 显示的核心指标与工作原理
  2. 掌握 I.MX6ULL 的 LCD 控制器(eLCDIF)硬件架构
  3. 熟练完成 I.MX6ULL LCD 裸机驱动开发(IO 配置、时钟配置、时序配置、显示控制)
  4. 实现 LCD 基础显示功能(清屏、颜色切换)

二、LCD 核心指标回顾(基础认知)

在进行硬件开发前,需先明确 LCD 的关键参数,这些参数直接决定驱动配置逻辑:

指标类型 核心参数 含义与细节
分辨率 1K/720p 1280×720(水平 1280 像素,垂直 720 像素),"1K" 为行业习惯称呼(非精确 1000)
2K/1080p 1920×1080(主流高清分辨率)
4K/2160p 4096×2160(超高清分辨率,裸机开发中较少用,需更高带宽)
帧率 / 刷新率 fps(帧 / 秒) 每秒显示的画面数量,决定动态显示流畅度:- 30fps:基础静态显示- 60fps:主流动态显示(视频、UI 交互)- 120/144fps:高刷场景(游戏,裸机开发中极少用)
色域 RGB 加色模型 屏幕可显示的颜色范围,裸机开发常用RGB888格式:- 每个颜色通道(R/G/B)8 位深度(0~255)- 总颜色数:2⁸×2⁸×2⁸=16777216(约 1600 万色)
ARGB 色彩模型 在 RGB 基础上增加 8 位不透明度(A 通道),总颜色数:2⁸×2⁸×2⁸×2⁸=4294967296(约 43 亿色),裸机 LCD 显示中 A 通道可忽略(默认不透明)

三、I.MX6ULL LCD 硬件基础

1. I.MX6ULL 的 LCD 控制器:eLCDIF

I.MX6ULL 集成增强型 LCD 接口(eLCDIF),是连接 LCD 与 CPU 的核心模块,主要功能:

  • 解析 CPU 下发的显示数据(帧缓冲区)
  • 生成 LCD 所需的时序信号(HSYNC、VSYNC、DOTCLK)
  • 控制数据传输速率(匹配 LCD 刷新率)
  • 支持多种像素格式(RGB565、RGB888 等)

2. 硬件连接核心引脚

I.MX6ULL 与 LCD 的连接分为数据引脚控制引脚,对应驱动代码中的 IO 配置:

引脚类型 数量 / 功能 I.MX6ULL 引脚复用功能 作用
数据引脚 24 位(D00~D23) IOMUXC_LCD_DATAxx_LCDIF_DATAxx 传输 RGB888 格式的颜色数据(R8+G8+B8)
控制引脚 LCD_ENABLE IOMUXC_LCD_ENABLE_LCDIF_ENABLE LCD 使能信号(高电平有效,控制屏幕是否显示)
LCD_CLK IOMUXC_LCD_CLK_LCDIF_CLK 像素时钟(DOTCLK),决定每像素的传输时间
LCD_HSYNC IOMUXC_LCD_HSYNC_LCDIF_HSYNC 水平同步信号(行同步),标记一行数据的开始 / 结束
LCD_VSYNC IOMUXC_LCD_VSYNC_LCDIF_VSYNC 垂直同步信号(场同步),标记一帧数据的开始 / 结束
eg. RGB565 的核心:位深分配逻辑 是一种16 位 RGB 色彩模式

"565" 代表将 16 个二进制位(总位深 16 位)按以下比例分配给 R、G、B 三个通道:

  • R(红色):5 位 → 二进制位数为 5,可表示的数值范围是 0~31(共 2^5 = 32 种亮度等级);
  • G(绿色):6 位 → 二进制位数为 6,可表示的数值范围是 0~63(共 2^6 = 64 种亮度等级);
  • B(蓝色):5 位 → 与红色一致,数值范围 0~31(32 种亮度等级)。
关键设计原因:人眼对绿色更敏感

之所以给绿色多 1 位(6 位),是因为人眼视网膜对绿色光的感知能力最强(含更多对绿光敏感的视锥细胞)。在总位深有限(16 位)的情况下,给绿色分配更多位数,能让颜色过渡更自然,视觉上减少 "色阶断层"(颜色突变的卡顿感),比平均分配(如 RGB555)的效果更好。

四、时钟与寄存器配置

1、时钟

1.配置对应引脚的功能
2. 配置LCD的时钟
3. 配置LCD参数----行场同步等等

1. 行同步(HSYNC):水平扫描的「指挥官」
  • 作用 :控制电子束(CRT)或像素刷新(LCD)在水平方向的移动,标记单行像素的开始与结束
  • 工作机制
    • 每行扫描结束后,HSYNC 信号触发电子束从屏幕右侧「回扫」到左侧起始位置,同时进入水平消隐期(不显示内容)。
    • 一个完整的行扫描周期包括:同步脉冲(Sync)、后沿(Back Porch)、有效图像(Addressable Video)、前沿(Front Porch)等阶段,单位为像素时钟周期。
  • 频率计算
  • 例如,640×480 分辨率下,水平总像素数为 800(含消隐),像素时钟 25MHz 时,行频约 31.5kHz。
2. 场同步(VSYNC):垂直刷新的「触发器」
  • 作用 :控制电子束或像素刷新在垂直方向的移动,标记整帧图像的开始与结束
  • 工作机制
    • 所有行扫描完成后,VSYNC 信号触发电子束从屏幕右下角「回扫」到左上角,同时进入垂直消隐期(不显示内容)。
    • 一个完整的场扫描周期包括:同步脉冲、后沿、有效图像、前沿等阶段,单位为行周期。
  • 频率计算
  • 例如,640×480 分辨率下,垂直总像素数为 525(含消隐),行频 31.5kHz 时,场频约 60Hz。

2、寄存器配置

以上参照 IMAX6ULL裸机驱动 手册 进行配置
以下是直接查询手册所得

1. 配置引脚功能

24根数据线 + DE / VSYNC / HSYNC / PCLK

(RGB:2^8 * 2^8 * 2^8,即3*8 = 24根)

电气配置为1011 1001 = 0xB9

2. 配置LCD时钟
1. 配置分频器

将24M配置为31MHz的时钟

24 * 31 = 744

即倍频至744M,然后使两个分频器相乘为24即可配置成功

CSCDR2② 和 CDCMR③ 均可配置到 8 (divide by)
① CSCDR2[LCDIF1_PRE_CLK_SEL] 选择PLL5, 配置为010

② CSCDR2[LCDIF1_PRED] 设置为 4 分频, 配置为011

③ CBCMR[CLDIF1_PORE] 设置为 6 分频, 配置为101

④ CSCDR2[LCDIF1_CLK_SEL] 选择CLDIF1 clock, 配置为000

2. 时钟计算
以下配置 LCD 寄存器(不完全)

五、函数详解与注释

------- 主函数 -----

1. LCD IO 初始化函数:lcd_io_init(void)

功能 :配置 LCD 数据引脚和控制引脚的复用功能 (复用为 LCDIF 外设)和电气属性(驱动能力、信号稳定性),是 LCD 硬件初始化的第一步。

cpp 复制代码
void lcd_io_init(void)
{
    // -------------------------- 1. 配置LCD数据引脚(24位)复用为LCDIF功能 --------------------------
    // IMX6引脚需通过IOMUXC配置复用:第二个参数=0表示禁用GPIO功能,直接复用为指定外设(LCDIF)
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0); // 数据引脚0 → LCDIF_DATA00
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0); // 数据引脚1 → LCDIF_DATA01
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA02_LCDIF_DATA02, 0); // 数据引脚2 → LCDIF_DATA02
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA03_LCDIF_DATA03, 0); // 数据引脚3 → LCDIF_DATA03
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA04_LCDIF_DATA04, 0); // 数据引脚4 → LCDIF_DATA04
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA05_LCDIF_DATA05, 0); // 数据引脚5 → LCDIF_DATA05
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA06_LCDIF_DATA06, 0); // 数据引脚6 → LCDIF_DATA06
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA07_LCDIF_DATA07, 0); // 数据引脚7 → LCDIF_DATA07
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA08_LCDIF_DATA08, 0); // 数据引脚8 → LCDIF_DATA08
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA09_LCDIF_DATA09, 0); // 数据引脚9 → LCDIF_DATA09
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA10_LCDIF_DATA10, 0); // 数据引脚10 → LCDIF_DATA10
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA11_LCDIF_DATA11, 0); // 数据引脚11 → LCDIF_DATA11
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA12_LCDIF_DATA12, 0); // 数据引脚12 → LCDIF_DATA12
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA13_LCDIF_DATA13, 0); // 数据引脚13 → LCDIF_DATA13
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA14_LCDIF_DATA14, 0); // 数据引脚14 → LCDIF_DATA14
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA15_LCDIF_DATA15, 0); // 数据引脚15 → LCDIF_DATA15
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA16_LCDIF_DATA16, 0); // 数据引脚16 → LCDIF_DATA16
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA17_LCDIF_DATA17, 0); // 数据引脚17 → LCDIF_DATA17
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA18_LCDIF_DATA18, 0); // 数据引脚18 → LCDIF_DATA18
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA19_LCDIF_DATA19, 0); // 数据引脚19 → LCDIF_DATA19
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA20_LCDIF_DATA20, 0); // 数据引脚20 → LCDIF_DATA20
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA21_LCDIF_DATA21, 0); // 数据引脚21 → LCDIF_DATA21
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA22_LCDIF_DATA22, 0); // 数据引脚22 → LCDIF_DATA22
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_LCDIF_DATA23, 0); // 数据引脚23 → LCDIF_DATA23

    // -------------------------- 2. 配置LCD控制引脚复用为LCDIF功能 --------------------------
    IOMUXC_SetPinMux(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0); // LCD使能引脚 → LCDIF_ENABLE(控制LCD显示开关)
    IOMUXC_SetPinMux(IOMUXC_LCD_CLK_LCDIF_CLK, 0);       // LCD像素时钟 → LCDIF_CLK(同步数据传输)
    IOMUXC_SetPinMux(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0);   // 水平同步引脚 → LCDIF_HSYNC(行切换同步)
    IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0);   // 垂直同步引脚 → LCDIF_VSYNC(帧切换同步)

    // -------------------------- 3. 配置所有LCD引脚的电气属性 --------------------------
    // 0xB9(二进制10111001):配置引脚驱动能力、 slew rate(信号边沿速度)、上下拉
    // 含义:高驱动能力(适应LCD长排线)、快速slew rate(匹配像素时钟)、无上下拉(LCD信号为推挽输出)
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00  , 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA02_LCDIF_DATA02	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA03_LCDIF_DATA03	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA04_LCDIF_DATA04	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA05_LCDIF_DATA05	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA06_LCDIF_DATA06	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA07_LCDIF_DATA07	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA08_LCDIF_DATA08	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA09_LCDIF_DATA09	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA10_LCDIF_DATA10	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA11_LCDIF_DATA11	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA12_LCDIF_DATA12	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA13_LCDIF_DATA13	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA14_LCDIF_DATA14	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA15_LCDIF_DATA15	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA16_LCDIF_DATA16	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA17_LCDIF_DATA17	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA18_LCDIF_DATA18	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA19_LCDIF_DATA19	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA20_LCDIF_DATA20	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA21_LCDIF_DATA21	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA22_LCDIF_DATA22	, 0xB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA23_LCDIF_DATA23	, 0xB9);

    // 控制引脚同样配置为0xB9,确保时序信号稳定
    IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0XB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_CLK_LCDIF_CLK, 0XB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0XB9);
    IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0XB9);
}

2. LCD 时钟初始化函数:lcd_clk_init(void)

功能:配置 LCDIF 的时钟源(PLL_VIDEO)及分频系数,确保 LCD 像素时钟(DOTCLK)符合 LCD 屏的时序要求(如 800x480@60Hz 需~27MHz 时钟)。

IMX6 时钟路径:PLL_VIDEO(视频专用 PLL)→ 分频器 → LCDIF 时钟。

cpp 复制代码
void lcd_clk_init(void)
{
    unsigned int t; // 临时变量,用于寄存器读写(避免直接多次操作寄存器)

    // -------------------------- 1. 暂存LCDIF时钟源配置(CSCDR2) --------------------------
    // CCM(时钟控制模块)-> CSCDR2(时钟状态控制寄存器2):控制LCDIF时钟源选择
    t = CCM->CSCDR2;
    t &= ~(0x7<<15); // 清除bit15~17(LCDIF_CLK_SEL):选择时钟源前先清零
    t |= (0x4<<15);  // 设置bit15~17=0100:暂选备用时钟(避免配置PLL时时钟异常)
    CCM->CSCDR2 = t;

    // -------------------------- 2. 配置PLL_VIDEO(LCDIF的核心时钟源) --------------------------
    CCM_ANALOG->PLL_VIDEO &= ~(1 << 16); // 清除bit16(PLL_VIDEO_POWERDOWN):唤醒PLL
    CCM_ANALOG->PLL_VIDEO &= ~(1 << 12); // 清除bit12(PLL_VIDEO_BYPASS):禁用PLL旁路(使用PLL输出)

    // 设置PLL_VIDEO的倍频系数(NUM/denom):PLL输出 = 输入时钟 * (NUM + 1) / denom
    // 此处NUM=0、denom=1 → 倍频系数=1(默认输入时钟为24MHz晶振,暂不倍频)
    CCM_ANALOG->PLL_VIDEO_NUM = 0;    // PLL倍频分子(+1后生效)
    CCM_ANALOG->PLL_VIDEO_DENOM = 1;  // PLL倍频分母

    // 配置PLL_VIDEO后分频(POST_DIV)和锁定参数
    t = CCM_ANALOG->PLL_VIDEO;
    t |= (0x2 << 19); // bit19~20(POST_DIV_SELECT)=010:PLL输出后分频系数=3
    t |= (1 << 13);   // bit13(PLL_VIDEO_LOCK_EN)=1:使能PLL锁定检测
    t &= ~(0x7F << 0); // 清除bit0~6(PLL_VIDEO_FRAC):禁用小数分频(整数分频模式)
    t |= (31 << 0);    // bit0~6=31:配置PLL环路滤波器(确保锁定稳定性)
    CCM_ANALOG->PLL_VIDEO = t;

    // 等待PLL_VIDEO锁定:bit31(PLL_VIDEO_LOCK)=1表示PLL稳定
    while((CCM_ANALOG->PLL_VIDEO & (1 << 31)) == 0);

    // -------------------------- 3. 配置LCDIF最终时钟分频 --------------------------
    CCM->CSCDR2 &= ~(0x7 << 15);          // 清除时钟源选择位
    CCM->CSCDR2 |= (0x2 << 15) | (0x3 << 12); // 配置:
                                            // bit15~17=0010 → 时钟源=PLL_VIDEO
                                            // bit12~14=011 → LCDIF时钟4分频(PLL输出/4)
    // 配置LCDIF时钟父时钟选择(CBCMR):bit23~25=0101 → 选择PLL_VIDEO作为父时钟
    CCM->CBCMR |= (0x5 << 23);
    // 清除LCDIF额外分频(CSCDR2 bit9~11):禁用额外分频,确保时钟仅受上述4分频控制
    CCM->CSCDR2 &= ~(0x7 << 9);
}

3. LCD 主初始化函数:lcd_init(void)

功能:整合 IO、时钟初始化,配置 LCD 核心参数(分辨率、时序)和 LCDIF 控制器,是 LCD 驱动的入口函数。

cpp 复制代码
// 全局变量:存储LCD关键参数(分辨率、时序、帧缓冲区地址等)
lcd_param_t lcd_info;

void lcd_init(void)
{
    // -------------------------- 1. 初始化IO和时钟 --------------------------
    lcd_io_init();  // 初始化LCD引脚(复用+电气属性)
    lcd_clk_init(); // 初始化LCDIF时钟

    // -------------------------- 2. 设置LCD核心参数(需与LCD屏 datasheet匹配) --------------------------
    lcd_info.width = 800;          // LCD水平分辨率(像素)
    lcd_info.height = 480;         // LCD垂直分辨率(像素)
    lcd_info.hbp = 88;             // 水平后沿(HSYNC后到有效数据的像素数)
    lcd_info.hfp = 40;             // 水平前沿(有效数据后到下一个HSYNC的像素数)
    lcd_info.hspw = 48;            // 水平同步脉宽(HSYNC信号的有效宽度,像素数)
    lcd_info.vbp = 32;             // 垂直后沿(VSYNC后到有效数据的行数)
    lcd_info.vfp = 13;             // 垂直前沿(有效数据后到下一个VSYNC的行数)
    lcd_info.vspw = 3;             // 垂直同步脉宽(VSYNC信号的有效宽度,行数)
    lcd_info.byte_per_pix = 4;     // 每个像素的字节数(ARGB8888格式,4字节)
    lcd_info.cur_frame_addr = FRAME_ADDR; // 当前帧缓冲区地址(定义在SDRAM中,需足够大:800*480*4=1.5MB)

    // -------------------------- 3. 配置LCDIF控制器寄存器 --------------------------
    // -------------------------- 3.1 核心控制寄存器(CTRL):配置工作模式 --------------------------
    LCDIF->CTRL |= (1 << 31);  // bit31(SOFT_RESET)=1:LCDIF软复位(初始化控制器状态)
    delay_ms(10);              // 延时10ms,等待复位完成
    LCDIF->CTRL &= ~(0x3 << 30); // 清除bit30~31(RESET状态):退出复位
    // 配置CTRL关键位:
    LCDIF->CTRL |= (1 << 19)    // bit19(DATA_FORMAT_SEL)=1:使用ARGB格式(而非RGB)
                | (1 << 17)    // bit17(LCDIF_ENABLE)=1:使能LCDIF数据传输
                | (0x3 << 10)  // bit10~11(PIXEL_FORMAT)=11:像素格式=ARGB8888(32位)
                | (0x3 << 8)   // bit8~9(DATA_WIDTH)=11:数据总线宽度=32位(匹配24位数据+8位控制)
                | (1 << 5);    // bit5(BURST_MODE)=1:使用突发传输(提高效率,适配SDRAM)

    // -------------------------- 3.2 数据有效字节配置(CTRL1) --------------------------
    LCDIF->CTRL1 &= ~(0xf << 16); // 清除bit16~19(VALID_DATA_MASK):先清零
    LCDIF->CTRL1 |= (0x7 << 16);  // bit16~18=111:32位总线中低24位为有效数据(LCD为24位RGB)

    // -------------------------- 3.3 传输像素数配置(TRANSFER_COUNT) --------------------------
    // 高16位:垂直方向传输行数(height);低16位:水平方向传输像素数(width)
    LCDIF->TRANSFER_COUNT = (lcd_info.height << 16) | (lcd_info.width);

    // -------------------------- 3.4 垂直同步控制0(VDCTRL0):同步信号极性 --------------------------
    LCDIF->VDCTRL0 |= (1<<28)    // bit28(HSYNC_POL)=1:HSYNC高电平有效
                    | (1 <<24)   // bit24(VSYNC_POL)=1:VSYNC高电平有效
                    | (1<<21)    // bit21(DOTCLK_POL)=1:DOTCLK上升沿采样数据
                    | (1<< 20)   // bit20(ENABLE_POL)=1:LCD_ENABLE高电平显示
                    | (0x3<< 0); // bit0~1(PERIOD_UNIT)=11:时序参数单位=像素(水平)/行(垂直)

    // -------------------------- 3.5 垂直同步控制1(VDCTRL1):垂直总周期 --------------------------
    // 垂直总周期 = 有效行数(height) + 垂直同步脉宽(vspw) + 垂直前沿(vfp) + 垂直后沿(vbp)
    LCDIF->VDCTRL1 = lcd_info.height + lcd_info.vspw + lcd_info.vfp + lcd_info.vbp;

    // -------------------------- 3.6 垂直同步控制2(VDCTRL2):水平总周期+垂直同步脉宽 --------------------------
    // 高16位:垂直同步脉宽(vspw);低16位:水平总周期(有效像素+hspw+hfp+hbp)
    LCDIF->VDCTRL2 = (lcd_info.vspw << 18) | (lcd_info.width + lcd_info.hspw + lcd_info.hfp + lcd_info.hbp);

    // -------------------------- 3.7 垂直同步控制3(VDCTRL3):同步后沿配置 --------------------------
    // 高16位:垂直同步后沿(vspw + vbp);低16位:水平同步后沿(hspw + hbp)
    // 作用:同步信号后多久开始传输有效数据
    LCDIF->VDCTRL3 |= (lcd_info.hspw + lcd_info.hbp) << 16 | (lcd_info.vspw + lcd_info.vbp);

    // -------------------------- 3.8 垂直同步控制4(VDCTRL4):有效像素配置 --------------------------
    LCDIF->VDCTRL4 |= (1 << 18)    // bit18(SYNC_SIGNALS_EN)=1:使能HSYNC/VSYNC信号输出
                    | (lcd_info.width << 0); // bit0~15:水平有效像素数(width)

    // -------------------------- 3.9 配置帧缓冲区地址 --------------------------
    LCDIF->CUR_BUF = lcd_info.cur_frame_addr;  // 当前帧缓冲区地址(LCDIF读取数据的地址)
    LCDIF->NEXT_BUF = lcd_info.cur_frame_addr; // 下一帧缓冲区地址(双缓冲用,此处单缓冲暂设为同一地址)

    // -------------------------- 3.10 使能LCDIF控制器 --------------------------
    LCDIF->CTRL |= (1 << 0); // bit0(LCDIF_MASTER_EN)=1:使能LCDIF主控制器,开始数据传输
}

4. LCD 清屏函数:lcd_clear(unsigned int color)

功能 :填充 LCD 屏幕像素,实现清屏效果(特殊逻辑:上半屏用指定颜色,下半屏用青色0xffff)。

cpp 复制代码
void lcd_clear(unsigned int color)
{
    int i = 0; // 水平方向像素索引(x轴)
    int j = 0; // 垂直方向像素索引(y轴)

    // 遍历所有像素(y从0到height-1,x从0到width-1)
    for(j = 0; j < lcd_info.height; j++)
    {
        for(i = 0; i < lcd_info.width; i++)
        {
            // 逻辑:上半屏(j < 高度/2)用指定颜色,下半屏用0xffff(ARGB8888=0x0000FFFF,青色)
            if(j < (lcd_info.height / 2))
                draw_point(i, j, color); // 调用画点函数填充上半屏
            else
                draw_point(i, j, 0xffff); // 填充下半屏为青色
        }
    }
}

5. 基础绘图函数

5.1 画点函数:draw_point(unsigned int x, unsigned int y, unsigned int color)

功能 :在指定坐标(x,y)绘制一个像素,是所有绘图函数的基础。

cpp 复制代码
// -------------------------- 画点函数 --------------------------
// 功能:在(x,y)坐标绘制指定颜色的点(ARGB8888格式)
// 参数:x-水平坐标(0~width-1),y-垂直坐标(0~height-1),color-颜色(0xRRGGBBAA或0xAARRGGBB,取决于格式)
void draw_point(unsigned int x, unsigned int y, unsigned int color)
{
    unsigned int *p;  // 像素地址指针(指向帧缓冲区中该像素的内存地址)

    // -------------------------- 坐标越界检查(注释待启用,避免内存访问错误) --------------------------
    // if (x >= lcd_info.width || y >= lcd_info.height)
    // {
    //     return; // 坐标超出屏幕范围,直接返回
    // }

    // -------------------------- 计算像素的内存地址 --------------------------
    // 公式:帧缓冲区基地址 + (y行号 * 每行像素数 + x列号) * 每个像素字节数
    // 例:(x=100,y=50) → 地址=FRAME_ADDR + (50*800 + 100)*4
    p = (unsigned int *)(lcd_info.cur_frame_addr + (y * lcd_info.width + x) * lcd_info.byte_per_pix);

    // -------------------------- 写入颜色值 --------------------------
    *p = color; // 将颜色值写入帧缓冲区,LCDIF会自动读取并显示
}
5.2 画横线函数:draw_h_line(unsigned int x0, unsigned int y0, unsigned int len, unsigned int color)

功能 :从(x0,y0)开始,绘制长度为len的水平直线(固定 y,x 递增)。

cpp 复制代码
void draw_h_line(unsigned int x0, unsigned int y0, unsigned int len, unsigned int color)
{
    int i = 0;
    // 循环绘制len个像素:x从x0到x0+len-1,y固定为y0
    for(i = 0; i < len; i++)
    {
        draw_point(x0 + i, y0, color);
    }
}
5.3 画竖线函数:draw_v_line(unsigned int x0, unsigned int y0, unsigned int len, unsigned int color)

功能 :从(x0,y0)开始,绘制长度为len的垂直直线(固定 x,y 递增)。

cpp 复制代码
void draw_v_line(unsigned int x0, unsigned int y0, unsigned int len, unsigned int color)
{
    int i = 0;
    // 循环绘制len个像素:y从y0到y0+len-1,x固定为x0
    for(i = 0; i < len; i++)
    {
        draw_point(x0, y0 + i, color);
    }
}
5.4 画矩形框函数:draw_recv(unsigned int x0, unsigned int y0, unsigned int w, unsigned int h, unsigned int color)

功能 :绘制一个左上角为(x0,y0)、宽度为w、高度为h的矩形边框(仅画四条边)。

cpp 复制代码
void draw_recv(unsigned int x0, unsigned int y0, unsigned int w, unsigned int h, unsigned int color)
{
    draw_h_line(x0, y0, w, color);       // 上边框:(x0,y0) → (x0+w,y0)
    draw_h_line(x0, y0 + h, w, color);   // 下边框:(x0,y0+h) → (x0+w,y0+h)
    draw_v_line(x0, y0, h, color);       // 左边框:(x0,y0) → (x0,y0+h)
    draw_v_line(x0 + w, y0, h, color);   // 右边框:(x0+w,y0) → (x0+w,y0+h)
}
5.5 填充矩形函数:draw_fill_recv(unsigned int x0, unsigned int y0, unsigned int w, unsigned int h, unsigned int color)

功能 :绘制一个左上角为(x0,y0)、宽度为w、高度为h的实心矩形(填充所有像素)。

cpp 复制代码
void draw_fill_recv(unsigned int x0, unsigned int y0, unsigned int w, unsigned int h, unsigned int color)
{
    int i = 0;
    // 循环绘制h行横线:y从y0到y0+h-1,每行宽度为w
    for(i = 0; i < h; i++)
    {
        draw_h_line(x0, y0 + i, w, color);
    }
}
相关推荐
szxinmai主板定制专家5 小时前
基于RK3588与ZYNQ7045的ARM+FPGA+AI实时系统解决方案
arm开发·人工智能·嵌入式硬件·fpga开发
一起搞IT吧9 天前
嵌入式ARM SOC开发中文专题分享一:ARM SOC外围资源介绍
arm开发·嵌入式硬件
研华嵌入式9 天前
如何在高通跃龙QCS6490 Arm架构上使用Windows 11 IoT企业版?
arm开发·windows·嵌入式硬件
优雅鹅9 天前
ARM、AArch64、amd64、x86_64、x86有什么区别?
arm开发·学习
sheepwjl9 天前
《嵌入式硬件(十二):基于IMX6ULL的时钟操作》
汇编·arm开发·单片机·嵌入式硬件·时钟·.s编译
我菜就多练10 天前
ARM-汇编的基础知识
汇编·arm开发
carysu10 天前
交换机协议栈FRR中使用
arm开发
Aczone2810 天前
硬件(十)IMX6ULL 中断与时钟配置
arm开发·单片机·嵌入式硬件·fpga开发
m0_6203551910 天前
sqlite3移植和使用(移植到arm上)
arm开发