注:本人已购买韦东山老师第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。
这一节主要讲述如何使用标准输入和LCD触摸屏操作电子书,实现电子书的上下翻页。多种方法支持多输入控制电子书翻页的程序框架如下图所示:
如上图所示:
- ① 首先定义一个
T_InputOpr
结构体,结构体代码如下:typedef struct InputOpr { char *name; /* 名字 */ int (*DeviceInit)(void); /* 设备初始化 */ int (*DeviceExit)(void); /* 设备退出 */ int (*GetInputEvent)(PT_InputEvent ptInputEvent); /* 获取输入事件 */ struct InputOpr *ptNext; /* 用于管理T_InputOpr的结构体链表指针 */ }T_InputOpr, *PT_InputOpr;
- ② 定义输入事件,相关代码如下:
/* 输入类型 */ #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_UNKNOW -1 /* 无法识别 */ /* 定义InputEvent 输入事件结构体 */ typedef struct InputEvent{ struct timeval tTime; /* 时间 */ int iType; /* stdin, touchscreen */ int iVal; /* 输入事件值 */ }T_InputEvent,*PT_InputEvent;
- ③ 在标准输入stdin.c 中,输入“n”控制电子书下翻,输入“u”控制电子输入上翻,输入“q”控制电子输入下翻;在LCD触摸屏touchscreen.c中,把LCD触摸屏分成上、中、下三部分,点击屏幕上半部控制电子输入上翻,点击屏幕下半部控制电子输入下翻,点击屏幕中间无反应。
- ④ 在input_manager.c中,通过
T_InputOpr
结构体链表管理输入设备 stdin.c 和 touchscreen.c。
控制电子书的上下翻页,CPU需要知道我们输入的控制事件。那么它怎么知道有上翻下翻的事件呢?方式一:轮询方式(不断查询按键或者触摸屏有没有操作)
使用轮询方式输入:
stdin.c:
(1) 对于标准输入,我们不能使用普通的输入函数,如 scanf,getchar 等,因为这些都是阻塞式输入,每次,只有输入回车换行之后才能把缓冲区中的值输出。我们需要stdin以非阻塞的方式输入,stdin设备初始化代码如下:
#include <input_manager.h>
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <stdio.h>
/* stdin以非阻塞的方式获得数据 */
static int StdinDeviceInit(void)
{
struct termios tTTYState;
/* get the terminal state */
tcgetattr(STDIN_FILENO, &tTTYState);
/* turn off canonical mode */
tTTYState.c_lflag &= ~ICANON;
/* minimum of number input read */
tTTYState.c_cc[VMIN] = 1; /* 有一个数据时就立刻返回 */
/* set the terminal attributes */
tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
return 0;
}
(2) 当程序退出后,我们需要恢复原来的阻塞方式,代码如下:
static int StdinDeviceExit(void)
{
struct termios tTTYState;
/* get the terminal state */
tcgetattr(STDIN_FILENO, &tTTYState);
/* turn on canonical mode */
tTTYState.c_lflag |= ICANON;
/* set the terminal attributes */
tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
return 0;
}
(3) 获取输入事件,代码如下:
static int StdinGetInputEvent(PT_InputEvent ptInputEvent)
{
/* 如果有数据就读取、处理、返回
* 如果没有数据,立刻返回,不等待
*/
struct timeval tTV;
fd_set tFDs;
char c;
/* 等待时间设为0,不等待 */
tTV.tv_sec = 0;
tTV.tv_usec = 0;
FD_ZERO(&tFDs);
FD_SET(STDIN_FILENO, &tFDs);
select(STDIN_FILENO + 1, &tFDs, NULL, NULL, &tTV);
if (FD_ISSET(STDIN_FILENO, &tFDs))
{
/* 处理数据 */
ptInputEvent->iType = INPUT_TYPE_STDIN;
gettimeofday(&ptInputEvent->tTime, NULL);
c = fgetc(stdin);
if ( c == 'u')
{
ptInputEvent->iVal = INPUT_VALUE_UP;
}
else if (c == 'n')
{
ptInputEvent->iVal = INPUT_VALUE_DOWN;
}
else if (c == 'q')
{
ptInputEvent->iVal = INPUT_VALUE_EXIT;
}
else
{
ptInputEvent->iVal = INPUT_VALUE_UNKNOW;
}
return 0;
}
else
{
return -1;
}
}
(4) 注册T_InputOpr
结构体:
static T_InputOpr g_tStdinInputOpr = {
.name = "stdin",
.DeviceInit = StdinDeviceInit,
.DeviceExit = StdinDeviceExit,
.GetInputEvent = StdinGetInputEvent,
};
int StdinInit(void)
{
return RegisterInputOpr(&g_tStdinInputOpr);
}
touchscreen.c:
(1) 对于应用程序的触摸屏设备的初始化,可以参考 tslib 里的 ts_print.c 文件,touchscreen的初始化代码如下:
/* 参考tslib里的ts_print.c
* 注意:由于要用到LCD的分辨率,所有TouchScreenDeviceInit函数要在
* SelectAndInitDisplay函数之后调用
*/
static int TouchScreenDeviceInit(void)
{
/* 其中#if里的代码是 tslib-1.4 触摸屏的初始化代码,#else里的是tslib-1.21触摸屏的初始化代码 */
#if 0
char *pcTSName = NULL;
/* 根据环境变量获取输入设备 */
if ((pcTSName = getenv("TSLIB_TSDEVICE")) != NULL)
{
/* 以非阻塞方式打开输入设备, 其中参数1表示以非阻塞方式打开*/
g_ptTSDev = ts_open(pcTSName, 1);
}
else /* 如果环境变量没有设置输入设备 */
{
g_ptTSDev = ts_open("/dev/input/event0", 1);
}
if (!g_ptTSDev)
{
DBG_PRINTF("ts_open error!\n");
return -1;
}
if (ts_config(g_ptTSDev)) {
DBG_PRINTF("ts_config error\n");
return -1;
}
#else
/* 以非阻塞方式初始化输入设备, 其中参数1表示以非阻塞方式 */
g_ptTSDev = ts_setup(NULL, 1);
#endif
if (GetDispResolution(&g_iXres, &g_iYres))
{
return -1;
}
return 0;
}
/* 其中GetDispResolution在draw.c定义,用于获取LCD屏幕的分辨率 */
int GetDispResolution(int *piXres, int *piYres)
{
if (g_ptDispOpr)
{
*piXres = g_ptDispOpr->iXres;
*piYres = g_ptDispOpr->iYres;
return 0;
}
else
{
return -1;
}
}
(2) 退出函数,可以不做任何事情,只做填充T_InputOpr
结构体的作用,代码如下:
static int TouchScreenDeviceExit(void)
{
return 0;
}
(3) 获取输入事件:在采样触摸屏的点的时候,手指一次触摸,会有多个点,我们必须有一个时间控制。这里,使用的是使用系统内部的时钟,实现 500ms 的计时,即当手指按下时间大于 500ms 时,我们才采样;否则,不予理会。代码如下:
static int isOutOf500ms(struct timeval *ptPreTime, struct timeval *ptNowTime)
{
unsigned int iPreMs;
unsigned int iNowMs;
iPreMs = ptPreTime->tv_sec * 1000 + ptPreTime->tv_usec / 1000;
iNowMs = ptNowTime->tv_sec * 1000 + ptNowTime->tv_usec / 1000;
return (iNowMs > iPreMs + 500);
}
static int TouchScreenGetInputEvent(PT_InputEvent ptInputEvent)
{
struct ts_sample tSamp;
int iRet;
static struct timeval tPreTime; /* 上一次的时间值 */
/* 读取数据 */
iRet = ts_read(g_ptTSDev, &tSamp, 1);
if (iRet < 0) {
DBG_PRINTF("ts_read error!\n");
return -1;
}
/* 处理数据 */
if (isOutOf500ms(&tPreTime, &tSamp.tv))
{
/* 如果此处触摸事件的发生时间, 距上次事件超过了500ms */
tPreTime = tSamp.tv;
ptInputEvent->tTime = tSamp.tv;
ptInputEvent->iType = INPUT_TYPE_TOUCHSCREEN;
if (tSamp.y < g_iYres / 3)
{
/* 上翻 */
ptInputEvent->iVal = INPUT_VALUE_UP;
}
else if (tSamp.y > g_iYres * 2 / 3)
{
/* 下翻 */
ptInputEvent->iVal = INPUT_VALUE_DOWN;
}
else
{
/* 无反应 */
ptInputEvent->iVal = INPUT_VALUE_UNKNOW;
}
return 0;
}
else
{
return -1;
}
}
(4) 注册T_InputOpr
结构体:
static T_InputOpr g_tTouchScreenInputOpr = {
.name = "touchscreen",
.DeviceInit = TouchScreenDeviceInit,
.DeviceExit = TouchScreenDeviceExit,
.GetInputEvent = TouchScreenGetInputEvent,
};
int TouchScreenInit(void)
{
return RegisterInputOpr(&g_tTouchScreenInputOpr);
}
input_manager.c 的代码如下:
#include <input_manager.h>
#include <config.h>
#include <string.h>
#include <stdio.h>
static PT_InputOpr g_ptInputOprHead = NULL;
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
PT_InputOpr ptCur;
if (!g_ptInputOprHead)
{
g_ptInputOprHead = ptInputOpr;
ptInputOpr->ptNext = NULL;
}
else
{
ptCur = g_ptInputOprHead;
while (ptCur->ptNext)
{
ptCur = ptCur->ptNext;
}
ptCur->ptNext = ptInputOpr;
ptInputOpr->ptNext = NULL;
}
return 0;
}
void ShowInputOpr(void)
{
PT_InputOpr ptCur;
int i = 0;
if (!g_ptInputOprHead)
{
printf("don't have InputOpr\n");
return;
}
else
{
ptCur = g_ptInputOprHead;
do{
printf("%02d %s\n", i++, ptCur->name);
ptCur = ptCur->ptNext;
}while(ptCur);
}
}
PT_InputOpr GetInputOpr(char *pcName)
{
PT_InputOpr ptCur;
if (!g_ptInputOprHead)
{
return NULL;
}
else
{
ptCur = g_ptInputOprHead;
do
{
if (strcmp(ptCur->name, pcName) == 0)
return ptCur;
else
ptCur = ptCur->ptNext;
}
while (ptCur);
}
return NULL;
}
int AllInputDevicesInit(void)
{
PT_InputOpr ptCur;
int iError = -1;
if (!g_ptInputOprHead)
{
DBG_PRINTF("don't have InputOpr\n");
return -1;
}
else
{
ptCur = g_ptInputOprHead;
do{
if (0 == ptCur->DeviceInit())
{
iError = 0;
}
ptCur = ptCur->ptNext;
}while(ptCur);
}
return iError;
}
int GetInputEvent(PT_InputEvent ptInputEvent)
{
/* 轮询方式 */
/* 把链表中的InputOpr的GetInputEvent都调用一次,一旦有数据就返回*/
PT_InputOpr ptCur;
if (!g_ptInputOprHead)
{
DBG_PRINTF("don't have InputOpr\n");
return -1;
}
else
{
ptCur = g_ptInputOprHead;
do{
if (0 == ptCur->GetInputEvent(ptInputEvent))
{
return 0;
}
ptCur = ptCur->ptNext;
}while(ptCur);
}
return -1;
}
int InputInit(void)
{
int iError;
iError = StdinInit();
iError |= TouchScreenInit();
return iError;
}
main.c 的代码如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <config.h>
#include <draw.h>
#include <encoding_manager.h>
#include <fonts_manager.h>
#include <disp_manager.h>
#include <input_manager.h>
#include <string.h>
/* ./show_file [-s Size] [-d display] [-f font_file] [-h HZK] <text_file> */
int main(int argc, char **argv)
{
int iError;
unsigned int dwFontSize = 16;
char acHzkFile[128];
char acFreetypeFile[128];
char acTextFile[128];
char acDisplay[128];
int bList = 0;
T_InputEvent tInputEvent;
acHzkFile[0] = '\0';
acFreetypeFile[0] = '\0';
acTextFile[0] = '\0';
strcpy(acDisplay, "fb");
while ((iError = getopt(argc, argv, "ls:f:h:d:")) != -1)
{
switch(iError)
{
case 'l':
{
bList = 1;
break;
}
case 's':
{
dwFontSize = strtoul(optarg, NULL, 0);
break;
}
case 'f':
{
strncpy(acFreetypeFile, optarg, 128);
acFreetypeFile[127] = '\0';
break;
}
case 'h':
{
strncpy(acHzkFile, optarg, 128);
acHzkFile[127] = '\0';
break;
}
case 'd':
{
strncpy(acDisplay, optarg, 128);
acDisplay[127] = '\0';
break;
}
default:
{
printf("Usage: %s [-s Size] [-d display] [-f font_file] [-h HZK] <text_file>\n", argv[0]);
printf("Usage: %s -l\n", argv[0]);
return -1;
break;
}
}
}
if (!bList && optind >= argc)
{
printf("Usage: %s [-s Size] [-d display] [-f font_file] [-h HZK] <text_file>\n", argv[0]);
printf("Usage: %s -l\n", argv[0]);
return -1;
}
iError = DisplayInit();
if (iError)
{
DBG_PRINTF("DisplayInit error!\n");
return -1;
}
iError = FontsInit();
if (iError)
{
DBG_PRINTF("FontsInit error!\n");
return -1;
}
iError = EncodingInit();
if (iError)
{
DBG_PRINTF("EncodingInit error!\n");
return -1;
}
iError = InputInit();
if (iError)
{
DBG_PRINTF("InputInit error!\n");
return -1;
}
if (bList)
{
printf("supported display:\n");
ShowDispOpr();
printf("supported font:\n");
ShowFontOpr();
printf("supported encoding:\n");
ShowEncodingOpr();
printf("supported input:\n");
ShowInputOpr();
return 0;
}
strncpy(acTextFile, argv[optind], 128);
acTextFile[127] = '\0';
iError = OpenTextFile(acTextFile);
if (iError)
{
DBG_PRINTF("OpenTextFile error!\n");
return -1;
}
iError = SetTextDetail(acHzkFile, acFreetypeFile, dwFontSize);
if (iError)
{
DBG_PRINTF("SetTextDetail error!\n");
return -1;
}
iError = SelectAndInitDisplay(acDisplay);
if (iError)
{
DBG_PRINTF("SelectAndInitDisplay error!\n");
return -1;
}
iError = AllInputDevicesInit();
if (iError)
{
DBG_PRINTF("AllInputDevicesInit error!\n");
return -1;
}
iError = ShowNextPage();
if (iError)
{
DBG_PRINTF("Error to show first page\n");
return -1;
}
printf("Enter 'n' to show next page, 'u' to show previous page, 'q' to exit:\n");
while (1)
{
//printf("Enter 'n' to show next page, 'u' to show previous page, 'q' to exit: ");
if (0 == GetInputEvent(&tInputEvent))
{
if (tInputEvent.iVal == INPUT_VALUE_DOWN)
{
ShowNextPage();
}
else if (tInputEvent.iVal == INPUT_VALUE_UP)
{
ShowPrePage();
}
else if (tInputEvent.iVal == INPUT_VALUE_EXIT)
{
return 0;
}
}
}
return 0;
}
测试:
-
① 安装触摸屏驱动;
-
② 在开发板启动telnet服务,为了登录进去观察CPU占用率:
telnetd -l /bin/sh
,然后使用 MobaXterm 通过 telnet 登录开发板,登录后如下图所示:
-
③ 运行电子书程序:
./show_file -s 24 -d fb -f MSYH.TTF -h HZK16 utf16le.txt
-
④ telnet 上开发板后,在 MobaXterm 输入 top 命令观察 CPU 占用率,如下图所示:
从上图可知,show_file 程序的CPU占用率为 99%,可见该程序的CPU占用率是非常高的。所以,轮询方式存在明显的缺陷:占用较大的CPU资源。