【IMX6ULL项目】IMX6ULL上Linux系统实现产测工具框架

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

1.简单易用:

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

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

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

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

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

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

3. 纯 C 语言编程

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

a18bbf83af014dcaaa2ef2f419c9805e.png

上图中的 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博客


 

一、总设计思路:

25f64ef0ef3b4904b611e94ae746eaa4.png

二、显示系统:

*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 显示管理器框架

995b592744cd4c21b3b7eb47731b694b.png

*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博客

三、输入系统:

        我们需要抽象出来俩个结果体,一个数据本身,一个设备本身。

6f23e9228a9c47bc87ba19b284d35bc7.png

*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  输入管理器框架 

ace31866ca2a45e4929cff69ae728fe5.png

* 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中编写环形缓冲区也是一个一维数组,并不是一个环形的数组,用于保存各个输入设备得到的数据

6a54f6b2c94e4e02b6c6cac472b5fc52.png

 

/* 定义环形缓冲区的大小 */
#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 字符管理器框架

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

96d6e3b664f743cf8a68edb1bc1888ed.png

 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) :取出某个页面

9d9de50f19af4ab89aba08f901f477cb.png

 

 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 业务系统流程图

60264a582c7945e482ec7228bfd42146.png

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 主页面流程图: 

46496d9986ae4ff0874f6224356aaff1.png

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

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

0e3dc7e0f26d43d4858b98d536aaacdf.png

 

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 生成界面

想显示这样的界面

06e20625f3cf4703acaee9757b93e577.png

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

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

 e1d8eec5d5184d6b8623b8de4bf7c41b.png

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博客

 

 

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

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

相关文章

Scratch四级:第08讲 排序算法

第08讲 排序算法 教练&#xff1a;老马的程序人生 微信&#xff1a;ProgrammingAssistant 博客&#xff1a;https://lsgogroup.blog.csdn.net/ 讲课目录 常考的排序算法项目制作&#xff1a;“三个数排序”项目制作&#xff1a;“成绩查询”项目制作&#xff1a;“排序”项目制…

使用LLaMA Factory来训练智谱ChatGLM3-6B模型

使用LLaMA Factory来训练智谱ChatGLM3-6B模型时&#xff0c;以下是一个训练过程&#xff1a; 1. 环境搭建 a. 安装Python和Anaconda 下载并安装适合你操作系统的Python版本&#xff08;推荐Python 3.10或更高版本&#xff09;。安装Anaconda&#xff0c;以管理Python环境和依…

“等保测评通过指南:打造企业安全生态环境“

等保测评&#xff0c;即网络安全等级保护测评&#xff0c;是企业构建安全生态环境的重要环节。以下是通过等保测评的指南&#xff0c;旨在帮助企业打造一个安全的网络环境&#xff1a; 理解等保测评的重要性&#xff1a; 等保测评是确保企业信息系统安全得到有效保护的重要标准…

轻松上手Spring AOP,掌握切面编程的核心技巧

文章目录 AOP 是什么AspectJSpring AOP切点表达式execution()语法结构示例注意事项 通知&#xff08;Advice&#xff09;类型如何使用准备阶段定义切面定义并使用切点测试 总结 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍…

轨迹规划 | 图解纯追踪算法Pure Pursuit(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 纯追踪算法原理推导2 自适应纯追踪算法(APP)3 规范化纯追踪算法(RPP)4 仿真实现4.1 ROS C仿真4.2 Python仿真4.3 Matlab仿真 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全局规划…

Android面试题之Kotlin和Java之间互操作

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 互操作性和可空性 要注意Java中所有类型都是可空的String!表示平台数据类型 public class JavaTest {public String generateName() {return …

瞬息全宇宙——平行宇宙终极教程,手把手教你做出百万点赞视频

最近一种叫“瞬息全宇宙”的视频火了&#xff0c;抖音一期视频百万赞&#xff0c;各个博主视频都在带瞬息全宇宙这个标签&#xff0c;于是就有很多朋友催我出教程了&#xff0c;在琢磨了几天之后&#xff0c;终于整出来了 教程包含了插件的安装&#xff0c;界面的讲解&#xff…

for 双重循环

一.双循环&#xff1a; 可以使用嵌套循环来实现脚本的双层循环&#xff0c;示例代码如下 1.显示外循环是$a 内循环$b encho -e \t 是制表符 2.9 9乘法表 for 循环&#xff1a; echo -n是不换行输出 while循环: 3.输出长度宽度都为9个星的矩形 for循环 while循环 …

开发者出海时都在用哪些组件库?

❝ 哈喽&#x1f44b;&#xff0c;我是树酱。今天我要介绍的是在开发者出海时经常使用的组件库。这些组件库大多采用Tailwind CSS作为基础&#xff0c;它们不仅风格独树一帜&#xff0c;而且外观也非常吸引人&#xff01; 1.Shadcn-ui shadcn的风格跟Notion风格很像&#xff0c…

博客互动革命:如何打造活跃读者社区并提升参与度

CSDN 的朋友你们好&#xff0c;我是未来&#xff0c;今天给大家带来专栏【程序员博主教程&#xff08;完全指南&#xff09;】的第 10 篇文章“与读者互动”。本文揭示了提升技术博客参与度的秘诀。从评论互动到社交媒体策略&#xff0c;本文将指导你如何建立强大的读者社区。掌…

编程技巧:什么是JavaScript递归

什么是递归 程序调用自身的编程技巧称为递归&#xff08;recursion&#xff09; 递归的基本思想是将一个复杂的问题分解成更小、更易于管理的子问题&#xff0c;这些子问题与原始问题相似&#xff0c;但规模更小。 递归的要素 基本情况&#xff08;Base Case&#xff09;&…

【C语言 | 数据结构】栈

文章目录 前言1、栈1.1栈的概念和定义1.1.2栈的基本概念&#xff1a; 1.2栈的方法(接口)1.3栈的实现方法1.4栈的性质1.5栈的应用1.6栈的结构 2、栈的实现2.1 顺序栈2.1.1 顺序栈的结构体2.1.2 顺序栈的初始化2.1.3 顺序栈的销毁2.1.4 顺序栈的入栈2.1.5 顺序栈的出栈2.1.5 顺序…

聚观早报 | 比亚迪海狮07 EV上市;苹果将升级Siri

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 5月13日消息 比亚迪海狮07 EV上市 苹果将升级Siri OpenAI开发全新技术 沃尔沃EX30车型将上市 SpaceX计划新建发…

template——模板进阶(C++)

在之前的文章中&#xff0c;介绍了模板初阶&#xff1a;Cpp_桀桀桀桀桀桀的博客-CSDN博客 在本篇中将会对模板进一步的讲解。本篇中的主要内容为&#xff1a;非类型模板参数、函数模板的特化、类模板的特化&#xff08;其中包含全特化和偏特化&#xff09;&#xff0c;最后讲解…

【计算机网络篇】数据链路层(9)使用集线器的共享式以太网

文章目录 &#x1f6f8;使用同轴电缆的共享总线以太网 &#x1f386;使用集线器的共享式以太网&#x1f95a;集线器的特点 &#x1f354;10BASE-T星型以太网 &#x1f6f8;使用同轴电缆的共享总线以太网 若总线上的某个机械连接点接触不良或断开&#xff0c;则整个网络通信就不…

【前端工程化指南】Git常见操作之忽略文件

默认情况下&#xff0c;Git管理代码版本时会对所有文件进行跟踪&#xff0c;但有些时候我们并不希望项目中的一些文件上传到远程仓库或公共仓库中&#xff0c;例如密钥&#xff0c;个人隐私文件等。因此Git提供了两种忽略跟踪文件的方式.gitignore文本文件与git rm命令&#xf…

亿级流量下通用的高并发架构设计

既然是亿级用户应用&#xff0c;那么高并发必然是其架构设计的核心要素。 本文我们将介绍高并发架构设计的一些通用设计方案。 关键词&#xff1a;读/写分离、数据缓存、缓存更新、CQRS、数据分片、异步写 本文节选自电子工业出版社博文视点刚刚出版的《亿级流量系统架构设计…

Java随笔1

1.编程中组件的概念&#xff1a; 在编程中&#xff0c;组件&#xff08;Component&#xff09;通常指的是一种可重用的、模块化的代码单元&#xff0c;它封装了特定的功能或用户界面元素&#xff0c;并提供了与其他代码进行交互的接口。组件可以看作是对数据和方法的简单封装&…

ADS基础操作篇2

上篇文章《ADS基础介绍篇1》,对ADS界面,常用小工具及自带设计模板进行了介绍。ADS使用非常方便,含大量的控件和仿真模板。这篇文章我们主要讲解ADS的基础操作,包含Workspace、原理图、symbol的创建,仿真结果查看及优化。 1. 新建Workspace 添加名称及路径后,点击create…

共享充电宝语音芯片ic方案支持远程4g无线更新语音

一、简介 共享充电宝语音芯片ic方案支持远程4g无线wifi蓝牙更新语音 共享充电宝已经是遍布在大街小巷的好产品&#xff0c;解决了携带充电宝麻烦的痛点 但是很多的共享充电宝在人机交互方便&#xff0c;还做得不够好&#xff0c;比如&#xff1a;借、还设备没有语音提示&…