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

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 170078|回復: 556
收起左側

請工程化,定制化你的單片機代碼

    [復制鏈接]
回帖獎勵 40 黑幣 回復本帖可獲得 2 黑幣獎勵! 每人限 1 次
ID:476527 發表于 2019-6-6 11:48 | 顯示全部樓層 |閱讀模式
  • - 前言 -
    • 時至今日,依舊看到很多小伙伴們放著單片機里的定時器不用,動輒delay1s(); delay500ms();雖然簡單粗暴,但是其實是很不妥當的。
    • 還有很多需要按鍵的程序,動不動就“while(k1==0);”的等按鍵松開,這樣的代碼只是“為了滿足某個功能“而設計的代碼,而非”為了保證產品的質量“而設計的代碼。
    • 我們應該時刻考慮“如果這傻逼用戶不按我的標準操作來搞我的產品,我該怎么寫程序?”,而不是”只要客戶乖乖聽我的,這樣做是沒問題的,就行了;出了問題都是他亂搞,跟我有個毛關系”,后面的思維是典型的只顧自己爽的思維,我們應當想方設法的提高產品的可靠度。
    • OK,扯遠了,現在只是想提出一些設計的思路。
  • part1 . 系統的時基 & 依托于時基去設計函數
- 系統的時鐘基線 -
  • 關鍵詞,是“時鐘基線”。
  • 我們需要設置一個定時器,這個定時器會告訴單片機:“好的,現在已經過了1ms了(或者100ms?這都沒關系)”單片機一拍腦袋:“啊,已經到了1ms了么,那我就把某一件事情做一下吧”。
  • 大致像這樣:
  • 主函數與定時器

    主函數與定時器
  • 這個程序的意思就是,讓單片機每隔1ms就do_sth()一次(定時器的初始化函數省略了沒畫);
  • 這樣寫還可以讓中斷函數非常的簡潔,幾乎一進去就出來,基本不會影響到被中斷打斷的函數的工作;
  • 以傳遞關鍵參數的形式,大循環里只要查一查這個flag1ms值,知道什么時間要做什么事情就對了。
- 按鍵函數舉例 -
  • 上圖中這個do_sth()可以是任意函數,以按鍵函數keyPress()為例,可以這樣寫:
  1. void keyPress(){
  2.     static unsigned int key_press_time = 0;  // ……請記得標為靜態變量
  3.     if(K1==0){
  4.         if(++key_press_time <=0 ) --key_press_time;//計量按鍵時間,并避免數據溢出
  5.         if(key_press_time==3000){
  6.             //在此寫下按鍵長按3s時要做的事情;
  7.         }
  8.     }else{
  9.         if(20<=key_press_time && key_press_time < 3000){
  10.             //大于20ms小于3s,視為短按,在此寫下寫短按的處理代碼
  11.         }
  12.         key_press_time=0;
  13.     }
  14. }
復制代碼

  • 這里有很多可以研究的地方:
    • ①“if(++key_press_time <=0 ) --key_press_time;”這一句,看起來用“++key_press_time”就能搞定,但是,誰也不能保證某些用戶真的不會按按鍵超過65秒的啊;萬一他真的按了65576ms;單片機還就真的以為用戶“短按”了一次呢(65576-65536=40ms,屬于短按范疇),這下那個短按程序段也會被執行一次;現在這樣寫,哪怕你按100年也沒關系了,反正我的單片機就每隔1ms進來看一次,K1這個按鈕你想按多久就按多久,掉在我的范圍內我就處理你,超出我的范圍我就無視你。
    • ②“if(key_press_time==3000){ ; }”這里的3000只是隨便設置的的一個時間(3s),如果你需要做按鍵長短按功能,這里就是你的長按程序所放的位置;你也可以不用3000;用2000,50000都沒事,別超過65534就行(提問:為什么這里又是65534呢?);
    • ③“if(20<=key_press_time && key_press_time < 3000)”這里,前面的>=20是個消抖的設計,客戶再強也是人類,再強的人類也不可能1秒按一個按鍵超過20次;也就是不可能短于50ms的時間;這里用了20ms相當于兼容 男上加男的快男 也來按按鍵了。后面<3000是不能和長按的時間沖突,因為3s我們已經人為的設置成長按時間節點了。
  • 好,現在知道相關的程序該怎么寫了,我們回到剛剛討論的時鐘基線處。

-  多個時鐘基線的方案  -
  • 剛剛的例子中我們只展示了1ms的時基,對于單片機來說,1ms已經很慢了,但對于人和某些設備來說來說還是快的不行,比如一些本就需要10ms間隔的通信設置,需要隔100ms才能刷一次的顯示,需要50ms才能重新采的某些傳感器等等,這些東西放進1ms的do_sth()里面肯定是不行的(其實也行,但不方便管理來著)——怎么辦呢?
  • ——好辦,放進10ms 或50ms 或100ms 的do_sth() 里就OK了
  • ——但是我們只有1個定時器啊,怎么弄幾個時基呢?
  • ——1個定時器就夠了,其他的時基都基于那個1ms的時基來就好。我們已經有了flag1ms,當然可以弄幾個flag10ms乃至flag1000ms,只要你需要,就能設置。
  • 如下圖,我們通過參數控制參數的形式,將flag1ms擴展為多個時基,使得程序漸漸的趨于模塊化,精準化
  • 增大時基的設置

    增大時基的設置
  • 這里有個要注意的地方,就是函數從宏觀上來看,確實是“每隔1ms就do_sth();每隔10ms就do_sth1();每隔100ms就do_sth2()"。
  • 但是,從微觀上看,單片機是沒法在同一時刻做2件事情的!所以,每到10ms的時候,單片機會”先把1ms的事情做完再做10ms的事“;每到100ms的時候,單片機會”先把1ms的事情做完再做10ms的事,再做100ms的事“。
  • 現在,我們的系統已經趨于成型了,不過要注意一點,這些做事情的子函數里萬萬不得有delay函數了(放幾個nop倒是無傷大雅),好不容易劃分好了時鐘基線,你在1ms的時基里塞上幾個delay2ms是要鬧怎樣。
  • 還有一個問題,那就是子函數不要拖泥帶水,寫得越簡潔越好,趕緊把事情做完,把控制權還給單片機,由它來決定接下來要做什么。

-  函數應該放在哪個時基里?  -
  • 這也是關鍵的地方,這要求我們要對自己的程序要有清楚的把握。以及一定的產品思維。
  • 總的原則還是沒變:所有的函數都要寫得簡潔干凈,不要有任何模塊的delay()加起來超過200us!
  • 一般來說,按鍵按一次不會超過50ms,放到flag10ms或者flag20ms的時間段里是沒什么關系的(注意,這時候函數里的3000表示的就不是3s了,而是30s或者60s!這是遵循乘法原則的!);
  • LCD顯示之類的,控制某個LED之類的,100ms一次就行了,人眼視覺暫留之類的看不出問題的。
  • 一般:檢測,通信這類的子程序都能放到flag10ms或者flag20ms里。輸出,顯示這類的放flag100ms就OK了。
  • flag1ms里面應該放什么呢?我的意見是盡量什么也別放,空著都OK,因為其實沒什么東西需要刷新得這么快的。
  • 如果有特別需要關照的部分,比如說步進電機的驅動啥的,請放到另一個定時器中斷里(單片機有倆定時器的,不用白不用),按你需要的來設置。
  • 定時器的中斷觸發時間建議不要低于0.5ms,不然進中斷就太頻繁了,誰也不會希望自己正看著小電影的時候,爸媽過來拍你的房門吧?
  • —— 2019年6月10日更新 ——
  • Part2.將代碼模塊化,降低耦合度
  • - 降低代碼間的耦合度 -
    • 通俗點說,耦合度就是表示兩個東西“你中有我,我中有你”的程度,代碼間的耦合度越高,你修改起來就越費勁,有時候看到代碼黏糊糊的像一大坨蜜糖,難免會讓人不知從何處下口。
    • 還是用個例子來說明一下吧——《電風扇系統設計1》
      • 假設我們要寫個電風扇的控制程序 —— 按鍵方面:風扇有開關鍵,強風弱風切換,擺頭控制3個按鍵(注意,這里的按鍵是那種大開大合的開關按鍵,不是那種輕觸按鍵); 顯示方面:每個按鍵旁邊各有1個小led燈,共3個燈; 輸出方面:有個控制風扇擺頭的負載,有個控制風扇轉不轉的負載,有個控制風扇轉得快或者慢的負載。
      • 或許有人三下兩下就畫了個這樣的流程圖(一般風扇沒開的時候是不能擺頭的,這里就先不管了):
      • 51hei2.jpg
      • 然后就隨手寫出了這樣的代碼:
      • 51hei3.jpg
      • 老實說,這沒啥問題,因為我們的題目要求是那么的簡單,簡單的要求那就簡單的實現吧。雖然這里的按鍵、顯示、負載的程序代碼像蜜糖一樣黏糊糊的,但是代碼也才十幾行,誰會在意呢?也大致畫一下如上代碼的結構吧:
      • 51hei4.jpg
      • 但是,別忘了2點!①是客戶的需求隨時都會改動;是這么簡單的項目還用不到你做。所以,我們稍微,加點難度上來看看?
    • 《電風扇系統設計2》
      • 在《風扇1》的基礎上,①風扇沒開時不能擺頭;②風扇沒開時,為了與沒電作區分,要讓3個LED燈同時以1s為周期閃爍;③開風扇后若是5小時內按鍵都沒有變化過,則自動關掉風扇(避免久開,當然此后LED也要閃爍),若想重開風扇必須把“風扇開關按鍵”先關掉再打開才行。
      • 說句實話,先前的那份代碼也到此為止了,如果有人還想在原來的代碼上改出滿足現在的需求的代碼,我只能祝他好運。雖然也不是不能改,但是若是想用你那delay500ms來滿足我的閃爍要求;或者硬是嵌入一些繞來繞去的東西在里面,那肯定沒有從頭再來要省事。
      • 那么——現在該怎么做呢?
        • 首先,把粘連的模塊分開,按鍵歸按鍵,顯示歸顯示,輸出歸輸出。就像這樣子: 51hei5.jpg
          • 這樣的話,大家各做各的事情,互不干擾,需要共享的信息則通過關鍵參數來傳遞。這里我們設置了3個全局變量:key_on_flag, key_strong_flag, key_sheak_flag; 在keyPress()函數里可以修改這3個變量的值,然后在display()和output()函數里查詢這3個變量的值來控制顯示和輸出。這時候的代碼結構就是這種樣子的了——各個模塊分開,用關鍵參數傳遞信息

          • 51hei6.jpg
          • 所謂的“模塊化,降低耦合度”大致就是這種感覺,現在你需要在哪里改動就單獨改動哪個部分的,不至于瞻前顧后、束手束腳了。
        • 還沒完呢,第二步,把我們的“時基”弄進來(不然上一堂課就白講了),如圖所示: 51hei7.jpg
          • 如此一來,關于那些指定時間的、指定頻率的要求,我們也能輕松的應對了。比如這時候的display()函數,可以這樣寫(這個其實還有缺陷,不過先湊合一下)——
          • 51hei8.jpg

      • 相信通過這樣一個例子,大家應該能看到模塊化對你的軟件系統有多大的改善了。模塊間的耦合度降低之后,自有一種九陽神功中的“他強由他強,清風拂山崗。它弱由他弱,明月照大江。”的感覺。你那邊愛怎么變就怎么變,我這邊有必要就改動,沒必要就不改動,將彼此間的影響減到最低。
      • 時間有限,模塊化的解說先到這里,后續有時間會將狀態機等知識盡可能的講解給大家。同時還得把這個需求變更后的《電風扇系統設計2》的軟件給寫完。
      • ——2019年6月11日 ,今天沒空更貼,暫時先優化一下排版——
      • ——2019年6月14日,修復了教程中的keyPress()和display()函數中的兩個靜態變量忘了加static關鍵字的bug——
      • ——2019年6月16日,更新,狀態機上篇 ——
      • part3.使用狀態機,幫助你管理系統狀態(上篇)
      • (本章較為復雜,需要讀者有較好的數電和C語言功底,方能融匯貫通)
      • - 狀態機入門(有基礎的可以跳過本小節) -
        • 直白的說,狀態機就是若干個“當前狀態 + 觸發條件 = 新狀態( + 附加動作)”的公式。
        • 狀態機可以畫成圖的形式(《狀態遷移圖》),也可以做成表格的形式(《狀態分析表》),如果大家還有數字電子技術的功底的話,應該對下圖還不至于太陌生。
          • 51hei9.jpg
          • ↑此為某狀態遷移圖舉例(與下表同義) 51hei10.jpg
          • ↑此為某狀態分析表舉例(與上圖同義)
        • 如果上述圖表使用公式來表達的話那就是4個公式(取決于圖的箭頭數,或者表的跳轉數),用公式的話,公式的數量不但多,看起來也很麻煩,所以我們一般用《圖》或者《表》來描述你的狀態機。
        • 注意,一般的狀態機是包含“動作”的,這里為了教學方便,略過了“動作”(后面會加回來的)。
        • 狀態機的使用有助于我們更直接,更便捷的管理我們的系統工作。尤其是在系統比較復雜的時候。
      • - 用程序來表現狀態機 -
        • 狀態用枚舉量為佳,因為我們應當保證系統的狀態處于可控范圍內,枚舉量是我們工程師自定義的一個量,可以使系統處處受我們控制。
        • 觸發條件用bit變量即可,觸發了就是1,沒觸發就是0;當然char之類也可以,但沒必要。
          • 有基礎的同學可以使用"位結構體"來優化這些觸發條件的內存,這里不提,請自行百度。
        • ok,那現在可以先定義我們的狀態機變量了(以上述圖表為例)。
            1. typedef enum{
            2.     STATE1,
            3.     STATE2,
            4.     STATE3
            5. }ENUM_STATE;     //定義ENUM_STATE枚舉類型,表狀態
            6. ENUM_STATE system_state = STATE1;    //定義上述枚舉類型的枚舉變量system_state, 初始化為STATE1
            7. bit test_flag_a,  test_flag_b, test_flag_c;   //定義3個觸發條件的bit變量。
            復制代碼

        • 那么,狀態機的程序要怎么寫呢?其實我們觀察《狀態分析表》的時候,有人會喜歡根據當前狀態,分析觸發條件,來決定下一刻的狀態;有人會喜歡從觸發條件開始,看看現在的狀態是否受這種觸發條件影響,而進入新的狀態
        • ——好吧,有兩種觀察方法,就有2種寫法,讀者可以自由選擇自己喜歡的寫法。
          • 寫法1(根據當前狀態,看觸發條件是否有效):
            1. void systemStateCtrl(){
            2.     switch(system_state){
            3.         case STATE1:
            4.             if(test_flag_b) system_state = STATE2;
            5.         break;
            6.         case STATE2:
            7.             if(test_flag_a) system_state = STATE1;
            8.             else if(test_flag_c) system_state = STATE3;//條件a比條件c優先
            9.         break;
            10.         case STATE3:
            11.             if(test_flag_a) system_state = STATE1;
            12.         break;
            13.         default:
            14.         break;
            15.     }
            16. }
            復制代碼


          • 寫法2(根據觸發條件,看當前狀態是否需要改變):
            1. void systemStateCtrl(){
            2.     if(test_flag_a){
            3.         if(system_state==STATE2 || system_state==STATE3)
            4.             system_state = STATE1;
            5.     }
            6.     else if(test_flag_b){
            7.         if(system_state==STATE1)
            8.             system_state = STATE2;
            9.     }
            10.     else if(test_flag_c){
            11.         if(system_state==STATE2)
            12.             system_state = STATE3;
            13.     }
            14.     else{
            15.         ;
            16.     }
            17. }
            復制代碼

        • 這2種寫法各有特點,并沒有優劣之分,各位可以自取所需。非要比較的話,我個人認為:第一種寫法"好讀一些",第二種寫法"好寫一些"。
      • - 根據狀態的值,控制系統的工作流 -
        • “系統的工作”很好理解,就是系統現在在干什么的意思,那么“系統的工作流”是什么意思?
        • ——系統的工作流,表示系統在某段時間內的工作流程。
        • ——為什么要普及“工作流”這個東西呢?
        • ——因為,很多情況下,某個狀態的工作不是寫死的,而是可變的。比如:冰箱在制冷時,制冷器并不是一直開著的,而是一段時間開,一段時間關;洗衣機在洗衣服時,滾筒不是一直開著的,而是先等注水完成之后,正著轉一段時間,然后反著轉一段時間,然后又正著轉……就是有一系列的工作步驟,這些工作的步驟其實就是我們所說的工作流。
        • 如果你非要把洗衣機在洗衣服時,滾筒正著轉和反著轉分成2種新的狀態的話……一路走好。
        • 總之,為了給狀態下的動作有一定的預留空間(因為天知道,需求會不會發生變化),我們需要給每個狀態都做一套關于此狀態下的動作的設計
        • 這個程序寫起來也很簡單,嵌入位置上,直接塞進狀態機的屁股后面就行。如下:
          1. /* 狀態機程序 */
          2. void systemStateCtrl(){
          3.     //你的狀態機程序
          4.     systemStateWork();//把狀態工作程序放這里
          5. }
          6. /* 狀態工作程序 */
          7. void systemStateWork(){ //設計你各個狀態下的工作
          8.     switch(system_state){
          9.         case STATE1:
          10.             do_sth1();break;
          11.         case STATE2:
          12.             do_sth2();break;
          13.         case STATE3:
          14.             do_sth3();break;
          15.         default:
          16.             break;
          17.     }
          18. }
          復制代碼

      • - 例程:《電風扇系統設計2》的狀態機初版 -
        • 狀態機真的是一個很龐大的知識點啊,好不容易把理論說完了,接下來諸位看看我的實例吧。
        • 這個系統設計的需求我就不再重復了,各位往回看一看就能找到,關鍵在于,需求③我們還未處理【③開風扇后若是5小時內按鍵都沒有變化過,則自動關掉風扇(避免久開,當然此后LED也要閃爍),若想重開風扇必須把“風扇開關按鍵”先關掉再打開才行。】。
        • 那么開始吧,我們在一開始,會將系統的狀態分成“風扇開”和“風扇關”兩種,直接由風扇的開機鍵控制切換。但是,多了新的需求之后,開機鍵就不好使了——因為有種“風扇關”的狀態,這時候的開機鍵也是按下的!經過一番思索,為了和真正的“風扇關”作區別,我們可以再創造一種新的狀態——“停機”!也就是開機太久了,需要停機休息。停機時候的搖頭鍵,強弱風鍵都無效,只有開機鍵松開,才能讓你退出“停機”,進入"關機"。
        • 根據我們的分析,可以畫出系統的狀態遷移圖:
        • 51hei11.jpg
        • 同樣的,狀態分析表。
        • 51hei12.jpg
        • 相關程序如下
        • typedef enum{
              STATE_OFF,
              STATE_ON,
              STATE_STOP
          }ENUM_STATE;     //定義ENUM_STATE枚舉類型
          ENUM_STATE system_state = STATE_OFF; //定義枚舉變量system_state, 初始化為STATE_OFF
          bit key_on_flag, key_off_flag, work_too_long_flag; //定義3個觸發條件的bit變量(其實用2個就行)
          void systemStateCtrl(){
              if(key_on_flag){
                  if(system_state==STATE_ON || system_state==STATE_STOP)
                      system_state = STATE_OFF;
              }
              else if(key_off_flag){
                  if(system_state==STATE_OFF)
                      system_state = STATE_ON;
              }
              else if(work_too_long_flag){
                  if(system_state==STATE_ON)
                      system_state = STATE_STOP;
              }
              else{
                  ;
              }
              systemStateWork();//把狀態工作程序放這里
          }
          void systemStateWork(){ //設計你各個狀態下的工作
              switch( system_state ){
                  case STATE_OFF:
                      do_sth1();    //關機時的工作
                  break;
                  case STATE_ON:
                      do_sth2();    //開機時的工作
                  break;
                  case STATE_STOP:
                      do_sth3();    //超時停機時的工作
                  break;
                  default:
                  break;
              }
          }


          • 現在,我們從《電風扇系統設計2》的需求出發,解析系統的狀態;再作圖表(其實圖和表做一個就行,這里為了解讀需要,都做了出來),將狀態機的大體結構都描述出來;這時候寫各個狀態下的工作程序,就是水到渠成的事情了。

      • - 狀態機小結 -
        • 狀態機確實是個比較長的教程,所以我將之分成了2部分來解說,第一部分只強調“狀態+條件=新狀態”這一部分,目的是讓大家先對狀態機有個初步的認知;第二部分再來描述“狀態+條件=新狀態+動作”的完整的狀態機。
        • 眼尖的貼友應該也發現了,這里為了解說方便,還沒有將“強弱風”,“是否擺頭”等動作做進狀態機里。不過他們其實只是一些“動作”而已,在開始分析系統的狀態時,我們要分清楚系統的“主要動作”和“次要動作”,對于電風扇而言,開和關是主要的動作,擺頭和強弱風只是一些次要的附加動作罷了。
        • 關于如何劃分系統的主要狀態?
        • ——這也是一個值得拿來長篇大論的話題,但我這里不想深入探討,也無力深入探討。因為“將指定物品分類”,或者“請給蘋果,梨,水果,蔬菜,白菜,豆腐,白板筆這幾個詞進行排序”這類問題本就有無數解法,是很看重解答者的主觀意識的。
        • 熟練掌握狀態機的用法,可以使你能夠更準確的把握系統的狀態
        • 本章未完,此外,由于我的個人原因,狀態機的下篇只能保證在今年7月15日前更新,還請各位諒解。屆時會將本教程劃上一個完美的句號,同時各種源碼等也會打包上傳。

評分

參與人數 32黑幣 +476 收起 理由
kusp + 30 絕世好帖!
June_aa + 5 很給力!
Fance9988 + 5
youziwb + 20
hqh8001 + 10 很給力!
130boby + 5 絕世好帖!
gqzjl + 15 絕世好帖!
stone2018 + 5
ko44 + 11 很給力!
wjl520oo + 12 很給力!
ag26259 + 6 絕世好帖!
leooo + 30 絕世好帖!
13487086265 + 12 絕世好帖!
hxszrb + 10 很給力!
大膽1 + 10 很給力!
digger + 15 絕世好帖!
菜鳥yc + 5 贊一個!
cheng95 + 30
wfqxgw + 5
1255230 + 5 共享資料的黑幣獎勵!
fl1983 + 30 很給力!
莫燁 + 10 贊一個!
梁廷明 + 4 淡定
5083 + 5 贊一個!
wps10025 + 9 好貼子

查看全部評分

回復

使用道具 舉報

ID:476527 發表于 2019-6-6 16:41 | 顯示全部樓層
如果有需要,我可以把相關的主程序和架構打包上傳……不過說實話這些代碼隨便敲敲就有了,需要鍛煉的是一種“精準控制”軟件的邏輯的思維。
跑快了的東西我們把它放慢些,跑慢了的東西給它放快些。
對于一個系統來說,“平衡”是很重要的事情。

最后再說一句,while(1)大循環里面不要放“delay();”或者“while(按鍵被按下);”了……
回復

使用道具 舉報

ID:476527 發表于 2019-7-7 19:26 | 顯示全部樓層
很抱歉的告訴大家,之前的想法還是太樂觀,現在每天忙成狗,完全不確定啥時候能抽時間寫完,7月15日要是沒有更新的話還請大家諒解。

不過我是不會就這么停筆的,只是不知道啥時候有時間寫而已。作為打工仔,真的身不由己。
為了不辜負大家的期待,下次更新時我會把文檔做成pdf,附帶源碼,流程圖,表格一并上傳。

其實還有很多可以寫的地方,但是現在這篇帖子就寫這3點——時基,模塊化,狀態機——已經夠大家消化一陣子了。而且,如果帖子的篇幅太長了,其實不利于閱讀的。
以后如果還有別的分享,我會另開帖子的,多蹭點管理員的黑幣,哈哈。
回復

使用道具 舉報

ID:476527 發表于 2019-8-2 20:36 | 顯示全部樓層
ccdmcu 發表于 2019-8-2 16:22
按鍵的松手檢測呢?怎么用定時器寫?

我不是給了個keyPress()程序么,你再好好看看。
簡單來說,有鍵按下時,時間就會++;
無鍵按下時,時間就會清零。
但在清零之前會先檢查,時間是否大于0,時間值大于0(或者消抖值)說明曾經有鍵按下過,并且剛剛松開。這時候處理“這次按鍵”不就好了。
回復

使用道具 舉報

ID:385637 發表于 2019-8-10 10:38 | 顯示全部樓層
樓主這文章比那些所謂的教科書好多了,那些教科書按鍵消抖動不動就是delay(20ms),真是誤人子弟,真正出來做到大型項目才知道 DELAY是多么不實際。希望樓主繼續更新,最好轉成PDF,多點例程,尤其是一些按鍵的,短按,長按,雙擊組合鍵例程。現在絕大部份寫按鍵程序都喜歡delay消抖,while松手檢測。郭天詳的書也是這么教的,一點不實用
回復

使用道具 舉報

ID:476527 發表于 2019-8-12 11:46 | 顯示全部樓層

確實不會“小于”0,但“等于”0是有可能的喔,所以這樣寫是沒問題的。
至于為什么我寫了“<=",而不是”==“呢?
理由是 —— 容錯機制:
萬一手殘了,在定義key_press_time的時候忘記加了unsigned,我們的按鍵程序照樣可以正常工作(只是計數最大值只有原來的一半了)
回復

使用道具 舉報

ID:476527 發表于 2019-8-14 14:12 | 顯示全部樓層
本帖最后由 Similarv 于 2019-8-15 10:40 編輯
IdeaMing 發表于 2019-8-14 10:27
我想問,怎么把DS18B20的加入到你這種機制里?一次通訊時間都要好幾毫秒

這個好說,把基礎時基變成10ms(或者20ms),也就是定時器中斷10ms(或者20ms)產生一次。
然后就是……
  1. unsigned char flag10ms=0,flag100ms=0;
  2. void t0Init()
  3. { /*略*/}
  4. void t0Xint() Interrupt n
  5. {
  6.     flag10ms = 1;//10ms產生一次中斷
  7. }
復制代碼



回復

使用道具 舉報

ID:476527 發表于 2019-8-19 01:00 來自手機 | 顯示全部樓層
IdeaMing 發表于 2019-8-16 16:25
如果同時存在數碼管的刷新呢,比如一秒讀一次18B20,讀的時候數碼管的刷新就被卡了一下

我剛剛特意去查了一下,這玩意僅僅是個溫度傳感器,而且還特么是數字的,讀一次溫度居然至少要5Ms?!(還不確定是否會更久)
這樣,如果你有示波器的話,請你做這么一個實驗,把單片機某個端口設為輸出,這個端口平時是低電平,然后用18b20讀取溫度之前拉高它,讀取結束的時候又拉低它,然后用示波器好好的看看這個端口被拉高的時間,你就知道它每次采溫需要多長時間了。再結合其他子程序的工作時長,設計你的時基。
其實,這種數字型溫度傳感器在我們實際做項目的人的眼里,只有華而不實四個字可以評價。如果我們的項目里需要測溫,我們一般會采用內部自帶adc模塊的單片機,測溫只要一個熱敏電阻就可以搞定。整個測溫的時間只需大約10us。不但更便宜,精度更好,效率還是你那個測溫模塊的500倍!具體方案這里就不贅述了。
回復

使用道具 舉報

ID:476527 發表于 2020-1-13 16:52 | 顯示全部樓層
本帖最后由 Similarv 于 2020-1-13 18:07 編輯
13303022280 發表于 2020-1-7 14:29
如果我只需要執行一次按鍵之后的程序,為了避免按鍵長按導致的重復運行,就用一個變量,在每次進入中斷檢 ...

你的做法當然也沒有問題,不過我是這么做的:
按鍵就只是做按鍵檢測,并且設置相應的“按鍵已觸發”標志(bit變量);至于按鍵的功能則是在別處比如狀態機中去執行。
例子:當按鍵按下之后開/關燈

key.c 文件:
bit key_flag_light;  //當按鍵觸發時為1,否則為0, 此變量是給狀態機使用的
static bit key_press_light; //當按鍵按下時為1,未按下為0,此變量僅在key.c內使用。
函數1-keyScan()  -- 功能:檢測按鍵是否按下,若按鍵按下則將key_press_light置一,否則清零。
函數2-keyDeal()   -- 功能:使用某靜態變量統計key_press_light的時間;當key_press_light從1變為0的時候,檢查靜態變量的值是否大于消抖值,若大于消抖時間則將key_flag_light設置為1;

state.c文件
函數1-StateMachine() -- 功能:當key_flag_light==1時,取反led_flag_out的值,隨后將key_flag_light設置為0.

led.c文件
bit led_flag_out; //當該值==1,開燈;當該值==0;關燈
函數1-ledOutput() -- 功能:根據led_flag_out的取值開燈或者關燈

main.c文件
在10ms或者20ms的時基中依次調用
keyScan();
keyDeal();
StateMachine();
在100ms的時基中調用
ledOutput();

/******/
稍微修改一下,剛剛沒有認真審題,你需要做長按的判定,我這個架構是基于短按的。
修改的地方位于keyDeal()部分,
短按 —— 是等待按鍵松開時裁決靜態變量的計時值。
長按 —— 則是直接在靜態變量等于‘長按時間點’的時刻將key_flag_light置一即可。key_flag_light==1的時候,狀態機自然會處理長按事件,此后keyDeal()里的靜態變量會在按鍵繼續按著的時候繼續加,但其值已經‘大于’‘長按時間點’了,所以key_flag_light不會被重復置一,當按鍵釋放之后,此靜態變量清零,一切重新開始。


回復

使用道具 舉報

ID:476527 發表于 2020-1-13 17:46 | 顯示全部樓層
hubj627 發表于 2020-1-13 09:20
這種針對代碼量小的項目實時性沒有問題。但代碼量大的整個程序架構就會顯得臃腫,也需針對同一個事情,在主 ...

進階教程尚待壇友們自己修行,鄙人不才,只能帶路到這了
回復

使用道具 舉報

ID:521890 發表于 2019-6-6 20:53 | 顯示全部樓層
很好的文章,請繼續。感謝!
回復

使用道具 舉報

ID:464755 發表于 2019-6-7 00:36 | 顯示全部樓層
我對delay1s(); delay500ms()這樣的延時函數就很不爽,當然程序流程很簡單的,用用也沒什么,如果是一個還算比較長的程序,使用delay1s(); delay500ms()這種就是在人為遲滯片子的運行,響應不過來又要罵,完全不會看自己做的程序寫的是什么狗 p。你可以發現,凡是這樣搞的,里面的變量,定義亂七八糟,想怎么寫就怎么寫。只要編譯的過,不求后面優化。
以前的專業程序員,可是對單片機了如指掌,每一點一滴的性能,一個字節都很珍惜,寫的程序兼職就是藝術品。
之前我們這也有個做機構設計的,畫的加工件,完全不考慮加工難度,想怎么畫就怎么畫,機構極度粗 暴yu 蠢,我看不下去了,問他還就一句話,加工不管我的事,一個月不到就讓他滾 蛋了。
回復

使用道具 舉報

ID:435708 發表于 2019-6-7 00:42 | 顯示全部樓層
真的是好文章,對51定時器的理解更深了,原來程序還能這么寫
回復

使用道具 舉報

ID:476527 發表于 2019-6-10 10:04 | 顯示全部樓層
nsush 發表于 2019-6-7 00:36
我對delay1s(); delay500ms()這樣的延時函數就很不爽,當然程序流程很簡單的,用用也沒什么,如果是一個還 ...

哈哈哈,做技術的可不能只管自己那小小的一畝三分地啊,不去學習和進步終究要被淘汰的。
還有些人在步入一個新領域的時候總是抱著玩一下的心態,勉強能用就行,卻不想把事情做到更好,其實也是自己限制住了自己的成長。
回復

使用道具 舉報

ID:476527 發表于 2019-6-10 17:25 | 顯示全部樓層
@admin 這應該是個比較長的教程,預計下下次更新才能寫完,屆時再發些源碼和參考例程
回復

使用道具 舉報

ID:58925 發表于 2019-6-10 21:08 | 顯示全部樓層
不錯的文章,希望有后續
回復

使用道具 舉報

ID:557774 發表于 2019-6-11 10:13 來自手機 | 顯示全部樓層
很好,學習了,現在實踐一下
回復

使用道具 舉報

ID:342911 發表于 2019-6-12 11:56 來自手機 | 顯示全部樓層
學習了  很受用  會慢慢去嘗試應用  期待后續
回復

使用道具 舉報

ID:56665 發表于 2019-6-13 09:04 | 顯示全部樓層
謝謝分享,多發一些類似文章。
回復

使用道具 舉報

ID:557550 發表于 2019-6-13 10:53 | 顯示全部樓層
他們寫程序粗的不行,可能自己覺的學個框架就算是學到了整個語言的精華,殊不知差得遠呢。
回復

使用道具 舉報

ID:25481 發表于 2019-6-13 11:30 | 顯示全部樓層
支持持續更新,很好的東西。
回復

使用道具 舉報

ID:104825 發表于 2019-6-13 12:24 來自手機 | 顯示全部樓層
好文章,好理論,!通熟易懂!
回復

使用道具 舉報

ID:235691 發表于 2019-6-13 13:46 | 顯示全部樓層
期待樓主的新文章
回復

使用道具 舉報

ID:382454 發表于 2019-6-13 21:19 來自手機 | 顯示全部樓層
專家講解,學習了。
回復

使用道具 舉報

ID:557774 發表于 2019-6-14 02:25 來自手機 | 顯示全部樓層
感謝樓主,學習到了
回復

使用道具 舉報

ID:562287 發表于 2019-6-14 06:48 來自手機 | 顯示全部樓層
這個必須加精啊,新手不知道有沒有收藏功能
回復

使用道具 舉報

ID:143924 發表于 2019-6-14 08:38 | 顯示全部樓層
很不錯的文章,啟迪匪淺。
回復

使用道具 舉報

ID:168971 發表于 2019-6-14 09:24 來自手機 | 顯示全部樓層
好文章!
回復

使用道具 舉報

ID:516571 發表于 2019-6-14 10:18 | 顯示全部樓層
很不錯的文章,請繼續
回復

使用道具 舉報

ID:111376 發表于 2019-6-14 11:31 | 顯示全部樓層
感謝樓主的文章!
回復

使用道具 舉報

ID:562693 發表于 2019-6-14 17:13 來自手機 | 顯示全部樓層
怎么關注樓主啊?感覺寫的很有意思
回復

使用道具 舉報

ID:461428 發表于 2019-6-16 11:24 來自手機 | 顯示全部樓層
新手完全不曉得你說的啥玩意
回復

使用道具 舉報

ID:476527 發表于 2019-6-16 12:05 | 顯示全部樓層
kissme 發表于 2019-6-16 11:24
新手完全不曉得你說的啥玩意

那就請新手(自稱)們加油,這帖子本來就是進階教程,希望他們先將基礎打牢
回復

使用道具 舉報

ID:564821 發表于 2019-6-17 18:05 | 顯示全部樓層
感謝樓主,學到了很多
回復

使用道具 舉報

ID:512205 發表于 2019-6-17 22:30 | 顯示全部樓層
很不錯的文章
回復

使用道具 舉報

ID:517235 發表于 2019-6-18 09:05 | 顯示全部樓層
很好的編程思路,條理清晰,把復雜的事情簡單化。
回復

使用道具 舉報

ID:509639 發表于 2019-6-18 12:55 | 顯示全部樓層
好貼!!!
回復

使用道具 舉報

ID:559308 發表于 2019-6-18 16:16 | 顯示全部樓層
送人玫瑰,手有余香
回復

使用道具 舉報

ID:307505 發表于 2019-6-19 15:30 | 顯示全部樓層
很好的技術貼
回復

使用道具 舉報

ID:282431 發表于 2019-6-20 08:39 | 顯示全部樓層
很好的文章,請繼續。感謝!
回復

使用道具 舉報

ID:467698 發表于 2019-6-20 10:48 | 顯示全部樓層
樓主經驗豐富,編程思想總結的很好,文章寫得也很棒,希望可以繼續分享,感謝!
回復

使用道具 舉報

ID:499184 發表于 2019-6-20 12:01 | 顯示全部樓層
去除按鍵的抖動 不用延時函數的話,怎么處理
回復

使用道具 舉報

ID:476527 發表于 2019-6-20 13:30 | 顯示全部樓層
tesrsm 發表于 2019-6-20 12:01
去除按鍵的抖動 不用延時函數的話,怎么處理

用靜態變量去統計按鍵的時間,隨后對此時間進行裁決。
延時只會讓你的程序卡在那里一段時間,動彈不得。
回復

使用道具 舉報

ID:243491 發表于 2019-6-20 13:46 | 顯示全部樓層
好文章,不知樓主能否弄成pdf格式上傳呢,想保存起來
回復

使用道具 舉報

ID:476527 發表于 2019-6-20 14:29 | 顯示全部樓層
lianzai 發表于 2019-6-20 13:46
好文章,不知樓主能否弄成pdf格式上傳呢,想保存起來

等我抽空把狀態機的下半部分寫完先再說吧
回復

使用道具 舉報

ID:140275 發表于 2019-6-20 18:27 | 顯示全部樓層
這樣就是操作系統的雛形
回復

使用道具 舉報

ID:306475 發表于 2019-6-21 15:13 | 顯示全部樓層
高手,膜拜了。
回復

使用道具 舉報

ID:476274 發表于 2019-6-24 11:34 | 顯示全部樓層
不錯學習了
回復

使用道具 舉報

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

本版積分規則

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

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 国产免费一级一级 | 影视先锋av资源噜噜 | 伦理片97| 精品亚洲永久免费精品 | 一区二区三区在线 | 国内精品一区二区 | 成人免费激情视频 | 国产在线小视频 | 91天堂网 | 免费精品视频 | 在线观看成年人视频 | 免费黄色的网站 | 亚洲精选久久 | 精品国产免费一区二区三区演员表 | 欧美在线a | 久久精品成人 | 懂色中文一区二区三区在线视频 | 美女爽到呻吟久久久久 | 欧美黑人一区二区三区 | 中文字幕欧美日韩一区 | 日本欧美视频 | 九九九国产 | 亚洲欧美日韩电影 | 午夜资源 | 欧美一二三 | 色婷婷精品国产一区二区三区 | 欧美日韩高清在线观看 | 麻豆毛片| 国产91久久久久久 | 在线国产一区二区 | 一区二区三区免费观看 | 99re热精品视频国产免费 | 91婷婷韩国欧美一区二区 | 九九亚洲精品 | www.色.com| 一级无毛片 | 国产成人久久精品一区二区三区 | 国产成人精品午夜 | 久久99精品久久久久久青青日本 | 国产视频1区 | 黄色成人免费在线观看 |