GEC6818的开发板有一个480*800的显示屏,那么在我们编写程序时,如何驱动这块屏幕?

Linux下,一切皆文件,开发板的屏幕在系统中也就是相当于一个文件,对这个文件的内容进行操作,便可改变屏幕显示的内容。那么要怎么做呢?

首先说说显示屏:

文件位于/dev/fb0(路径仅供参考),使用open函数打开(需要包含sys/types.h、sys/stat.h、fcntl.h),对其进行操作就可以啦。

那么先简单写一个初始化屏幕的函数:

int *plcd;//全局变量,plcd是操作屏幕文件的指针
int lcd_init()
{
    int fd=open("/dev/fb0",O_RDWR);//获取帧缓冲的文件描述符
    if(fd==-1)//如果打开失败打印错误信息
    {
        perror("screen open error:");
        return 0;
    }
    plcd=mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//将屏幕文件映射到内存中,加快速度
    return fd;
}

这里使用的perror函数需要包含errno.h和stdio.h,作用是将自定义的信息+上一个函数发生错误的原因输出,这里在屏幕文件打开失败时调用,可以帮助抓虫(
关于mmap函数(需要包含sys/mman.h)的用法这里不做讲解,其中第二个参数,映射到内存的文件长度为800*480*4,因为文件中存储的像素数据是16进制形式的int类型数据,因此文件大小就是分辨率*sizeof(int)。

有始有终,就顺便再把关闭屏幕文件的函数写好:

void lcd_uninit(int fd)
{
    close(fd);//关闭帧缓冲文件
    munmap(plcd,800*480*4);//解映射
}

有了初始化和关闭,就可以试着操作屏幕文件啦,我们先简单画一个像素点:

/*在屏幕上的(x,y)处写一个颜色为color的像素点*/
void lcd_draw_point(int x,int y,int color)
{
    if(x>=0 && x<800 && y>=0 && y<480)//判断传入坐标是否越界
    *(plcd+800*y+x)=color;//画点
}

(x,y)表示上面有y行像素点,前面有x个像素点,color就是透明度(直接00即可)+颜色代码,例如0x00ff0000(红),0x0000ff00(绿),0x000000ff(蓝)。

这样我们就可以对这块屏幕为所欲为♂了,比如说在屏幕上绘制一个圆:

/*以(x,y)为圆心,r为半径画color颜色的圆*/
void lcd_draw_circle(int x,int y,int r,int color)
{
    int i,j;
    for(j=0;j<480;j++)
    {
        for(i=0;i<800;i++)
        {
            if((i-x)*(i-x)+(j-y)*(j-y)<=r*r)//(i,j)这个点满足圆内的关系,就画圆
            {
                lcd_draw_point(i,j,color);
            }
        }
    }
}

如果要绘制其他图形,只要修改if中判断的公式就好了,复杂一些的图形可能还要稍作修改,这里就不扩展啦qwq

但显然,这样还不够,那么重中之重来了,如何在屏幕上显示图片?
这里我们选择bmp格式的图片,因为其中存储的就直接是rgb数据,不需要我们做额外的处理,比较方便。

bmp文件的前54字节存储着图片信息,后面才是像素数据,因此我们在读取时需要偏移光标,但我们又需要获取图片的分辨率、色深数据,所以首先光标偏移到18字节处,读4字节,这是图片的横向分辨率(宽),然后偏移到22字节处,读4字节,这是图片的纵向分辨率(高),然后偏移到28字节处,读2字节,这是图片的色深。有关前54字节存储的其他的信息是什么,还请自行上网查阅相关资料,这里用到的信息就这些~
先写一个将屏幕初始化为白色的函数,以保证图片显示的效果:


void lcd_clear(int color)
{
    int i,j;
    for(j=0;j<480;j++)
    {
        for(i=0;i<800;i++)
        {
            lcd_draw_point(i,j,color);
        }
    }
}

这里引用一篇讲解bmp行字节数的文章:https://blog.csdn.net/u012313335/article/details/80432155

因此在下方函数中,我们不能忽视因为字节对齐所产生的的无效字节,所以在定义临时存放像素信息的数组时,应加上无效字节的大小,并在往屏幕写入像素信息时,忽略掉每行的无效字节。

void load_img(char *path,int x0,int y0)
{/*以(x,y)为原点(图片左上角)绘制图片(传进来的path为图片路径)*/
    lcd_clear(0x00ffffff);//清屏
    int bmp_fd=open(path,O_RDWR);
    if(bmp_fd==-1)
    {
        perror("");
        return;
    }
    int w,h;//宽,高
    short depth;//色深
    //光标偏移到18字节处,读宽
    lseek(bmp_fd,18,SEEK_SET);
    read(bmp_fd,&w,4);
    //此时光标来到22字节处,读高
    read(bmp_fd,&h,4);
    //光标偏移到28字节处,读色深
    lseek(bmp_fd,28,SEEK_SET);
    read(bmp_fd,&depth,2);
    int laizi=0;//表示无效字节数
    if((w*depth/8)%4)
    {
        laizi=4-(w*depth/8)%4;
    }
    //总字节数
    int total=(w*depth/8+laizi)*h;
    unsigned char pix[total];//定义像素数组
    lseek(bmp_fd,54,SEEK_SET);//光标移动到54字节处
    read(bmp_fd,pix,total);//读像素信息,存入像素数组
    int i,j,num=0;
    unsigned char a=0,r,g,b;
    for(j=0;j0?x0+i:abs(w)-1-i+x0;
            y=h<0?y0+j:abs(h)-1-j+y0;
            lcd_draw_point(x,y,color);
        }
        num+=laizi;//跳过每行末尾的laizi
    }
}

关于int color = b|g<<8|r<<16|a<<24;因为屏幕文件中的argb数据和bmp中存储的bgra是相反的,因此需要通过位移来修正。abs函数为求绝对值函数,保证在读取到负的(翻转)分辨率信息时不会出错。

整理下代码,交叉编译将程序传到板子上运行,效果如下:

五月织姬~快去单推吧(

接下来讲一下触摸屏的驱动:

刚刚我们是打开显示屏的文件进行操作,同理,我们这次需要打开触摸屏的文件:/dev/input/event0

Linux系统IO库里提供了input.h,其中有一个叫input_event的结构体,内容对应触摸屏文件,我们只要不停的读取event0,匹配上input_event的大小时处理数据即可,我们需要用到结构体中的type、code、value。

当type==EV_ABS时为触摸屏绝对坐标事件,此时有code==ABS_X或code==ABS_Y,当code==ABS_X读取到value的值为x轴的坐标,以此类推。
当type==EV_KEY时为键盘事件,此时当value为0时是手指触摸、离开触摸屏的瞬间,此时对获取到的坐标进行运算就可以判断是上滑、下滑、左滑、右滑、点击操作。

关于input_event结构体的详解,可自行查找资料,这里不做赘述。

//返回:1 向上,2 向下,3 向左,4 向右,-1 点击
int get_touchscreen_index(int *x,int *y)
{
    int ts_fd = open("/dev/input/event0", O_RDONLY);
    if(ts_fd == -1)
    {
        perror("open event error:");
    }
    struct input_event ev;
    int i, j, x1, y1;
    int x0 = -1, y0 = -1;
    while(1)
    {
        int r = read(ts_fd, &ev, sizeof(ev));
        if(r != sizeof(ev))
        {
            continue;
        }
        if(ev.type == EV_ABS && ev.code == ABS_X)
        {
            if(x0 == -1)
            {
                x0 = ev.value;
            }
            x1 = ev.value;
            *x = ev.value;
        }
        if(ev.type == EV_ABS && ev.code == ABS_Y)
        {
            if(y0 == -1)
            {
                y0 = ev.value;
            }
            y1 = ev.value;
            *y = ev.value;
        }
        if(ev.type == EV_KEY && ev.value == 0)
        {
            int i = 0;
            if(abs(x0-x1)>abs(y0-y1))
            {
                if(x0-x1>0)
                {
                    i = 3;
                    printf("left\n");
                }
                else
                {
                    i = 4;
                    printf("right\n");
                }
            }
            else if(abs(x0-x1)0)
                {
                    i = 1;
                    printf("up\n");
                }
                else
                {
                    i = 2;
                    printf("down\n");
                }
            }
            else
            {
                i = -1;
                printf("click\n");
            }
            return i;
        }
    }
    close(ts_fd);
}

printf用于抓虫,方便判断触摸是否工作正常,当需要用到触摸屏时,将其放到死循环中不停的读取返回的信息,就可以获取到触摸屏的状态,以及点击的位置坐标信息等。

那么接下来写一个简单的程序综合一下上面的成果~

int main()
{
    float x,y;
    int fd=lcd_init();
    lcd_clear(0x00ffffff);
	while(1)
	{
		int z=get_touchscreen_index(&x,&y);
		if((x<400*1.28)&&(y<240*1.28)&&z==-1)
		{
			load_img("./1.bmp",0,0);
			printf("loadimg1\n");
		}
		else if((x>400*1.28)&&(y>240*1.28)&&z==-1)
		{
			load_img("./2.bmp",0,0);
			printf("loadimg2\n");
		}
	}
    lcd_uninit(fd);
}

因为触摸屏的分辨率是1024*600,和显示屏不一样,因此在对触摸精度要求不高的情况下将坐标乘1.28即可

当点击左半边的屏幕时显示图片1,右边屏幕时显示图片2,同时打印信息到终端。

\五月/\五月/\五月/\五月/
\五月/\五月/\五月/\五月/
可以看到终端输出的debug信息,测试完成~

到这里,我们算是征服6818的这块屏幕啦~


逸一时,误一世