LCD屏顯示練習【二】

来源:https://www.cnblogs.com/fly-home/p/18192642
-Advertisement-
Play Games

目錄題目題目分析思路解析知識點涉及代碼展示優化思考問題一:觀察界面切換效果,可明顯觀察到界面切換時有明顯的刷新效果,有點影響使用效果問題二:圖片的按鍵位置不能相近或者重合,否則有誤觸導致執行了別的功能問題三:當快速來回點擊觸摸屏兩個位置時,會出現點擊位置坐標讀取與實際觸摸坐標不一致的情況 題目 設計 ...


目錄

題目

設計一個程式,該程式在運行之後自動播放一段開機動畫,開機動畫結束後可以調轉到登錄界面,登錄界面有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	 :  [email protected]
 *	date	 :  2024/05/14
 *	function :  該案例是掌握LCD屏觸摸原理以及顯示圖片切換過程
 * 	note	 :  None
 *
 *	CopyRight (c)  2023-2024   [email protected]   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	 :  [email protected]
*	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;    // R
            data |= buffer[3 * i + 1] << 8; // G
            data |= 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   open
    int 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進行記憶體映射  mmap
    int *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 = 60HZ
        usleep的單位是μ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()函數那樣有等待的過程,所以當用戶沒有觸摸時,程式會卡在read()處。

優化方向:

  1. 對程式進行模塊化編程:這樣可以將各個界面的讀取參數與判斷間隔開,降低各個界面之間的耦合性。即在這個界面函數結束前,只會進行該界面的按鍵位置條件判斷,從而消除誤觸執行其他功能的可能性。

問題三:當快速來回點擊觸摸屏兩個位置時,會出現點擊位置坐標讀取與實際觸摸坐標不一致的情況

分析:

​ 初步分析是因為讀取觸摸坐標的機制導致的,但是目前沒有辦法證實,期待後續的學習。

優化方向:

  1. 滿足一次條件判斷後,將存儲獲取參數的變數清空:經過實測發現,只要在滿足條件判斷後,將變數內參數情況,再進行讀取賦值操作,便可以絲滑進行界面切換,不會出現讀取坐標與實際觸摸坐標不同的情況。

    同樣,原理未知,期待後續學習補充。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 設置SSH免密登錄本機主要涉及生成密鑰對、將公鑰複製到本地(或遠程伺服器,如果是雙向免密)以及測試免密登錄等步驟。以下是一個基本的設置流程: 生成密鑰對: 打開終端或命令提示符,並執行以下命令來生成RSA密鑰對:ssh-keygen -t rsa 系統將會提示你指定保存密鑰文件的路徑和文件名。預設情 ...
  • 簡單回顧 在開始 lab3 的學習之前,我們先簡單回顧下 到目前為止,我們的內核能做了什麼: lab1中,我們學習了 PC啟動的過程,看到BIOS將我們編寫的boot loader 載入記憶體,然後通過bootloader 將內核載入記憶體。同時,使用了一個寫死的臨時頁表(entry_pgdir)完成了 ...
  • 要在Nginx中配置允許跨域(Cross-Origin Resource Sharing, CORS),你需要修改Nginx的配置文件(通常是nginx.conf或者某個包含在nginx.conf中的單獨的配置文件)。下麵是一個基本的例子,展示瞭如何在Nginx中設置CORS: 打開你的Nginx配 ...
  • 具體的軟硬體實現點擊 http://mcu-ai.com/ MCU-AI技術網頁_MCU-AI 打鼾是一種普遍的癥狀,嚴重影響睡眠呼吸障礙患者(單純打鼾者)、阻塞性睡眠呼吸暫停(OSA)患者及其床伴的生活質量。研究表明,打鼾可用於OSA的篩查和診斷。因此,從夜間睡眠呼吸音頻中準確檢測打鼾聲一直是最重 ...
  • Linux 是一種自由和開放源代碼的操作系統,它的使用在全球範圍內非常廣泛。在 Linux 中,進程是操作系統中最重要的組成部分之一,它代表了正在運行的程式。瞭解如何查看正在運行的進程是非常重要的,因為它可以幫助你瞭解系統的運行狀態並對其進行管理。今天飛飛將和你分享如何在 Linux 中查看正在運行... ...
  • Linux 可用 pid 上限是多少?如何提升上限?為何提升上限可以實時生效?Linux 底層如何實現 pid 快速分配與歸還?這種實現為何只需要極少的記憶體開銷?本文通過閱讀 Linux 內核源碼,一一為你揭秘 ...
  • 大家好,我是 Java陳序員。 俗話說,上班不摸魚,不如當頭驢。上班不摸魚是沒有靈魂的! 但是,上班摸魚需要有一定的技巧,需要與老闆鬥智鬥勇,需要時時刻刻註意查崗。 今天,給大家安利一個摸魚APP,幫助你更好的摸魚! 關註微信公眾號:【Java陳序員】,獲取開源項目分享、AI副業分享、超200本經典 ...
  • @目錄前言第一步:查看Docker Root目錄第二步:查到容器的長id(container id)第三步:停止容器第四步:編輯修改環境變數env第五步:重載服務的配置文件第六步:重啟docker總結 前言 請各大網友尊重本人原創知識分享,謹記本人博客:南國以南i、 提示:以下是本篇文章正文內容,下 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...