字體:小 中 大 | |
|
|
2009/04/13 12:00:32瀏覽8323|回應0|推薦0 | |
寫PC程式的可能不需要知道這些東西,但是做embedded system的工程師們,一定會碰到基本的i/o需求,尤其是現在的embedded system許多都有複雜的作業系統在內,已經不是硬體工程師用硬幹的方式就ok,所以都需要專業的軟體工程師完成。 . 從使用高功率元件刻出110V的電源波形,到最簡單的LED點燈,都是用I/O完成,有時想到以前學校計算機課程的【跑馬燈】,真的感覺那個沒用的爛課程真是不知道害死了多少人,學生就好像屍鬼一樣,依法師指令做動作,下了課後一大半的人不知道在幹什麼,我看連助教自己也不知道自己在幹什麼?然後每個學生都被冠上”混”的罪名,很冤枉! . 現在的CPU不只程式區(flash)可以動態寫入,連各種內部功能都可以使用tool來動態配置,I/O port也可以動態設定成i/o mode、open drain、floating等特性,這些特性是什麼東西一定要知道,不然做出來的東西一定是問題百出。 . 上圖是典型的i/o port,同樣的圖在各種cpu的I/O說明章節應該都會有,但這張圖我已經把它更簡化,一些控制訊號已經移除,例如DFF(D flip-flop)好幾根腳都已經不見,這類的圖一定要看的懂,因為它是該功能的設計概念,每顆cpu概念都不同,都是由一些很厲害的人設計出來的,不要直覺都是一樣的東西,直覺的毛病不只在228、林宅血案、白色恐怖或是某些古文等地方有,其實做這些設計也是常常有這種問題,不然就不會聽到工程師們喊:【完了!怎麼會這樣~~~】,說實話,其實我也有一樣的毛病。 . DFF閘基本上可以把值存在裡面,除非某些控制腳動作,否則input端無論怎麼動作都不會影響到output值。 . BUF(buffer)只是一個概念性的名詞,有各種buffer可供使用,但基本上就是輸入等於輸出,問題來了,既然輸入等於輸出,那直接接起來就好了,幹麻多此一舉?這個問題不只軟體有,有的硬體工程師也搞不清楚這個問題。 . Buffer通常與隔絕、改變電流推力、IO(open drain,電壓位準變換等)、時序等原因有關,其中隔絕有時只是單純隔絕,有時是改變線路路徑,有時是阻抗匹配(其實這才是真正原因),所以buffer元件的設計不容易,需要知道很基礎的物理變化,尤其是一些很奇怪的buffer,是硬功夫。 . BJT(電晶體)是NPN,MOS是N-Type MOSFET,簡稱NMOS,這裡只講最簡單的操作,就是當成開關,BJT的B極設成high,CE路徑會導通,同時BE路徑的電壓差=0.7V,反之關閉;NMOS是G極high,DS路徑會導通。注意這裡我改用high/low,是對軟體說明概略功能的權宜之計,但是硬體就要搞清楚元件特性,什麼是電流控制?什麼是電壓控制?甚至是細項的參數,我以前就看過好幾個人,竟然會直接在BJT的B極用5V/0V控制,BJT燙翻天還不知道為什麼? . MOS與BJT是重要的基礎元件,有空了解一下【電流控制電流源】與【電壓控制電流源】有什麼不同?數位類比都可以用這些東西完成,很了不起的發明。 . 電阻是限電流元件,”歐姆”這個人發現電壓與電流會呈現某種比例關係,關係式是V=IR,他當時提出電阻的概念時,被許多人恥笑,還覺得他瘋了,也排擠他,結果這個瘋子觀念成為了近一百多年來最重要的觀念之ㄧ,為了紀念他,電阻的單位便命名為”歐姆”。 . 以上元件的敘述只是簡略的提到,詳細內容除了可以參考電子學外,最重要的是有機會可以請教IC設計與元件廠的前輩們,才能獲得正確且詳細的解答。 . 簡單的元件說明結束後,就要提到重點了,圖中,P1.0(PORT_1的BIT 0)腳位用程式設為high後,電晶體=ON,LED便會點亮,程式為: P1.0 = 1; // LED ON . 問題是正常的產品不會只有一顆LED,IO腳也絕對會是東接一個,西接一個,有的人會寫一個driver,可以做出各種bit的設定,例如: Void PortSet(UINT mask, UINT value); PortSet(0x6, 0x2); //只變更bit1,2,並設定bit1=1,bit2=0 0x6=00000110 0x2=00000010,這個function的功能是只動作mask中該bit值為1的bit,其餘不變,然後把value設定進該bit的位置。 很方便,這種程式內容通常會有: void PortSet(UINT mask, UINT value) { UINT Tmp; Tmp = P1 & ~mask; // clear specified bits value &= mask; P1 = Tmp | value; …… } 可是問題來了,記不記得LED點亮了,bit0=1,可是該腳位因為電晶體特性,雖然是邏輯high,但是電壓=0.7V,這樣的電壓在邏輯上=LOW,所以當執行: Tmp = P1 & ~mask; . P1.0被讀入,回頭看圖,【讀取的路徑是走BUF那條路(READ BUS:A) 】,結果邏輯是LOW,下一行指令回寫回P1時bit0就被誤改了,LED從此熄滅,這種問題很麻煩,因為當發展程式時通常都是軟體工程師用測試板發展,只要這個板子不接成上述的電路,根本不會有這個問題,因為程式太簡單,這個driver就會理所當然的被視為穩定,當放上產品時,只會在整個程式執行到上述巧合時出現問題。 . 如果今天是linux的程式,雖然上述driver很簡單,也會被寫為囉唆的樣子,所以硬體工程師要看懂就難了,軟體又不懂這個東西,當出問題時又分段debug,分段時上述巧合便不一定會出現,因為linux在ARM上運行的程式,debug方式通常都是用送出message的方式完成,不會單步執行,更增加了debug的難度。 . 所以cpu指令會特別強調位元操作指令(bit manipulation),位元指令在執行位元變更時也是會執行讀取的動作,但不同的地方是會讀DFF的輸出點,就是【READ BUS:B的路徑】,所以要很熟悉如何在C語言中表達出位元指令,有的compiler會用#progma來表達,有的會用一些內插組合語言的方法表達等等,都不太相同,一般來說,下列的寫法很可能會被compiler翻成位元指令,注意只是可能,一定要在list檔內再度確認結果,要注意如果該cpu根本沒有這樣的位元指令,那就要想別的方法了。 P1 &= ~mask; . 問題又來了,雖然結果正確,但是該IO腳位原本是high,結果被上述指令設為low,如果我們又把它再度設定為high,那這種行為就會產生不需要的low pulse,LED是看不出有什麼問題,可是如果是做一些通訊協定,那就要造成誤動作了,所以driver是不可以這樣寫了。 . 比較理想的是 void PortSet(UINT mask, UINT value) { static UINT Tmp = 0; Tmp &= ~mask; // clear specified bits value &= mask; Tmp |= value; P1 = Tmp; …… } 注意static等同global的效果,只是只能被這個function看見,把P1的值永久存在記憶體內,對這個值運作再設進P1就沒有問題了,千萬要注意該function不可以被遞迴呼叫,也不可以被同時執行,一定要做一些critical section或是mutex的預防才會正常執行,以上提到的只是一個概念,並不是說這樣的程式碼是比較好的,例如需要超快的執行,上述方法就不管用,要依據專案特性來設計才是。 |
|
( 心情隨筆|心情日記 ) |