玩轉STM32MP157- 在應用層中使用 fbtft


fbtft使用的是framebuffer框架,這種框架將顯示設備抽象為幀緩沖區,對framebuffer設備(/dev/fbx(0、1、2..))進行相關操作可以反應到LCD上。

現在嘗試下在用戶空間用 C 來操作 LCD 設備。

獲取參數

要對 framebuffer 進行操作,首先要知道所操作設備的相關參數,因為要驅動一個屏,首先需要知道該屏尺寸、單色還是彩色等,Linux在用戶空間提供了兩個跟 framebuffer 參數相關的結構體(在文件 fb.h 中):

  • fb_fix_screeninfo
struct fb_fix_screeninfo {
	char id[16];			/* identification string eg "TT Builtin" */
	unsigned long smem_start;	/* Start of frame buffer mem */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* length of a line in bytes    */
	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
					/* (physical address) */
	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
	__u32 accel;			/* Indicate to driver which	*/
					/*  specific chip/card we have	*/
	__u16 capabilities;		/* see FB_CAP_*			*/
	__u16 reserved[2];		/* Reserved for future compatibility */
};
  • fb_var_screeninfo
struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution		*/
	__u32 yres;
	__u32 xres_virtual;		/* virtual resolution		*/
	__u32 yres_virtual;
	__u32 xoffset;			/* offset from virtual to visible */
	__u32 yoffset;			/* resolution			*/

	__u32 bits_per_pixel;		/* guess what			*/
	__u32 grayscale;		/* 0 = color, 1 = grayscale,	*/
					/* >1 = FOURCC			*/
	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
	struct fb_bitfield green;	/* else only length is significant */
	struct fb_bitfield blue;
	struct fb_bitfield transp;	/* transparency			*/	

	__u32 nonstd;			/* != 0 Non standard pixel format */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/* height of picture in mm    */
	__u32 width;			/* width of picture in mm     */

	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */

	/* Timing: All values in pixclocks, except pixclock (of course) */
	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
	__u32 left_margin;		/* time from sync to picture	*/
	__u32 right_margin;		/* time from picture to sync	*/
	__u32 upper_margin;		/* time from sync to picture	*/
	__u32 lower_margin;
	__u32 hsync_len;		/* length of horizontal sync	*/
	__u32 vsync_len;		/* length of vertical sync	*/
	__u32 sync;			/* see FB_SYNC_*		*/
	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 rotate;			/* angle we rotate counter clockwise */
	__u32 colorspace;		/* colorspace for FOURCC-based modes */
	__u32 reserved[4];		/* Reserved for future compatibility */
};

這兩個參數都可以通過ioctl獲得,首先定義3個變量,一個是打開的fb設備的句柄,剩下2個分別是fb_var_screeninfo、fb_fix_screeninfo:

int fbfd = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

打開fb設備:

    fbfd = open("/dev/fb0", O_RDWR);
    if (!fbfd)
    {
        printf("Error: cannot open framebuffer device.\n");
        exit(1);
    }

然后分別獲取兩個結構體,並在main函數中調用:

    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo))
    {
        printf("Error reading fixed information.\n");
        exit(2);
    }
    

	if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo))
	{
		printf("Error: reading variable information.\n");
		exit(3);
	}

創建兩個函數分別輸出這兩個結構體元素:

void show_fb_fix_info(struct fb_fix_screeninfo info)
{
    printf("fb's fix msg:\n");
    printf("\tid is:%s\n",info.id);
    printf("\tsmem_start is:%d\n",info.smem_start);
    printf("\tsmem_len is:%d\n",info.smem_len);
    printf("\ttype_aux is:%d\n",info.type_aux);
    printf("\tvisual is:%d\n",info.visual);
    printf("\txpanstep is:%d\n",info.xpanstep);
    printf("\typanstep is:%d\n",info.ypanstep);
    printf("\tywrapstep is:%d\n",info.ywrapstep);
    printf("\tline_length is:%d\n",info.line_length);
    printf("\tmmio_start is:%d\n",info.mmio_start);
}

void show_fb_var_info(struct fb_var_screeninfo info)
{
    printf("fb's var msg:\n");
    printf("\txres is:%d\n",info.xres);
    printf("\tyres is:%d\n",info.yres);
    printf("\txres_virtual is:%d\n",info.xres_virtual);
    printf("\tyres_virtual is:%d\n",info.yres_virtual);
    printf("\txoffset is:%d\n",info.xoffset);
    printf("\tyoffset is:%d\n",info.yoffset);
    printf("\tbits_per_pixel is:%d\n",info.bits_per_pixel);
    printf("\tgrayscale is:%d\n",info.grayscale);
    printf("\tnonstd is:%d\n",info.nonstd);
    printf("\tactivate is:%d\n",info.activate);
    printf("\theight is:%d\n",info.height);
    printf("\twidth is:%d\n",info.width);
    printf("\taccel_flags is:%d\n",info.accel_flags);
    printf("\tpixclock is:%d\n",info.pixclock);
    printf("\tleft_margin is:%d\n",info.left_margin);
    printf("\tright_margin is:%d\n",info.right_margin);
    printf("\tupper_margin is:%d\n",info.upper_margin);
    printf("\tlower_margin is:%d\n",info.lower_margin);
    printf("\thsync_len is:%d\n",info.hsync_len);
    printf("\tvsync_len is:%d\n",info.vsync_len);
    printf("\tsync is:%d\n",info.sync);
    printf("\tvmode is:%d\n",info.vmode);
    printf("\trotate is:%d\n",info.rotate);
    printf("\tcolorspace is:%d\n",info.colorspace);
}

編譯、拷貝到目標板上,運行結果如下:

從上面運行結果看,成功的獲取了屏的一系列參數。

詳細代碼framebuffer/show_fb_msg.c

填充顏色

​ 現在試下給整個屏填充顏色,要填充屏的話,需要幾個參數,一個是framebuffer所需內存的大小,一個是屏的像素的個數,還有就是顏色深度。

​ 要對framebuffer進行操作首先需要做的是通過mmap進行地址映射,這里就要用到framebuffer所需內存的大小,framebuffer所需內存的大小可以從fb_fix_screeninfo獲得,如下:

static char *fbp = 0;

fbp = (char *)mmap(0, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
if ((int)fbp == -1)
{
    printf("Error: failed to map framebuffer device to memory.\n");
    exit(4);
}

​ 這里申請一段finfo.smem_len大小的連續內存,對這段內存進行操作就會反應到屏上了。

​ 對整個屏進行填充就是操作屏上的所有像素(也可以說遍歷所有像素),這里就需要知道屏幕的像素的個數,這個參數可以從之前獲取到的fb_var_screeninfo中的參數中的xres,yres。從fb.h中的注釋翻譯過來,xres,yres是可見分辨率,我理解為可見區域,應該就是對應屏幕顯示區域的大小了。

int screensize = 0;

screensize = xres * yres;

​ 還有一個很關鍵的參數顏色深度(bpp:bits per pixel),也就是表示一個像素顏色所需的位數(bit),一般來說有這么幾種:1位,8位,16位,24位,32位。,比如1位的屏,也就是單色屏,用1bit來表示顏色,如果是白色單色屏的話,0表示黑色,1表示白色。24bit屏,就是用24bit(3字節)表示顏色,也就是RGB888,R、G、B分別占8bit。

​ 從上面獲取到的參數知道,該屏是16bit的,也就是RGB565,用兩個字節表示。應為之前用mmap申請的內存是char指針,用該指針來操作像素的話不方便,索性就把char型轉成short型,如下:

short *fb_s;
fb_s = (short *)fbp;

定義4個宏,分別表示紅色、綠色、藍色、黑色,

#define RED 0xf800
#define GREEN 0x07e0
#define BLUE 0x001f
#define BLACK  0x0000

創建一個寫framebuffer內存的函數,如下:

void fill_screen(short *fb_men,short color,int pix_size)
{	
	for(int i=0;i<pix_size;i++)
	{
		*fb_men = color;
		fb_men ++;
	}
}

第一個參數是所要操作的framebuffer對應的內存,第二個是所要填充的顏色,第三個是屏幕的尺寸,然后就可以對屏幕進行填充了,如下:

	printf("Fill red\n");
	fill_screen(fb_s,RED,screensize/2);
	sleep(1);
	printf("Fill green\n");
	// fb_s = (short *)fbp;
	fill_screen(fb_s,GREEN,screensize/2);
	sleep(1);
	printf("Fill blue\n");
	// fb_s = (short *)fbp;
	fill_screen(fb_s,BLUE,screensize/2);
	sleep(1);	
	fill_screen(fb_s,BLACK,screensize/2);

上面的代碼是先填充紅色,等待1秒,然后是綠色、藍色,最后填充黑色(清屏)。效果如下:

詳細代碼framebuffer/fb_fill_color.c

顯示圖片

現有的圖片格式非常多,JPEG、TIFF、PNG、BMP,SVG等等,這些又分有壓縮、無壓縮。這里是為了學習如何操作framebuffer,就選擇一種無壓縮格式的格式的圖片來操作。可以直接以二進制的方式讀取圖片數據,然后顯示到屏上。比較常用的無壓縮的圖片是BMP,這里就選擇BMP格式的圖片。

​ BMP格式圖片數據有以下4部分組成:

  • 位圖頭文件數據結構,它包含BMP圖像文件的類型、顯示內容等信息;
  • 位圖信息數據結構,它包含有BMP圖像的寬、高、壓縮方法,以及定義顏色等信息;
  • 調色板,這個部分是可選的,有些位圖需要調色板,有些位圖,比如真彩色圖(24位的BMP)就不需要調色板;
  • 位圖數據,這部分的內容根據BMP位圖使用的位數不同而不同,在24位圖中直接使用RGB,而其他的小於24位的使用調色板中顏色索引值。

由以上信息可以知道24位的bmp圖片,數據部分就是圖片像素的數據,也就是說讀取24位bmp圖片的數據部分不用進行任何操作,就可以直接拿來用,所以這里選擇使用24bit的BMP圖片,以下是我用來測試的24bit圖片中的一張圖片的信息:

BMP文件的位圖頭跟位圖信息部分占BMP圖片數據的前54字節,24位BMP圖片的圖片數據部分就是剩下的所有數據了(第55字節開始)。要對BMP圖片的位圖數據進行讀,還需要幾個關鍵參數,所讀的圖片的寬、高。還有顏色深度(bpc),因為選用了24位的圖片,顏色深度算是已近知道了。

獲取BMP圖片信息

首先獲取BMP圖片的信息,定義個結構體:

typedef struct
{	
	uint16_t	bfType;			
	uint32_t	bfSize;			
	uint32_t	biWidth;			 					 
	uint32_t	biHeight;			
	uint16_t	biBitCount;			
}BMP_HEADER;

這個結構體里面的元素分別有圖片類型,圖片數據大小,圖片的尺寸跟顏色深度。

創建個獲取該結構圖的函數:

BMP_HEADER TFTBmpGetHeadInfo(uint8_t *buf)
{
	BMP_HEADER bmpHead;

	bmpHead.bfType = (buf[0] << 8) + buf[1];		 
	bmpHead.bfSize	= (buf[5]<<24) + (buf[4]<<16) + (buf[3]<<8) + buf[2];   
	bmpHead.biWidth = (buf[21]<<24) + (buf[20]<<16) + (buf[19]<<8) + buf[18]; 
	bmpHead.biHeight = (buf[25]<<24) + (buf[24]<<16) + (buf[23]<<8) + buf[22];
	bmpHead.biBitCount = (buf[29] << 8) + buf[28];	

	return bmpHead;
}

打開圖片並讀取前54字節,傳給函數TFTBmpGetHeadInfo,然后判斷圖片格式是否為BMP,顏色深度是否為24bit,不是的話,程序不再繼續運行:

	bmp_fd  = open(argv[1], O_RDWR);
	if(bmp_fd <0)
	{
		printf("open file faile\n");
	}
	
	if(read(bmp_fd,buffer,54) <0)
	{
		printf("read file \"%s\" faile\n",argv[1]);
		return -1;	
	}

	bmp_header = TFTBmpGetHeadInfo(buffer);
	printf("%s's msg:\n",argv[1]);
	printf("\ttype:%2x\n",bmp_header.bfType);
	printf("\tsize:%d\n",bmp_header.bfSize);
	printf("\tWidth:%d\n",bmp_header.biWidth);
	printf("\tHeight:%d\n",bmp_header.biHeight);
	printf("\tBitCount:%d\n",bmp_header.biBitCount);

	if(bmp_header.bfType != 0x424d)
	{
		printf("The picture is not bmp file\n");
		return -1;
	}

	if(bmp_header.biBitCount != 24)
	{
		printf("Just support 24bit bmp file\n");
		return -1;
	}

把圖片顯示到屏上

​ 我這里實現的方法是,每次讀取BMP圖片的一行就寫到Framebuffer對應的緩存中,雖然要求read讀取指定個字節,可是程序運行的時候並不是每次都能夠讀取所要求的字節數。所以還需要加個判斷,判斷每次讀取的字節數是否為達到要求,沒達到要求就繼續讀,知道達到要求為止。具體代碼如下:

	int perline_size = bmp_header.biWidth * bmp_header.biBitCount /8;

	char * readed_data = (char *)malloc(perline_size);
	int i=0;
	int line_count=0;
	int read_size = perline_size;
	int read_count=0;
	int read_per_line=0;
	int buf_offset=0;
	while((ret = read(bmp_fd,readed_data+buf_offset,read_size)) !=0)
	{

			if(ret == -1)
			{
				if(errno == EINTR)
					continue;
				perror("read");
				break;
			}else if(ret !=perline_size )
			{
				read_per_line += ret;
				read_size = perline_size - read_per_line;
				buf_offset = read_per_line;
			}else 
				read_per_line = ret;

			if(read_per_line == perline_size)
			{
				read_size = perline_size;
				buf_offset =0;

				read_per_line = 0;
				if(bits_per_pixel ==16)
				{
					short pixl = 0;
					if(screan_width > bmp_header.biWidth)
					{
						for(i=0;i<bmp_header.biWidth;i++)
						{
							short blue = (readed_data[i*3]>>3) & 0x001f;
							short green = (readed_data[i*3+1]>>2) & 0x003f;
							short red =  (readed_data[i*3+2]>>3) & 0x001f;


							pixl = (red << 11 ) + (green << 5) + blue;
							*fb_s = pixl;
							fb_s ++;
						}
						fb_s += screan_width -bmp_header.biWidth  ;
					}else if(screan_width <= bmp_header.biWidth)
					{
						for(i=0;i<screan_width;i++)
						{
							short blue = (readed_data[i*3]>>3) & 0x001f;
							short green = (readed_data[i*3+1]>>2) & 0x003f;
							short red =  (readed_data[i*3+2]>>3) & 0x001f;

							pixl = (red << 11 ) + (green << 5) + blue;
							*fb_s = pixl;
							fb_s ++;
						}
						
					}
				}
			}
			line_count++;
			if(line_count >= screan_height)
				break;
	}

這里還涉及到一個問題,由於使用的圖片是24bit的,顯示屏使用的卻是16位的,那就需要把24bit轉成16bit了,在做之前就有想過,轉換的時候是取高位還是低位呢?一開始,我是取低位的,代碼為:

short blue = (readed_data[i*3]) & 0x001f;
short green = (readed_data[i*3+1]) & 0x003f;
short red =  (readed_data[i*3+2]) & 0x001f;

pixl = (red << 11 ) + (green << 5) + blue;
*fb_s = pixl;
fb_s ++;

顯示一張有紅、綠、藍色塊的圖片:

明顯是不正常的,紅色顯示有問題,修改為獲取高位:

short blue = (readed_data[i*3]>>3) & 0x001f;
short green = (readed_data[i*3+1]>>2) & 0x003f;
short red =  (readed_data[i*3+2]>>3) & 0x001f;


pixl = (red << 11 ) + (green << 5) + blue;
*fb_s = pixl;
fb_s ++;

編譯運行,結果如下圖,顯示看起來是正常了,從實驗結果來看,轉換的時候是取高位。

還有顯示一些其他圖片的:

顯示可愛的禰豆子:

最后附上一張顯示圖片時輸出的信息:

詳細代碼:show_bmp.c

參考:PNG、JPEG、BMP等幾種圖片格式詳解(三)—— BMP


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM