文章目录

  • 前言
  • 1、显示系统
    • 1.1、程序分层
    • 1.2、几个重要的数据结构
    • 1.3、程序分析
  • 2、输入系统
    • 2.1、程序分层
    • 2.2、触摸屏输入
      • 2.2.1、几个重要的数据结构
      • 2.2.1、程序分析
    • 2.3、网络输入
    • 2.4、输入管理
      • 2.4.1、框架framework
        • 程序分析
      • 2.4.2、环形缓冲区circlebuff
        • 什么是环形缓冲区?
        • 环形缓冲区的用法
        • 环形缓冲区的工作过程
        • 环形缓冲区工作机制
        • 程序分析
    • 2.5、测试程序
  • 3、文字系统
    • 3.1、数据结构抽象
    • 3.2、实现Freetype代码
    • 3.3、文字管理
    • 3.5、单元测试
  • 参考资料

前言

韦东山项目实战之七步从零编写带GUI的应用(项目开发|论文参考|C|GUI)学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容

视频教程地址: https://www.bilibili.com/video/BV1it4y1Q75z

1、显示系统

【嵌入式Linux】嵌入式Linux应用开发基础知识之Framebuffer应用编程和字符汉字显示

1.1、程序分层

▲程序分层

目前还没有按钮和web显示的代码

1.2、几个重要的数据结构

/*
*   显示缓冲区
*   iXres iYres:屏幕的x,y轴长度
*   iBpp:字符的颜色Bpp(bit per pixel)信息
*   buff:framebuffer基地址
*/
typedef struct DispBuff {int iXres;int iYres;int iBpp;char *buff;
}DispBuff, *PDispBuff;/*
*   区域信息
*   iLeftUpX iLeftUpY:里面包含了绘制的起始坐标
*   iWidth iHeigh:字符的长宽信息
*/
typedef struct Region {int iLeftUpX;int iLeftUpY;int iWidth;int iHeigh;
}Region, *PRegion;/*
*   Display相关函数集合结构体 用来表示一个Display设备
*   DeviceInit:初始化设备
*   DeviceExit:退出回收设备
*   GetBuffer :获得Displaybuffer
*   FlushRegio:融合区
*   ptNext:指向下一个DispOpr类型结构体指针即下一个Display设备
*/
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, *PDispOpr;

在程序中使用了一个链表g_DispDevs来保存Display设备,LCD设备和web设备都将保存在这个链表中
g_DispDefault指向默认的Display设备
g_tDispBuff保存Dispalybuffer使用LCD时可以理解为Frambuffer

/* 管理底层的LCD、WEB */
static PDispOpr g_DispDevs = NULL;     //Display设备链表
static PDispOpr g_DispDefault = NULL;  //默认Display设备指针 从Display设备链表中获取Display设备地址
static DispBuff g_tDispBuff;            //保存Dispalybuffer 在这里可以理解为Frambuffer

1.3、程序分析

disp_test.c:使屏幕上显示字符A
注:fontdata_8x16数组见韦东山老师的程序

#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>#include <disp_manager.h>/*********************************************************************** 函数名称: lcd_put_ascii* 功能描述: 在LCD指定位置上显示一个8*16的字符* 输入参数: x坐标,y坐标,ascii码* 输出参数: 无* 返 回 值: 无* 修改日期        版本号     修改人        修改内容* -----------------------------------------------* 2020/05/12      V1.0     zh(angenao)         创建***********************************************************************/
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)){/* show */PutPixel(x+7-b, y+i, 0xffffff); /* 白 */}else{/* hide */PutPixel(x+7-b, y+i, 0); /* 黑 */}}}
}int main(int argc, char **argv)
{Region region;PDispBuff ptBuffer;//初始化Display相关设备,LCD设备和Web设备(暂无),在Display设备链表中插入节点 DisplayInit();//设定默认Display设备,在g_DispDevs链表中寻找name == fb的设备使g_DispDefault指向它SelectDefaultDisplay("fb");//初始化默认Display设备 包含设备初始化和获得DisplaybufferInitDefaultDisplay();//向Framebuffer缓冲区中填写内容lcd_put_ascii(100, 100, 'A');/* *   下面的函数及内容是为web设备准备的,由于在InitDefaultDisplay时已经获得了framebuffer的基地址*   所以这里不用FlushDisplayRegion*///填写区域信息region.iLeftUpX = 100;region.iLeftUpY = 100;region.iWidth   = 8;region.iHeigh   = 16;//获得Framebuffer地址ptBuffer = GetDisplayBuffer();//将区域信息和Framebuffer信息融合FlushDisplayRegion(&region, ptBuffer);return 0;
}

disp_manager.h:包含一些数据结构和函数声明

#ifndef _DISP_MANAGER_H
#define _DISP_MANAGER_H#ifndef NULL
#define NULL (void *)0
#endif/*
*   显示缓冲区
*   iXres iYres:屏幕的x,y轴长度
*   iBpp:字符的颜色Bpp(bit per pixel)信息
*   buff:framebuffer基地址
*/
typedef struct DispBuff {int iXres;int iYres;int iBpp;char *buff;
}DispBuff, *PDispBuff;/*
*   区域信息
*   iLeftUpX iLeftUpY:里面包含了绘制的起始坐标
*   iWidth iHeigh:字符的长宽信息
*/
typedef struct Region {int iLeftUpX;int iLeftUpY;int iWidth;int iHeigh;
}Region, *PRegion;/*
*   Display相关函数集合结构体 用来表示一个Display设备
*   DeviceInit:初始化设备
*   DeviceExit:退出回收设备
*   GetBuffer :获得Displaybuffer
*   FlushRegio:融合区
*   ptNext:指向下一个DispOpr类型结构体指针即下一个Display设备
*/
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, *PDispOpr;void RegisterDisplay(PDispOpr ptDispOpr);void DisplayInit(void);
int SelectDefaultDisplay(char *name);
int InitDefaultDisplay(void);
int PutPixel(int x, int y, unsigned int dwColor);
int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff);
PDispBuff GetDisplayBuffer(void);#endif

disp_manager.c:管理Display设备相关函数

#include <stdio.h>
#include <string.h>
#include <disp_manager.h>/* 管理底层的LCD、WEB */
static PDispOpr g_DispDevs = NULL;     //Display设备链表
static PDispOpr g_DispDefault = NULL;  //默认Display设备指针 从Display设备链表中获取Display设备地址
static DispBuff g_tDispBuff;            //保存Dispalybuffer 在这里可以理解为Frambuffer
static int line_width;
static int pixel_width;/*显示一个像素,涉及到颜色计算*/
int PutPixel(int x, int y, unsigned int dwColor)
{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;switch (g_tDispBuff.iBpp){case 8:{*pen_8 = dwColor;break;}case 16:{/* 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:{*pen_32 = dwColor;break;}default:{printf("can't surport %dbpp\n", g_tDispBuff.iBpp);return -1;break;}}return 0;
}void RegisterDisplay(PDispOpr ptDispOpr)
{//在Display设备链表中用头插法插入节点ptDispOpr->ptNext = g_DispDevs; //ptNext指向原来的Display设备g_DispDevs = ptDispOpr;          //g_DispDevs指向新的Dispay设备
}int SelectDefaultDisplay(char *name)
{PDispOpr pTmp = g_DispDevs;while (pTmp) {//遍历g_DispDevs设备链表寻找name相同设备让g_DispDefault指向该Display设备if (strcmp(name, pTmp->name) == 0){g_DispDefault = pTmp;return 0;}pTmp = pTmp->ptNext;}return -1;
}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;}//LCD行宽 单位:byteline_width  = g_tDispBuff.iXres * g_tDispBuff.iBpp/8;//像素宽度 单位:bytepixel_width = g_tDispBuff.iBpp/8;return 0;
}PDispBuff GetDisplayBuffer(void)
{//返回Displaybuffer地址return &g_tDispBuff;
}int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)
{//将区域信息和Framebuffer信息融合return g_DispDefault->FlushRegion(ptRegion, ptDispBuff);
}void DisplayInit(void)
{extern void FramebufferInit(void);//初始化LCD设备FramebufferInit();
}

framebuffer.c:LCD的framebuffer操作的相关函数,同时也定义了LCD设备结构体g_tFramebufferOpr

#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>#include <disp_manager.h>static int fd_fb;
static struct fb_var_screeninfo var;    /* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;/*Framebuffer初始化*/
static int FbDeviceInit(void)
{fd_fb = open("/dev/fb0", O_RDWR);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;         //行宽 单位:bytepixel_width = var.bits_per_pixel / 8;                       //像素宽度 单位:bytescreen_size = var.xres * var.yres * var.bits_per_pixel / 8;   //屏幕尺寸 单位:bytefb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);//申请屏幕尺寸大小的内存并返回基地址if (fb_base == (unsigned char *)-1){printf("can't mmap\n");return  -1;}return 0;
}/*Framebuffer回收*/
static int FbDeviceExit(void)
{munmap(fb_base, screen_size);close(fd_fb);return 0;
}/* 可以返回LCD的framebuffer, 以后上层APP可以直接操作LCD, 可以不用FbFlushRegion* 也可以malloc返回一块无关的buffer, 要使用FbFlushRegion*/
static int FbGetBuffer(PDispBuff ptDispBuff)
{ptDispBuff->iXres = var.xres;ptDispBuff->iYres = var.yres;ptDispBuff->iBpp  = var.bits_per_pixel;ptDispBuff->buff  = (char *)fb_base;return 0;
}static int FbFlushRegion(PRegion ptRegion, PDispBuff ptDispBuff)
{return 0;
}//初始化g_tFramebufferOpr结构体
static DispOpr g_tFramebufferOpr = {.name        = "fb",.DeviceInit  = FbDeviceInit,.DeviceExit  = FbDeviceExit,.GetBuffer   = FbGetBuffer,.FlushRegion = FbFlushRegion,
};void FramebufferInit(void)
{//注册一个Display设备 RegisterDisplay(&g_tFramebufferOpr);
}

2、输入系统

【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程
【嵌入式Linux】嵌入式Linux应用开发基础知识之网络通信
【嵌入式Linux】嵌入式Linux应用开发基础知识之多线程编程

2.1、程序分层

▲程序分层

2.2、触摸屏输入

2.2.1、几个重要的数据结构

抽象出两个结构体,数据和设备

/*
*   输入事件结构体
*   iType:输入事件类型
*   iX iY:输入事件坐标
*   iPressure:输入事件压力值
*   str:输入事件字符串,网络编程使用
*/
typedef struct InputEvent {struct timeval   tTime;int iType;int iX;int iY;int iPressure;char str[1024];
}InputEvent, *PInputEvent;/*
*   输入设备结构体 用来表示一个输入设备
*   DeviceInit:初始化设备
*   DeviceExit:退出回收设备
*   GetInputEvent:接收输入事件
*   ptNext:指向下一个InputDevice类型结构体指针即下一个输入设备
*/
typedef struct InputDevice {char *name;int (*GetInputEvent)(PInputEvent ptInputEvent);int (*DeviceInit)(void);int (*DeviceExit)(void);struct InputDevice *ptNext;
}InputDevice, *PInputDevice;

2.2.1、程序分析

input_manager.h:包含数据结构和函数声明

#ifndef _INPUT_MANAGER_H
#define _INPUT_MANAGER_H#include <sys/time.h>#ifndef NULL
#define NULL (void *)0
#endif#define INPUT_TYPE_TOUCH 1
#define INPUT_TYPE_NET   2/*
*   输入事件结构体
*   iType:输入事件类型
*   iX iY:输入事件坐标
*   iPressure:输入事件压力值
*   str:输入事件字符串,网络编程使用
*/
typedef struct InputEvent {struct timeval   tTime;int iType;int iX;int iY;int iPressure;char str[1024];
}InputEvent, *PInputEvent;/*
*   输入设备结构体 用来表示一个输入设备
*   DeviceInit:初始化设备
*   DeviceExit:退出回收设备
*   GetInputEvent:接收输入事件
*   ptNext:指向下一个InputDevice类型结构体指针即下一个输入设备
*/
typedef struct InputDevice {char *name;int (*GetInputEvent)(PInputEvent ptInputEvent);int (*DeviceInit)(void);int (*DeviceExit)(void);struct InputDevice *ptNext;
}InputDevice, *PInputDevice;void RegisterInputDevice(PInputDevice ptInputDev);
void InputSystemRegister(void);
void IntpuDeviceInit(void);
int GetInputEvent(PInputEvent ptInputEvent);#endif

touchscreen.c:监测触控屏设备事件及输出报点信息

#include <input_manager.h>
#include <tslib.h>
#include <stdio.h>static struct tsdev *g_ts;static int TouchscreenGetInputEvent(PInputEvent ptInputEvent)
{struct ts_sample samp;int ret;//使用ts_lib库函数读取报点信息ret = ts_read(g_ts, &samp, 1);if (ret != 1)return -1;//将报点信息传入ptInputEventptInputEvent->iType     = INPUT_TYPE_TOUCH;ptInputEvent->iX        = samp.x;ptInputEvent->iY        = samp.y;ptInputEvent->iPressure = samp.pressure;ptInputEvent->tTime     = samp.tv;return 0;
}static int TouchscreenDeviceInit(void)
{//使用ts_lib函数初始化触控设备g_ts = ts_setup(NULL, 0);if (!g_ts){printf("ts_setup err\n");return -1;}return 0;
}static int TouchscreenDeviceExit(void)
{//使用ts_lib函数关闭触控设备ts_close(g_ts);return 0;
}/*创建一个输入设备:触控屏*/
static InputDevice g_tTouchscreenDev ={.name = "touchscreen",.GetInputEvent  = TouchscreenGetInputEvent,.DeviceInit     = TouchscreenDeviceInit,.DeviceExit     = TouchscreenDeviceExit,
};#if 1int 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

2.3、网络输入

网络输入编程使用的数据结构和触摸屏使用的数据结构相同,这里不再赘述
在开发板上运行的是server端的程序使用udp通信,所以在创建设备的时候需要完成

/* socket* bind* sendto/recvfrom*/

netinput.c:创建一个server设备使用udp通信协议,接收client信息并打印到console终端上

#include <input_manager.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/* socket* bind* sendto/recvfrom*/#define SERVER_PORT 8888static int g_iSocketServer;/*网络设备文件描述符*//*获取网络设备上报信息*/
static int NetinputGetInputEvent(PInputEvent ptInputEvent)
{struct sockaddr_in tSocketClientAddr;int iRecvLen;char aRecvBuf[1000];unsigned int iAddrLen = sizeof(struct sockaddr);//没有数据报到达时阻塞iRecvLen = recvfrom(g_iSocketServer, aRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);if (iRecvLen > 0){aRecvBuf[iRecvLen] = '\0';//printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);ptInputEvent->iType   = INPUT_TYPE_NET;//设置上报信息类型为网络类型gettimeofday(&ptInputEvent->tTime, NULL);//获取默认时区的时间strncpy(ptInputEvent->str, aRecvBuf, 1000);//拷贝缓冲区的数据到ptInputEventptInputEvent->str[999] = '\0';//添加结束符return 0;}elsereturn -1;
}/*网络输入设备初始化*/
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;}tSocketServerAddr.sin_family      = AF_INET;tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;memset(tSocketServerAddr.sin_zero, 0, 8);//绑定socket文件描述符和socket addressiRet = bind(g_iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));if (-1 == iRet){printf("bind error!\n");return -1;}return 0;
}/*关闭网络设备*/
static int NetinputDeviceExit(void)
{close(g_iSocketServer);    return 0;
}/*创建一个网络输入设备*/
static InputDevice g_tNetinputDev ={.name = "touchscreen",.GetInputEvent  = NetinputGetInputEvent,.DeviceInit     = NetinputDeviceInit,.DeviceExit     = NetinputDeviceExit,
};#if 1int main(int argc, char **argv)
{InputEvent event;int ret;g_tNetinputDev.DeviceInit();while (1){/*接收client信息并输出*/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

client.c:测试使用的client程序,使用./client <server_ip> <str>来向server发送信息

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>/* socket* connect* send/recv*/#define SERVER_PORT 8888int 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;}iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);tSocketServerAddr.sin_family      = AF_INET;tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short *///tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr)){printf("invalid server_ip\n");return -1;}memset(tSocketServerAddr.sin_zero, 0, 8);#if 0iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));  if (-1 == iRet){printf("connect error!\n");return -1;}
#endifiAddrLen = sizeof(struct sockaddr);iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0,(const struct sockaddr *)&tSocketServerAddr, iAddrLen);close(iSocketClient);return 0;
}

2.4、输入管理

2.4.1、框架framework

▲输入管理器

在输入管理器中一共向外部提供了三个函数:

  void InputInit(void)
    连接到驱动侧,驱动侧通过这个函数将注册输入设备结构体到设备链表中
  void RegisterInputDevice(PInputDevice ptInputDev) / void InpuInit(PInputDevice ptInputDev)
    连接到APP侧,APP侧可以使用这个函数初始化设备链表中所有的输入设备,并且为其创建监听线程
  int GetInputEvent(PT_InputEvent ptInputEvent)
    连接到APP侧,APP侧可以使用这个函数获取底层上报的事件信息

此外还有一个线程函数static void *input_recv_thread_func (void *data)负责完成监听线程工作

程序分析

input_manage.c:其中int GetInputEvent(PT_InputEvent ptInputEvent)static void *input_recv_thread_func (void *data) static void *input_recv_thread_func (void *data)函数没写完

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>static PInputDevice g_InputDevs  = NULL;void RegisterInputDevice(PInputDevice ptInputDev)
{ptInputDev->ptNext = g_InputDevs;g_InputDevs = ptInputDev;
}/*  */
void InputInit(void)
{/* regiseter touchscreen */extern void TouchscreenRegister(void);TouchscreenRegister();/* regiseter netinput */extern void NetInputRegister(void);NetInputRegister();
}static void *input_recv_thread_func (void *data)
{PInputDevice tInputDev = (PInputDevice)data;InputEvent tEvent;int ret;while (1){/* 读数据 */ret = tInputDev->GetInputEvent(&tEvent);if (!ret){   /* 保存数据 *//* 唤醒等待数据的线程 */pthread_mutex_lock(&g_tMutex);pthread_cond_wait(&g_tConVar, &g_tMutex);    pthread_mutex_unlock(&g_tMutex);}}return NULL;
}void IntpuDeviceInit(void)
{int ret;pthread_t tid;/* for each inputdevice, init, pthread_create */PInputDevice ptTmp = g_InputDevs;while (ptTmp){/* init device */ret = ptTmp->DeviceInit();/* pthread create */if (!ret){ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp);}ptTmp= ptTmp->ptNext;}
}int GetInputEvent(PT_InputEvent ptInputEvent)
{/* 无数据则休眠 *//* 返回数据 */
}

touchscreen.c:添加函数void TouchscreenRegister(void)

...
void TouchscreenRegister(void)
{RegisterInputDevice(&g_tTouchscreenDev);
}

netinput.c:添加函数void NetInputRegister(void)

...
void NetInputRegister(void)
{RegisterInputDevice(&g_tNetinputDev);
}

2.4.2、环形缓冲区circlebuff

建立一个环形缓冲区,APP、触摸屏设备和网络设备会互斥的访问改写这个缓冲区来让APP获得设备上报的事件信息

什么是环形缓冲区?

  圆形缓冲区(circular buffer),也称作圆形队列(circular queue),循环缓冲区(cyclic buffer),环形缓冲区(ring buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。

环形缓冲区的用法

  圆形缓冲区的一个有用特性是:当一个数据元素被用掉后,其余数据元素不需要移动其存储位置。相反,一个非圆形缓冲区(例如一个普通的队列)在用掉一个数据元素后,其余数据元素需要向前搬移。换句话说,圆形缓冲区适合实现先进先出缓冲区,而非圆形缓冲区适合后进先出缓冲区。
  圆形缓冲区适合于事先明确了缓冲区的最大容量的情形。扩展一个圆形缓冲区的容量,需要搬移其中的数据。因此一个缓冲区如果需要经常调整其容量,用链表实现更为合适。
  写操作覆盖圆形缓冲区中未被处理的数据在某些情况下是允许的。特别是在多媒体处理时。例如,音频的生产者可以覆盖掉声卡尚未来得及处理的音频数据。

环形缓冲区的工作过程

  一个圆形缓冲区最初为空并有预定的长度。例如,这是一个具有七个元素空间的圆形缓冲区,其中底部的单线与箭头表示“头尾相接”形成一个圆形地址空间:

▲环形缓冲区

  假定1被写入缓冲区中部(对于圆形缓冲区来说,最初的写入位置在哪里是无关紧要的):

▲环形缓冲区初次写入数据

  再写入2个元素,分别是2 & 3 — 被追加在1之后:

▲环形缓冲区再次写入数据

  如果两个元素被处理,那么是缓冲区中最老的两个元素被移除。在本例中,1 & 2被移除,缓冲区中只剩下3:

▲环形缓冲区移除数据

  如果缓冲区中有7个元素,则是满的:

▲环形缓冲区满员

  如果缓冲区是满的,又要写入新的数据,一种策略是覆盖掉最老的数据。此例中,2个新数据— A & B — 写入,覆盖了3 & 4:

▲环形缓冲区覆盖写入数据

  也可以采取其他策略,禁止覆盖缓冲区的数据,采取返回一个错误码或者抛出异常。
  最终,如果从缓冲区中移除2个数据,不是3 & 4 而是 5 & 6 。因为 A & B 已经覆盖了3 & 4:

▲环形缓冲区覆盖后移除数据

环形缓冲区工作机制

由于计算机内存是线性地址空间,因此圆形缓冲区需要特别的设计才可以从逻辑上实现。
读指针与写指针
一般的,圆形缓冲区需要4个指针:

  • 在内存中实际开始位置;
  • 在内存中实际结束位置,也可以用缓冲区长度代替;(对应BUFFER_LEN)
  • 存储在缓冲区中的有效数据的开始位置(读指针);(对应g_iRead)
  • 存储在缓冲区中的有效数据的结尾位置(写指针)。(对应g_iWrite)

区分缓冲区满或者空(满指缓冲区中充满了新的数据/未读的数据,空指缓冲区中没有新的数据)
缓冲区是满、或是空,都有可能出现读指针与写指针指向同一位置:

▲读指针与写指针指向同一位置

有多种策略用于检测缓冲区是满、或是空:

  1. 总是保持一个存储单元为空
      缓冲区中总是有一个存储单元保持未使用状态。缓冲区最多存入(size - 1) 个数据。如果读写指针指向同一位置,则缓冲区为空。如果写指针位于读指针的相邻后一个位置,则缓冲区为满。这种策略的优点是简单、鲁棒;缺点是语义上实际可存数据量与缓冲区容量不一致,测试缓冲区是否满需要做取余数计算。
  2. 使用数据计数
      这种策略不使用显式的写指针,而是保持着缓冲区内存储的数据的计数。因此测试缓冲区是空是满非常简单;对性能影响可以忽略。缺点是读写操作都需要修改这个存储数据计数,对于多线程访问缓冲区需要并发控制。
  3. 镜像指示位
      缓冲区的长度如果是n,逻辑地址空间则为0至n-1;那么,规定n至2n-1为镜像逻辑地址空间。本策略规定读写指针的地址空间为0至2n-1,其中低半部分对应于常规的逻辑地址空间,高半部分对应于镜像逻辑地址空间。当指针值大于等于2n时,使其折返(wrapped)到ptr-2n。使用一位表示写指针或读指针是否进入了虚拟的镜像存储区:置位表示进入,不置位表示没进入还在基本存储区。

▲带有镜像指示位的环形缓冲区

  在读写指针的值相同情况下,如果二者的指示位相同,说明缓冲区为空如果二者的指示位不同,说明缓冲区为满。这种方法优点是测试缓冲区满/空很简单;不需要做取余数操作;读写线程可以分别设计专用算法策略,能实现精致的并发控制。 缺点是读写指针各需要额外的一位作为指示位。
  如果缓冲区长度是2的幂,则本方法可以省略镜像指示位。如果读写指针的值相等,则缓冲区为空;如果读写指针相差n,则缓冲区为满,这可以用条件表达式(写指针 == (读指针 异或 缓冲区长度))来判断。

  1. 读/写计数
      用两个有符号整型变量分别保存写入、读出缓冲区的数据数量。其差值就是缓冲区中尚未被处理的有效数据的数量。这种方法的优点是读线程、写线程互不干扰;缺点是需要额外两个变量。
  2. 记录最后的操作
      使用一位记录最后一次操作是读还是写。读写指针值相等情况下,如果最后一次操作为写入,那么缓冲区是满的;如果最后一次操作为读出,那么缓冲区是空。 这种策略的缺点是读写操作共享一个标志位,多线程时需要并发控制。

程序分析

input_manage.c

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>static PInputDevice g_InputDevs  = NULL;static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;/* start of 实现环形buffer */
#define BUFFER_LEN 20
static int g_iRead  = 0;
static int g_iWrite = 0;
static InputEvent g_atInputEvents[BUFFER_LEN];/* 环形缓冲区满返回1 否则返回0 */
static int isInputBufferFull(void)
{return (g_iRead == ((g_iWrite + 1) % BUFFER_LEN));
}/* 环形缓冲区空返回1 否则返回0 */
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;}
}/* 从环形缓冲区获取事件信息 成功获得信息返回1 否则返回0*/
static int GetInputEventFromBuffer(PInputEvent ptInputEvent)
{if (!isInputBufferEmpty())//如果环形缓冲区不为空{*ptInputEvent = g_atInputEvents[g_iRead];g_iRead = (g_iRead + 1) % BUFFER_LEN;return 1;}else{return 0;}
}/* end of 实现环形buffer */void RegisterInputDevice(PInputDevice ptInputDev)
{ptInputDev->ptNext = g_InputDevs;g_InputDevs = ptInputDev;
}void InputInit(void)
{/* regiseter touchscreen */extern void TouchscreenRegister(void);TouchscreenRegister();/* regiseter netinput */extern void NetInp utRegister(void);NetInputRegister();
}/* 设备子线程函数 */
static void *input_recv_thread_func (void *data)
{PInputDevice ptInputDev = (PInputDevice)data;InputEvent tEvent;int ret;while (1){/* 读数据 *///这里使用的是设备结构体中的GetInputEvent函数,并非下面的GetInputEvent函数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;
}void IntpuDeviceInit(void)
{int ret;pthread_t tid;/* for each inputdevice, init, pthread_create */PInputDevice ptTmp = g_InputDevs;while (ptTmp){/* init device */ret = ptTmp->DeviceInit();/* pthread create */if (!ret){ret = pthread_create(&tid, NULL, input_recv_thread_func, ptTmp);}ptTmp= ptTmp->ptNext;}
}/* 从环形缓冲区中获取事件信息 成功返回0 失败返回-1*/
int GetInputEvent(PT_InputEvent ptInputEvent)
{InputEvent tEvent;int ret;/* 无数据则休眠 */pthread_mutex_lock(&g_tMutex);if (GetInputEventFromBuffer(&tEvent)){*ptInputEvent = tEvent;pthread_mutex_unlock(&g_tMutex);return 0;}else{/* 休眠等待 等待设备线程唤醒 */pthread_cond_wait(&g_tConVar, &g_tMutex);  if (GetInputEventFromBuffer(&tEvent)){ret = 0;}else{ret = -1;}pthread_mutex_unlock(&g_tMutex);        }return ret;}

2.5、测试程序

#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>#include <input_manager.h>int main(int argc, char **argv)
{int ret;InputEvent event;/* 设备注册、设备初始化、设备线程创建 */InputInit();IntpuDeviceInit();while (1){printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);ret = GetInputEvent(&event);printf("%s %s %d, ret = %d\n", __FILE__, __FUNCTION__, __LINE__, ret);if (ret) {printf("GetInputEvent err!\n");return -1;}else{printf("%s %s %d, event.iType = %d\n", __FILE__, __FUNCTION__, __LINE__, event.iType );if (event.iType == INPUT_TYPE_TOUCH)//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)//web设备{printf("Type      : %d\n", event.iType);printf("str       : %s\n", event.str);}}}return 0;
}

3、文字系统

【嵌入式Linux】嵌入式Linux应用开发基础知识之Framebuffer应用编程和字符汉字显示

▲程序分层

3.1、数据结构抽象

▲字形指标

font_manage.h:

#ifndef _FONT_MANAGER_H
#define _FONT_MANAGER_H#ifndef NULL
#define NULL (void *)0
#endif/*    描述一个文字的位图
*   iLeftUpX、iLeftUpY:左上角(x,y)坐标
*   iWidth、iRows:宽度、行数
*   iCurOriginX、iCurOriginY:当前字符的基点(x,y)坐标
*   iNextOriginX、iNextOriginY:下一个字符的基点(x,y)坐标
*   pucBuffer:位图指针
*/
typedef struct FontBitMap {int iLeftUpX;int iLeftUpY;int iWidth;int iRows;int iCurOriginX;int iCurOriginY;int iNextOriginX;int iNextOriginY;unsigned char *pucBuffer;
}FontBitMap, *PFontBitMap;/*    字库的操作结构体name:使用freetype还是点阵FontInit:初始化函数SetFontSize:设置字体大小GetFontBitMap:获得字符的位图ptNext:下一个字库的操作结构体
*/
typedef struct FontOpr {char *name;int (*FontInit)(char *aFineName);int (*SetFontSize)(int iFontSize);int (*GetFontBitMap)(unsigned int dwCode, PFontBitMap ptFontBitMap);struct FontOpr *ptNext;
}FontOpr, *PFontOpr;#endif

3.2、实现Freetype代码

freetype.c

#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 <math.h>
#include <wchar.h>
#include <sys/ioctl.h>
#include <font_manager.h>#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_Hstatic FT_Face g_tFace;              //矢量字体文字
static int g_iDefaultFontSize = 12;    //默认字体大小/* 初始化FreeType 创建字形结构体 */
static int FreeTypeFontInit(char *aFineName)
{FT_Library    library;int error;error = FT_Init_FreeType( &library );                 /* initialize library */    if (error){printf("FT_Init_FreeType err\n");return -1;}error = FT_New_Face(library, aFineName, 0, &g_tFace ); /* create face object */if (error){printf("FT_New_Face err\n");return -1;}FT_Set_Pixel_Sizes(g_tFace, g_iDefaultFontSize, 0);return 0;
}/* 设置字体大小 */
static int FreeTypeSetFontSize(int iFontSize)
{FT_Set_Pixel_Sizes(g_tFace, iFontSize, 0);return 0;
}/* 根据编码值dwCode获得位图保存在ptFontBitMap指向的结构体中 */
static int FreeTypeGetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
{int error;FT_Vector pen;                       //对应originFT_Glyph  glyph;                  //用来保存字体文件中保存有字符的原始关键点信息FT_GlyphSlot slot = g_tFace->glyph; //字形槽,用来保存字符处理后的结果 glyph->bitmap 中包含了中文位图信息pen.x = ptFontBitMap->iCurOriginX * 64; /* 单位: 1/64像素 */pen.y = ptFontBitMap->iCurOriginY * 64; /* 单位: 1/64像素 *//* 转换:transformation */FT_Set_Transform(g_tFace, 0, &pen);/* 加载位图: load glyph image into the slot (erase previous one) */error = FT_Load_Char(g_tFace, dwCode, FT_LOAD_RENDER);if (error){printf("FT_Load_Char error\n");return -1;}/* 将加载出来的位图数据保存到ptFontBitMap */ptFontBitMap->pucBuffer = slot->bitmap.buffer;/* 位图左上角坐标x值 */ptFontBitMap->iLeftUpX = slot->bitmap_left;/*  *   iCurOriginY是LCD坐标系  bitmap_top是笛卡尔坐标系 iLeftUpY是LCD坐标系*  iLeftUpY = iCurOriginY - delta*    delta = bitmap_top - iCurOriginY       //此处有疑问*    iLeftUpY = 2 * iCurOriginY - bitmap_top*/ptFontBitMap->iLeftUpY = ptFontBitMap->iCurOriginY*2 - slot->bitmap_top;/* 位图的总宽度即有多少列像素点 */ptFontBitMap->iWidth   = slot->bitmap.width;/* 位图的总高度即有多少行 */ptFontBitMap->iRows    = slot->bitmap.rows;/* 计算下一个文字位图的基点 */ptFontBitMap->iNextOriginX = ptFontBitMap->iCurOriginX + slot->advance.x / 64;ptFontBitMap->iNextOriginY = ptFontBitMap->iCurOriginY;return 0;
}/* 初始化freetype字库的操作结构体 */
static FontOpr g_tFreetypeOpr = {.name          = "freetype",.FontInit      = FreeTypeFontInit,.SetFontSize   = FreeTypeSetFontSize,.GetFontBitMap = FreeTypeGetFontBitMap,
};

3.3、文字管理

font_manage.c:管理文字库设备

#include <font_manager.h>static PFontOpr g_ptFonts = NULL;            //字库设备链表头
static PFontOpr g_ptDefaulFontOpr = NULL;  //默认字库设备指针/* 将字库的操作结构体加入链表 */
void RegisterFont(PFontOpr ptFontOpr)
{ptFontOpr->ptNext = g_ptFonts;g_ptFonts = ptFontOpr;
}/* 承上启下 */
void FontsRegister(void)
{extern void FreetypeRegister(void);FreetypeRegister();
}/* 把下面的各个字体注册进来 */
int SelectAndInitFont(char *aFontOprName, char *aFontFileName)
{PFontOpr ptTmp = g_ptFonts;/* 在链表中寻找指定名称的设备将其设定为默认设备 */while (ptTmp){if (strcmp(ptTmp->name, aFontOprName) == 0)break;ptTmp = ptTmp->ptNext;}if (!ptTmp)return -1;g_ptDefaulFontOpr = ptTmp;return ptTmp->FontInit(aFontFileName);
}/* 上层可以选择某个字体模块 */
int SetFontSize(int iFontSize)
{return g_ptDefaulFontOpr->SetFontSize(iFontSize);
}/* 上层可以得到某个字符的位图 */
int GetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
{return g_ptDefaulFontOpr->GetFontBitMap(dwCode, ptFontBitMap);
}

3.5、单元测试

common.h:包含通用结构体及宏定义声明

#ifndef _COMMON_H
#define _COMMON_H#ifndef NULL
#define NULL (void *)0
#endif/*
*   区域信息
*   iLeftUpX iLeftUpY:里面包含了绘制的起始坐标
*   iWidth iHeigh:字符的长宽信息
*/
typedef struct Region {int iLeftUpX;int iLeftUpY;int iWidth;int iHeigh;
}Region, *PRegion;#endif

font_manager.h中修改struct FontBitMapRegion结构体代替原来保存区域信息的变量

typedef struct FontBitMap {int iLeftUpX;int iLeftUpY;int iWidth;int iRows;int iCurOriginX;int iCurOriginY;int iNextOriginX;int iNextOriginY;unsigned char *pucBuffer;
}FontBitMap, *PFontBitMap;												

【嵌入式Linux】嵌入式项目实战之七步从零编写带GUI的应用之显示系统、输入系统、文字系统相关推荐

  1. C语言到嵌入式Linux开发项目指导

    C语言到嵌入式Linux开发项目指导 第一阶段C语言 1.常量与变量,数据类型,数据类型转换,数据输入与输出: 2.C语言运算符,C语言操作符,C语言表达式,表达式优先级: 3.C语言流程控制,分支, ...

  2. 零基础学习嵌入式入门以及项目实战开发【手把手教+国内独家+原创】

    零基础学习嵌入式入门以及项目实战开发[手把手教+国内独家+原创] 独家拥有,绝对经典                            创 科 之 龙 嵌入式开发经典系列教程 [第一期] 主讲人: ...

  3. 嵌入式linux驱动开发实战教程,嵌入式Linux驱动开发实战视频教程

    嵌入式Linux驱动开发实战教程(内核驱动.看门狗技术.触摸屏.视频采集系统) 适合人群:高级 课时数量:109课时 用到技术:嵌入式 Linux 涉及项目:驱动开发.看门狗技术.触摸屏.视频采集 咨 ...

  4. 【创科之龙】零基础学习嵌入式开发以及项目实战开发【第二期视频】

    [创科之龙]零基础学习嵌入式开发以及项目实战开发[学习交流零基础火热进行ing] 大家好,我是aiku,上期的项目学习资料在电子发烧友论坛上分享,大家觉得都很好. 在这里我首先要感谢电子发烧友给我们的 ...

  5. 嵌入式Linux小项目之图片编解码播放器学习导读

      首先欢迎大家阅读本篇文章,在这里我将会为大家简要介绍一下图片编解码播放器系列文章的学习路线.   该小项目共有七篇文章,分别为<嵌入式Linux小项目之图片编解码播放器(1-7)>,这 ...

  6. 嵌入式入门和项目实战开发【菜鸟内心深处最真实感想篇】

    菜鸟 参加嵌入式入门和项目实战开发后[终于拿到年到10万了]--千真万确!!绝无虚言!!!        这里述说我菜鸟学习嵌入式的内心深处最真实感受!!![希望大家不要拍砖]我只是我的学习嵌入式过程 ...

  7. 【项目实战课】从零掌握安卓端Pytorch原生深度学习模型部署

    欢迎大家来到我们的项目实战课,本期内容是<从零掌握安卓端Pytorch原生深度学习模型部署>.所谓项目课,就是以简单的原理回顾+详细的项目实战的模式,针对具体的某一个主题,进行代码级的实战 ...

  8. 嵌入式Linux小项目之图片编解码播放器(1)

    目录 前言 一.项目展示与整体规划 二.环境搭建和基础确认 三.开始动手写代码 四.framebuffer基本操作代码 五.图片显示原理和实践 前言 首先非常感谢大家来阅读我的文章,在这里特别感谢朱老 ...

  9. 嵌入式 linux 开源项目

    http://www.linaro.org/ Linaro,一间非营利性质的开放源代码软件工程公司,主要的目标在于开发不同半导体公司系统单芯片(SoC)平台的共通软件,以促进消费者及厂商的福祉.针对于 ...

最新文章

  1. ESP8266 D1-UNO-R3开发板的初步测试
  2. juniper路由器主备路由引擎主机名配置
  3. 源哥每日一题第十三弹 百练4124:海贼王之伟大航路 状压dp
  4. iPhone开发 调用阿asp.net程序的webservice
  5. Dapr 客户端 搭配 WebApiClientCore 玩耍服务调用
  6. 微信小程序实现文字跑马灯
  7. 约瑟夫环问题2(顺序表+链表求解)
  8. LeetCode 169 Majority Element 解题报告
  9. Spotfire使用经验-自定义饼图中显示的数据量(Top N分析,排名分析)
  10. 冒泡排序——《图解算法》
  11. 对与连连看求解算法的研究
  12. 独立院校转设,高考新生何去何从|转设对新生有哪些影响
  13. 心肌损伤的标志物题库【1】
  14. 阿里云弹性云桌面安装失败问题解决记录(.net framework 4.6.2 or later:Error Code: 12029)
  15. 鸿蒙渊主线任务,《天下3》更新公告(版本2.0.848)
  16. 艺赛旗RPA 网页处理系列(三):网页检查 / 审查小技巧
  17. mc服务器修改别人领地权限,我的世界领地权限设置 领地指令大全
  18. 山水印|竹林野茶:在这个临近八月中秋的九月,再喝桂花香茶
  19. Python——SMTP发送邮件(发送不同格式、附件)
  20. RHCSA操作第四次作业

热门文章

  1. pandas计算一个维度中的所有数值占总价值的占比
  2. 呆萌的图模型学习——基本概念(一)
  3. 解决Numpy 报错 ValueError: zero-size array to reduction operation maximum which has no identity
  4. web虚拟服务器4核32g,Web服务器配备四核的优势
  5. kubernetes ConfigMap和Secret:配置应用程序
  6. Intellij插件之JRebel
  7. quartz集群报错but has failed to stop it. This is very likely to create a memory leak.
  8. 四种转换方式:自动,强制,Parse,Convert
  9. 使用log4net记录日志到数据库(含有自定义属性)
  10. Linux内存之Cache