ARM.9(RGBLCD,PWM)

1.LCD

LCD 全称是 Liquid Crystal Display,也就是液晶显示器,是现在最常用到的显示器,手机、电脑、各种人机交互设备等基本都用到了 LCD,最常见就是手机和电脑显示器了。这里需要注意的是,我们所提及的 LCD 不包括触摸功能,触摸功能是通过另外的设备实现的,LCD 在这里只是输出设备。LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置 TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过 TFT 上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的。我们现在要在 IMX6U-ALPHA 开发板上使用 LCD,所以不需要去研究 LCD 的具体实现原理,我们只需要从使用的角度去关注 LCD 的几个重要点:

**分辨率:**提起 LCD 显示器,我们都会听到 720P、1080P、2K 或 4K 这样的字眼,这个就是 LCD 显示器分辨率。LCD 显示器都是由一个一个的像素点组成,像素点就类似一个灯(在 OLED 显示器中,像素点就是一个小灯),这个小灯是 RGB 灯,也就是由 R(红色)、G(绿色)和 B(蓝色)这三种颜色组成的,而 RGB 就是光的三原色。1080P 的意思就是一个 LCD 屏幕上的像素数量是 1920 * 1080 个,也就是这个屏幕一列 1080 个像素点,一共 1920 列

上图就是 1080P 显示器的像素示意图,X 轴就是 LCD 显示器的横轴,Y 轴就是显示器的竖轴。图中的小方块就是像素点,一共有 1920 * 1080=2073600 个像素点。左上角的 A 点是第一个像素点,右下角的 C 点就是最后一个像素点。2K 就是 2560 * 1440 个像素点,4K 是 3840 * 2160 个像素点。很明显,在 LCD 尺寸不变的情况下,分辨率越高越清晰。同样的,分辨率不变的情况下,LCD 尺寸越小越清晰。比如我们常用的 24 寸显示器基本都是 1080P 的,而我们现在使用的 5 寸的手机基本也是 1080P 的,但是手机显示细腻程度就要比 24 寸的显示器要好很多。

此外,LCD 显示器的分辨率是一个很重要的参数,但是并不是分辨率越高的 LCD 就越好。衡量一款 LCD 的好坏,分辨率只是其中的一个参数,还有色彩还原程度、色彩偏离、亮度、可视角度、屏幕刷新率等其他参数。

一个像素点就相当于一个 RGB 小灯,通过控制 R、G、B 这三种颜色的亮度就可以显示出各种各样的色彩。那该如何控制 R、G、B 这三种颜色的显示亮度呢?一般一个 R、G、B 这三部分分别使用 8bit 的数据,那么一个像素点就是 8bit*3=24bit,也就是说一个像素点 3 个字节,这种像素格式称为 RGB888。如果再加入 8bit 的 Alpha(透明)通道的话一个像素点就是 32bit,也就是 4 个字节,这种像素格式称为 ARGB8888。如果学习过 STM32 的话应该还听过 RGB565 这种像素格式,在本次实验中我们使用 ARGB8888 这种像素格式,一个像素占用 4个字节的内存。

上图中,一个像素点是 4 个字节,其中 bit31~bit24 是 Alpha 通道,bit23~bit16 是 RED 通道,bit15~bit14 是 GREEN 通道,bit7~bit0 是 BLUE 通道。所以红色对应的值就是 0x00FF0000,蓝色对应的值就是 0x000000FF,绿色对应的值为 0x0000FF00。通过调节 R、G、B 的比例可以产生其它的颜色,比如 0x00FFFF00 就是黄色,0x00000000 就是黑色,0x00FFFFFF 就是白色。

2.RGBLCD

在垂直同步信号和行同步信号前后会拉高de信号线让发送数据无效。

(VFP + VSPW + VBP + height) * (HFP + HSPW + HPB + width)

这个公式通常用于计算 LCD 显示屏的总显存大小(Frame Buffer Size)​ 或者 LCD 扫描总行数与总列数的乘积。它代表了屏幕完成一整帧图像显示所需处理的总像素点数量。

各参数含义如下:

纵向参数(高度相关):

height:LCD 屏幕的物理有效显示高度(即分辨率中的高,如 1080)。

VFP(Vertical Front Porch):垂直前肩(消隐期行数)。

VSPW(Vertical Sync Pulse Width):垂直同步脉冲宽度(行数)。

VBP(Vertical Back Porch):垂直后肩(消隐期行数)。

这四项之和代表了 LCD 控制器在一帧画面内需要扫描的总行数。

横向参数(宽度相关):

width:LCD 屏幕的物理有效显示宽度(即分辨率中的宽,如 1920)。

HFP(Horizontal Front Porch):水平前肩(消隐期像素数)。

HSPW(Horizontal Sync Pulse Width):水平同步脉冲宽度(像素数)。

HPB(Horizontal Back Porch):水平后肩(消隐期像素数)。

这四项之和代表了 LCD 控制器在每一行内需要扫描的总像素数。

最终计算逻辑:

(总行数) × (每行总像素数) = 屏幕一帧所需的总像素点数

LCD行显示时序:

LCD帧显示时序:

因此:(VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)= (3 + 32 + 480 + 13) * (48 + 88 + 800 + 40) = 。显示一帧图像需要515328个时钟数,那么显示60帧就是:515328 * 60 = 30,919,680≈31M,所以像素时钟就是 31MHz。

LCD的PCLK来自PLL5,因此我们首先配置时钟以及引脚复用和LCD相关寄存器配置:

引脚复用:

时钟配置:

cs 复制代码
void init_lcd_clk(void)
{
    // ========== 1、配置视频PLL(PLL5)生成基准时钟 ==========
    
    // 临时使能PLL视频时钟输出(bit16=1),防止配置过程中时钟不稳定
    CCM_ANALOG->PLL_VIDEO |= (1 << 16);
    
    // 使能PLL视频时钟的BYPASS旁路模式(bit12=1),暂时绕过PLL直接输出参考时钟
    CCM_ANALOG->PLL_VIDEO |= (1 << 12);
    
    // 清空分子寄存器(整数分频配置)
    CCM_ANALOG->PLL_VIDEO_NUM = 0;
    
    // 设置分母寄存器为1(整数分频配置)
    CCM_ANALOG->PLL_VIDEO_DENOM = 1;
    
    // 清除低7位的分频系数(bits 6:0)
    CCM_ANALOG->PLL_VIDEO &= ~(0x7F << 0);
    
    // 设置分频系数为42(配置PLL的输出频率)
    CCM_ANALOG->PLL_VIDEO |= (42 << 0);
    
    // 清除后分频系数(bits 19:18)
    CCM_ANALOG->PLL_VIDEO &= ~(3 << 19);
    
    // 设置后分频系数为2(输出频率 = VCO频率 / 后分频)
    CCM_ANALOG->PLL_VIDEO |= (2 << 19);
    
    // 关闭BYPASS旁路模式(bit12=0),让PLL正常工作
    CCM_ANALOG->PLL_VIDEO &= ~(1 << 12);
    
    // 使能PLL视频时钟的内部滤波和稳定电路(bit13=1)
    CCM_ANALOG->PLL_VIDEO |= (1 << 13);
    
    // 等待PLL锁定(bit31为锁定位,1表示PLL已锁定输出稳定时钟)
    while((CCM_ANALOG->PLL_VIDEO & (1 << 31)) == 0);
    
    // 关闭临时使能位(bit16=0),PLL配置完成
    CCM_ANALOG->PLL_VIDEO &= ~(1 << 16);
    
    // ========== 2、配置LCDIF时钟分频器(CSCDR2寄存器) ==========
    unsigned int t;
    t = CCM->CSCDR2;                    // 读取时钟分频控制寄存器2
    
    // 配置LCDIF时钟预分频器(bits 17:15)
    t &= ~(7 << 15);                    // 清除bit17-bit15(原分频值)
    t |= (2 << 15);                     // 设置预分频值为2
    
    // 配置LCDIF时钟后分频器(bits 14:12)
    t &= ~(7 << 12);                    // 清除bit14-bit12(原分频值)
    t |= (3 << 12);                     // 设置后分频值为3
    
    // 配置LCDIF时钟根分频器(bits 11:9)
    t &= ~(7 << 9);                     // 清除bit11-bit9(原分频值)
                                        // 根分频值保持为0(不分频)
    
    // 写入配置
    CCM->CSCDR2 = t;
    
    // ========== 3、选择LCDIF时钟源 ==========
    // 设置CBCMR寄存器的bit25-bit23为0b111,选择视频PLL作为LCDIF的时钟源
    CCM->CBCMR |= (7 << 23);            // 选择视频PLL作为LCDIF时钟源
}

LCD相关寄存器配置:

cs 复制代码
void init_lcd(void)
{
    init_lcd_io(); 
    reset_lcd();
    init_lcd_clk();

    // ========== 2、设置LCD显示参数 ==========
    lcd_dev.width = 800;        // 水平分辨率800像素
    lcd_dev.height = 480;       // 垂直分辨率480像素
    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.pix_size = 4;       // 每个像素4字节(32位色深,ARGB格式)
    lcd_dev.frame_buffer = LCD_FRAMEBUFFER_ADDR;  // 显存基地址
    lcd_dev.fore_color = 0x00FF00;   // 前景色:绿色
    lcd_dev.back_color = 0x00000000; // 背景色:黑色

    // ========== 3、配置LCDIF控制器寄存器 ==========
    // 配置CTRL控制寄存器
    LCDIF->CTRL |= (1 << 19) |      // 设置运行模式:使能显示
                   (1 << 17) |      // 设置数据格式为RGB888
                   (3 << 10) |      // 选择输入数据格式为32位
                   (3 << 8)  |      // 设置字节交换模式
                   (1 << 5);        // 使能输出数据格式转换
    
    // 配置CTRL1控制寄存器1
    LCDIF->CTRL1 = (7 << 16);       // 设置数据缓冲大小
    
    // 设置传输计数值(一帧的总像素数)
    LCDIF->TRANSFER_COUNT = (lcd_dev.height << 16) | (lcd_dev.width);
    
    // 设置当前帧缓冲区和下一帧缓冲区地址
    LCDIF->CUR_BUF = lcd_dev.frame_buffer;   // 当前显示缓冲区
    LCDIF->NEXT_BUF = lcd_dev.frame_buffer;  // 下一帧缓冲区(单缓冲模式,指向相同地址)
    
    // 配置VDCTRL0(视频控制寄存器0)- 垂直时序控制
    LCDIF->VDCTRL0 = (1 << 28) |      // 使能显示
                     (1 << 24) |      // 设置VSYNC有效电平为高
                     (1 << 21) |      // 使能数据使能(ENABLE)信号
                     (1 << 20) |      // 设置数据使能极性为高有效
                     (lcd_dev.vspw << 0);  // 垂直同步脉冲宽度
    
    // 配置VDCTRL1(视频控制寄存器1)- 垂直周期总行数
    LCDIF->VDCTRL1 = lcd_dev.height + lcd_dev.vspw + lcd_dev.vfp + lcd_dev.vbp;
    
    // 配置VDCTRL2(视频控制寄存器2)- 水平时序控制
    LCDIF->VDCTRL2 = (lcd_dev.hspw << 18) |                    // 水平同步脉冲宽度
                     (lcd_dev.hspw + lcd_dev.hfp + lcd_dev.hbp + lcd_dev.width);
    
    // 配置VDCTRL3(视频控制寄存器3)- 同步信号位置
    LCDIF->VDCTRL3 = ((lcd_dev.hspw + lcd_dev.hbp) << 16) |   // 水平同步到数据起始的间隔
                     (lcd_dev.vspw + lcd_dev.vbp);            // 垂直同步到数据起始的间隔
    
    // 配置VDCTRL4(视频控制寄存器4)- 水平周期总像素数
    LCDIF->VDCTRL4 = (1 << 18) |      // 数据有效信号使能
                     (lcd_dev.width); // 水平显示宽度
    
    // 使能LCD控制器(启动显示)
    LCDIF->CTRL |= (1 << 0);
    
    // 等待LCD稳定
    delayms(20);

    // ========== 4、清屏:将整个屏幕填充为蓝色 ==========
    // 将显存地址转换为二维数组指针(方便像素操作)
    unsigned int (*p)[800] = (unsigned int (*)[800])lcd_dev.frame_buffer;
    int i, j;
    for(i = 0; i < lcd_dev.height; ++i)    
    {
        for(j = 0; j < lcd_dev.width; ++j)   
        {
            p[i][j] = 0x000000FF;  
        }
    }
}

3.pwm

PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过调节方波信号的高电平时间占整个周期的比例(即占空比)来控制输出电压或功率的技术。它利用数字输出产生模拟效果,在固定频率下改变占空比,可以实现对电机转速、LED亮度、伺服电机角度等设备的精确控制。PWM的主要参数包括周期(频率的倒数)和占空比(高电平时间与周期的比值),占空比越高,等效输出的平均电压越高。这种调制方式具有能量效率高、抗干扰能力强、实现简单等优点,广泛应用于嵌入式系统的各类控制场景中。

不管是使用显示器还是手机,其屏幕背光都是可以调节的,通过调节背光就可以控制屏幕的亮度。在户外阳光强烈的时候可以通过调高背光来看清屏幕,在光线比较暗的地方可以调低背光,防止伤眼睛并且省电。我们使用的开发吧LCD有一个背光控制引脚,给这个背光控制引脚输入高电平就会点亮背光,输入低电平就会关闭背光。假如我们不断的打开和关闭背光,当速度足够快的时候就不会感觉到背光关闭这个过程了。

PWM信号有两个关键的术语:频率和占空比,频率就是开关速度,把一次开关算作一个周期,那么频率就是1秒内进行了多少次开关。占空比就是一个周期内高电平时间和低电平时间的比例,一个周期内高电平时间越长占空比就越大,反之占空比就越小。占空比用百分之表示,如果一个周期内全是低电平那么占空比就是0%,如果一个周期内全是高电平那么占空比就是100%。

我们给LCD的背光引脚输入一个PWM信号,这样就可以通过调整占空比的方式来调整LCD背光亮度了。提高占空比就会提高背光亮度,降低占空比就会降低背光亮度。重点就在于PWM信号的产生和占空比的控制,很幸运的是,IMX6U提供了PWM外设,因此我们可以配置PWM外设来可以方便地产生PWM信号。

功能框图:

pwm.c

cs 复制代码
#include "pwm.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "interrupt.h"
#include "core_ca7.h"

float global_ratio = 1.0;

void set_ratio(void)
{
    int i;
    for(i = 0; i < 4; ++i)  // 循环4次,确保写入生效
    {
        PWM1->PWMSAR = PWM1->PWMPR * global_ratio;  // 设置采样寄存器值 = 周期值 * 占空比系数
    }
}

void pwm1_interrupt_handler(void)
{
    if((PWM1->PWMSR & (1 << 3)) != 0)  // 判断FIFO空中断标志是否置位(FIFO空触发中断)
    {
        set_ratio();  // 重新填充PWM采样寄存器
        PWM1->PWMSR |= (1 << 3);  // 写1清除FIFO空中断标志
    }
}

void init_pwm1(void)
{
    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_PWM1_OUT, 0);  // 配置GPIO1_IO08复用为PWM1输出引脚
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_PWM1_OUT, 0xB9);  // 配置引脚电气属性(驱动能力、压摆率等)

    PWM1->PWMCR = 0;  // 清零PWM控制寄存器
    PWM1->PWMCR |= (1 << 26) |  // 使能FIFO空中断
                   (1 << 16) |  // 使能PWM输出
                   (65 << 4) |  // 设置时钟分频系数(分频后时钟 = IPG时钟 / (65+1))
                   (3 << 1);    // 设置FIFO触发水位为3(FIFO空时触发中断)
    PWM1->PWMIR |= (1 << 0);  // 使能PWM1的中断输出
    PWM1->PWMPR = 998;  // 设置PWM周期寄存器值(决定PWM频率)
    set_ratio();  // 设置初始占空比

    system_interrupt_register(PWM1_IRQn, pwm1_interrupt_handler);  // 注册PWM1中断处理函数
    GIC_EnableIRQ(PWM1_IRQn);  // 使能GIC中断控制器中的PWM1中断

    PWM1->PWMCR |= (1 << 0);  // 使能PWM1模块(启动PWM输出)
}

实现了lcd屏幕亮度逐渐降低。

相关推荐
仙俊红2 小时前
反射到底解决什么问题?
java·开发语言
潜创微科技2 小时前
2026年专业创作KVM方案服务商选型指南:技术、场景与服务的全维度评估
嵌入式硬件·音视频
济6172 小时前
ROS开发专栏---ROS2 机械臂应用入门(1)---JointState 消息解析与机械臂往复运动控制实验---适配Ubuntu 22.04
嵌入式硬件·嵌入式·ros2·机器人开发·机器人方向
caimouse3 小时前
ReactOS 项目目录工程分析文档
stm32·单片机·嵌入式硬件
珊瑚里的鱼3 小时前
C++14 和 C++17 的核心新特性
开发语言·c++
techdashen3 小时前
深入理解 Rust Futures:从零开始,一头扎到底
开发语言·后端·rust
DS小龙哥3 小时前
基于STM32与华为云的智能康养木屋环境监测与控制系统
stm32·嵌入式硬件·华为云
顾喵3 小时前
嵌入式完整中断开发流程:注册函数、服务函数、回调函数详解
单片机·嵌入式硬件
程序猿乐锅3 小时前
【JAVASE | 第十六篇】多线程
java·开发语言