Điều khiển màn hình TFT LCD bằng giao thức FSMC trên STM32

Trong các hệ thống nhúng sử dụng STM32, việc hiển thị dữ liệu lên màn hình TFT LCD đòi hỏi tốc độ xử lý cao. Giao thức FSMC (Flexible Static Memory Controller) là giải pháp tối ưu nhất để giao tiếp với các bộ điều khiển LCD nhờ khả năng ánh xạ bộ nhớ trực tiếp, giúp việc ghi lệnh và dữ liệu nhanh như thao tác với RAM nội bộ.

1. Cấu hình phần cứng và ánh xạ địa chỉ

Trong thiết kế này, chân RS (Register Select) của LCD được nối với đường địa chỉ A6 của FSMC và chân chọn chip CS nối với vùng NE4. Với bus dữ liệu 16-bit, vùng nhớ Bank 1 Sector 4 sẽ bắt đầu từ địa chỉ 0x6C000000. Do cơ chế dịch bit địa chỉ của STM32 khi ở chế độ 16-bit, chúng ta cần tính toán offset chính xác cho chân A6.

// Cấu trúc quản lý thanh ghi và bộ nhớ đệm LCD
typedef struct {
    volatile uint16_t REG_ADDR; // Địa chỉ ghi lệnh (RS = 0)
    uint16_t RAM_DATA;          // Địa chỉ ghi dữ liệu (RS = 1)
} TFT_MemoryMap_TypeDef;

/* 
   Sử dụng Bank1 Sector4: Địa chỉ cơ sở 0x6C000000
   Với chân A6 làm chân RS: 
   Dịch bit 16-bit: Địa chỉ thực tế = (0x6C000000 | 0x0000007E)
*/
#define TFT_BASE_ADDR        ((uint32_t)(0x6C000000 | 0x0000007E))
#define LCD_CTRL             ((TFT_MemoryMap_TypeDef *) TFT_BASE_ADDR)

Khi sử dụng cấu trúc này, việc chuyển đổi giữa ghi lệnh và ghi dữ liệu trở nên cực kỳ đơn giản:

  • LCD_CTRL->REG_ADDR = CMD; sẽ kéo chân RS xuống thấp để gửi lệnh.
  • LCD_CTRL->RAM_DATA = DATA; sẽ kéo chân RS lên cao để gửi dữ liệu màu sắc hoặc tham số.

2. Quản lý thông số kỹ thuật màn hình

Để driver có tính tương thích cao với nhiều loại IC điều khiển khác nhau (như ILI9341, NT35310, hay SSD1963), chúng ta định nghĩa một cấu trúc lưu trữ các thông tin quan trọng của thiết bị hiện tại.

typedef struct {
    uint16_t screen_width;   // Chiều rộng hiển thị
    uint16_t screen_height;  // Chiều cao hiển thị
    uint16_t device_id;      // Mã ID của IC Driver
    uint8_t  orientation;    // Hướng hiển thị (0: Dọc, 1: Ngang)
    uint16_t cmd_write_gram; // Lệnh ghi vào GRAM
    uint16_t cmd_set_x;      // Lệnh thiết lập tọa độ X
    uint16_t cmd_set_y;      // Lệnh thiết lập tọa độ Y
} TFT_Device_Manager;

TFT_Device_Manager g_tft_dev; // Biến toàn cục quản lý màn hình

3. Các hàm xử lý cấp thấp

Các hàm cơ bản dưới đây thực hiện việc giao tiếp trực tiếp với phần cứng thông qua FSMC. Việc thêm các lệnh gán giả giúp tránh lỗi khi trình biên dịch thực hiện tối ưu hóa (O2 Optimization).

// Ghi mã lệnh tới LCD
void TFT_Write_Cmd(volatile uint16_t cmd) {
    cmd = cmd; // Tránh lỗi tối ưu hóa compiler
    LCD_CTRL->REG_ADDR = cmd;
}

// Ghi dữ liệu 16-bit tới LCD
void TFT_Write_Data(volatile uint16_t data) {
    data = data;
    LCD_CTRL->RAM_DATA = data;
}

// Đọc dữ liệu từ LCD
uint16_t TFT_Read_Data(void) {
    volatile uint16_t val;
    val = LCD_CTRL->RAM_DATA;
    return val;
}

// Thiết lập tọa độ điểm ảnh (Cursor)
void TFT_Set_Cursor(uint16_t x, uint16_t y) {
    if (g_tft_dev.device_id == 0x9341 || g_tft_dev.device_id == 0x5310) {
        TFT_Write_Cmd(g_tft_dev.cmd_set_x);
        TFT_Write_Data(x >> 8); TFT_Write_Data(x & 0xFF);
        TFT_Write_Cmd(g_tft_dev.cmd_set_y);
        TFT_Write_Data(y >> 8); TFT_Write_Data(y & 0xFF);
    } 
    // Các dòng IC khác được xử lý tương tự tùy theo tập lệnh cụ thể
}

4. Thao tác vẽ điểm ảnh và hiển thị ký tự

Hàm vẽ điểm ảnh là hạt nhân của mọi chức năng đồ họa. Từ hàm này, chúng ta có thể xây dựng các hàm vẽ đường thẳng, hình chữ nhật và hiển thị văn bản.

// Vẽ một điểm màu lên màn hình
void TFT_Draw_Pixel(uint16_t x, uint16_t y, uint16_t color) {
    TFT_Set_Cursor(x, y);
    TFT_Write_Cmd(g_tft_dev.cmd_write_gram);
    LCD_CTRL->RAM_DATA = color;
}

// Hiển thị ký tự từ bộ font
void TFT_Display_Char(uint16_t x, uint16_t y, uint8_t chr, uint8_t size, uint8_t is_overlay, uint16_t color) {
    uint8_t temp, t, t1;
    uint16_t y_start = y;
    // Tính toán kích thước bộ đệm font dựa trên size (12, 16, 24, 32)
    uint8_t char_bytes = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2);
    uint8_t offset = chr - ' '; // Lấy vị trí trong bảng ASCII

    for (t = 0; t < char_bytes; t++) {
        // Lấy dữ liệu font (ví dụ cho size 16)
        if (size == 16) temp = ascii_1608[offset][t];
        else continue;

        for (t1 = 0; t1 < 8; t1++) {
            if (temp & 0x80) {
                TFT_Draw_Pixel(x, y, color);
            } else if (is_overlay == 0) {
                TFT_Draw_Pixel(x, y, BACK_COLOR);
            }
            temp <<= 1;
            y++;
            if ((y - y_start) == size) {
                y = y_start;
                x++;
                break;
            }
        }
    }
}

5. Quy trình khởi tạo hệ thống

Hàm khởi tạo thực hiện hai nhiệm vụ chính: Cấu hình phần cứng FSMC và nhận diện mã ID của màn hình để nạp tham số tương ứng.

void TFT_Init_Sequence(void) {
    // 1. Cấu hình GPIO cho FSMC (D0-D15, NOE, NWE, NE4, A6)
    // 2. Thiết lập thời gian đáp ứng (Timing) cho SRAM Bank 4
    // 3. Reset LCD và đọc mã ID
    
    TFT_Write_Cmd(0x0000);
    g_tft_dev.device_id = TFT_Read_Reg(0x0000);
    
    // Nếu không đọc được ID bằng cách thông thường, thử các lệnh đặc biệt cho ILI9341/NT35310
    if (g_tft_dev.device_id == 0 || g_tft_dev.device_id == 0xFFFF) {
        TFT_Write_Cmd(0xD3);
        TFT_Read_Data(); // Dummy read
        TFT_Read_Data(); // 0x00
        g_tft_dev.device_id = TFT_Read_Data(); // 0x93
        g_tft_dev.device_id <<= 8;
        g_tft_dev.device_id |= TFT_Read_Data(); // 0x41
    }

    // 4. Dựa vào ID để cấu hình tập lệnh và vùng nhớ GRAM
    if (g_tft_dev.device_id == 0x9341) {
        g_tft_dev.cmd_set_x = 0x2A;
        g_tft_dev.cmd_set_y = 0x2B;
        g_tft_dev.cmd_write_gram = 0x2C;
        // Gọi chuỗi lệnh khởi tạo riêng cho ILI9341...
    }
}

6. Chương trình chính

Trong hàm main, sau khi khởi tạo hệ thống và LCD, chúng ta thực hiện vòng lặp thay đổi màu nền và hiển thị thông tin hệ thống để kiểm tra khả năng hoạt động của màn hình.

int main(void) {
    char info_buffer[20];
    uint8_t color_idx = 0;

    HAL_Init();
    SystemClock_Config(); // 168MHz cho F4
    TFT_Init_Sequence();
    
    sprintf(info_buffer, "ID Device: %04X", g_tft_dev.device_id);
    
    while(1) {
        // Xóa màn hình với các màu khác nhau
        TFT_Clear_Screen(get_color_by_index(color_idx));
        
        // Hiển thị nhãn
        TFT_Print_String(30, 50, "STM32 FSMC Driver", 24, RED);
        TFT_Print_String(30, 80, info_buffer, 16, BLUE);
        
        color_idx++;
        if(color_idx > 10) color_idx = 0;
        
        HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); // Nháy LED trạng thái
        HAL_Delay(1000);
    }
}

Thẻ: STM32 FSMC TFT LCD ILI9341 Embedded C

Đăng vào ngày 13 tháng 11 lúc 16:24