【IMX6ULL项目】IMX6ULL下Linux实现产测工具框架

电子产品量产测试与烧写工具。这是一套软件,用在我们的实际生产中, 有如下特点:

1.简单易用:

         把这套软件烧写在 SD 卡上,插到 IMX6ULL 板子里并启动,它就会自动测试各个模块、烧写 EMMC 系统。

        工人只要按照说明接入几个模块,就可以完成整个测试、烧写过程。

         测试结果一目了然:等 LCD 上所有模块的图标都变绿时,就表示测试通过。

2.软件可配置、易扩展:

        通过配置文件添加测试项,可以添加不限个数的测试项。

        每个测试项有自己的测试程序,测试通过后把结果发送给 GUI 即可。各个测试程序互不影响。

3. 纯 C 语言编程

        工具设计的界面,它可以一边测试一边烧写:

上图中的 led、speaker 按钮,可以点击: 

1.当你看到 LED 闪烁时,就点击 led 按钮,它变成绿色表示测试通过;

2. 当你从耳机里听到声音时,就点击 speaker 按钮,它变成绿色表示测试通过。

        其他按钮无法点击,接上对应模块后会自动测试,测试通过时图标就会变绿。

        上图中的蓝色按钮表示烧写 EMMC 的进度,烧写成功后它也会变绿。

        LCD 上所有图标都变绿时,就表示测试、烧写全部完成;某项保持红色的话,就表示对应模块测试失败。

目录:

目录:

一、总设计思路:

二、显示系统:

*2.1 DisplayInit();         

2.2 framebuffer(); 

2.3 RegisterDisplay(); 

 && 2.4 显示管理器框架

​编辑

*2.5 SelectDefaultDisplay(char *name)  

*2.6 InitDefaultDisplay(void)

 2.7 DeviceInit(void);

2.8 FbGetBuffer(PDispBuff ptDispBuff)

*2.9 PutPixel(int x, int y, unsigned int dwColor)

*2.10 FlushDisplayRegion

2.11 测试代码 

详细介绍:Linux基础项目开发1:量产工具——显示系统(二)_linux入门项目-CSDN博客

三、输入系统:

*3.1  InputEvent   数据本身结构体

*3.2 InputDevice   输入设备结构体

*3.3 InputDevice结构体实例(Touchscreen)

3.4 TouchscreenGetInputEvent(PInputEvent ptInputEvent)

3.5 TouchscreenDeviceInit(void)

3.6 TouchscreenDeviceExit(void)

3.9 显示屏的测试函数

*3.10 InputDevice结构体实例(net)

3.11 NetinputGetInputEvent(PInputEvent ptInputEvent)

3.12 NetinputDeviceInit(void)

3.13 NetinputDeviceExit(void)

3.14 网络测试函数

3.15 client.c

&& 3.16  输入管理器框架 

* 3.17 InputInit(void)

 3.18 TouchscreenRegister(void)

3.19 NetInputRegister(void)

 *3.20 RegisterInputDevice(PInputDevice ptInputDev)

*3.21 IntpuDeviceInit(void)

 3.22 *input_recv_thread_func(void *data)

*3.23 GetInputEvent(PT_InputEvent ptInputEvent)

3.24 环形缓冲区

3.25 测试代码

详细介绍:Linux基础项目开发1:量产工具——输入系统(三)-CSDN博客

四、 文字系统:

*4.1 FontBitMap

  Region 

*4.2 FontOpr

*4.3 FontOpr g_tFreetypeOpr

4.4 FreeTypeFontInit(char *aFineName)

4.5 FreeTypeSetFontSize(int iFontSize)

4.6 FreeTypeGetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)

4.7 FreetypeRegister(void)

&& 4.8 字符管理器框架

 4.9 FontsRegister(void)

4.10 SelectAndInitFont(char *aFontOprName, char *aFontFileName)

4.10 SetFontSize(int iFontSize)

4.11 GetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)

*4.12 DrawFontBitMap(PFontBitMap ptFontBitMap, unsigned int dwColor)

*4.13 测试代码:

详细介绍:Linux基础项目开发1:量产工具——文字系统(四)-CSDN博客

五、UI系统:

*5.1 Button

*5.2 InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed)

5.3 DrawRegion(PRegion ptRegion, unsigned int dwColor)

5.4 DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor)

* 5.5 DefaultOnDraw(struct Button *ptButton, PDispBuff ptDispBuff)

*5.6  DefaultOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)

5.7 测试代码:

详细介绍:Linux基础项目开发1:量产工具——UI系统(五)_linux ui界面开发-CSDN博客

六、页面系统:

*6.1 PageAction

&& 6.2 页面管理器框架

 6.3 PageRegister(PPageAction ptPageAction)

6.4 PPageAction Page(char *name)

6.5 PageAction MainPage

6.6 测试代码:

详细介绍:Linux基础项目开发1:量产工具——页面系统(六)-CSDN博客

七、业务系统

7.1 业务系统流程图

7.2 主页面流程图: 

&&& 7.3 抽象出配置文件结构体(ItemCfg) 

7.4 ParseConfigFile(char *strFileName)

7.4 GetItemCfgCount(void)

7.5 GetItemCfgByIndex(int index) 

7.6 GetItemCfgByName(char *name) 

*7.7 生成界面

7.8 *MainPageRun(void *pParams)

7.9 GenerateButtons(void)

*7.10 处理输入事件:GetButtonByInputEvent(PInputEvent ptInputEvent)

7.11 isTouchPointInRegion(int iX, int iY, PRegion ptRegion)

7.12 GetButtonByName(char *name)

7.13 MainPageOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)

 详细介绍:Linux基础项目开发1:量产工具——业务系统(七)_量产工具开发-CSDN博客


一、总设计思路:

二、显示系统:

*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(&region, 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)函数,用于注册网络设备到输入管理器

/* 函数,用于注册网络输入设备到输入管理器 */
void NetInputRegister(void)
{
    RegisterInputDevice(&g_tNetinputDev); /* 调用输入管理器的注册函数,将网络输入设备注册到输入管理器中 */
}

将以下结构体注册到输入管理器

/* 定义一个InputDevice结构体实例,表示网络输入设备 */
static InputDevice g_tNetinputDev ={
    .name = "netinput",
    .GetInputEvent  = NetinputGetInputEvent,
    .DeviceInit     = NetinputDeviceInit,
    .DeviceExit     = NetinputDeviceExit,
};

 *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; // 正常退出程序	
}

详细介绍:Linux基础项目开发1:量产工具——输入系统(三)-CSDN博客

四、 文字系统:

*4.1 FontBitMap

font_manager.h描述一个文字的位图, 定义字体位图结构体,用于存储字体的位图信息

// 定义字体位图结构体,用于存储字体的位图信息
typedef struct FontBitMap {
    int iLeftUpX; // 字体位图左上角的X坐标
    int iLeftUpY; // 字体位图左上角的Y坐标
    int iWidth;   // 字体位图的宽度
    int iRows;    // 字体位图的行数
    int iCurOriginX; // 当前字符的原点X坐标
    int iCurOriginY; // 当前字符的原点Y坐标
    int iNextOriginX; // 下一个字符的原点X坐标
    int iNextOriginY; // 下一个字符的原点Y坐标
    unsigned char *pucBuffer; // 指向字体位图数据的指针
} FontBitMap, *PFontBitMap; // FontBitMap是结构体类型,PFontBitMap是指向该结构体的指针类型

  Region 

因为显示系统和文字系统都需要用到这个区域结构体,所以我们直接把它拿出来定义成一个公共的头文件common.h


// 定义矩形区域结构体
typedef struct Region {
	int iLeftUpX; // 区域左上角的X坐标
	int iLeftUpY; // 区域左上角的Y坐标
	int iWidth;   // 区域的宽度
	int iHeigh;   // 区域的高度
}Region, *PRegion; // 定义矩形区域及其指针类型

*4.2 FontOpr

font_manager.h定义字体操作结构体,用于管理不同字体的操作函数

// 定义字体操作结构体,用于管理不同字体的操作函数
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; // FontOpr是结构体类型,PFontOpr是指向该结构体的指针类型

*4.3 FontOpr g_tFreetypeOpr

freetype.c中定义一个字体操作结构体,包含字体名字、字体初始化、设置字体大小、获取字体位图等函数指针


// 定义一个字体操作结构体,包含字体初始化、设置字体大小、获取字体位图等函数指针
static FontOpr g_tFreetypeOpr = {
	.name          = "freetype",
	.FontInit      = FreeTypeFontInit,
	.SetFontSize   = FreeTypeSetFontSize,
	.GetFontBitMap = FreeTypeGetFontBitMap,
};

4.4 FreeTypeFontInit(char *aFineName)

freetype.c中初始化FreeType字体库

// 初始化FreeType字体库
static int FreeTypeFontInit(char *aFineName)
{
    FT_Library    library;
    int error;
 
    // 初始化FreeType库
    error = FT_Init_FreeType( &library );                 
	if (error)
	{
		printf("FT_Init_FreeType err\n");
		return -1;
	}
	
    // 从指定文件创建字体面
    error = FT_New_Face(library, aFineName, 0, &g_tFace ); 
	if (error)
	{
		printf("FT_New_Face err\n");
		return -1;
	}
 
    // 设置字体大小
    FT_Set_Pixel_Sizes(g_tFace, g_iDefaultFontSize, 0);
 
	return 0;
}

4.5 FreeTypeSetFontSize(int iFontSize)

freetype.c中设置字体大小


// 设置字体大小
static int FreeTypeSetFontSize(int iFontSize)
{
    // 设置字体大小
    FT_Set_Pixel_Sizes(g_tFace, iFontSize, 0);
	return 0;
}

4.6 FreeTypeGetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)

freetype.c中获取字体位图

// 获取字体位图
static int FreeTypeGetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
{
	int error;
    FT_Vector pen;
    FT_Glyph  glyph;
    
 
    // 设置当前光标位置,单位为1/64像素
    pen.x = ptFontBitMap->iCurOriginX * 64; 
    pen.y = ptFontBitMap->iCurOriginY * 64; 
 
    // 设置变换矩阵
    FT_Set_Transform(g_tFace, 0, &pen);
 
    // 加载字符位图
    error = FT_Load_Char(g_tFace, dwCode, FT_LOAD_RENDER);
	if (error)
	{
		printf("FT_Load_Char error\n");
		return -1;
	}
 
    // 获取位图缓冲区
	ptFontBitMap->pucBuffer = slot->bitmap.buffer;
	
    // 设置字体位图的区域信息
	ptFontBitMap->tRegion.iLeftUpX = slot->bitmap_left;
	ptFontBitMap->tRegion.iLeftUpY = ptFontBitMap->iCurOriginY*2 - slot->bitmap_top;
	ptFontBitMap->tRegion.iWidth   = slot->bitmap.width;
	ptFontBitMap->tRegion.iHeigh   = slot->bitmap.rows;
	ptFontBitMap->iNextOriginX = ptFontBitMap->iCurOriginX + slot->advance.x / 64;
	ptFontBitMap->iNextOriginY = ptFontBitMap->iCurOriginY;
 
	return 0;
}

4.7 FreetypeRegister(void)

freetype.c中注册FreeType字体操作结构体 


// 注册FreeType字体操作结构体
void FreetypeRegister(void)
{
	RegisterFont(&g_tFreetypeOpr);
}

&& 4.8 字符管理器框架

我们可能要用到的字体有多种,那么怎么选择用哪个字符呢,所以我们要编写一个程序管理多种字符。

 4.9 FontsRegister(void)

font_manager.c注册字体操作结构体,这里示例了注册FreeType字体

// 注册字体操作结构体,这里示例了注册FreeType字体
void FontsRegister(void)
{
	extern void FreetypeRegister(void);
	FreetypeRegister();
}

4.10 SelectAndInitFont(char *aFontOprName, char *aFontFileName)

font_manager.c选择并初始化一个字体操作结构体


// 选择并初始化一个字体操作结构体
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);
}

4.10 SetFontSize(int iFontSize)

font_manager.c设置当前默认字体的字体大小


// 设置当前默认字体的字体大小
int SetFontSize(int iFontSize)
{
	return g_ptDefaulFontOpr->SetFontSize(iFontSize);
}

4.11 GetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)

font_manager.c获取当前默认字体的字符位图

// 获取当前默认字体的字符位图
int GetFontBitMap(unsigned int dwCode, PFontBitMap ptFontBitMap)
{
    return g_ptDefaulFontOpr->GetFontBitMap(dwCode, ptFontBitMap);
}

*4.12 DrawFontBitMap(PFontBitMap ptFontBitMap, unsigned int dwColor)

在LCD上显示出来,在disp_manager.c中还要 实现一个绘制函数:DrawFontBitMap

void DrawFontBitMap(PFontBitMap ptFontBitMap, unsigned int dwColor)
{
    // 定义并初始化各种变量
    int i, j, p, q;
	// 获取绘制区域的左上角X坐标
	int x = ptFontBitMap->tRegion.iLeftUpX;
	// 获取绘制区域的左上角Y坐标
	int y = ptFontBitMap->tRegion.iLeftUpY;
    // 计算绘制区域的右下角X坐标
    int x_max = x + ptFontBitMap->tRegion.iWidth;
    // 计算绘制区域的右下角Y坐标
    int y_max = y + ptFontBitMap->tRegion.iHeigh;
	// 获取区域宽度
	int width = ptFontBitMap->tRegion.iWidth;
	// 获取位图缓冲区
	unsigned char *buffer = ptFontBitMap->pucBuffer;
 
    // 遍历绘制区域中的每个像素
    for ( j = y, q = 0; j < y_max; j++, q++ )
    {
        for ( i = x, p = 0; i < x_max; i++, p++ )
        {
            // 如果像素位置超出屏幕范围,忽略该像素
            if ( i < 0      || j < 0       ||
                i >= g_tDispBuff.iXres || j >= g_tDispBuff.iYres )
            continue;
 
            // 如果位图缓冲区中该像素点有数据(即需要绘制的点),则用指定颜色在屏幕上绘制该像素
            if (buffer[q * width + p])
	            PutPixel(i, j, dwColor);
        }
    }
}

*4.13 测试代码:

 
#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 <stdlib.h>
#include <font_manager.h>
#include <disp_manager.h>
 
#define FONTDATAMAX 4096
 
// 8x16 字体数据
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
    // 省略了点阵数据
};
 
// 在LCD指定位置上显示一个8*16的字符
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); // 黑色
            }
        }
    }
}
 
int main(int argc, char **argv)
{
    PDispBuff ptBuffer;
    int error;
 
    FontBitMap tFontBitMap;
    char *str= "Hello Linux";
    int i = 0;
    int lcd_x;
    int lcd_y;
    int font_size;
 
    // 检查输入参数个数
    if (argc != 5)
    {
        printf("Usage: %s <font_file> <lcd_x> <lcd_y> <font_size>\n", argv[0]);
        return -1;
    }
 
    lcd_x = strtol(argv[2], NULL, 0);
    lcd_y = strtol(argv[3], NULL, 0);
    font_size  = strtol(argv[4], NULL, 0);
 
    // 初始化显示设备
    DisplayInit();
    SelectDefaultDisplay("fb");
    InitDefaultDisplay();
    ptBuffer = GetDisplayBuffer();
 
    // 注册并初始化字体
    FontsRegister();
    error = SelectAndInitFont("freetype", argv[1]);
    if (error)
    {
        printf("SelectAndInitFont err\n");
        return -1;
    }
 
    // 设置字体大小
    SetFontSize(font_size);
 
    // 循环显示每个字符
    while (str[i])
    {
        // 获取字符位图
        tFontBitMap.iCurOriginX = lcd_x;
        tFontBitMap.iCurOriginY = lcd_y;
        error = GetFontBitMap(str[i], &tFontBitMap);
        if (error)
        {
            printf("SelectAndInitFont err\n");
            return -1;
        }
 
        // 绘制字符到缓冲区
        DrawFontBitMap(&tFontBitMap,0xff0000); // 红色
 
        // 刷新显示区域
        FlushDisplayRegion(&tFontBitMap.tRegion, ptBuffer);
 
        // 更新下一个字符的位置
        lcd_x = tFontBitMap.iNextOriginX;
        lcd_y = tFontBitMap.iNextOriginY;
        i++;
    }
 
    return 0;
}

详细介绍:Linux基础项目开发1:量产工具——文字系统(四)-CSDN博客

五、UI系统:

        所谓UI,就是User Interface(用户界面),有图像界面(GUI)等,我们的UI系统,就是构造各类GUI元素,比如按钮(目前只实现按钮)

*5.1 Button

ui.h定义Button结构体,包含按钮的名称、状态、区域、绘制函数和按下处理函数


// 定义两个函数指针类型,用于按钮的绘制和按下事件处理
typedef int (*ONDRAW_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff); // 绘制函数指针类型
typedef int (*ONPRESSED_FUNC)(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent); // 按下处理函数指针类型


// 定义Button结构体,包含按钮的名称、状态、区域、绘制函数和按下处理函数
typedef struct Button {
	char *name; // 按钮名称
	int status; // 按钮状态
	Region tRegion; // 按钮区域
	ONDRAW_FUNC OnDraw; // 绘制函数指针
	ONPRESSED_FUNC OnPressed; // 按下处理函数指针
}Button, *PButton; // Button是结构体类型,PButton是指向Button的指针类型

*5.2 InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed)

button.c初始化按钮的函数

// 初始化按钮的函数
void InitButton(PButton ptButton, char *name, PRegion ptRegion, ONDRAW_FUNC OnDraw, ONPRESSED_FUNC OnPressed)
{
	ptButton->status = 0; // 初始化状态为0,即未按下
	ptButton->name = name; // 设置按钮名称
	ptButton->tRegion = *ptRegion; // 设置按钮区域
	ptButton->OnDraw = OnDraw ? OnDraw : DefaultOnDraw; // 设置绘制函数,如果未提供,则使用默认绘制函数
	ptButton->OnPressed = OnPressed ? OnPressed : DefaultOnPressed; // 设置按下处理函数,如果未提供,则使用默认处理函数
}

5.3 DrawRegion(PRegion ptRegion, unsigned int dwColor)

disp_manager.c中将ptRegion这一部分区域绘制成dwColor的颜色

// 函数DrawRegion用于绘制一个指定颜色和区域的矩形
void DrawRegion(PRegion ptRegion, unsigned int dwColor)
{
	// 获取区域的左上角坐标
	int x = ptRegion->iLeftUpX;
	int y = ptRegion->iLeftUpY;
 
	// 获取区域的宽度和高度
	int width = ptRegion->iWidth;
	int heigh = ptRegion->iHeigh;
 
	// 使用两个嵌套的循环来遍历区域内的每一个像素
	int i, j;
	for (j = y; j < y + heigh; j++)
	{
		for (i = x; i < x + width; i++)
		{
			// 对于每个像素,调用PutPixel函数来设置像素的颜色
			PutPixel(i, j, dwColor);
		}
	}
}

5.4 DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor)

disp_manager.c中实现函数DrawTextInRegionCentral用于在指定区域内居中显示文本

// 函数DrawTextInRegionCentral用于在指定区域内居中显示文本
void DrawTextInRegionCentral(char *name, PRegion ptRegion, unsigned int dwColor)
{
	// 计算文本字符串的长度
	int n = strlen(name);
 
	// 计算每个字符的宽度,以便文本能够居中显示
	int iFontSize = ptRegion->iWidth / n / 2;
	FontBitMap tFontBitMap; // 字体位图结构体
 
	int iOriginX, iOriginY; // 字符绘制的起始坐标
	int i = 0; // 用于遍历字符串的索引
	int error; // 用于存储错误代码
 
	// 如果计算出的字体大小超过了区域的高度,则将字体大小设置为区域的高度
	if (iFontSize > ptRegion->iHeigh)
		iFontSize =  ptRegion->iHeigh;
 
	// 计算文本在区域内的起始X坐标,确保文本居中
	iOriginX = (ptRegion->iWidth - n * iFontSize)/2 + ptRegion->iLeftUpX;
	// 计算文本在区域内的起始Y坐标,确保文本居中
	iOriginY = (ptRegion->iHeigh - iFontSize)/2 + iFontSize + ptRegion->iLeftUpY;
 
	// 设置字体大小
	SetFontSize(iFontSize);
 
	// 遍历文本字符串中的每个字符
	while (name[i])
	{
		// 获取当前字符的字体位图
		tFontBitMap.iCurOriginX = iOriginX;
		tFontBitMap.iCurOriginY = iOriginY;
		error = GetFontBitMap(name[i], &tFontBitMap);
		if (error)
		{
			printf("SelectAndInitFont err\n");
			return;
		}
 
		// 在缓冲区上绘制当前字符的字体位图
		DrawFontBitMap(&tFontBitMap, dwColor);
 
		// 更新下一个字符的起始坐标
		iOriginX = tFontBitMap.iNextOriginX;
		iOriginY = tFontBitMap.iNextOriginY;
		i++;
	}
}

* 5.5 DefaultOnDraw(struct Button *ptButton, PDispBuff ptDispBuff)

button.c默认的绘制按钮的函数

// 默认的绘制按钮的函数
static int DefaultOnDraw(struct Button *ptButton, PDispBuff ptDispBuff)
{
	/* 绘制按钮的底色 */
	DrawRegion(&ptButton->tRegion, BUTTON_DEFAULT_COLOR);
 
	/* 在按钮的中央绘制文本 */
	DrawTextInRegionCentral(ptButton->name, &ptButton->tRegion, BUTTON_TEXT_COLOR);
 
	/* 将绘制的区域刷新到显示设备上,比如LCD或网页 */
	FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);
 
    return 0; // 返回0表示成功
}

*5.6  DefaultOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)

button.c默认的按钮按下处理函数

// 默认的按钮按下处理函数
static int DefaultOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)
{
	unsigned int dwColor = BUTTON_DEFAULT_COLOR; // 默认底色
	
	ptButton->status = !ptButton->status; // 切换按钮状态
	if (ptButton->status)
		dwColor = BUTTON_PRESSED_COLOR; // 如果按钮被按下,改变底色
 
	/* 绘制按钮的底色 */
	DrawRegion(&ptButton->tRegion, dwColor);
 
	/* 在按钮的中央绘制文本 */
	DrawTextInRegionCentral(ptButton->name, &ptButton->tRegion, BUTTON_TEXT_COLOR);
 
	/* 将绘制的区域刷新到显示设备上 */
	FlushDisplayRegion(&ptButton->tRegion, ptDispBuff);
}

以上在ui.h中定义按钮的默认颜色、按下时的颜色和文本颜色

#define BUTTON_DEFAULT_COLOR 0xff0000 // 默认颜色,通常是红色
#define BUTTON_PRESSED_COLOR 0x00ff00 // 按下时的颜色,通常是绿色
#define BUTTON_TEXT_COLOR    0x000000 // 文本颜色,通常是黑色

5.7 测试代码:

// 包含必要的系统头文件
#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 <stdlib.h>
 
// 包含自定义的显示管理器和字体管理器头文件
#include <disp_manager.h>
#include <font_manager.h>
#include <ui.h> // 假设这是一个包含GUI相关定义的头文件
 
// 主函数
int main(int argc, char **argv)
{
	// 定义显示缓冲区和错误代码的指针,以及按钮和区域的结构体
	PDispBuff ptBuffer;
	int error;
	Button tButton;
	Region tRegion;
 
	// 检查命令行参数是否正确
	if (argc != 2)
	{
		printf("Usage: %s <font_size>\n", argv[0]);
		return -1;
	}
		
	// 初始化显示
	DisplayInit();
 
	// 选择默认的显示操作接口,这里假设"fb"是一个帧缓冲设备
	SelectDefaultDisplay("fb");
 
	// 初始化默认的显示操作接口
	InitDefaultDisplay();
 
	// 获取显示缓冲区
	ptBuffer = GetDisplayBuffer();
 
	// 注册字体
	FontsRegister();
	
	// 选择并初始化字体,这里使用的是"freetype"字体,字体大小由命令行参数指定
	error = SelectAndInitFont("freetype", argv[1]);
	if (error)
	{
		printf("SelectAndInitFont err\n");
		return -1;
	}
 
	// 初始化区域的坐标和大小
	tRegion.iLeftUpX = 200;
	tRegion.iLeftUpY = 200;
	tRegion.iWidth   = 300;
	tRegion.iHeigh   = 100;
	
	// 初始化按钮,设置按钮的文本和区域,以及回调函数
	InitButton(&tButton, "test", &tRegion, NULL, NULL);
	// 绘制按钮
	tButton.OnDraw(&tButton, ptBuffer);
 
	// 进入主循环,当按钮被按下时执行相应的操作
	while (1)
	{
		tButton.OnPressed(&tButton, ptBuffer, NULL);
		// 暂停2秒
		sleep(2);
	}
	
	// 程序正常结束
	return 0;	
}

详细介绍:Linux基础项目开发1:量产工具——UI系统(五)_linux ui界面开发-CSDN博客

六、页面系统:

*6.1 PageAction

定义一个结构体类型 PageAction,用于表示页面动作

// 定义一个结构体类型 PageAction,用于表示页面动作
typedef struct PageAction {
    // 页面动作的名称,是一个字符串指针
    char *name;
    // 指向执行页面动作的函数指针,该函数接受一个void指针参数
    void (*Run)(void *pParams);
    // 指向下一个PageAction结构体的指针,用于链表结构
    struct PageAction *ptNext;
} PageAction, *PPageAction;

&& 6.2 页面管理器框架

页面管理器用来管理页面,只需要实现2个函数:

        1. PagesRegister : 把多个页面注册进链表

        2. Page(name) :取出某个页面

 6.3 PageRegister(PPageAction ptPageAction)

page_manager.c 中实现页面动作注册函数,将新的页面动作添加到链表的头部

// 声明一个静态的全局变量,用于存储页面动作链表的头指针
static PPageAction g_ptPages = NULL;


// 实现页面动作注册函数,将新的页面动作添加到链表的头部
void PageRegister(PPageAction ptPageAction)
{
    // 将新动作的下一个指针指向当前链表的头
    ptPageAction->ptNext = g_ptPages;
    // 更新链表头指针,使其指向新添加的动作
    g_ptPages = ptPageAction;
}

6.4 PPageAction Page(char *name)

page_manager.c 中实现根据名称查找页面动作的函数



// 实现根据名称查找页面动作的函数
PPageAction Page(char *name)
{
    // 从链表头开始遍历
    PPageAction ptTmp = g_ptPages;
 
    // 遍历链表直到找到匹配的名称或者遍历完整个链表
    while (ptTmp)
    {
        // 使用strcmp函数比较名称是否相等
        if (strcmp(name, ptTmp->name) == 0)
            // 如果找到匹配的名称,返回对应的页面动作指针
            return ptTmp;
        // 移动到下一个页面动作
        ptTmp = ptTmp->ptNext;
    }
 
    // 如果没有找到匹配的名称,返回NULL
    return NULL;
}

6.5 PageAction MainPage

main_page.c中创建一个PageAction MainPage,定义一个静态的PageAction结构体变量,表示主页面动作

// 包含页面管理器头文件,这个文件中声明了页面动作相关的结构体和函数
#include <page_manager.h>
 
// 包含标准输入输出头文件,提供了printf等函数
#include <stdio.h>
 
// 定义一个静态函数,用于执行主页面动作
static void MainPageRun(void *pParams)
{
    // 使用printf打印文件名、函数名和当前行号,用于调试信息
    printf("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}
 
// 定义一个静态的PageAction结构体变量,表示主页面动作
static PageAction g_tMainPage = {
    // 设置页面动作的名称为"main"
    .name = "main",
    // 设置执行页面动作的函数为MainPageRun
    .Run  = MainPageRun,
};
 
// 定义一个函数,用于注册主页面动作
void MainPageRegister(void)
{
    // 调用页面管理器的注册函数,将主页面动作添加到页面动作链表中
    PageRegister(&g_tMainPage);
}

6.6 测试代码:

// 包含内存映射相关的头文件
#include <sys/mman.h>
 
// 包含系统类型定义的头文件
#include <sys/types.h>
 
// 包含文件状态相关的头文件
#include <sys/stat.h>
 
// 包含Unix标准函数定义的头文件
#include <unistd.h>
 
// 包含Linux帧缓冲设备相关的头文件
#include <linux/fb.h>
 
// 包含文件控制选项相关的头文件
#include <fcntl.h>
 
// 包含标准输入输出函数定义的头文件
#include <stdio.h>
 
// 包含字符串处理函数定义的头文件
#include <string.h>
 
// 包含输入输出控制函数定义的头文件
#include <sys/ioctl.h>
 
// 包含标准库函数定义的头文件
#include <stdlib.h>
 
// 包含页面管理器头文件,这个文件中声明了页面动作相关的结构体和函数
#include <page_manager.h>
 
// 程序入口点,接受命令行参数
int main(int argc, char **argv)
{
    // 注册所有页面动作,这通常会在程序启动时执行
    PagesRegister();
 
    // 查找名为"main"的页面动作,并执行其Run函数
    // 这里传递了一个NULL参数,表示没有额外的参数传递给Run函数
    Page("main")->Run(NULL);
 
    // 程序正常退出,返回0
    return 0;
}

详细介绍:Linux基础项目开发1:量产工具——页面系统(六)-CSDN博客

七、业务系统

7.1 业务系统流程图

main.c中实现业务系统 

// 包含内存管理、系统类型、文件状态、Unix标准函数、Linux帧缓冲设备、文件控制、标准输入输出、字符串处理、输入输出控制和标准库函数相关的头文件
#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 <stdlib.h>
 
// 包含显示管理、字体管理、输入管理和页面管理相关的头文件
#include <disp_manager.h>
#include <font_manager.h>
#include <input_manager.h>
#include <page_manager.h>
 
// 程序的主函数,接受命令行参数
int main(int argc, char **argv)
{
    // 声明一个显示缓冲区指针和错误码变量
    PDispBuff ptBuffer;
    int error;
 
    // 检查命令行参数的数量,如果不等于2(程序名和字体文件名),则打印使用说明并返回错误码
    if (argc != 2)
    {
        printf("Usage: %s <font_file>\n", argv[0]);
        return -1;
    }
 
    // 初始化显示系统
    DisplayInit();
    // 选择默认的显示设备,这里是帧缓冲设备
    SelectDefaultDisplay("fb");
    // 初始化默认的显示设备
    InitDefaultDisplay();
 
    // 初始化输入系统,包括输入设备的初始化
    InputInit();
    IntpuDeviceInit();
 
    // 注册并初始化字体系统
    FontsRegister();
    error = SelectAndInitFont("freetype", argv[1]);
    // 如果字体选择和初始化失败,打印错误信息并返回错误码
    if (error)
    {
        printf("SelectAndInitFont err\n");
        return -1;
    }
 
    // 注册页面系统
    PagesRegister();
 
    // 运行主页面
    Page("main")->Run(NULL);
 
    // 程序正常退出,返回0
    return 0;
}
  1. 程序开始运行,进入main函数,首先检查命令行参数的数量。如果参数数量不等于2(一个是程序自身的路径,另一个是字体文件的路径),则打印使用说明并返回错误码,程序结束。

  2. 如果命令行参数数量正确,程序会进行显示系统的初始化(DisplayInit)。这可能包括分配内存、设置显示分辨率等操作。

  3. 然后,程序会选择默认的显示设备(SelectDefaultDisplay),在这个例子中是帧缓冲设备(frame buffer),并初始化该显示设备(InitDefaultDisplay)。帧缓冲设备是一种可以直接在内存中操作像素来绘制图像的设备。

  4. 接下来,程序会初始化输入系统(InputInitIntpuDeviceInit)。输入设备的初始化。

  5. 然后,程序会注册并初始化字体系统(FontsRegisterSelectAndInitFont)。字体文件的路径从命令行参数中获取。如果字体选择和初始化失败,程序会打印错误信息并返回错误码,结束运行。

  6. 接着,程序会注册页面系统(PagesRegister)。页面系统通常用于管理GUI的各个页面(如主页、菜单页等)。

  7. 最后,程序会运行主页面(Page("main")->Run(NULL))。这通常意味着显示主页面并等待用户的输入。

7.2 主页面流程图: 

 在main_page.c中实现主页面流程

&&& 7.3 抽象出配置文件结构体(ItemCfg) 

config.h中定义一个结构体,用于表示配置文件中的一个条目

// 定义一个结构体,用于表示配置文件中的一个条目
typedef struct ItemCfg {
    int index; // 条目的索引
    char name[100]; // 条目的名称,最大长度为100个字符
    int bCanBeTouched; // 条目是否可以被触摸(例如,在GUI中)
    char command[100]; // 条目对应的命令,最大长度为100个字符
} ItemCfg, *PItemCfg; // ItemCfg是结构体类型,PItemCfg是指向该结构体的指针类型

7.4 ParseConfigFile(char *strFileName)

config.h中定义一个函数,用于解析配置文件


// 定义一个静态数组,用于存储配置文件中的条目,最大数量由ITEMCFG_MAX_NUM定义
static ItemCfg g_tItemCfgs[ITEMCFG_MAX_NUM];
 
// 定义一个静态变量,用于记录当前配置文件中条目的数量
static int g_iItemCfgCount = 0;
 
// 定义一个函数,用于解析配置文件
int ParseConfigFile(char *strFileName)
{
    FILE *fp; // 文件指针,用于打开和读取文件
    char buf[100]; // 缓冲区,用于存储从文件读取的一行数据
    char *p = buf; // 指向缓冲区的指针,用于处理读取的数据
 
    /* 1. 打开配置文件 */
    fp = fopen(CFG_FILE, "r");
    if (!fp)
    {
        printf("can not open cfg file %s\n", CFG_FILE);
        return -1; // 打开文件失败,返回-1
    }
 
    while (fgets(buf, 100, fp)) // 循环读取文件的每一行
    {
        /* 2.1 读取每一行数据 */
        buf[99] = '\0'; // 确保缓冲区末尾有一个字符串结束符
 
        /* 2.2 跳过开头的空格或制表符 */
        p = buf;
        while (*p == ' ' || *p =='\t')
            p++;
 
        /* 2.3 忽略注释行(以#开头的行) */
        if (*p == '#')
            continue;
 
        /* 2.4 处理有效行数据 */
        g_tItemCfgs[g_iItemCfgCount].command[0] = '\0'; // 初始化命令字段为空字符串
        g_tItemCfgs[g_iItemCfgCount].index = g_iItemCfgCount; // 设置索引为当前条目计数器值
        sscanf(p, "%s %d %s", g_tItemCfgs[g_iItemCfgCount].name, &g_tItemCfgs[g_iItemCfgCount].bCanBeTouched, \
            g_tItemCfgs[g_iItemCfgCount].command); // 从缓冲区读取数据到结构体字段
        g_iItemCfgCount++; // 增加条目计数器
    }
    return 0; // 解析成功,返回0
}

7.4 GetItemCfgCount(void)

config.h中定义一个函数,用于获取配置文件中条目的数量 

// 定义一个函数,用于获取配置文件中条目的数量
int GetItemCfgCount(void)
{
    return g_iItemCfgCount; // 返回条目计数器的值
}

7.5 GetItemCfgByIndex(int index) 

config.h中定义一个函数,用于根据索引获取配置文件中的条目 

// 定义一个函数,用于根据索引获取配置文件中的条目
PItemCfg GetItemCfgByIndex(int index)
{
    if (index < g_iItemCfgCount) // 检查索引是否在有效范围内
        return &g_tItemCfgs[index]; // 返回指向对应条目的指针
    else
        return NULL; // 索引无效,返回NULL
}

7.6 GetItemCfgByName(char *name) 

config.h中定义一个函数,用于根据名称获取配置文件中的条目

// 定义一个函数,用于根据名称获取配置文件中的条目
PItemCfg GetItemCfgByName(char *name)
{
    int i;
    for (i = 0; i < g_iItemCfgCount; i++) // 遍历所有条目
    {
        if (strcmp(name, g_tItemCfgs[i].name) == 0) // 比较名称是否匹配
            return &g_tItemCfgs[i]; // 返回指向匹配条目的指针
    }
    return NULL; // 没有找到匹配的条目,返回NULL
}

*7.7 生成界面

想显示这样的界面

通过配置文件可以知道有哪些按钮,知道每个按钮的名字

只剩最后一项了:怎么确定每个按钮的位置、大小?需要计算!

 

7.8 *MainPageRun(void *pParams)

main_page.c中定义了主页面的运行函数 MainPageRun,它负责读取配置文件、生成按钮和界面,并进入一个无限循环来监听和处理

static void MainPageRun(void *pParams)
{
   int error; // 用于存储函数执行过程中的错误代码
   InputEvent tInputEvent; // 用于存储输入事件的结构体
   PButton ptButton; // 指向按钮对象的指针
   PDispBuff ptDispBuff = GetDisplayBuffer(); // 获取显示缓冲区的指针

   /* 读取配置文件 */
   error = ParseConfigFile(); // 解析配置文件,将错误代码存储在error变量中
   if (error) // 如果解析配置文件出错
       return; // 直接返回,不继续执行

   /* 根据配置文件生成按钮、界面 */
   GenerateButtons(); // 根据配置文件中的信息生成按钮和界面

   while (1) // 无限循环,用于持续监听和处理输入事件
   {
       /* 读取输入事件 */
       error = GetInputEvent(&tInputEvent); // 获取用户的输入事件,将错误代码存储在error变量中
       if (error) // 如果获取输入事件出错
           continue; // 跳过本次循环,继续下一次循环

       /* 根据输入事件找到按钮 */
       ptButton = GetButtonByInputEvent(&tInputEvent); // 根据输入事件找到对应的按钮对象
       if (!ptButton) // 如果没有找到对应的按钮
           continue; // 跳过本次循环,继续下一次循环

       /* 调用按钮的OnPressed函数 */
       ptButton->OnPressed(ptButton, ptDispBuff, &tInputEvent); // 调用找到的按钮的OnPressed函数,处理按钮按下事件
   }
}

7.9 GenerateButtons(void)

main_page.c中根据屏幕的分辨率和按钮总数计算每个按钮的大小和位置,并居中显示这些按钮。按钮的宽度和高度根据屏幕分辨率和黄金分割比(0.618)进行计算以确定其尺寸,同时保留了一定的间隔(X_GAP 和 Y_GAP)。代码中利用两层嵌套循环计算并设置每个按钮的位置和区域,并对每个按钮对象进行初始化和绘制。

static void GenerateButtons(void)
{
    int width, height;  // 用于存储单个按钮的宽度和高度
    int n_per_line;     // 用于存储每行能放置的按钮数量
    int row, rows;      // row用于记录当前行号,rows用于记录总行数
    int col;            // 用于记录当前列号
    int n;              // 用于存储按钮总数
    PDispBuff pDispBuff; // 指向显示缓冲区的指针
    int xres, yres;     // 用于存储显示区域的水平和垂直分辨率
    int start_x, start_y; // 用于存储按钮区域开始的x,y坐标
    int pre_start_x, pre_start_y; // 用于记录前一个按钮的起始x,y坐标
    PButton pButton;    // 用于指向当前操作的按钮对象
    int i = 0;          // 用于在循环中迭代按钮对象数组

    /* 算出单个按钮的width/height */
    g_tButtonCnt = n = GetItemCfgCount(); // 从配置获取按钮总数
    
    pDispBuff = GetDisplayBuffer(); // 获取显示缓冲区对象
    xres = pDispBuff->iXres; // 获取屏幕的水平分辨率
    yres = pDispBuff->iYres; // 获取屏幕的垂直分辨率
    width = sqrt(1.0 / 0.618 * xres * yres / n); // 根据页面比例和按钮总数计算单个按钮的宽度
    n_per_line = xres / width + 1; // 计算每行可以放置的按钮数目
    width = xres / n_per_line; // 调整按钮宽度,以适应每行的按钮数目
    height = 0.618 * width;  // 按照黄金比例计算按钮的高度

    /* 居中显示: 计算每个按钮的region */
    start_x = (xres - width * n_per_line) / 2; // 计算按钮起始x坐标,使其居中显示
    rows = n / n_per_line; // 计算总行数
    if (rows * n_per_line < n) // 如果按钮总数无法被整除,则行数+1
        rows++;
    
    start_y = (yres - rows * height) / 2; // 计算按钮起始y坐标,使其居中显示

    /* 计算每个按钮的region */
    for (row = 0; (row < rows) && (i < n); row++) // 遍历所有行
    {
        pre_start_y = start_y + row * height; // 计算当前行的y坐标
        pre_start_x = start_x - width; // 设置初始x坐标值
        for (col = 0; (col < n_per_line) && (i < n); col++) // 遍历每行的按钮
        {
            pButton = &g_tButtons[i]; // 获取当前按钮对象的引用
            pButton->tRegion.iLeftUpX = pre_start_x + width; // 设置按钮的左上角x坐标
            pButton->tRegion.iLeftUpY = pre_start_y; // 设置按钮的左上角y坐标
            pButton->tRegion.iWidth = width - X_GAP; // 设置按钮的宽度,并减去预设的间隔
            pButton->tRegion.iHeigh = height - Y_GAP; // 设置按钮的高度,并减去预设的间隔
            pre_start_x = pButton->tRegion.iLeftUpX; // 更新x坐标以供下一个按钮使用

            /* InitButton */
            InitButton(pButton, GetItemCfgByIndex(i)->name, NULL, NULL, MainPageOnPressed); // 初始化按钮,设置按钮的名称和事件处理函数
            i++; // 按钮索引递增,移动到下一个按钮对象
        }
    }
 
    /* OnDraw */
    for (i = 0; i < n; i++) // 遍历所有按钮对象
        g_tButtons[i].OnDraw(&g_tButtons[i], pDispBuff); // 调用按钮的OnDraw方法绘制按钮
}

*7.10 处理输入事件:GetButtonByInputEvent(PInputEvent ptInputEvent)

main_page.c中定义了一个名为 GetButtonByInputEvent 的函数,其目的是根据提供的输入事件来寻找对应的按钮对象。输入事件 PInputEvent 包含事件类型 iType 以及与特定事件类型相关的数据(例如触摸事件的坐标或网络事件的字符串数据)。

static PButton GetButtonByInputEvent(PInputEvent ptInputEvent)
{
    int i;
    char name[100]; // 用于存储从网络事件中解析出的按钮名称
    
    if (ptInputEvent->iType == INPUT_TYPE_TOUCH) // 检查输入事件是否为触摸类型
    {
        for (i = 0; i < g_tButtonCnt; i++) // 遍历所有按钮
        {
            // 检查触摸点是否在当前遍历按钮的区域内
            if (isTouchPointInRegion(ptInputEvent->iX, ptInputEvent->iY, &g_tButtons[i].tRegion))
                return &g_tButtons[i]; // 如果是,返回对应的按钮对象
        }
    }
    else if (ptInputEvent->iType == INPUT_TYPE_NET) // 检查输入事件是否为网络事件
    {
        // 解析网络事件中的字符串,提取按钮名称
        sscanf(ptInputEvent->str, "%s", name);
        return GetButtonByName(name); // 根据按钮名称查找并返回对应的按钮对象
    }
    else
    {
        return NULL; // 如果事件类型既不是触摸也不是网络,返回NULL
    }
    return NULL; // 如果没有找到匹配的按钮,返回NULL
}

7.11 isTouchPointInRegion(int iX, int iY, PRegion ptRegion)

main_page.c中定义了一个名为 isTouchPointInRegion 的静态函数,用于检查给定的触摸点坐标 (iX, iY) 是否位于指定的区域 ptRegion 内。区域由其左上角坐标 (iLeftUpX, iLeftUpY) 和宽度 iWidth 以及高度 iHeigh 定义。

  • 函数首先检查触摸点的X坐标是否在区域的左边界和右边界之间。如果触摸点的X坐标小于区域的左上角X坐标,或者大于或等于左上角X坐标加上区域的宽度(即右下角X坐标),则触摸点不在区域内,函数返回0。
  • 接着,函数检查触摸点的Y坐标是否在区域的上边界和下边界之间。如果触摸点的Y坐标小于区域的左上角Y坐标,或者大于或等于左上角Y坐标加上区域的高度(即右下角Y坐标),则触摸点不在区域内,函数返回0。
  • 如果触摸点的X和Y坐标都通过了上述检查,即触摸点完全位于区域内,函数返回1,表示触摸点在区域内。
static int isTouchPointInRegion(int iX, int iY, PRegion ptRegion)
{
   // 检查触摸点的X坐标是否在区域的左上角X坐标和右下角X坐标之间
   if (iX < ptRegion->iLeftUpX || iX >= ptRegion->iLeftUpX + ptRegion->iWidth)
       return 0; // 如果不在,返回0,表示触摸点不在区域内

   // 检查触摸点的Y坐标是否在区域的左上角Y坐标和右下角Y坐标之间
   if (iY < ptRegion->iLeftUpY || iY >= ptRegion->iLeftUpY + ptRegion->iHeigh)
       return 0; // 如果不在,返回0,表示触摸点不在区域内

   return 1; // 如果触摸点的X和Y坐标都在区域内,返回1,表示触摸点在区域内
}

7.12 GetButtonByName(char *name)

main_page.c中定义了一个名为 GetButtonByName 的静态函数,其目的是根据提供的名称来查找并返回对应的按钮对象。函数接受一个字符串指针 name 作为参数,该参数代表要查找的按钮的名称。

  • 函数通过一个循环遍历全局数组 g_tButtons 中的所有按钮,数组的大小由 g_tButtonCnt 决定。
  • 在循环中,使用 strcmp 函数来比较传入
static PButton GetButtonByName(char *name)
{
   int i;
   
   // 遍历所有按钮
   for (i = 0; i < g_tButtonCnt; i++)
   {
       // 使用strcmp函数比较传入的名称与当前按钮的名称是否相同
       if (strcmp(name, g_tButtons[i].name) == 0)
           return &g_tButtons[i]; // 如果名称匹配,返回当前按钮的指针
   }

   return NULL; // 如果遍历完所有按钮都没有找到匹配的名称,返回NULL
}

7.13 MainPageOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)

main_page.c中定义了一个名为 MainPageOnPressed 的静态函数,用于处理按钮的按下事件。函数接受三个参数:一个指向按钮结构体的指针 ptButton,一个指向显示缓冲区的指针 ptDispBuff,以及一个指向输入事件结构体的指针 ptInputEvent

  • 函数首先初始化按钮的颜色为默认颜色,并准备用于存储名称和状态的缓冲区。
  • 然后,根据输入事件的类型(触摸屏事件或网络事件),执行不同的逻辑。
  • 对于触摸屏事件,函数检查按钮是否可以被点击,如果可以,则切换按钮的状态并相应地修改按钮的颜色。
  • 对于网络事件,函数从输入事件中解析出名称和状态,并根据状态的不同设置按钮的颜色。如果状态以数字开头,则将按钮名称更新为该数字,并设置为百分比颜色。
  • 如果事件类型不是触摸屏或网络事件,或者状态不符合任何条件,函数返回-1。
  • 最后,函数绘制按钮的底色,在按钮区域中央绘制文字,并将更新后的按钮区域刷新到显示缓冲区,然后返回0表示成功处理事件。
static int MainPageOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent)
{
   unsigned int dwColor = BUTTON_DEFAULT_COLOR; // 初始化按钮颜色为默认颜色
   char name[100]; // 用于存储名称的缓冲区
   char status[100]; // 用于存储状态的缓冲区
   char *strButton; // 指向按钮名称的指针

   strButton = ptButton->name; // 获取按钮的名称

   /* 1. 处理触摸屏事件 */
   if (ptInputEvent->iType == INPUT_TYPE_TOUCH)
   {
       /* 1.1 检查按钮是否可以被点击 */
       if (GetItemCfgByName(ptButton->name)->bCanBeTouched == 0)
           return -1; // 如果按钮不可点击,返回-1

       /* 1.2 修改按钮状态和颜色 */
       ptButton->status = !ptButton->status; // 切换按钮状态
       if (ptButton->status)
           dwColor = BUTTON_PRESSED_COLOR; // 如果按钮被按下,设置为按下时的颜色
   }
   else if (ptInputEvent->iType == INPUT_TYPE_NET)
   {
       /* 2. 处理网络事件 */

       /* 根据传入的字符串修改颜色 */
       sscanf(ptInputEvent->str, "%s %s", name, status); // 从输入事件中解析名称和状态
       if (strcmp(status, "ok") == 0)
           dwColor = BUTTON_PRESSED_COLOR; // 如果状态为"ok",设置为按下时的颜色
       else if (strcmp(status, "err") == 0)
           dwColor = BUTTON_DEFAULT_COLOR; // 如果状态为"err",设置为默认颜色
       else if (status[0] >= '0' && status[0] <= '9')
       {
           dwColor = BUTTON_PERCENT_COLOR; // 如果状态以数字开头,设置为百分比颜色
           strButton = status; // 更新按钮名称
       }
       else
           return -1; // 如果状态不符合任何条件,返回-1
   }
   else
   {
       return -1; // 如果事件类型不是触摸屏或网络事件,返回-1
   }

   /* 绘制按钮底色 */
   DrawRegion(&ptButton->tRegion, dwColor); // 根据当前颜色绘制按钮区域

   /* 在按钮区域中央绘制文字 */
   DrawTextInRegionCentral(strButton, &ptButton->tRegion, BUTTON_TEXT_COLOR); // 在按钮中央绘制文字,颜色为按钮文字颜色

   /* 刷新显示到LCD或Web界面 */
   FlushDisplayRegion(&ptButton->tRegion, ptDispBuff); // 刷新按钮区域到显示缓冲区
   return 0; // 返回0表示成功处理事件
}

 详细介绍:Linux基础项目开发1:量产工具——业务系统(七)_量产工具开发-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/619381.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

40 -1 入侵检测系统(IDS)- IDS的使用及规则

xampp 官网:XAMPP Installers and Downloads for Apache Friends 一、安装 XAMPP XAMPP 简介 XAMPP是完全免费且易于安装的Apache发行版,其中包含MariaDB、PHP和Perl。XAMPP开放源码包的设置让安装和使用出奇容易。 开始安装 如果命令行下载太慢,就在浏览器中下载完再拉…

【2024新版】龙年新版ui周易测算网站H5源码/在线起名网站源码/运势测算网站系统源码

>>>功能说明&#xff1a; 1、系统配置&#xff1a;系统基本配置、测算价格配置、在线预约配置、系统信息配置、代理分成配置、推广积分配置、VIP价格配置、账号管理 2、推广管理&#xff1a;我的信息、推广链接、订单管理、体现管理 3、付费应用&#xff0c;订单管…

高校课程评价|基于SSM+vue的高校课程评价系统的设计与实现(源码+数据库+文档)

高校课程评价系统 目录 基于SSM&#xff0b;vue的高校课程评价系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1管理员功能模块 2学生功能 3教师功能 4专家功能 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&…

Windows Docker 使用 httpd 部署静态 Web 站点

一、简介 httpd 是 Apache超文本传输协议&#xff08;HTTP&#xff09;服务器的主程序&#xff0c;是一个独立运行的后台进程&#xff0c;专门负责处理 HTTP 请求。它通过建立子进程或线程的池来高效管理请求&#xff0c;确保服务器能够迅速响应客户端的需求。httpd 因其高效率…

【Delphi】OpenCV 实战(一):OpenCV简介及开发环境配置

目录 一、OpenCV 功能模块 二、Delphi 中使用OpenCV 三、OpenCV 4.7 Delphi开发环境配置 1. 环境配置 2. OpenCV 中Demo程序的编译配置 3. 运行 Demo (OpenCV for Delphi) OpenCV 是世界上最大的计算机视觉库。 它是开源的,包含 2500 多种算法,由非营利…

信息系统项目管理师0105:项目评估与决策(7项目立项管理—7.3项目评估与决策)

点击查看专栏目录 文章目录 7.3项目评估与决策1.评估依据2.评估的程序3.项目评估的内容4.项目评估报告内容大纲记忆要点总结7.3项目评估与决策 项目评估指在项目可行性研究的基础上,由第三方(国家、银行或有关机构)根据国家颁布的政策、法规、方法、参数和条例等,从国民经济…

算法题② —— 链表专栏

1. 链表数据结构 struct ListNode {int val;ListNode *next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}};2. 链表的删除 2.1 移除链表元素 力扣&#xff1a;https://leetco…

大规模 RGB LED灯控系统 Lumos:创新与智能化的融合

灯控系统&#xff1a;创新与智能化的融合 在现代照明技术不断进步的背景下&#xff0c;灯控系统的应用已经从简单的开关控制&#xff0c;发展到能够进行复杂程控操作的智能化管理。我们推出的新一代灯控解决方案&#xff0c;凭借其高度的可配置性和跨平台兼容性&#xff0c;已…

Python | Leetcode Python题解之第86题分隔链表

题目&#xff1a; 题解&#xff1a; class Solution:def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:sml_dummy, big_dummy ListNode(0), ListNode(0)sml, big sml_dummy, big_dummywhile head:if head.val < x:sml.next headsml sm…

Android 10.0 Launcher3定制folder文件夹2x2布局之二foldericon的2x2的显示布局

1.前言 在10.0的系统rom产品定制化开发中,在对Launcher3的folder文件夹功能定制中,要求folder文件夹跨行显示,就是 2x2布局显示,默认的都是占1格的,现在要求占4格显示,系统默认是不支持显示4格的,所以接下来需要分析相关的 功能,然后来实现这个功能 2.Launcher3定制fo…

项目管理-计算题公式-补充【复习】

1.EMV决策树 定义&#xff1a;用决策树在若干备选行动方案中选择一个最佳方案。在决策树 中&#xff0c;用不同的分支代表不同的决策或事件&#xff0c;即项目的备选路径。每个决策或事件 都有相关的成本和单个项目风险(包括威胁和机会)。决策树分支的终点表示沿特 定路径发展的…

[C/C++] -- 搜索迷宫路径

DFS&#xff08;深度优先搜索&#xff09;和BFS&#xff08;广度优先搜索&#xff09;是两种常用的图遍历算法&#xff0c;它们在搜索图或树中的节点时有着不同的策略和特点。 深度优先搜索 (DFS): 在DFS中&#xff0c;从起始节点开始&#xff0c;沿着一条路径尽可能深地搜索&a…

基于数据挖掘与机器学习揭秘脱发主因

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 基于数据挖掘与机器学习揭秘脱发主因 目录 一、绪论背景描述数据说明内容大概 二、导入包以及数据读取三、数据预览四、探究导致脱发的因素4.1…

萤火虫优化算法(Firefly Algorithm)

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 算法背景 萤火虫优化算法&#xff0c;是由剑桥大学的Xin-She Yang在2009年提出的一种基于群体智能的优化算法。它的灵感来源于萤火虫在夜晚闪烁…

[AIGC] 跳跃表是如何实现的?原理?

文章目录 什么是跳跃表查找流程&#xff1a;为什么使用跳跃表?跳跃表是怎么实现的&#xff1f; PS:跳跃表是比较常问的一种结构。 什么是跳跃表 Skip Lists: A Probabilistic Alternative to Balanced Trees 跳跃表是一种可以用来代替平衡树的数据结构。跳跃表使用概率平衡…

微服务核心01-Maven【项目管理工具】高级

一、分模块开发与设计&#xff08;重点⭐&#xff09; ssm_pojo 拆分 新建模块拷贝原始项目中对应的相关内容到 ssm_pojo 模块中 实体类 &#xff08;User&#xff09;配置 文件&#xff08;无&#xff09; ssm_dao 拆分 ssm_service 拆分 ssm_control 拆分 二、聚合&#xff…

齿轮滚刀刃口钝化技术简介

介绍 在滚刀的使用中发现&#xff0c;进口滚刀和国产滚刀在加工质量和寿命方面存在显著差异。经过多次比较得知&#xff0c;滚刀的使用寿命可以达到国产滚刀的两倍以上&#xff0c;而进口滚刀返回原厂磨削后的使用寿命约为新刀具的90% &#xff0c;但同样经过国内厂家磨削后&a…

第 1 天_二分查找【算法基础】

第 1 天_二分查找 前言34. 在排序数组中查找元素的第一个和最后一个位置题解官方33. 搜索旋转排序数组题解官方74. 搜索二维矩阵 前言 这是陈旧已久的草稿2021-11-09 19:33:44 当时在学习数据结构&#xff0c;然后再LeetCode上找了一个算法基础。 但是后来又没做了。 现在20…

1. 抓娃娃-二分

因为这个限制&#xff0c;所以不用担心线段比区间长 线段一定比区间短的话&#xff0c;想要判断是否线段的二分之一及以上在区间内&#xff0c;则可以转化为线段中点是否在区间内的问题 如果没有那个限制&#xff0c;那么就无法这么考虑了&#xff0c;因为即使中点在区间内&…

PUBG非升级实用枪皮-部分盘点

藏匿处的黑货箱武器需要耗费高额成本才能升级 对于像我这样的日常休闲玩家来说是一笔不小的&#xff08;巨大的&#xff01;&#xff09;负担 其实有许多普通非升级枪皮也是不错的选择 今天就来盘点一下我自己日常在用的普通皮 来看看你是不是也在用一样的 &#xff08;仅是盘点…