Linux基础项目开发1:量产工具——显示系统(二)

简介: Linux基础项目开发1:量产工具——显示系统(二)


前言:

前面我们已经对这个项目的基本框架有了一个初步的了解与认识,要实现显示管理器与输入管理器,有输入有输出基本就实现这个项目的大部分功能了,首先我们先来做显示系统,对于上层系统为了让程序更好扩展,我们得添加一个显示管理器,在下面有各种设备,就比如有Framebuflerweb输出。

一、数据结构抽象

       我们添加的一个显示管理器中有Framebuflerweb输出,对于俩个不同的设备我们需要抽象出同一个结构体类型。

1.使用场景

       在上图中我们可以将其分为两层,上层要获得下层某个结构体,通过这个结构体中的函数来操作、绘制、刷新上层的界面。

       所有我们需要定义一个统一的结构体DispOpr

2.disp_manager.h

 #ifndef _DISP_MANAGER_H
 #define _DISP_MANAGER_H
 
 typedef struct Region {
     int iLeftUpX;                //左上角x坐标
      int iLeftUpY;                //左上角y坐标
     int iWidth;                  //宽度
    int iHeigh;                  //高度
  }Region, *PRegion;
 
 typedef struct DispOpr {
    char *name;                  //显示模块的名字 
     char *GetBuffer(int *pXres, int *pYres, int *pBpp);//分辨率(长和宽)每个像素占据多少位
     int FlushRegion(PRegion ptRegion, char *buffer);//刷出某个区域
     struct DispOpr *ptNext;      //链表
 };
 
#endif

第1~2行:防止头文件在.c文件中多次定义

第4~9行:定义刷新区域结构体

第11~16行:定义统一管理的结构体

二、Framebuffer编程

1.disp_manager.h

 #ifndef _DISP_MANAGER_H
 #define _DISP_MANAGER_H
 
 typedef struct Region {
     int iLeftUpX;
     int iLeftUpY;
     int iWidth;
     int iHeigh;
 }Region, *PRegion;
 
 typedef struct DispOpr {
     char *name;
     int DeviceInit(void);
     int DeviceExit(void);
     char *GetBuffer(int *pXres, int *pYres, int *pBpp);
     int FlushRegion(PRegion ptRegion, char *buffer);
     struct DispOpr *ptNext;
 }DispOpr, *PDispOpr;
 
 #endif

第13行:初始化函数定义

第14行:退出函数定义

2.framebuffer.c

  #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;                       //framebuffer文件
  static struct fb_var_screeninfo var;    /* Current var */
  static int screen_size;                 //framebuffer长度
  static unsigned char *fb_base;          //framebuffer地址
  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))
      {
          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;
  }
 
 
  /* 可以返回LCD的framebuffer, 以后上层APP可以直接操作LCD, 可以不用FbFlushRegion
   * 也可以malloc返回一块无关的buffer, 要使用FbFlushRegion刷到LCD上
   */
  static int FbGetBuffer(PDispBuff ptDispBuff);
 
  {
      ptDispBuff->iXres = var.xres;
      ptDispBuff->iYres = var.yres;
      ptDispBuff->iBpp  = var.bits_per_pixel;
      ptDispBuff->buff  = (char *)fb_base;
      return 0;
  }
 
  static int FbFlushRegion(PRegion ptRegion, PDispBuff ptDispBuff)
  {
      return 0;
  }
 
 
  static DispOpr g_tFramebufferOpr = {
      .name        = "fb",
      .DeviceInit  = FbDeviceInit,
      .DeviceExit  = FbDeviceExit,
      .GetBuffer   = FbGetBuffer,
      .FlushRegion = FbFlushRegion,
  };
 
 
  void FramebufferInit(void)
  {
      RegisterDisplay(&g_tFramebufferOpr);
  }
 
20 static int DeviceInit(void)

第20~45行:初始化函数

47 static int DeviceExit(void)

第47~52行:退出函数

第58~66行的FbGetBuffer是已经封装好的,详细封装过程后续会讲

第61~62行:设置分辨率

第63行:设置bpp

74 static DispOpr g_tFramebufferOpr

第74~80行:上层代码可以根据这个结构体里的函数来初始化LCD来得到Buffer

       第78行:通过.GetBuffer   = FbGetBuffer来构建好图像和文字

       第79行:通过.FlushRegion = FbFlushRegion来刷新到LCD上

第83~86行:注册结构体g_tFramebufferOpr,详细介绍在三、显示管理

       

三、显示管理

       上层函数想要选择哪个设备进行显示,需要中间加一个函数进行选择,起到承上启下的作用,用来实现显示管理,是操作Framebuffer还是WEB设备,需要进行选择某个模块,好可以提供一些函数,描点等

1.disp_manager.h

 #ifndef _DISP_MANAGER_H
 #define _DISP_MANAGER_H
 
 #ifndef NULL
 #define NULL (void *)0
 #endif
 
 typedef struct DispBuff {
     int iXres;     //x坐标分辨率
     int iYres;     //y坐标分辨率
     int iBpp;      //bpp
     char *buff;    缓冲区地址
 }DispBuff, *PDispBuff;
 
 
 typedef struct Region {
     int iLeftUpX;
     int iLeftUpY;
     int iWidth;
     int iHeigh;
 }Region, *PRegion;
 
 typedef struct DispOpr {
     char *name;
     int (*DeviceInit)(void);
     int (*DeviceExit)(void);
     int (*GetBuffer)(PDispBuff ptDispBuff);
     int (*FlushRegion)(PRegion ptRegion, PDispBuff ptDispBuff);
     struct DispOpr *ptNext;
 }DispOpr, *PDispOpr;
 
 void RegisterDisplay(PDispOpr ptDispOpr);
 
 void DisplayInit(void);
 int SelectDefaultDisplay(char *name);
 int InitDefaultDisplay(void);
 int PutPixel(int x, int y, unsigned int dwColor);
 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff);
 PDispBuff GetDisplayBuffer(void);
 
 
 #endif

第8~13行:进一步封装GetBuffer(int *pXres, int *pYres, int *pBpp)

第27行:将4~9行的PDispBuff保存到GetBuffer

第32行:定义注册函数RegisterDisplay

第34行:调用底层提供的模块

第35行:定义选择模块函数

第36行:选择默认的SelectDefaultDisplay后,我们还需要定义初始化函数

第37行:提供绘制图像函数

第38行:将绘制好的图像刷新到硬件上

第39行:返回取址

2.disp_manager.c

 #include <disp_manager.h>
 #include <stdio.h>
 #include <string.h>
 
 /* 管理底层的LCD、WEB */
 static PDispOpr g_DispDevs = NULL;
 static PDispOpr g_DispDefault = NULL;
 static DispBuff g_tDispBuff;
 static int line_width;
 static int pixel_width;
 
  int PutPixel(int x, int y, unsigned int dwColor)
  {
      unsigned char *pen_8 = (*unsigned char*)(g_tDispBuff.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_tDispBuff.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_tDispBuff.iBpp);
              return -1;
              break;
          }
      }
       return 0;
  }
 
 
 
  void RegisterDisplay(PDispOpr ptDispOpr)
  {
      ptDispOpr->ptNext = g_DispDevs;
      g_DispDevs = ptDispOpr;
  }
 
 
  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;
  }
 
  int InitDefaultDisplay(void)
  {
      int ret;
 
      ret = g_DispDefault->DeviceInit();
      if (ret)
      {
          printf("DeviceInit err\n");
          return -1;
      }
 
 
      ret = g_DispDefault->GetBuffer(&g_tDispBuff);
      if (ret)
      {
          printf("GetBuffer err\n");
          return -1;
      }
 
      line_width  = g_tDispBuff.iXres * g_tDispBuff.iBpp/8;
      pixel_width = g_tDispBuff.iBpp/8;
 
      return 0;
 }
 
 
 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)
 {
     return g_DispDefault->FlushRegion(ptRegion, ptDispBuff);
 }
 
 
 void DisplayInit(void)
 {
     void FramebufferInit(void);
     FramebufferInit();
 }
 
 PDispBuff GetDisplayBuffer(void)
 {
    return &g_tDispBuff;
 }

第6行:将底层的LCD、WEB所实现的结构体放入到链表(指针)中,所以我们需要提供一个函数。void RegisterDisplay(PDispOpr ptDispOpr)

第7行:存放找到名字的新链表

第8行:定义一个全局变量 g_tDispBuff之后用来放第89行中GetBuffer中的(int* pxres, int * pYres, int * pBpp)

void RegisterDisplay(PDispOpr ptDispOpr)

 第57~61行:构造注册函数,将typedef struct DispOpr这个结构体注册到g_DispDevs链表中

       第59行:传入底层结构体指针ptDispOpr指向链表头g_DispDevs

       第60行:链表头g_DispDevs指向ptDispOpr

void DisplayInit(void)

第113~117行:调用底层提供的 FramebufferInit();目前我们只需要调用FramebufferInit()即可,还没有实现WEB的功能

       如果g_DispDevs链表中有好几个模块,那我们应该如何选择呢,现在我们就要设计一个模块选择函数进行模块的选择

int SelectDefaultDisplay(char *name)

第60~75行:模块选择函数

       定义一个临时指针pTmp存放链表头

       while循环里根据名字name找到那一项

       找到的名字放到新链表g_DispDefault中,在第五行中有定义

       第75行,如果这个不对,这找寻下一个是否正确    

       因为上层需要通过中间的显示管理来绘制图像,坐标反馈给底层进行反应,所有需要在中间层加入绘制函数

int PutPixel(int x, int y, unsigned int dwColor)

第9~10:每个像素占据多长多少像素可以先定义好,事先计算好,每一行多少字节,每个像素多少宽度,这个在InitDefaultDisplay()中已经计算好了

第12~53行:设置绘制图像函数

       函数内表示绘制的x、y坐标和绘制图像的颜色      

       第14行:g_tDispBuff.buff是写存的基地址

       第23行:g_tDispBuff.iBpp是每个像素的宽度

 

      想要在上层显示一个像素,首先需要得到一块内存,内存可以调用到底层提供的结构体,里面的g_tDispBuff这个函数,在上层得到一个新的buf,在这块buf里面绘制图像,但是g_tDispBuff不可以放到PutPixel,我们需要再定义一个函数: InitDefaultDisplay(void)

选择默认的SelectDefaultDisplay后,我们还需要初始化

int InitDefaultDisplay(void)

第81~104行:

          第85行: 调用 g_DispDefault 中的DeviceInit()

         第93行: 调用 g_DispDefault  中的GetBuffer(&g_tDispBuff)

         这里我们需要定义一个新的GetBuffer结构体,把它的(int* pxres, int * pYres, int * pBpp)再次进行封装,封装结构体在disp_manager.h中的DispBuff

          第93行:将GetBuffer中的(int* pxres, int * pYres, int * pBpp)放到全局变量 g_tDispBuff

          第100行:line_width表示每一行占据多少个字节

          第101行:pixel_width表示每个像素的宽度

       当我们上层已经通过 PutPixel 已经绘制好图像,那我们就需要把它刷到硬件上,我们需要再提供一个函数FlushDisplayRegion

 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)

       第109行:调用底层设备提供的FlushRegion函数

       返回取址

PDispBuff GetDisplayBuffer(void)

献上韦东山老师对以上代码流程的梳理过程:

电子产品量产工具显示系统代码流程

四、测试单元

book@100ask:~/04_disp_unittest/display$ vi Makefile

1.通用Makefile

 
 CROSS_COMPILE ?=
 AS      = $(CROSS_COMPILE)as
 LD      = $(CROSS_COMPILE)ld
 CC      = $(CROSS_COMPILE)gcc
 CPP     = $(CC) -E
 AR      = $(CROSS_COMPILE)ar
 NM      = $(CROSS_COMPILE)nm
 
 STRIP       = $(CROSS_COMPILE)strip
 OBJCOPY     = $(CROSS_COMPILE)objcopy
 OBJDUMP     = $(CROSS_COMPILE)objdump
 
 export AS LD CC CPP AR NM
 export STRIP OBJCOPY OBJDUMP
 
 CFLAGS := -Wall -O2 -g
 CFLAGS += -I $(shell pwd)/include
 
 LDFLAGS :=
 
 export CFLAGS LDFLAGS
 
 TOPDIR := $(shell pwd)
 export TOPDIR
 
 TARGET := test
 
 
 obj-y += display/
 obj-y += unittest/
 
 all : start_recursive_build $(TARGET)
     @echo $(TARGET) has been built!
 
 start_recursive_build:
     make -C ./ -f $(TOPDIR)/Makefile.build
 
 $(TARGET) : built-in.o
     $(CC) -o $(TARGET) built-in.o $(LDFLAGS)
 
 clean:
     rm -f $(shell find -name "*.o")
     rm -f $(TARGET)
 
 distclean:
     rm -f $(shell find -name "*.o")
     rm -f $(shell find -name "*.d")
     rm -f $(TARGET)

第27行:编译出test的应用程序

第30行:指定display下的目录

第31行:指定unittest下的目录

2.display下的Makefile

 
EXTRA_CFLAGS  :=
CFLAGS_file.o :=
 
obj-y += disp_manager.o
obj-y += framebuffer.o

3. unittest下的Makefile

EXTRA_CFLAGS  :=
CFLAGS_file.o :=
  
obj-y += disp_test.o

4.

#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>
 
#define FONTDATAMAX 4096
 
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
    //字符编码,由于太多就不展示了
    .........
}
 
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); /* 黑 */
      }
    }
  }
}
 
int main(int argc, char **argv)
{
  Region region;                 //定义刷新区域的大小
  PDispBuff ptBuffer;
    
  DisplayInit();                 //调用DisplayInit()
 
  SelectDefaultDisplay("fb");    //选择默认的设备,传入一个名字为fd的设备
 
  InitDefaultDisplay();          //初始化这个默认的设备
 
  lcd_put_ascii(100, 100, 'A');  //在屏幕100,100的位置显示一个字母A
 
  region.iLeftUpX = 100;
  region.iLeftUpY = 100;
  region.iWidth   = 8;
  region.iHeigh   = 16;
 
  ptBuffer = GetDisplayBuffer();
  FlushDisplayRegion(&region, ptBuffer);//将这块区域刷到这个硬件中
  
  return 0; 
}
 
 

五、上板测试:

1.ubuntu上进行make编译:

2.开发板进行配置

对于 IMX6ULL,首先需要关闭默认的 qt gui 程序,才可以执行 ts_test_mt 测试命令,关闭 qt 命令如下所示:

[root@100ask:/etc/init.d]# mv S99myirhmi2 /root
[root@100ask:/etc/init.d]# reboot

如果遇到移除开发板GUI程序后,仍显示Poky界面。

解决方法

在imx6ull系统 /etc/init.d 目录下,搜索psplash 关键字,看在那个文件有用到,有用到就把它注释掉。

可以看到:

在/etc/init.d/S50sshd的12和15行有使用,在该行前面加#号注释。

在/etc/init.d/rcS的7行有使用,在该行前面加#号注释。

然后重启开发板

poky界面消失。

挂载网络文件系统

[root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

执行./test最终效果:在开发板上打印出字符A,到此,显示管理系统代码完成


目录
相关文章
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
17 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
15 2
|
3天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
18 3
|
6天前
|
安全 Linux 数据安全/隐私保护
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。
在 Linux 系统中,查找文件所有者是系统管理和安全审计的重要技能。本文介绍了使用 `ls -l` 和 `stat` 命令查找文件所有者的基本方法,以及通过文件路径、通配符和结合其他命令的高级技巧。还提供了实际案例分析和注意事项,帮助读者更好地掌握这一操作。
23 6
|
6天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
30 6
|
11天前
|
缓存 监控 Linux
|
14天前
|
Linux Shell 数据安全/隐私保护
|
15天前
|
域名解析 网络协议 安全