1. 程式人生 > >純C語言寫的按鍵驅動,將按鍵邏輯與按鍵處理事件分離~

純C語言寫的按鍵驅動,將按鍵邏輯與按鍵處理事件分離~

button drive

傑傑自己寫的一個按鍵驅動,支援單雙擊、連按、長按;採用回撥處理按鍵事件(自定義消抖時間),使用只需3步,建立按鍵,按鍵事件與回撥處理函式連結對映,週期檢查按鍵。 原始碼地址:https://github.com/jiejieTop/ButtonDrive。作者:傑傑

前言

前幾天寫了個按鍵驅動,參考了MulitButton的資料結構的用法,邏輯實現並不一樣。 在這裡感謝所有的開源開發者,讓我從中學到了很多,同時網路也是一個好平臺,也希望所有的開發者能形成良性迴圈,從網路中學知識,回饋到網路中去。感謝MulitButton的作者0x1abin,感謝兩位rtt的大佬:大法師流光

Button_drive簡介

Button_drive是一個小巧的按鍵驅動,支援單擊、雙擊、長按、連續觸發等(後續可以在按鍵控制塊中新增觸發事件),理論上可無限量擴充套件Button,Button_drive採用按鍵觸發事件回撥方式處理業務邏輯,支援在RTOS中使用,我目前僅在RT-Thread上測試過。 寫按鍵驅動的目的是想要將使用者按鍵邏輯與按鍵處理事件分離,使用者無需處理複雜麻煩的邏輯事件。

Button_drive使用效果

  1. 單擊與長按

單擊與長按

  1. 雙擊

雙擊

  1. 連按

連按

  1. 連按釋放

連按釋放

使用方法

  1. 建立按鍵控制代碼
Button_t Button1;
Button_t Button2; 
  1. 建立按鍵,初始化按鍵資訊,包括按鍵名字、按鍵電平檢測函式介面、按鍵觸發電平。
  Button_Create("Button1",				//按鍵名字
                &Button1, 				//按鍵控制代碼
                Read_Button1_Level, 	//按鍵電平檢測函式介面
                BTN_TRIGGER);		   	//觸發電平
                
                ......
  1. 按鍵觸發事件與事件回撥函式連結對映,當按鍵事件被觸發的時候,自動跳轉回調函式中處理業務邏輯。
  Button_Attach(&Button1,BUTTON_DOWM,Btn2_Dowm_CallBack);		//按鍵單擊
  Button_Attach(&Button1,BUTTON_DOUBLE,Btn2_Double_CallBack);	//雙擊
  Button_Attach(&Button1,BUTTON_LONG,Btn2_Long_CallBack);		//長按
				
				.......
  1. 週期呼叫回撥按鍵處理函式即可,建議呼叫週期20-50ms。
Button_Process();     //需要週期呼叫按鍵處理函式

需要使用者實現的 2 個函式:

  • 按鍵電平檢測介面:
uint8_t Read_Button1_Level(void)
{
  return GPIO_ReadInputDataBit(BTN1_GPIO_PORT,BTN1_GPIO_PIN);
}

uint8_t Read_Button2_Level(void)
{
  return GPIO_ReadInputDataBit(BTN2_GPIO_PORT,BTN2_GPIO_PIN);
}

// 這是我在stm32上簡單測試的虛擬碼,以實際原始碼為準

  • 按鍵邏輯處理
void Btn1_Dowm_CallBack(void *btn)
{
  PRINT_INFO("Button1 單擊!");
}

void Btn1_Double_CallBack(void *btn)
{
  PRINT_INFO("Button1 雙擊!");
}

void Btn1_Long_CallBack(void *btn)
{
  PRINT_INFO("Button1 長按!");
  
  Button_Delete(&Button2);
  PRINT_INFO("刪除Button1");
  Search_Button();
}

特點

Button_drive開放原始碼,按鍵控制塊採用資料結構方式,按鍵事件採用列舉型別,確保不會重複,也便於新增使用者需要邏輯,採用巨集定義方式定義消抖時間、連按觸發時間、雙擊時間間隔、長按時間等,便於修改。 同時所有被建立的按鍵採用單鏈表方式連擊,使用者只管建立,無需理會按鍵處理,只需呼叫Button_Process()即可,在函式中會自動遍歷所有被建立的按鍵。 支援按鍵刪除操作,使用者無需在程式碼中刪除對應的按鍵創建於對映連結程式碼,也無需刪除關於按鍵的任何回撥事件處理函式,只需呼叫Button_Delete()函式即可,這樣子,就不會處理關於被刪除按鍵的任何狀態。當然目前按鍵記憶體不會釋放,如果使用os的話,建議釋放按鍵記憶體。

按鍵控制塊
/*
	每個按鍵對應1個全域性的結構體變數。
	其成員變數是實現消抖和多種按鍵狀態所必須的
*/
typedef struct button
{
	/* 下面是一個函式指標,指向判斷按鍵手否按下的函式 */
	uint8_t (*Read_Button_Level)(void); /* 讀取按鍵電平函式,需要使用者實現 */
  
  char Name[BTN_NAME_MAX];
  	
  uint8_t Button_State              :   4;	  /* 按鍵當前狀態(按下還是彈起) */
  uint8_t Button_Last_State         :   4;	  /* 上一次的按鍵狀態,用於判斷雙擊 */
  uint8_t Button_Trigger_Level      :   2;    /* 按鍵觸發電平 */
  uint8_t Button_Last_Level         :   2;    /* 按鍵當前電平 */
  
  uint8_t Button_Trigger_Event;     /* 按鍵觸發事件,單擊,雙擊,長按等 */
  
  Button_CallBack CallBack_Function[number_of_event];
  uint8_t Button_Cycle;	           /* 連續按鍵週期 */
  
  uint8_t Timer_Count;			/* 計時 */
  uint8_t Debounce_Time;		/* 消抖時間 */
  
  uint8_t Long_Time;		  /* 按鍵按下持續時間 */
  
  struct button *Next;
  
}Button_t;

觸發事件
typedef enum {
  BUTTON_DOWM = 0,
  BUTTON_UP,
  BUTTON_DOUBLE,
  BUTTON_LONG,
  BUTTON_CONTINUOS,
  BUTTON_CONTINUOS_FREE,
  BUTTON_ALL_RIGGER,
  number_of_event, /* 觸發回撥的事件 */
  NONE_TRIGGER
}Button_Event;

巨集定義選擇
#define BTN_NAME_MAX  32     //名字最大為32位元組

/* 按鍵消抖時間40ms, 建議呼叫週期為20ms
 只有連續檢測到40ms狀態不變才認為有效,包括彈起和按下兩種事件
*/

#define CONTINUOS_TRIGGER             0  //是否支援連續觸發,連發的話就不要檢測單雙擊與長按了	

/* 是否支援單擊&雙擊同時存在觸發,如果選擇開啟巨集定義的話,單雙擊都回調,只不過單擊會延遲響應,
   因為必須判斷單擊之後是否觸發了雙擊否則,延遲時間是雙擊間隔時間 BUTTON_DOUBLE_TIME。
   而如果不開啟這個巨集定義,建議工程中只存在單擊/雙擊中的一個,否則,在雙擊響應的時候會觸發一次單擊,
   因為雙擊必須是有一次按下並且釋放之後才產生的 */
#define SINGLE_AND_DOUBLE_TRIGGER     1 

/* 是否支援長按釋放才觸發,如果開啟這個巨集定義,那麼長按釋放之後才觸發單次長按,
   否則在長按指定時間就一直觸發長按,觸發週期由 BUTTON_LONG_CYCLE 決定 */
#define LONG_FREE_TRIGGER             0 

#define BUTTON_DEBOUNCE_TIME 	  2   //消抖時間      (n-1)*呼叫週期
#define BUTTON_CONTINUOS_CYCLE  1	  //連按觸發週期時間  (n-1)*呼叫週期  
#define BUTTON_LONG_CYCLE       1	  //長按觸發週期時間  (n-1)*呼叫週期 
#define BUTTON_DOUBLE_TIME      15 	//雙擊間隔時間  (n-1)*呼叫週期  建議在200-600ms
#define BUTTON_LONG_TIME 	      50		/* 持續n秒((n-1)*呼叫週期 ms),認為長按事件 */

#define TRIGGER_CB(event)   \
        if(btn->CallBack_Function[event]) \
          btn->CallBack_Function[event]((Button_t*)btn)
例子
  Button_Create("Button1",
              &Button1, 
              Read_KEY1_Level, 
              KEY_ON);
  Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack);                       //單擊
  Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack);                   //雙擊
  Button_Attach(&Button1,BUTTON_CONTINUOS,Btn1_Continuos_CallBack);             //連按  
  Button_Attach(&Button1,BUTTON_CONTINUOS_FREE,Btn1_ContinuosFree_CallBack);    //連按釋放  
  Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack);                       //長按


  Button_Create("Button2",
              &Button2, 
              Read_KEY2_Level, 
              KEY_ON);
  Button_Attach(&Button2,BUTTON_DOWM,Btn2_Dowm_CallBack);                     //單擊
  Button_Attach(&Button2,BUTTON_DOUBLE,Btn2_Double_CallBack);                 //雙擊
  Button_Attach(&Button2,BUTTON_CONTINUOS,Btn2_Continuos_CallBack);           //連按
  Button_Attach(&Button2,BUTTON_CONTINUOS_FREE,Btn2_ContinuosFree_CallBack);  //連按釋放
  Button_Attach(&Button2,BUTTON_LONG,Btn2_Long_CallBack);                     //長按

  Get_Button_Event(&Button1);
  Get_Button_Event(&Button2);

後續

流光大佬的要求,讓我玩一玩RTT的rtkpgs,打算用Button_drive練一練手吧。

ButtonDrive在env使用

目前我已將按鍵驅動做成軟體包(packages),如果使用RT-Thread作業系統的話,可以在env中直接配置使用!

步驟如下:

  1. 選擇線上軟體包

  1. 選擇軟體包屬性為外設相關

  1. 選擇button_drive

  1. 進入驅動的選項配置(自帶預設屬性)

  1. 如果不懂按鍵的配置是什麼意思,按下“shift+?”,即可有解釋

  1. 編譯生成mdk/iar工程

buildpkg 是用於生成 RT-Thread package 的快速構建工具。

一個優秀的 package 應該是這樣的:

  1. 程式碼優雅, 規範化。
  2. examples 例程,提供通俗易懂的使用例程。
  3. SConscript 檔案,用於和 RT-Thread 環境一起進行編譯。
  4. README.md 文件,向用戶提供必要的功能說明。
  5. docs 資料夾, 放置除了 README 之外的其他細節文件。
  6. license 許可檔案,版權說明。

為了方便快速的生成 RT-Thread package 規範化模板 以及 減輕開源倉庫遷移 RT-Thread 的前期準備工作的負擔,基於此目的的 buildpkg 應運而生,為開發 Rt-Thread 的 package 的開發者提供輔助開發工具。

序號 支援功能 描述
1 構建 package 模板 建立指定名稱 package , 自動新增 readme /版本號/ github ci指令碼/demo/開源協議檔案
2 遷移開源倉庫 從指定 git 倉庫構建 package , 自動新增readme/版本號/ github ci指令碼/demo/開源協議檔案, 但是遷移的倉庫需要使用者自己按照實際情況修改
3 更新 package 生成package後可以再次更新之前設定的版本號,開源協議或者scons指令碼等

使用說明

1. 構建package

buildpkg.exe make pkgdemo

2. 遷移開源倉庫

3. 更新package

buildpkg.exe update pkgname

4. 可選配置

長引數 短引數 描述
–version=v1.0.0 -v v1.0.0 設定 package 的版本
–license=MIT -l MIT 設定 package 所遵循的版權協議
–submodule -s 刪除 git 子模組

Windows10 及 Linux 平臺的演示動圖

buildpkg

測試平臺

序號 測試平臺 測試結果
1 win10 exe測試通過, py測試通過
2 win7 exe待測試, py待測試
3 mac py指令碼不知道是否相容, 沒有測試條件, 後面維護下
4 linux py指令碼不知道是否相容, 沒有測試條件, 後面維護下

聯絡人