嵌入式分享合集57
一、兩種單片機編程思想
分層思想
????分層的思想,并不是什么神秘的東西,事實上很多做項目的工程師本身自己也會在用。看了不少帖子都發現沒有提及這個東西,然而分層結構確是很有用的東西,參透后會有一種恍然大悟的感覺。如果說我不懂LCD怎么驅動,那好辦,看一下datasheet,參考一下阿別人的程序,很快就可以做出來。但是如果不懂程序設計的思想的話,會給你做項目的過程中帶來很多很多的困惑。
????參考了市面上各種各樣的嵌入式書籍,MCS-51,AVR ,ARM 等都有看過,但是沒有發現有哪本是介紹設計思想的,就算有也是鳳毛麟角。寫程序不難,但是程序怎么樣才能寫的好,寫的快,那是需要點經驗積累的。結構化模塊化的程序設計的思想,是最基本的要求。
????然而這么將這個抽象的概念運用到工程實踐當中恩?那需要在做項目的過程中經歷磨難,將一些東西總結出來,抽象升華為理論,對經驗的積累和技術的傳播都大有裨益。所以在下出來獻丑一下,總結一些東西。
????就我個人的經驗而談,有兩個設計思想是非常重要的。
????一個就是“時間片輪的設計思想”,這個對實際中解決多任務問題非常有用,通常可以用這個東西來判斷一個人是單片機學習者,還是一個單片機工程師。這個必須掌握。(下文將介紹)。
?????第二個就是“分層屏蔽的設計思想”即分層思想。下面用掃描鍵盤程序例子作為引子,引出今天說的東西。
問題的提出
????單片機學習板一般為了簡單起見,將按鍵分配的很好,例如整個 4*4 的鍵盤矩陣分配到 P1 口上面,8條控制線,剛好。這樣的話程序也非常好寫。只需要簡單的:
KEY_DAT= P1;????端口的數據就讀進來了。
????誠然,現實中沒有這么好的事情。在實際的項目應用當中,單片機引腳的復用相當厲害,這跟那些所謂的單片機學習板就有很大的差別了。
????另外一個原因,一般設計來說,是“軟件配合硬件”的設計流程,簡單點說就是,先確定好硬件原理圖,硬件布線,最后才是軟件的開發,因為硬件修改起來比較麻煩,相對來說軟件修改的時候比較好改。這個就是中國傳統的陰陽平衡哲學原理。硬件設計和軟件設計本來就是魚和熊掌的關系,兩者不可兼得。方便了硬件設計,很可能給寫軟件帶來很大的麻煩。
????反過來說,方便了軟件設計,硬件設計也會相當的麻煩。如果硬件設計和軟件設計同時方便了,那只有兩種可能,一是這個設計方案非常簡單,二是設計師已經達到了一個非常高的境界。我們不考慮那么多情況,單純從常用的實際應用的角度來看問題。
????硬件為了布線的方便,很多時候會可能將IO口分配到不同的端口上面,例如上面說的4*4鍵盤,8根線分別分配到 P0 P1 P2 P3 上面去了。那么,開發板的那些掃描鍵盤程序可以去見鬼了。怎么掃按鍵?我想起了我剛開始學習的時候,分成3段非常相似的程序,一個一個按鍵的掃描的經歷......
????或許有人不甘心,“那些東西我花了很長時間學習的,也用的好好的,怎么能說一句不用就不用?”雖然有點殘忍,但是我還是想說“兄弟,接受現實吧,現實是殘酷的......”
????不過,人區別于低等動物的差別,是人會創造,在碰到困難的時候會想辦法解決,于是我們開始了沉思......
????最后我們引入初中數學學的“映射”的概念來解決問題。基本思想就是,將不同端口的按鍵映射到相同端口上面。
按鍵掃描程序如何分成3個層
????最底層的是硬件層,完成端口掃描,20ms延時消抖,將端口的數據映射到一個KEY_DAT寄存器上面,KEY_DAT作為對上層驅動層的一個接口。
????中間的一層是驅動層,驅動層只對 KEY_DAT 寄存器的數值進行操作。簡單點說,我們無論底層的硬件是怎么接線的,在驅動層都不需要關心,只需要關心 KEY_DAT 這個寄存器的數值是什么就可以了。這樣出來的間接效果就是“屏蔽了底層硬件的差異”,所以驅動層寫的程序就可以通用了。
????驅動層的另外一個功能是為了上層提供消息接口。我們用了類似window程序的消息的概念。這里可以提供一些按鍵消息,例如:按下消息,松開消息,長按鍵消息,長按鍵的時候的步進消息,等等。
????應用層屬于最上層的程序,這里就是根據項目的不同分別寫按鍵功能程序。它使用的是驅動層提供的消息接口。在應用層寫程序的思想就是,我不管下層是怎么工作的,我只關心按鍵消息。有按鍵消息來的時候我就執行功能,沒有消息來的時候,我就什么也不做。
????下面用一個簡單的常用的例子,說明我們這個設計思想的用法。
????秒表調整時間的時候,要求按著某個按鍵不放,時間能連續的向上增加。這個東西很實用,實際的家電中用途很廣泛。
????在看下面的東西之前,大家可以想一下,這東西難嗎?相信大家都會很響亮的回答,“不難!!”,然而我再問:“這東西麻煩嗎?”我相信很多人肯定會說“很麻煩!!” 這不禁讓我想起開始學單片機的時候寫這種按鍵的那程序,亂七八糟的結構。如果不相信的話,可以自己用51寫一下哦,那樣就更加能體會本文說的分層結構的優越性。
項目要求:
????兩個按鍵,分別分配在P10 和P20,分別是“加”“減”按鍵,要求長按鍵的時候實現連續加和連續減的功能。
實戰:
????假設按鍵上拉,沒有按鍵的時候高電平,有按鍵的時候低電平,另外,為了突出問題,這里沒有將延時消抖的程序寫上去,在實際項目中應該加上。C語言函數參數的傳遞多種多樣,這里作為例子,用了最簡單的全局變量來傳遞參數,當然你也可以用 unsigned charReadPort(void)返回一個讀鍵結果,甚至還可以 void ReadPort(unsigned char*pt) 用一個指針變量傳遞地址而達到直接修改變量的目的。方法是多種多樣的,這個決定于每個人的程序風格。
?1)開始寫硬件層程序,完成映射
#defineKYE_MIN 0X01#defineKEY_PLUS 0X01unsignedchar KeyDat;voidReadPort(void){if (P1 & KEY_PLUS == 0 ) { KeyDat |= 0x01 ; }if (P2 & KEY_MIN == 0 ) { KeyDat |= 0x02 ;??}}????C語言應該很容易看懂吧?如果 KEY_PLUS 按下,P10口讀到低電平,則 P1 &KEY_PLUS 的結果為 0 (xxxx xxx0 & 0000 0001),滿足if 的條件,進入KeyDat |=0x01 ?是將 KeyDat 的bit0 置一,也就是說,將 KEY_PLUS 映射到 KeyDat 的 bit0
??? KEY_MIN是同樣的道理映射到 KeyDat 的 bit1,如果 KeyDat 的 bit0 為 1 ,則說明 KEY_PLUS 按下,反則亦然。
????不需要想的很神秘,映射就是這么一回事。如果還有其他按鍵的話,用同樣辦法,將他們全部映射到 KeyDat 上面。
2)驅動層程序編寫
????如果將 KeyDat想象成 P1 口,那么這個跟學習板那標準的掃描程序不就是一樣了嗎?對的,這個就是底層映射的目的了。
3)應用層程序編寫
????根據消息,硬件層是必須分離出來,然而驅動層和應用層的要求就不那么嚴格了,事實上一些簡單的項目沒有必要將這兩層分離開來,根據實際應用靈活應對就可以了。
????其實這樣寫程序是很方便移植的,根據板子的不同而適當的修改一下硬件層那個 ReadPort 函數就完成了,驅動層和應用層很多代碼可以不經過修改直接用,很能提高開發效率的。當然這個按鍵程序會存在一定的問題,特別是遇到常閉按鍵和點觸按鍵的混合使用的場合。這個留給大家自己去想了,反正問題總是能找到解決辦法的,盡管方法有好有壞。
時間片輪設計思想
????先用一個小例子引出今天的主題,想象一下,一個基本的家電控制板,肯定或多或少的會包含 :LED 或者 數碼管顯示,按鍵, 繼電器或者可控硅的輸出 這3部分。數碼管需要 10ms到20ms的動態掃描,按鍵也需要20ms左右的延時消抖,有沒有意識到,其實這些時間是同時在進行的。
????回想一下咱們的教科書怎么教 按鍵 的延時消抖的?沒錯,死循環,絕對是原地踏步死循環,用指令來計時。這樣很自然的引發一個問題,單片機在原地踏步死循環的話,那么其它的工作怎么辦?如數碼管的動態掃描怎么辦?
????唯有等按鍵掃描之后再進行了,這樣出來的效果,數碼管肯定會閃爍的,掃描時間過長了,縮短按鍵消抖時間也不是解決辦法,想象如果咱們還有其它很多工作也是同時做的呢?解決辦法之一,就是今天的主題,分時掃描的思想。當然不會是唯一的辦法,只不過俺一直在用,覺得這個是非常不錯的思想,可以解決很多實際問題。大膽妄言一下,分時掃描的思想也是單片機編程最核心的思想了,信不信就由你自己判斷了。
核心思想的實現過程
????第一、用RTC中斷來計時,RTC的中斷時間短一點,我習慣是125us ,為了解紅外遙控的碼,這個時間是需要的。RTC計時是相當準的,盡量利用。
????第二、在RTC的中斷服務程序里面放3個(數量自定)記時器(說白了就是計數器),我的習慣是 2ms 5ms 500ms 這3個是作為基準時間,提供給整個系統來調用的,所以必須準確一點,實際用示波器調一下就OK了,不難。
????第三、在主程序的循環里面放一個專門處理時間的子程序。(注:單片機是不會停的,永遠在不斷循環的跑,這個跟學校學的貌似有點不同,俺面試的時候被問過這個問題 ….) ?將所有的時間處理都放在時間處理子程序里面做,這樣是非常方便的,一個單片機系統最起碼需要處理 10~20個不同的時間,也需要10~20個計時器了,而且相當多要求同時不同步工作的,如果每個都單獨的話是相當的麻煩。
????第四、“程序是跑著來等,而不是站著來等”,這話看來有點玄,一個跟俺一起進去公司的工程師討論的時候提到的這個問題,俺覺得這個也是分時系統的一個比較重要的思想,所以也這樣叫,下面有細說。
????第五、下面用程序來說話,注釋盡量詳細,可以不用看代碼,直接看注釋就可以了。
先中斷服務程序部分
????每 125us 中斷一次,產生幾個基準時間。
(1) ref_2ms寄存器不斷的減1,每次中斷減1,一共減 16次,所以這里經過的時間是 125us × 16 = 2ms,這個就是所謂的計時/計數器 了。這樣就可以靠一個系統的RTC中斷,來實現我們需要的很多個定時時間。
(2)置2ms 計時結束標志,這個是提供給時間處理程序用的,這是一個計時器的框架,下面的5ms計時完全相同。
????這程序還用了一個塊的框架,比較方便的,不過跟今天的主題無關,以后郁悶的時候再上來寫寫這個。上面的程序就是中斷服務程序里面的計時器,分別定時 2ms 5ms 500ms,計時完畢溢出是flag_time 標志來記錄的,程序通過讀這個標志就可以知道定時的時間是否已經到了。
下面看那個統一的時間服務子程序
????上面用了按鍵20ms消抖的計時器作為例子,如果理解之后就可以發現,我們可以完全模仿那個計時器而在下面放很多很多的計時器,則每5ms 進來一下,每個計時器都同時在計數了,誰先計算完畢就先關掉自己,置相應的標志給其它程序調用,而對其它計時器完全沒有影響!這樣,我們可以在這里放很多個計時器了,一般來說,十來二十個是沒有問題的,完全滿足一個單片機系統對多個時間的需求了。
????單個計時器的結構很簡單,先判斷允許計時標志是否進入計時,然后一個專用的寄存器在加1或者減1,加/減相應的數值之后也就是相應的時間到了,關掉計時器,置相應需要用到的標志。
????到這里差不多了,俺們需要的時間都可以出來了,這樣做是不是非常方便?咱們再來看看在這段時間里單片機在做了什么東西?只有中斷計時夠 5ms 或者 500ms ,那個溢出標志才有效,才能進入上面的計時程序,其它時間都是在做其它事情。而且進入上面的計時器的時候,可以看出,并不是在那里死循環,只是單純的加減一下寄存器就退出了,整個過程耗時極其短,看代碼不同吧,5us到 20us左右吧,對主程序的執行沒有什么影響。
下面看看具體怎么調用
????最開始談過的按鍵的消抖時間處理問題,現在就用上面介紹的辦法來看具體怎么解決問題。
????大概是這樣的:判斷什么時候有健,沒有的話跳出,有的話開始延時消抖的計時,第二次進來的時候直接由標志位控制過去判斷時間時候夠。
????同樣是等待,這里就是最后一點所說的,咱這是跑著來等,不是站著來等。跟死循環定時比較,在沒有定時到20ms 的這段時間里面單片機在做什么?死循環的話,肯定就是在原地等,什么都不做,而看看上面的程序,他只是判斷是否定時夠,具體的定時在統一的時間子程序里面做,判斷沒有到時間的話就跳出了,繼續跑其它的程序,直到當時間到了,單片機判斷出flag_delay,key_flow 符合條件,開始進入按鍵處理程序了,在這個期間,單片機都在做其它事情,只是一個主循環跑回來判斷一次,所以單片機完全有空跑其它的程序,而沒有將時間都耗在消抖上面。
主程序循環體
????這個就是用到的循環體了,所有功能都做成子程序形式了,需要就掛上去就可以了,比較方便,這樣一個總的循環體,單片機就是在不斷的執行這個循環體,如果整個程序都采用上面說的分時掃的思想的話,一周循環回來的時間是相當短的,其實是不是跟電腦的思想有點像呢?
????電腦再快也并不是同時處理多個任務,而且每次處理一個,然后非常快的速度來循環處理,讓我們感覺上他是在同時處理多個程序那樣,我想,我最終想表達的思想也就是這個而已。有這個思想支撐下,單片機的程序變得比較容易上手了,剩下的只是集中精力去用程序來實現我們的思想而已,當然,這里只是說一種可行的辦法而已,不是說只有這種辦法。
二、嵌入式開發中的C語言編程思想
1 編程風格
????《計算機程序的構造和解釋》一書在開篇寫到:程序寫出來是給人看的,附帶能在機器上運行。
1.1 整潔的樣式
????使用什么樣的編碼樣式一直都頗具爭議性的,比如縮進和大括號的位置。因為編碼的樣式也會影響程序的可讀性,面對一個亂放括號、對齊都不一致的源碼,我們很難提起閱讀它的興趣。
????我們總要看別人的程序,如果彼此編碼樣式相近,讀起源碼來會覺得比較舒適。但是編碼風格的問題是主觀的,永遠不可能在編碼風格上達成統一意見。因此只要你的編碼樣式整潔、結構清晰就足夠了。除此之外,對編碼樣式再沒有其它要求。
????提出匈牙利命名法的程序員、前微軟首席架構師Charles Simonyi說:我覺得代碼清單帶給人的愉快同整潔的家差不多。你一眼就能分辨出家里是雜亂無章還是整潔如新。這也許意義不大。因為光是房子整潔說明不了什么,它仍可能藏污納垢!
????但是第一印象很重要,它至少反映了程序的某些方面。我敢打賭,我在3米開外就能看出程序拙劣與否。我也許沒法保證它很不錯,但如果從3米外看起來就很糟,我敢保證這程序寫得不用心。如果寫得不用心,那它在邏輯上也許就不會優美。
1.2清晰的命名
????變量、函數、宏等等都需要命名,清晰的命名是優秀代碼的特點之一。命名的要點之一是名稱應能清晰的描述這個對象,以至于一個初級程序員也能不費力的讀懂你的代碼邏輯。我們寫的代碼主要給誰看是需要思考的:給自己、給編譯器還是給別人看?
????我覺得代碼最主要的是給別人看,其次是給自己看。如果沒有一個清晰的命名,別人在維護你的程序時很難在整個全貌上看清代碼,因為要記住十多個以上的糟糕命名的變量是件非常困難的事;而且一段時間之后你回過頭來看自己的代碼,很有可能不記得那些糟糕命名的變量是什么意思。
????為對象起一個清晰的名字并不是簡單的事情。首先能認識到名稱的重要性需要有一個過程,這也許跟譚式C程序教材被大學廣泛使用有關:滿書的a、b、c、x、y、z變量名是很難在關鍵的初學階段給人傳達優秀編程思想的;其次如何恰當的為對象命名也很有挑戰性,要準確、無歧義、不羅嗦,要對英文有一定水平,所有這些都要滿足時,就會變得很困難;此外,命名還需要考慮整體一致性,在同一個項目中要有統一的風格,堅持這種風格也并不容易。
????關于如何命名,Charles Simonyi說:面對一個具備某些屬性的結構,不要隨隨便便地取個名字,然后讓所有人去琢磨名字和屬性之間有什么關聯,你應該把屬性本身,用作結構的名字。
1.3恰當的注釋
????注釋向來也是爭議之一,不加注釋和過多的注釋我都是反對的。不加注釋的代碼顯然是很糟糕的,但過多的注釋也會妨礙程序的可讀性,由于注釋可能存在的歧義,有可能會誤解程序真實意圖,此外,過多的注釋會增加程序員不必要的時間。如果你的編碼樣式整潔、命名又很清晰,那么,你的代碼可讀性不會差到哪去,而注釋的本意就是為了便于理解程序。
????這里建議使用良好的編碼樣式和清晰的命名來減少注釋,對模塊、函數、變量、數據結構、算法和關鍵代碼做注釋,應重視注釋的質量而不是數量。如果你需要一大段注釋才能說清楚程序做什么,那么你應該注意了:是否是因為程序變量命名不夠清晰,或者代碼邏輯過于混亂,這個時候你應該考慮的可能就不是注釋,而是如何精簡這個程序了。
2 數據結構
????數據結構是程序設計的基礎。在設計程序之前,應該先考慮好所需要的數據結構。
前微軟首席架構師Charles Simonyi:編程的第一步是想象。就是要在腦海中對來龍去脈有極為清晰的把握。在這個初始階段,我會使用紙和鉛筆。我只是信手涂鴉,并不寫代碼。
????我也許會畫些方框或箭頭,但基本上只是涂鴉,因為真正的想法在我腦海里。我喜歡想象那些有待維護的結構,那些結構代表著我想編碼的真實世界。一旦這個結構考慮得相當嚴謹和明確,我便開始寫代碼。
????我會坐到終端前,或者換在以前的話,就會拿張白紙,開始寫代碼。這相當容易。我只要把頭腦中的想法變換成代碼寫下來,我知道結果應該是什么樣的。大部分代碼會水到渠成,不過我維護的那些數據結構才是關鍵。我會先想好數據結構,并在整個編碼過程中將它們牢記于心。
????開發過以太網和操作系統SDS 940的Butler Lampson:(程序員)最重要的素質是能夠把問題的解決方案組織成容易操控的結構。
????開發CP/M操作系統的Gary.A:如果不能確認數據結構是正確的,我是決不會開始編碼的。我會先畫數據結構,然后花很長時間思考數據結構。在確定數據結構之后我就開始寫一些小段的代碼,并不斷地改善和監測。在編碼過程中進行測試可以確保所做的修改是局部的,并且如果有什么問題的話,能夠馬上發現。
????微軟創始人比爾**·**蓋茨:編寫程序最重要的部分是設計數據結構。接下來重要的部分是分解各種代碼塊。
????編寫世界上第一個電子表格軟件的Dan Bricklin:在我看來,寫程序最重要的部分是設計數據結構,此外,你還必須知道人機界面會是什么樣的。
????我們舉個例子來說明。在介紹防御性編程的時候,提到公司使用的LCD顯示屏抗干擾能力一般,為了提高LCD的穩定性,需要定期讀出LCD內部的關鍵寄存器值,然后跟存在Flash中的初始值相比較。需要讀出的LCD寄存器有十多個,從每個寄存器讀出的值也不盡相同,從1個到8個字節都有可能。如果不考慮數據結構,編寫出的程序將會很冗長。
?????我們分析這個過程,發現能提取出很多相同的元素,比如每次讀LCD寄存器都需要該寄存器的命令號,都會經過讀寄存器、判斷值是否相同、處理異常情況這一過程。所以我們可以提取一些相同的元素,組織成數據結構,用統一的方法去處理這些數據,將數據與處理過程分開來。
????我們可以先提取相同的元素,將之組織成數據結構:
????這里lcd_command表示的是LCD寄存器命令號;lcd_get_value是一個數組,表示寄存器要初始化的值,這是因為對于一個LCD寄存器,可能要初始化多個字節,這是硬件特性決定的;lcd_value_num是指一個寄存器要多少個字節的初值,這是因為每一個寄存器的初值數目是不同的,我們用同一個方法處理數據時,是需要這個信息的。
????就本例而言,我們將要處理的數據都是事先固定的,所以定義好數據結構后,我們可以將這些數據組織成表格:
/*LCD部分寄存器設置值列表*/ lcd_redu_list_struct const lcd_redu_list_str[]= { {SSD1963_Get_Address_Mode,{0x20} ,1}, /*1*/ {SSD1963_Get_Pll_Mn ,{0x3b,0x02,0x04} ,3}, /*2*/ {SSD1963_Get_Pll_Status ,{0x04} ,1}, /*3* {SSD1963_Get_Lcd_Mode ,{0x24,0x20,0x01,0xdf,0x01,0x0f,0x00} ,7}, /*4*/ {SSD1963_Get_Hori_Period ,{0x02,0x0c,0x00,0x2a,0x07,0x00,0x00,0x00},8}, /*5*/ {SSD1963_Get_Vert_Period ,{0x01,0x1d,0x00,0x0b,0x09,0x00,0x00} ,7}, /*6*/ {SSD1963_Get_Power_Mode ,{0x1c} ,1}, /*7*/ {SSD1963_Get_Display_Mode,{0x03} ,1}, /*8*/ {SSD1963_Get_Gpio_Conf ,{0x0F,0x01} ,2}, /*9*/ {SSD1963_Get_Lshift_Freq ,{0x00,0xb8} ,2}, /*10* };????至此,我們就可以用一個處理過程來完成數十個LCD寄存器的讀取、判斷和異常處理了:
/** * lcd 顯示冗余 * 每隔一段時間調用該程序一次 */ void lcd_redu(void) uint8_t tmp[8]; uint32_t i,j; uint32_t lcd_init_flag; lcd_init_flag =0; for(i=0;i<sizeof(lcd_redu_list_str)/sizeof(lcd_redu_list_str[0]);i++) { LCD_SendCommand(lcd_redu_list_str[i].lcd_command); uyDelay(10); for(j=0;j<lcd_redu_list_str[i].lcd_value_num;j++) { tmp[j]=LCD_ReadData(); if(tmp[j]!=lcd_redu_list_str[i].lcd_get_value[j]) { lcd_init_flag=0x55; //一些調試語句,打印出錯的具體信息 goto handle_lcd_init; } } } handle_lcd_init: if(lcd_init_flag==0x55) { //重新初始化LCD //一些必要的恢復措施 } }????通過合理的數據結構,我們可以將數據和處理過程分開,LCD冗余判斷過程可以用很簡潔的代碼來實現。更重要的是,將數據和處理過程分開更有利于代碼的維護。
????比如,通過實驗發現,我們還需要增加一個LCD寄存器的值進行判斷,這時候只需要將新增加的寄存器信息按照數據結構格式,放到LCD寄存器設置值列表中的任意位置即可,不用增加任何處理代碼即可實現!這僅僅是數據結構的優勢之一,使用數據結構還能簡化編程,使復雜過程變的簡單,這個只有實際編程后才會有更深的理解。
3 閱讀書目
????每年都有億萬計的C程序運行在單片機、ARM7、Cortex-M3這些微處理器上,但在這些處理器上如何編寫優質高效的C程序,還要在看看相關書籍。?whaosoft aiot?http://143ai.com?
3.1關于C語言特性
-
Stephen Prata 著 云巔工作室 譯 《C Primer Plus(第五版)中文版》
-
Andrew Koenig 著 高巍 譯 《C陷阱與缺陷》
-
Peter Van Der Linden 著 徐波 譯 《C專家編程》
-
陳正沖 編著 《C語言深度解剖》
3.2關于編譯器
-
杜春雷 編著 《ARM體系結構與編程》
-
Keil MDK 編譯器幫助手冊
3.3關于防御性編程
-
MISRA-C-:2004 Guidelines for the use of the C language in criticalsystems
-
Robert C.Seacord 著 徐波 譯 《C安全編碼標準》
3.4關于編程思想
-
Pete Goodliffe 著 韓江、陳玉 譯 《編程匠藝---編寫卓越的代碼》
-
Susan Lammers 著 李琳驍、吳詠煒、張菁《編程大師訪談錄》
三、了解電容器
電容器是怎么一回事 ? 它是如何裝電的,又是如何放電的?怎么同法拉弟扯上關系了?
電容器是這么一回事......
它是一種裝電的容器,是一種容納電荷的器件。
?
任何兩個彼此絕緣且相隔很近的導體(包括導線)間都構成一個電容器
絕緣介質不同,導體間距不同,其裝電的本領不同的,這個我們后面再詳細說。?
我們理解一下
電容器是如何裝電又如何放電?
電容器充電示意圖
電容器放電示意圖
?
電容器裝電的本領?—?靜電容量
水桶裝水的本領用容積表示,基本單位:立方米.
電容器裝電的本領用靜電容量表示,基本單位:法拉(F).
1法拉的意思是給1V的電壓,這個電容器能裝1庫侖(6.25×1018個電子)電荷!
在電容器這個領域1法拉太大了……!皮法、納法和微法才是常用的單位。
1法拉(F)=103毫法(mF)=106微法(uF)=109納法(nF)=1012皮法(pF)
"為什么用法拉做容量的單位?"
童鞋們總是很認真!
用“法拉”做容量的單位,是讓世人緬懷那個名叫“法拉弟”的牛人在電學上無與倫比的牛逼貢獻!
讓我們看看他有多牛逼......
?
他的全名叫邁克爾·法拉第——Michael Faraday
他幼年時沒有受過正規教育,因家里貧窮只讀了兩年小學.
牛逼的人都早早退學創業去了!
他22歲開始有幸做了當時科技大咖戴維的仆人,幫老師擦鞋,做實驗,跟著這個牛逼的大師在歐洲各國混江湖!
有個牛逼的導師是你是否能牛逼的前提!
他27歲開始在大師戴維的指導下,做了一系列不僅僅是擦鞋的事兒,其中在研究合金鋼時首創了金相分析方法,今天科技研究合金都用這個方法….
牛逼的人干啥都像樣!
他30歲時,干了一件他自己覺得很牛逼的事,發明一個只要有電通過相關機件就會轉動的裝置,人類歷史上第一臺電動機,,裝置簡陋,但它卻是今天世界上使用的所有電動機的祖先。
但由于他沒有先向當時的大牛逼導師戴維匯報就自行發表成果,大牛逼導師戴維表示很生氣.他被認為不乖被打屁屁了,各門各派向他發出噓聲……
即使你已是個牛逼,但有大牛逼在場時,低調點!
在冷眼、誹謗甚至污辱中郁悶了很多年……,?他只能聽服從安排去玩玻璃….,直到大牛逼戴維去世,才又做他喜歡做的事。
他40歲時,干了一件轟動江湖連當時各門各派的大咖們都覺得牛逼的大事,他用實驗揭開了電磁感應定律,發明了圓盤發電機。
結構雖然簡單,但它卻是人類創造出的第一個發電機,現代世界上產生電力的發電機就是從它開始的。
……..他還有很多牛逼的事兒,如最先提出電場概念和電場線概念的。在電化學方面精心試驗,總結了兩個電解定律,這兩個定律均以他的名字命名,構成了電化學的基礎。他將化學中的許多重要術語給予了通俗的名稱,如陽極、陰極、電極、離子等…….
法位第的勞動成果如同太陽般無私而實在地給人類帶來光明動力!
是從法拉第的發明成果開始,才有我們人類社會今天五彩繽紛的世界!
法拉第的貢獻惠及每個人,把人類文明提高到空前高度,把文明進程提前幾百年……!
用“法拉”做容量的單位,我們就是這樣率真地緬懷這個名叫“法拉弟”的牛人!
關于電容器裝電的本領——靜電容量的大小可以通過公式C=Q/U進行測算,當然現在有很多專用的儀器可以直接測試出來了。
關鍵是,電容器裝電的本領——靜電容量,由電極板正對面積、電極板間距和兩電極板間的介質種類三決定的,公式關系如下:
即是說,電容器的絕緣介質不同,電極板間距不同,電板板正對面積不同其裝電的本領不同的.
還有一點很重要的是,電容器的絕緣介質當存在某方面的優點時總會在另一方面存在缺點。
據此,為提高電容器的裝電能力,或適應不同的使用場合,工程師們各顯神通,創造出各有五花八門各有特點電容器。
四、紅外遙控編解碼
紅外遙控器原理介紹
????紅外線遙控是目前使用最廣泛的一種通信和遙控手段。由于紅外線遙控裝置具有體積小、 功耗低、 功能強、 成本低等特點, 因而, 繼彩電、 錄像機之后, 在錄音機、 音響設備、 空調機以及玩具等其它小型電器裝置上也紛紛采用紅外線遙控。工業設備中, 在高壓、 輻射、 有毒氣體、 粉塵等環境下, 采用紅外線遙控不僅完全可靠而且能有效地隔離電氣干擾。
????紅外遙控系統:通用紅外遙控系統由發射和接收兩大部分組成, 應用編/解碼專用集成電路芯片來進行控制操作, 如圖1所示。發射部分包括鍵盤矩陣、 編碼調制、 LED紅外發送器;接收部分包括光、 電轉換放大器、 解調、 解碼電路。
紅外的簡單發射接收原理
????在發射端,輸入信號經放大后送入紅外發射管發射,在接收端,接收管收到紅外信號后,由放大器放大處理后還原成信號,這就是紅外的簡單發射接收原理。
1、紅外遙控系統結構
????紅外遙控系統的主要部分為調制、發射和接收,如下圖所示:
????紅外遙控是以調制的方式發射數據,就是把數據和一定頻率的載波進行“與”操作,這樣既可以提高發射效率又可以降低電源功耗。
????調制載波頻率一般在30khz到60khz之間,大多數使用的是38kHz,占空比1/3的方波,載波波形如下圖所示,這是由發射端所使用的455kHz晶振決定的。在發射端要對晶振進行整數分頻,分頻系數一般取12,所以455kHz÷12≈37.9kHz≈38kHz。
????目前有很多種芯片可以實現紅外發射,可以根據選擇發出不同種類的編碼。由于發射系統一般用電池供電,這就要求芯片的功耗要很低,芯片大多都設計成可以處于休眠狀態,當有按鍵按下時才工作,這樣可以降低功耗芯片所用的晶振應該有足夠的耐物理撞擊能力,不能選用普通的石英晶體,一般是選用陶瓷共鳴器,陶瓷共鳴器準確性沒有石英晶體高,但通常一點誤差可以忽略不計。
????紅外線通過紅外發光二極管(LED)發射出去,紅外發光二極管(紅外發射管)內部構造與普通的發光二極管基本相同,材料和普通發光二極管不同,在紅外發射管兩端施加一定電壓時,它發出的是紅外線而不是可見光。
????如上圖是LED的驅動最簡單電路,選用元件時要注意三極管的開關速度要快,還要考慮到LED的正向電流和反向漏電流,一般流過LED的最大正向電流為100mA,電流越大,其發射的波形強度越大。
????電路有一點缺陷,當電池電壓下降時,流過LED的電流會降低,發射波形強度降低,遙控距離就會變小。
????上圖所示的射極輸出驅動電路可以解決這個問題,兩個二極管把三級管基極電壓鉗位在1.2V左右,因此三級管發射極電壓固定在0.6V左右,發射極電流IE基本不變,根據IE≈IC,所以流過LED的電流也基本不變,這樣保證了當電池電壓降低時還可以保證一定的遙控距離。
2、一體化紅外接收頭
????紅外信號收發系統的典型電路如上圖所示,紅外接收電路通常被廠家集成在一個元件中,成為一體化紅外接收頭。內部電路包括紅外監測二極管,放大器,限幅器,帶通濾波器,積分電路,比較器等。紅外監測二極管監測到紅外信號,然后把信號送到放大器和限幅器,限幅器把脈沖幅度控制在一定的水平,而不論紅外發射器和接收器的距離遠近。交流信號進入帶通濾波器,帶通濾波器可以通過30khz到60khz的負載波,通過解調電路和積分電路進入比較器,比較器輸出高低電平,還原出發射端的信號波形。注意輸出的高低電平和發射端是反相的,這樣的目的是為了提高接收的靈敏度。
????一體化紅外接收頭,如下圖所示:
????紅外接收頭的種類很多,引腳定義也不相同,一般都有三個引腳,包括供電腳,接地和信號輸出腳。根據發射端調制載波的不同應選用相應解調頻率的接收頭。
????紅外接收頭內部放大器的增益很大,很容易引起干擾,因此在接收頭的供電腳上須加上濾波電容,一般在22uf以上。有的廠家建議在供電腳和電源之間接入330歐電阻,進一步降低電源干擾。
????紅外發射器可從遙控器廠家定制,也可以自己用單片機的PWM產生,家庭遙控推薦使用紅外發射管(L5IR4-45)的可產生37.91KHz的PWM,PWM占空比設置為1/3,通過簡單的定時中斷開關PWM,即可產生發射波形。
紅外編解碼解析
1、編碼格式
????現有的紅外遙控包括兩種方式:PWM(脈沖寬度調制)和PPM(脈沖位置調制)。
兩種形式編碼的代表分別為NEC 和PHILIPS 的RC-5、RC-6 以及將來的RC-7。
??? PWM(脈沖寬度調制):以發射紅外載波的占空比代表“0”和“1”。為了節省能量,一般情況下,發射紅外載波的時間固定,通過改變不發射載波的時間來改變占空比。例如常用的電視遙控器,使用NEC upd6121,其“0”為載波發射0.56ms,不發射0.56ms;其“1”為載波發射0.56ms,不發射1.68ms;此外,為了解碼的方便,還有引導碼,upd6121 的引導碼為載波發射9ms,不發射4.5ms。upd6121 總共的編碼長度為108ms。
????但并不是所有的編碼器都是如此,比如TOSHIBA 的TC9012,其引導碼為載波發射4.5ms,不發射4.5ms,其“0”為載波發射0.52ms,不發射0.52ms,其“1”為載波發射0.52ms,不發射1.04ms。
????PPM(脈沖位置調制):以發射載波的位置表示“0”和“1”。從發射載波到不發射載波為“0”,從不發射載波到發射載波為“1”。其發射載波和不發射載波的時間相同,都為0.68ms,也就是每位的時間是固定的。
????通過以上對編碼的分析,可以得出以某種固定格式的“0”和“1”去學習紅外,是很有可能不成功的。即市面上所宣傳的可以學習64 位、128 位必然是不可靠的。
????另外,由于空調的狀態遠多于電視、音像,并且沒有一個標準,所以各廠家都按自己的格式去做一個,造成差異更大。比如:美的的遙控器采用PWM 編碼,碼長120ms 左右;新科的遙控器也采用PWM 編碼,碼長500ms 左右。如此大的差異,如果按“位”的概念來講,應該是多少位呢?64?128?顯然都不可能包含如此長短不一的編碼。
2、紅外遙控編碼格式
????紅外遙控器的編碼格式通常有兩種格式:NEC 和RC5
??? NEC 格式的特征:
-
使用38 kHz 載波頻率
-
引導碼間隔是9 ms + 4.5 ms
-
使用16 位客戶代碼
-
使用8 位數據代碼和8 位取反的數據代碼
????不過需要將波形反轉一下才方便分析:
??? NEC 協議通過脈沖串之間的時間間隔來實現信號的調制(英文簡寫PWM)。
????邏輯“0”是由0.56ms的38KHZ載波和0.560ms 的無載波間隔組成;邏輯“1”是由0.56ms 的38KHZ 載波和1.68ms 的無載波間隔組成;結束位是0.56ms 的38K 載波。
????下面實例是已知NEC類型遙控器所截獲的波形:
????遙控器的識別碼是Address=0xDD20;其中一個鍵值是Command=0x0E;
????注意:波形先是發低位地址再發高位地址。
????所以0000,0100,1011,1011 反轉過來就是1101,1101,0010,000 十六進制的DD20;
????鍵值波形如下:
????也是要將0111,0000 反轉成0000,1110得到十六進制的0E;另外注意8 位的鍵值代碼是取反后再發一次的,如圖0111,0000 取反后為1000,1111。最后一位是一個邏輯“1”。
??? RC5 編碼相對簡單一些:同樣由于取自紅外接收頭的波形需要反相一下波形以便于分析:
????反相后的波形:
????根據編碼規則:
????得到一組數字:110, 11010, 001101根據編碼定義:
????第一位是起始位S通常是邏輯1。
????第二位是場位F通常為邏輯1, 在RC5 擴展模式下它將最后6位命令代碼擴充到7 位代碼(高位MSB) , 這樣可以從64 個鍵值擴充到128 個鍵值。
????第三位是控制位C它在每按下了一個鍵后翻轉, 這樣就可以區分一個鍵到底是一直按著沒松手還是松手后重復按。
????如圖所示是同一按鍵重復按兩次所得波形, 只有第三位是相反的邏輯, 其它的位邏輯都一樣。
????其后是五個系統地址位:11010=1A, 最后是六個命令位:001101=0D。
總結
- 上一篇: bzoj2435: [Noi2011]道
- 下一篇: [luoguP2760] 科技庄园(背包