正點原子Linux Framebuffer程式設計:解決示例程式在開發板上LCD顯示錯位和顏色異常

月舞纱雨日發表於2024-09-01

正點原子Linux Framebuffer程式設計:解決示例程式在開發板上執行7寸LCD顯示錯位和顏色異常

作者在學習【正點原子】I.MX6U嵌入式Linux C應用程式設計指南V1.4時,發現其配套的程式在開發板上執行不正常。

使用的硬體版本:正點原子 I.MX6U ALPHA V2.4版本底板LCD:正點原子7寸1024*600,型號ATK-MD0700R-1024600

問題描述

經研究發現問題主要有

  1. 我開發板使用的LCD顏色格式是RGB888,而非RGB565,導致顏色錯位
  2. 老師使用的LCD尺寸和筆者一樣,但解析度卻不同(沒仔細看直接用導致顯示錯位。。。。)

在手冊587-588頁可看到如下片段:

image
image

本人跟著左盟主從ubuntu入門操作開始學,軟硬體的教程一直是沒問題的。到C應用程式設計換了個主講人,影片也是比較新的日期錄製的。因為稍微更換了使用的硬體平臺,示例程式自然會發生變動。

解決過程

下面是老師的示例程式:

/***************************************************************
 Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.
 檔名 : lcd_test.c
 作者 : 鄧濤
 版本 : V1.0
 描述 : FrameBuffer應用程式示例程式碼
 其他 : 無
 論壇 : www.openedv.com
 日誌 : 初版 V1.0 2021/6/15 鄧濤建立
 ***************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

#define argb8888_to_rgb565(color)   ({ \
            unsigned int temp = (color); \
            ((temp & 0xF80000UL) >> 8) | \
            ((temp & 0xFC00UL) >> 5) | \
            ((temp & 0xF8UL) >> 3); \
            })

static int width;                   //LCD X解析度
static int height;                      //LCD Y解析度
static unsigned short *screen_base = NULL;      //對映後的視訊記憶體基地址

/********************************************************************
 * 函式名稱: lcd_draw_point
 * 功能描述: 打點
 * 輸入引數: x, y, color
 * 返 回 值: 無
 ********************************************************************/
static void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);//得到RGB565顏色值

    /* 對傳入引數的校驗 */
    if (x >= width)
        x = width - 1;
    if (y >= height)
        y = height - 1;

    /* 填充顏色 */
    screen_base[y * width + x] = rgb565_color;
}

/********************************************************************
 * 函式名稱: lcd_draw_line
 * 功能描述: 畫線(水平或垂直線)
 * 輸入引數: x, y, dir, length, color
 * 返 回 值: 無
 ********************************************************************/
static void lcd_draw_line(unsigned int x, unsigned int y, int dir,
            unsigned int length, unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);//得到RGB565顏色值
    unsigned int end;
    unsigned long temp;

    /* 對傳入引數的校驗 */
    if (x >= width)
        x = width - 1;
    if (y >= height)
        y = height - 1;

    /* 填充顏色 */
    temp = y * width + x;//定位到起點
    if (dir) {  //水平線
        end = x + length - 1;
        if (end >= width)
            end = width - 1;

        for ( ; x <= end; x++, temp++)
            screen_base[temp] = rgb565_color;
    }
    else {  //垂直線
        end = y + length - 1;
        if (end >= height)
            end = height - 1;

        for ( ; y <= end; y++, temp += width)
            screen_base[temp] = rgb565_color;
    }
}

/********************************************************************
 * 函式名稱: lcd_draw_rectangle
 * 功能描述: 畫矩形
 * 輸入引數: start_x, end_x, start_y, end_y, color
 * 返 回 值: 無
 ********************************************************************/
static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x,
            unsigned int start_y, unsigned int end_y,
            unsigned int color)
{
    int x_len = end_x - start_x + 1;
    int y_len = end_y - start_y - 1;

    lcd_draw_line(start_x, start_y, 1, x_len, color);//上邊
    lcd_draw_line(start_x, end_y, 1, x_len, color); //下邊
    lcd_draw_line(start_x, start_y + 1, 0, y_len, color);//左邊
    lcd_draw_line(end_x, start_y + 1, 0, y_len, color);//右邊
}

/********************************************************************
 * 函式名稱: lcd_fill
 * 功能描述: 將一個矩形區域填充為引數color所指定的顏色
 * 輸入引數: start_x, end_x, start_y, end_y, color
 * 返 回 值: 無
 ********************************************************************/
static void lcd_fill(unsigned int start_x, unsigned int end_x,
            unsigned int start_y, unsigned int end_y,
            unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);//得到RGB565顏色值
    unsigned long temp;
    unsigned int x;

    /* 對傳入引數的校驗 */
    if (end_x >= width)
        end_x = width - 1;
    if (end_y >= height)
        end_y = height - 1;

    /* 填充顏色 */
    temp = start_y * width; //定位到起點行首
    for ( ; start_y <= end_y; start_y++, temp+=width) {

        for (x = start_x; x <= end_x; x++)
            screen_base[temp + x] = rgb565_color;
    }
}

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    /* 開啟framebuffer裝置 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    /* 獲取引數資訊 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    screen_size = fb_fix.line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;

    /* 將顯示緩衝區對映到程序地址空間 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 畫正方形方塊 */
    int w = height * 0.25;//方塊的寬度為1/4螢幕高度
    lcd_fill(0, width-1, 0, height-1, 0x0); //清屏(螢幕顯示黑色)
    lcd_fill(0, w, 0, w, 0xFF0000); //紅色方塊
    lcd_fill(width-w, width-1, 0, w, 0xFF00);   //綠色方塊
    lcd_fill(0, w, height-w, height-1, 0xFF);   //藍色方塊
    lcd_fill(width-w, width-1, height-w, height-1, 0xFFFF00);//黃色方塊

    /* 畫線: 十字交叉線 */
    lcd_draw_line(0, height * 0.5, 1, width, 0xFFFFFF);//白色線
    lcd_draw_line(width * 0.5, 0, 0, height, 0xFFFFFF);//白色線

    /* 畫矩形 */
    unsigned int s_x, s_y, e_x, e_y;
    s_x = 0.25 * width;
    s_y = w;
    e_x = width - s_x;
    e_y = height - s_y;

    for ( ; (s_x <= e_x) && (s_y <= e_y);
            s_x+=5, s_y+=5, e_x-=5, e_y-=5)
        lcd_draw_rectangle(s_x, e_x, s_y, e_y, 0xFFFFFF);

    /* 退出 */
    munmap(screen_base, screen_size);  //取消對映
    close(fd);  //關閉檔案
    exit(EXIT_SUCCESS);    //退出程序
}

直接編譯執行會出現以下問題:

可以看到影像顯示錯位、顏色顯示不正常

image

對比程式碼。問題主要出現在以下幾個方面

  1. RGB顏色模型不同,導致顏色非正常
  2. 解析度設定錯誤

聽起來是不是很簡單,解析度雖不同,但是是透過如下程式碼動態獲取的,我們不需要更改

    /* 獲取引數資訊 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    screen_size = fb_fix.line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;

那是不是隻需要把argb8888_to_rgb565這個宏去掉,就能改好了呢?

很遺憾並不能。

去掉宏之後顏色變了,但還是不對,有三個色塊的顏色都是綠色。。。

別急,我已經摸索到了解決辦法!

接下來是見證奇蹟的時刻,如果你在使用vim,在普通模式下輸入以下命令:

:%s/short/int/g

將短整型關鍵字全部替換為整形,在虛擬機器中使用arm-linux-gnueabihf-gcc -o lcd_test lcd_test.c編譯,在開發板上執行,解決!

image

問題所在

老師使用的7寸800*480螢幕的RGB格式是RGB565。

RGB565

  • 這種格式將紅色和藍色各分配了5位,綠色分配了6位,總共16位。
  • 紅色和藍色的強度可以有32級(從0到31),綠色可以有64級(從0到63)。
  • RGB565可以表示大約65,536種不同的顏色(2^16)。
  • 由於人眼對綠色的敏感度高於紅色和藍色,所以RGB565透過為綠色分配更多的位來最佳化顏色表現。

而我使用的是7寸1024*600螢幕是RGB888格式

RGB888

  • 這種格式為紅色、綠色和藍色各分配了8位(bit),總共24位。
  • 每種顏色的強度可以有256級(從0到255)。
  • 因此,RGB888可以表示大約1677萬種不同的顏色(256 x 256 x 256)。
  • 由於每個顏色通道都有8位,所以它可以提供更平滑的顏色過渡和更豐富的顏色細節。

當然,螢幕在硬體上大多數是支援多種顏色格式的,如同上文所述可以透過修改裝置樹或透過ioctl更改,在此只介紹最簡便的解決方法

這兩種格式不僅在RGB每個顏色佔的bit數量比例不一樣,最重要的是佔的總位數不一樣

前者通常使用short短整型16位變數儲存顏色資料,後者則需要24位,因為位元組對齊和變數訪問便利通常使用32位整型儲存顏色資料

在老師的程式碼中,使用mmap將fb裝置資料緩衝區的起始地址對映到screen_base (short型別指標變數)

static unsigned short *screen_base = NULL;      //對映後的視訊記憶體基地址
/* 將顯示緩衝區對映到程序地址空間 */
screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);

然後在程式碼中填充顏色的時候訪問都是按照short的長度依次訪問,每次偏移16位,導致0xFFFFFF這樣的32位RGB888資料填充錯誤,後來的資料會覆蓋前資料的一部分,

/* 填充顏色 */
screen_base[y * width + x] = rgb565_color;

最後附上修改後的完整程式碼:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

#define UNUSED_argb8888_to_rgb565(color)   (color)

static int width;                   //LCD X解析度
static int height;                      //LCD Y解析度
static unsigned int *screen_base = NULL;      //對映後的視訊記憶體基地址

/********************************************************************
 * 函式名稱: lcd_draw_point
 * 功能描述: 打點
 * 輸入引數: x, y, color
 * 返 回 值: 無
 ********************************************************************/
static void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color)
{
    unsigned int rgb565_color = UNUSED_argb8888_to_rgb565(color);//不需要轉換,本身就是RGB888

    /* 對傳入引數的校驗 */
    if (x >= width)
        x = width - 1;
    if (y >= height)
        y = height - 1;

    /* 填充顏色 */
    screen_base[y * width + x] = rgb565_color;
}

/********************************************************************
 * 函式名稱: lcd_draw_line
 * 功能描述: 畫線(水平或垂直線)
 * 輸入引數: x, y, dir, length, color
 * 返 回 值: 無
 ********************************************************************/
static void lcd_draw_line(unsigned int x, unsigned int y, int dir,
            unsigned int length, unsigned int color)
{
    unsigned int rgb565_color = UNUSED_argb8888_to_rgb565(color);//不需要轉換,本身就是RGB888
    unsigned int end;
    unsigned long temp;

    /* 對傳入引數的校驗 */
    if (x >= width)
        x = width - 1;
    if (y >= height)
        y = height - 1;

    /* 填充顏色 */
    temp = y * width + x;//定位到起點
    if (dir) {  //水平線
        end = x + length - 1;
        if (end >= width)
            end = width - 1;

        for ( ; x <= end; x++, temp++)
            screen_base[temp] = rgb565_color;
    }
    else {  //垂直線
        end = y + length - 1;
        if (end >= height)
            end = height - 1;

        for ( ; y <= end; y++, temp += width)
            screen_base[temp] = rgb565_color;
    }
}

/********************************************************************
 * 函式名稱: lcd_draw_rectangle
 * 功能描述: 畫矩形
 * 輸入引數: start_x, end_x, start_y, end_y, color
 * 返 回 值: 無
 ********************************************************************/
static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x,
            unsigned int start_y, unsigned int end_y,
            unsigned int color)
{
    int x_len = end_x - start_x + 1;
    int y_len = end_y - start_y - 1;

    lcd_draw_line(start_x, start_y, 1, x_len, color);//上邊
    lcd_draw_line(start_x, end_y, 1, x_len, color); //下邊
    lcd_draw_line(start_x, start_y + 1, 0, y_len, color);//左邊
    lcd_draw_line(end_x, start_y + 1, 0, y_len, color);//右邊
}

/********************************************************************
 * 函式名稱: lcd_fill
 * 功能描述: 將一個矩形區域填充為引數color所指定的顏色
 * 輸入引數: start_x, end_x, start_y, end_y, color
 * 返 回 值: 無
 ********************************************************************/
static void lcd_fill(unsigned int start_x, unsigned int end_x,
            unsigned int start_y, unsigned int end_y,
            unsigned int color)
{
    unsigned int rgb565_color = UNUSED_argb8888_to_rgb565(color);//不需要轉換,本身就是RGB888
    unsigned long temp;
    unsigned int x;

    /* 對傳入引數的校驗 */
    if (end_x >= width)
        end_x = width - 1;
    if (end_y >= height)
        end_y = height - 1;

    /* 填充顏色 */
    temp = start_y * width; //定位到起點行首
    for ( ; start_y <= end_y; start_y++, temp+=width) {

        for (x = start_x; x <= end_x; x++)
            screen_base[temp + x] = rgb565_color;
    }
}

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;
    struct fb_var_screeninfo fb_var;
    unsigned int screen_size;
    int fd;

    /* 開啟framebuffer裝置 */
    if (0 > (fd = open("/dev/fb0", O_RDWR))) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    /* 獲取引數資訊 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    screen_size = fb_fix.line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;

    /* 將顯示緩衝區對映到程序地址空間 */
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if (MAP_FAILED == (void *)screen_base) {
        perror("mmap error");
        close(fd);
        exit(EXIT_FAILURE);
    }

    /* 畫正方形方塊 */
    int w = height * 0.25;//方塊的寬度為1/4螢幕高度
    lcd_fill(0, width-1, 0, height-1, 0x0); //清屏(螢幕顯示黑色)
    lcd_fill(0, w, 0, w, 0xFF0000); //紅色方塊
    lcd_fill(width-w, width-1, 0, w, 0xFF00);   //綠色方塊
    lcd_fill(0, w, height-w, height-1, 0xFF);   //藍色方塊
    lcd_fill(width-w, width-1, height-w, height-1, 0xFFFF00);//黃色方塊

    /* 畫線: 十字交叉線 */
    lcd_draw_line(0, height * 0.5, 1, width, 0xFFFFFF);//白色線
    lcd_draw_line(width * 0.5, 0, 0, height, 0xFFFFFF);//白色線

    /* 畫矩形 */
    unsigned int s_x, s_y, e_x, e_y;
    s_x = 0.25 * width;
    s_y = w;
    e_x = width - s_x;
    e_y = height - s_y;

    for ( ; (s_x <= e_x) && (s_y <= e_y);
            s_x+=5, s_y+=5, e_x-=5, e_y-=5)
        lcd_draw_rectangle(s_x, e_x, s_y, e_y, 0xFFFFFF);

    /* 退出 */
    munmap(screen_base, screen_size);  //取消對映
    close(fd);  //關閉檔案
    exit(EXIT_SUCCESS);    //退出程序
}

相關文章