所学参考百问网
目录
1.1 简介
Frame是帧的意思,buffer是缓冲的意思,这意味着Framebuffer就是一块内存,里面保存着一帧图像。Framebuffer 中保存着一帧图像的每一个像素颜色值。
1.2 设置LCD操作
1.驱动程序设置好LCD控制器:
根据LCD的参数设置LCD控制器的时序、信号极性;
根据LCD分辨率、BPP分配Framebuffer。
2.APP使用ioctl获得LCD分辨率、BPP
3.APP通过mmap映射Framebuffer,在Framebuffer中写入数据
1.3 设置LCD坐标颜色
假设需要设置LCD中坐标(x,y)处像素的颜色,首先要找到这个像素对应的内存,然后根据它的BPP值(每个像素用多少位表示颜色)设置颜色。假设fb_base是APP执行mmap后得到的Framebuffer地址(即帧缓冲区的基地址)
如图 5.2所示:
可以用以下公式算出(x,y)坐标处像素对应的Framebuffer地址:
(x,y)像素起始地址= fb_base + (xres * bpp / 8) * y + x * bpp / 8 fb_base:帧缓存的基地址 xres:屏幕水平分辨率,即屏幕宽度(以像素为单位) bpp:每个像素的比特数,表示每个像素点占用的位数,例如:对于24位深度的颜色,bpp为24。 bpp/8:即该像素占用多少个字节
像素颜色的表示
它是用RGB三原色(红、绿、 蓝)来表示的,在不同的BPP格式中,用不同的位来分别表示R、G、B,如图 5.3 所示:
对于32BPP,一般只设置其中的低24位,高8位表示透明度,一般的LCD 都不支持。
对于24BPP,硬件上为了方便处理,在 Framebuffer 中也是用 32 位来表 示,效果跟32BPP是一样的。
对于 16BPP,常用的是 RGB565;很少的场合会用到 RGB555,这可以通过 ioctl 读取驱动程序中的RGB位偏移来确定使用哪一种格式。
相关结构体
struct fb_var_screeninfo 是 Linux 帧缓冲(framebuffer)设备中用于描述屏幕变量信息的一个结构体
以下为部分结构体参数的解释
struct fb_var_screeninfo {
__u32 xres; // 水平分辨率(宽度)
__u32 yres; // 垂直分辨率(高度)
__u32 xres_virtual; / * xres_virtual、yres_virtual定义虚拟屏幕的分辨率,它可能比实际屏幕分辨率大,
__u32 yres_virtual; 用于滚动屏幕或实现其他特效。 */
__u32 xoffset; /* xoffset、yoffset定义虚拟屏幕到可见屏幕的偏移量,
__u32 yoffset; 用于滚动屏幕或选择屏幕上的显示区域。*/
__u32 bits_per_pixel; // 每个像素的颜色深度,即每个像素使用多少位来表示颜色
__u32 grayscale; // 表示屏幕是否使用灰度模式(0=彩色,1=灰度,>1时表示使用FOURCC编码的像素格式)。
// 以下这四个结构体成员分别定义了红色、绿色、蓝色和透明度分量在帧缓冲内存中的位置和长度,仅当使用真彩色模式时有效。
struct fb_bitfield red; // 红色
struct fb_bitfield green; // 绿色
struct fb_bitfield blue; // 蓝色
struct fb_bitfield transp; // 透明度
... ...
}
示例代码
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>
static unsigned int line_width; // 水平分辨率
static unsigned int point_width; // 一个点所占像素
static int screen_size; //整个屏幕占的像素
int fd;
static unsigned char *fb_base;
struct fb_var_screeninfo info;
int i;
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fb_base + y * line_width + x * point_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red,blue,green;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch(info.bits_per_pixel)
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
// 565 red 5 blue 6 green 5
/*
示例:color = 0xff0000
red = (0xff0000 >> 16) & 0xff = 0xff
green = (0xff0000 >> 8) & 0xff = 0x00
blue = (0xff0000 >> 0) & 0xff = 0x00
*/
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", info.bits_per_pixel);
break;
}
}
}
int main(int argc, char * * argv)
{
// 打开/dev/fb0设备节点 可读可写
fd = open("/dev/fb0",O_RDWR);
if(fd < 0){
perror("open");
exit(EXIT_FAILURE);
}
// FBIOGET_VSCREENINFO:获取帧缓冲区设备的可变参数信息
if(ioctl(fd,FBIOGET_VSCREENINFO,&info) < 0)
{
perror("ioctl");
exit(EXIT_FAILURE);
}
// 分别算出行、点、整个屏幕的所占像素大小
line_width = info.xres * info.bits_per_pixel / 8;
point_width = info.bits_per_pixel / 8;
screen_size = info.xres * info.yres * info.bits_per_pixel / 8;
// 映射整个屏幕的大小 可读可写 共享
fb_base = (unsigned char *)mmap(NULL,screen_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(fb_base < (unsigned char *)0)
{
perror("mmap");
exit(EXIT_FAILURE);
}
// 清屏
// 0:灭 1:亮
memset(fb_base,0xff,screen_size);
// 设置500个像素点为蓝色
for(i = 0; i < 500; i++)
lcd_put_pixel(info.xres/2+i,info.yres/2,0x0000FF);
// 释放资源
munmap(fb_base,screen_size);
close(fd);
return 0;
}
2. 字符的编码格式
2.1 ASCII
是“American Standard Code for Information Interchange”的缩写,美国信息交换标准代码。
电脑毕竟是西方人发明的,他们常用字母就26个,区分大小写、加上标点符号也没超过127个,每个字符用一个字节来表示就足够了。一个字节的7位就 可以表示128个数值,在ASCII码中最高位永远是0。
2.2 ANSI
ASNI是ASCII的扩展,向下包含ASCII。对于ASCII字符仍以一个字节来表示,对于非ASCII字符则使用2字节来表示。并没有固定的ASNI编码,它跟 “本地化”(locale)密切相关。比如在中国大陆地区,ANSI的默认编码是GB2312; 在港澳台地区默认编码是BIG5。以数值“0xd0d6”为例,对于GB2312编码它 表示“中”;对于BIG5编码它表示“笢”。
2.3 UNICODE
在ANSI标准中,很多种文字都有自己的编码标准,汉字简体字有GB2312、 繁体字有 BIG5,这难免同一个数值对应不同字符。比如数值“0xd0d6”,对于 GB2312 编码它表示“中”;对于BIG5编码它表示“笢”。这造成了使用ANSI编码保存的文件,不适合跨地区交流。
UNICODE 编码就是解决这类问题:对于地球上任意一个字符,都给它一个唯一的数值。
UNICODE 仍然向下兼容ASCII,但是对于其他字符会有对应的数值,比如对 于“中”、“笢”,它们的数值分别是:0x4e2d、0x7b22
UNICODE 中的数值范围是0x0000至0x10FFFF,有 1,114,111即100多万 个数值,可以表示100多万个字符,足够地球人使用了。
2.3.1 UNICODE的编码实现
1.UCS-2 Little endian/UTF-16 LE
每个UNICODE 值用 3 字节来表示有点浪费,那只用2字节呢?它可以表示 2^16=65536 个字符,全世界常用的字符都可以表示了。
Little endian 表示小字节序,最低有效字节(LSB, Least Significant Byte)存储在最低的内存地址处,而最高有效字节(MSB, Most Significant Byte)则存储在最高的内存地址处。比如字符 “A中”在TXT文件中的数值如下,其中的“A”使用“0x41 0x00”两字节表示;“中”使用“0x2d 0x4e”两字节表示。文件开头的“0xff 0xfe”表示“UTF 16 LE”。
2.UCS-2 Big endian/UTF-16 BE
Big endian表示大字节序,最低有效字节(LSB, Least Significant Byte)存储在最高的内存地址处,而最高有效字节(MSB, Most Significant Byte)则存储在最低的内存地址处,比如字符“ab 中”在TXT文件中的数值如下,其中的“A”使用“0x00 0x41”两字节表示; “中”使用“0x4e 0x2d”两字节表示。文件开头的“0xfe 0xff”表示“UTF 16 BE”。
3.UTF8
在上面2种方法中,每一个UNICODE使用2字节来表示,这有3个缺点: 表示的字符数量有限、对于ASCII字符有空间浪费、如果文件中有某个字节丢失,这会使得后面所有字符都因为错位而无法显示。 使用UTF8可以解决上述所有问题。UTF8是变长的编码方法,有2种UTF8 格式的文件:带有头部、不带头部。
对于其中的ASCII字符,在UTF8文件中直接用其ASCII码来表示,比如上图中的0x41表示字符A、0x42表示字符B。上图中的3个字节“0xe4 0xb8 0xad”表示的数值是0x4e2d,对应“中”的UNICODE码。
对于非ASCII字符,使用变长的编码:每一个字节的高位都自带长度信息。 请看图 6.9:
上图中,0xe4的二进制是“11100100”,高位有3个1,表示从当前字节起有3字节参与表示UNICODE;
0xb8 的二进制是“10111000”,高位有1个1,表示从当前字节起有1字节参与表示UNICODE;
0xad 的二进制是“10101101”,高位有1个1,表示从当前字节起有1字节参与表示UNICODE;
除去高位的“1110”、“ 10”、“ 10”后,剩下的二进制数组合起来得到 “01001110001101”,它就是0x4e2d,即“中”的UNICODE值。 使用UTF8编码时,即使TXT文件中丢失了某些数据,也只会影响到当前字符的显示,后面的字符不受影响。
2.4 编码格式的修改
使用点阵字库时,中文字符的显示原理跟ASCII字符是一样的。要注意的地方在于中文的编码:在C源文件中它的编码方式是GB2312还是UTF-8?编译出 的可执行程序,其中的汉字编码方式是GB2312还是UTF-8?
注意:一般不会使用UTF-16的编码方式,在这种方式下ASCII字符也是用2字 节来表示,而其中一个字节是0,但是在C语言中0表示字符串的结束符,会引 起误会。
我们编写C程序时,可以使用ANSI编码,或是UTF-8编码;在编译程序时, 可以使用以下的选项告诉编译器:
-finput-charset=UTF-8
-finput-charset=GB2312
如果不指定“-finput-charset”,GCC就会默认C程序的编码方式为UTF-8,即使你是以ANSI格式保存,也会被当作UTF-8来对待。
对于编译出来的可执行程序,可以指定它里面的字符是以什么方式编码,可以使用以下的选项编译器:
-fexec-charset=GB2312
-fexec-charset=UTF-8
如果不指定“-fexec-charset”,GCC就会默认编译出的可执行程序中字符 的编码方式为UTF-8。
如果“-finput-charset”与“-fexec-charset”不一样,编译器会进行格式转换。
3. 涉及API
open函数
-
fstat函数通过文件描述符来获取文件的状态信息
-
int fstat(int fd, struct stat *buf);
-
int fd:已打开文件的文件描述符。
-
struct stat *buf:指向
struct stat
结构的指针,该结构用于存储获取到的文件状态信息。 -
返回值:执行成功时返回0,失败时返回-1,并设置全局变量
errno
以指示错误原因。
-
-
获取LCD分辨率、BPP
-
int ioctl(int fd,unsigned long request, ...);
-
int fd:文件描述符
-
unsigned long request:表示与驱动程序交互的命令
-
...:可变参数,根据requset命令,设置驱动程序返回输出的数据
-
返回值:成功返回文件描述符,失败返回-1
-
ioctl 的作用非常强大、灵活。不同的驱动程序内部会实现不同的 ioctl, APP 可以使用各种ioctl跟驱动程序交互:可以传数据给驱动程序,也可以从驱 动程序中读出数据。
-
映射
-
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
-
void *addr:addr表示指定映射的內存起始地址,通常设为 NULL表示让系统自动选定 地址,并在成功映射后返回该地址;
-
size_t length:length表示将文件中多大的内容映射到内存中
-
int prot:prot 表示映射区域的保护方式
-
PROT_EXEC 映射区域可被执行
-
PROT_READ 映射区域可被读出
-
PROT_WRITE 映射区域可被写入
-
PROT_NONE 映射区域不能存取
-
-
int flags:表示影响映射区域的不同特性
-
MAP_SHARED 表示对映射区域写入的数据会复制回文件内,原来的文 件会改变。
-
MAP_PRIVATE 表示对映射区域的操作会产生一个映射文件的复制,对 此区域的任何修改都不会写回原来的文件内容中。
-
-
off_t offset:偏移量,一般填0
-
返回值:若成功映射,将返回指向映射的区域的指针,失败将返回-1
-
-
获取已打开文件的状态信息
-
int fstat(int fd, struct stat *buf);
-
fd
:需要查询状态的文件的文件描述符。 -
buf
:指向struct stat
结构体的指针,用于存储查询到的文件状态信息。 -
返回值:成功时返回0,失败时返回-1,并设置errno以指示错误。
-
struct stat结构体 struct stat{ dev_t st_dev:设备ID。 ino_t st_ino:inode节点号。 mode_t st_mode:文件类型和权限。 nlink_t st_nlink:硬链接数。 uid_t st_uid:所有者ID。 gid_t st_gid:组ID。 dev_t st_rdev:设备类型(如果为inode设备)。 off_t st_size:文件大小(字节数)。 blksize_t st_blksize:文件系统I/O块大小。 blkcnt_t st_blocks:分配块数。 time_t st_atime:最后访问时间。 time_t st_mtime:最后修改时间。 time_t st_ctime:最后状态改变时间。 }
-
4. 示例代码
4.1 显示字符
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#define FONTDATAMAX 4096
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
/* 1 0x01 '^A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x7e, /* 01111110 */
0x81, /* 10000001 */
0xa5, /* 10100101 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0xbd, /* 10111101 */
0x99, /* 10011001 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0x7e, /* 01111110 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
... ...
}
struct fb_var_screeninfo info;
int fd;
unsigned char *size;
int screen_size;
unsigned int line_width;
unsigned int pixel_width;
void put_pixel(int x,int y,unsigned int color)
{
unsigned char *pen_8 = size + y * line_width + x * pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
unsigned int red,green,blue;
switch(info.bits_per_pixel)
{
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("color err\n");
break;
}
}
}
void show_ascii(int x,int y,unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
int i,b;
unsigned char byte;
for(i = 0; i < 16;i++) //遍历行
{
byte = dots[i]; // 将每一行存储在byte里
for(b = 7; b >= 0; b--) // 位移的次数
{
if(byte & (1 << b)) //将1位移后与byte相与
{
put_pixel(x+7-b,y+i,0xffffff); // 1就显示为白色
}else
{
put_pixel(x+7-b,y+i,0x000000); // 0就显示为黑色
}
}
}
}
int main(int argc,char **argv)
{
fd = open("/dev/fb0",O_RDWR);
if(fd < 0)
{
printf("open err\n");
return -1;
}
if(ioctl(fd,FBIOGET_VSCREENINFO,&info))
{
printf("ioctl err\n");
return -1;
}
line_width = info.xres * info.bits_per_pixel / 8;
pixel_width = info.bits_per_pixel / 8;
screen_size = info.xres * info.yres * info.bits_per_pixel / 8;
size = (unsigned char *)mmap(NULL,screen_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(size == (unsigned char *)-1)
{
printf("mmap err\n");
return -1;
}
// 将整个屏幕都显示为黑色
memset(size,0,screen_size);
show_ascii(info.xres/2,info.yres/2,'A'); //显示字符'A'
munmap(size,screen_size);
close(fd);
return 0;
}
4.2 显示中文
HZK16文件:每个汉字使用32字节来描述
跟ASCII字库一样,每个字节中每一位用来表示一个像素,位值等于1时表示对 应像素被点亮,位值等于0时表示对应像素被熄灭。
HZK16 中是以GB2312编码值来查找点阵的,以“中”字为例,它的编码值 是“ 0xd6 0xd0”,其中的0xd6表示“区码”,表示在哪一个区:第“0xd6 - 0xa1” 区;其中的0xd0表示“位码”,表示它是这个区里的哪一个字符:第“0xd0 - 0xa1”个。每一个区有94个汉字。区位码从0xa1而不是从0开始,是为了兼容ASCII码。
所以,我们要显示的“中”字,它的GB2312编码是d6d0,它是HZK16里 第“(0xd6-0xa1)*94+(0xd0-0xa1)”个字符。
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#define FONTDATAMAX 4096
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
/* 1 0x01 '^A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x7e, /* 01111110 */
0x81, /* 10000001 */
0xa5, /* 10100101 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0xbd, /* 10111101 */
0x99, /* 10011001 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0x7e, /* 01111110 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
... ...
}
struct fb_var_screeninfo info;
int fd;
unsigned char *size;
int screen_size;
unsigned int line_width;
unsigned int pixel_width;
int fd_hzk16;
struct stat hzk_stat;
unsigned char *hzkmem;
void put_pixel(int x,int y,unsigned int color)
{
unsigned char *pen_8 = size + y * line_width + x * pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
unsigned int red,green,blue;
switch(info.bits_per_pixel)
{
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("color err\n");
break;
}
}
}
void show_ascii(int x,int y,unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
int i,b;
unsigned char byte;
for(i = 0; i < 16;i++) //遍历行
{
byte = dots[i]; // 将每一行存储在byte里
for(b = 7; b >= 0; b--) // 位移的次数
{
if(byte & (1 << b)) //将1位移后与byte相与
{
put_pixel(x+7-b,y+i,0xffffff); // 1就显示为白色
}else
{
put_pixel(x+7-b,y+i,0x000000); // 0就显示为黑色
}
}
}
}
void show_chinese(int x,int y,unsigned char *str)
{
// 注意:区码和位码都是从0XA1开始的
unsigned int area = str[0] - 0xA1; // 区码
unsigned int where = str[1] - 0xA1; // 位码
// 获得起始映射地址 乘以32是因为在HZK16中每个字符占32字节
// 每个区有94个汉字
unsigned char *dots = hzkmem + (area * 94 + where)*32;
unsigned char byte;
int i, j, b;
for (i = 0; i < 16; i++) //遍历每一行
for (j = 0; j < 2; j++) //有两个字节
{
byte = dots[i*2 + j];
for (b = 7; b >=0; b--) //位移的次数
{
if (byte & (1<<b))
{
/* show */
put_pixel(x+j*8+7-b, y+i, 0xffffff); /* 白 */
}
else
{
/* hide */
put_pixel(x+j*8+7-b, y+i, 0); /* 黑 */
}
}
}
}
int main(int argc,char **argv)
{
unsigned char str[] = "中";
fd = open("/dev/fb0",O_RDWR);
if(fd < 0)
{
printf("open err\n");
return -1;
}
if(ioctl(fd,FBIOGET_VSCREENINFO,&info))
{
printf("ioctl err\n");
return -1;
}
line_width = info.xres * info.bits_per_pixel / 8;
pixel_width = info.bits_per_pixel / 8;
screen_size = info.xres * info.yres * info.bits_per_pixel / 8;
size = (unsigned char *)mmap(NULL,screen_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(size == (unsigned char *)-1)
{
printf("mmap err\n");
return -1;
}
// 已只读方式打开"HZK16"文件
fd_hzk16 = open("HZK16",O_RDONLY);
if(fd_hzk16 < 0)
{
printf("open err\n");
return -1;
}
// 获取文件状态并存储到hzk_stat结构体里
if(fstat(fd_hzk16,&hzk_stat))
{
printf("fstat err\n");
return -1;
}
// 指向字体库起始地址的指针
hzkmem =(unsigned char *)mmap(NULL,hzk_stat.st_size, PROT_READ,MAP_SHARED, fd_hzk16,0);
if(hzkmem == (unsigned char *)-1)
{
printf("mmap err\n");
return -1;
}
// 将整个屏幕都显示为黑色
memset(size,0,screen_size);
show_ascii(info.xres/2,info.yres/2,'A'); //显示字符'A'
printf("chinese code: %02x %02x\n", str[0], str[1]);
show_chinese(info.xres/2 + 8, info.yres/2, str);
munmap(size,screen_size);
close(fd);
return 0;
}
交叉编译需要指定编码格式
arm-buildroot-linux-gnueabihf-gcc -fexec-charset=GB2312 -o chinese_show chinese_show.c