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屏幕亮度逐渐降低。