正點原子Linux Framebuffer程式設計:解決示例程式在開發板上執行7寸LCD顯示錯位和顏色異常
作者在學習【正點原子】I.MX6U嵌入式Linux C應用程式設計指南V1.4時,發現其配套的程式在開發板上執行不正常。
使用的硬體版本:正點原子 I.MX6U ALPHA V2.4版本底板,LCD:正點原子7寸1024*600,型號ATK-MD0700R-1024600
問題描述
經研究發現問題主要有
- 我開發板使用的LCD顏色格式是RGB888,而非RGB565,導致顏色錯位
- 老師使用的LCD尺寸和筆者一樣,但解析度卻不同(沒仔細看直接用導致顯示錯位。。。。)
在手冊587-588頁可看到如下片段:
本人跟著左盟主從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); //退出程序
}
直接編譯執行會出現以下問題:
可以看到影像顯示錯位、顏色顯示不正常
對比程式碼。問題主要出現在以下幾個方面
- RGB顏色模型不同,導致顏色非正常
- 解析度設定錯誤
聽起來是不是很簡單,解析度雖不同,但是是透過如下程式碼動態獲取的,我們不需要更改
/* 獲取引數資訊 */
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
編譯,在開發板上執行,解決!
問題所在
老師使用的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); //退出程序
}