摘要:在看別人單片機程序時,你也許是崩潰的,因為全局變量滿天飛,不知道哪個在哪用了,哪個表示什么,而且編寫極其不規(guī)范。自己寫單片機程序時,也許你也是崩潰的??偢杏X重新開啟一個項目,之前的寫過相似的代碼也無法使用,得重新敲,代碼重用度不高,編程效率低下,代碼無法積累。而且感覺寫這個代碼沒有思想,沒有靈魂,沒有框架,只是一個一個功能代碼的堆砌,很空泛。
那么這個時候,你也許應(yīng)該在單片機中引入面向?qū)ο蟮乃枷肓?,使代碼更規(guī)范。
一、單片機程序框架
1、輪流執(zhí)行
intmain(void) { while(1) { sing(); dance(); play(); } }
函數(shù)sing執(zhí)行的時間比較長的話,函數(shù)dance就不能很快的被執(zhí)行。任何一個函數(shù)死掉的話就會影響整個系統(tǒng)。
2、前后臺
在使用 51、AVR、STM32 單片機裸機的時候一般都是在main函數(shù)里面用while(1)做一個大循環(huán)來完成所有的處理,即應(yīng)用程序是一個無限的循環(huán),循環(huán)中調(diào)用相應(yīng)的函數(shù)完成所需的處理。有時候我們也需要中斷中完成一些處理。相對于多任務(wù)系統(tǒng)而言,這個就是單任務(wù)系統(tǒng),也稱作前后臺系統(tǒng),中斷服務(wù)函數(shù)作為前臺程序,大循環(huán)while(1)作為后臺程序。
對應(yīng)的編程代碼大概是這樣的:
voidEXTI_IRQHandler() { flag=1; } intmain(void) { while(1) { if(flag=1) { do_something(); flag=0; } } }
有什么問題?
前后臺系統(tǒng)的實時性差,前后臺系統(tǒng)各個任務(wù)(應(yīng)用程序)都是排隊等著輪流執(zhí)行,不管你這個程序現(xiàn)在有多緊急,沒輪到你就只能等著!相當于所有任務(wù)(應(yīng)用程序)的優(yōu)先級都是一樣的。但是前后臺系統(tǒng)簡單啊,資源消耗也少??!在稍微大一點的嵌入式應(yīng)用中前后臺系統(tǒng)就明顯力不從心了。
3、多任務(wù)
voidfirst_task() { while(1) { if(has_data()) put_data(); } } voidsecond_task() { while(1) { if(get_data()) do_something(); } } intmain(void) { create_task(first_task); create_task(second_task); start_scheduler(); }
多任務(wù)系統(tǒng)會把一個大問題“分而治之”,把大任務(wù)劃分成很多個小問題,逐步的把小任務(wù)解決掉,大任務(wù)也就隨之解決了,這些任務(wù)是并發(fā)處理的。注意,并不是說同一時刻一起執(zhí)行很多個任務(wù),而是由于每個任務(wù)執(zhí)行的時間很短,導致看起來像是同一時刻執(zhí)行了很多個任務(wù)一樣。
二、執(zhí)行的程序怎么寫?
以按鍵為例,點亮一個小燈!
1.常規(guī)寫法
intmian(void) { while(1) { if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3)==GPIO_PIN_SET) { printf("按鍵按下 "); } } }
2.面向?qū)ο蟮膶懛?/p>
首先我們把每一個按鍵都看成一個對象,既然是對象就肯定有屬性和行為,比如我們定義一個學生,那么這個學生有什么屬性呢?
肯定有姓名、年齡、身高、體重對吧,這些是一些基本的屬性,我們可以用一些單獨的變量來定義它,比如:
typedefstruct { uint8_t*name;//姓名(變量) uint8_tage;//年齡(變量) uint8_theight;//身高(變量) uint8_tweight;//體重(變量) }student_t;
但是一個學生還有很多行為對吧,它會唱歌、跳舞、打籃球,于是我們就可以這樣定義:
typedefstruct { uint8_t*name;//姓名(變量) uint8_tage;//年齡(變量) uint8_theight;//身高(變量) uint8_tweight;//體重(變量) void(*Sing_song)(void);//會唱歌(函數(shù)指針) void(*Dance_latin)(void);//會跳舞(函數(shù)指針) void(*Wechat_zhiguoxin)(void);//會關(guān)注果果的公眾號(函數(shù)指針) }student_t;
好了,這里我們提到了函數(shù)指針,所以就來說一說函數(shù)指針。
函數(shù)指針,顧名思義它就是一個指針,只不過它是一個函數(shù)指針,所以指向的是一個函數(shù)。類比一般的變量指針,指針變量,實質(zhì)上是一個變量,只不過這個變量存放的是一個地址,在32位單片機中,任何類型的指針變量都存放的是一個大小為4字節(jié)的地址。
重要的話說三遍!牢記在心?。?!為什要記住函數(shù)指針,因為在單片機面向?qū)ο缶幊讨?,結(jié)構(gòu)體的成員不是變量就是函數(shù)指針這兩種類型。變量就不用說了,函數(shù)指針理解就好。
其實函數(shù)指針可以類比一般的變量,看下面:
inta;=?>voidSing_song(void); int*p;=?>void(*zhiguoxin)(void); p=&a;=?>zhiguoxin=&Sing_song;
左邊走義變量a,右邊定義函數(shù)Sing_song;
左邊定義int指針,右邊定義函數(shù)指針;
左邊賦值指針,右邊賦值函數(shù)指針;
那么函數(shù)指針怎么用呢?我們還是以單片機為例,把按鍵類比為一個對象,這個按鍵有按鍵標志位,有長按或者短按,按鍵還有行為:按鍵初始化、按鍵循環(huán)檢測等。
所以我們創(chuàng)建下面這樣一個結(jié)構(gòu)體,當然這個結(jié)構(gòu)體不一定僅僅有這些變量和函數(shù),這完全取決于你自己的定義,你想怎么定義就怎么定義,你甚至可以定義按鍵的顏色都。
typedefstruct { uint8_tKEY_Flag;//標志位(變量) uint8_tClick;//按下(變量) void(*KEY_Init)(void);//按鍵初始化(函數(shù)指針) void(*KEY_Detect)(void);//按鍵檢測(函數(shù)指針) }KEY_t;
現(xiàn)在已經(jīng)定義了KEY_t這種類型的結(jié)構(gòu)體,處理器還沒有分配給這個結(jié)構(gòu)體內(nèi)存,因為我們只是聲明這樣一個類型,而類型是不占用內(nèi)存的,只有我們定義對應(yīng)的結(jié)構(gòu)體類型的變量時才會在占用內(nèi)存空間。
那么怎么定義一個結(jié)構(gòu)體類型的變量呢?
KEY_tKEY1;
然后就要初始化結(jié)構(gòu)體的成員變量了。
KEY_tKEY1={0,0,KEY_init,KEY_detect};
這里要注意了現(xiàn)在結(jié)構(gòu)體有四個成員,前兩個普通的變量,我們初始化為0,還有兩個函數(shù)指針,我們是不是要把我們想寫得函數(shù)的函數(shù)名字放在這里啊。
那么聰明的你肯定知道還要定義KEY_init();和KEY_detect();這兩個函數(shù)。這兩個函數(shù)可以這樣寫。
staticvoidKEY_init() { GPIO_InitTypeDefGPIO_InitStruct; GPIO_InitStruct.Pin=GPIO_PIN_3; GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull=GPIO_NOPULL; GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA,&GPIO_InitStruct); } staticvoidKEY_detect() { uint8_ti=0; if(KEY1.KEY_Flag==1) { HAL_Delay(100); if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_3)==GPIO_PIN_SET) { printf("按鍵按下 "); } KEY1.KEY_Flag=0; } }
好了具體函數(shù)中的代碼我就不需要解釋了。這樣一個按鍵的對象我們就定義好了,這個按鍵我們賦予了"他"生命,有屬性(變量)有行為(函數(shù))。
這樣我們在主函數(shù)就可以這樣的調(diào)用,來實現(xiàn)相應(yīng)的功能了。按鍵使用了中斷,這里并沒有講解。
voidmain(void) { KEY1.KEY_Init();//初始化按鍵 while(1) { KEY1.KEY_Detect();//按鍵檢測 } }
如果理解了這些,那么面向?qū)ο蟮木枘慊疽呀?jīng)掌握了,接下來就是不斷地去練習和實踐了。
三、為什么要面向?qū)ο螅?/p>
我們知道,現(xiàn)有的編程范式主要是:面向過程編程、面向?qū)ο缶幊?、函?shù)式編程。
對于流程清晰的簡單程序,一般只有一條流程主線,很容易被劃分成順序執(zhí)行的幾個步驟,面向?qū)ο缶幊毯兔嫦蜻^程編程沒有太大差別,并且面向過程編程常常比面向?qū)ο缶幊谈又庇^高效。
但當我們面對一個大型的復(fù)雜程序,由于其錯綜復(fù)雜的流程和交互關(guān)系,很難將其簡單地拆分成一條主線串成的簡單步驟,而通常表現(xiàn)為一個網(wǎng)狀關(guān)系結(jié)構(gòu)。這個時候,面向過程編程的這種流程化和線性化的思維方式就會顯得比較吃力,而面向?qū)ο缶幊痰膬?yōu)勢就比較明顯了。
面向?qū)ο缶幊田L格的代碼更容易復(fù)用、擴展和維護、更高級、更人性化、更適合大規(guī)模復(fù)雜程序的開發(fā)。在Linux中就是用的面向?qū)ο缶幊蹋锩嬗泻芏嗟慕Y(jié)構(gòu)體、指針、鏈表等等。如果還沒有接觸到面向?qū)ο缶幊讨荒苷f明你做的東西還不夠復(fù)雜。
在單片機舉一個例子,一塊開發(fā)板可能會適配不同的屏幕
一塊板子,三個屏幕
那么每一塊板子肯定有不同的代碼適配,在程序中我們可以讀出屏幕的ID,然后通過if判斷來執(zhí)行不同的指令,就行這樣。
果果小師弟
如果使用面向?qū)ο缶幊?,那么就可以這樣寫代碼。
typedefstructlcd{ uint8_ttype; void(*LCD_Init)(void) }lcd_t,*plcd_t; intRead_id() { /*0:LCDA *1:LCDB */ return0; } intGet_Lcd_Type(void) { returnRead_id(); } voidLCDA_Init(void)//屏幕A初始化 { LCD_WR_REG(0xCF); LCD_WR_DATA(0x00); LCD_WR_DATA(0xC1); LCD_WR_DATA(0X30); } voidLCDB_Init(void)//屏幕B初始化 { LCD_WR_REG(0X11); delay_ms(20); LCD_WR_REG(0XD0); LCD_WR_DATA(0X07); } lcd_topenedv_com_lcds[]={ {0,LCDA_Init}, {1,LCDB_Init}, }; plcd_tget_lcd(void)//獲取到屏幕類型 { inttype=Get_Lcd_Type(); return&openedv_com_lcds[type]; } intmain(void) { plcd_tlcd; lcd=get_lcd();//獲取到屏幕類型 lcd->LCD_Init();//初始化對應(yīng)屏幕 while(1) {} }
這里只是偽代碼處理辦法,原理就和上面所講的一樣,在結(jié)構(gòu)體中使用變量和函數(shù)。
到這里你應(yīng)該掌握了面向?qū)ο蟮?a href="http://ttokpm.com/v/tag/1778/" target="_blank">單片機編程方法,一起來試驗幾個例子:
LED燈
typedefstruct { void(*LED_ON)(uint8_tLED_Num);//打開 void(*LED_OFF)(uint8_tLED_Num);//關(guān)閉 void(*LED_Flip)(uint8_tLED_Num);//翻轉(zhuǎn) }LED_t;
按鍵KEY
typedefstruct { uint8_tKEY_Flag;//標志位(變量) uint8_tClick;//按下(變量) void(*KEY_Init)(void);//按鍵初始化(函數(shù)指針) void(*KEY_Detect)(void);//按鍵檢測(函數(shù)指針) }KEY_t;
蜂鳴器BEEP
typedefstruct { uint8_tStatus;//狀態(tài) void(*ON)(void);//打開 void(*OFF)(void);//關(guān)閉 }BEEP_t;
串口UART
typedefstruct { USART_TypeDef*uart;/*STM32內(nèi)部串口設(shè)備指針*/ uint8_t*pTxBuf;/*發(fā)送緩沖區(qū)*/ uint8_t*pRxBuf;/*接收緩沖區(qū)*/ uint16_tusTxBufSize;/*發(fā)送緩沖區(qū)大小*/ uint16_tusRxBufSize;/*接收緩沖區(qū)大小*/ uint16_tusTxWrite;/*發(fā)送緩沖區(qū)寫指針*/ uint16_tusTxRead;/*發(fā)送緩沖區(qū)讀指針*/ uint16_tusTxCount;/*等待發(fā)送的數(shù)據(jù)個數(shù)*/ uint16_tusRxWrite;/*接收緩沖區(qū)寫指針*/ uint16_tusRxRead;/*接收緩沖區(qū)讀指針*/ uint16_tusRxCount;/*還未讀取的新數(shù)據(jù)個數(shù)*/ void(*RS485_Set_SendMode)(void);//RS-485接口設(shè)置為發(fā)送模式 void(*RS485_Set_RecMode)(void);//RS-485接口設(shè)置為接收模式 }UART_T;
審核編輯:劉清
-
嵌入式
+關(guān)注
關(guān)注
5059文章
18974瀏覽量
302062 -
AVR
+關(guān)注
關(guān)注
11文章
1122瀏覽量
78546 -
STM32單片機
+關(guān)注
關(guān)注
58文章
549瀏覽量
58591
原文標題:面向?qū)ο笏枷刖帉憜纹瑱C程序其實很簡單!
文章出處:【微信號:嵌入式悅翔園,微信公眾號:嵌入式悅翔園】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論