網路城邦
上一篇 回創作列表 下一篇   字體:
硬體與軟體的模糊地帶:Read Modify Write
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 modeopen drainfloating等特性,這些特性是什麼東西一定要知道,不然做出來的東西一定是問題百出。

.

上圖是典型的i/o port,同樣的圖在各種cpuI/O說明章節應該都會有,但這張圖我已經把它更簡化,一些控制訊號已經移除,例如DFF(D flip-flop)好幾根腳都已經不見,這類的圖一定要看的懂,因為它是該功能的設計概念,每顆cpu概念都不同,都是由一些很厲害的人設計出來的,不要直覺都是一樣的東西,直覺的毛病不只在228、林宅血案、白色恐怖或是某些古文等地方有,其實做這些設計也是常常有這種問題,不然就不會聽到工程師們喊:【完了!怎麼會這樣~~~】,說實話,其實我也有一樣的毛病。

.

DFF基本上可以把值存在裡面,除非某些控制腳動作,否則input端無論怎麼動作都不會影響到output值。

.

BUF(buffer)只是一個概念性的名詞,有各種buffer可供使用,但基本上就是輸入等於輸出,問題來了,既然輸入等於輸出,那直接接起來就好了,幹麻多此一舉?這個問題不只軟體有,有的硬體工程師也搞不清楚這個問題。

.

Buffer通常與隔絕、改變電流推力、IO(open drain,電壓位準變換等)、時序等原因有關,其中隔絕有時只是單純隔絕,有時是改變線路路徑,有時是阻抗匹配(其實這才是真正原因),所以buffer元件的設計不容易,需要知道很基礎的物理變化,尤其是一些很奇怪的buffer,是硬功夫。

.

BJT(電晶體)NPNMOSN-Type MOSFET,簡稱NMOS,這裡只講最簡單的操作,就是當成開關,BJTB極設成highCE路徑會導通,同時BE路徑的電壓差=0.7V,反之關閉;NMOSGhighDS路徑會導通。注意這裡我改用high/low,是對軟體說明概略功能的權宜之計,但是硬體就要搞清楚元件特性,什麼是電流控制?什麼是電壓控制?甚至是細項的參數,我以前就看過好幾個人,竟然會直接在BJTB極用5V/0V控制,BJT燙翻天還不知道為什麼?

.

MOSBJT是重要的基礎元件,有空了解一下【電流控制電流源】與【電壓控制電流源】有什麼不同?數位類比都可以用這些東西完成,很了不起的發明。

.

電阻是限電流元件歐姆這個人發現電壓與電流會呈現某種比例關係,關係式是V=IR,他當時提出電阻的概念時,被許多人恥笑,還覺得他瘋了,也排擠他,結果這個瘋子觀念成為了近一百多年來最重要的觀念之ㄧ,為了紀念他,電阻的單位便命名為歐姆

.

以上元件的敘述只是簡略的提到,詳細內容除了可以參考電子學外,最重要的是有機會可以請教IC設計與元件廠的前輩們,才能獲得正確且詳細的解答。

.

簡單的元件說明結束後,就要提到重點了,圖中,P1.0PORT_1BIT 0)腳位用程式設為high後,電晶體=ONLED便會點亮,程式為:

    P1.0 = 1;   // LED ON

.

問題是正常的產品不會只有一顆LEDIO腳也絕對會是東接一個,西接一個,有的人會寫一個driver,可以做出各種bit的設定,例如:

    Void PortSet(UINT mask, UINT value);

    PortSet(0x6, 0x2); //只變更bit1,2,並設定bit1=1,bit2=0

    0x6=00000110  0x2=00000010,這個function的功能是只動作mask中該bit值為1bit,其餘不變,然後把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,下一行指令回寫回P1bit0就被誤改了,LED從此熄滅,這種問題很麻煩,因為當發展程式時通常都是軟體工程師用測試板發展,只要這個板子不接成上述的電路,根本不會有這個問題,因為程式太簡單,這個driver就會理所當然的被視為穩定,當放上產品時,只會在整個程式執行到上述巧合時出現問題。

.

        如果今天是linux的程式,雖然上述driver很簡單,也會被寫為囉唆的樣子,所以硬體工程師要看懂就難了,軟體又不懂這個東西,當出問題時又分段debug,分段時上述巧合便不一定會出現,因為linuxARM上運行的程式,debug方式通常都是用送出message的方式完成,不會單步執行,更增加了debug的難度。

.

        所以cpu指令會特別強調位元操作指令(bit manipulation),位元指令在執行位元變更時也是會執行讀取的動作,但不同的地方是會讀DFF的輸出點,就是READ BUS:B的路徑】,所以要很熟悉如何在C語言中表達出位元指令,有的compiler會用#progma來表達,有的會用一些內插組合語言的方法表達等等,都不太相同,一般來說,下列的寫法很可能會被compiler翻成位元指令,注意只是可能,一定要在list檔內再度確認結果,要注意如果該cpu根本沒有這樣的位元指令,那就要想別的方法了。

        P1 &= ~mask;

.

        問題又來了,雖然結果正確,但是該IO腳位原本是high,結果被上述指令設為low,如果我們又把它再度設定為high,那這種行為就會產生不需要的low pulseLED是看不出有什麼問題,可是如果是做一些通訊協定,那就要造成誤動作了,所以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的預防才會正常執行,以上提到的只是一個概念,並不是說這樣的程式碼是比較好的,例如需要超快的執行,上述方法就不管用,要依據專案特性來設計才是。

 

( 心情隨筆心情日記 )
回應 推薦文章 列印 加入我的文摘
上一篇 回創作列表 下一篇

引用
引用網址:https://classic-blog.udn.com/article/trackback.jsp?uid=cchahacaptain&aid=2844692