欢迎光临
我们一直在努力

DAY66 LCD Display and PWM Brightness Adjustment Based on IMX6ULL + Multi-Touch Implementation

LCD Display and PWM Brightness Adjustment Based on IMX6ULL + Multi-Touch Implementation

1. Core Principle Analysis

1.1 IMX6ULL LCD Display Principle

The IMX6ULL integrates an LCDIF (LCD Interface) controller, specifically responsible for transmitting image data from the frame buffer to an external LCD screen. This article uses an 800*480 resolution LCD screen with a 24-bit parallel RGB interface. The core principles are as follows:

  • Data Transmission: The LCDIF controller sends pixel data from the frame buffer line by line and column by column to the LCD screen via the 24-bit data bus (DATA00~DATA23).
  • Synchronization Signals: The HSYNC (horizontal sync) and VSYNC (vertical sync) signals synchronize the screen’s line/frame scanning, working with the LCD_CLK pixel clock to ensure data transmission timing.
  • Timing Parameters: Configure the horizontal/vertical sync pulse width (HSPW/VSPW) and front/back porch (HBP/HFP/VBP/VFP) to match the LCD screen’s scanning timing.
  • Frame Buffer Planning: Use the SDRAM address 0x89000000 as the frame buffer, with each pixel occupying 4 bytes (RGB888 format). Directly manipulating this address enables pixel drawing.

1.2 PWM Backlight Dimming Principle

LCD backlighting is typically driven by LEDs. The IMX6ULL’s PWM module adjusts backlight brightness through Pulse Width Modulation (PWM), with the following core logic:

  • Duty Cycle Control: The duty cycle of the PWM output’s high/low levels determines the average operating current of the LED. A higher duty cycle (longer high-level duration) results in higher brightness.
  • Hardware Mapping: Multiplex GPIO1_IO08 as the PWM1_OUT pin, connected to the backlight-driving MOSFET. The PWM signal controls the MOSFET’s switching.
  • Dynamic Adjustment: Update the duty cycle register (PWMSAR) in real-time via PWM interrupts to achieve smooth brightness adjustment.

1.3 Multi-Touch Principle

This article implements multi-touch using the GT911 touch chip based on the I2C interface. The core workflow includes:

  • Touch Detection: The GT911 detects capacitance changes at touch points in real-time, identifying touch coordinates and the number of touch points.
  • Data Transmission: The touch chip reports touch data packets (including touch point IDs and X/Y coordinates) to the IMX6ULL via the I2C bus.
  • Multi-Touch Parsing: Parse the data packets to distinguish different touch points, enabling single-point drawing, two-point zooming/brightness adjustment, and other interactive logic.

2. Hardware Circuit Description

  • LCD Interface: The IMX6ULL’s LCDIF pins (DATA00~23, HSYNC, VSYNC, LCD_CLK) are directly connected to the LCD screen’s 24-bit RGB parallel interface.
  • PWM Backlight: GPIO1_IO08 (PWM1_OUT) is connected to the gate of an N-channel MOSFET, whose drain is connected to the positive terminal of the backlight LED and whose source is grounded. The PWM signal controls the LED’s power supply.
  • Touch Interface: The IMX6ULL’s I2C1_SCL/I2C1_SDA are connected to the GT911’s I2C interface. The GT911’s INT pin is connected to the IMX6ULL’s GPIO interrupt pin for reporting touch events.

3. Code Implementation Details

3.1 Project File Structure

├── bsp/
│ ├── lcd.c // LCD initialization and drawing functions
│ ├── lcd.h // LCD data structures and function declarations
│ ├── pwm.c // PWM initialization and duty cycle adjustment
│ ├── pwm.h // PWM function declarations
│ ├── touch.c // Touch data reading and parsing
│ ├── touch.h // Touch-related definitions
│ ├── gpt.c // Delay function implementation
│ └── gpt.h // Delay function declarations
├── imx6ull/
│ ├── fsl_iomuxc.h // Pin multiplexing configuration
│ └── MCIMX6Y2.h // Register definitions
└── main.c // Main function (module initialization + interaction logic)

3.2 LCD Display Module Implementation

3.2.1 Header File Definition (lcd.h)

Core definitions for LCD timing parameters, frame buffer address, and operational functions:

#ifndef _LCD_H_
#define _LCD_H_

// LCD device structure (stores resolution, timing, frame buffer parameters)
struct tftlcd_t {
unsigned int width; // Width
unsigned int height; // Height
unsigned int pix_size; // Pixel byte size
unsigned int hspw; // Horizontal sync pulse width
unsigned int hbp; // Horizontal back porch
unsigned int hfp; // Horizontal front porch
unsigned int vspw; // Vertical sync pulse width
unsigned int vbp; // Vertical back porch
unsigned int vfp; // Vertical front porch
unsigned int frame_addr;// Frame buffer address
unsigned int fore_color;// Foreground color
unsigned int back_color;// Background color
};

#define _FRAME_RAM_ADDRESS (0x89000000) // Frame buffer start address
extern struct tftlcd_t lcd_dev;
extern void lcd_init(void); // LCD initialization
extern void lcd_drawpoint(int x, int y, unsigned int color); // Draw pixel
extern void screen_clear(unsigned int color); // Clear screen
#endif // !_LCD_H_

3.2.2 LCD Core Implementation (lcd.c)

Includes pin initialization, clock configuration, and controller parameter configuration:

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

// Pin multiplexing and electrical characteristics configuration
void lcd_pad_init(void) {
// Multiplex 24-bit data pins as LCDIF function
IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0);
IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0);
// … Omitted DATA02~DATA23 configurations (same format as above)
IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_LCDIF_DATA23, 0);

// Sync/clock pin multiplexing
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);

// Configure electrical properties for all LCD pins (0xB9: Disable pull-up/down + speed settings)
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00, 0xB9);
IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01, 0xB9);
// … Omitted DATA02~DATA23 configurations
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 clock initialization (PLL5 outputs 31MHz as LCDIF clock source)
void lcd_clk_init(void) {
// Configure PLL5 (VIDEO PLL) parameters: 24M crystal * (31+0/1) = 744M, output 31M after division
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); // Enable PLL5

// Configure LCDIF clock division
t = CCM->CSCDR2;
t |= (2 << 15); // LCDIF1_PRE_CLK_SEL: Select PLL5
t &= ~(7 << 12);
t |= (3 << 12); // LCDIF1_PREDIV: Prescaler
t &= ~(7 << 9); // LCDIF1_CLK_SEL: Clock source selection
CCM->CSCDR2 = t;
CCM->CBCMR &= ~(7 << 23);
CCM->CBCMR |= (5 << 23); // LCDIF1_PODF: Final division
}

// LCD controller reset
void reset_lcd(void) {
LCDIF->CTRL |= (1 << 31);
delay_ms(20);
LCDIF->CTRL &= ~(1 << 31);
LCDIF->CTRL &= ~(1 << 30);
}

struct tftlcd_t lcd_dev;
// Main LCD initialization function
void lcd_init(void) {
lcd_pad_init(); // Pin initialization
reset_lcd(); // Controller reset
lcd_clk_init(); // Clock configuration
delay_ms(50);

// Configure 800*480 resolution LCD timing parameters
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; // Foreground color: red
lcd_dev.back_color = 0x00FFFFFF; // Background color: white

// LCDIF controller configuration (24-bit RGB, sync mode)
LCDIF->CTRL |= (1 << 19) | (1 << 17) | (3 << 10) | (3 << 8) | (1 << 5);
LCDIF->CTRL1 &= ~(0xF << 16);
LCDIF->CTRL1 |= (0x7 << 16);

// Set transfer resolution and frame buffer address
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;

// Configure timing registers
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); // Enable LCDIF
delay_ms(50);
screen_clear(lcd_dev.back_color); // Clear screen with background color
}

// Draw single pixel (core: directly manipulate frame buffer address)
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) { // Boundary check
return;
}
*(p + lcd_dev.width * y + x) = color; // Calculate pixel address and assign value
}

// Full-screen clear
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 Brightness Adjustment Module Implementation

3.3.1 PWM Core Code (pwm.c)

Implementation of PWM initialization, duty cycle adjustment, and interrupt-driven dynamic brightness control:

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

static float g_dc = 0.5; // Global duty cycle (0~1)

// Set duty cycle
void set_a_dc(float dc)
{
g_dc = dc;
}

// Get current duty cycle
float get_a_dc(void)
{
return g_dc;
}

// Update PWM compare register based on duty cycle
void set_ratio(float dc)
{
unsigned short temp = PWM1->PWMPR; // Period register value
int i = 0;
for (i = 0; i < 4; i++)
{
PWM1->PWMSAR = temp * dc; // Duty cycle = PWMSAR / PWMPR
}
}

// PWM interrupt service function (real-time duty cycle update)
void pwm_interrupt_handler(void)
{
if ((PWM1->PWMSR & (1 << 3))) // Check interrupt flag
{
set_ratio(g_dc); // Update duty cycle
printf("Current duty cycle: %d%%\\n", (int)(g_dc * 100));
PWM1->PWMSR |= (1 << 3); // Clear interrupt flag
}
}

// PWM1 initialization
void pwm1_init(void)
{
// Pin multiplexing: GPIO1_IO08 -> PWM1_OUT
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_PWM1_OUT, 0);
// Electrical characteristics configuration
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_PWM1_OUT, 0xB9);

// PWM controller reset
PWM1->PWMCR |= (1 << 3);
while ((PWM1->PWMCR & (1 << 3)) != 0);

// PWM mode configuration: clock source=IPG_CLK, divider=65, alignment mode=center-aligned
PWM1->PWMCR = 0;
PWM1->PWMCR |= (1 << 26) | (1 << 16) | (65 << 4) | (3 << 1);

PWM1->PWMIR |= (1 << 0); // Enable PWM interrupt
PWM1->PWMPR = 1000 2; // Period: 1000 clock cycles (~1ms)

// Interrupt configuration: register interrupt function + enable IRQ + set priority
system_interrupt_register(PWM1_IRQn, pwm_interrupt_handler);
GIC_EnableIRQ(PWM1_IRQn);
GIC_SetPriority(PWM1_IRQn, 0);

set_ratio(0.5); // Initial duty cycle 50%
PWM1->PWMCR |= (1 << 0); // Enable PWM1
}

3.4 Multi-Touch Module Implementation (GT911 Example Supplement)

3.4.1 Touch Header File (touch.h)

#ifndef _TOUCH_H_
#define _TOUCH_H_

#define TOUCH_I2C_ADDR 0xBA >> 1 // GT911 I2C address

// Touch data structure
typedef struct {
int point_num; // Number of touch points
int x[5]; // X coordinates of touch points (up to 5 points)
int y[5]; // Y coordinates of touch points
int id[5]; // Touch point IDs
} touch_data_t;

extern void touch_init(void); // Touch initialization
extern int touch_get_data(touch_data_t *data); // Get touch data
#endif // !_TOUCH_H_

3.4.2 Touch Core Implementation (touch.c)

#include "touch.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "gpt.h"

// I2C initialization (for communication with GT911)
void i2c1_init(void)
{
// Pin multiplexing: GPIO1_IO02=I2C1_SCL, GPIO1_IO03=I2C1_SDA
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO02_I2C1_SCL, 1);
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_I2C1_SDA, 1);
// Electrical characteristics: open-drain output + pull-up
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO02_I2C1_SCL, 0x70B0);
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_I2C1_SDA, 0x70B0);

// I2C configuration: 400KHz rate
I2C1->I2CR = 0;
I2C1->I2FR = 0x14; // Divider coefficient
I2C1->I2CR = (1 << 7) | (1 << 5); // Enable I2C master mode
}

// I2C write data
void i2c_write(unsigned char reg, unsigned char data)
{
while(I2C1->I2SR & (1 << 4)); // Wait for bus idle
I2C1->I2DR = TOUCH_I2C_ADDR; // Send device address
I2C1->I2CR |= (1 << 4); // Start transmission

while(!(I2C1->I2SR & (1 << 1))); // Wait for ACK
I2C1->I2SR &= ~(1 << 1);
I2C1->I2DR = reg; // Send register address

while(!(I2C1->I2SR & (1 << 1)));
I2C1->I2SR &= ~(1 << 1);
I2C1->I2DR = data; // Send data

while(!(I2C1->I2SR & (1 << 1)));
I2C1->I2SR &= ~(1 << 1);
I2C1->I2CR |= (1 << 3); // Stop transmission
}

// I2C read data
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); // Repeat start + read mode
I2C1->I2DR = TOUCH_I2C_ADDR | 0x01;

while(!(I2C1->I2SR & (1 << 1)));
I2C1->I2SR &= ~(1 << 1);
I2C1->I2CR &= ~(1 << 2); // Disable ACK

while(!(I2C1->I2SR & (1 << 0))); // Wait for data reception
data = I2C1->I2DR;
I2C1->I2CR |= (1 << 3); // Stop transmission
return data;
}

// Touch initialization
void touch_init(void)
{
i2c1_init();
// Configure GT911: 800*480 resolution, enable multi-touch
i2c_write(0x8000, 0x00);
i2c_write(0x8001, 0x00);
i2c_write(0x8002, 0x03); // Maximum touch points: 3
i2c_write(0x8003, 0x20); // X-axis maximum: 800
i2c_write(0x8004, 0xE0);
i2c_write(0x8005, 0x01); // Y-axis maximum: 480
i2c_write(0x8006, 0xE0);
}

// Get touch data
int touch_get_data(touch_data_t *data)
{
unsigned char buf[16];
// Read touch status register
buf[0] = i2c_read(0x814E);
data->point_num = buf[0] & 0x0F; // Extract number of touch points
if(data->point_num == 0) return 0;

// Read touch point coordinates
for(int i=0; i<data->point_num; i++)
{
buf[1+i*4] = i2c_read(0x814F + i*4); // X coordinate high 8 bits
buf[2+i*4] = i2c_read(0x8150 + i*4); // X coordinate low 8 bits
buf[3+i*4] = i2c_read(0x8151 + i*4); // Y coordinate high 8 bits
buf[4+i*4] = i2c_read(0x8152 + i*4); // Y coordinate low 8 bits
buf[5+i*4] = i2c_read(0x8153 + i*4); // Touch point 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;
}
// Clear touch flag
i2c_write(0x814E, 0x00);
return 1;
}

3.5 Main Function Integration (main.c)

Implement LCD display, PWM dimming, and touch interaction linkage:

#include "lcd.h"
#include "pwm.h"
#include "gpt.h"
#include "touch.h"
#include "stdio.h"
#include "math.h"

int main(void)
{
// Basic initialization
gpt1_init(); // Delay initialization
lcd_init(); // LCD initialization
pwm1_init(); // PWM initialization
touch_init(); // Touch initialization

// Initial screen clear + draw test point
screen_clear(lcd_dev.back_color);
lcd_drawpoint(100, 100, lcd_dev.fore_color);

// Main loop: Handle touch events
while(1)
{
touch_data_t touch_data;
if(touch_get_data(&touch_data))
{
// Single-touch: Draw red pixel
if(touch_data.point_num == 1)
{
lcd_drawpoint(touch_data.x[0], touch_data.y[0], 0x00FF0000);
}
// Dual-touch: Adjust brightness based on distance (greater distance = higher brightness)
else if(touch_data.point_num == 2)
{
// Calculate Euclidean distance between two points
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);
// Map distance to 0~1 duty cycle (max distance 500)
float dc = (float)distance / 500;
if(dc > 1.0) dc = 1.0;
if(dc < 0.0) dc = 0.0;
set_a_dc(dc); // Update PWM duty cycle
}
}
delay_ms(10); // Reduce polling frequency to lower CPU usage
}
return 0;
}

4. Testing and Verification

4.1 Compilation and Flashing

Compile the code into a bin file using the IMX6ULL cross-compilation toolchain, then flash it to the development board via SD card or JTAG.

4.2 Functional Verification

  • LCD Display: After powering on the development board, the LCD screen lights up with a white background, displaying a red test point at position (100, 100).
  • Single-Touch: Tap any position on the screen with a finger to draw a red pixel.
  • Dual-Touch Dimming: Touch the screen with two fingers and spread them apart or bring them closer—the screen brightness changes with the distance (brighter when spread farther apart).
  • Serial Output: The serial terminal prints the current PWM duty cycle (e.g., “Current duty cycle: 70%”) to verify duty cycle updates.
  • 5. Summary and Expansion

    This article fully implements three core functionalities of the IMX6ULL—LCD display, PWM backlight dimming, and multi-touch—covering hardware principles and code implementation from register configuration to interaction logic. Based on this foundation, further expansions can include:

    • Integrating GUI libraries (e.g., LVGL, GUIX) for more complex interfaces.
    • Optimizing touch algorithms to add gesture recognition (e.g., swipe, zoom, long press).
    • Combining DMA transfers to improve LCD drawing efficiency and reduce CPU usage.
    • Adding automatic brightness adjustment (using light sensors).
    赞(0)
    未经允许不得转载:171主机测评 » DAY66 LCD Display and PWM Brightness Adjustment Based on IMX6ULL + Multi-Touch Implementation
    分享到: 更多 (0)

    评论 抢沙发

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