基于按鍵開源MultiButton框架深入理解代碼框架(三)(指針的深入理解與應用)

文章目錄

  • 3、分析代碼
    • 3.3 按鍵的插入
    • 3.4 按鍵的刪除
    • 3.5 繼續分析狀態機核心理解
  • 4、寫在最后的總結
  • 5、思想感悟篇
  • 6、慈悲不渡自絕人


3、分析代碼

3.3 按鍵的插入


// Button handle list headstatic Button* head_handle = NULL;/*** @brief  Start the button work, add the handle into work list* @param  handle: target handle struct* @retval 0:succeed, -1:already exist, -2:invalid parameter*/int button_start(Button* handle){if (!handle) return -2;  // invalid parameterButton* target = head_handle;while (target) {if (target == handle) return -1;  // already existtarget = target->next;}handle->next = head_handle;head_handle = handle;return 0;}

主要是最后兩行代碼:

表示的是頭插法,也就是說只要是一個新的按鍵,這個新的按鍵結構體里面永遠存儲的是當前已經存在按鍵鏈表的第一個節點,然后我們的頭結點就被這個新按鍵給覆蓋掉。為什么head_handle可以被循環替換,這是因為我們前面定義了static關鍵字,它存在于整個程序生命周期,并且還只能在按鍵的文件調用。

但是需要注意的是,這個使用的是頭指針,不是頭結點方式插入。

概念??頭指針 (head_handle)???頭結點?
?定義?指向鏈表第一個節點的指針變量(存儲地址)位于鏈表首部的輔助節點?(不存儲有效數據)
?內存占用?僅占用一個指針的空間(如 4/8 字節)占用完整節點結構體的空間(含數據域和指針域)
?初始化?NULL(表示空鏈表)需動態分配內存(如 malloc
?static Button* head_handle = NULL;未定義,代碼中未創建頭結點
無頭結點節省了內存(尤其在小內存嵌入式設備中關鍵)。

3.4 按鍵的刪除

需要注意的是首節點刪除需特殊處理
若刪除鏈表第一個節點(如 btn2),需更新頭指針:

void delete_first_node() {Button* temp = head_handle;head_handle = head_handle->next;  // 更新頭指針free(temp);
}

相當于是直接把第一個結點內部存的下一個節點的直接賦值給當前的head_handle

在鏈表頭節點刪除操作中,使用臨時指針 Button* temp = head_handle; ?是必要且關鍵的,直接跳過此步驟僅更新頭指針(如 head_handle = head_handle->next;)會導致嚴重內存問題。

void delete_first_node() {head_handle = head_handle->next;  // 直接更新頭指針
}
  • 原頭節點(head_handle 指向的節點)未被釋放,其內存空間無法被系統回收。
  • 在嵌入式系統中,內存資源有限,頻繁操作后可能耗盡內存,引發系統崩潰。
  • 若其他代碼持有原頭節點的指針,該指針會指向已失效的內存區域(稱為野指針)。
  • 后續訪問野指針(如 head_handle->data)會導致未定義行為(程序崩潰或數據錯誤)。
  • 原頭節點的數據若需清理(如動態分配的子資源),跳過 free(temp) 會遺漏資源釋放

相當于是雖然改變了head_handle 指向的節點,但是之前結點的地址還是存著內容,所以我們需要將這個結點的入口地址傳遞給free(temp);進行釋放。

  • 安全釋放內存?
    temp 臨時保存原頭節點地址,確保 free() 能準確釋放該內存。

  • ?避免野指針?
    釋放后,temp 生命周期結束,不會遺留野指針(而原 head_handle 已更新指向新節點)。

  • ?支持資源清理?
    若節點包含動態分配的資源(如字符串緩沖區),可在 free(temp) 前先釋放其子資源。

  • 當執行 temp = head_handle 時,temp 保存了原頭節點的物理內存地址?(如 0x0012FF88)。

  • 此后 head_handle = head_handle->next 修改頭指針,但 temp 仍持有原節點的地址,確保能精準定位需釋放的內存塊。

  • 原節點成為“內存孤島”,程序失去對其訪問權,但內存仍被占用 → ?內存泄漏?(Memory Leak);

  • 在長期運行的嵌入式系統中,此類泄漏會累積耗盡內存,導致系統崩潰。

內存的釋放,釋放的是這個地址,而這個地址是存儲在指針變量里面的,注意我們不是釋放指針變量,是釋放指針變量存儲的地址,這個一定要繞過來,別被繞進去了。這一點很關鍵。

3.5 繼續分析狀態機核心理解

static void button_handler(Button* handle)
{uint8_t read_gpio_level = button_read_level(handle);// Increment ticks counter when not in idle stateif (handle->state > BTN_STATE_IDLE) {handle->ticks++;}/*------------Button debounce handling---------------*/if (read_gpio_level != handle->button_level) {// Continue reading same new level for debounceif (++(handle->debounce_cnt) >= DEBOUNCE_TICKS) {handle->button_level = read_gpio_level;handle->debounce_cnt = 0;}} else {// Level not changed, reset counterhandle->debounce_cnt = 0;}/*-----------------State machine-------------------*/switch (handle->state) {case BTN_STATE_IDLE:if (handle->button_level == handle->active_level) {// Button press detectedhandle->event = (uint8_t)BTN_PRESS_DOWN;EVENT_CB(BTN_PRESS_DOWN);handle->ticks = 0;handle->repeat = 1;handle->state = BTN_STATE_PRESS;} else {handle->event = (uint8_t)BTN_NONE_PRESS;}break;case BTN_STATE_PRESS:if (handle->button_level != handle->active_level) {// Button releasedhandle->event = (uint8_t)BTN_PRESS_UP;EVENT_CB(BTN_PRESS_UP);handle->ticks = 0;handle->state = BTN_STATE_RELEASE;} else if (handle->ticks > LONG_TICKS) {// Long press detectedhandle->event = (uint8_t)BTN_LONG_PRESS_START;EVENT_CB(BTN_LONG_PRESS_START);handle->state = BTN_STATE_LONG_HOLD;}break;case BTN_STATE_RELEASE:if (handle->button_level == handle->active_level) {// Button pressed againhandle->event = (uint8_t)BTN_PRESS_DOWN;EVENT_CB(BTN_PRESS_DOWN);if (handle->repeat < PRESS_REPEAT_MAX_NUM) {handle->repeat++;}EVENT_CB(BTN_PRESS_REPEAT);handle->ticks = 0;handle->state = BTN_STATE_REPEAT;} else if (handle->ticks > SHORT_TICKS) {// Timeout reached, determine click typeif (handle->repeat == 1) {handle->event = (uint8_t)BTN_SINGLE_CLICK;EVENT_CB(BTN_SINGLE_CLICK);} else if (handle->repeat == 2) {handle->event = (uint8_t)BTN_DOUBLE_CLICK;EVENT_CB(BTN_DOUBLE_CLICK);}handle->state = BTN_STATE_IDLE;}break;case BTN_STATE_REPEAT:if (handle->button_level != handle->active_level) {// Button releasedhandle->event = (uint8_t)BTN_PRESS_UP;EVENT_CB(BTN_PRESS_UP);if (handle->ticks < SHORT_TICKS) {handle->ticks = 0;handle->state = BTN_STATE_RELEASE;  // Continue waiting for more presses} else {handle->state = BTN_STATE_IDLE;  // End of sequence}} else if (handle->ticks > SHORT_TICKS) {// Held down too long, treat as normal presshandle->state = BTN_STATE_PRESS;}break;case BTN_STATE_LONG_HOLD:if (handle->button_level == handle->active_level) {// Continue holdinghandle->event = (uint8_t)BTN_LONG_PRESS_HOLD;EVENT_CB(BTN_LONG_PRESS_HOLD);} else {// Released from long presshandle->event = (uint8_t)BTN_PRESS_UP;EVENT_CB(BTN_PRESS_UP);handle->state = BTN_STATE_IDLE;}break;default:// Invalid state, reset to idlehandle->state = BTN_STATE_IDLE;break;}
}

在這里插入圖片描述

基于以上兩個圖片進行相關內容解析

1、如果一個按鍵是按下有效,那么就要避免增加長按功能的。
首先根據這兩個圖可以看出無論是短按或者長按都會產生一下按鍵按下事件,因此如果一個按鍵在設計為長按有效(不論是長按抬起有效還是按下有效)的同時還具有按下有效的功能,都會觸發按鍵按下事件。這樣就會造成功能沖突,也就是在想要長按功能的時候必定會帶來短按功能的啟動,并且在當前狀態機時沒辦法解決的,因此只能在設計的時候避免。

2、該狀態機在按鍵長按模式下,還可以區分按鍵長按抬起有效和按鍵長按按下有效。
因為在按鍵長按的過程中會有劃分了三個事件:
第一個是按鍵長按開始事件(按鍵按下狀態機),這個事件就是按鍵屬性為長按按下有效發生,因為只要滿足了長按的時間閾值就會立即觸發該事件;
第二個是按鍵長按保持事件(按鍵長按保持狀態機),這個事件只是單純的在超過按鍵長按時間閾值以后會一直觸發的事件,保持的時間并沒有明確要求,只要是超過按鍵長按時間閾值以后的時間都屬于按鍵長按保持時間并且會觸發按鍵長安保持事件;
第三個是按鍵長按抬起事件(按鍵長按保持狀態機),這個事件就是表示在按鍵長按保存狀態的時候釋放按鍵,那么就會產生電平跳變,在消抖以后就會判斷出按鍵抬起,此時就是觸發按鍵長按抬起事件。
長按保持這段時間在當前狀態機是沒有辦法衡量的,因為沒有增加相關變量記錄時間。

3、該狀態機按鍵短按單擊完成的判斷時間需要仔細衡量一下。
這是因為在短按按下動作和抬起動作會有消抖因素的存在,因此這一部分其實也是占用時間的,而按鍵短按單擊完成(按鍵釋放狀態機)判斷的時間累計是從該按鍵短按抬起以后開始累計的。并且按鍵單擊完成和雙擊完成都是在按鍵釋放狀態機完成相關事件觸發的。

4、該狀態機Tick時間的更新。
Tick時間的更新條件是(按鍵狀態> 按鍵釋放狀態),也就是說存在一種情況再按鍵長按的時候,Tick是一直增加的,但是在按鍵釋放狀態機和按鍵長按保持狀態機都是沒有清零的,如果按鍵長按松開以后狀態變為按鍵空閑狀態,此時Tick就不會增加, 按照現有代碼清零是在該按鍵下一次按下的時候清空這個Tick。可以在這里進行改進,將Tick清零放在(按鍵長按保持狀態機)按鍵長按抬起的時候。

在這里插入圖片描述
狀態機:按鍵釋放狀態。

此時間是兩次雙擊最大間隔時間,如果超過這個間隔,就會觸發是按鍵短按單擊事件。

狀態機:按鍵釋放狀態。
此時處于第二次按鍵按下狀態,首先觸發第二次按鍵按下事件,repeat++自增,接著觸發重復按下第二次事件,清空時間tick=0,進入按鍵重復狀態機。
觸發重復按下第二次事件:這個地方可以理解為只要出現第二次按鍵按下的狀態,就認為連擊完成,僅作為理解。

分析:分析按鍵重復狀態機

需要在這里著重分析里面的狀態,因為源碼在這里存在一些不合理之處。
1、按鍵重復狀態機在第二次按鍵按下動作以后并沒有釋放而是一直按下,此時tick是從零自增,那么時間如果超過SHORT_TICKS狀態機跳轉至按鍵按下狀態機BTN_STATE_PRESS,并且一直還是按下,那么最終會進入按鍵長按保持狀態機BTN_STATE_LONG_HOLD,這種情況并無什么影響,也符合邏輯。但是如果我在按鍵長按判斷之前釋放按鍵,此時出現按鍵抬起事件,tick清空并且進入到按鍵釋放狀態機,然后在該狀態機重新執行handle->ticks > SHORT_TICKS判斷,并且由于前面repeat自增,導致repeat=2,那么就會觸發按鍵雙擊完成事件,這在邏輯是不合理的因為這個過程已經過去了很多個時間,準確來說已經不屬于連擊的范疇了。

2、按鍵重復狀態機第二次按鍵抬起, 首先會經過抬起動作消抖,接著會產生一個第二次按鍵抬起事件,并且tick會自增,并且這個時間是理論上是小于handle->ticks < SHORT_TICKS,這是因為上一個狀態是按下,時間會一直自增,接著抬起消抖完成時間不會清空,因為是在SHORT_TICKS時間內完成的抬起,不然就已經跳到了上述分析的情況1,但是由于消抖會有時間,因此也可能存在卡點,那么這種情況就是直接將狀態機跳轉至按鍵釋放狀態,然后產生一個無按鍵按下事件,這種情況也是無傷大雅,符合邏輯。接著我們繼續分析符合理論情況的時間小于SHORT_TICKS,首先將ticks清空,然后狀態機跳轉至按鍵釋放狀態BTN_STATE_RELEASE,最后在延時一個SHORT_TICKS時間,由于此時repeat=2,因此完美的執行按鍵雙擊完成事件,

在這里插入圖片描述

假如一個按鍵是按下有效,那么就要避免增加長按功能的,因為這種設計會造成功能沖突,也就是說當這個按鍵按下的時候,首先會觸發按下有效事件,接著會觸發長按事件功能。

如果想把一個按鍵集成短按有用和長按都有不同的功能,那其實就需要將短按設計為抬起有效,這樣如果在長按的過程中沒有抬起,那就不會觸發短按功能,按鍵按下的時間累計達到長按閾值,就會觸發長按功能,這樣就完美的避免了長按和短按同時存在,長按會先觸發短按功能。

4、寫在最后的總結

根據開源按鍵框架MultiButton-master,在basic_example.c文件里面有一個buttons_init函數,其作用是首先是初始化一個 button_init(&btn1, read_button_gpio, 1, 1);但是這個函數 是在multi_button.c里面,因此為了避免暴露bnt1,傳入的是這個結構體的首地址,完成初始化以后,更新鏈表 button_start(&btn1); button_start(&btn2);同樣這兩個函數也在multi_button.c里面,而在文件multi_button.c里面static Button* head_handle = NULL;鏈表用來接收bt1和bt2,并且在調用按鍵處理函數的時候 simulate_button_press(1, 100);并沒有傳入btn1和btn2,并且這個函數是實在multi_button.c里面,所以他實際執行 void button_ticks(void) { Button* target; for (target = head_handle; target; target = target->next) { button_handler(target); } }里面的head_handle這個鏈表已經包含了btn1和btn2,,這樣既保證了按鍵的數據流動,又讓處理過程在basic_example.c不可見,并且bt1和btn2對于multi_button.c也是不可見。 這就是模塊化編程的意義。

5、思想感悟篇

后續還會再更新一篇文章單獨聊一聊這個按鍵框架的思想,為什么要這么做,試圖體會作者的思想,進而觸類旁通,舉一反三。

加油!!!!!!!

6、慈悲不渡自絕人

未完待續…


文章源碼獲取方式:
如果您對本文的源碼感興趣,歡迎在評論區留下您的郵箱地址。我會在空閑時間整理相關代碼,并通過郵件發送給您。由于個人時間有限,發送可能會有一定延遲,請您耐心等待。同時,建議您在評論時注明具體的需求或問題,以便我更好地為您提供針對性的幫助。

【版權聲明】
本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議。這意味著您可以自由地共享(復制、分發)和改編(修改、轉換)本文內容,但必須遵守以下條件:
署名:您必須注明原作者(即本文博主)的姓名,并提供指向原文的鏈接。
相同方式共享:如果您基于本文創作了新的內容,必須使用相同的 CC 4.0 BY-SA 協議進行發布。

感謝您的理解與支持!如果您有任何疑問或需要進一步協助,請隨時在評論區留言。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/91562.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/91562.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/91562.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ACOUSLIC-AI挑戰報告:基于低收入國家盲掃超聲數據的胎兒腹圍測量|文獻速遞-醫學影像算法文獻分享

Title題目ACOUSLIC-AI challenge report: Fetal abdominal circumferencemeasurement on blind-sweep ultrasound data from low-income countriesACOUSLIC-AI挑戰報告&#xff1a;基于低收入國家盲掃超聲數據的胎兒腹圍測量01文獻速遞介紹胎兒生長受限&#xff08;FGR&#xf…

集群聊天服務器各個類進行詳解

1.dh.h類定義概要類名&#xff1a; MySQL功能&#xff1a; 簡化MySQL的連接、查詢和更新操作&#xff0c;提供接口給上層應用使用。成員變量private:MYSQL *_conn;_conn&#xff1a;指向MYSQL結構體的指針&#xff0c;用于代表數據庫連接實例。由mysql_init()初始化&#xff0c…

電纜安全雙保險:不止防盜,更能防觸電的塔能智慧照明守護方案

城市照明、地下車庫以及園區路燈所涉及的電纜安全問題&#xff0c;向來都是運維管理方面頗為棘手的難題。在傳統的運維管理模式之下&#xff0c;電纜一旦被盜&#xff0c;那么所造成的影響可不小&#xff0c;一方面會帶來直接的經濟損失&#xff0c;另一方面還極有可能因為線路…

Leetcode刷題營第二十九,三十題:二叉樹的中序以及后序遍歷

94.二叉樹的中序遍歷 給定一個二叉樹的根節點 root &#xff0c;返回 它的 中序 遍歷 。 示例 1&#xff1a; 輸入&#xff1a;root [1,null,2,3] 輸出&#xff1a;[1,3,2]示例 2&#xff1a; 輸入&#xff1a;root [] 輸出&#xff1a;[]示例 3&#xff1a; 輸入&#x…

Rabbitmq Direct Exchange(直連交換機)可以保證消費不被重復消費嗎,可以多個消費者,但是需要保證同一個消息,不會被投遞給多個消費者

在 RabbitMQ 中&#xff0c;默認情況下&#xff0c;不能保證消息不被重復消費&#xff0c;但可以通過 隊列綁定方式 消費者競爭機制 來確保 同一消息只被一個消費者處理。以下是幾種可行的方案&#xff1a;方案 1&#xff1a;單隊列 競爭消費者模式&#xff08;默認行為&…

常用的OTP語音芯片有哪些?

唯創知音在 OTP 語音芯片有著26年的歷史&#xff0c;有著豐富的技術積累與產品迭代歷程。1999 年&#xff0c;唯創知音在廣州成立&#xff0c;彼時便開始在電子領域積極探索。2000 年&#xff0c;公司敏銳捕捉到語音芯片行業的發展潛力&#xff0c;正式進軍該領域。經過數年技術…

分布式光伏發電系統中的“四可”指的是什么?

在分布式光伏電站規模爆發式增長的今天&#xff0c;“看不見、管不住、調不動”的難題卻成為行業痛點。如何讓散布各處的光伏電站真正成為穩定高效的“綠色能量站”&#xff1f;2025年《分布式光伏發電開發建設管理辦法》大型工商業項目&#xff08;≥6MW&#xff09;明確要求具…

健康管理系統新趨勢:AI + 物聯網如何重塑健康管理

一、傳統健康管理的痛點與變革之必然長久以來&#xff0c;我們熟悉的健康管理方式存在明顯局限&#xff1a;數據孤島嚴重&#xff1a;體檢報告在抽屜里沉睡&#xff0c;健身手環數據僅存于手機&#xff0c;不同醫療機構信息互不相通&#xff0c;個人健康信息猶如碎片散落各處。…

git基本操作【GIT-2】

git基本操作初始化一個倉庫&#xff08;repository&#xff09;、開始或停止跟蹤&#xff08;track&#xff09;文件、暫存&#xff08;stage&#xff09;或提交&#xff08;commit&#xff09;更改如何配置 Git 來忽略指定的文件和文件模式、如何迅速而簡單地撤銷錯誤操作、如…

【數據準備】——深度學習.全連接神經網絡

目錄 1 數據加載器 1.1 構建數據類 1.1.1 Dataset類 1.1.2 TensorDataset類 1.2 數據加載器 2 數據加載案例 2.1 加載csv數據集 2.2 加載圖片數據集 2.3 加載官方數據集 2.4 pytorch實現線性回歸 1 數據加載器 分數據集和加載器2個步驟~ 1.1 構建數據類 1.1.1 Dat…

健康生活,從細節開始

健康生活&#xff0c;從細節開始在當今快節奏的生活中&#xff0c;健康逐漸成為人們關注的焦點。擁有健康的身體&#xff0c;才能更好地享受生活、追求夢想。那么&#xff0c;如何才能擁有健康呢&#xff1f;這就需要我們從生活中的點滴細節入手&#xff0c;培養良好的生活習慣…

javax.servlet.http.HttpServletResponse;API導入報錯解決方案

javax.servlet.http.HttpServletResponse;API導入報錯解決方案與Postman上傳下載文件驗證 1. 主要錯誤&#xff1a;缺少 Servlet API 依賴 錯誤信息顯示 javax.servlet.http 包不存在。這是因為你的項目缺少 Servlet API 依賴。 解決方案&#xff1a; 如果你使用的是 Maven&…

reids依賴刪除,但springboot仍然需要redis參數才能啟動

背景&#xff1a;項目需要刪除redis。我刪除完項目所有配置redis的依賴&#xff0c;啟動報錯。[2025-07-17 15:08:37:561] [DEBUG] [restartedMain] DEBUG _.s.w.s.H.Mappings - [detectHandlerMethods,295] [] - o.s.b.a.w.s.e.BasicErrorController:{ [/error]}: error(HttpS…

【前端】CSS類命名規范指南

在 CSS 中&#xff0c;合理且規范的 class 命名格式對項目的可維護性和協作效率至關重要。以下是主流的 class 命名規范和方法論&#xff1a;一、核心命名原則語義化命名&#xff1a;描述功能而非樣式 ? .search-form&#xff08;描述功能&#xff09;? .red-text&#xff08…

C++網絡編程 4.UDP套接字(socket)編程示例程序

以下是基于UDP協議的完整客戶端和服務器代碼。UDP與TCP的核心區別在于無連接特性&#xff0c;因此代碼結構會更簡單&#xff08;無需監聽和接受連接&#xff09;。 UDP服務器代碼&#xff08;udp_server.cpp&#xff09; #include <iostream> #include <sys/socket.h&…

King’s LIMS:實驗室數字化轉型的智能高效之選

實驗室數字化轉型不僅是技術升級&#xff0c;更是管理理念和工作方式的革新。LIMS系統作為這一轉型的核心工具&#xff0c;能夠將分散的實驗數據轉化為可分析、可復用的資產&#xff0c;為科研決策提供支持&#xff1b;規范檢測流程&#xff0c;減少人為干預&#xff0c;確保結…

【力扣 中等 C】97. 交錯字符串

目錄 題目 解法一 題目 待添加 解法一 bool isInterleave(char* s1, char* s2, char* s3) {const int len1 strlen(s1);const int len2 strlen(s2);const int len3 strlen(s3);if (len1 len2 ! len3) {return false;}if (len1 < len2) {return isInterleave(s2, s1,…

Class9簡潔實現

Class9簡潔實現 %matplotlib inline import torch from torch import nn from d2l import torch as d2l# 初始化訓練樣本、測試樣本、樣本特征維度和批量大小 n_train,n_test,num_inputs,batch_size 20,100,200,5 # 設置真實權重和偏置 true_w,true_b torch.ones((num_inputs…

ELK日志分析,涉及logstash、elasticsearch、kibana等多方面應用,必看!

目錄 ELK日志分析 1、下載lrzsc 2、下載源包 3、解壓文件,下載elasticsearch、kibana、 logstash 4、配置elasticsearch 5、配種域名解析 6、配置kibana 7、配置logstash 8、進行測試 ELK日志分析 1、下載lrzsc [rootlocalhost ~]# hostnamectl set-hostname elk ##…

終極剖析HashMap:數據結構、哈希沖突與解決方案全解

文章目錄 引言 一、HashMap底層數據結構&#xff1a;三維存儲架構 1. 核心存儲層&#xff08;硬件優化設計&#xff09; 2. 內存布局對比 二、哈希沖突的本質與數學原理 1. 沖突產生的必然性 2. 沖突概率公式 三、哈希沖突解決方案全景圖 1. 鏈地址法&#xff08;Hash…