I.MX6ULL之LCD显示
代码放在git仓库,有需要的可以自行下载:Gitee
LCD的操作原理:
在Linux系统中通过Framebuffer驱动程序来控制LCD。我们只需要知道怎么获取LCD的参数,并且使用mmap映射到Framebuffer上,然后再Framebuffer中写入数据就可以了。
详细的流程如下图所示:
对于上图的32bpp,我们要引入RGB的显示格式,其中包括RGB888,RGB565,RGB555。本文中使用的时RGB565。他们的不同点就是在于后面根据FrameBuff取出来的数据要进行不同的处理,比如使用RGB565时,代码的配置如下:
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = (((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3));/* 分别取出R的高5位,G的高6位,B的高6位,组成一个数据 */
API函数:具体函数使用请参考man手册
1.open函数:
描述:用于打开一个文件,并且返回文件描述符(系统的一些宏定义,非负整数),如果失败了会返回-1
参数:pathname是要打开或创建的文件的名字。oflag参数可用来说明此函数的多个选择项,用一些宏来传入。
2.ioctl函数:
描述:针对设备的控制操作,可以传数据给驱动程序,也可以从驱动程序中读出数据。。
参数:fd 参数为某个设备或文件已打开的文件描述符, request 参数指定了将在 fd 上执行的控制操作。具体设备的头文件定义了可传递给 request 参数的常量 。
3.mmap函数:
描述:在调用进程的虚拟地址空间中创建一个新映射。
参数:addr 参数指定了映射被放置的虚拟地址。length 参数指定了映射的字节数。port参数是一个位掩码,它制定了施加于映射之上的保护信息,其取值为一些宏。flags 参数是一个控制映射操作各个方面的选项的位掩码(私有映射/共享映射)。fd参数是一个标识被映射的文件的文件描述符。offset参数指定了映射在文件中的起点。
4.close函数:
描述:关闭一个打开的文件描述符,并将其释放回调用进程。
参数:文件描述符
5.munmap函数:
描述:从调用进程的虚拟地址空间中删除一个映射。
获取LCD的参数
包含在#include<linux/fb.h>
字符的编码格式
编码与字体:
1.编码:编码是信息从一种形式或格式转换为另一种形式的过程,也称为计算机编程语言的代码简称编码。此处使用的编码即为将一个字符采用什么ascii
码来显示。
1️⃣ASCII:是American Standard Code for Information Interchange
的缩写,美国信息交换标准代码。
即采用一个数值来表示对应的字符。
2️⃣ANSI:ANSI
是一种字符代码,为使计算机支持更多语言,通常使用 0x00~0x7f
范围的1 个字节来表示 1 个英文字符。超出此范围的使用0x80~0xFFFF
来编码,即扩展的ASCII编码。
对于ASCII
字符仍以一个字节来表示,对于非ASCII
字符则使用2字节来表示。其实ANSI并不是某一种特定的字符编码,而是在不同的系统中,ANSI表示不同的编码。
3️⃣UNICODE:统一码,也叫万国码、单一码(Unicode).Unicode用数字0-0x10FFFF
来映射这些字符,最多可以容纳1114112
个字符,或者说有1114112
个码位。码位就是可以分配给字符的数字。
ASCII编码中,使用一个字节来表示一个字符,只用到其中的7位,最高位恒为0;
ANSI编码中,对于ASCII字符仍使用一个字节来表示(BIT7是0),对于非ASCII字符一般使用2个字节来表示,非ASCII字符的数值BIT7都是1。
UNICODE编码中,对不同范围的字符使用不同长度的编码。常用UTF-8
2.ASCII
字符的显示:
要在LCD中显示一个ASCII字符,即英文字母这些字符,首先是要找到字符对应的点阵。在Linux内核源码中有这个文件:lib\fonts\font_8x16.c
,里面以数组形式保存各个字符的点阵。
3.中文字符的点阵显示:
手动指定编码格式,以及手动指定可执行程序的编码格式,默认为UTF-8
#编译时的编码格式
-finput-charset=GB2312
-finput-charset=UTF-8
#编译出来的可执行程序的编码格式
-fexec-charset=GB2312
-fexec-charset=UTF-8
4.代码实现:
static int fd_fb;
static struct fb_var_screeninfo var; /* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;
int fd_hzk16;
struct stat hzk_stat;
unsigned char *hzkmem;
/*
description:在(x,y)处画颜色可修改的点
int x: x坐标
int y: y坐标
unsigned int color: 颜色(采用RGB)
*/
void lcd_put_pixel(int x, int y, unsigned int color)
{
/* 字号大小 */
unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch(var.bits_per_pixel) /* 选择BPP */
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* 565格式 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = (((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3));
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
/**********************************************************************
* 函数名称: lcd_put_ascii
* 功能描述: 在LCD指定位置上显示一个8*16的字符
* 输入参数: x坐标,y坐标,ascii码,颜色
* r o y g c b p
* 红 橙 黄 绿 青 蓝 紫
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void lcd_put_ascii(int x,int y,unsigned char c,unsigned int rgb)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16]; /* 获取一个字符的首地址 */
int i,b; /* i->16行 b->8列 */
unsigned char byte; /* 用于中间存储每一行的byte数值 */
unsigned int color;
switch(rgb)
{
case 'r':color = 0xff0000;break;
case 'o':color = 0xffa500;break;
case 'y':color = 0xffff00;break;
case 'g':color = 0x00ff00;break;
case 'c':color = 0x00ffff;break;
case 'b':color = 0x0000ff;break;
case 'p':color = 0x800080;break;
default:
break;
}
for(i = 0; i < 16;i++)
{
byte = dots[i]; /* 指针数组 */
for(b = 0;b < 8;b++)
{
if(byte & (1<<b)) /* 如果这一位为1:点亮 */
lcd_put_pixel(x+7-b,y+i, color); /* 因为X轴是低位在前,高位在后 */
else
lcd_put_pixel(x+7-b,y+i, 0);
}
}
}
/**********************************************************************
* 函数名称: lcd_put_string
* 功能描述: 在LCD指定位置上显示8*16的字符串
* 输入参数: x坐标,y坐标,字符串,颜色
* r o y g c b p
* 红 橙 黄 绿 青 蓝 紫
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void lcd_put_string(int x,int y,unsigned char *c,unsigned int rgb)
{
while(*c != '\0')
{
lcd_put_ascii(x,y,*c++,rgb);
x += 8;
}
}
/**********************************************************************
* 函数名称: lcd_put_chinese
* 功能描述: 在LCD指定位置上显示一个16*16的汉字
* 输入参数: x坐标,y坐标,ascii码
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void lcd_put_chinese(int x,int y,unsigned char *str,unsigned int rgb)
{
unsigned int area = str[0] - 0xa1; /* 区码 从0xa1开始,避免和ascii码冲突 */
unsigned int where = str[1] - 0xa1; /* 位码 */
unsigned char *dots = hzkmem + (area * 94 + where)*32; /* 从HZK的映射地址开始,每个区94个汉字+位的地址,一个汉字占32个字节 */
unsigned char byte;
unsigned int color;
int i,j,b;
switch(rgb)
{
case 'r':color = 0xff0000;break;
case 'o':color = 0xffa500;break;
case 'y':color = 0xffff00;break;
case 'g':color = 0x00ff00;break;
case 'c':color = 0x00ffff;break;
case 'b':color = 0x0000ff;break;
case 'p':color = 0x800080;break;
default:
break;
}
for(i = 0;i < 16;i++)
{
/* 判断是哪个字节 */
for(j = 0;j < 2;j++)
{
byte = dots[i*2 + j];
for(b = 0;b < 7;b++)
{
if(byte & (1 << b))
{
lcd_put_pixel(x+j*8+7-b, y+i, color); /* 白 */
}
else
{
lcd_put_pixel(x+j*8+7-b, y+i, 0); /* 黑 */
}
}
}
}
}
int main(int argc, char **argv)
{
int i,j;
unsigned char *str = "中";
unsigned char *str1 = "国";
fd_fb = open("/dev/fb0",O_RDWR); /* 以可读可写的方式打开/dev/fb0 */
if(fd_fb < 0)
{
printf("can't open /dev/fb0\n"); /* 返回值为-1,表示打开文件失败! */
return -1;
}
if(ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) /* get var screen info:获取屏幕的可变信息 */
{
printf("can't get var\n");
return -1;
}
line_width = var.xres * var.bits_per_pixel / 8; /* 行宽 = X*BPP/8 */
pixel_width = var.bits_per_pixel / 8; /* 列宽 = BPP/8 */
screen_size = var.xres * var.yres * var.bits_per_pixel / 8; /* FrameBuffer的总大小 */
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if(fb_base == (unsigned char *)-1)
{
printf("can't mmap\n'");
return -1;
}
fd_hzk16 = open("HZK16", O_RDONLY); /* 打开字体库 */
if (fd_hzk16 < 0)
{
printf("can't open HZK16\n");
return -1;
}
if(fstat(fd_hzk16,&hzk_stat))
{
printf("can't get fstat\n");
return -1;
}
/* mmap的返回结果保存在hzkmem中,作为字库的基地址 */
hzkmem = (unsigned char *)mmap(NULL,hzk_stat.st_size,PROT_READ, MAP_SHARED,fd_hzk16,0);
if(hzkmem == (unsigned char *)-1)
{
printf("can't mmap for hzk16\n'");
return -1;
}
// /* 清屏: 全部设为白色 */
// memset(fb_base, 0xff, screen_size); /* 在fb_base地址上的screen_size大小的区域全部设为1 */
/* 清屏: 全部设为黑色 */
memset(fb_base, 0, screen_size); /* 在fb_base地址上的screen_size大小的区域全部设为1 */
// /* 随便设置出100个为红色 */
// for (i = 0; i < 500; i++)
// {
// for(j=0; j < 100;j++)
// {
// lcd_put_pixel(var.xres/2+i, var.yres/2+j, 0x800080); /* 在中间X轴画100个点 */
// // lcd_put_pixel(var.xres+i, var.yres+j, 0x800080); /* 在中间X轴画100个点 */
// }
// }
// lcd_put_ascii(var.xres/2,var.yres/2,'S','r'); /* 在屏幕中间显示一个8*16的字母S */
// lcd_put_string(var.xres/2,var.yres/2,"hello world!",'r');
lcd_put_chinese(var.xres/2 + 8, var.yres/2, str,'r');
lcd_put_chinese(var.xres/2 + 32, var.yres/2, str1,'r');
munmap(fb_base , screen_size); /* 取消映射 */
close(fd_fb); /* 关掉文本 */
return 0;
}
// }
// }
// lcd_put_ascii(var.xres/2,var.yres/2,'S','r'); /* 在屏幕中间显示一个8*16的字母S */
// lcd_put_string(var.xres/2,var.yres/2,"hello world!",'r');
lcd_put_chinese(var.xres/2 + 8, var.yres/2, str,'r');
lcd_put_chinese(var.xres/2 + 32, var.yres/2, str1,'r');
munmap(fb_base , screen_size); /* 取消映射 */
close(fd_fb); /* 关掉文本 */
return 0;
}