久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 4838|回復(fù): 4
打印 上一主題 下一主題
收起左側(cè)

[后續(xù)]單片機狀態(tài)機+事件驅(qū)動 按鍵掃描

  [復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:84652 發(fā)表于 2020-4-9 23:38 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
本帖最后由 王朗的誘惑 于 2020-4-14 22:26 編輯

前接[失敗]狀態(tài)機+事件驅(qū)動 按鍵掃描。(如果懶得看前面的內(nèi)容可以直接看本帖,無妨)
繼2018年10月實驗過一個按鍵掃描失敗,時隔將近一年半,后續(xù)它終于來了!
這次換用了IAP15W4K61S4,把之前的程序搞好了。必須要承認一個錯誤:之前單片機死機其實不是單片機速度問題,而是程序設(shè)計問題[慚愧……],現(xiàn)在已經(jīng)解決。這次的程序添加了事件隊列,進一步釋放定時器資源。定時器只負責(zé)添加任務(wù),由主程序清空任務(wù)隊列+執(zhí)行事件函數(shù)。所以芯片慢點也沒關(guān)系啦。

正文
狀態(tài)機、事件隊列
狀態(tài)機還是挺常見,不過為什么要用事件隊列呢?因為中斷函數(shù)不能拖得太長,如果定時器中斷執(zhí)行的事件函數(shù)還沒完,下一次中斷又來了可怎么辦……所以加入事件隊列機制,中斷只負責(zé)把需要執(zhí)行的事件入隊,由主函數(shù)執(zhí)行并清空隊列里面的事件函數(shù),也算是某種“異步”了吧。
假如主函數(shù)清空隊列的速度還沒有定時器入隊快,那么隊列滿了以后,之后的按鍵事件直接拋棄,不會響應(yīng)。
參考資料資源占用
  • 定時器0 - 按鍵掃描
  • 定時器1 - 串口1
  • Program Size: data=59.3 xdata=100 code=4214(MDK5 C51編譯,8級優(yōu)化,還是占了挺多空間的,不過功能強大)

功能說明
  • 支持button和switch
  • 支持button單擊、多擊、長按、多按鍵組合
  • 支持任意IO的按鍵連接

文件說明
  • KeyScanConfig.h
    按鍵掃描相關(guān)的一些常量,根據(jù)需要修改。
    1. #define     EN_P4                        //如果按鍵連接到P4上需要使能此項 有的封裝沒有P4
    2. #define     EN_P5                        //如果按鍵連接到P5上需要使能此項 有的封裝沒有P5
    3. #define     EN_P6                        //如果按鍵連接到P6上需要使能此項 有的封裝沒有P6
    4. typedef     unsigned int        keyTriggerType_t;    //數(shù)據(jù)類型是幾位就支持幾個按鍵 定義多了占內(nèi)存
    5. #define     MAX_KEY_NUMBER      (sizeof(keyTriggerType_t)<<3)//最大支持按鍵數(shù)量為keyTriggerType_t的位數(shù)即(sizeof(keyTriggerType_t)*8)

    6. #define     DEBOUNCE_TIME               20          //消抖延時ms
    7. #define     LONG_PRESS_TIME             1500        //長按判定時間ms
    8. #define     N_CLICK_NUMBER              2           //連擊判定次數(shù)
    9. #define     N_CLICK_TIMELIMIT           300         //連擊間隔超時時間ms (超過此時間判定為單擊)
    10. #define     EVENT_QUEUE_LEN             8           // 事件隊列長度,為了簡化計算,需要為2的整數(shù)次冪
    復(fù)制代碼

  • KeyScan.h
    各種枚舉、結(jié)構(gòu)體、函數(shù)定義。不需要用戶修改。
  • KeyScan.c
    按鍵掃描的實現(xiàn)。不需要用戶修改。

使用方法
以下步驟在主函數(shù)main.c中操作。
  • 把KeyScan.h, KeyScan.c, KeyScanConfig.h放到工程目錄下。
  • 在主函數(shù)中引入頭文件
    1. #include "main.h"
    2. #include "Uart.h"
    3. #include "KeyScan.h"
    復(fù)制代碼
    main.h包含基本數(shù)據(jù)類型的typedef定義,一些C庫的頭文件引入和系統(tǒng)時鐘設(shè)置;
    Uart.h是單片機串口模塊頭文件;
    KeyScan.h是按鍵掃描模塊頭文件。
  • 假如現(xiàn)在有4個按鍵A,B,C,D,給按鍵編號,放到枚舉類型里。
    1. enum EnumUserKey{                 //按鍵編號 從0開始 不得超過(MAX_KEY_NUMBER-1)
    2.     EnumKey_A = 0,
    3.     EnumKey_B = 1,
    4.     EnumKey_C = 2,
    5.     EnumKey_D = 3
    6. };
    復(fù)制代碼

  • 定義按鍵相關(guān)的兩個結(jié)構(gòu)體,作為全局變量。GPIO_KEY_NUM是第3步中按鍵的數(shù)量,這里是4;FUNC_KEY_NUM是第5步中事件函數(shù)的數(shù)量,這里是3,功能與按鍵是獨立的,數(shù)量可以不相等。
    1. #define GPIO_KEY_NUM 4                                  // 按鍵總數(shù),即enum EnumUserKey定義的按鍵數(shù)量
    2. xdata KeyIO_t SingleKey[GPIO_KEY_NUM];                  // 按鍵IO數(shù)組
    3. #define FUNC_KEY_NUM 3                                  // 用戶自定義的功能總數(shù)
    4. xdata KeyFunc_t KeyFuncs[FUNC_KEY_NUM];                 // 按鍵功能數(shù)組
    復(fù)制代碼

  • 定義按鍵功能函數(shù)。比如,需要按鍵A,B單擊分別觸發(fā),按鍵C,D同時按下觸發(fā),功能函數(shù)可以定義成下面這樣,函數(shù)名字隨意。
    1. void KeyAPressEvent(void){
    2.     P40 = ~P40;
    3. }
    4. void KeyBPressEvent(void){
    5.     Delay100ms();
    6. }
    7. void KeyCDPressEvent(void){
    8.     P41 = ~P41;
    9.     // printf發(fā)送長串被中斷打斷會死機,使用UartSendString
    10.     // 如果很短可以使用printf
    11.     UartSendString("testtesttesttesttesttesttesttesttesttesttesttesttest\r\n");
    12.     Delay100ms();       // 長延時也不會死機了,哈哈
    13.     UartSendString("testtesttesttesttesttesttesttesttesttesttesttesttest\r\n");
    14.     Delay100ms();
    15. }
    復(fù)制代碼

  • 好的,現(xiàn)在按鍵有了,功能也有了,但是還沒聯(lián)系到一起。下面是按鍵掃描初始化函數(shù),把它們聯(lián)系起來。
    1. //按鍵掃描初始化
    2. void KeyInit(void){
    3.     u8 i;
    4.     // 函數(shù)指針必須全部初始化為NULL
    5.     for(i=0; i<FUNC_KEY_NUM; i++){
    6.         KeyFuncs.fp_singleClick = NULL;
    7.         KeyFuncs.fp_comboClick = NULL;
    8.         KeyFuncs.fp_longPress = NULL;
    9.         KeyFuncs.fp_multiPress = NULL;
    10.     }
    11.    
    12.     // 注冊按鍵 Port1必須是IO口 Port2是IO口或"GND"
    13.     SingleKey[EnumKey_A].IOPort1 = "P36"; SingleKey[EnumKey_A].IOPort2 = "GND";
    14.     SingleKey[EnumKey_B].IOPort1 = "P52"; SingleKey[EnumKey_B].IOPort2 = "GND";
    15.     SingleKey[EnumKey_C].IOPort1 = "P54"; SingleKey[EnumKey_C].IOPort2 = "GND";
    16.     SingleKey[EnumKey_D].IOPort1 = "P53"; SingleKey[EnumKey_D].IOPort2 = "GND";
    17.    
    18.     // 需要響應(yīng)的鍵值 注意是鍵值! 不是鍵編號! 組合按鍵用或
    19.     KeyFuncs[0].triggerValue = TRIGGER_VALUE(EnumKey_A);
    20.     // 注冊回調(diào)函數(shù)為單擊功能
    21.     KeyFuncs[0].fp_singleClick = KeyAPressEvent;
    22.    
    23.     // 需要響應(yīng)的鍵值 注意是鍵值! 不是鍵編號! 組合按鍵用或
    24.     KeyFuncs[1].triggerValue = TRIGGER_VALUE(EnumKey_B);
    25.     // 注冊回調(diào)函數(shù)為單擊功能
    26.     KeyFuncs[1].fp_singleClick = KeyBPressEvent;
    27.    
    28.     // 需要響應(yīng)的鍵值 注意是鍵值! 不是鍵編號! 組合按鍵用或
    29.     KeyFuncs[2].triggerValue = TRIGGER_VALUE(EnumKey_C) | TRIGGER_VALUE(EnumKey_D);
    30.     // 注冊回調(diào)函數(shù)為組合鍵功能
    31.     KeyFuncs[2].fp_multiPress = KeyCDPressEvent;
    32.    
    33.     KeyScanInit((KeyIO_t*)&SingleKey, GPIO_KEY_NUM, (KeyFunc_t*)&KeyFuncs, FUNC_KEY_NUM);
    34. }
    復(fù)制代碼
    初始化過程可以分為4個步驟:

    • 初始化函數(shù)指針為NULL,這里的函數(shù)指針變量來自第4步定義的xdata KeyFunc_t KeyFuncs[FUNC_KEY_NUM]
    • 告訴單片機按鍵的硬件連線位置,假如按鍵A,B,C,D的一端分別連到單片機的P36,P52,P54,P53上,另一端接地,就按照上面的程序設(shè)置。如果按鍵是矩陣的,沒有接地,就把按鍵兩端的IO都對應(yīng)寫成字符串。

    (這么做的好處就是可以把按鍵隨便亂接,畢竟有的封裝,比如SOP-16是沒有完整的一組8bit IO引出的,假如在這個單片機上用傳統(tǒng)的方式應(yīng)用4×4矩陣鍵盤,位處理是不是特別難受?)
    • 這一步把按鍵和一個事件函數(shù)聯(lián)系起來。
      需要用到TRIGGER_VALUE宏,把按鍵編號轉(zhuǎn)換成觸發(fā)值,如果用到組合鍵,把各個按鍵的觸發(fā)值用|連接即可。
      還需要注意的就是按鍵的功能是靠結(jié)構(gòu)體成員的名字來區(qū)分的,有fp_singleClick, fp_comboClick, fp_longPress, fp_multiPress共4種,給哪個賦值,對應(yīng)的事件函數(shù)就是什么功能。
    • 把剛才設(shè)置好的結(jié)構(gòu)體給到按鍵掃描程序,開始按鍵掃描。

  • 編寫主函數(shù),把KeyInit()放到初始化里,把KeyEventProcess()放到while(1)里。
    1. void main(){
    2.     EA = 1;
    3.     UartInit();
    4.     KeyInit();        //按鍵掃描初始化
    5.     // printf發(fā)送長串被中斷打斷會死機,使用UartSendString
    6.     // printf("testtesttesttesttesttesttesttesttesttesttesttesttest\r\n");
    7.     UartSendString("testtesttesttesttesttesttesttesttesttesttesttesttest\r\n");
    8.     while(1){
    9.         KeyEventProcess();
    10.     }
    11. }
    復(fù)制代碼
    KeyEventProcess()檢查事件隊列,執(zhí)行隊列里所有函數(shù)。如果隊列滿了,那么再有按鍵事件的話,就會被忽略。所以,慢點按,哈哈。(其實單片機速度夠,快點按也沒事,而且隊列長度能改,在KeyScanConfig.h里)

注意事項
  • printf發(fā)送字符串有bug。
    具體發(fā)生在printf發(fā)送過程中被中斷打斷的時候,現(xiàn)象是死機,串口不斷發(fā)送亂碼。調(diào)節(jié)串口中斷優(yōu)先級為最高沒有改善。
    如果沒有中斷打斷,不會有bug。
  • 系統(tǒng)主頻率在main.h里對應(yīng)實際主頻率修改。
  • 串口波特率在Uart.h里對應(yīng)實際波特率修改。
  • 目前僅在IAP15W4K61S4上實驗,15系列的單片機可以直接用,如果更換其他系列單片機需要修改Timer0的初始化和中斷函數(shù)。(在KeyScan.c里定義)
  • 編譯報了3個警告,不影響。

其他
害,原諒我偷懶下吧……其實文檔已經(jīng)在github上面寫好了,這里不知道怎么用markdown編輯器,搞得排版很亂……所以剩下的設(shè)計思路參考Wiki吧。

工程代碼已上傳。(也可以到github上面下載https://github.com/AdjWang/C51KeyScan)


評分

參與人數(shù) 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎勵!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏8 分享淘帖 頂1 踩
回復(fù)

使用道具 舉報

來自 2#
ID:84652 發(fā)表于 2020-4-10 11:50 | 只看該作者
本帖最后由 王朗的誘惑 于 2020-4-14 22:30 編輯

之前上傳失敗了…… 狀態(tài)機按鍵20200409bak.zip (1.12 MB, 下載次數(shù): 38)
更新:(僅在github上更新,暫不在論壇上傳更新后的附件,避免附件版本和帖子里的說明對不上)2020/04/14 添加對switch,即自鎖開關(guān)的支持。

回復(fù)

使用道具 舉報

板凳
ID:626079 發(fā)表于 2020-4-14 15:28 | 只看該作者
大俠真的太歷害得不要不要的。 佩服,啥時候有樓主一半的水平就可以了。
回復(fù)

使用道具 舉報

地板
ID:84652 發(fā)表于 2020-4-14 22:35 | 只看該作者
xmfjfhcel 發(fā)表于 2020-4-14 15:28
大俠真的太歷害得不要不要的。 佩服,啥時候有樓主一半的水平就可以了。

哈哈,多謝夸獎。我也不聰明啊,只是努力并堅持下來,玩單片機快6年了,我能做到的,你也一定能,說不定還能做得更好。
回復(fù)

使用道具 舉報

5#
ID:137736 發(fā)表于 2022-9-1 18:06 | 只看該作者
很好,可以參考,不過太消耗資源了
回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復(fù) 返回頂部 返回列表
主站蜘蛛池模板: 久久精彩视频 | 日本二区在线观看 | 夜夜草 | 欧美日韩亚洲一区 | 亚洲人精品午夜 | 日韩成人精品 | 日韩精品一区在线 | 久久一区二区三区四区 | 成人精品一区 | 91国在线高清视频 | 亚洲国产精品第一区二区 | 久久精品一区 | 欧美日韩一 | 国产精品亚洲一区二区三区在线观看 | 国产一级视频在线观看 | 亚洲一区日韩 | 国产精品视频专区 | 午夜久久久久久久久久一区二区 | 美女激情av | 亚洲成人免费视频在线观看 | 正在播放国产精品 | 99视频免费在线观看 | 久久i | 永久www成人看片 | www312aⅴ欧美在线看 | 夜夜爽夜夜操 | 91成人午夜性a一级毛片 | 99久9 | 永久免费在线观看 | 亚洲精品一区久久久久久 | 日韩中文字幕网 | 国产成人精品免费视频大全最热 | 一区二区成人 | 国产色婷婷精品综合在线播放 | 久久午夜视频 | 伊人二区 | 香蕉久久久 | 欧美一级久久精品 | 国产精品视频一区二区三区 | 狠狠的干 | 91五月天 |