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

    技术2022-07-11  98

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

    硬件平台:韦东山嵌入式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、软件框架图2、Makefile框架图 三、input输入部分1、管理者头文件`input_manager.h`2、管理者实现文件`input_manager.c`3、input设备——stdin4、input设备——touchscreen 四、修改1、main.c文件修改1.1 显示支持信息部分1.2 循环部分 2、draw.c文件修改2.1 初始化部分修改 3、Makefile修改3.1 /input目录下Makefile3.2 顶层目录Makefile 五、编译与运行1、编译2、运行


    一、前言

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

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

    二、框架图

    1、软件框架图

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

    encoding编码部分:去文件中获得编码信息,对于每个文件,保存的时候,系统对自动或手动的根据编码规范,对文件的信息进行编码:如保存为ASCII、GBK、UTF-8、UTF16LE、UTF16BE;fonts获取字体点阵部分:根据获得的编码信息得到字体数据(LCD上表示为点阵);display显示部分:把字体数据(点阵)显示在LCD上;input输入部分:管理不同的输入方式,定制不同的输入方式所实现的控制,满足多种控制场合;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("d %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("d %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部分进行下页显示。
    Processed: 0.012, SQL: 12