欢迎光临
我们一直在努力

ARM之lcd与pwm

一、LCD 显示核心底层:行场同步时序深度解析

1. 行场同步的物理本质与作用

LCD 显示依赖 “逐行扫描 + 逐帧刷新” 的工作机制,行场同步信号(HSYNC/VSYNC)是协调扫描节奏的核心:

  • HSYNC(行同步信号):标记一行像素的起始与结束,确保每行数据按顺序传输,避免行与行之间的数据错位。
  • VSYNC(帧同步信号):标记一帧图像的起始与结束,决定画面刷新频率(如 60Hz),避免帧撕裂。
  • 若时序参数不匹配 LCD 手册要求,会出现花屏、闪烁、画面偏移等问题,因此必须严格遵循硬件规格。

2. 行场同步时序关键参数与计算逻辑

LCD 扫描分为 “有效显示区” 和 “同步信号区”,核心参数需结合 LCD 手册精准配置(以正点原子 ATK4384 为例,800×480 分辨率):

参数定义取值(ATK4384)单位
HSPW 行同步脉冲宽度(HSYNC 信号持续时间) 48 像素时钟周期(tCLK)
HBP 行同步后肩(HSYNC 结束到有效像素开始的间隔,稳定数据采样) 88 tCLK
HFP 行同步前肩(有效像素结束到下一行 HSYNC 开始的间隔,电路恢复) 40 tCLK
HOZVAL 水平有效像素数(LCD 宽度) 800 像素
VSPW 帧同步脉冲宽度(VSYNC 信号持续时间) 3 行周期(th)
VBP 帧同步后肩(VSYNC 结束到有效行开始的间隔) 32 th
VFP 帧同步前肩(有效行结束到下一页 VSYNC 开始的间隔) 13 th
LINE 垂直有效像素数(LCD 高度) 480
像素时钟 决定扫描速度,计算公式:1/[(HSPW+HBP+HOZVAL+HFP)×(VSPW+VBP+LINE+VFP)× 刷新率] 31 MHz

关键计算示例:

  • 单行扫描总周期 = HSPW + HBP + HOZVAL + HFP = 48+88+800+40=976(tCLK)
  • 一帧扫描总周期 = (VSPW+VBP+LINE+VFP)× 单行周期 = (3+32+480+13)×976=528×976=515,328(tCLK)
  • 像素时钟 = 一帧总周期 × 刷新率 = 515328×60≈30.9MHz,取标准值 31MHz。

3. 行场同步时序工作流程(含硬件时序图逻辑)

(1)行扫描时序(从左到右)
  • 输出 HSYNC 信号(低电平有效),持续 HSPW 个像素时钟周期(ATK4384 为 48 个 tCLK)。
  • HSPW 结束后进入 HBP 阶段,等待 HBP 个时钟周期(88 个 tCLK),确保数据总线稳定,避免采样错误。
  • 传输该行有效像素数据(800 个像素),每个像素对应 1 个像素时钟,数据通过 RGB 数据线写入 LCD 控制器。
  • 有效像素传输完成后进入 HFP 阶段,等待 HFP 个时钟周期(40 个 tCLK),为下一行扫描的电路状态恢复做准备。
  • (2)帧扫描时序(从上到下)
  • 重复行扫描流程,直至完成 LINE 行有效像素(480 行)。
  • 所有有效行传输完成后,输出 VSYNC 信号(低电平有效),持续 VSPW 个行周期(3 个 th)。
  • VSPW 结束后进入 VBP 阶段,等待 VBP 个行周期(32 个 th),稳定帧同步信号。
  • 进入 VFP 阶段,等待 VFP 个行周期(13 个 th),为下一帧扫描做准备。
  • 一帧刷新完成,触发下一轮帧扫描,形成连续显示。
  • 二、LCD 驱动完整实现

    1. 核心功能升级说明

    lcd.c 实现了 LCD 引脚复用、像素时钟配置、行场同步时序精准配置、Framebuffer 绑定及基础绘图功能,补充了 像素时钟校准、时序参数动态适配 等细节,适配 800×480 分辨率、ARGB8888 像素格式(4 字节 / 像素),基于 I.MX6ULL 的 eLCDIF 控制器开发。

    2. 完整优化代码实现

    #include "lcd.h"
    #include "MCIMX6Y2.h"
    #include "fsl_iomuxc.h"
    #include "gpt.h"
    #include "stdio.h"

    // 帧缓冲地址定义(DDR中分配,需确保在DDR地址范围内:0x80000000~0x9FFFFFFF)
    #define _FRAME_RAM_ADDRESS 0x89000000
    // LCD设备结构体(补充时序参数、像素时钟等细节)
    struct tftlcd_t {
    unsigned short width; // 屏幕宽度(像素)
    unsigned short height; // 屏幕高度(像素)
    unsigned char pix_size; // 每个像素占用字节数(ARGB8888为4)
    // 行同步时序参数
    unsigned short hspw; // 行同步脉冲宽度
    unsigned short hbp; // 行同步后肩
    unsigned short hfp; // 行同步前肩
    // 帧同步时序参数
    unsigned short vspw; // 帧同步脉冲宽度
    unsigned short vbp; // 帧同步后肩
    unsigned short vfp; // 帧同步前肩
    unsigned int frame_addr; // 帧缓冲地址(物理地址)
    unsigned int fore_color; // 前景色(默认红色:0x00FF0000)
    unsigned int back_color; // 背景色(默认白色:0x00FFFFFF)
    unsigned int pixel_clk; // 像素时钟频率(MHz)
    };

    struct tftlcd_t lcd_dev;

    /**
    * @brief LCD引脚初始化(复用功能+电气特性配置,补充抗干扰配置)
    */
    void lcd_pad_init(void) {
    // 1. 配置LCD数据线(24根)复用为LCDIF功能
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA02_LCDIF_DATA02, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA03_LCDIF_DATA03, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA04_LCDIF_DATA04, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA05_LCDIF_DATA05, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA06_LCDIF_DATA06, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA07_LCDIF_DATA07, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA08_LCDIF_DATA08, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA09_LCDIF_DATA09, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA10_LCDIF_DATA10, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA11_LCDIF_DATA11, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA12_LCDIF_DATA12, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA13_LCDIF_DATA13, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA14_LCDIF_DATA14, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA15_LCDIF_DATA15, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA16_LCDIF_DATA16, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA17_LCDIF_DATA17, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA18_LCDIF_DATA18, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA19_LCDIF_DATA19, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA20_LCDIF_DATA20, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA21_LCDIF_DATA21, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA22_LCDIF_DATA22, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_LCDIF_DATA23, 0);

    // 2. 配置LCD控制信号(CLK/HSYNC/VSYNC/ENABLE)复用为LCDIF功能
    IOMUXC_SetPinMux(IOMUXC_LCD_CLK_LCDIF_CLK, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0);
    IOMUXC_SetPinMux(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0);

    // 3. 电气特性配置:关闭HYS、上拉使能、100MHz速度、R0/7驱动能力(抗干扰优化)
    uint32_t pin_config = 0xB9; // 0xB9 = 0b10111001:上拉+100MHz+R0/7驱动
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA02_LCDIF_DATA02, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA03_LCDIF_DATA03, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA04_LCDIF_DATA04, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA05_LCDIF_DATA05, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA06_LCDIF_DATA06, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA07_LCDIF_DATA07, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA08_LCDIF_DATA08, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA09_LCDIF_DATA09, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA10_LCDIF_DATA10, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA11_LCDIF_DATA11, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA12_LCDIF_DATA12, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA13_LCDIF_DATA13, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA14_LCDIF_DATA14, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA15_LCDIF_DATA15, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA16_LCDIF_DATA16, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA17_LCDIF_DATA17, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA18_LCDIF_DATA18, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA19_LCDIF_DATA19, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA20_LCDIF_DATA20, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA21_LCDIF_DATA21, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA22_LCDIF_DATA22, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_DATA23_LCDIF_DATA23, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_CLK_LCDIF_CLK, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, pin_config);
    IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, pin_config);
    }

    /**
    * @brief LCD像素时钟初始化(精准配置PLL5,补充时钟树校准)
    */
    void lcd_clk_init(void) {
    // 1. 配置PLL5(VIDEO PLL):输入24MHz晶振,输出744MHz(用于生成31MHz像素时钟)
    CCM_ANALOG->PLL_VIDEO_NUM = 0; // 分子为0
    CCM_ANALOG->PLL_VIDEO_DENOM = 1; // 分母为1
    unsigned int t = CCM_ANALOG->PLL_VIDEO;
    t &= ~(3 << 19); // 清除POST_DIV_SEL(1分频)
    t |= (2 << 19);
    t &= ~(0x7F << 0); // 清除DIV_SELECT
    t |= (31 << 0); // DIV_SELECT=31,PLL5输出=24*(31+0/1)=744MHz
    CCM_ANALOG->PLL_VIDEO = t;
    CCM_ANALOG->PLL_VIDEO |= (1 << 13); // 使能PLL5

    // 等待PLL5锁定(确保时钟稳定,避免花屏)
    while((CCM_ANALOG->PLL_VIDEO & (1 << 31)) == 0);
    printf("LCD PLL5 locked successfully! Pixel Clock: 31MHz\\n");

    // 2. 配置LCDIF时钟路径:PLL5 → 24分频 → 31MHz
    CCM_ANALOG->MISC2 &= ~(3 << 30); // VIDEO_DIV=1分频
    t = CCM->CSCDR2;
    t |= (2 << 15); // LCDIF1_PRE_CLK_SEL=PLL5
    t |= (3 << 12); // LCDIF1_PRED=4分频(744MHz→186MHz)
    t &= ~(7 << 9); // LCDIF1_CLK_SEL=预分频后时钟
    CCM->CSCDR2 = t;
    CCM->CBCMR &= ~(7 << 23); // LCDIF1_PODF=6分频(186MHz→31MHz)
    CCM->CBCMR |= (5 << 23);

    lcd_dev.pixel_clk = 31; // 记录像素时钟频率
    }

    /**
    * @brief LCD模块复位(软复位+延时稳定,补充复位时序)
    */
    void reset_lcd(void) {
    LCDIF->CTRL |= (1 << 31); // 软复位LCDIF控制器
    delay_ms(20); // 等待复位完成(手册要求≥10ms)
    LCDIF->CTRL &= ~(1 << 31);
    LCDIF->CTRL &= ~(1 << 30); // 关闭时钟门控,确保时钟正常输出
    }

    /**
    * @brief LCD初始化(引脚+时钟+时序+Framebuffer绑定,补充参数校验)
    */
    void lcd_init(void) {
    // 1. 引脚初始化
    lcd_pad_init();
    // 2. 模块复位
    reset_lcd();
    // 3. 像素时钟初始化
    lcd_clk_init();
    delay_ms(50); // 等待时钟与LCD模块稳定

    // 4. 配置LCD设备参数(ATK4384手册标准参数)
    lcd_dev.width = 800;
    lcd_dev.height = 480;
    lcd_dev.pix_size = 4;
    lcd_dev.hspw = 48;
    lcd_dev.hbp = 88;
    lcd_dev.hfp = 40;
    lcd_dev.vspw = 3;
    lcd_dev.vbp = 32;
    lcd_dev.vfp = 13;
    lcd_dev.frame_addr = _FRAME_RAM_ADDRESS;
    lcd_dev.back_color = 0x00FFFFFF;
    lcd_dev.fore_color = 0x00FF0000;

    // 5. 配置eLCDIF控制器(DOTCLK模式,适配RGB接口)
    LCDIF->CTRL |= (1 << 19) | (1 << 17) | (3 << 10) | (3 << 8) | (1 << 5);
    // CTRL配置:BYPASS_COUNT=1(DOTCLK模式必需)、DOTCLK_MODE=1、总线宽度24位、像素宽度24位、主模式
    LCDIF->CTRL1 &= ~(0xF << 16);
    LCDIF->CTRL1 |= (0x7 << 16); // 字节打包格式:0x7表示24位有效(ARGB8888)

    // 6. 配置分辨率
    LCDIF->TRANSFER_COUNT = (lcd_dev.height << 16) | (lcd_dev.width << 0);

    // 7. 绑定Framebuffer地址(当前帧+下一帧,支持双缓冲避免撕裂)
    LCDIF->CUR_BUF = lcd_dev.frame_addr;
    LCDIF->NEXT_BUF = lcd_dev.frame_addr; // 单缓冲模式,双缓冲可设置为不同地址

    // 8. 配置行场同步时序参数(严格匹配手册)
    LCDIF->VDCTRL0 = (1 << 28) | (1 << 24) | (1 << 21) | (1 << 20) | (lcd_dev.vspw << 0);
    // VDCTRL0:使能DE信号、DE高电平有效、VSYNC周期单位=行、VSYNC脉冲宽度单位=行、VSPW=3
    LCDIF->VDCTRL1 = lcd_dev.height + lcd_dev.vspw + lcd_dev.vbp + lcd_dev.vfp; // 垂直总周期
    LCDIF->VDCTRL2 = (lcd_dev.hspw << 18) | (lcd_dev.width + lcd_dev.hspw + lcd_dev.hbp + lcd_dev.hfp); // 水平总周期
    LCDIF->VDCTRL3 = ((lcd_dev.hspw + lcd_dev.hbp) << 16) | ((lcd_dev.vspw + lcd_dev.vbp) << 0); // 同步后肩+脉冲宽度
    LCDIF->VDCTRL4 = (1 << 18) | (lcd_dev.width << 0); // 使能同步信号、水平有效像素数

    // 9. 使能LCDIF控制器
    LCDIF->CTRL |= (1 << 0);
    delay_ms(50); // 等待LCD稳定显示

    // 10. 清屏(填充背景色,验证Framebuffer有效性)
    screen_clear(lcd_dev.back_color);
    }

    /**
    * @brief 在指定坐标绘制点(补充边界校验,避免越界访问)
    * @param x:横坐标(0~width-1)
    * @param y:纵坐标(0~height-1)
    * @param color:颜色值(ARGB8888格式)
    */
    void lcd_drawpoint(int x, int y, unsigned int color) {
    if (x < 0 || x >= lcd_dev.width || y < 0 || y >= lcd_dev.height) {
    printf("Draw point out of bounds! x:%d, y:%d\\n", x, y);
    return;
    }
    unsigned int *p = (unsigned int *)lcd_dev.frame_addr;
    *(p + lcd_dev.width * y + x) = color; // 直接操作Framebuffer物理地址
    }

    /**
    * @brief 清屏函数(优化填充效率,减少循环开销)
    * @param color:清屏颜色(ARGB8888格式)
    */
    void screen_clear(unsigned int color) {
    unsigned int *frame_buf = (unsigned int *)lcd_dev.frame_addr;
    unsigned int total_pixels = lcd_dev.width * lcd_dev.height;
    // 连续内存块填充,比嵌套循环效率高
    for (unsigned int i = 0; i < total_pixels; i++) {
    frame_buf[i] = color;
    }
    }

    3. 关键优化细节说明

    • 引脚电气特性:统一配置为上拉使能 + 100MHz 速度,增强信号抗干扰能力,避免传输过程中信号失真。
    • 像素时钟校准:等待 PLL5 锁定(PLL_VIDEO寄存器 bit31),确保时钟稳定,解决因时钟抖动导致的花屏问题。
    • 时序参数校验:严格按照 LCD 手册配置VDCTRL0~VDCTRL4寄存器,避免因参数错误导致的画面偏移。
    • 边界校验:lcd_drawpoint中添加坐标范围判断,防止越界访问 DDR 内存,导致程序崩溃。

    三、Framebuffer 深度解析:从物理内存到用户空间

    1. Framebuffer 核心工作机制

    Framebuffer 是 Linux 内核提供的虚拟设备驱动,本质是DDR 中一块连续的物理内存区域,与 LCD 的像素一一对应:

    • 地址映射:通过mmap函数将物理显存(如0x89000000)映射到用户空间,应用程序直接读写该内存即可更新 LCD 显示,无需操作底层寄存器。
    • 像素格式适配:内核自动处理 ARGB8888/RGB565 等格式与 LCD 硬件的适配,用户只需按格式写入颜色值。
    • 与行场同步的联动:LCD 控制器(eLCDIF)按照行场同步时序,从 Framebuffer 中逐行读取像素数据,实时刷新到 LCD 屏幕。

    2. Framebuffer 用户空间操作示例

    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <sys/ioctl.h>
    #include <linux/fb.h>

    int fb_fd;
    struct fb_var_screeninfo var;
    unsigned int *fb_buf; // 映射后的用户空间地址

    /**
    * @brief Framebuffer初始化(补充设备打开与参数获取)
    */
    int fb_init(void) {
    // 1. 打开Framebuffer设备
    fb_fd = open("/dev/fb0", O_RDWR);
    if (fb_fd < 0) {
    perror("open fb0 failed");
    return -1;
    }

    // 2. 获取屏幕参数(分辨率、像素格式等)
    if (ioctl(fb_fd, FBIOGET_VSCREENINFO, &var) < 0) {
    perror("ioctl FBIOGET_VSCREENINFO failed");
    close(fb_fd);
    return -1;
    }

    // 3. 计算显存大小并映射到用户空间
    unsigned int fb_size = var.xres * var.yres * var.bits_per_pixel / 8;
    fb_buf = (unsigned int *)mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
    if (fb_buf == MAP_FAILED) {
    perror("mmap failed");
    close(fb_fd);
    return -1;
    }

    printf("Framebuffer init success! xres:%d, yres:%d, bpp:%d\\n",
    var.xres, var.yres, var.bits_per_pixel);
    return 0;
    }

    /**
    * @brief 显示字符串(补充ASCII字库映射)
    * @param x:起始横坐标
    * @param y:起始纵坐标
    * @param str:要显示的字符串
    * @param color:文字颜色(ARGB8888)
    */
    void fb_show_string(int x, int y, const char *str, unsigned int color) {
    // 简化版16×16 ASCII字库(实际项目需引入完整字库文件)
    unsigned char font[16][16] = {
    // 'A'的16×16点阵示例
    {0x00,0x00,0x00,0x00,0x00,0x3F,0x7F,0x7F,0x7F,0x3F,0x00,0x00,0x00,0x00,0x00,0x00},
    {0x00,0x00,0x00,0x00,0x00,0x7F,0xFF,0xFF,0xFF,0x7F,0x00,0x00,0x00,0x00,0x00,0x00},
    // 更多字符点阵…
    };

    int str_len = strlen(str);
    for (int i = 0; i < str_len; i++) {
    unsigned char c = str[i] – 'A'; // 假设只显示大写字母
    for (int row = 0; row < 16; row++) {
    for (int col = 0; col < 16; col++) {
    if (font[row][col] & (1 << (7 – col))) {
    int px = x + i*16 + col;
    int py = y + row;
    if (px < var.xres && py < var.yres) {
    fb_buf[py * var.xres + px] = color;
    }
    }
    }
    }
    }
    }

    /**
    * @brief 关闭Framebuffer(补充资源释放)
    */
    void fb_close(void) {
    munmap(fb_buf, var.xres * var.yres * var.bits_per_pixel / 8);
    close(fb_fd);
    }

    3. Framebuffer 与 eLCDIF 的底层关联

    • 底层驱动中,LCDIF->CUR_BUF寄存器直接指向 Framebuffer 的物理地址(如0x89000000)。
    • LCD 控制器按照行场同步时序,从该地址逐行读取像素数据,通过 RGB 数据线传输到 LCD 屏幕。
    • 双缓冲机制:可设置LCDIF->NEXT_BUF为另一块物理地址,应用程序更新完下一帧数据后,切换缓冲地址,避免画面撕裂。

    四、PWM 背光控制完整实现

    1. 核心功能升级说明

    pwm.c 实现了 I.MX6ULL 的 PWM1 外设初始化、占空比动态调整、中断驱动的 FIFO 数据补充,补充了 时钟源校准、占空比边界限制、中断防抖 等细节,用于控制 LCD 背光亮度(GPIO1_IO08 引脚),支持 0%~100% 占空比精准可调。

    2. 完整优化代码实现

    #include "pwm.h"
    #include "MCIMX6Y2.h"
    #include "fsl_iomuxc.h"
    #include "interrupt.h"
    #include "stdio.h"

    static float g_dc = 0.5; // 初始占空比50%(0.0~1.0)
    #define PWM_PERIOD 65998 // PWM周期值(1KHz:66MHz/(65998+2)=1000Hz)

    /**
    * @brief 设置PWM占空比(补充边界限制,避免异常值)
    * @param dc:占空比(0.0~1.0)
    */
    void set_a_dc(float dc) {
    if (dc < 0.0f) dc = 0.0f;
    if (dc > 1.0f) dc = 1.0f;
    g_dc = dc;
    printf("Set PWM duty cycle: %.0f%%\\n", g_dc * 100);
    }

    /**
    * @brief 获取当前占空比
    * @return 当前占空比(0.0~1.0)
    */
    float get_a_dc(void) {
    return g_dc;
    }

    /**
    * @brief 向PWM FIFO写入占空比数据(补充FIFO深度适配)
    * @param dc:占空比(0.0~1.0)
    */
    void set_ratio(float dc) {
    unsigned short period = PWM1->PWMPR;
    unsigned short compare_val = (unsigned short)(period * dc); // 计算比较值
    int i = 0;
    // PWM1 FIFO深度为4,连续写入4次,确保数据不中断
    for (i = 0; i < 4; i++) {
    PWM1->PWMSAR = compare_val;
    }
    }

    /**
    * @brief PWM中断处理函数(FIFO空时补充数据,添加防抖)
    */
    void pwm_interrupt_handler(void) {
    // 检测FIFO水位线中断(bit3=1表示FIFO≤2个数据)
    if ((PWM1->PWMSR & (1 << 3))) {
    set_ratio(g_dc); // 补充新的占空比数据
    PWM1->PWMSR |= (1 << 3); // 清除中断标志(写1清零)
    }
    }

    /**
    * @brief PWM1初始化(控制LCD背光,补充时钟校准与引脚防抖)
    */
    void pwm1_init(void) {
    // 1. 引脚复用:GPIO1_IO08复用为PWM1_OUT
    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_PWM1_OUT, 0);
    // 电气特性配置:开漏输出+上拉使能+100MHz速度+R0/7驱动能力(防抖)
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_PWM1_OUT, 0xB9);

    // 2. PWM1软件复位(确保初始状态干净)
    PWM1->PWMCR |= (1 << 3);
    while ((PWM1->PWMCR & (1 << 3)) != 0); // 等待复位完成

    // 3. 配置PWMCR寄存器
    PWM1->PWMCR = 0;
    // FWM=1(FIFO<2时中断)、CLKSRC=ipg_clk(66MHz)、PRESCALER=65(66分频→1MHz)、REPEAT=3(数据复用8次)
    PWM1->PWMCR |= (1 << 26) | (1 << 16) | (65 << 4) | (3 << 1);

    // 4. 使能FIFO空中断
    PWM1->PWMIR |= (1 << 0);

    // 5. 设置PWM周期(1KHz):PWMO = PCLK/(PERIOD+2) → PERIOD=66MHz/1KHz -2=65998
    PWM1->PWMPR = PWM_PERIOD;

    // 6. 注册中断服务函数
    system_interrupt_register(PWM1_IRQn, pwm_interrupt_handler);
    // 7. 使能GIC中断
    GIC_EnableIRQ(PWM1_IRQn);
    // 8. 设置中断优先级(0为最高,避免被其他中断抢占)
    GIC_SetPriority(PWM1_IRQn, 0);

    // 9. 初始化占空比(50%)
    set_ratio(g_dc);

    // 10. 使能PWM1
    PWM1->PWMCR |= (1 << 0);
    printf("PWM1 initialized successfully! Initial Duty: 50%%\\n");
    }

    /**
    * @brief 亮度渐变调节(补充平滑过渡,避免突变)
    * @param target_dc:目标占空比
    * @param step:每次调节步长(0.01~0.1)
    * @param delay_ms:步长间隔(ms)
    */
    void pwm_smooth_adjust(float target_dc, float step, int delay_ms) {
    float current_dc = get_a_dc();
    if (target_dc > current_dc) {
    while (current_dc < target_dc) {
    current_dc += step;
    if (current_dc > target_dc) current_dc = target_dc;
    set_a_dc(current_dc);
    set_ratio(current_dc);
    delay_ms(delay_ms);
    }
    } else {
    while (current_dc > target_dc) {
    current_dc -= step;
    if (current_dc < target_dc) current_dc = target_dc;
    set_a_dc(current_dc);
    set_ratio(current_dc);
    delay_ms(delay_ms);
    }
    }
    }

    3. 关键优化细节说明

    • 占空比边界限制:set_a_dc中添加 0.0~1.0 范围判断,避免因异常值导致 PWM 输出不稳定。
    • FIFO 深度适配:PWM1 FIFO 深度为 4,set_ratio中连续写入 4 次数据,确保中断间隙数据不中断。
    • 平滑调节:新增pwm_smooth_adjust函数,通过步长控制实现亮度渐变,提升用户体验。
    • 时钟校准:PWM 周期值精准计算(66MHz 时钟源,1KHz 输出),避免频率偏差导致的亮度波动。

    五、联合应用:Framebuffer + 行场同步 + PWM 完整系统

    1. 系统架构升级(补充分层设计)

    • 硬件层:LCD 屏幕(RGB 接口)、PWM 输出引脚(GPIO1_IO08)、DDR 显存(0x89000000)、按键(KEY0)。
    • 驱动层:eLCDIF 控制器配置行场同步时序,Framebuffer 驱动关联显存与 LCD,PWM 驱动控制背光,中断驱动处理按键输入。
    • 应用层:通过 Framebuffer 实现图像 / 文字显示,按键触发 PWM 平滑调光,时序由底层自动同步。

    2. 核心功能联动流程(补充异常处理)

  • 系统启动后,先初始化 LCD(lcd_init)、PWM(pwm1_init)和 Framebuffer(fb_init),完成硬件配置。
  • 应用层通过fb_drawpoint、fb_show_string等函数操作 Framebuffer,更新显示内容。
  • LCD 控制器按照行场同步时序,从 Framebuffer 中逐行读取数据并显示,刷新率 60Hz。
  • 按键中断触发时,调用pwm_smooth_adjust函数,以 0.05 步长、50ms 间隔渐变调节 PWM 占空比,同时通过 Framebuffer 显示当前亮度值。
  • 异常处理:LCD 显示异常时,通过串口打印时序参数与 Framebuffer 地址,方便调试;PWM 中断未触发时,自动重新初始化 PWM。
  • 3. 工程实践:Makefile 与链接脚本配置(补充编译优化)

    (1)Makefile 优化(支持多文件编译与调试)

    # 交叉编译器配置
    cross_compiler = arm-linux-gnueabihf-
    cc = $(cross_compiler)gcc
    ld = $(cross_compiler)ld
    objcopy = $(cross_compiler)objcopy
    objdump = $(cross_compiler)objdump

    # 工程配置
    target = lcd_pwm_demo
    objs = start.o main.o lcd.o pwm.o fb.o interrupt.o gpt.o
    incdirs = .
    srcdirs = .

    # 编译选项(添加调试信息与优化)
    CFLAGS = -g -Wall -nostdlib -fno-builtin -I$(incdirs)
    LDFLAGS = -Timx6ul.lds -o $(target).elf

    # 目标文件生成
    $(target).bin: $(objs)
    $(ld) $(LDFLAGS) $(objs)
    $(objcopy) -O binary -S -g $(target).elf $(target).bin
    $(objdump) -D $(target).elf > $(target).dis

    # 汇编文件编译
    %.o: %.s
    $(cc) $(CFLAGS) -c -o $@ $<

    # C文件编译
    %.o: %.c
    $(cc) $(CFLAGS) -c -o $@ $<

    # 清理目标文件
    clean:
    rm -rf $(objs) $(target).bin $(target).elf $(target).dis

    (2)链接脚本(imx6ul.lds)配置(Framebuffer 地址指定)

    SECTIONS {
    .=0x87800000; // 链接起始地址(DDR中)

    .text: {
    obj/start.o // 启动文件优先链接
    *(.text) // 所有代码段
    }

    .rodata ALIGN(4): { *(.rodata*) } // 只读数据段(4字节对齐)

    .data ALIGN(4): { *(.data) } // 已初始化数据段

    .=ALIGN(4);
    __bss_start = .;
    .bss ALIGN(4): { *(.bss) *(COMMON) } // 未初始化数据段
    __bss_end = .;

    // Framebuffer地址指定(与lcd.c中_FRAME_RAM_ADDRESS一致)
    .framebuffer ALIGN(4K): {
    . = . + 0x180000; // 1.5MB显存(800×480×4字节)
    }
    }

    六、常见问题与深度优化技巧

    1. 行场同步时序相关问题排查

    • 画面撕裂:开启双缓冲机制,设置LCDIF->NEXT_BUF为另一块显存地址,更新完下一帧后切换缓冲。
    • 花屏 / 错位:核对VDCTRL0~VDCTRL4寄存器参数与 LCD 手册一致,检查像素时钟是否稳定(可通过示波器测量)。
    • 闪烁:增加 VFP/VBP 时长(如 VFP 从 13 改为 20),或提高刷新率(如从 50Hz 改为 60Hz),确保时序余量充足。

    2. Framebuffer 优化

    • 局部刷新:仅更新变化区域(如文字显示区域),避免全屏幕填充,减少 DDR 带宽占用。
    • 格式转换:若 LCD 支持 RGB565 格式,将像素格式改为 16 位,显存占用减少一半(800×480×2=768KB)。
    • 内存对齐:Framebuffer 地址按 4K 对齐(链接脚本中ALIGN(4K)),提升 DDR 访问效率。

    3. PWM 背光优化

    • 功耗控制:低亮度时降低 PWM 频率(如 500Hz),高亮度时提高频率(如 2KHz),平衡功耗与稳定性。
    • 中断优化:调整 FIFO 水位线(PWMCR寄存器 FWM 位),减少中断触发次数(如 FWM=2,FIFO<3 时中断)。
    • 防抖处理:在 PWM 中断函数中添加延时(如delay_us(10)),避免因 FIFO 抖动导致的亮度波动。
    赞(0)
    未经允许不得转载:171主机测评 » ARM之lcd与pwm
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址