StudyNotes

1 量产工具项目

1.1 项目涉及到的知识

① Framebuffer应用编程

② 文字显示及图像显示

③ 输入系统应用编程

④ 网络通信

⑤ 多线程编程

1.2 程序框架

image-20220214100558756

2 显示部分

2.1 数据结构抽象

2.1.1 显示部分分析

显示部分有多种形式显示设备,在程序框架中可以看出可以有LCD显示,也可以有网络传输之后Web显示。

如何构造一个通用的数据结构,使得不管对于LCD显示还是Web显示,都可以很方便的修改移植呢?

image-20220214110526854

2.1.2 显示部分抽象结构体

image-20220214102134396

2.1.3 编程

C:\Users\Administrator\Desktop\LYH\myLinux\量产工具项目\myCode\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();  // 初始化函数
    int DeviceExit();  // 退出函数,做一些清理工作
	char *GetBuffer(int *pXres, int *pYres, int *pBpp);  // 获得一个Buffer的数据
	int FlushRegion(PRegion ptRegion, char *buffer);  // 把buffer数据刷新到ptRegion区域
	struct DispOpr *ptNext;  // 如果有多个显示设备,可以用链表把它们串起来
}DispOpr, *PDispOpr;

#endif /* _DISP_MANAGER_H */

2.2 Framebuffer编程

2.2.1 fb应用编程思路

2.2.2 编程

C:\Users\Administrator\Desktop\LYH\myLinux\量产工具项目\myCode\framebuffer.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <string.h>

#include "disp_manager.h"

static int fd_fb;  // lcd节点句柄
static struct fb_var_screeninfo var;  // 可变参数
static unsigned int line_width;       // 显示一行需要多少字节
static unsigned int pixel_width;      // 显示一个像素需要多少字节
static int screen_size;
static unsigned char *fb_base;


static int FbDeviceInit(void)
{
	// 1.打开framebuffer设备
	fd_fb = open("/dev/fb0", O_RDWR);
	if(fd_fb<0)
	{
		printf("can't open /dev/fb0 \n");
		return -1;
	}
	// 2.测试ioctl能否获取可变信息,信息存储在 var变量中
	if(ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
	{
		printf("can't get var \n");
		return -1;
	}

	// 3.从可变参数总提取重要信息
	// 3.1 x方向1行需要多少个字节
	line_width = var.xres * var.bits_per_pixel / 8;
	// 3.2 1个像素需要多少个字节
	pixel_width = var.bits_per_pixel / 8;
	// 3.3 1幅屏幕需要多少个字节
	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
	
	// 4.framebuffer基地址
	/* 作用:将内存空间映射到用户空间,用户空间直接访问映射的空间就是访问实际的内核态的内存空间
	 * addr:都写为NULL
	 * length:映射多少字节
	 * prot:可读可写属性 PROT_EXEC  PROT_READ  PROT_WRITE  PROT_NONE
	 * flags:MAP_SHARED写共享模式,两边同步   MAP_PRIVATE写复制模式
	 * fd:打开lcd的设备文件,里面有对应的 mmap函数
	 * offset:偏移多少
	 * 返回虚拟基地址:fb_base
	 */
	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;
	}

	// 5.虚拟地址映射之后,直接往该framebuffer中填充像素数据就可以显示出来了
	// 5.1 清屏:0xff是黑色,0x00是白色
	memset(fb_base, 0xff, screen_size);

	// 前面都成功的话,返回 0
	return 0;
	
}


static int FbDeviceExit(void)
{
	// 取消mmap地址映射
	munmap(fb_base, screen_size);

	// 关闭framebuffer文件句柄
	close(fd_fb);
	
	return 0;	
}


/* 可以返回一个LCD的framebuffer,以后上层APP可以直接操作LCD,可以不用FbFlushRegion,可以写为空
 * 也可以malloc返回一块无关的buffer,要使用FbFlushRegion
 */
static char *FbGetBuffer(int *pXres, int *pYres, int *pBpp)
{
	*pXres = var.xres;
	*pYres = var.yres;
	*pBpp  = var.bits_per_pixel;
	
	return fb_base;
}


static int FbFlushRegion(PRegion ptRegion, char *buffer)
{
	return 0;
}


// 实现一个使用LCD显示的结构体,需要实现里面的函数
// 之后综合程序只需要调用这个 g_tFramebufferOpr 里面的函数
// 就可以实现用 LCD显示了,相当于再封装了一层
static DispOpr g_tFramebufferOpr = {
	.name        = "fb",
	.DeviceInit  = FbDeviceInit,
	.DeviceExit  = FbDeviceExit,
	.GetBuffer   = FbGetBuffer,
	.FlushRegion = FbFlushRegion,	
};

2.3 显示管理

image-20220214110843327

2.4 单元测试

2.4.1 通用Makefile

gedit ~/.bashrc 修改交叉编译工具链路径

source ~/.bashrc 使能环境变量

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/myDoc/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin

2.4.2 顶层目录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

# 目标文件,最终在顶层目录中生成可执行文件 test
TARGET := test

# 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
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)
	

2.4.3 子目录Makefile

需要添加下面的语句

EXTRA_CFLAGS  := 
CFLAGS_file.o := 

obj-y += disp_manager.o
obj-y += framebuffer.o

2.4.4 测试过程

echo -e "\033[9;0]" > /dev/tty1
echo -e "\033[?25l"  > /dev/tty1

2.5 小结

在显示设备中添加了一层,显示管理层,承上启下的作用,编写应用程序的时候,就不用苦哈哈的去打开设备节点,去mmap之后再进行逻辑操作了。应用程序就会比较清晰。

3 输入系统

3.1 数据结构抽象

在应用程序和各种输入设备之间,需要添加一层输入管理层,可以用来给应用程序提供统一的接口函数,同时可以管理各种不同的输入设备。

image-20220214145548958

image-20220214150343920

#ifndef _INPUT_MANAGER_H
#define _INPUT_MANAGER_H

#define INPUT_TYPE_TOUCH 1
#define INPUT_TYOE_NET   2


typedef struct InputEvent{
	struct timeval tTime;  // 加上一个时间
	int iType;
	int iX;
	int iY;
	int iPressure;
	char str[1024];
}InputEvent*PInputEvent;


typedef struct InputDevice{
	char *name;  // 哪一个输入设备
	int (*GetInputEvent)(PInputEvent ptInputEvent);  // 获得输入设备数据函数
	int (*DeviceInit)(void);  // 输入设备初始化函数:打开设备节点
	int (*DeviceExit)(void);  // 输入设备退出函数:关闭设备节点
	struct InputDevice *ptNext;  // 所有输入设备可以链在一起
}InputDevice, InputDevice;

#endif /* _INPUT_MANAGER_H */

3.2 触摸屏编程

3.3 触摸屏单元测试

3.3.1 Makefile需要指定库

image-20220214160452365

#if 1  // 单元测试触摸屏

int main(int argc, char **argv)
{
	InputEvent event;
	int ret;

	g_tTouchscreenDev.DeviceInit();

	while(1)
	{
		// ts_read 函数 没有监测到触摸,就会阻塞
		ret = g_tTouchscreenDev.GetInputEvent(&event);
		if(ret)
		{
			printf("GetInputEvent err!\n");
			return -1;
		}
		else
		{
			printf("Type:      %d \n", event.iType);
			printf("iX:        %d \n", event.iX);
			printf("iY:        %d \n", event.iY);
			printf("iPressure: %d \n", event.iPressure);
		}
	}
}

#endif 

【测试结果】是真的能得到压力值的。

3.4 网络输入编程

image-20220214162709327

3.5 网络输入单元测试

直接移植 UDP1的程序,在开发板上测试就行。

image-20220214172806677

处于同一个局域网的两台电脑进行通信

image-20220214173019114

3.6 输入管理_先写框架

3.6.1 输入系统分析

为什么需要添加一层输入系统呢?

image-20220214174102772

3.7 输入管理_环形缓冲区

image-20220214174619371

image-20220214184909563

image-20220214185226928

3.8 输入管理单元测试

3.8.1 输入框架重要思路

// 基本思路就是:主函数线程中一直读取环形缓冲区的 InputEvent 类型数据
// 没有数据就阻塞等待,有数据就通过 InputEvent 里面的 iType类型来判断是哪个输入设备数据
// 各个输入设备(触摸屏和网络输入),都有自己的线程,会一直获取数据,获取到了就往环形缓冲区里面填

查看是否生成了线程的方法:

image-20220214205845404

【测试结果】

image-20220214211134560

image-20220214211208282

4 文字系统

4.1 数据结构抽象

image-20220215094531421

image-20220215095254629

image-20220215095620424

image-20220215104237338

4.2 实现Freetype代码

4.3 文字管理

image-20220215114343610

image-20220215114728337

4.4 单元测试_编程

关闭LCD屏幕自动息屏功能

echo -e "\033[9;0]" > /dev/tty1
echo -e "\033[?25l"  > /dev/tty1

image-20220215175423507

4.5 单元测试_上机

4.5.1 思考

5 UI系统

5.1 按钮数据结构抽象

image-20220215180917569

5.2 编写按钮

5.2.1 typedef函数指针

可以给函数指针起别名

typedef int (*ONDRAW_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff);
typedef int (*ONPRESSED_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent);


typedef struct Button {
	char *name;
	Region tRegion;
	ONDRAW_FUNC OnDraw;
	ONPRESSED_FUNC OnPressed;
}Button, *Button;

image-20220215204850946

5.3 按钮单元测试

6 页面系统

6.1 数据结构抽象

image-20220216094336411

image-20220216094714883

6.2 编写页面管理器

6.3 单元测试

7 业务系统

7.1 流程及代码框架

image-20220216102755453

image-20220216102909777

7.2 处理配置文件

7.3 生成界面

7.4 处理输入事件

7.5 综合测试

8 改进

8.1 按钮文字

8.2 接口函数名优化

8.3 支持配置文件的command

image-20220216205521983

image-20220216205702581

image-20220216205827833

image-20220216210217244

image-20220216211003304

8.4 触摸按下抬起判断