创客神器NanoPi
直播中

donatello1996

9年用户 737经验值
擅长:MEMS/传感技术
私信 关注
[经验]

【NanoPi K1 Plus试用体验】使用FrameBuffer外设从显示屏输出自己想要的东西【静态数据篇】

``【NanoPi K1 Plus试用体验】使用FrameBuffer外设从显示屏输出自己想要的东西【静态数据篇】
Nanopi K1+板子自带了HDMI接口,并且/dev目录下有fb0外设:

35.JPG


这个外设,不用怎么猜都知道是对HDMI显示屏输出的外设,也就是说,如果往这个外设写数据,外接的HDMI屏就会做出相应反应。在使用这个外设之前,应该先获取三个参数:屏幕的长和宽(即分辨率)、屏幕像素颜色显示格式。通常,带有fb0的Linux开发板都有一个固定的可接受最大分辨率值,在系统配置文件里面可以修改,一般是1024*768/1366*768/1600*1200/1920*1080/2000*1125/4000*2250等4:3或者16:9分辨率的屏,像NanoPi K1+这种新出的Cortex-A53板子,支持4K HDMI输出是毫无压力的,开发板会对接入的fb0显示设备进行检测,如果这个显示屏设备比较辣鸡,分辨率不大于系统设定最大分辨率的话(比如RK3399板子接了个1024*768的屏),就按显示设备的分辨率来走;如果fb0设备是很吊的显示屏,如4K屏,而这个Linux板子又没法支持到4K这么大的话,就按板子的最大分辨率来走,也就是说,显示分辨率一定小于或等于系统支持最大分辨率。
    说了这么多,其实可以用几行代码来检测屏幕分辨率和像素位色:



void Get_Information_Frame_Buffer()

{

    struct fb_var_screeninfo vinfo;

    lcd_fd=open("/dev/fb0",O_RDWR);

    if (ioctl(lcd_fd,FBIOGET_VSCREENINFO,&vinfo))

     {

         printf("Error: reading variable information.
");


         return ;

     }

    printf("%d %d %d
",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel);


}

fb_var_screeninfo结构体包含了一些常用的fb0显示设备参数,我们只取要用到的三个参数(x分辨率,y分辨率,像素位色),运行程序:

36.jpg


可以得出,屏幕的分辨率为1920*1080,32位色彩,也就是说,我们需要开辟一个32位长度,1920*1080=2073600大小的数组来作为fb0外设的显示缓存:
unsigned int lcd_buf[LCD_WIDTH*LCD_HEIGHT];



在64位编译器中,只有int和unsigned int类型是32位长度的,不可以用long/long int等代替,我之前就是犯了这个错误导致显示一直出错。众所周知,32位色里面,从高位到低位的四个字节分别是透明度、红色灰度、绿色灰度、蓝色灰度,我们将数据刷入到fb0的显存的时候也必须遵循这个规则:
37.jpg
然后我们可以简单理解一下fb0外设刷写每个像素点的顺序,是先行后列,从左上到右下刷:

38.jpg


第0号像素   |第1号像素   |第2号像素   |...   |第1919号像素

第1920号像素|第1921号像素|第1922号像素|...   |第3838号像素

...

...

第1920*1079号像素|第1920*1079+1号像素|第1920*1079+2号像素|...|第1920*1080-1号像素


每个像素点都占四个字节。另外,在Linux系统中,对于FrameBuffer的操作,必须是以帧为单位的,也就是说,每次往fb0所在内存块所传输的数据大小,必须是分辨率*像素位数,即使是只改变屏幕某一个点或几个点的像素,也要以分辨率*像素位数即1920*1080*4为整体单位来传输。我们先来试试最简单的刷屏:


int Clear_Screen(long color)

{

        int i;
        for(i=0;i<LCD_WIDTH*LCD_HEIGHT;i++)

                lcd_buf=color;

}


int LCD_Effect()

{

        lcd_fd=open("/dev/fb0",O_RDWR);    //  O_RDONLY, O_WRONLY, or  O_RDWR

        if(lcd_fd==-1)

        {

                printf("open LCD failed!
");


                return -1;

        }

        write(lcd_fd,lcd_buf,LCD_WIDTH*LCD_HEIGHT*4);

        close(lcd_fd);

}


main函数的循环:


while(1)

{

        Clear_Screen(0xffffff00);

        LCD_Effect();

        sleep(1);

        Clear_Screen(0xff00ffff);

        LCD_Effect();

        sleep(1);

        Clear_Screen(0xffff00ff);

        LCD_Effect();

        sleep(1);

        Clear_Screen(0xffffffff);

        LCD_Effect();

        sleep(1);

}
open("/dev/fb0",O_RDWR);一行语句即为以文件方式打开/dev/fb0外设,由于Linux系统中有用户内存与系统外设内存的映射关系,lcd_fd文件就是这个映射的通道,open()函数是建立映射通道用的,而write()函数则可以让用户可以通过操作文件的方式来操作系统外设的内存数据。参数0xffffff00代表每个像素的32位色彩,最左边八位即最高八位就是透明度,默认0xff,完全不透明,0xffff00是RGB(256,256,0)即黄色,以此类推,0xff00ffff就是水蓝色,0xffff00ff就是紫色,0xffffffff就是白色。然后再试试显示特定的文字(ASCII码+汉字):
IMG_20180812_020339R.jpg


无论是显示汉字还是ASCII码,都要预先取模,取模软件我用的是PC2LCD2002软件,非常简单好用,适用于单片机和嵌入式系统的字体取模需要,取模需要注意三点,那就是取模方式、阴阳码和字体,取模方式有列行式、行列式、逐行、逐列4种方式取模,我选的是比较好理解的逐行式、阴码、64*64分辨率取模:
39.jpg


阴码的意思是字体中要显示的部分为1,不显示的部分为0.

int Show_ASCII_64(int x,int y,int fontcolor,int backcolor,int word)

{

    int i,j,k;

    unsigned char temp;

    word-=0x20;

    for(j=0;j<64;j++)

    {

        for(i=0;i<4;i++)

        {

            temp=ascii_font_64[j*4+i+word*256];

            for(k=0;k<8;k++)

            {

                if(temp&0x80)

                    LCD_Draw_Pixel(i*8+k+x,j+y,fontcolor);

                else LCD_Draw_Pixel(i*8+k+x,j+y,backcolor);

                temp<<=1;

            }

        }
    }

}




int Show_ASCII_String_64(int x,int y,int wordcolor,int backcolor,char s[])

{

        int i=0;

        for(i=0;s!='';i++)

                Show_ASCII_64(x+i*32,y,wordcolor,backcolor,s);
}




int Show_Chinese_64(int x,int y,int fontcolor,int backcolor,int word)

{

    int i,j,k;

    unsigned char temp;

    for(j=0;j<64;j++)

    {

        for(i=0;i<8;i++)

        {

            temp=chinese_font_64[j*8+i+word*512];

            for(k=0;k<8;k++)

            {

                if(temp&0x80)

                    LCD_Draw_Pixel(i*8+k+x,j+y,fontcolor);

                else LCD_Draw_Pixel(i*8+k+x,j+y,backcolor);

                temp<<=1;

            }

        }
    }

}




最后就是显示BMP图片了,这个就只做简单说明,BMP图片通常是24位色的(少数应用可以在PS软件中获得32位色的BMP图片),每一个像素点的颜色数据按蓝 绿 红各八位(BGR888)的格式逐个逐个排列,并且BMP文件前54个字节是文件信息,54字节后面的数据才是图片数据。另外BMP图片的数据存放方式非常奇葩,是行颠倒型排列方式,即图片效果与数据格式是上下轴对称颠倒的,因此在程序处理上要做出相应调整:
40.jpg 41.jpg


int LCD_Show_BMP_File(int width,int height,char* filename)

{

        int i,j,k,fbmp,fd;

        fd =  open("/dev/fb0",O_RDWR);

        if(fd == -1)

        {

                printf("open LCD failed!
");


                return -1;

        }




        fbmp=open(filename,O_RDONLY);

        read(fbmp,bmp_buf,width*height*3+54);







        for(i=0;i<LCD_HEIGHT;i++)

        {

            for(j=0;j<LCD_WIDTH;j++)

            {

                if(i<=height&&j<=width)

                {

                    lcd_buf[i*LCD_WIDTH+j]=bmp_buf[(i*width+j)*3+54]|bmp_buf[(i*width+j)*3+55]<<8|bmp_buf[(i*width+j)*3+56]




<<16;

                }

                else lcd_buf[i*LCD_WIDTH+j]=0;

            }

        }




        for(i=0;i<LCD_HEIGHT;i++)

                for(j=0;j<LCD_WIDTH;j++)

                    lcd_buff[j]=lcd_buf[i*LCD_WIDTH+j];




        for(i=0;i<LCD_HEIGHT;i++)

                for(j=0;j<LCD_WIDTH;j++)

                    lcd_buff_2[LCD_HEIGHT-i][j]=lcd_buff[j];




        for(i=0;i<LCD_HEIGHT;i++)

                for(j=0;j<LCD_WIDTH;j++)

                    lcd_buf[i*LCD_WIDTH+j]=lcd_buff_2[j];




        //ret = write(fd,lcd_buf,LCD_WIDTH*LCD_HEIGHT*4);

        if(ret == -1)

        {

                printf("fill frame failed!
");


                return -1;

        }

        close(fd);

}




这是在电脑上玩游戏的截图原图,1920*1080分辨率:




显示效果:

IMG_20180812_021052R.jpg

`` 1.gif 42.bmp

回帖(1)

zpzdd

2018-8-14 09:57:13
厉害,学习了


举报

更多回帖

×
20
完善资料,
赚取积分