跳到主要内容位置

显示系统

1.数据结构抽象#

显示系统场景:

假设程序是在一个buf里面绘制图片,上层APP在buf里绘制图片,更新某个区域后把这个图片更新到LCD或者WEB上。

定义这个绘制图片的结构体

struct DispOpr{
char *name;
char *GetBuffer(int *pXres, int *pYres, int *pBpp); //X,Y分辨率,每个像素占据多少位
int FlushRegion(struct PRegion ptRegion); //把区域ptRegion刷新到buf里面
struct Dispopr *ptNext;
}

disp_manager.h

#ifndef _DISP_MANAGER_H
#define _DISP_MANAGER_H
typedef struct Region
{
int iLeftUpX;
int iLeftUpy;
int iWidth;
int iHight;
} Region, *PRegion;
typedef struct DispOpr
{
char *name;
char *GetBuffer(int *pXres, int *pYres, int *pBpp); // X,Y分辨率,每个像素占据多少位
int FlushRegion(PRegion ptRegion, char * buffer); //把区域ptRegion刷新到buf里面
struct Dispopr *ptNext;
};
#endif

2.Framebuffer编程#

参考01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\07_framebuffer代码:

初始化函数:

  • 打开 LCD 设备节点(open),获取分辨率等参数(ioctl)

  • 通过 mmap 映射 Framebuffer,在 Framebuffer 中写入数据

    函数原型:

    /* mmap建立内存映射, 并返回映射首地址指针start.
    1.参数start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
    2.参数length:代表将文件中多大的部分映射到内存。
    3.参数prot:映射区域的保护方式。可以为以下几种方式的组合:
    PROT_EXEC 映射区域可被执行
    PROT_READ 映射区域可被读取
    PROT_WRITE 映射区域可被写入
    PROT_NONE 映射区域不能存取
    4.参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或 MAP_PRIVATE。
    MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
    MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
    MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
    MAP_ANONYMOUS 建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
    MAP_DENYWRITE 只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
    MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
    5.参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
    6.参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。
    */
    base = void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
    int munmap(void* base, size_t length);

退出函数:

  • munmap取消映射
  • close文件句柄

实现如下:

#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 "disp_manager.h"
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;
static int DeviceInit(void)
{
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) //获取屏幕信息到var变量
{
printf("can't get var\n");
return -1;
}
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
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;
}
return 0;
}
static int DeviceExit(void)
{
munmap(fb_base, screen_size);
close(fd_fb);
return 0;
}

实例化一个结构体及其函数:将fb_var_screeninfo结构体变量var的值保存返回,并直接返回显存地址

/**
* @brief 1.可以返回LCD的framebuffer,以后上层APP可以直接操作LCD
* 2.也可以malloc返回一块无关的buffer,要使用FbFlushRegion
*
* @param pXres X方向分辨率
* @param pYres Y方向分辨率
* @param pBpp 一个像素的位数
* @return char*
*/
static char *FbGetBuffer(int *pXres, int *pYres, int *pBpp)
{
*pXres = var.xres;
*pYres = var.yres;
*pBpp = var.bits_per_pixel;
return fb_base; //直接返回了显存的地址,即第1种方式
}
static int FbFlushRegion(PRegion ptRegion, char *buffer)
{
return 0;
}
static DispOpr g_tFramebufferOpr = {
.name = "fb",
.DeviceInit = DeviceInit,
.DeviceExit = DeviceExit,
.GetBuffer = FbGetBuffer,
.FlushRegion = FbFlushRegion,
}

3.显示管理#

继续抽象出公共部分,比如支持多个显示方式,Framebuffer或WEB,需要提供一个选择输出方式函数。

我们加入了中间层disp_manager.c, 所以底层Framebuffer需要一个注册函数,把自己的显示操作函数DispOpr g_tFramebufferOpr注册到上层链表中。

/* framebuffer.c */
void FramebufferInit(void)
{
RegisterDisplay(&g_tFramebufferOpr);
}

在disp_manager.c中,我们主要实现抽象各种显示方式公共部分,提供给上层APP:

  • 显示初始化,让下层注册各自的DispOpr DisplayInit
  • 选择默认显示方式函数 SelectDefaultDisplay
  • 硬件初始化默认显示设备 InitDefaultDisplay
  • 给上层使用的获取显示buffer GetDisplayBuffer
  • 绘制一个像素点的函数 PutPixel
  • 将buffer刷新到设备 FlushDispalyRegion

先定义一个显示buf结构体与刷新区域结构体:

disp_manager.h#

typedef struct DispBuff
{
int iXres;
int iYres;
int iBpp;
char *buff;
} DispBuff, *PDispBuff;
typedef struct Region
{
int iLeftUpX;
int iLeftUpY;
int iWidth;
int iHight;
} Region, *PRegion;

disp_manager.c#

定义全局变量

static PDispOpr g_DispDevs = NULL; //显示设备操作指针,链表头
static PDispOpr g_DispDefault = NULL; //当前显示设备操作指针
static DispBuff g_tDisplay; //显示buff存储像素信息
static int line_width; //每行宽度:每一行占据多少字节
static int pixel_width; //每个像素宽度

注册函数:将ptDispOpr结构体指针放入链表

void RegisterDisplay(PDispOpr ptDispOpr)
{
ptDispOpr->ptNext = g_DispDevs;
g_DispDevs = ptDispOpr;
}

显示初始化(让下层注册结构体),选择默认显示函数:

void DisplayInit(void)
{
extern void FramebufferInit(void);
FramebufferInit(); //初始化Framebuffer
// WebInit();
}
int SelectDefaultDisplay(char *name)
{
PDispOpr pTmp = g_DispDevs;
while (pTmp)
{
if (strcmp(name, pTmp->name) == 0)
{
g_DispDefault = pTmp;
return 0;
}
pTmp = pTmp->ptNext;
}
return -1;
}

初始化默认显示方式:调用下层的初始化函数,获取buf、line_width、pixel_width

int InitDefaultDisplay(void)
{
int ret;
ret = g_DispDefault->DeviceInit();
if (ret)
{
printf("DeviceInit err\n");
return -1;
}
ret = g_DispDefault->GetBuffer(&g_tDisplay);
if (ret)
{
printf("GetBuffer err\n");
return -1;
}
line_width = g_tDisplay.iXres * g_tDisplay.iBpp / 8;
pixel_width = g_tDisplay.iBpp / 8;
return 0;
}
/* 返回显示buf */
PDispBuff GetDisplayBuffer(void)
{
return &g_tDisplay;
}

绘制像素点与刷新函数:

int PutPixel(int x, int y, unsigned int dwColor)
{
unsigned char *pen_8 = (unsigned char *)g_tDisplay.buff + 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 (g_tDisplay.iBpp)
{
case 8:
{
*pen_8 = dwColor;
break;
}
case 16:
{
/* 565 */
red = (dwColor >> 16) & 0xff;
green = (dwColor >> 8) & 0xff;
blue = (dwColor >> 0) & 0xff;
dwColor = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = dwColor;
break;
}
case 32:
{
*pen_32 = dwColor;
break;
}
default:
{
printf("can't surport %dbpp\n", g_tDisplay.iBpp);
return -1;
break;
}
}
return 0;
}
int FlushDispalyRegion(PRegion ptRegion, PDispBuff ptDispBuff)
{
return g_DispDefault->FlushRegion(ptRegion, ptDispBuff); //调用底层的刷新函数
}

4.测试#

上层APP函数#

参考"01_all_series_quickstart\04_嵌入式Linux应用开发基础知识\source\08_show_ascii\show_ascii.c"

修改lcd_put_ascii函数中的lcd_put_pixel为我们自己像素点绘制函数:

/**********************************************************************
* 函数名称: lcd_put_ascii
* 功能描述: 在LCD指定位置上显示一个8*16的字符
* 输入参数: x坐标,y坐标,ascii码
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
void lcd_put_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];
for (b = 7; b >= 0; b--)
{
if (byte & (1 << b))
{
/* show */
PutPixel(x + 7 - b, y + i, 0xffffff); /* 白 */
}
else
{
/* hide */
PutPixel(x + 7 - b, y + i, 0); /* 黑 */
}
}
}
}

修改主函数:调用我们封装设计的disp_manager

int main(int argc, char **argv)
{
Region region;
PDispBuff ptBuffer;
/* 显示初始化 */
DisplayInit();
SelectDefaultDisplay("fb");
InitDefaultDisplay();
lcd_put_ascii(100, 100, 'A'); /*在屏幕中间显示8*16的字母A*/
/* 刷新区域 */
region.iLeftUpX = 100;
region.iLeftUpY = 100;
region.iWidth = 8;
region.iHight = 16;
ptBuffer = GetDisplayBuffer();
FlushDispalyRegion(&region, ptBuffer);
return 0;
}

总体代码图#

上机#

设置环境变量-交叉编译工具链

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

设置完毕后,要执行

source ~/.bashrc

命令使其生效,并通过

arm-buildroot-linux-gnueabihf-gcc -v

验证

关闭 qt gui 可以执行/etc/init.d/S99myirhmi2 stop命令

挂载网络文件系统:

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt/

错误#

  • 函数指针

    typedef struct DispOpr
    {
    char *name;
    int (*DeviceInit)(void);
    int (*DeviceExit)(void);
    int (*GetBuffer)(PDispBuff ptDispBuff); // ptDispBuff 包含 X,Y分辨率,每个像素占据多少位
    int (*FlushRegion)(PRegion ptRegion, PDispBuff ptDispBuff); // 把区域ptRegion刷新到buf里面
    struct DispOpr *ptNext;
    } DispOpr, *PDispOpr;
  • strcmp(字符串1,字符串2)

    比较字符串s1和s2。

    当s1<s2时,返回为负数 注意不是-1 当s1==s2时,返回值= 0 当s1>s2时,返回正数 注意不是1 即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。如:"A"<"B" "a">"A" "computer">"compare"


请点击左侧菜单(移动端为右下角)选择要查看的所有笔记吧。