20世紀30年代,英國送奶公司送奶到訂戶(hù)門(mén)口,沒(méi)蓋子也沒(méi)封口,麻雀和紅襟鳥(niǎo)可以很容易的喝到上層的奶皮。后來(lái),牛奶公司把瓶口用錫箔紙封裝起來(lái)防止鳥(niǎo)的偷食。20年后,英國的麻雀都學(xué)會(huì )了用嘴把奶瓶的錫箔紙啄開(kāi),繼續偷吃它們喜歡的奶皮。然而,同樣是20年,紅襟鳥(niǎo)卻一直沒(méi)學(xué)會(huì )這種方法。生物學(xué)家對這兩種鳥(niǎo)進(jìn)行了研究,從生理角度看它們沒(méi)多大區別,但在進(jìn)化上卻如此的不同。后來(lái)發(fā)現這于它們的生活習性有關(guān),麻雀是群居的鳥(niǎo)類(lèi),當某只發(fā)現了啄破錫箔紙的方法,就可以教會(huì )別的麻雀。而紅襟鳥(niǎo)則喜歡獨居,它們圈地為主,溝通僅止于求偶和對侵犯者的驅逐,因此,就是有某只發(fā)現了那個(gè)方法,別的鳥(niǎo)也無(wú)法知曉。對于人類(lèi)也是如此,進(jìn)步需要交流和行動(dòng),這樣,新技能才可以發(fā)揚光大。于是,我寫(xiě)了一下這些學(xué)習硬件編程中的感受。 一、 初學(xué)匯編 研究生課程結束時(shí),我知道畢設應該是硬件相關(guān)的方向,當時(shí)對硬件的認識是一片空白?粗(zhù)同學(xué)們早就投入到自己的課題中,自己很著(zhù)急。忙著(zhù)去圖書(shū)館借了很多關(guān)于硬件的書(shū),和電子、電路、單片機等相關(guān)的,五花八門(mén),只要覺(jué)得里面有想知道的,就拿回來(lái)啃,像一只忙碌的沒(méi)頭的蒼蠅一樣亂撞。對單片機類(lèi)的,沒(méi)有"型號"的概念,天書(shū)一般,只好理解一些硬件基本知識,很多東西覺(jué)得很好,下功夫理解記憶,但沒(méi)多久就忘了一干二凈,F在想想很正常,因為哪些東西是需要實(shí)踐的。那個(gè)學(xué)期前后借了幾十本書(shū),但看懂的很少。當時(shí)做的筆記,只限于對三態(tài)門(mén)、總線(xiàn)驅動(dòng)器、鎖存器、計數器之類(lèi)的概念了解,很是低級的東西。后來(lái)就看DSP的書(shū),實(shí)驗室的書(shū)不管是寫(xiě)什么系列的,都被我瀏覽了一番,好像朦朧的明白了什么。腦袋里裝了一堆不知前因后果的片斷就到了新學(xué)期,DSP和操作系統更是沒(méi)有頭緒,好在當時(shí)有了確定的目標——TMS320F240。寫(xiě)開(kāi)題報告前,努力的看了ucOS-II操作系統,仔細的讀了有關(guān)240的原理的書(shū)。但仍然對這兩者怎么聯(lián)系起來(lái)的概念很模糊。在開(kāi)題報告里,寫(xiě)了很多怎樣在ucOS 里編程的問(wèn)題,在當時(shí)的理解下,覺(jué)得寫(xiě)了很"充分"的東西,想象著(zhù)以后編程就是那個(gè)樣子的。但實(shí)踐以后知道那時(shí)的理解有些本末倒置了。 開(kāi)題后已是四月上旬了,"嵌入式"、"操作系統"、"移植"、"DSP"這些東西一直在腦子了盤(pán)旋,看書(shū)上網(wǎng)查資料都要朝著(zhù)這些目標。比較蠢的做法是,像我一樣只是努力的找書(shū),企圖把這些底層的東西都理解了,甚至想把匯編的指令都記住。直到五月中旬的一天,覺(jué)得這樣埋頭讀書(shū)不能前進(jìn)了,就準備動(dòng)手。當時(shí)已經(jīng)是非典隔離期了,大家都在拼命的運動(dòng)發(fā)泄各種心情,沒(méi)有學(xué)習的氣氛。我也很怕,沒(méi)心情學(xué)習,但不敢消沉下去。于是我拿過(guò)來(lái)板子、仿真器、電源這些很陌生的東西,試著(zhù)把它們裝起來(lái),接著(zhù)就是裝軟件、仿真器驅動(dòng),因為有安裝步驟的說(shuō)明,我很順利的完成了。但測試軟件時(shí)卻顯示沒(méi)有成功,仿真器不能用,安裝軟件的能力我還是比較自信的,但就是找不到問(wèn)題,請有經(jīng)驗的同學(xué)幫忙結果一樣。忙了兩天還沒(méi)搞定仿真器,嚴重的打擊了我本來(lái)就很迷茫的自信。正當我無(wú)所適從時(shí),很幸運的突然發(fā)現了電腦CMOS設置里有并口設置的選項,我發(fā)現了"EPP"模式,我當時(shí)就知道了這次成功了。這個(gè)開(kāi)頭很難,但困難有多大,解決困難后就有多興奮,興奮之余渾身充滿(mǎn)了前進(jìn)的動(dòng)力。 接下來(lái)就可以編程了,第一步要熟悉軟件編程環(huán)境,我的第一個(gè)疑問(wèn)就是"Simulator"和"Emulator"的區別。我上網(wǎng)到清華的BBS上發(fā)現有很多人在討論DSP,我在別人的貼子中隱約知道了我用仿真器就是"Emulator"(Simulator是在軟件中模擬,開(kāi)始我還想試試,但有仿真器,最終沒(méi)去理會(huì ))。論壇人氣很旺,很多問(wèn)題我都不知所云,大開(kāi)眼界,原來(lái)問(wèn)題有這么多!我的第一個(gè)程序是最簡(jiǎn)單的加法。由于我之前還是努力的看了書(shū),所以用到的簡(jiǎn)單指令不用很費力就可以寫(xiě)出來(lái),但一個(gè)完整的程序不止這些,要知道cmd文件怎么寫(xiě),知道它的作用(當時(shí)不能完全理解,按照大家一貫的寫(xiě)法寫(xiě)),還有中斷向量表、頭文件等。這些文件的作用開(kāi)始是我不能完全理解的,不太明白為什么那么寫(xiě)。大多書(shū)中只是稍微提一下,不能足以幫一個(gè)初學(xué)者建立一個(gè)很明確的概念和編程框架。因為程序很簡(jiǎn)單,我仿照師姐留下的一個(gè)加法程序寫(xiě)了出來(lái)。這個(gè)加法程序用了三天時(shí)間,其中大部分時(shí)間花費在一個(gè)小問(wèn)題上:第一次寫(xiě)程序太隨意,可能是寫(xiě)高級語(yǔ)言程序的毛病,一個(gè)標號的第一個(gè)字母我沒(méi)有寫(xiě)在第一列,而是隨意的打了個(gè)空格(當時(shí)沒(méi)有意識到后果),這個(gè)空格害我找了調試了一天時(shí)間!找出錯誤以后,我又總結了一下,哪些格式可以隨意寫(xiě),哪些要嚴格遵守,這樣再寫(xiě)程序就不那么不自在了。這樣簡(jiǎn)單的"1+1=2",畢竟不能解決我的大部分疑問(wèn),只是稍微了解了編程環(huán)境,還有很多個(gè)疑問(wèn)不能找到答案,所以就把CC'C2000的幫助文件很仔細的看了一遍,很費勁,當時(shí)覺(jué)得沒(méi)有很大收獲,但從此對幫助文件的內容有了大致的了解,在以后的編程過(guò)程中,很習慣的查看幫助。 完成了第一個(gè)程序,以后的就按照這個(gè)結構寫(xiě)其他一些簡(jiǎn)單的程序,逐漸的用到了寄存器、中斷等。這樣就熟悉了以前看書(shū)時(shí)想努力記住但沒(méi)有成功的一些指令和寄存器的配置,還逐步的有了一些調試經(jīng)驗。逐漸的我不像以前那樣急切,很多問(wèn)題是要細心的體會(huì ),試驗一次不能反映全部問(wèn)題,經(jīng)驗是在不斷更正錯誤的基礎上積累起來(lái)的。我的一個(gè)體會(huì )是:不要試圖在最初知道所有問(wèn)題的答案,即使找到的答案也只是紙上談兵,還要在實(shí)踐中深化理解,大部分答案都在實(shí)踐中自然浮出水面。最初編程很死,不敢越雷池半步,這是因為很多東西不完全理解,只有按部就班的把它做出來(lái),不敢加以隨意的變化,變了就不知道對不對。我把這些不理解的東西就當作學(xué)習的目標,帶著(zhù)這些問(wèn)題從練習中逐漸找出答案。這個(gè)過(guò)程中,我養成了一個(gè)習慣:因為問(wèn)題很多,我隨時(shí)都有可能明白了一些,也可能又有了一個(gè)新的疑問(wèn),我就把這些想法隨時(shí)記下來(lái),等待以后驗證或尋找答案。寫(xiě)下來(lái)對我加深印象真的很有用。后來(lái),如果有了什么重要的經(jīng)驗,等一個(gè)程序成功后,都會(huì )把它們總結出來(lái),算是對自己的一種肯定,很有成就感?偨Y的多了,就了解了很多細節的東西,哪怕是以前看起來(lái)很簡(jiǎn)單的指令,也有運用是很巧妙的地方。舉幾個(gè)例子: 1.對形如:y=a1*x1+a2*x2+a3*x3的多項式編程,240指令的裝載臨時(shí)寄存器的指令有LT、LTD、LTP、LTA、LTS,乘法指令有MPY、MPYS、MPYA、MPYU,這些指令中有很多可以同時(shí)執行幾步,如果能巧妙的結合利用,程序很簡(jiǎn)潔、效率很高,但要很好的運用,不是很容易(這些是最能體現DSP特點(diǎn)的指令,還有塊移動(dòng)指令,它們和流水線(xiàn)有關(guān),所以效率很高)。自己寫(xiě)程序不要求很高,但知道它們之間有區別即使不用、記不大清楚,看別人的程序也能充分的領(lǐng)會(huì )其中的巧妙。有一條指令BANZ,我的程序中最初肯定不會(huì )利用它,偶爾一次看到有人用,仔細的體會(huì )了它的用法,發(fā)現用在循環(huán)中真是個(gè)不錯的選擇。 2.有一次看一個(gè)程序,涉及到了定標問(wèn)題,我幾乎是看著(zhù)程序抄下來(lái)的試驗的。其中有幾條非常常見(jiàn)的指令MPY、MPYA、ADD、OR在編譯時(shí)提示有錯誤,程序中有這么兩句: MPY #7FFBH MPYA #0H 我看不出有問(wèn)題,而且和書(shū)上是一模一樣的呀。我就查軟件中的幫助,發(fā)現原來(lái)書(shū)上用錯了!那個(gè)錯誤實(shí)在是非常容易犯的!對MPY #k指令,操作數為立即數時(shí)為只能是 "13-bit short immediate value"。對MPYA指令根本就沒(méi)有立即數尋址方式,只有直接或間接尋址方式。還有ADD和OR的用法都是類(lèi)似的想當然地用,而不注意它地特殊之處。比較幸運的是,這種錯誤編譯器可以發(fā)現的,但有些隱含的錯誤它可能發(fā)現不了,自己又覺(jué)得不可能會(huì )錯,結果出來(lái)后錯誤很難排除。 3.最初面對240眾多的寄存器,初始化時(shí)總覺(jué)得多寫(xiě)了沒(méi)有用到,不然就是少了一些配置,這些要和240內部結構結合起來(lái)記憶理解。開(kāi)始是對CPU寄存器、系統配置寄存器、時(shí)鐘模塊寄存器熟悉,其次熟悉了定時(shí)器、比較模塊的寄存器配置,上手后就慢慢熟悉其他的,比較麻煩的就是EV模塊。大多寄存器只要在程序的最初配置一次,就可以不用管了,個(gè)別的比較特殊。如等待狀態(tài)發(fā)生寄存器WSGR在IO空間中,對它賦值就要用out指令而不是常用的splk。還有如 COMCON,要配置為PWM模式,為保證全比較單元的正確操作,需要對它連續兩次寫(xiě)操作。還有時(shí)鐘控制寄存器CKCR0、CKCR1,編程時(shí),必須先使 CKCR0的CLKMD1=0,禁止PLL,然后根據要求設置CKCR1設置其他位,最后使CLKMD1=1,允許PLL工作(如果使用PLL的話(huà))。還有定時(shí)器的控制寄存器TxCON有時(shí)也需要寫(xiě)兩次,第一次配置,第二次啟動(dòng)?傊,對一些需要比較"特殊"的做法,如果注意總結會(huì )對整體有個(gè)清晰的把握。 二、 移植系統 練習多了,就有點(diǎn)柳暗花明的感覺(jué)。于是躍躍欲試,開(kāi)始試著(zhù)做我的重要任務(wù)——移植uCOS II,看了紹貝貝的書(shū),明白了我要做的是什么,雖然這時(shí)還是霧里看花,理解也很朦朧,但已經(jīng)有了前所未有的自信。最簡(jiǎn)單的方法是去www.ucos.com網(wǎng)站下載已經(jīng)移植的代碼,當時(shí)我查的時(shí)候,對TI的DSP,只有C31和C54的移植代碼下載(不知現在有沒(méi)有更新)。我試著(zhù)從從這兩個(gè)例程中學(xué)習我需要的東西。首先是要明白這么多文件的組織關(guān)系,最初面對一大堆文件,根本不知它們是何種關(guān)系,為什么存在?大多文獻里的都是對幾個(gè)移植文件做了詳細的說(shuō)明,而對怎樣組織的好像是不言而瑜的事情,對一個(gè)資深的程序員確實(shí)沒(méi)有必要教怎么做,可是我沒(méi)有開(kāi)發(fā)大程序的經(jīng)驗,沒(méi)有清晰的把握,因為自己做的匯編程序都是十幾行的小程序,而且還是對cmd文件、向量表、頭文件這些不是程序"核心"的東西沒(méi)有深刻的認識,我對這些零散的文件研究了很久,才意識到我不止要改幾個(gè)函數那么簡(jiǎn)單,還要要寫(xiě)一個(gè)有main.c的文件、一個(gè) cmd文件、中斷向量表、一些必要的頭文件,還要象寫(xiě)其他簡(jiǎn)單的程序一樣做一個(gè)框架,操作系統當成普通的用戶(hù)程序一樣和這個(gè)框架結合起來(lái),然后再寫(xiě)程序和普通的不同的是我有這么多別人都已經(jīng)做了的東西,我要實(shí)現那樣的機制不用自己去寫(xiě),只需拿來(lái)用。 明白這些算是思想上的重大突破,不然連 DSP程序和操作系統的關(guān)系都不知道。這樣就動(dòng)手寫(xiě)移植部分代碼,代碼中的一些是參照一個(gè)例子,那個(gè)應該是240的移植,也只是移植部分的代碼,不是一個(gè)完整的代碼。最初我對ucos認識不深的時(shí)候,借鑒了那個(gè)程序中的一些做法,如任務(wù)切換函數是用軟件中斷INT31,當時(shí)對中斷的認識都很淺,不用說(shuō)軟件中斷了。對于什么是硬件中斷什么是軟件中斷的問(wèn)題也困擾我很長(cháng)時(shí)間,曾經(jīng)問(wèn)過(guò)一個(gè)比我早入手的同學(xué),在他的編程中也沒(méi)有用過(guò)軟件中斷,可以說(shuō)沒(méi)有意識到這兩種中斷只是匯編中普通的中斷。至于為什么用INT31就不知道了,現在知道了可以用任何一個(gè)軟中斷的。還有一個(gè)就是調用庫函數I$$SAVE和I$$ REST用于移植,最初我在傻傻的想,我是不是可以直接用這兩個(gè)函數?可是它們在什么地方?我怎樣找到它們看看代碼?還有很多別的疑問(wèn),但我還是建立了一個(gè).mak把那些覺(jué)得需要的文件加了進(jìn)去并按照以前的做法把它們"歸位"。然后編譯,當然有很多很多錯誤,除了語(yǔ)法錯誤,當然有我不懂的錯誤。我盡可能的改了一些,但不能完全正確。問(wèn)題是出在用C編程上,所以我還要熟悉用C編程的方法。 (一) 用C編程 最初用C寫(xiě)沒(méi)有什么可以參考的書(shū),我還是從最簡(jiǎn)單的加法開(kāi)始,寫(xiě)一個(gè)純粹的C程序,同學(xué)說(shuō)可以用輸出語(yǔ)句輸出結果,我的即使運行正確也不能輸出,找了人幫忙看,還是于事無(wú)補。畢竟用C的人很少,于是我自己開(kāi)始仔細的找原因?瓷傻膍ap文件找結果所在的地址,有結果而且正確,看交*列表,C語(yǔ)句對應的匯編語(yǔ)句,沒(méi)什么錯誤,就是不明白為什么有錯,和別人的不一樣。這當然影響我的自信心,覺(jué)得為什么倒霉的總是我?抱怨當然不是辦法,還是要繼續找錯了。于是我拿起了放了很久都沒(méi)看的TI的C編譯器文檔,雖是全英文的,還是堅持了三天看完了,好像沒(méi)有找到答案,但更堅定了我的想法,錯誤是肯定有的,因為文檔上有printf函數,但好像不影響程序的運行結果,于是在以后的編程中只好暫時(shí)放棄用輸出語(yǔ)句(后來(lái)調試基本成功后換了板子就可以輸出了),這對調試當然很不方便,要在映射的地址中看結果?次臋n的好處是,更清楚的知道了用C和匯編編程的不同,如cmd文件、中斷向量表的寫(xiě)法。因為要涉及到混合編程,就要對在C中和匯編中的函數、變量互相調用問(wèn)題弄明白,這是個(gè)難點(diǎn),那個(gè)文檔看了很多遍,有的問(wèn)題還是不能完全明白,最終在老師的幫助下對它有了比較清晰的理解,理解后就用編程來(lái)驗證,結果是我們的理解是正確的(后來(lái)看到有些書(shū)上也有對此的討論,我甚至能判斷作者的理解是否完全正確)。下面是幾點(diǎn)總結: cmd文件寫(xiě)法可以參照CC‘C2000幫助文件中的例子,還可以查閱TI文檔spru024D的2.8.3。 寄存器映射地址頭文件,和匯編中的不同,要重新定義,定義方法如下: #define CKCR0 (volatile unsigned int *) 0x702B 使用方法: *CKCR0=0x0041; 中斷向量表的第一條語(yǔ)句應該跳轉到_c_int0對于這點(diǎn)我最初不是很明白,因為我看到的程序都是以main開(kāi)始的。后來(lái)逐漸明白了,_c_int0是程序真正開(kāi)始的地方,只是這個(gè)開(kāi)始不是開(kāi)發(fā)者寫(xiě)出來(lái)的,而是編譯器自動(dòng)為我們做好的,你要配合它做的是就是在Build Option中對linker的C Initialization的選項選擇ROM Autoinitialization Model或RAM Autoinitialization Model,而不是匯編中的No Autoinitialization,開(kāi)發(fā)者的程序要以main函數開(kāi)始,初始化結束后會(huì )跳轉到main函數。在反匯編代碼中可以看到這些過(guò)程。兩種初始化的方式詳見(jiàn)上面文檔的同一節。 匯編代碼中要用到的C的變量或符號,都要在前面加"_ ",即C中的fun要用在匯編中寫(xiě)為"_fun"。當然互調前要聲明為全局變量或外部函數。詳細的說(shuō)明見(jiàn)spru024D的4.2.2。在C中要嵌入匯編的格式為: asm(" clrc INTM"); 這個(gè)地方要注意的是引號里面第一個(gè)字符為空格或Tab鍵(還可以是別的記不大清了),不能直接寫(xiě)指令。為什么會(huì )有上面的一些規定,看看反匯編的代碼就很清楚了,編譯后編譯器會(huì )為C中的符號都加以下劃線(xiàn),所以在匯編中用當然要寫(xiě)成"一樣"的了,第二條規則也是和編譯以后的程序格式有關(guān),可以在你的程序中故意不正確的寫(xiě),看看顯示的錯誤就明白了。 比較難的是C調用匯編函數,匯編函數的寫(xiě)法。這時(shí)要在匯編函數的開(kāi)始和結束加入一些語(yǔ)句。C中用三個(gè)寄存器管理堆棧和局部幀:AR1作為堆棧指針 SP,AR0作為幀指針FP,AR2最為局部變量指針LVP。調用函數時(shí)當前指針必須為AR1,首先要在軟件堆棧中保存函數返回地址、FP,分配局部幀空間,空間的大小是局部變量的個(gè)數加1,如果被調函數中可能修改寄存AR6、AR7,也要保存(當編譯器優(yōu)化時(shí)它們被用作保存寄存器變量)。函數實(shí)現過(guò)程中注意調用它的函數傳遞的參數的存放次序:從右到左按照堆棧增長(cháng)的方向放置。函數退出時(shí)和進(jìn)入函數時(shí)的操作相反。這個(gè)規則的原因也可以從生成的交*列表中找到答案,和上面的原因大同小異,C語(yǔ)句編譯后的匯編代碼可以看出在任何一個(gè)函數調用前都會(huì )有這樣的"保存 "工作,結束時(shí)做相應的恢復。詳見(jiàn)spru024D的4.2.2,4.2.4和4.3節。 用C時(shí)需要.lib庫函數,這個(gè)格式的不能用文本的形式看,在它的同一目錄下有rts.scr文件可以以文本形式打開(kāi)。用一個(gè)命令可以提取某個(gè)庫函數可以對它查看或是修改。我知道這個(gè)過(guò)程,但沒(méi)有用過(guò),所以不多說(shuō)了。詳見(jiàn)spru024D的4.1.3。 (二)系統調試及總結 明白了以上的規則就可以大膽的用C編程了,確實(shí)要比匯編方便了很多,F在還回到我的移植程序中,弄懂上面的東西后又修改了一些錯誤,到了六月上旬,整個(gè)程序編譯通過(guò)了。我很興奮,終于有了進(jìn)展。接下來(lái)就是調試,調試是比編寫(xiě)程序痛苦的事,對那些隱藏的錯誤要能順利的找出來(lái)實(shí)在是困難。系統不能正常工作,首先懷疑的就是移植代碼部分,移植代碼是按照ucOS提供的步驟寫(xiě)的,寫(xiě)的時(shí)候由于借鑒了別的例子并沒(méi)有深刻理解,調試時(shí)就必須有個(gè)深刻地認識。 難點(diǎn)一:對任務(wù)堆棧初始初始化函數OSTaskStkInit()的作用的理解,方法是模擬TI公司的I$$SAVE庫函數對任務(wù)堆棧初始化,按照庫函數地保存順序開(kāi)辟?臻g,得到堆棧指針。這個(gè)函數的編寫(xiě)要充分理解"堆棧"的概念。芯片本身的堆棧只有 8 級,無(wú)法作為系統堆棧使用,這8級堆棧用來(lái)保存函數調用和中斷的返回地址。C 編譯器將寄存器AR0、AR1作為SP和FP管理系統堆棧和局部幀(上面有說(shuō)明)。編譯器使保存在硬件堆棧里的返回地址彈出保存在系統堆棧里,并保存其他寄存器,即保存了任務(wù)運行的現場(chǎng),這些工作都由I$$SAVE來(lái)做。有了任務(wù)堆棧初始化函數OSTaskStkInit(),系統在進(jìn)行初始化時(shí),這個(gè)函數將任務(wù)地址放在堆棧中,然后用中斷返回也就是I$$REST函數將寄存器和TOS初始化,將任務(wù)的起始地址彈回到TOS中,這樣就能從中斷的任務(wù)開(kāi)始運行了。 難點(diǎn)二:時(shí)鐘節拍。對節拍地作用是在看了操作系統的內核代碼后有了深刻認識的,雖然移植是可能不太了解具體的代碼,但沒(méi)有操作系統的概念最好把這個(gè)比較易學(xué)的操作系統的代碼看看,有個(gè)結構和原理的認識,我大概花了一周仔細的看了書(shū)和代碼,這次和以前看不同,了解了代碼實(shí)現地細節,看完會(huì )更加頭腦清醒。最初地時(shí)鐘節拍中斷是用實(shí)時(shí)時(shí)鐘中斷RTI,看完代碼知道了應該不可以用它來(lái)實(shí)現節拍,因為系統時(shí)鐘地啟動(dòng)是在初始化結束后第一個(gè)任務(wù)開(kāi)始前啟動(dòng)的,實(shí)時(shí)時(shí)鐘不能這樣控制,它在板子上電是就啟動(dòng)了,所以我改用定時(shí)器1實(shí)現。時(shí)鐘節拍函數OSTickISR()的實(shí)現是定時(shí)器的硬件中斷,因為在我修改過(guò)程中不斷地出錯,這使我熟練掌握了F240的中斷編程方法。我對中斷編程做了總結,包括用C編寫(xiě)中斷,對初學(xué)者應該有幫助: 在UCOS中的中斷編程和一般的中斷編程稍有不同。共同的是: 1.中斷矢量表。中斷矢量表一定要定位在程序空間的地址0開(kāi)始的地方,0000h~003Fh為中斷矢量表。第0行跳轉到代碼開(kāi)始的地方、第1到第6行是硬件中斷跳轉指令,除NMI中斷其他是軟件中斷指令(INTk最大為INT31)。發(fā)生硬件中斷后,處理器自動(dòng)到前面查表跳轉到相應位置。軟件中斷是在程序中執行了INTk指令才會(huì )發(fā)生,然后根據x查中斷矢量表跳轉到相應的ISR(移植系統時(shí)用到了軟件中斷指令,即任務(wù)切換函數)。 2.硬件中斷系統。其中的可屏蔽中斷INTk(k=1~6)對應了多個(gè)中斷源,有系統中斷和EV中斷,它們都通過(guò)INTk和CPU相關(guān)(將INTk稱(chēng)為內核中斷)。寫(xiě)系統中斷或EV中斷必須知道對應哪個(gè)內核中斷。因為每個(gè)內核中斷對應了幾個(gè)中斷源(具體對應查閱書(shū)籍)。有中斷源復用問(wèn)題,當一個(gè)內核中斷中有多于一個(gè)的外部中斷發(fā)生時(shí),就要查外部中斷矢量表,它是根據中斷標識排列的。注意每條跳轉指令占兩個(gè)字節。外部中斷矢量表的位置可以在程序空間的任何位置(表中有一個(gè)代表表的起始位置的符號)。對它的查表方式為基址+變址;窞楸淼钠鹗嫉刂,變址為中斷源的標識×2。 3.中斷編程。本程序的 INT1有中斷復用,如果用匯編編寫(xiě),需要外部中斷矢量表。但在ucOS中編寫(xiě)串口通訊時(shí),發(fā)送任務(wù)沒(méi)有采用中斷方式,它要和接收任務(wù)通訊而有相應的動(dòng)作(沒(méi)有操作系統時(shí)就是查詢(xún)方式)。即接收中斷發(fā)生后要調用發(fā)送信號量函數(也可以使用別的通訊機制),在匯編中調用較麻煩,所以采用了C編程,這樣在 INT1的ISR中用了switch語(yǔ)句,就不用外部中斷矢量表。其他中斷源沒(méi)有復用問(wèn)題。所以整個(gè)程序也不需要外部中斷矢量表。 在ucOS中的中斷編程要注意的是進(jìn)入中斷調用庫函數I$$SAVE(C編寫(xiě)自動(dòng)調用),而程序結束是調用OSINTExit()(最后調用了I$$REST)。 看了操作系統的源代碼再加上對F240編程方法進(jìn)一步掌握,感覺(jué)對這個(gè)系統的整體有很清晰的認識,很高興自己在無(wú)數的錯誤中摸索出來(lái)了。不幸的是我的程序仍然不能成功地運行,但我相信應該問(wèn)題不太大,應該不是在關(guān)鍵處。因為系統可以單步運行且結果正確。我很幸運的在網(wǎng)上遇到了一位做過(guò)在2407移植的人,網(wǎng)上還有完整地源碼下載。我對比了程序,仍然不知錯在哪里,然后我向他討教,他給了一個(gè)建議是,查看map文件,找到兩個(gè)常量表OSMapTbl和 OSUnMapTbl的映射地址,看看它們的值是否正確。我查看了一下沒(méi)錯,但偶然發(fā)現一個(gè)寄存器的映射地址是錯的,再看其他寄存器也是錯的,因為用C編程寄存器的映射頭文件要重新定義,我采用的是賦值語(yǔ)句,定義指針,程序中用指針尋址,這種方法應該是沒(méi)有問(wèn)題的,但我知道有它的不方便的地方,不利于我的程序中在每個(gè)需要的文件中包含這個(gè)頭文件,于是就改成宏定義寄存器,這樣改過(guò)之后地址映射就正確了。我把程序從頭到尾的看了很多遍,包括反匯編的代碼都仔細去找錯,倒是有點(diǎn)小bug被找出來(lái),但都不是致命的那個(gè)。在我身心俱憊時(shí),我決定放松一下。放風(fēng)了兩天,仍然心有不甘,就又著(zhù)手我的程序。這次我把所有的寄存器配置從操作系統中拿開(kāi),在匯編中檢驗,終于找到了那個(gè)bug:SYSCR的配置錯誤,這個(gè)常用的寄存器不用看幫助就知道怎樣配置,但我寫(xiě)錯了,可能是最初的筆誤,直接導致了系統的復位。系統終于可以運行了!從我的第一個(gè)程序到這時(shí)有兩個(gè)多月的時(shí)間,我學(xué)業(yè)生涯中最不平坦的兩個(gè)多月。 我換了一塊板子,以前不能顯示輸出語(yǔ)句結果的問(wèn)題也解決了,于是我又寫(xiě)了比較復雜的測試程序測試系統運行情況,雖然不是一次就可以寫(xiě)成功的,但這次調試用了很短的時(shí)間。還有下面的總結,是C語(yǔ)言幾個(gè)關(guān)鍵字有關(guān)的內容,雖然較為基礎,在操作系統中可能用到的,對透徹把握程序很有幫助。 1.volatile類(lèi)型限定符。用它修飾的對象叫易變對象,用于告訴編譯程序它所修飾的對象(可以是變量或常量)的值可能會(huì )以程序中未顯式指定的方式發(fā)生變化,即不是由程序中的賦值、初始化等顯式指定的方式發(fā)生變化。如,其變化可能式由中斷程序或IO端口所施加的。再如,在程序中可能會(huì )把某個(gè)全局變量的地址傳送給操作系統的時(shí)鐘并用于存放系統的實(shí)際時(shí)間,盡管程序中沒(méi)有對這個(gè)變量使用賦值語(yǔ)句賦值,但它的內容還是變了。舉個(gè)例子: volatile int ticks; interrupt timer( ) { ticks++; } wait (int time) { ticks = 0; while (ticks < time ) ; } 如果ticks沒(méi)有聲明為易失變量,編譯程序可能會(huì )把它當作寄存器變量分配,從而wait函數執行永遠不會(huì )中止。上例中的interrupt 關(guān)鍵字是修飾中斷函數的,表示改函數與中斷相聯(lián)系。 2.typedef 定義新的數據類(lèi)型。例: typedef unsigned int INT16U; 定義了類(lèi)型INT16U,這樣做可以提高程序的可移植性。例如,有些C編譯系統沒(méi)有提供無(wú)符號短整數unsigned short int類(lèi)型,這樣,在其他編譯系統整運行的使用了這種類(lèi)型的編譯器就要把程序中出現的unsigned short int都換成另一種合適的類(lèi)型如unsigned int,這樣改動(dòng)比較大。使用類(lèi)型定義可以解決這個(gè)問(wèn)題,這時(shí),在允許使用unsigned short int 的編譯系統中編寫(xiě)程序時(shí),先使用一個(gè)類(lèi)型定義: typedef unsigned short int USINT ; 以后在其他編譯系統運行時(shí),只要把改類(lèi)型定義改成: typedef unsigned int USINT ; 其他地方不動(dòng)就可以了。 還有一點(diǎn)就是可以避免因不同編譯系統實(shí)現上的差別而帶來(lái)的可移植性問(wèn)題。例在某些編譯系統中char、short(有無(wú)符號)用8位表示,而在 TMS320C2xx的系統中,都用16位表示,為使在前者系統中運行的程序在C2xx中運行,必須把char、short改成int,這時(shí),使用類(lèi)型定義可以減少修改。 3.register存儲類(lèi)區分符。用它修飾說(shuō)明的變量叫寄存器變量,它的用途一是與auto一樣,使它所修飾的對象成為自動(dòng)的,二是建議編譯程序在存儲分配使盡可能的把它分配到機器存儲器中,以便用時(shí)能快速存取。使用寄存器變量最常見(jiàn)的情況時(shí)把循環(huán)控制變量說(shuō)明成寄存器變量,因為這個(gè)變量在每次循環(huán)時(shí)都要至少訪(fǎng)問(wèn)一次,例: register int I ; fun( ) { for ( I =0 ;I <1000;I ++) { ...... } } 理論上,一個(gè)程序中可說(shuō)明任意多個(gè)寄存器變量,實(shí)際上,由于寄存器數目的物理限制,編譯系統只把最前面的有限數目的寄存器變量分配在寄存器中,而把其余的當作普通自動(dòng)變量處理。在F240中,編譯系統允許把AR6、AR7用了存儲寄存器變量,使用優(yōu)化選項是還允許使用AR5(見(jiàn) spru024D4.2.4)。 以上是我學(xué)習中的一些總結。我相信有很多像我這樣從零開(kāi)始的后來(lái)者,希望能給他們一點(diǎn)信心和勇氣,讓他們知道有我這樣一個(gè)人也經(jīng)歷許多郁悶的日子,最終解決了當時(shí)覺(jué)得解決不了的問(wèn)題。如果看到我用了"幸運"這個(gè)詞,不要羨慕我的運氣,不要怪自己得問(wèn)題為什么不能"幸運"的解決(我就曾這樣痛苦的想過(guò)),因為可能你更幸運,根本就不會(huì )像我這么"倒霉"遇到它。但總會(huì )有問(wèn)題發(fā)生,如果解決了,你更幸運的獲得了一個(gè)寶貴的經(jīng)驗、非常難得的財富。萬(wàn)事開(kāi)頭難,入了門(mén)腳下的路就就會(huì )逐步平坦了。 |