LCD屏显示练习【二】

news/2024/9/21 10:59:43

目录
  • 题目
    • 题目分析
    • 思路解析
    • 知识点涉及
    • 代码展示
    • 优化思考
      • 问题一:观察界面切换效果,可明显观察到界面切换时有明显的刷新效果,有点影响使用效果
      • 问题二:图片的按键位置不能相近或者重合,否则有误触导致执行了别的功能
      • 问题三:当快速来回点击触摸屏两个位置时,会出现点击位置坐标读取与实际触摸坐标不一致的情况

题目

设计一个程序,该程序在运行之后自动播放一段开机动画,开机动画结束后可以调转到登录界面,登录界面有2个按钮,分别是登录和退出,点击登录之后可以显示系统主界面。主界面自拟,要求主界面有一个返回按钮,点击返回按钮可以回到登录界面。要求:不可以使用 goto 语句。

题目分析

该题目的主要诉求可总结为:

  1. 开机时需要有一段开机动画,且在开机动画结束后可以之间到达操作主界面
  2. 主界面上会有两个按钮,即切换界面和退出
  3. 界面切换不能使用goto语句

思路解析

  1. 开机动画可以使用裁剪工具GIFtiqu将动态图裁剪为一张张jpeg图片,在将jpeg图片解码循环显示,且可以将登录界面图片放至循环的最后一张。
  2. 触屏按键切换,该功能涉及到读取LCD屏的触摸屏设备信息。需要创建对应格式的结构体变量,并利用read()函数将设备文件中的信息存储进创建的结构体变量中。
  3. 由于读取触摸屏参数不能只读一次,所以采取死循环作为循环,并设置退出键坐标为退出循环或者退出整个程序。

知识点涉及

  1. 开机动画图片循环时,需要使用usleep()控制循环间隙。该函数的单位为微秒,且1s(秒) = 1000ms(毫秒),1ms(毫秒) = 1000μs(微秒)。经过计算,使得图片循环能够满足人眼视觉残留条件,最终达到图片“动”起来的效果。
  2. 由于开机动画使用的是JPEG图片,所以还涉及到JPEG的解码步骤。
  3. 触摸屏的设备信息在linux系统下也是一个文件,所以我们可以通过read()将这些信息读取到特定结构的结构体变量中,再来对获取到的参数做相应的操作。

代码展示

/*********************************************************************	file name:	main.c*	author	 :  790557054@qq.com*	date	 :  2024/05/14*	function :  该案例是掌握LCD屏触摸原理以及显示图片切换过程* 	note	 :  None**	CopyRight (c)  2023-2024   790557054@qq.com   All Right Reseverd** *****************************************************************/#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/input.h>#include "jpeglib.h"/********************************************************************
*
*	name	 :	read_JPEG_file
*	function :  实现完成libjpeg库的移植,实现在LCD上的任意位置显示一张任意大小的jpg图片,并且对可能越界的情况做错误处理。
*	argument :
*				@filename  :需要解码的jpg图片@start_x   :图片显示初始位置的横坐标@start_y   :图片显示初始位置的纵坐标@lcd_mp    :LCD屏内存映射空间的地址
*
*	retval	 :  调用成功返回1,调用失败返回0
*	author	 :  790557054@qq.com
*	date	 :  2024/05/13
* 	note	 :  学习JPEG的解码过程,以及JPEG存储颜色分量的方式
*
* *****************************************************************/
int read_JPEG_file(char *filename, int start_x, int start_y, int *lcd_mp)
{/*[1]:创建解码对象,并且对解码对象进行初始化,另外需要创建错误处理对象,并和解码对象进行关联*/// 创建解码对象,其是一个结构体变量struct jpeg_decompress_struct cinfo;// 创建错误处理对象struct jpeg_error_mgr jerr;// 将错误处理对象与解码对象相关联cinfo.err = jpeg_std_error(&jerr);// 对解码对象进行初始化jpeg_create_decompress(&cinfo);/*[2]:打开待解码的jpg图片,使用fopen的时候需要添加选项”b”,以二进制方式打开文件!*/FILE *infile;          // 接收打开文件的文件指针unsigned char *buffer; // 输出行缓冲区int row_stride;        // buffer一行的像素点数量,即图片的宽度// 以二进制方式打开图片,并进行错误处理if ((infile = fopen(filename, "rb")) == NULL){fprintf(stderr, "can't open %s\n", filename);return 0;}// 把打开的文件的文件指针和解码对象进行绑定jpeg_stdio_src(&cinfo, infile);/*[3]:读取待解码图片的文件头,并把图像信息和解码对象进行关联,通过解码对象对jpg图片进行解码*/(void)jpeg_read_header(&cinfo, TRUE);/*[4]:可以选择设置解码参数,如果打算以默认参数对jpg图片进行解码,则可以省略该步骤!*//* 在该习题要求中,并不涉及图片缩放等问题,所以我们可以省略该步骤* jpeg_read_header(),*//*[5]:开始对jpg图片进行解码,调用函数之后开始解码,可以得到图像宽、图像高等信息!*/// 我们只需要调用该函数,将图像信息放入解码对象中,无需注意其的返回值(void)jpeg_start_decompress(&cinfo);/*[6]:开始设计一个循环,在循环中每次读取1行的图像数据,并写入到LCD中*/// 计算图像一行的大小row_stride = cinfo.output_width * cinfo.output_components;// 为自定义缓冲区申请堆内存,注意申请的内存空间大小应为图像一行的大小buffer = calloc(1, row_stride);// 定义一个int类型变量,用于存放颜色分量数据int data = 0;/*定义一个循环,用于循环写入一行的图像数据;使用解码对象当前扫描行数与图像的高比较结果作为循环条件,当两者相等,即图像数据写入完后退出循环*/while (cinfo.output_scanline < cinfo.output_height){/*调用jpeg_read_scanlines函数,读取解码对象中的图像一行数据,并存放进自定义缓冲区中且cinfo.output_scanline会随着调用该函数而增加1,保证while循环能够正常退出*/(void)jpeg_read_scanlines(&cinfo, &buffer, 1); // 从上到下,从左到右  RGB RGB RGB RGB// 将缓冲区中存储的数据逐一写入LCD的内存映射空间中for (int i = 0; i < cinfo.output_width; ++i) // 012 345{/*由于图片没有透明度,所以一个像素点大小为3byte,而data为int类型变量,所以需要借助"|=" 使得颜色分量顺序存储正确;又因为JEPG存储颜色分量顺序为RGB,所以进行下面算法*/data |= buffer[3 * i] << 16;    // Rdata |= buffer[3 * i + 1] << 8; // Gdata |= buffer[3 * i + 2];      // B/*把像素点写入到LCD的指定位置。其中800*start_y + start_x控制的是用户自定义的图片显示初始位置;800*(cinfo.output_scanline-1)控制的是写入图像数据的行数切换;+ i控制的是写入图像数据的列数切换*/lcd_mp[800 * start_y + start_x + 800 * (cinfo.output_scanline - 1) + i] = data;// 最后需将data内部清零,避免对下一次循环的颜色分量写入造成影响data = 0;}}/*[7]:在所有的图像数据都已经解码完成后,则调用函数完成解码即可,然后释放相关资源!(不要遗漏打开的图像文件)*/// 解码完成(void)jpeg_finish_decompress(&cinfo);// 释放解码对象jpeg_destroy_decompress(&cinfo);// 关闭打开的图像文件fclose(infile);return 1;
}int main(int argc, char const *argv[])
{// 1.打开LCD   openint lcd_fd = open("/dev/fb0", O_RDWR);if(lcd_fd == -1){perror("open file of /dev/fb0 is fail");return -1;}// 读取用户触摸坐标// 1.打开触摸屏int ts_fd = open("/dev/input/event0", O_RDWR);int cnt = 0;                 // 记录收到的触摸函数值int x, y;                    // 定义两个存放横坐标与纵坐标值的变量struct input_event ts_event; // 定义触摸屏输入信息的结构体变量// 2.对LCD进行内存映射  mmapint *lcd_mp = (int *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);if(lcd_mp == MAP_FAILED){perror("memory map LCd is fail");return -1;}// 3.显示开机动画char gif_path[128] = {0};// 开机动画总共有60张图片+一张主页面图片;使得开机动画结束后能够之间进入for (int i = 0; i < 61; ++i){sprintf(gif_path, "./gif/Frame%d.jpg", i); // 构造jpg图片的路径read_JPEG_file(gif_path, 0, 0, lcd_mp);    // 在LCD上显示/*FPS = 60HZusleep的单位是μs微秒,且1s(秒) = 1000 ms(毫秒) , 1ms(毫秒) = 1000μs(微秒)1HZ = 1000 / 1000μs*/usleep(1000 * 16);}// 4.死循环:在用户点退出之前无需退出操作界面while (1){// 5.分析读取的设备信息 (type + code + value)// 获取信息read(ts_fd, &ts_event, sizeof(ts_event));// 分析读取的设备信息 (type + code + value)if (ts_event.type == EV_ABS) // 说明是触摸屏{if (ts_event.code == ABS_X) // 说明是X轴{cnt++;x = ts_event.value * 800 / 1024;}if (ts_event.code == ABS_Y) // 说明是Y轴{cnt++;y = ts_event.value * 480 / 600;}if (cnt >= 2){printf("x = %d\t", x); // 输出X轴坐标printf("y = %d\n", y); // 输出Y轴坐标cnt = 0;}}// 6.实时判断读取到的触摸屏坐标,施行相应的功能// 登录界面if (x >= 336 && x <= 450 && y >= 396 && y <= 465){read_JPEG_file("./pic/login.jpg", 0, 0, lcd_mp);x = 0;y = 0;}// 返回主界面else if ((x >= 646 && x <= 773) && (y >= 33 && y <= 120)){cnt = 0;read_JPEG_file("./gif/Frame60.jpg", 0, 0, lcd_mp);x = 0;y = 0;}// 退出else if ((x >= 628 && x <= 733) && (y >= 397 && y <= 454)){read_JPEG_file("./pic/close.jpg", 0, 0);break;}}return 0;
}

优化思考

问题一:观察界面切换效果,可明显观察到界面切换时有明显的刷新效果,有点影响使用效果

分析:

​ 初步分析是因为JPEG解码后,是通过一行一行像素点写入LCD映射空间的,故而导致可以看到明显的从上到下的切换效果。

优化方向:

  1. 优化JPEG解码步骤的循环:使得图片可以更快更好的写入进LCD映射空间内,但是这个方向实现起来较为麻烦。
  2. 将界面切换过程均换成动态图:经过观察开机动画,发现过程中完全看不到刷新效果,可以达到丝滑切换图片的效果,所以初步设想是因为图片切换速度超过人眼观察速度,所以我认为这个方向可以实现。且考虑到切换界面动画的框架可以保持一致,这样省去了大量的准备步骤,还能够获得更好的效果,准备在项目内试验。

问题二:图片的按键位置不能相近或者重合,否则有误触导致执行了别的功能

分析:

​ 这是因为当前程序的架构,是将读取屏幕触屏参数与条件判断直接放在一起循环导致的。read()函数并不会像scanf()函数那样有等待的过程,故而每一次获取参数后会立马进行条件判断。即便界面完成切换,但实际上一个界面按键的触屏位置参数依然生效。

优化方向:

  1. 对程序进行模块化编程:这样可以将各个界面的读取参数与判断间隔开,降低各个界面之间的耦合性。即在这个界面函数结束前,只会进行该界面的按键位置条件判断,从而消除误触执行其他功能的可能性。

问题三:当快速来回点击触摸屏两个位置时,会出现点击位置坐标读取与实际触摸坐标不一致的情况

分析:

​ 初步分析是因为读取触摸坐标的机制导致的,但是目前没有办法证实,期待后续的学习。

优化方向:

  1. 满足一次条件判断后,将存储获取参数的变量清空:经过实测发现,只要在满足条件判断后,将变量内参数情况,再进行读取赋值操作,便可以丝滑进行界面切换,不会出现读取坐标与实际触摸坐标不同的情况。

    同样,原理未知,期待后续学习补充。

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

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

相关文章

.net DataGirdView 通过列索引修改单元格字体

场景是这样、我需要DataGirdView某几列数量大于0字体就变成蓝色,某几列超过标准值字体就变成红色具体列名属性void InitCols(){var col = _DataGridView.BuildCol<DataGridViewTextBoxColumn>(dgvDetail, "OrderNo", " 工单号");col.Width = 125;//…

原型设计软件对比以及介绍项目EventSphere原型设计方案

一、介绍项目的原型设计方案 项目名称:EventSphere EventSphere 旨在建立一个全面的、多维度的学生活动发布平台,涉及不仅是面对面的实体活动,还包括虚拟活动的体验,如虚拟现实(VR)活动。该平台计划提供以下功能:用户注册与登录 :允许学生、教师和活动组织者创建账户并登…

初识汇编-第一篇

计算机的组成-cpu的模型

dotnet 后台线程发送 X11 窗口消息

本文将告诉大家如何在 dotnet 里面的后台线程向自己进程内的窗口发送消息核心是通过 XSendEvent 发送消息,发送消息想要有反应需要另开 XOpenDisplay 获取 display 对象,最后再将其关闭才能发送出去 核心代码如下 _ = Task.Run(async () => {while (true){await Task.Dela…

dotnet X11 窗口之间发送鼠标消息 模拟鼠标输入

本文记录我阅读 Avalonia 代码过程中所学习到的在 X11 的窗口之间发送鼠标消息,可以跨进程给其他进程的窗口发送鼠标消息,通过此方式可以实现模拟鼠标输入直接使用 XSendEvent 给指定窗口发送消息即可,如以下示例代码var xEvent = new XEvent{MotionEvent ={type = XEventNa…

28-SpringMVC源码解析

Quiz:Spring和SpringMVC整合使用时,会创建一个容器还是两个容器(父子容器?) DispatcherServlet初始化过程中做了什么? 请求的执行流程是怎么样的?SpringMVC 是基于 Servlet 和 Spring 容器设计的 Web 框架。1. Servlet 回顾 Servlet 接口及其实现类结构:public interfa…

读人工智能时代与人类未来笔记03_演变

读人工智能时代与人类未来笔记03_演变1. 演变 1.1. 每个社会都找到了属于自己的一套适应世界的方法 1.1.1. 适应的核心,是有关人类心智与现实之间关系的概念 1.1.2. 人类认识周围环境的能力 1.1.2.1. 这种能力通过知识获得,同时…

爱芯通元产品与关键技术

爱芯通元产品与关键技术 混合精度NPU 以算子为原子指令集的AI计算处理器爱芯通元NPU采用多线程异构多核设计,实现算子、网络微结构、数据流和内存访问优化,高效支持混合精度算法设计,natively支持Transformer网络结构,为大模型在边缘侧、端侧的应用提供良好的基础。 经过多…