基于IMX6ULL的LCD显示与PWM亮度调节+多点触控实现
一、核心原理剖析
1.1 IMX6ULL LCD显示原理
IMX6ULL内置LCDIF(LCD Interface)控制器,专门负责将显存中的图像数据传输至外部LCD屏,本文采用24位并行RGB接口的800*480分辨率LCD屏,核心原理如下:
- 数据传输:LCDIF控制器通过24位数据总线(DATA00~DATA23)将显存中的像素数据逐行、逐列发送至LCD屏;
- 同步信号:通过HSYNC(行同步)、VSYNC(场同步)信号同步屏幕的行/帧扫描,配合LCD_CLK像素时钟保证数据传输时序;
- 时序参数:需配置行/场同步脉宽(HSPW/VSPW)、前后沿(HBP/HFP/VBP/VFP),匹配LCD屏的扫描时序;
- 显存规划:将SDRAM的0x89000000地址作为帧缓存,每个像素占用4字节(RGB888格式),直接操作该地址即可实现像素绘制。
1.2 PWM背光调光原理
LCD背光通常由LED驱动,IMX6ULL的PWM模块通过脉冲宽度调制(PWM) 调节背光亮度,核心逻辑:
- 占空比控制:PWM输出高低电平的占空比决定LED平均工作电流,占空比越大(高电平时间越长),亮度越高;
- 硬件映射:将GPIO1_IO08复用为PWM1_OUT引脚,连接背光驱动MOS管,通过PWM信号控制MOS管通断;
- 动态调节:通过PWM中断实时更新占空比寄存器(PWMSAR),实现亮度的无卡顿调节。
1.3 多点触控原理
本文基于I2C接口的GT911触控芯片实现多点触控,核心流程:
- 触控检测:GT911实时检测触摸点的电容变化,识别触摸坐标和触摸点数;
- 数据传输:触控芯片通过I2C总线向IMX6ULL上报触控数据包(包含触摸点ID、X/Y坐标);
- 多点解析:解析数据包区分不同触摸点,实现单点绘制、两点缩放/亮度调节等交互逻辑。
二、硬件电路说明
- LCD接口:IMX6ULL的LCDIF引脚(DATA00~23、HSYNC、VSYNC、LCD_CLK)直接连接LCD屏的24位RGB并行接口;
- PWM背光:GPIO1_IO08(PWM1_OUT)连接N沟道MOS管栅极,MOS管漏极接背光LED正极,源极接地,通过PWM信号控制LED供电;
- 触控接口:IMX6ULL的I2C1_SCL/I2C1_SDA连接GT911的I2C接口,GT911的INT引脚连接IMX6ULL的GPIO中断引脚,用于上报触摸事件。
三、代码实现详解
3.1 工程文件结构
├── bsp/
│ ├── lcd.c // LCD初始化与绘制函数
│ ├── lcd.h // LCD数据结构与函数声明
│ ├── pwm.c // PWM初始化与占空比调节
│ ├── pwm.h // PWM函数声明
│ ├── touch.c // 触控数据读取与解析
│ ├── touch.h // 触控相关定义
│ ├── gpt.c // 延时函数实现
│ └── gpt.h // 延时函数声明
├── imx6ull/
│ ├── fsl_iomuxc.h // 引脚复用配置
│ └── MCIMX6Y2.h // 寄存器定义
└── main.c // 主函数(模块初始化+交互逻辑)
3.2 LCD显示模块实现
3.2.1 头文件定义(lcd.h)
核心定义LCD屏的时序参数、显存地址和操作函数:
#ifndef _LCD_H_
#define _LCD_H_
// LCD设备结构体(存储分辨率、时序、显存等参数)
struct tftlcd_t{
unsigned int width; // 宽度
unsigned int height; // 高度
unsigned int pix_size; // 像素字节数
unsigned int hspw; // 行同步脉宽
unsigned int hbp; // 行后沿
unsigned int hfp; // 行前沿
unsigned int vspw; // 场同步脉宽
unsigned int vbp; // 场后沿
unsigned int vfp; // 场前沿
unsigned int frame_addr;// 帧缓存地址
unsigned int fore_color;// 前景色
unsigned int back_color;// 背景色
};
#define _FRAME_RAM_ADDRESS (0x89000000) // 帧缓存起始地址
extern struct tftlcd_t lcd_dev;
extern void lcd_init(void); // LCD初始化
extern void lcd_drawpoint(int x, int y, unsigned int color); // 绘制像素点
extern void screen_clear(unsigned int color); // 清屏
#endif // !_LCD_H_
3.2.2 LCD核心实现(lcd.c)
包含引脚初始化、时钟配置、控制器参数配置三大核心部分:
#include "lcd.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "gpt.h"
#include "stdio.h"
// 引脚复用与电气特性配置
void lcd_pad_init(void)
{
// 24位数据引脚复用为LCDIF功能
IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0);
// … 省略DATA02~DATA23引脚配置(与上述格式一致)
IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_LCDIF_DATA23, 0);
// 同步/时钟引脚复用
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);
// 所有LCD引脚电气特性配置(0xB9:上下拉禁用+速率配置)
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0xB9);
// … 省略DATA02~DATA23引脚配置
IOMUXC_SetPinConfig(IOMUXC_LCD_CLK_LCDIF_CLK, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE, 0xB9);
}
// LCD时钟初始化(PLL5输出31MHz作为LCDIF时钟源)
void lcd_clk_init(void)
{
// 配置PLL5(VIDEO PLL)参数:24M晶振 * (31+0/1) = 744M,分频后输出31M
CCM_ANALOG->PLL_VIDEO_NUM = 0;
CCM_ANALOG->PLL_VIDEO_DENOM = 1;
unsigned int t = CCM_ANALOG->PLL_VIDEO;
t &= ~(3 << 19);
t |= (2 << 19);
t &= ~(0x7F << 0);
t |= (31 << 0);
CCM_ANALOG->PLL_VIDEO = t;
CCM_ANALOG->PLL_VIDEO |= (1 << 13); // 使能PLL5
// 配置LCDIF时钟分频
t = CCM->CSCDR2;
t |= (2 << 15); // LCDIF1_PRE_CLK_SEL:选择PLL5
t &= ~(7 << 12);
t |= (3 << 12); // LCDIF1_PREDIV:预分频
t &= ~(7 << 9); // LCDIF1_CLK_SEL:时钟源选择
CCM->CSCDR2 = t;
CCM->CBCMR &= ~(7 << 23);
CCM->CBCMR |= (5 << 23); // LCDIF1_PODF:最终分频
}
// LCD控制器复位
void reset_lcd(void)
{
LCDIF->CTRL |= (1 << 31);
delay_ms(20);
LCDIF->CTRL &= ~(1 << 31);
LCDIF->CTRL &= ~(1 << 30);
}
struct tftlcd_t lcd_dev;
// LCD初始化主函数
void lcd_init(void)
{
lcd_pad_init(); // 引脚初始化
reset_lcd(); // 控制器复位
lcd_clk_init(); // 时钟配置
delay_ms(50);
// 配置800*480分辨率LCD时序参数
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.fore_color = 0x00FF0000; // 前景色:红色
lcd_dev.back_color = 0x00FFFFFF; // 背景色:白色
// LCDIF控制器配置(24位RGB、同步模式)
LCDIF->CTRL |= (1 << 19) | (1 << 17) | (3 << 10) | (3 << 8) | (1 << 5);
LCDIF->CTRL1 &= ~(0xF << 16);
LCDIF->CTRL1 |= (0x7 << 16);
// 设置传输分辨率、帧缓存地址
LCDIF->TRANSFER_COUNT = (lcd_dev.height << 16) | (lcd_dev.width << 0);
LCDIF->CUR_BUF = lcd_dev.frame_addr;
LCDIF->NEXT_BUF = lcd_dev.frame_addr;
// 配置时序寄存器
LCDIF->VDCTRL0 = (1 << 28) | (1 << 24) | (1 << 21) | (1 << 20) | (lcd_dev.vspw << 0);
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);
LCDIF->CTRL |= (1 << 0); // 使能LCDIF
delay_ms(50);
screen_clear(lcd_dev.back_color); // 清屏为背景色
}
// 绘制单个像素点(核心:直接操作帧缓存地址)
void lcd_drawpoint(int x, int y, unsigned int color)
{
unsigned int * p = (unsigned int *)lcd_dev.frame_addr;
if (x >= lcd_dev.width || y >= lcd_dev.height){ // 边界检查
return;
}
*(p + lcd_dev.width * y + x) = color; // 计算像素地址并赋值
}
// 全屏清屏
void screen_clear(unsigned int color)
{
int j = 0;
int i = 0;
for (j = 0; j < lcd_dev.height; j++){
for (i = 0; i < lcd_dev.width; i++){
lcd_drawpoint(i, j, color);
}
}
}
3.3 PWM亮度调节模块实现
3.3.1 PWM核心代码(pwm.c)
实现PWM初始化、占空比调节、中断驱动的动态亮度控制:
#include "pwm.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "interrupt.h"
#include "stdio.h"
static float g_dc = 0.5; // 全局占空比(0~1)
// 设置占空比
void set_a_dc(float dc)
{
g_dc = dc;
}
// 获取当前占空比
float get_a_dc(void)
{
return g_dc;
}
// 根据占空比更新PWM比较寄存器
void set_ratio(float dc)
{
unsigned short temp = PWM1->PWMPR; // 周期寄存器值
int i = 0;
for (i = 0; i < 4; i++)
{
PWM1->PWMSAR = temp * dc; // 占空比 = PWMSAR / PWMPR
}
}
// PWM中断服务函数(实时更新占空比)
void pwm_interrupt_handler(void)
{
if ((PWM1->PWMSR & (1 << 3))) // 检测中断标志
{
set_ratio(g_dc); // 更新占空比
printf("当前占空比:%d%%\\n", (int)(g_dc * 100));
PWM1->PWMSR |= (1 << 3); // 清除中断标志
}
}
// PWM1初始化
void pwm1_init(void)
{
// 引脚复用:GPIO1_IO08 -> PWM1_OUT
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_PWM1_OUT, 0);
// 电气特性配置
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_PWM1_OUT, 0xB9);
// PWM控制器复位
PWM1->PWMCR |= (1 << 3);
while ((PWM1->PWMCR & (1 << 3)) != 0);
// PWM模式配置:时钟源=IPG_CLK、分频=65、对齐模式=中心对齐
PWM1->PWMCR = 0;
PWM1->PWMCR |= (1 << 26) | (1 << 16) | (65 << 4) | (3 << 1);
PWM1->PWMIR |= (1 << 0); // 使能PWM中断
PWM1->PWMPR = 1000 – 2; // 周期:1000个时钟周期(约1ms)
// 中断配置:注册中断函数+使能IRQ+设置优先级
system_interrupt_register(PWM1_IRQn, pwm_interrupt_handler);
GIC_EnableIRQ(PWM1_IRQn);
GIC_SetPriority(PWM1_IRQn, 0);
set_ratio(0.5); // 初始占空比50%
PWM1->PWMCR |= (1 << 0); // 使能PWM1
}
3.4 多点触控模块实现(补充GT911示例)
3.4.1 触控头文件(touch.h)
#ifndef _TOUCH_H_
#define _TOUCH_H_
#define TOUCH_I2C_ADDR 0xBA >> 1 // GT911 I2C地址
// 触控数据结构体
typedef struct {
int point_num; // 触摸点数
int x[5]; // 触摸点X坐标(最多5点)
int y[5]; // 触摸点Y坐标
int id[5]; // 触摸点ID
} touch_data_t;
extern void touch_init(void); // 触控初始化
extern int touch_get_data(touch_data_t *data); // 获取触控数据
#endif // !_TOUCH_H_
3.4.2 触控核心实现(touch.c)
#include "touch.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "gpt.h"
// I2C初始化(用于与GT911通信)
void i2c1_init(void)
{
// 引脚复用:GPIO1_IO02=I2C1_SCL, GPIO1_IO03=I2C1_SDA
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO02_I2C1_SCL, 1);
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_I2C1_SDA, 1);
// 电气特性:开漏输出+上拉
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO02_I2C1_SCL, 0x70B0);
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_I2C1_SDA, 0x70B0);
// I2C配置:400KHz速率
I2C1->I2CR = 0;
I2C1->I2FR = 0x14; // 分频系数
I2C1->I2CR = (1 << 7) | (1 << 5); // 使能I2C主机模式
}
// I2C写数据
void i2c_write(unsigned char reg, unsigned char data)
{
while(I2C1->I2SR & (1 << 4)); // 等待总线空闲
I2C1->I2DR = TOUCH_I2C_ADDR; // 发送设备地址
I2C1->I2CR |= (1 << 4); // 启动传输
while(!(I2C1->I2SR & (1 << 1))); // 等待ACK
I2C1->I2SR &= ~(1 << 1);
I2C1->I2DR = reg; // 发送寄存器地址
while(!(I2C1->I2SR & (1 << 1)));
I2C1->I2SR &= ~(1 << 1);
I2C1->I2DR = data; // 发送数据
while(!(I2C1->I2SR & (1 << 1)));
I2C1->I2SR &= ~(1 << 1);
I2C1->I2CR |= (1 << 3); // 停止传输
}
// I2C读数据
unsigned char i2c_read(unsigned char reg)
{
unsigned char data;
while(I2C1->I2SR & (1 << 4));
I2C1->I2DR = TOUCH_I2C_ADDR;
I2C1->I2CR |= (1 << 4);
while(!(I2C1->I2SR & (1 << 1)));
I2C1->I2SR &= ~(1 << 1);
I2C1->I2DR = reg;
while(!(I2C1->I2SR & (1 << 1)));
I2C1->I2SR &= ~(1 << 1);
I2C1->I2CR |= (1 << 4) | (1 << 0); // 重复启动+读模式
I2C1->I2DR = TOUCH_I2C_ADDR | 0x01;
while(!(I2C1->I2SR & (1 << 1)));
I2C1->I2SR &= ~(1 << 1);
I2C1->I2CR &= ~(1 << 2); // 禁止ACK
while(!(I2C1->I2SR & (1 << 0))); // 等待数据接收
data = I2C1->I2DR;
I2C1->I2CR |= (1 << 3); // 停止传输
return data;
}
// 触控初始化
void touch_init(void)
{
i2c1_init();
// 配置GT911:800*480分辨率、使能多点触控
i2c_write(0x8000, 0x00);
i2c_write(0x8001, 0x00);
i2c_write(0x8002, 0x03); // 最大触摸点数:3
i2c_write(0x8003, 0x20); // X轴最大值:800
i2c_write(0x8004, 0xE0);
i2c_write(0x8005, 0x01); // Y轴最大值:480
i2c_write(0x8006, 0xE0);
}
// 获取触控数据
int touch_get_data(touch_data_t *data)
{
unsigned char buf[16];
// 读取触摸状态寄存器
buf[0] = i2c_read(0x814E);
data->point_num = buf[0] & 0x0F; // 提取触摸点数
if(data->point_num == 0) return 0;
// 读取触摸点坐标
for(int i=0; i<data->point_num; i++)
{
buf[1+i*4] = i2c_read(0x814F + i*4); // X坐标高8位
buf[2+i*4] = i2c_read(0x8150 + i*4); // X坐标低8位
buf[3+i*4] = i2c_read(0x8151 + i*4); // Y坐标高8位
buf[4+i*4] = i2c_read(0x8152 + i*4); // Y坐标低8位
buf[5+i*4] = i2c_read(0x8153 + i*4); // 触摸点ID
data->x[i] = (buf[1+i*4] << 8) | buf[2+i*4];
data->y[i] = (buf[3+i*4] << 8) | buf[4+i*4];
data->id[i] = buf[5+i*4] & 0x0F;
}
// 清除触摸标志
i2c_write(0x814E, 0x00);
return 1;
}
3.5 主函数整合(main.c)
实现LCD显示、PWM调光、触控交互的联动:
#include "lcd.h"
#include "pwm.h"
#include "gpt.h"
#include "touch.h"
#include "stdio.h"
#include "math.h"
int main(void)
{
// 基础初始化
gpt1_init(); // 延时初始化
lcd_init(); // LCD初始化
pwm1_init(); // PWM初始化
touch_init(); // 触控初始化
// 初始清屏+绘制测试点
screen_clear(lcd_dev.back_color);
lcd_drawpoint(100, 100, lcd_dev.fore_color);
// 主循环:处理触控事件
while(1)
{
touch_data_t touch_data;
if(touch_get_data(&touch_data))
{
// 单点触摸:绘制红色像素点
if(touch_data.point_num == 1)
{
lcd_drawpoint(touch_data.x[0], touch_data.y[0], 0x00FF0000);
}
// 两点触摸:根据两点距离调节亮度(距离越大,亮度越高)
else if(touch_data.point_num == 2)
{
// 计算两点欧氏距离
int dx = touch_data.x[0] – touch_data.x[1];
int dy = touch_data.y[0] – touch_data.y[1];
int distance = sqrt(dx*dx + dy*dy);
// 距离映射到0~1的占空比(最大距离500)
float dc = (float)distance / 500;
if(dc > 1.0) dc = 1.0;
if(dc < 0.0) dc = 0.0;
set_a_dc(dc); // 更新PWM占空比
}
}
delay_ms(10); // 降低轮询频率,减少CPU占用
}
return 0;
}
四、测试验证
4.1 编译烧录
将代码基于IMX6ULL交叉编译工具链编译为bin文件,通过SD卡或JTAG烧录到开发板。
4.2 功能验证
五、总结与拓展
本文完整实现了IMX6ULL的LCD显示、PWM背光调光、多点触控三大核心功能,从底层寄存器配置到上层交互逻辑,覆盖了硬件原理和代码实现。基于该基础,可进一步拓展:
- 集成GUI图形库(如LVGL、GUIX),实现更复杂的界面;
- 优化触控算法,添加滑动、缩放、长按等手势识别;
- 结合DMA传输,提升LCD绘制效率,减少CPU占用;
- 增加亮度自动调节功能(结合光敏传感器)。






