欢迎光临
我们一直在努力

66、基于IMX6ULL的LCD显示与PWM亮度调节+多点触控实现

基于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 功能验证

  • LCD显示:开发板上电后,LCD屏点亮,显示白色背景,100,100位置有红色测试点;
  • 单点触控:用手指点击屏幕任意位置,会绘制红色像素点;
  • 两点触控调光:双指触摸屏幕并拉开/靠近,屏幕亮度随两点距离变化(拉开越远,亮度越高);
  • 串口打印:串口终端会输出当前PWM占空比(如“当前占空比:70%”),验证占空比更新。
  • 五、总结与拓展

    本文完整实现了IMX6ULL的LCD显示、PWM背光调光、多点触控三大核心功能,从底层寄存器配置到上层交互逻辑,覆盖了硬件原理和代码实现。基于该基础,可进一步拓展:

    • 集成GUI图形库(如LVGL、GUIX),实现更复杂的界面;
    • 优化触控算法,添加滑动、缩放、长按等手势识别;
    • 结合DMA传输,提升LCD绘制效率,减少CPU占用;
    • 增加亮度自动调节功能(结合光敏传感器)。
    赞(0)
    未经允许不得转载:171主机测评 » 66、基于IMX6ULL的LCD显示与PWM亮度调节+多点触控实现
    分享到: 更多 (0)

    评论 抢沙发

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