电子产品量产测试与烧写工具。这是一套软件,用在我们的实际生产中, 有如下特点:
1.简单易用:
把这套软件烧写在 SD 卡上,插到 IMX6ULL 板子里并启动,它就会自动测试各个模块、烧写 EMMC 系统。
工人只要按照说明接入几个模块,就可以完成整个测试、烧写过程。
测试结果一目了然:等 LCD 上所有模块的图标都变绿时,就表示测试通过。
2.软件可配置、易扩展:
通过配置文件添加测试项,可以添加不限个数的测试项。
每个测试项有自己的测试程序,测试通过后把结果发送给 GUI 即可。各个测试程序互不影响。
3. 纯 C 语言编程
工具设计的界面,它可以一边测试一边烧写:
上图中的 led、speaker 按钮,可以点击:
1.当你看到 LED 闪烁时,就点击 led 按钮,它变成绿色表示测试通过;
2. 当你从耳机里听到声音时,就点击 speaker 按钮,它变成绿色表示测试通过。
其他按钮无法点击,接上对应模块后会自动测试,测试通过时图标就会变绿。
上图中的蓝色按钮表示烧写 EMMC 的进度,烧写成功后它也会变绿。
LCD 上所有图标都变绿时,就表示测试、烧写全部完成;某项保持红色的话,就表示对应模块测试失败。
一、总设计思路:
二、显示系统:
*2.1 DisplayInit();
初始化显示系统,在disp_manager.c这个中间管理器中初始化显示器,选择是framebuffer的应用端还是Web的网络端,这里如果还想要加入其他设备都可以在这里调用这个设备的初始化函数,这里我们先拿framebuffer举例。
// 函数实现:初始化显示管理器 void DisplayInit(void) { void FramebufferInit(void); // 假设有一个FramebufferInit函数用于初始化framebuffer FramebufferInit(); // 调用该函数 }
2.2 framebuffer();
这里会在framebuffer.c注册framebuffer操作的结构体到显示管理器的链表中。
// 初始化framebuffer void FramebufferInit(void) { RegisterDisplay(&g_tFramebufferOpr); // 注册framebuffer操作到显示管理器 }
定义一个DispOpr结构体实例,用于描述framebuffer的操作。
static DispOpr g_tFramebufferOpr = { .name = "fb", // 操作名称 .DeviceInit = FbDeviceInit, // 初始化函数指针 .DeviceExit = FbDeviceExit, // 退出函数指针 .GetBuffer = FbGetBuffer, // 获取缓冲区信息函数指针 .FlushRegion = FbFlushRegion, // 刷新区域函数指针 };
2.3 RegisterDisplay();
将上面的显示设备framebuffer操作的结构体注册到disp_manager.c这个链表中
// 函数实现:注册一个显示设备 void RegisterDisplay(PDispOpr ptDispOpr) { ptDispOpr->ptNext = g_DispDevs; // 新设备指针指向当前第一个设备 g_DispDevs = ptDispOpr; // 更新第一个设备指针为新设备 }
&& 2.4 显示管理器框架
*2.5 SelectDefaultDisplay(char *name)
当放到这个链表里后会有很多设备,那么我们选择哪个模块呢?这里我们就需要disp_manager.c中编写一个设备选择函数SelectDefaultDisplay(char *name) 从头遍历链表找设备名字。
// 函数实现:选择一个默认的显示设备 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 -1; // 没有找到匹配的设备 }
这里disp_manager.h定义一个结构体DispOpr,用于描述显示操作的接口。
// 定义一个结构体DispOpr,用于描述显示操作的接口 typedef struct DispOpr { char *name; // 显示操作的名称 int (*DeviceInit)(void); // 指向初始化函数的指针 int (*DeviceExit)(void); // 指向退出函数的指针 int (*GetBuffer)(PDispBuff ptDispBuff); // 指向获取缓冲区信息函数的指针 int (*FlushRegion)(PRegion ptRegion, PDispBuff ptDispBuff); // 指向刷新区域函数的指针 struct DispOpr *ptNext; // 指向下一个DispOpr结构体的指针,用于链表结构 } DispOpr, *PDispOpr; // DispOpr是结构体类型,PDispOpr是指向DispOpr的指针类型
*2.6 InitDefaultDisplay(void)
选择好了显示设备需要在disp_manager.c中编写初始化代码进行设备初始化
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; // 初始化成功 }
在这之前我们需要先在disp_manager.h中定义一个DispBuff,用于存储显示缓冲区的信息,这样会更好的调用显示缓冲信息。
typedef struct DispBuff { int iXres; // x坐标分辨率 int iYres; // y坐标分辨率 int iBpp; // 每像素位数(bits per pixel) char *buff; // 缓冲区地址 } DispBuff, *PDispBuff; // DispBuff是结构体类型,PDispBuff是指向DispBuff的指针类型
2.7 DeviceInit(void);
调用framebuffer.c中的DeviceInit(void)进行初始化设备。
static int DeviceInit(void) { fd_fb = open("/dev/fb0", O_RDWR); // 打开/dev/fb0设备 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); // 内存映射framebuffer if (fb_base == (unsigned char *)-1) { printf("can't mmap\n"); // 如果映射失败,打印错误信息 return -1; // 返回错误代码 } return 0; // 初始化成功,返回0 }
2.8 FbGetBuffer(PDispBuff ptDispBuff)
再调用framebuffer.c中的FbGetBuffer(PDispBuff ptDispBuff)获取设备的framebuffer缓冲区信息。
// 获取framebuffer缓冲区信息 static int FbGetBuffer(PDispBuff ptDispBuff) { ptDispBuff->iXres = var.xres; // 设置X分辨率 ptDispBuff->iYres = var.yres; // 设置Y分辨率 ptDispBuff->iBpp = var.bits_per_pixel; // 设置每像素位数 ptDispBuff->buff = (char *)fb_base; // 设置缓冲区地址 return 0; // 返回0表示成功 }
*2.9 PutPixel(int x, int y, unsigned int dwColor)
想在这块内存上绘制一个像素,再以这个像素作为起点确定图标来绘制图片,绘制像素这个最基本的函数应该在disp_manager.c这里实现,在屏幕上画一个像素点。
// 函数实现:在屏幕上画一个像素点 int PutPixel(int x, int y, unsigned int dwColor) { // 根据屏幕的bpp(每像素位数)计算像素在缓冲区中的位置 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; // 根据bpp选择不同的颜色表示方式 switch (g_tDispBuff.iBpp) { case 8: // 8bpp,直接设置颜色值 { *pen_8 = dwColor; break; } case 16: // 16bpp,通常是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: // 32bpp,直接设置颜色值 { *pen_32 = dwColor; break; } default: // 不支持的bpp { printf("can't support %dbpp\n", g_tDispBuff.iBpp); return -1; break; } } return 0; // 成功画点 }
*2.10 FlushDisplayRegion
我们绘制好图像以后需要刷到硬件上面,我们需要在disp_manager.c中再提供一个FlushDisplayRegion()函数。
// 函数实现:刷新屏幕上的一个区域 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff) { return g_DispDefault->FlushRegion(ptRegion, ptDispBuff); // 调用设备的刷新区域函数 }
2.11 测试代码
int main(int argc, char **argv) { Region region; // 定义刷新区域的大小 PDispBuff ptBuffer; DisplayInit(); // 初始化显示系统 SelectDefaultDisplay("fb"); // 选择默认的显示设备,这里是帧缓冲设备 InitDefaultDisplay(); // 初始化选定的显示设备 lcd_put_ascii(100, 100, 'A'); // 在屏幕的(100, 100)位置显示字符'A' // 设置刷新区域的位置和大小 region.iLeftUpX = 100; region.iLeftUpY = 100; region.iWidth = 8; region.iHeigh = 16; ptBuffer = GetDisplayBuffer(); // 获取显示缓冲区 FlushDisplayRegion(®ion, ptBuffer); // 刷新指定区域的显示内容 return 0; }
lcd_put_ascii(100, 100, 'A');
// 在指定位置显示一个ASCII字符 void lcd_put_ascii(int x, int y, unsigned char c) { unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16]; // 获取字符的字体位图数据 int i, b; unsigned char byte; for (i = 0; i < 16; i++) // 遍历字体的每一行 { byte = dots[i]; for (b = 7; b >= 0; b--) // 遍历每一行的每一位 { if (byte & (1<<b)) // 判断位图中的点是否需要显示 { // 显示白色像素 PutPixel(x+7-b, y+i, 0xffffff); // 白色 } else { // 显示黑色像素 PutPixel(x+7-b, y+i, 0); // 黑色 } } } }
详细介绍:Linux基础项目开发1:量产工具——显示系统(二)_linux入门项目-CSDN博客
三、输入系统:
我们需要抽象出来俩个结果体,一个数据本身,一个设备本身。
*3.1 InputEvent 数据本身结构体
在input_manager.h中抽象出数据本身的结构体:
typedef struct InputEvent { // 时间戳结构体,包含事件发生的时间,精确到微秒 struct timeval tTime; // 事件类型,可能表示不同的输入动作,如点击、滑动、长按等 int iType; // 事件的X坐标,通常表示在屏幕或输入区域的水平位置 int iX; // 事件的Y坐标,通常表示在屏幕或输入区域的垂直位置 int iY; // 事件的压力值,对于触摸屏等设备,可能表示用户触摸的力度 int iPressure; // 一个字符串,用于存储与事件相关的额外信息,长度限制为1024个字符 char str[1024]; } InputEvent, *PInputEvent;
*3.2 InputDevice 输入设备结构体
在input_manager.h中抽象出输入设备的结构体:
typedef struct InputDevice { // 指向设备名称的字符串指针,用于标识输入设备 char *name; // 指向函数的指针,该函数用于获取输入事件。它接受一个指向InputEvent结构体的指针作为参数,并返回一个整数值 int (*GetInputEvent)(PInputEvent ptInputEvent); // 指向函数的指针,该函数用于初始化输入设备。它不接受任何参数,并返回一个整数值 int (*DeviceInit)(void); // 指向函数的指针,该函数用于退出或释放输入设备。它不接受任何参数,并返回一个整数值 int (*DeviceExit)(void); // 指向另一个InputDevice结构体的指针,用于链接多个输入设备,形成链表 struct InputDevice *ptNext; } InputDevice, *PInputDevice;
*3.3 InputDevice结构体实例(Touchscreen)
在touchscreen.c 中定义一个InputDevice结构体实例,表示触摸屏设备
// 定义一个InputDevice结构体实例,表示触摸屏设备 static InputDevice g_tTouchscreenDev ={ .name = "touchscreen", .GetInputEvent = TouchscreenGetInputEvent, .DeviceInit = TouchscreenDeviceInit, .DeviceExit = TouchscreenDeviceExit, };
下面我们需要将触摸屏设备的各个模块函数进行实现。
3.4 TouchscreenGetInputEvent(PInputEvent ptInputEvent)
在touchscreen.c 中定义静态函数,用于从触摸屏设备获取输入事件
// 静态函数,用于从触摸屏设备获取输入事件 static int TouchscreenGetInputEvent(PInputEvent ptInputEvent) { struct ts_sample samp; int ret; // 从触摸屏设备读取一个样本 ret = ts_read(g_ts, &samp, 1); // 如果读取失败,返回-1 if (ret != 1) return -1; // 填充输入事件结构体 ptInputEvent->iType = INPUT_TYPE_TOUCH; ptInputEvent->iX = samp.x; ptInputEvent->iY = samp.y; ptInputEvent->iPressure = samp.pressure; ptInputEvent->tTime = samp.tv; // 读取成功,返回0 return 0; }
3.5 TouchscreenDeviceInit(void)
在touchscreen.c 中定义静态函数,用于初始化触摸屏设备
static int TouchscreenDeviceInit(void) { // 初始化触摸屏设备 g_ts = ts_setup(NULL, 0); if (!g_ts) { // 如果初始化失败,打印错误信息并返回-1 printf("ts_setup err\n"); return -1; } // 初始化成功,返回0 return 0; }
3.6 TouchscreenDeviceExit(void)
在touchscreen.c 中定义静态函数,用于退出触摸屏设备
static int TouchscreenDeviceExit(void) { // 关闭触摸屏设备 ts_close(g_ts); return 0; }
3.9 显示屏的测试函数
#if 1 // 主函数,程序的入口点 int main(int argc, char **argv) { // 定义一个输入事件的变量 InputEvent event; int ret; // 初始化触摸屏设备 g_tTouchscreenDev.DeviceInit(); // 无限循环,持续从触摸屏获取事件 while (1) { // 从触摸屏设备获取一个输入事件 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); } } // 理论上,无限循环不会结束,这行代码不会被执行 return 0; } #endif
*3.10 InputDevice结构体实例(net)
在netiput.c 中定义一个InputDevice结构体实例,表示网络输入设备
/* 定义一个InputDevice结构体实例,表示网络输入设备 */ static InputDevice g_tNetinputDev ={ .name = "netinput", .GetInputEvent = NetinputGetInputEvent, .DeviceInit = NetinputDeviceInit, .DeviceExit = NetinputDeviceExit, };
下面我们需要将网络设备的各个模块函数进行实现。
3.11 NetinputGetInputEvent(PInputEvent ptInputEvent)
在netiput.c 中定义静态函数,用于从网络套接字获取输入事件
/* 静态函数,用于从网络套接字获取输入事件 */ static int NetinputGetInputEvent(PInputEvent ptInputEvent) { struct sockaddr_in tSocketClientAddr; /* 客户端地址结构体 */ struct int iRecvLen; /* 接收到的数据长度,这里应该是int类型 */ char aRecvBuf[1000]; /* 接收缓冲区 */ int iAddrLen = sizeof(struct sockaddr); /* 地址结构体长度 */ /* 从套接字接收数据 */ iRecvLen = recvfrom(g_iSocketServer, aRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen); if (iRecvLen > 0) { aRecvBuf[iRecvLen] = '\0'; /* 在接收缓冲区末尾添加字符串结束符 */ /* 填充输入事件结构体 */ ptInputEvent->iType = INPUT_TYPE_NET; gettimeofday(&ptInputEvent->tTime, NULL); strncpy(ptInputEvent->str, aRecvBuf, 1000); ptInputEvent->str[999] = '\0'; return 0; /* 成功获取事件,返回0 */ } else return -1; /* 获取事件失败,返回-1 */ }
3.12 NetinputDeviceInit(void)
在netiput.c 中定义静态函数,用于初始化网络输入设备
static int NetinputDeviceInit(void) { struct sockaddr_in tSocketServerAddr; /* 服务器地址结构体 */ int iRet; /* 返回值 */ /* 创建一个UDP套接字 */ g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == g_iSocketServer) { printf("socket error!\n"); return -1; /* 创建套接字失败,返回-1 */ } /* 设置服务器地址结构体 */ tSocketServerAddr.sin_family = AF_INET; tSocketServerAddr.sin_port = htons(SERVER_PORT); /* 将端口号转换为网络字节序 */ tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* 监听所有本地地址 */ memset(tSocketServerAddr.sin_zero, 0, 8); /* 将结构体中未使用的部分置零 */ /* 绑定套接字到服务器地址 */ iRet = bind(g_iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (-1 == iRet) { printf("bind error!\n"); return -1; /* 绑定失败,返回-1 */ } return 0; /* 初始化成功,返回0 */ }
3.13 NetinputDeviceExit(void)
在netiput.c 中定义静态函数,用于退出网络输入设备
static int NetinputDeviceExit(void) { close(g_iSocketServer); /* 关闭套接字 */ return 0; /* 退出成功,返回0 */ }
3.14 网络测试函数
#if 1 /* 主函数,程序的入口点 */ int main(int argc, char **argv) { InputEvent event; /* 定义一个输入事件结构体变量 */ int ret; /* 用于存储函数返回值 */ /* 调用网络输入设备的初始化函数 */ g_tNetinputDev.DeviceInit(); /* 进入无限循环,不断获取并处理输入事件 */ while (1) { /* 从网络输入设备获取输入事件 */ ret = g_tNetinputDev.GetInputEvent(&event); if (ret) { /* 如果获取事件失败,打印错误信息并退出程序 */ printf("GetInputEvent err!\n"); return -1; } else { /* 如果获取事件成功,打印事件的类型和内容 */ printf("Type : %d\n", event.iType); printf("str : %s\n", event.str); } } return 0; /* 正常退出程序 */ } #endif
3.15 client.c
这里充当客户端,用来发送数据
#include <sys/types.h> /* 包含系统数据类型头文件 */ #include <sys/socket.h> /* 包含套接字相关函数和结构体头文件 */ #include <string.h> /* 包含字符串处理函数头文件 */ #include <sys/socket.h> /* 重复包含,可以删除 */ #include <netinet/in.h> /* 包含网络地址结构体头文件 */ #include <arpa/inet.h> /* 包含IP地址转换函数头文件 */ #include <unistd.h> /* 包含文件操作和进程控制函数头文件 */ #include <stdio.h> /* 包含标准输入输出函数头文件 */ /* 定义服务器端口号 */ #define SERVER_PORT 8888 /* 主函数,程序的入口点 */ int main(int argc, char **argv) { int iSocketClient; /* 客户端套接字描述符 */ struct sockaddr_in tSocketServerAddr; /* 服务器地址结构体 */ int iRet; /* 用于存储函数返回值 */ int iSendLen; /* 用于存储发送数据的长度 */ int iAddrLen; /* 用于存储地址结构体的长度 */ /* 检查命令行参数数量是否正确 */ if (argc != 3) { printf("Usage:\n"); printf("%s <server_ip> <str>\n", argv[0]); return -1; } /* 创建一个UDP套接字 */ iSocketClient = socket(AF_INET, SOCK_DGRAM, 0); /* 设置服务器地址结构体 */ tSocketServerAddr.sin_family = AF_INET; /* 使用IPv4地址族 */ tSocketServerAddr.sin_port = htons(SERVER_PORT); /* 设置服务器端口号,转换为网络字节序 */ //tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* 注释掉,改为使用命令行参数指定的IP地址 */ /* 将命令行参数指定的IP地址转换为网络字节序,并设置到地址结构体中 */ if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr)) { printf("invalid server_ip\n"); return -1; } /* 将地址结构体的剩余部分填充为0 */ memset(tSocketServerAddr.sin_zero, 0, 8); #if 0 /* 注释掉的部分是尝试进行TCP风格的连接,但由于是UDP套接字,这里实际上不会进行连接操作 */ iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (-1 == iRet) { printf("connect error!\n"); return -1; } #endif /* 计算地址结构体的长度 */ iAddrLen = sizeof(struct sockaddr); /* 向服务器发送数据 */ iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0, (const struct sockaddr *)&tSocketServerAddr, iAddrLen); /* 关闭套接字 */ close(iSocketClient); /* 正常退出程序 */ return 0; }
&& 3.16 输入管理器框架
* 3.17 InputInit(void)
在input_manager.c中向上提供一个InputInit(void) 函数,用于注册触摸屏设备和注册网络设备
/* 初始化输入系统 */ void InputInit(void) { /* 注册触摸屏设备 */ extern void TouchscreenRegister(void); TouchscreenRegister(); /* 注册网络输入设备 */ extern void NetInputRegister(void); NetInputRegister(); }
3.18 TouchscreenRegister(void)
在touchscreen.c 中注册TouchscreenRegister(void)函数,用于注册触摸屏设备到输入管理器
// 函数,用于注册触摸屏设备到输入管理器 void TouchscreenRegister(void) { // 调用输入管理器的注册函数,将触摸屏设备注册到输入管理器中 RegisterInputDevice(&g_tTouchscreenDev); }
将以下结构体注册到输入管理器
// 定义一个InputDevice结构体实例,表示触摸屏设备 static InputDevice g_tTouchscreenDev ={ .name = "touchscreen", .GetInputEvent = TouchscreenGetInputEvent, .DeviceInit = TouchscreenDeviceInit, .DeviceExit = TouchscreenDeviceExit, };
3.19 NetInputRegister(void)
在netiput.c 中注册NetInputRegister(void)函数,用于注册网络设备到输入管理器
1. /* 函数,用于注册网络输入设备到输入管理器 */ 2. void NetInputRegister(void) 3. { 4. RegisterInputDevice(&g_tNetinputDev); /* 调用输入管理器的注册函数,将网络输入设备注册到输入管理器中 */ 5. }
将以下结构体注册到输入管理器
/* 函数,用于注册网络输入设备到输入管理器 */ void NetInputRegister(void) { RegisterInputDevice(&g_tNetinputDev); /* 调用输入管理器的注册函数,将网络输入设备注册到输入管理器中 */ }
*3.20 RegisterInputDevice(PInputDevice ptInputDev)
在input_manager.c 注册上面的输入设备,这一层要接受下一层传输上来的注册信息 ,所以要定义一个 g_InputDevs的链表头,g_InputDevs这个链表中存放设备。将所有设备放到一个链表中
/* 注册输入设备函数 */ void RegisterInputDevice(PInputDevice ptInputDev) { ptInputDev->ptNext = g_InputDevs; g_InputDevs = ptInputDev; }
*3.21 IntpuDeviceInit(void)
在input_manager.c中向上提供一个IntpuDeviceInit(void)对于每个设备初始化它,并且创建线程
* 初始化输入设备 */ void IntpuDeviceInit(void) { int ret; pthread_t tid; /* 遍历所有输入设备,进行初始化和创建接收线程 */ PInputDevice ptTmp = g_InputDevs; while (ptTmp) { /* 初始化设备 */ ret = ptTmp->DeviceInit(); /* 创建接收线程 */ if (!ret) { ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp); } ptTmp = ptTmp->ptNext; } }
3.22 *input_recv_thread_func(void *data)
在input_manager.c 中创建*input_recv_thread_func(void *data)输入接收线程函数,等待数据,保存数据,唤醒线程
/* 输入接收线程函数 */ static void *input_recv_thread_func(void *data) { PInputDevice ptInputDev = (PInputDevice)data; InputEvent tEvent; int ret; while (1) { /* 从输入设备读取数据 */ ret = ptInputDev->GetInputEvent(&tEvent); if (!ret) { /* 保存数据到环形缓冲区 */ pthread_mutex_lock(&g_tMutex); PutInputEventToBuffer(&tEvent); /* 唤醒等待数据的线程 */ pthread_cond_signal(&g_tConVar); pthread_mutex_unlock(&g_tMutex); } } return NULL; }
*3.23 GetInputEvent(PT_InputEvent ptInputEvent)
在input_manager.c 中定义一个最重要的一个函数 ,最上层的代码只要调用这个函数就可以得到这些设备的数据,得到线程数据则返回数据,无数据则休眠
/* 获取输入事件的函数 */ int GetInputEvent(PInputEvent ptInputEvent) { InputEvent tEvent; // 临时变量,用于存储从缓冲区读取的事件 int ret; // 返回值,用于表示函数执行的结果 /* 加锁以保证线程安全 */ pthread_mutex_lock(&g_tMutex); // 获取互斥锁,保护共享资源 /* 尝试从缓冲区获取事件 */ if (GetInputEventFromBuffer(&tEvent)) // 如果成功从缓冲区获取事件 { *ptInputEvent = tEvent; // 将获取的事件复制到传入的指针所指向的变量 pthread_mutex_unlock(&g_tMutex); // 释放互斥锁 return 0; // 返回0表示成功获取事件 } else // 如果缓冲区为空,没有事件可获取 { /* 休眠等待新事件的到来 */ pthread_cond_wait(&g_tConVar, &g_tMutex); // 在条件变量上等待,同时释放互斥锁,直到被唤醒 /* 再次尝试从缓冲区获取事件 */ if (GetInputEventFromBuffer(&tEvent)) // 如果被唤醒后成功获取事件 { *ptInputEvent = tEvent; // 将获取的事件复制到传入的指针所指向的变量 ret = 0; // 设置返回值为0,表示成功获取事件 } else // 如果仍然没有事件可获取 { ret = -1; // 设置返回值为-1,表示获取事件失败 } pthread_mutex_unlock(&g_tMutex); // 释放互斥锁 } return ret; // 返回函数执行的结果 }
3.24 环形缓冲区
在input_manager.c中编写环形缓冲区也是一个一维数组,并不是一个环形的数组,用于保存各个输入设备得到的数据
/* 定义环形缓冲区的大小 */ #define BUFFER_LEN 20 /* 定义环形缓冲区的读写指针 */ static int g_iRead = 0; // 读指针,指向缓冲区中下一个将被读取的元素位置 static int g_iWrite = 0; // 写指针,指向缓冲区中下一个将被写入的元素位置 /* 定义环形缓冲区存储的数据类型,这里假设为InputEvent */ static InputEvent g_atInputEvents[BUFFER_LEN]; // 环形缓冲区,用于存储InputEvent类型的数据 /* 检查环形缓冲区是否已满 */ static int isInputBufferFull(void) { // 如果写指针的下一个位置是读指针,则缓冲区已满 return (g_iRead == ((g_iWrite + 1) % BUFFER_LEN)); } /* 检查环形缓冲区是否为空 */ static int isInputBufferEmpty(void) { // 如果读指针和写指针相同,则缓冲区为空 return (g_iRead == g_iWrite); } /* 将输入事件放入环形缓冲区 */ static void PutInputEventToBuffer(PInputEvent ptInputEvent) { // 如果缓冲区未满,则将输入事件写入缓冲区,并更新写指针 if (!isInputBufferFull()) { g_atInputEvents[g_iWrite] = *ptInputEvent; g_iWrite = (g_iWrite + 1) % BUFFER_LEN; } } /* 从环形缓冲区获取输入事件 */ static int GetInputEventFromBuffer(PInputEvent ptInputEvent) { // 如果缓冲区不为空,则从缓冲区读取输入事件,并更新读指针 if (!isInputBufferEmpty()) { *ptInputEvent = g_atInputEvents[g_iRead]; g_iRead = (g_iRead + 1) % BUFFER_LEN; return 1; // 表示成功获取事件 } else { return 0; // 表示缓冲区为空,无法获取事件 } }
要想访问环形缓冲区,需要加个互斥锁
/* 互斥锁和条件变量,用于线程同步 */ static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
3.25 测试代码
#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> // IO控制设备的函数定义 #include <input_manager.h> // 自定义输入管理的头文件 int main(int argc, char **argv) { int ret; // 用于函数返回值 InputEvent event; // 定义一个输入事件的结构体变量 InputInit(); // 初始化输入系统 IntpuDeviceInit(); // 初始化输入设备,注意这里应该是 InputDeviceInit while (1) // 主循环 { // 打印当前文件名、函数名和行号 printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__); ret = GetInputEvent(&event); // 从输入设备获取一个事件 // 再次打印,并显示GetInputEvent函数的返回值 printf("%s %s %d, ret = %d\n", __FILE__, __FUNCTION__, __LINE__, ret); if (ret) { // 如果返回值不是0,表示获取事件时出错 printf("GetInputEvent err!\n"); return -1; // 出错返回-1 } else // 如果成功获取事件 { // 打印事件类型 printf("%s %s %d, event.iType = %d\n", __FILE__, __FUNCTION__, __LINE__, event.iType ); if (event.iType == INPUT_TYPE_TOUCH) // 如果是触摸屏事件 { // 打印触摸屏事件的详细信息 printf("Type : %d\n", event.iType); printf("iX : %d\n", event.iX); printf("iY : %d\n", event.iY); printf("iPressure : %d\n", event.iPressure); } else if (event.iType == INPUT_TYPE_NET) // 如果是网络事件 { // 打印网络事件的详细信息 printf("Type : %d\n", event.iType); printf("str : %s\n", event.str); } } } return 0; // 正常退出程序 }