目录
前言
一、项目介绍和应用
1.简单易用
2.软件可配置、易扩展
3.纯 C 语言编程
4.类似界面应用
二、项目总体框架
三、显示系统
1.显示系统数据结构抽象
(1)common.h
(2)disp_manager.h
2.Framebuffer编程
(1)framebuffer.c
3.显示管理
(1)disp_manager.c
4.测试单元
(1)测试代码
(2)通用Makefile
四、上机测试
前言
该项目是韦东山老师Linux入门基础课程的第一个项目,在这里我用的是IMX6ULL开发板。通过学习这个项目,可以学到良好的编程规范,面向对象的编程思想,对事物的抽象能力,对整个系统的把控能力。
一、项目介绍和应用
电子产品量产测试与烧写工具,这是一套软件,用在我们的实际生产中, 有如下特点:
1.简单易用
把这套软件烧写在 SD 卡上,插到 IMX6ULL 板子里并启动,它就会自动测试各个模块、烧写 EMMC 系统。
工人只要按照说明接入几个模块,就可以完成整个测试、烧写过程。
测试结果一目了然:等 LCD 上所有模块的图标都变绿时,就表示测试通过。
2.软件可配置、易扩展
通过配置文件添加测试项,可以添加不限个数的测试项。
每个测试项有自己的测试程序,测试通过后把结果发送给 GUI 即可。各个测试程序互不影响。
3.纯 C 语言编程
工具设计的界面,它可以一边测试一边烧写:
上图中的 led、speaker 按钮,可以点击:
1.当你看到 LED 闪烁时,就点击 led 按钮,它变成绿色表示测试通过;
2. 当你从耳机里听到声音时,就点击 speaker 按钮,它变成绿色表示测试通过。
- 其他按钮无法点击,接上对应模块后会自动测试,测试通过时图标就会变绿。
- 上图中的蓝色按钮表示烧写 EMMC 的进度,烧写成功后它也会变绿。
- LCD 上所有图标都变绿时,就表示测试、烧写全部完成;某项保持红色的话,就表示对应模块测试失败。
4.类似界面应用
二、项目总体框架
在软件编程当中,可以把一个项目拆分成各个子系统,并且这些子系统跟业务无关,以后还可以用在其他项目上。对于一个子系统,可以抽象出它的对外接口,减少与其他模块的耦合,方便扩展。
接下来先分析他的第一个框架,显示系统。
三、显示系统
1.显示系统数据结构抽象
我们添加的一个显示管理器中有Framebufler和web输出,对于两个不同的设备我们可以抽象出同一个结构体类型。
(1)common.h
该头文件用来包含通用区域结构体
#ifndef _COMMON_H
#define _COMMON_H#ifndef NULL
#define NULL (void *)0#endiftypedef struct Region {int iLeftUpX;int iLeftUpY;int iWidth;int iHeigh;
}Region, *PRegion;#endif
(2)disp_manager.h
#ifndef _DISP_MANAGER_H
#define _DISP_MANAGER_H#include <common.h>typedef struct DispBuff {int iXres;int iYres;int iBpp;char *buff;
}DispBuff, *PDispBuff;typedef struct DispOpr {char *name;int (*DeviceInit)(void);int (*DeviceExit)(void);int (*GetBuffer)(PDispBuff ptPDispBuff);int (*FlushRegion)(PRegion ptRegion, PDispBuff ptPDispBuff);struct DispOpr *ptNext;
}DispOpr, *PDispOpr;int PutPixel(int x, int y, unsigned int dwColor);
void RegisterDisplay(PDispOpr ptPDispOpr);
int SelectDefaultDisplay(char *name);
int InitDefaultDisplay(void);
PDispBuff GetDisplayBuffer(void);
int FlushDisplayRegion(PRegion ptPRegion, PDispBuff ptPDispBuff);
void DisplaySystemRegister(void);#endif
2.Framebuffer编程
Framebuffer编程原理和实操可以看:
(1)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;
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 FbDeviceInit(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 FbDeviceExit(void)
{munmap(fb_base, screen_size);close(fd_fb);return 0;
}/* 可以返回LCD的framebuffer, 以后上层APP可以直接操作LCD, 可以不用FbFlushRegion* 也可以malloc返回一块无关的buffer, 要使用FbFlushRegion*/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 ptPDispBuff)
{return 0;
}static DispOpr g_tFramebufferOpr = {.name = "fb",.DeviceInit = FbDeviceInit,.DeviceExit = FbDeviceExit,.GetBuffer = FbGetBuffer,.FlushRegion = FbFlushRegion,
};void FramebufferRegister(void)
{RegisterDisplay(&g_tFramebufferOpr);
}
3.显示管理
上层函数想要选择哪个设备进行显示,需要中间加一个函数进行选择,起到承上启下的作用,用来实现显示管理,是操作Framebuffer还是WEB设备,需要我们选择某个模块,以便于可以提供一些函数,描点等。
(1)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 ptPDispOpr)
{ptPDispOpr->ptNext = g_DispDevs;g_DispDevs = ptPDispOpr;
}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 0;
}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;
}PDispBuff GetDisplayBuffer(void)
{return &g_tDispBuff;
}int FlushDisplayRegion(PRegion ptPRegion, PDispBuff ptPDispBuff)
{return g_DispDefault->FlushRegion(ptPRegion, ptPDispBuff);
}void DisplaySystemRegister(void)
{extern void FramebufferRegister(void);FramebufferRegister();
}
4.测试单元
display目录存放 framebuffer.c,disp_manager.c 文件
include目录存放头文件
unittest目录存放单元测试.c文件
(1)测试代码
disp_test.c
(2)通用Makefile
通用Makefile的原理和使用可以看:
顶层目录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)nmSTRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdumpexport AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMPCFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/includeLDFLAGS := export CFLAGS LDFLAGSTOPDIR := $(shell pwd)
export TOPDIRTARGET := testobj-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)
顶层目录Makefile.build
PHONY := __build
__build:obj-y :=
subdir-y :=
EXTRA_CFLAGS :=include Makefile# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)# c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))ifneq ($(dep_files),)include $(dep_files)
endifPHONY += $(subdir-y)__build : $(subdir-y) built-in.o$(subdir-y):make -C $@ -f $(TOPDIR)/Makefile.buildbuilt-in.o : $(cur_objs) $(subdir_objs)$(LD) -r -o $@ $^dep_file = .$@.d%.o : %.c$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<.PHONY : $(PHONY)
底层目录display中的Makefile
EXTRA_CFLAGS :=
CFLAGS_file.o := obj-y += disp_manager.o
obj-y += framebuffer.o
底层目录unittest中的Makefile
EXTRA_CFLAGS :=
CFLAGS_file.o := obj-y += disp_test.o
四、上机测试
打开ubuntu,将交叉编译编译成功的文件复制到nfs目录。
上电开发板,挂载 Ubuntu 的 NFS 目录,详细可看:
开发板挂载 Ubuntu 的 NFS 目录_开发板nfs挂载ubuntu-CSDN博客
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
进入NFS目录,编译测试: