数码相框项目学习笔记(一)

news/2024/11/8 17:09:02/

之前看了韦东山老师的数码相框项目,断断续续学完了,现在再整理回顾,做个笔记记录一下。

项目需求:

实现在开发板上显示、浏览图片文件,并能进行图片的放大、缩小、移动、连播等操作

项目的主体框架:

在这里插入图片描述

项目的主要流程:

在这里插入图片描述

硬件准备

Linux开发板、触摸屏
我的开发板是IMX6ULL,带有4.3英寸触摸屏

主函数分析

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <config.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <encoding_manager.h>
#include <fonts_manager.h>
#include <disp_manager.h>
#include <input_manager.h>
#include <pic_operation.h>
#include <render.h>
#include <picfmt_manager.h>/* main <freetype_file> */
int main(int argc, char **argv)
{	int ret;if (argc != 2){printf("Usage:\n");printf("%s <freetype_file>\n", argv[0]);return 0;}/* 注册显示设备 */DisplayInit();/* 可能可支持多个显示设备: 选择和初始化指定的显示设备 */SelectAndInitDefaultDispDev("fb");/* 分配5个VideoMem缓存,用于预先存放要显示的数据,以加快显示 */AllocVideoMem(5);/* 注册输入设备 */InputInit();/* 调用所有输入设备的初始化函数 */AllInputDevicesInit();/* 注册编码模块 */EncodingInit();/* 注册字库模块 */ret = FontsInit();if (ret){printf("FontsInit error!\n");}/* 设置freetype字库所用的文件和字体尺寸 */ret = SetFontsDetail("freetype", argv[1], 24);if (ret){printf("SetFontsDetail error!\n");}/* 注册图片文件解析模块 */PicFmtsInit();/* 注册页面 */PagesInit();/* 运行主页面 */Page("main")->Run(NULL);return 0;
}

当我们打开数码相框应用程序时,应该马上显示主页面,然后就根据用户的操作采取不同的动作响应。这些工作在Page(“main”)->Run(NULL);中完成。这个函数就是调用页面显示函数显示主页面,同时调用获取输入事件函数,当有输入事件产生,则调用相应的处理函数。例如,用户点击浏览模式,显示浏览界面;当点击页面中图片则显示该图片,当点击目录则进入目录并显示目录内容。
我们接下来分析下具体的模块。

模块分析

1、显示模块
首先要使图片在屏幕上显示出来,这个屏幕可以是LCD触摸屏,VGA屏等等,所以需要一个显示模块来进行显示设备的选择、初始化,获得显示设备的参数,如分辨率、像素位数、显存等,获取了这些参数后我们才可以在屏幕的某一位置描画数据。
为了方便扩展,采用面向对象的思想,先建立一个设备管理模块disp_manager,它提供了一个统一的设备结构体和一些操作函数,这个结构体中包含设备的基本信息和操作集,当有新的设备接入时,它需要提供这样一个结构体向管理模块注册。注册的具体实现是管理模块维护一个链表,链表成员是显示设备的相关结构体,当有显示设备注册时,将其提供的结构体加入链表,这样当选择显示设备时,遍历链表即可。

/* "disp_manager.h" */
typedef struct DispOpr {char *name;              										// 显示模块的名字int iXres;               										// X分辨率int iYres;               										// Y分辨率 int iBpp;                										// 像素位数 int iLineWidth;          										// 行字节数 unsigned char *DispMem;   										// 显存地址 int (*DeviceInit)(void);     									// 设备初始化函数 int (*ShowPixel)(int iPenX, int iPenY, unsigned int dwColor);   // 指定像素显示颜色 int (*CleanScreen)(unsigned int dwBackColor);                   // 清屏为某颜色 int (*ShowPage)(PT_VideoMem ptVideoMem);                        // 显示一页struct DispOpr *ptNext;     
}T_DispOpr, *PT_DispOpr;
#include <config.h>
#include <disp_manager.h>
#include <string.h>static PT_DispOpr g_ptDispOprHead;
static PT_DispOpr g_ptDefaultDispOpr;
static PT_VideoMem g_ptVideoMemHead;/* 注册某个显示模块,加入到链表中(链表中存放已经注册的显示设备)*/
int RegisterDispOpr(PT_DispOpr ptDispOpr)
{PT_DispOpr tmp;/* 当前链表中尚未有显示设备 */if (!g_ptDispOprHead){g_ptDispOprHead   = ptDispOpr;ptDispOpr->ptNext = NULL;}else{/*链表中已有一些显示设备,在链表尾加入即可 */tmp = g_ptDispOprHead;while (tmp->ptNext){tmp = tmp->ptNext;}tmp->ptNext	  = ptDispOpr;ptDispOpr->ptNext = NULL;}return 0;
}

disp_manager提供了一些统一的操作函数:取出某个指定的显示设备结构体、获得设备基本信息、获得设备显存等

/* 根据名字取出指定的显示设备模块 */
PT_DispOpr GetDispOpr(char *pcName)
{PT_DispOpr tmp = g_ptDispOprHead;while (tmp){if (strcmp(tmp->name, pcName) == 0){return tmp;}tmp = tmp->ptNext;}return NULL;
}
/* 根据名字取出指定的显示设备模块,并调用初始化函数,设为默认设备*/
void SelectAndInitDefaultDispDev(char *name)
{g_ptDefaultDispOpr = GetDispOpr(name);if (g_ptDefaultDispOpr){g_ptDefaultDispOpr->DeviceInit();g_ptDefaultDispOpr->CleanScreen(0);}
}
/* 获取显示设备的分辨率和Bpp */
int GetDispResolution(int *piXres, int *piYres, int *piBpp)
{if (g_ptDefaultDispOpr){*piXres = g_ptDefaultDispOpr->iXres;*piYres = g_ptDefaultDispOpr->iYres;*piBpp  = g_ptDefaultDispOpr->iBpp;return 0;}else{return -1;}
}
/* typedef struct VideoMem {int iID;                        // 标识不同的页面,主页面、浏览页面...int bDevFrameBuffer;            // 1标识显存; 0标识普通缓存E_VideoMemState eVideoMemState; // VideoMem本身状态E_PicState ePicState;           // VideoMem中内存里图片的状态T_PixelDatas tPixelDatas;       // 存储数据struct VideoMem *ptNext;      
}T_VideoMem, *PT_VideoMem;
*/
/* 获得显存 */
PT_VideoMem GetDevVideoMem(void)
{PT_VideoMem tmp = g_ptVideoMemHead;while (tmp){if (tmp->bDevFrameBuffer){return tmp;}tmp = tmp->ptNext;}return NULL;
}

在显示过程中,如果要切换显示页面,直接获取数据在向显存中写入效率会很低,画面会有明显的卡顿,为加快显示速度,我们事先在缓存中构造好显示的页面的数据存入VideoMem,显示时再把VideoMem中的数据复制到设备的显存上 。因此可以预先准备多个VideoMem,提供一个分配VideoMem的函数。

/* VideoMem的状态:* 	空闲* 	用于预先准备显示内容* 	用于当前线程*/
typedef enum {VMS_FREE = 0,VMS_USED_FOR_PREPARE,VMS_USED_FOR_CUR,	
}E_VideoMemState;/* VideoMem中内存里图片的状态:* 空白* 正在生成* 已经生成*/
typedef enum {PS_BLANK = 0,PS_GENERATING,PS_GENERATED,	
}E_PicState;int AllocVideoMem(int iNum)
{int i;int iXres = 0;int iYres = 0;int iBpp  = 0;int iVMSize;int iLineBytes;PT_VideoMem ptNew;GetDispResolution(&iXres, &iYres, &iBpp);iVMSize = iXres * iYres * iBpp / 8;iLineBytes = iXres * iBpp / 8;/* 先把设备本身的framebuffer放入链表将tPixelDatas.aucPixelDatas指向显示设备的framebuffer */ptNew = malloc(sizeof(T_VideoMem));if (ptNew == NULL){return -1;}ptNew->tPixelDatas.aucPixelDatas = g_ptDefaultDispOpr->pucDispMem;ptNew->iID = 0;ptNew->bDevFrameBuffer = 1;       	//表明设备本身的FramebufferptNew->eVideoMemState  = VMS_FREE;  ptNew->ePicState	   = PS_BLANK;ptNew->tPixelDatas.iWidth  = iXres;ptNew->tPixelDatas.iHeight = iYres;ptNew->tPixelDatas.iBpp    = iBpp;ptNew->tPixelDatas.iLineBytes  = iLineBytes;ptNew->tPixelDatas.iTotalBytes = iVMSize;/* 如果下面要分配用于缓存的VideoMem, * 把设备本身framebuffer对应的VideoMem状态设置为VMS_USED_FOR_CUR,* 表示这个VideoMem不会被作为缓存分配出去*/if (iNum != 0){ptNew->eVideoMemState = VMS_USED_FOR_CUR;}/* 放入链表 */ptNew->ptNext = g_ptVideoMemHead;g_ptVideoMemHead = ptNew;/* 分配用于缓存的VideoMem */for (i = 0; i < iNum; i++){/* 分配T_VideoMem结构体本身和"跟framebuffer同样大小的缓存"* 即T_VideoMem结构体后面紧跟着数据缓存区,这样ptNew+1即是数据缓冲区地址*/ptNew = malloc(sizeof(T_VideoMem) + iVMSize);if (ptNew == NULL){return -1;}ptNew->tPixelDatas.aucPixelDatas = (unsigned char *)(ptNew + 1);ptNew->iID = 0;ptNew->bDevFrameBuffer = 0;ptNew->eVideoMemState = VMS_FREE;ptNew->ePicState      = PS_BLANK;ptNew->tPixelDatas.iWidth  = iXres;ptNew->tPixelDatas.iHeight = iYres;ptNew->tPixelDatas.iBpp    = iBpp;ptNew->tPixelDatas.iLineBytes = iLineBytes;ptNew->tPixelDatas.iTotalBytes = iVMSize;/* 放入链表 */ptNew->ptNext = g_ptVideoMemHead;g_ptVideoMemHead = ptNew;}return 0;
}

还有一些其他的函数就不一一介绍了,具体见源码。
这个项目我们用到的显示设备是触摸屏,因此需要提供一个触摸屏设备的结构体。
fb.c

#include <config.h>
#include <disp_manager.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>static int FBDeviceInit(void);
static int FBShowPixel(int iX, int iY, unsigned int dwColor);
static int FBCleanScreen(unsigned int dwBackColor);
static int FBShowPage(PT_VideoMem ptVideoMem);static int fd;static struct fb_var_screeninfo FBVar;
static struct fb_fix_screeninfo FBFix;			
static unsigned char *FBMem;
static unsigned int ScreenSize;static unsigned int LineWidth;
static unsigned int PixelWidth;static T_DispOpr FBOpr = {.name        = "fb",.DeviceInit  = FBDeviceInit,.ShowPixel   = FBShowPixel,.CleanScreen = FBCleanScreen,.ShowPage    = FBShowPage,
};
/* 初始化fb */
static int FBDeviceInit(void)
{int ret;fd = open(FB_DEVICE_NAME, O_RDWR);if (0 > fd){printf("can't open %s\n", FB_DEVICE_NAME);}//获取可变参数ret = ioctl(fd, FBIOGET_VSCREENINFO, &FBVar);if (ret < 0){printf("can't get fb's var\n");return -1;}//获取固定参数ret = ioctl(fd, FBIOGET_FSCREENINFO, &FBFix);if (ret < 0){printf("can't get fb's fix\n");return -1;}//计算屏幕字节大小ScreenSize = FBVar.xres * FBVar.yres * FBVar.bits_per_pixel / 8;//内存映射FBMem = (unsigned char *)mmap(NULL , ScreenSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (0 > FBMem)	{printf("can't mmap\n");return -1;}FBOpr.iXres       = FBVar.xres;FBOpr.iYres       = FBVar.yres;FBOpr.iBpp        = FBVar.bits_per_pixel;FBOpr.iLineWidth  = FBVar.xres * FBOpr.iBpp / 8;FBOpr.DispMem  	  = FBMem;LineWidth  = FBVar.xres * FBVar.bits_per_pixel / 8;PixelWidth = FBVar.bits_per_pixel / 8;return 0;
}/* 指定fb上的某一像素为指定颜色 */
static int FBShowPixel(int iX, int iY, unsigned int dwColor)
{unsigned char *pucFB;unsigned short *pwFB16bpp;unsigned int *pdwFB32bpp;unsigned short wColor16bpp;int iRed;int iGreen;int iBlue;if ((iX >= FBVar.xres) || (iY >= FBVar.yres)){printf("out of region\n");return -1;}pucFB      = FBMem + LineWidth * iY + PixelWidth * iX;pwFB16bpp  = (unsigned short *)pucFB;pdwFB32bpp = (unsigned int *)pucFB;//根据像素位数,构建RGBswitch (FBVar.bits_per_pixel){case 8:{*pucFB = (unsigned char)dwColor;break;}case 16:{iRed   = (dwColor >> (16+3)) & 0x1f;iGreen = (dwColor >> (8+2)) & 0x3f;iBlue  = (dwColor >> 3) & 0x1f;wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;*pwFB16bpp	= wColor16bpp;break;}case 32:{*pdwFB32bpp = dwColor;break;}default :{printf("can't support %d bpp\n", FBVar.bits_per_pixel);return -1;}}return 0;
}/* 显示一整页 */
static int FBShowPage(PT_VideoMem ptVideoMem)
{memcpy(FBOpr.DispMem, ptVideoMem->tPixelDatas.aucPixelDatas, ptVideoMem->tPixelDatas.iTotalBytes);return 0;
}/* 清屏 */
static int FBCleanScreen(unsigned int dwBackColor)
{unsigned char *pucFB;unsigned short *pwFB16bpp;unsigned int *pdwFB32bpp;unsigned short wColor16bpp;int iRed;int iGreen;int iBlue;int i = 0;pucFB      = FBMem;pwFB16bpp  = (unsigned short *)pucFB;pdwFB32bpp = (unsigned int *)pucFB;switch (FBVar.bits_per_pixel){case 8:{memset(FBMem, dwBackColor, ScreenSize);break;}case 16:{iRed   = (dwBackColor >> (16+3)) & 0x1f;iGreen = (dwBackColor >> (8+2)) & 0x3f;iBlue  = (dwBackColor >> 3) & 0x1f;wColor16bpp = (iRed << 11) | (iGreen << 5) | iBlue;while (i < ScreenSize){*pwFB16bpp	= wColor16bpp;pwFB16bpp++;i += 2;}break;}case 32:{while (i < ScreenSize){*pdwFB32bpp	= dwBackColor;pdwFB32bpp++;i += 4;}break;}default :{printf("can't support %d bpp\n", FBVar.bits_per_pixel);return -1;}}return 0;
}/* 注册FB设备 */
int FBInit(void)
{return RegisterDispOpr(&FBOpr);
}

2、输入设备模块
输入模块主要是注册输入设备,并且获取输入设备的输入事件,返回输入的数据。与上面一样,为了便于扩展,它也提供了一个管理模块,提供统一的输入设备结构体和注册、操作函数,输入设备实现此结构体并向输入管理模块注册。

typedef struct InputOpr {char *name;          		//输入模块名pthread_t tTreadID;  		//子线程IDint (*DeviceInit)(void);  	int (*DeviceExit)(void);  int (*GetInputEvent)(PT_InputEvent ptInputEvent); //获取输入数据struct InputOpr *ptNext;
}T_InputOpr, *PT_InputOpr;

输入设备当接收到输入事件时,他需要知道这个输入的具体信息,比如输入事件类型、产生时间、输入事件产生在哪个位置,对于触摸屏而言即X、Y坐标;压力值(是否按下);按键值等等。因此需要定义一个存储这些信息的结构体。

// 输入事件类型
#define INPUT_TYPE_STDIN        0
#define INPUT_TYPE_TOUCHSCREEN  1
...typedef struct InputEvent {struct timeval tTime;   //时间int iType;  			//类型int iX;    				//X坐标int iY;					//Y坐标int iKey;   			//按键值int iPressure; 			//压力值
}T_InputEvent, *PT_InputEvent;

注册函数:

/* 注册单个输入模块 */
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{PT_InputOpr ptTmp;if (!g_ptInputOprHead){g_ptInputOprHead   = ptInputOpr;ptInputOpr->ptNext = NULL;}else{ptTmp = g_ptInputOprHead;while (ptTmp->ptNext){ptTmp = ptTmp->ptNext;}ptTmp->ptNext	  = ptInputOpr;ptInputOpr->ptNext = NULL;}return 0;
}

初始化输入设备并创建一个线程来获取输入事件

/* 初始化输入设备,并创建用于读取设备输入数据的子线程 */
int AllInputDevicesInit(void)
{PT_InputOpr ptTmp = g_ptInputOprHead;int ret = -1;while (ptTmp){if (0 == ptTmp->DeviceInit()){/* 创建子线程 */pthread_create(&ptTmp->tTreadID, NULL, InputEventThreadFunction, ptTmp->GetInputEvent);			ret = 0;}ptTmp = ptTmp->ptNext;}return ret;
}
/* 读取设备输入数据的子线程 */
static void *InputEventThreadFunction(void *pVoid)
{T_InputEvent tInputEvent;/* 定义函数指针 *//* pVoid: ptTmp->GetInputEvent ,具体输入设备的获取输入事件函数 */int (*GetInputEvent)(PT_InputEvent ptInputEvent);GetInputEvent = (int (*)(PT_InputEvent))pVoid;while (1){if(0 == GetInputEvent(&tInputEvent)){/* 唤醒主线程, 把tInputEvent的值赋给一个全局变量 *//* 访问临界资源前,先获得互斥量 */pthread_mutex_lock(&g_tMutex);g_tInputEvent = tInputEvent;/*  唤醒主线程 */pthread_cond_signal(&g_tConVar);/* 释放互斥量 */pthread_mutex_unlock(&g_tMutex);}}return NULL;
}

获取输入数据:

/* 获取输入数据,休眠状态,当输入线程读取到数据后会将其唤醒 */
int GetInputEvent(PT_InputEvent ptInputEvent)
{/* 休眠 */pthread_mutex_lock(&g_tMutex);pthread_cond_wait(&g_tConVar, &g_tMutex);	/* 被唤醒后,返回数据 */*ptInputEvent = g_tInputEvent;pthread_mutex_unlock(&g_tMutex);return 0;	
}

因为我们用的是触摸屏,所以注册一个触摸屏设备即可
touchscreen.c

#include <config.h>
#include <input_manager.h>
#include <disp_manager.h>
#include <stdlib.h>
//利用tslib库
#include <tslib.h>static struct tsdev *g_tTSDev;
static int giXres;
static int giYres;/* 初始化 */
static int TouchScreenDevInit(void)
{char *pcTSName = NULL;int iBpp;if ((pcTSName = getenv("TSLIB_TSDEVICE")) != NULL ) {g_tTSDev = ts_open(pcTSName, 0);  /* 以阻塞方式打开 */}else{g_tTSDev = ts_open("/dev/event0", 1);}if (!g_tTSDev) {printf(A"ts_open error!\n");return -1;}if (ts_config(g_tTSDev)) {printf("ts_config error!\n");return -1;}if (GetDispResolution(&giXres, &giYres, &iBpp)){return -1;}return 0;
}/* 读取数据 */
static int TouchScreenGetInputEvent(PT_InputEvent ptInputEvent)
{struct ts_sample tSamp;int iRet;	while (1){/* #include <tslib.h>int ts_read(struct tsdev *dev, struct ts_sample *samp, int nr);*/iRet = ts_read(g_tTSDev, &tSamp, 1); /* 如果无数据则休眠 */if (iRet == 1){ptInputEvent->tTime     = tSamp.tv;ptInputEvent->iType     = INPUT_TYPE_TOUCHSCREEN;ptInputEvent->iX        = tSamp.x;ptInputEvent->iY        = tSamp.y;ptInputEvent->iPressure = tSamp.pressure;return 0;}else{return -1;}}return 0;
}/* 退出触摸屏模块 */
static int TouchScreenDevExit(void)
{return 0;
}static T_InputOpr g_tTouchScreenOpr = {.name          = "touchscreen",.DeviceInit    = TouchScreenDevInit,.DeviceExit    = TouchScreenDevExit,.GetInputEvent = TouchScreenGetInputEvent,
};/* 注册 */
int TouchScreenInit(void)
{return RegisterInputOpr(&g_tTouchScreenOpr);
}

http://www.ppmy.cn/news/588348.html

相关文章

数码相框(一、系统框架)

注&#xff1a;本人已购买韦东山第三期项目视频&#xff0c;内容来源《数码相框项目视频》&#xff0c;只用于学习记录&#xff0c;如有侵权&#xff0c;请联系删除。 1.项目流程 ① 弄清需求 (弄清产品需要实现的功能)&#xff1b; ② 设计框架 (怎么实现需求)&#xff1b; ③…

vue3-element-admin 项目说明文档

vue3-element-admin官方文档 | 在线预览 项目介绍 vue3-element-admin 是基于 Vue3 Vite4 TypeScript5 Element-Plus Pinia 等最新主流技术栈构建的后台管理前端模板&#xff08;配套后端源码&#xff09;。 项目有以下特性&#xff1a; 基于 vue-element-admin 升级到…

12、数码相框编写程序之效果演示与代码讲解

文章目录 1、编译方法2、运行3、完整代码讲解1、main函数先进行DebugInit()注册调试模块2、注册调试模块之后初始化调试通道3、注册显示设备4、为显示页面预先分配缓存5、初始化输入设备6、注册编码模块7、注册字库模块8、注册图片解析模块9、注册页面10、运行1、显示页面2、获…

数码相框(十七、数码相框程序编写_先写框架)

注&#xff1a;本人已购买韦东山老师第三期项目视频&#xff0c;内容来源《数码相框项目视频》&#xff0c;只用于学习记录&#xff0c;如有侵权&#xff0c;请联系删除。 1.数码相框需求框架 数码相框项目需求的框架如下图所示&#xff1a; ① 开发板上电后&#xff0c;进入…

1、数码相框之框架分析

文章目录 1、需求分析2、设计框架3、编写代码4、测试 下一节&#xff1a;2、数码相框之显示文字 设计产品时的思路大体如下&#xff1a; 弄清需求设计框架编写代码测试 1、需求分析 2、设计框架 系统工作大致流程如下&#xff1a; 1、输入进程 触摸屏线程&#xff08;或按键…

10、数码相框编写程序之图标显示

文章目录 1、bmp数据提取1.1、bmp 文件格式1.2、文件信息头1.3、位图信息头1.4、RGB 颜色阵列1.5、关于pic_operation.h1.6、关于bmp.c 2、图片缩放3、图片合并4、测试 上一节&#xff1a;9、数码相框编写程序之框架分析 下一节&#xff1a;11、数码相框编写程序之MainPage显存…

Wifi-ESL接口文档

一、更新Wifi ESL数据接口&#xff0c;updateScreen 使用系统模板 接口简介&#xff1a; 使用服务器模板更新ESL数据 基本信息&#xff1a; 标识 接口信息 接口状态 开发中 接口地址 http://47.106.109.236:8010/mms/associate/updateScreen 请求方式 POST 请求类型 …

关于小米路由器青春版刷老固件

首先要知道因为重置过的路由器&#xff0c;未配置拨号或者宽带连联网&#xff0c;所以手动降级要将我们的电脑与路由器用网线连接&#xff0c;再登陆mini.com,进行刷机。