第三阶段应用层——1.7 数码相册—电子书(3)—轮询方式支持多输入

数码相册——电子书轮询方式支持多输入

  • 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
  • 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
  • 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》、《gcc中文手册》
  • 开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链
  • 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3


一、前言

【1.7 数码相册—电子书(1)—实现】【1.7 数码相册—电子书(2)—编写通用的Makefile】这两篇博文中,我们实现了在开发板中显示电子书,但是仍然存在一些缺点,这节课针对这些缺点来进行改进。

  • 实现:
    1、电子书支持多种输入方式如标准串口输入实现翻页退出操作,触摸LCD实现翻页操作;
    2、在main.c中采用轮询的方式支持多种输入方式。

二、框架图

1、软件框架图

在这里插入图片描述
对于上述完成主要功能的5个部分:encoding编码部分、fonts获取字体点阵部分、display显示部分、input输入部分、draw协调部分

  1. encoding编码部分去文件中获得编码信息,对于每个文件,保存的时候,系统对自动或手动的根据编码规范,对文件的信息进行编码:如保存为ASCII、GBK、UTF-8、UTF16LE、UTF16BE
  2. fonts获取字体点阵部分根据获得的编码信息得到字体数据(LCD上表示为点阵)
  3. display显示部分把字体数据(点阵)显示在LCD上
  4. input输入部分管理不同的输入方式,定制不同的输入方式所实现的控制,满足多种控制场合;
  5. draw协调部分组织各个模块部分进行合作,实现电子书的显示、翻页功能等。

2、Makefile框架图

在这里插入图片描述
由于整个分为如下3部分:顶层目录的Makefile顶层目录的Makefile.build各级子目录的Makefile

所以对于新添加的input输入部分需要进行如下修改:其目录下也需要添加该目录下的Makefile,对于顶层目录的Makeifle也需要添加obj-y += /input,具体的在编译的时候会介绍。

而对于顶层的Makefile.build不需要进行修改,因为它是一个比较通用的文件根据顶层目录的Makefile信息进入到各个子目录下进行预处理、编译、汇编,最后每个子目录与顶层得到的built-in.o进行链接,最后得到可执行文件show_file.

下面就来介绍添加的input输入部分

三、input输入部分

1、管理者头文件input_manager.h

在这个文件:

  • 定义一个结构体变量拓展文件可以根据自己的情况:分配结构体、设置结构体、注册结构体
  • 定义一些函数,供外部文件调用

#ifndef _INPUT_MANAGER_H
#define _INPUT_MANAGER_H
#include <sys/time.h>

#define INPUT_TYPE_STDIN 			0
#define INPUT_TYPE_TOUCHSCREEN 		1

#define INPUT_VALUE_UP 				0
#define INPUT_VALUE_DOWN 			1
#define INPUT_VALUE_EXIT 			2
#define INPUT_VALUE_UNKONW 		   -1

/* 输入事件信息结构体 */
typedef struct InputEvent {
	struct timeval time;
	int type;
	int val;
}T_InputEvent, *PT_InputEvent;

/* 输入事件结构体 */
typedef struct InputOpr {
	char *name;
	int (*DeviceInit)(void);
	int (*DeviceExit)(void);
	int (*GetInputEvent)(PT_InputEvent ptInputEvent);
	struct InputOpr *ptNext;
}T_InputOpr, *PT_InputOpr;

int RegisterInputOpr(PT_InputOpr ptFontOpr);
int InputInit(void);
void ShowInputOpr(void);
int GetInputEvent(PT_InputEvent ptInputEvent);
int AllInputDeviceInit(void);
int StdinInit(void);
int TouchScreenInit(void);

#endif /* _INPUT_MANAGER_H */

2、管理者实现文件input_manager.c

  • 注册函数
    每个输入方式的结构体,可以调用这个函数把结构体注册到链表中
/* 函数名:	注册函数
 * 函数功能:构建一个链表:把多个拓展文件的结构体“串”起来
 * 函数实现:根据传入的结点,首先判断该链表头是否为空
 *				空则,头结点指向传入的节点,且把节点的ptNext域指向NULL
 *				不空则,尾插法插入链表
 */
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
	PT_InputOpr ptTmp;

	if (!s_ptInputOprHead) {
		s_ptInputOprHead   = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	} else {
		ptTmp = s_ptInputOprHead;
		while (ptTmp->ptNext)
		{
			ptTmp = ptTmp->ptNext;
		}
		ptTmp->ptNext	  = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	}

	return 0;
}
  • 显示函数
    通过这个函数,可以把链表中所有input方式的名字打印出来,供用户查看所支持的input方式。
/* 显示支持拓展文件的名字 */
void ShowInputOpr(void)
{
	int i = 0;
	PT_InputOpr ptTmp = s_ptInputOprHead;

	while (ptTmp) {
		printf("%02d %s\n", i++, ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
}
  • 初始化函数
    调用此函数,可以把对应的input方式结构体放入链表中。
/* 初始化函数 */
int InputInit(void)
{
	int error;
	
	error = StdinInit();
	error |= TouchScreenInit();

	return error;
}
  • 调用input设备事件函数
/* 调用所有的Input设备的事件发生函数,即轮询的方式获取LCD或标准输入的数据 */
int GetInputEvent(PT_InputEvent ptInputEvent)
{
	/* 把链表中的InputOpr的GetInputEvent都调用一次,一旦有数据立即返回 */
	PT_InputOpr ptTmp;

	ptTmp = s_ptInputOprHead;
	
	while (ptTmp) {
		if (ptTmp->GetInputEvent(ptInputEvent) == 0)
			return 0;
		ptTmp = ptTmp->ptNext;
	}

	return -1;
}
  • 设备初始化函数
    初始化每个设备的函数(设置input设备、调整模式等)
/* 初始化所有支持的Input设备 */
int AllInputDeviceInit()
{
	int error;
	PT_InputOpr ptTmp;

	error = -1;
	ptTmp = s_ptInputOprHead;
	
	while (ptTmp) {
		if (ptTmp->DeviceInit() == 0)
			error = 0;
		ptTmp = ptTmp->ptNext;
	}

	return 0;
}

  • 完整文件

#include <config.h>
#include <string.h>
#include <stdlib.h>

#include "input_manager.h"

static PT_InputOpr s_ptInputOprHead;	//链表头

/* 函数名:  注册函数
 * 函数功能:构建一个链表:把多个拓展文件的结构体“串”起来
 * 函数实现:根据传入的结点,首先判断该链表头是否为空
 *				空则,头结点指向传入的节点,且把节点的ptNext域指向NULL
 *				不空则,尾插法插入链表
 */
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
	PT_InputOpr ptTmp;

	if (!s_ptInputOprHead) {
		s_ptInputOprHead   = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	} else {
		ptTmp = s_ptInputOprHead;
		while (ptTmp->ptNext)
		{
			ptTmp = ptTmp->ptNext;
		}
		ptTmp->ptNext	  = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	}

	return 0;
}

/* 显示支持拓展文件的名字 */
void ShowInputOpr(void)
{
	int i = 0;
	PT_InputOpr ptTmp = s_ptInputOprHead;

	while (ptTmp) {
		printf("%02d %s\n", i++, ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
}

/* 初始化函数 */
int InputInit(void)
{
	int error;
	
	error = StdinInit();
	error |= TouchScreenInit();

	return error;
}

/* 调用所有的Input设备的事件发生函数,即轮询的方式获取LCD或标准输入的数据 */
int GetInputEvent(PT_InputEvent ptInputEvent)
{
	/* 把链表中的InputOpr的GetInputEvent都调用一次,一旦有数据立即返回 */
	PT_InputOpr ptTmp;

	ptTmp = s_ptInputOprHead;
	
	while (ptTmp) {
		if (ptTmp->GetInputEvent(ptInputEvent) == 0)
			return 0;
		ptTmp = ptTmp->ptNext;
	}

	return -1;
}

/* 初始化所有支持的Input设备 */
int AllInputDeviceInit()
{
	int error;
	PT_InputOpr ptTmp;

	error = -1;
	ptTmp = s_ptInputOprHead;
	
	while (ptTmp) {
		if (ptTmp->DeviceInit() == 0)
			error = 0;
		ptTmp = ptTmp->ptNext;
	}

	return 0;
}

3、input设备——stdin

在这个函数中,为stdin设备进行:分配结构体、设置结构体、注册结构体

/**
 * @file  stdin.c
 * @brief 标准输入input设备的处初始化,处理过程与取消
 * @version 1.0 (版本声明)
 * @author Dk
 * @date  July 1,2020
 */
#include <termios.h>
#include <unistd.h>
#include <stdio.h>

#include "input_manager.h"

/**
 * @Description: 获取标准串口输入的初始化,设置模式使其可以采用非阻塞的方式获取输入
 * @return 成功:0
 */
static int StdinDevInit(void)
{
	struct termios ttystate;
	 
	/* 获得终端的状态 */
	tcgetattr(STDIN_FILENO, &ttystate);

	/* 关闭标准模式,并设置位为  1,表示接受最小用户数输入为1 */
	ttystate.c_lflag &= ~ICANON;	
	ttystate.c_cc[VMIN] = 1;		
		
	/* 设置术语状态 */
	tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);

	return 0;
}

/**
 * @Description: 获取标准串口输入的退出,恢复原本的模式
 * @return 成功:0
 */
static int StdinDevExit(void)
{
	struct termios ttystate;
	 
	/* 获得终端的状态 */
	tcgetattr(STDIN_FILENO, &ttystate);
 
	/* 打开标准模式 */
	ttystate.c_lflag |= ICANON;

	/* 设置术语状态 */
	tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);

	return 0;
}

/**
 * @Description: 根据标准输入的结果进行相应处理,采用非阻塞的方式获取输入
 * @param ptInputEvent - 表示input设备的结构体.
 * @return 描述符fd在描述符集fds中:0 不在:-1
 */
static int StdinGetInputEvent(PT_InputEvent ptInputEvent)
{
	struct timeval tv;
	fd_set fds;
	char c;

	/* 输入(stdin)执行无阻塞检查,而不会将超时0 */
	tv.tv_sec = 0;
	tv.tv_usec = 0;

	/* 指定的文件描述符集清空 */
	FD_ZERO(&fds);

	/* 在文件描述符集合中增加一个新的fds */
	FD_SET(STDIN_FILENO, &fds);

	/* 测试指定的fds可读 */
	select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);

	/* 测试指定的fds是否在该集合中 */
	if (FD_ISSET(STDIN_FILENO, &fds)) {
		/* 存在,处理数据 */
		ptInputEvent->type = INPUT_TYPE_STDIN;
		c = fgetc(stdin);

		if (c == 'u') {
			ptInputEvent->val = INPUT_VALUE_UP;
			gettimeofday(&ptInputEvent->time, NULL);
		} else if (c == 'n') {
			ptInputEvent->val = INPUT_VALUE_DOWN;		
			gettimeofday(&ptInputEvent->time, NULL);
		} else if (c == 'q') {
			ptInputEvent->val = INPUT_VALUE_EXIT;
			gettimeofday(&ptInputEvent->time, NULL);
		} else {
			ptInputEvent->val = INPUT_VALUE_UNKONW;
			gettimeofday(&ptInputEvent->time, NULL);
		}
		return 0;
	}else
		return -1;	
}

static T_InputOpr s_tStdinOpr = {
	.name 			= "stdin",
	.DeviceInit 	= StdinDevInit,
	.DeviceExit 	= StdinDevExit,
	.GetInputEvent  = StdinGetInputEvent,
};

/**
 * @Description: 标准输入初始化函数,供上层调用注册设备
 * @return 成功:0
 */
int StdinInit(void)
{
	return RegisterInputOpr(&s_tStdinOpr);
}

4、input设备——touchscreen

在这个函数中,为touchscreen设备进行:分配结构体、设置结构体、注册结构体

/**
 * @file  touchscreen.c
 * @brief touchscreen input设备的处初始化,处理过程与取消,参考tslib中的ts_print.c
 * @version 1.0 (版本声明)
 * @author Dk
 * @date  July 1,2020
 */
#include <stdlib.h>
#include <tslib.h>

#include "input_manager.h"
#include "config.h"
#include "draw.h"

static struct tsdev *s_pTSDev;	//touchscreen设备
static int s_Xres;	//LCD x方向的分辨率
static int s_Xres;	//LCD y方向的分辨率

/**
 * @Description: 获取touchscreen输入的初始化,设置模式使其可以采用非阻塞的方式获取输入
 * @return 成功:0 失败: -1
 * @note 由于需要获取到LCD的分辨率,所以这个函数被调用之前,SelectAndInitDisplay必须被调用,才可以获取到数据
 */
static int TouchScreenDevInit(void)
{
	char *pTSName = NULL;	

	/* 根据环境变量获得设备名,并以非阻塞的方式打开 */
	if((pTSName = getenv("TSLIB_TSDEVICE")) != NULL) 
		s_pTSDev = ts_open(pTSName, 1);
	else
		s_pTSDev = ts_open("/dev/event0", 1);

	if (!s_pTSDev) {
		DBG_PRINTF("ts_open error!\n");
		return -1;
	}

	if (ts_config(s_pTSDev)) {
		DBG_PRINTF("ts_config error!\n");
		return -1;
	}

	/* 获取LCD分辨率 */
	if (GetDispResolution(&s_Xres, &s_Xres))
		return -1;

	return 0;
}

/**
 * @Description: touchscreen输入的退出,恢复原本的模式
 * @return 成功:0
 */
static int TouchScreenDevExit(void)
{
	return 0;
}

/**
 * @Description: 判断上一次事件与此次事件相隔的时间是否超过500ms
 * @param ppreTime - 存储上一次事件时间的结构体指针,pcurTime - 此次存储事件时间的结构体指针 
 * @return 两次事件时间间隔超过500ms:0 无超过:1
 */
static int isOutOf500ms(struct timeval *ppreTime, struct timeval *pcurTime)
{
	int prems;
	int curms;

	/* tv_sec - 秒, tv_usec - 微秒 */
	prems = ppreTime->tv_sec * 1000 + ppreTime->tv_usec / 1000;
	curms = pcurTime->tv_sec * 1000 + pcurTime->tv_usec / 1000;

	return (curms > (prems + 500));
}

/**
 * @Description: 采用查询的方式获读取touchscreen数据
 * @param ptInputEvent - 表示input设备的结构体.
 * @return 有数据:0 无数据:-1
 */
static int TouchScreenGetInputEvent(PT_InputEvent ptInputEvent)
{
	int ret;
	struct ts_sample samp;
	static struct timeval preTime;
	
	ret = ts_read(s_pTSDev, &samp, 1);

	/* 无数据返回 */
	if (ret < 0) {
		DBG_PRINTF("ts_read no data!\n");
		return -1;
	}

	/* 有数据处理 */
	if ((isOutOf500ms(&preTime, &samp.tv))) {
		/* 两次事件时间间隔超过500ms */
		ptInputEvent->type = INPUT_TYPE_TOUCHSCREEN;
		preTime = samp.tv;
		ptInputEvent->time = samp.tv;

		/* 根据读取到的LCD y坐标值,进行对应操作
		 * y < y分辨率的1/3,则为上翻
		 * y > y分辨率的2/3,则为下翻
 		 * 其他情况,则为未定义操作
		 */
		if (samp.y < (s_Xres / 3))
			ptInputEvent->val = INPUT_VALUE_UP;
		else if (samp.y > (2 * s_Xres / 3))
			ptInputEvent->val = INPUT_VALUE_DOWN;
		else
			ptInputEvent->val = INPUT_VALUE_UNKONW;
		return 0;
	} else
		return -1;

	return 0;
}

static T_InputOpr s_tTouchScreenOpr = {
	.name 			= "touchscreen",
	.DeviceInit 	= TouchScreenDevInit,
	.DeviceExit 	= TouchScreenDevExit,
	.GetInputEvent  = TouchScreenGetInputEvent,
};

/**
 * @Description: touchscreen input设备初始化函数,供上层调用注册设备
 * @return 成功:0
 */
int TouchScreenInit(void)
{
	return RegisterInputOpr(&s_tTouchScreenOpr);
}

四、修改

1、main.c文件修改

1.1 显示支持信息部分

  • 修改前:

```c
	/* 显示当前支持信息 */
	if (List)
	{
		printf("supported display:\n");
		ShowDispOpr();

		printf("supported font:\n");
		ShowFontOpr();

		printf("supported encoding:\n");
		ShowEncodingOpr();
		
		return 0;
	}
  • 修改后:
	/* 显示当前支持信息 */
	if (List)
	{
		printf("supported display:\n");
		ShowDispOpr();

		printf("supported font:\n");
		ShowFontOpr();

		printf("supported encoding:\n");
		ShowEncodingOpr();

		printf("supported input:\n");
		ShowInputOpr();
		
		return 0;
	}

1.2 循环部分

  • 修改前:只支持串口标准输入控制
/* 循环根据输入信息进行对应操作
     * n:  显示下一页信息
     * u:  显示上一页信息
     * q:  退出程序
	 */
	while (1)
	{
		printf("Enter 'n' to show next page, 'u' to show previous page, 'q' to exit: ");

		do {
			Opr = getchar();			
		} while ((Opr != 'n') && (Opr != 'u') && (Opr != 'q'));

		if (Opr == 'n')
			ShowNextPage();
		else if (Opr == 'u')
			ShowPrePage();			
		else 
			return 0;		
	}
  • 修改后:通过轮询的方式,调用GetInputEvent(&InputEvent)不断的判断串口和LCD是否有输入根据输入的标志位来进行相关操作
	/* 循环根据输入信息进行对应操作
     * n:  显示下一页信息
     * u:  显示上一页信息
     * q:  退出程序
	 */
	printf("Enter 'n' to show next page, 'u' to show previous page, 'q' to exit\n\r");
	printf("Touch the upper third of the touch screen to show previous page\n\r");
	printf("Touch the bottom third of the touch screen to show next page\n\r");
	
	while (1)
	{
		if (GetInputEvent(&InputEvent) == 0) {
			if (InputEvent.val == INPUT_VALUE_DOWN)
				ShowNextPage();
			else if (InputEvent.val == INPUT_VALUE_UP)
				ShowPrePage();			
			else if (InputEvent.val == INPUT_VALUE_EXIT) {
				printf("\n\r");
				return 0;
			}
		}
	}

2、draw.c文件修改

2.1 初始化部分修改

  • 修改前:
int DrawInit(void)
{
	int error;
	
	/* 初始化 */
	error = DisplayInit();
	if (error) {
		printf("DisplayInit error!\n");
		return -1;
	}

	error = FontsInit();
	if (error) {
		printf("FontsInit error!\n");
		return -1;
	}

	error = EncodingInit();
	if (error) {
		printf("EncodingInit error!\n");
		return -1;
	}

	return 0;
}
  • 修改后:添加了input部分的初始化
int DrawInit(void)
{
	int error;
	
	/* 初始化 */
	error = DisplayInit();
	if (error) {
		printf("DisplayInit error!\n");
		return -1;
	}

	error = FontsInit();
	if (error) {
		printf("FontsInit error!\n");
		return -1;
	}

	error = EncodingInit();
	if (error) {
		printf("EncodingInit error!\n");
		return -1;
	}

	error = InputInit();
	if (error) {
		printf("InputInit error!\n");
		return -1;
	}

	return 0;
}

3、Makefile修改

3.1 /input目录下Makefile

# 子目录Makefile

obj-y += input_manager.o
obj-y += stdin.o
obj-y += touchscreen.o

3.2 顶层目录Makefile

  • 添加库:
LDFLAGS := -lm -lfreetype -lts
  • 添加input目录:
obj-y += input/
  • 完整文件:
# 顶层目录Makefile
#
# 目的:
#	1、每个子目录都会建立一个Makefile,包含当前目录下.c文件.h文件
# 	2、顶层目录下


# 交叉编译工具链
CROSS_COMPILE = arm-linux-

# 定义一些变量 $(CROSS_COMPILE):取出该变量的值 $(CROSS_COMPILE)gcc->arm-linux-gcc
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	方便您利用隐含规则指定编译C语言源程序的旗标
# -Wall 	帮助列出所有警告信息
# -02		多优化一些,除了涉及空间和速度交换的优化选项,执行几乎所有的优化工作
# -g		可以认为它是缺省推荐的选项	
CFLAGS := -Wall -O2 -g

# 把当前目录下的include目录下指定为系统目录
# +=		为已经定以过的变量的值追加更多的文本
# -I		指定搜寻包含makefile文件的路径
# $(shell pwd)  执行shell命令的pwd命令,获取执行命令的结果
CFLAGS += -I $(shell pwd)/include

# LDFLAGS	用于调用linker(‘ld’)的编译器的额外标志
# -l		连接库的搜寻目录 -lm调用math库 -lfreetype调用freetype库
LDFLAGS := -lm -lfreetype -lts

# 取出变量的值
export 	CFLAGS LDFLAGS

TOPDIR := $(shell pwd)
export TOPDIR

# TARGET	目标变量
# := 		简单扩展型变量的值是一次扫描永远使使用
TARGET := show_file

obj-y += main.o
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/
obj-y += input/

# 规则		arm-linux-gcc -lm -lfreetype -o show_file built-in.o
all : 
	make -C ./ -f $(TOPDIR)/Makefile.build
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
	
# clean 	删除所有make正常创建的文件
# 规则		找到所有make创建出来.o文件进行删除
#			删除show_file
clean :
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)
	
# distclean 删除所有正常创建的文件
#			配置文件或为编译正常创建的准备文件,甚至makefile文件自身不能创建的文件
# 规则		找到所有mkae创建出来的.o文件进行删除
#			删除show_file
distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)

五、编译与运行

1、编译

执行make,得到可执行文件show_file
在这里插入图片描述

2、运行

由于使用到触摸屏,需要调用tslib库来进行校准

  • 执行./show_file -l,显示出当前支持的设备
    在这里插入图片描述
  • 执行./shoe_file -s 16 -h HZK16 -f ./MSYH.TTF hz.txt
    通过串口输入,可以立刻根据输入的值进行操作,不需要回车键后才进行操作;
    通过触摸屏输入点击屏幕的上1/3部分进行上页显示点击屏幕的下1/3部分进行下页显示在这里插入图片描述
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42813232/article/details/107063799
今日推荐