7.3
操作像素的程序分析
本节源码位于如下目录:
7.3.1 打开文件
代码如下:
73 fd_fb = open("/dev/fb0", O_RDWR);
74 if (fd_fb < 0)
75 {
76 printf("can't open /dev/fb0\n");
77 return -1;
78 }
7.3.2 获取LCD参数
LCD驱动程序给APP提供2类参数:可变的参数fb_var_screeninfo、固定的参数fb_fix_screeninfo。编写应用程序时主要关心可变参数,它的结构体定义如下(#include
图7.6 fb_var_screeninfo
可以使用以下代码获取fb_var_screeninfo:
12 static struct fb_var_screeninfo var; /* Current var */
……
79 if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
80 {
81 printf("can't get var\n");
82 return -1;
83 }
注意到ioctl里用的参数是:FBIOGET_VSCREENINFO,它表示get varscreen info,获得屏幕的可变信息;当然也可以使用FBIOPUT_VSCREENINFO来调整这些参数,但是很少用到。
对于固定的参数fb_fix_screeninfo,在应用编程中很少用到。它的结构体定义如下:
图7.7 fb_fix_screeninfo
可以使用ioctl FBIOGET_FSCREENINFO来读出这些信息,但是很少用到。
7.3.3 映射Framebuffer
要映射一块内存,需要知道它的地址──这由驱动程序来设置,需要知道它的大小──这由应用程序决定。代码如下:
85 line_width = var.xres * var.bits_per_pixel / 8;
86 pixel_width = var.bits_per_pixel / 8;
87 screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
88 fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
89 if (fb_base == (unsigned char *)-1)
90 {
91 printf("can't mmap\n");
92 return -1;
93 }
第88行中,screen_size是整个Framebuffer的大小;PROT_READ | PROT_WRITE表示该区域可读、可写;MAP_SHARED表示该区域是共享的,APP写入数据时,会直达驱动程序,这个参数的更深刻理解可以参考后面驱动基础中讲到的mmap知识。
7.3.4 描点函数
能够在LCD上描绘指定像素后,就可以写字、画图,描点函数是基础。代码如下:
28 void lcd_put_pixel(int x, int y, unsigned int color)
29 {
30 unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;
31 unsigned short *pen_16;
32 unsigned int *pen_32;
33
34 unsigned int red, green, blue;
35
36 pen_16 = (unsigned short *)pen_8;
37 pen_32 = (unsigned int *)pen_8;
38
39 switch (var.bits_per_pixel)
40 {
41 case 8:
42 {
43 *pen_8 = color;
44 break;
45 }
46 case 16:
47 {
48 /* 565 */
49 red = (color >> 16) & 0xff;
50 green = (color >> 8) & 0xff;
51 blue = (color >> 0) & 0xff;
52 color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
53 *pen_16 = color;
54 break;
55 }
RemiPi
Software Development Manual
56 case 32:
57 {
58 *pen_32 = color;
59 break;
60 }
61 default:
62 {
63 printf("can't surport %dbpp\n",var.bits_per_pixel);
64 break;
65 }
66 }
67 }
第28行中传入的color表示颜色,它的格式永远是0x00RRGGBB,即RGB888。当LCD是16bpp时,要把color变量中的R、G、B抽出来再合并成RGB565格式。
第30行计算(x,y)坐标上像素对应的Framebuffer地址。
第43行,对于8bpp,color就不再表示RBG三原色了,这涉及调色板的概念,color是调色板的值。
第49~51行,先从color变量中把R、G、B抽出来。
第52行,把red、green、blue这三种8位颜色值,根据RGB565的格式,只保留red中的高5位、green中的高6位、blue中的高5位,组合成一个新的16位颜色值。
第53行,把新的16位颜色值写入Framebuffer。
第58行,对于32bpp,颜色格式跟color参数一致,可以直接写入Framebuffer。
7.3.5 上机实验
本程序的main函数,在最后只是简单地画了几个点:
95 /* 清屏: 全部设为白色 */
96 memset(fbmem, 0xff, screen_size);
97
98 /* 随便设置出 100 个为红色 */
99 for (i = 0; i < 100; i++)
100 lcd_put_pixel(var.xres/2+i, var.yres/2, 0xFF0000);
编译:
source /opt/remi-sdk/environment-setup-aarch64-poky-linux
$CC show_pixel.c -o show_pixel
假设设置开发板的IP为:192.168.5.9,上传程序到开发板上。
scp ./show_pixel root@192.168.5.9:/mnt/
测试:进入/mnt目录运行程序(在运行程序前,请确保已经接好HMDI显示屏):
root@myir-remi-1g:~
root@myir-remi-1g:/mnt
show_pixel
root@myir-remi-1g:/mnt# ./show_pixel
运行程序后,可以看到HDMI显示屏上会显示白色的背景和红色的横线。
7.4
基于Framebuffer的字符显示
要在LCD中显示一个ASCII字符,即英文字母这些字符,首先是要找到字符对应的点阵。在Linux内核源码中有这个文件:lib\fonts\font_8x16.c,里面以数组形式保存各个字符的点阵,比如:
图7.8 LCD的ASCII点阵
数组里的数字是如何表示点阵的?以字符A为例,如图7.9所示:
图7.9 字符A的点阵
上图左侧有16行数值,每行1个字节。每一个节对应右侧一行中8个像素:像素从右边数起,bit0对应第0个像素,bit1对应第1个像素,……,bit7对应第7个像素。某位的值为1时,表示对应的像素要被点亮;值为0时表示对应的像素要熄灭。
所以要显示某个字符时,根据它的ASCII码在fontdata_8x16数组中找到它的点阵,然后取出这16个字节去描画16行像素。
比如字符A的ASCII值是0x41,那么从fontdata_8x16[0x41*16]开始取其点阵数据。
下载所有源码后,本节源码位于如下目录:
核心函数是void lcd_put_ascii(int x, int y, unsigned char c),它在LCD的(x,y)位置处显示字符c,代码如下图所示:
图7.10 显示字符函数
7.4.1 获取点阵
对于字符c,char c,它的点阵获取方法如下:
4693 unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
7.4.2 描点
根据“图7.9 字符A的点阵”,我们分析下如何利用点阵在LCD上显示一个英文字母。
因为有十六行,所以首先要有一个循环16次的大循环,然后每一行里有8位,那么在每一个大循环里也需要一个循环8次的小循环。小循环里的判断单行的描点情况,如果是1,就填充白色,如果是0就填充黑色,如此一来,就可以显示出黑色底,白色轮廓的英文字母。
4697 for (i = 0; i < 16; i++)
4698 {
4699 byte = dots[i];
4700 for (b = 7; b >= 0; b--)
4701 {
4702 if (byte & (1<
4703 {
4704 /* show */
4705 lcd_put_pixel(x+7-b, y+i, 0xffffff); /* 白 */
4706 }
4707 else
4708 {
4709 /* hide */
4710 lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */
4711 }
4712 }
4713 }
7.4.3 main函数
main函数中首先要打开LCD设备,获取Framebuffer参数,实现lcd_put_pixel函数;然后调用lcd_put_ascii即可绘制字符。
代码如下:
4716 int main(int argc, char **argv)
4717 {
4718 fd_fb = open("/dev/fb0", O_RDWR);
4719 if (fd_fb < 0)
4720 {
4721 printf("can't open /dev/fb0\n");
4722 return -1;
4723 }
4724 if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
4725 {
4726 printf("can't get var\n");
4727 return -1;
4728 }
4729
4730 line_width = var.xres * var.bits_per_pixel / 8;
4731 pixel_width = var.bits_per_pixel / 8;
4732 screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
4733 fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP
_SHARED, fd_fb, 0);
4734 if (fbmem == (unsigned char *)-1)
4735 {
4736 printf("can't mmap\n");
4737 return -1;
4738 }
4739
4740 /* 清屏: 全部设为黑色 */
4741 memset(fbmem, 0, screen_size);
4742
4743 lcd_put_ascii(var.xres/2, var.yres/2, 'A'); /*在屏幕中间显示 8*16 的字母 A*/
4744
4745 munmap(fbmem , screen_size);
4746 close(fd_fb);
4747
4748 return 0;
4749 }
4750
7.4.4 编译c文件show_ascii.c
编译命令如下:
source /opt/remi-sdk/environment-setup-aarch64-poky-linux
$CC show_ascii.c -o show_ascii
假设设置开发板的IP为:192.168.5.9,上传程序到开发板上。
scp ./show_ascii root@192.168.5.9:/mnt/
7.4.5 上机实验
测试:进入/mnt目录运行程序:
root@myir-remi-1g:~
root@myir-remi-1g:/mnt
show_ascii
如果实验成功,我们将看到HDMI显示屏幕中间会显示出一个白色的字母‘A’。
如您在使用瑞萨MCU/MPU产品中有任何问题,可识别下方二维码或复制网址到浏览器中打开,进入瑞萨技术论坛寻找答案或获取在线技术支持。
https://community-ja.renesas.com/zh/forums-groups/mcu-mpu/
未完待续
推荐阅读
Linux I2C设备访问方法 - RZ MPU工业控制教程连载(9)
用户态接口应用与编写APP - RZ MPU工业控制教程连载(10)
Linux SPI协议介绍与访问方法 - RZ MPU工业控制教程连载(11)