DSP編程的一些經驗?

我看過一個故事:在20世紀30年代,英國送奶公司送到訂戶門口的牛奶,沒有蓋子也沒封口,麻雀和紅襟鳥可以很容易的喝到上層的奶皮。後來,牛奶公司把瓶口用錫箔紙封裝起來,想防止鳥的偷食。但20年後,英國的麻雀都學會了用嘴把奶瓶的錫箔紙啄開,繼續偷吃它們喜歡的奶皮。然而,同樣是20年,紅襟鳥卻一直沒學會這種方法。生物學家對這兩種鳥進行了研究,從生理角度看它們沒多大區別,但在進化上卻如此的不同。後來發現這於它們的生活習性有關,麻雀是群居的鳥類,常常一起行動,當某隻發現了啄破錫箔紙的方法,就可以教會別的麻雀。而紅襟鳥則喜歡獨居,它們圈地為主,溝通僅止於求偶和對侵犯者的驅逐,因此,就是有某隻發現了那個方法,別的鳥也無法知曉。對於人類也是如此,進步需要交流和行動,這樣,任何一個有了新技能才可以真正的發揚光大,使人類生生不息。於是,我寫了一下這些學習硬件編程中的感受。如果你已經有硬件開發經驗,我的東西也不必看了,裡面可能是比較幼稚的想法。如果你準備移植ucOS,最好先去網上查有無移植好的代碼,如果有,也不用看了。

一、 初學彙編我的研究生課程結束時,知道畢設應該是硬件相關的方向,當時我對硬件的認識是一片空白,看著同學們早就忙忙碌碌的投入到自己的課題中,自己很著急。忙著去圖書館借了很多關於硬件的書,和電子、電路、單片機等相關的,五花八門的,只要覺得裡面有想知道的,就拿回來啃,飢不擇食,又像一隻忙碌的沒頭的蒼蠅一樣亂撞。對單片機類的,沒有"型號"的概念,天書一般,只好理解一些硬件基本知識,很多東西覺得很好,下功夫理解記憶,但沒多久就忘了一乾二淨。現在想想很正常,因為哪些東西是需要實踐的。那個學期前後借了幾十本書,但看懂的很少。當時做的筆記,只限於對三態門、總線驅動器、鎖存器、計數器之類的概念瞭解,很是低級的東西。後來就看DSP的書,實驗室的書不管是寫什麼系列的,都被我瀏覽了一番,好像朦朧的明白了什麼。腦袋裡裝了一堆不知前因後果的片斷就到了新學期,DSP和操作系統更是沒有頭緒,好在當時有了確定的目標——TMS320F240。寫開題報告前,努力的看了ucOS-II操作系統,仔細的讀了有關240的原理的書。但仍然對這兩者怎麼聯繫起來的概念很模糊。在開題報告裡,寫了很多怎樣在ucOS 裡編程的問題,在當時的理解下,覺得寫了很"充分"的東西,想象著以後編程就是那個樣子的。但實踐以後知道那時的理解有些本末倒置了。

開題後已是四月上旬了,"嵌入式"、"操作系統"、"移植"、"DSP"這些東西一直在腦子了盤旋,看書上網查資料都要朝著這些目標。比較蠢的做法是,像我一樣只是努力的找書,企圖把這些底層的東西都理解了,甚至想把彙編的指令都記住。直到五月中旬的一天,覺得這樣埋頭讀書不能前進了,就準備動手。當時已經是非典隔離期了,大家都在拼命的運動發洩各種心情,沒有學習的氣氛。我也很怕,所以沒心情學習,但總覺得課題是我不能輕鬆完成的任務,不敢消沉下去。於是我拿過來板子、仿真器、電源這些很陌生的東西,試著把它們裝起來,接著就是裝軟件、仿真器驅動,因為有安裝步驟的說明,我很順利的完成了。但測試軟件時卻顯示沒有成功,仿真器不能用,安裝軟件的能力我還是比較自信的,但就是找不到問題,請有經驗的同學幫忙結果一樣。忙了兩天還沒搞定仿真器,嚴重的打擊了我本來就很迷茫的自信。正當我無所適從時,很幸運的突然發現了電腦CMOS設置裡有並口設置的選項,我發現了"EPP"模式,我當時就知道了這次成功了。這個開頭很難,但困難有多大,解決困難後就有多興奮,興奮之餘渾身充滿了前進的動力。

接下來就可以編程了,第一步要熟悉軟件編程環境,我的第一個疑問就是"Simulator"和"Emulator"的區別。我上網到清華的BBS上發現有很多人在討論DSP,我在別人的貼子中隱約知道了我用仿真器就是"Emulator"(Simulator是在軟件中模擬,開始我還想試試,但有仿真器,最終沒去理會)。論壇人氣很旺,很多問題我都不知所云,大開眼界,原來問題有這麼多!我的第一個程序是最簡單的加法。由於我之前還是努力的看了書,所以用到的簡單指令不用很費力就可以寫出來,但一個完整的程序不止這些,要知道cmd文件怎麼寫,知道它的作用(當時不能完全理解,按照大家一貫的寫法寫),還有中斷向量表、頭文件等。這些文件的作用開始是我不能完全理解的,不太明白為什麼那麼寫。大多書中只是稍微提一下,不能足以幫一個初學者建立一個很明確的概念和編程框架。因為程序很簡單,我仿照師姐留下的一個加法程序寫了出來。這個加法程序用了三天時間,其中大部分時間花費在一個小問題上:第一次寫程序太隨意,可能是寫高級語言程序的毛病,一個標號的第一個字母我沒有寫在第一列,而是隨意的打了個空格(當時沒有意識到後果),這個空格害我找了調試了一天時間!找出錯誤以後,我又總結了一下,哪些格式可以隨意寫,哪些要嚴格遵守,這樣再寫程序就不那麼不自在了。這樣簡單的"1+1=2",畢竟不能解決我的大部分疑問,只是稍微瞭解了編程環境,還有很多個疑問不能找到答案,所以就把CC'C2000的幫助文件很仔細的看了一遍,很費勁,當時覺得沒有很大收穫,但從此對幫助文件的內容有了大致的瞭解,在以後的編程過程中,很習慣的查看幫助。

完成了第一個程序,以後的就按照這個結構寫其他一些簡單的程序,逐漸的用到了寄存器、中斷等。這樣就熟悉了以前看書時想努力記住但沒有成功的一些指令和寄存器的配置,還逐步的有了一些調試經驗。逐漸的我不像以前那樣急切,很多問題是要細心的體會,試驗一次不能反映全部問題,經驗是在不斷更正錯誤的基礎上積累起來的。我的一個體會是:不要試圖在最初知道所有問題的答案,即使找到的答案也只是紙上談兵,還要在實踐中深化理解,大部分答案都在實踐中自然浮出水面。最初編程很死,不敢越雷池半步,這是因為很多東西不完全理解,只有按部就班的把它做出來,不敢加以隨意的變化,變了就不知道對不對。我把這些不理解的東西就當作學習的目標,帶著這些問題從練習中逐漸找出答案。這個過程中,我養成了一個習慣:因為問題很多,我隨時都有可能明白了一些,也可能又有了一個新的疑問,我就把這些想法隨時記下來,等待以後驗證或尋找答案。寫下來對我加深印象真的很有用。後來,如果有了什麼重要的經驗,等一個程序成功後,都會把它們總結出來,算是對自己的一種肯定,很有成就感。總結的多了,就瞭解了很多細節的東西,哪怕是以前看起來很簡單的指令,也有運用是很巧妙的地方。舉幾個例子:

1.對形如:y=a1*x1+a2*x2+a3*x3的多項式編程,240指令的裝載臨時寄存器的指令有LT、LTD、LTP、LTA、LTS,乘法指令有MPY、MPYS、MPYA、MPYU,這些指令中有很多可以同時執行幾步,如果能巧妙的結合利用,程序很簡潔、效率很高,但要很好的運用,不是很容易(這些是最能體現DSP特點的指令,還有塊移動指令,它們和流水線有關,所以效率很高)。自己寫程序不要求很高,但知道它們之間有區別即使不用、記不大清楚,看別人的程序也能充分的領會其中的巧妙。有一條指令BANZ,我的程序中最初肯定不會利用它,偶爾一次看到有人用,仔細的體會了它的用法,發現用在循環中真是個不錯的選擇。

2.有一次看一個程序,涉及到了定標問題,我幾乎是看著程序抄下來的試驗的。其中有幾條非常常見的指令MPY、MPYA、ADD、OR在編譯時提示有錯誤,程序中有這麼兩句:

MPY #7FFBHMPYA #0H

我看不出有問題,而且和書上是一模一樣的呀。我就查軟件中的幫助,發現原來書上用錯了!那個錯誤實在是非常容易犯的!對MPY #k指令,操作數為立即數時為只能是 "13-bit short immediate value"。對MPYA指令根本就沒有立即數尋址方式,只有直接或間接尋址方式。還有ADD和OR的用法都是類似的想當然地用,而不注意它地特殊之處。比較幸運的是,這種錯誤編譯器可以發現的,但有些隱含的錯誤它可能發現不了,自己又覺得不可能會錯,結果出來後錯誤很難排除。

3.最初面對240眾多的寄存器,初始化時總覺得多寫了沒有用到,不然就是少了一些配置,這些要和240內部結構結合起來記憶理解。開始是對CPU寄存器、系統配置寄存器、時鐘模塊寄存器熟悉,其次熟悉了定時器、比較模塊的寄存器配置,上手後就慢慢熟悉其他的,比較麻煩的就是EV模塊。大多寄存器只要在程序的最初配置一次,就可以不用管了,個別的比較特殊。如等待狀態發生寄存器WSGR在IO空間中,對它賦值就要用out指令而不是常用的splk。還有如 COMCON,要配置為PWM模式,為保證全比較單元的正確操作,需要對它連續兩次寫操作。還有時鐘控制寄存器CKCR0、CKCR1,編程時,必須先使 CKCR0的CLKMD1=0,禁止PLL,然後根據要求設置CKCR1設置其他位,最後使CLKMD1=1,允許PLL工作(如果使用PLL的話)。還有定時器的控制寄存器TxCON有時也需要寫兩次,第一次配置,第二次啟動。總之,對一些需要比較"特殊"的做法,如果注意總結會對整體有個清晰的把握。

二、 移植系統練習多了,就有點柳暗花明的感覺。於是躍躍欲試,開始試著做我的重要任務——移植uCOS II,看了紹貝貝的書,明白了我要做的是什麼,雖然這時還是霧裡看花,理解也很朦朧,但已經有了前所未有的自信。最簡單的方法是去www.ucos.com網站下載已經移植的代碼,當時我查的時候,對TI的DSP,只有C31和C54的移植代碼下載(不知現在有沒有更新)。我試著從從這兩個例程中學習我需要的東西。首先是要明白這麼多文件的組織關係,最初面對一大堆文件,根本不知它們是何種關係,為什麼存在?大多文獻裡的都是對幾個移植文件做了詳細的說明,而對怎樣組織的好像是不言而瑜的事情,對一個資深的程序員確實沒有必要教怎麼做,可是我沒有開發大程序的經驗,沒有清晰的把握,因為自己做的彙編程序都是十幾行的小程序,而且還是對cmd文件、向量表、頭文件這些不是程序"核心"的東西沒有深刻的認識,我對這些零散的文件研究了很久,才意識到我不止要改幾個函數那麼簡單,還要要寫一個有main.c的文件、一個 cmd文件、中斷向量表、一些必要的頭文件,還要象寫其他簡單的程序一樣做一個框架,操作系統當成普通的用戶程序一樣和這個框架結合起來,然後再寫程序和普通的不同的是我有這麼多別人都已經做了的東西,我要實現那樣的機制不用自己去寫,只需拿來用。

明白這些算是思想上的重大突破,不然連 DSP程序和操作系統的關係都不知道。這樣就動手寫移植部分代碼,代碼中的一些是參照一個例子,那個應該是240的移植,也只是移植部分的代碼,不是一個完整的代碼。最初我對ucos認識不深的時候,借鑑了那個程序中的一些做法,如任務切換函數是用軟件中斷INT31,當時對中斷的認識都很淺,不用說軟件中斷了。對於什麼是硬件中斷什麼是軟件中斷的問題也困擾我很長時間,曾經問過一個比我早入手的同學,在他的編程中也沒有用過軟件中斷,可以說沒有意識到這兩種中斷只是彙編中普通的中斷。至於為什麼用INT31就不知道了,現在知道了可以用任何一個軟中斷的。還有一個就是調用庫函數I$$SAVE和I$$ REST用於移植,最初我在傻傻的想,我是不是可以直接用這兩個函數?可是它們在什麼地方?我怎樣找到它們看看代碼?還有很多別的疑問,但我還是建立了一個.mak把那些覺得需要的文件加了進去並按照以前的做法把它們"歸位"。然後編譯,當然有很多很多錯誤,除了語法錯誤,當然有我不懂的錯誤。我儘可能的改了一些,但不能完全正確。問題是出在用C編程上,所以我還要熟悉用C編程的方法。

(一) 用C編程 最初用C寫沒有什麼可以參考的書,我還是從最簡單的加法開始,寫一個純粹的C程序,同學說可以用輸出語句輸出結果,我的即使運行正確也不能輸出,找了人幫忙看,還是於事無補。畢竟用C的人很少,於是我自己開始仔細的找原因。看生成的map文件找結果所在的地址,有結果而且正確,看交*列表,C語句對應的彙編語句,沒什麼錯誤,就是不明白為什麼有錯,和別人的不一樣。這當然影響我的自信心,覺得為什麼倒黴的總是我?抱怨當然不是辦法,還是要繼續找錯了。於是我拿起了放了很久都沒看的TI的C編譯器文檔,雖是全英文的,還是堅持了三天看完了,好像沒有找到答案,但更堅定了我的想法,錯誤是肯定有的,因為文檔上有printf函數,但好像不影響程序的運行結果,於是在以後的編程中只好暫時放棄用輸出語句(後來調試基本成功後換了板子就可以輸出了),這對調試當然很不方便,要在映射的地址中看結果。看文檔的好處是,更清楚的知道了用C和彙編編程的不同,如cmd文件、中斷向量表的寫法。因為要涉及到混合編程,就要對在C中和彙編中的函數、變量互相調用問題弄明白,這是個難點,那個文檔看了很多遍,有的問題還是不能完全明白,最終在老師的幫助下對它有了比較清晰的理解,理解後就用編程來驗證,結果是我們的理解是正確的(後來看到有些書上也有對此的討論,我甚至能判斷作者的理解是否完全正確)。下面是幾點總結:

cmd文件寫法可以參照CC‘C2000幫助文件中的例子,還可以查閱TI文檔spru024D的2.8.3。

寄存器映射地址頭文件,和彙編中的不同,要重新定義,定義方法如下:#define CKCR0 (volatile unsigned int *) 0x702B使用方法: *CKCR0=0x0041;

中斷向量表的第一條語句應該跳轉到_c_int0對於這點我最初不是很明白,因為我看到的程序都是以main開始的。後來逐漸明白了,_c_int0是程序真正開始的地方,只是這個開始不是開發者寫出來的,而是編譯器自動為我們做好的,你要配合它做的是就是在Build Option中對linker的C Initialization的選項選擇ROM Autoinitialization Model或RAM Autoinitialization Model,而不是彙編中的No Autoinitialization,開發者的程序要以main函數開始,初始化結束後會跳轉到main函數。在反彙編代碼中可以看到這些過程。兩種初始化的方式詳見上面文檔的同一節。

彙編代碼中要用到的C的變量或符號,都要在前面加"_ ",即C中的fun要用在彙編中寫為"_fun"。當然互調前要聲明為全局變量或外部函數。詳細的說明見spru024D的4.2.2。在C中要嵌入彙編的格式為:asm(" clrc INTM");這個地方要注意的是引號裡面第一個字符為空格或Tab鍵(還可以是別的記不大清了),不能直接寫指令。為什麼會有上面的一些規定,看看反彙編的代碼就很清楚了,編譯後編譯器會為C中的符號都加以下劃線,所以在彙編中用當然要寫成"一樣"的了,第二條規則也是和編譯以後的程序格式有關,可以在你的程序中故意不正確的寫,看看顯示的錯誤就明白了。

比較難的是C調用匯編函數,彙編函數的寫法。這時要在彙編函數的開始和結束加入一些語句。C中用三個寄存器管理堆棧和局部幀:AR1作為堆棧指針SP,AR0作為幀指針FP,AR2最為局部變量指針LVP。調用函數時當前指針必須為AR1,首先要在軟件堆棧中保存函數返回地址、FP,分配局部幀空間,空間的大小是局部變量的個數加1,如果被調函數中可能修改寄存AR6、AR7,也要保存(當編譯器優化時它們被用作保存寄存器變量)。函數實現過程中注意調用它的函數傳遞的參數的存放次序:從右到左按照堆棧增長的方向放置。函數退出時和進入函數時的操作相反。這個規則的原因也可以從生成的交*列表中找到答案,和上面的原因大同小異,C語句編譯後的彙編代碼可以看出在任何一個函數調用前都會有這樣的"保存 "工作,結束時做相應的恢復。詳見spru024D的4.2.2,4.2.4和4.3節。

用C時需要.lib庫函數,這個格式的不能用文本的形式看,在它的同一目錄下有rts.scr文件可以以文本形式打開。用一個命令可以提取某個庫函數可以對它查看或是修改。我知道這個過程,但沒有用過,所以不多說了。詳見spru024D的4.1.3。

(二)系統調試及總結 明白了以上的規則就可以大膽的用C編程了,確實要比彙編方便了很多。現在還回到我的移植程序中,弄懂上面的東西后又修改了一些錯誤,到了六月上旬,整個程序編譯通過了。我很興奮,終於有了進展。接下來就是調試,調試是比編寫程序痛苦的事,對那些隱藏的錯誤要能順利的找出來實在是困難。系統不能正常工作,首先懷疑的就是移植代碼部分,移植代碼是按照ucOS提供的步驟寫的,寫的時候由於借鑑了別的例子並沒有深刻理解,調試時就必須有個深刻地認識。

難點一:對任務堆棧初始初始化函數OSTaskStkInit()的作用的理解,方法是模擬TI公司的I$$SAVE庫函數對任務堆棧初始化,按照庫函數地保存順序開闢棧空間,得到堆棧指針。這個函數的編寫要充分理解"堆棧"的概念。芯片本身的堆棧只有 8 級,無法作為系統堆棧使用,這8級堆棧用來保存函數調用和中斷的返回地址。C 編譯器將寄存器AR0、AR1作為SP和FP管理系統堆棧和局部幀(上面有說明)。編譯器使保存在硬件堆棧裡的返回地址彈出保存在系統堆棧裡,並保存其他寄存器,即保存了任務運行的現場,這些工作都由I$$SAVE來做。有了任務堆棧初始化函數OSTaskStkInit(),系統在進行初始化時,這個函數將任務地址放在堆棧中,然後用中斷返回也就是I$$REST函數將寄存器和TOS初始化,將任務的起始地址彈回到TOS中,這樣就能從中斷的任務開始運行了。

難點二:時鐘節拍。對節拍地作用是在看了操作系統的內核代碼後有了深刻認識的,雖然移植是可能不太瞭解具體的代碼,但沒有操作系統的概念最好把這個比較易學的操作系統的代碼看看,有個結構和原理的認識,我大概花了一週仔細的看了書和代碼,這次和以前看不同,瞭解了代碼實現地細節,看完會更加頭腦清醒。最初地時鐘節拍中斷是用實時時鐘中斷RTI,看完代碼知道了應該不可以用它來實現節拍,因為系統時鐘地啟動是在初始化結束後第一個任務開始前啟動的,實時時鐘不能這樣控制,它在板子上電是就啟動了,所以我改用定時器1實現。時鐘節拍函數OSTickISR()的實現是定時器的硬件中斷,因為在我修改過程中不斷地出錯,這使我熟練掌握了F240的中斷編程方法。我對中斷編程做了總結,包括用C編寫中斷,對初學者應該有幫助:

在UCOS中的中斷編程和一般的中斷編程稍有不同。共同的是:

1.中斷矢量表。中斷矢量表一定要定位在程序空間的地址0開始的地方,0000h~003Fh為中斷矢量表。第0行跳轉到代碼開始的地方、第1到第6行是硬件中斷跳轉指令,除NMI中斷其他是軟件中斷指令(INTk最大為INT31)。發生硬件中斷後,處理器自動到前面查表跳轉到相應位置。軟件中斷是在程序中執行了INTk指令才會發生,然後根據x查中斷矢量表跳轉到相應的ISR(移植系統時用到了軟件中斷指令,即任務切換函數)。

2.硬件中斷系統。其中的可屏蔽中斷INTk(k=1~6)對應了多箇中斷源,有系統中斷和EV中斷,它們都通過INTk和CPU相關(將INTk稱為內核中斷)。寫系統中斷或EV中斷必須知道對應哪個內核中斷。因為每個內核中斷對應了幾個中斷源(具體對應查閱書籍)。有中斷源複用問題,當一個內核中斷中有多於一個的外部中斷髮生時,就要查外部中斷矢量表,它是根據中斷標識排列的。注意每條跳轉指令佔兩個字節。外部中斷矢量表的位置可以在程序空間的任何位置(表中有一個代表表的起始位置的符號)。對它的查表方式為基址+變址。基址為表的起始地址,變址為中斷源的標識×2。

3.中斷編程。本程序的 INT1有中斷複用,如果用匯編編寫,需要外部中斷矢量表。但在ucOS中編寫串口通訊時,發送任務沒有采用中斷方式,它要和接收任務通訊而有相應的動作(沒有操作系統時就是查詢方式)。即接收中斷髮生後要調用發送信號量函數(也可以使用別的通訊機制),在彙編中調用較麻煩,所以採用了C編程,這樣在 INT1的ISR中用了switch語句,就不用外部中斷矢量表。其他中斷源沒有複用問題。所以整個程序也不需要外部中斷矢量表。

在ucOS中的中斷編程要注意的是進入中斷調用庫函數I$$SAVE(C編寫自動調用),而程序結束是調用OSINTExit()(最後調用了I$$REST)。

看了操作系統的源代碼再加上對F240編程方法進一步掌握,感覺對這個系統的整體有很清晰的認識,很高興自己在無數的錯誤中摸索出來了。不幸的是我的程序仍然不能成功地運行,但我相信應該問題不太大,應該不是在關鍵處。因為系統可以單步運行且結果正確。我很幸運的在網上遇到了一位做過在2407移植的人,網上還有完整地源碼下載。我對比了程序,仍然不知錯在哪裡,然後我向他討教,他給了一個建議是,查看map文件,找到兩個常量表OSMapTbl和 OSUnMapTbl的映射地址,看看它們的值是否正確。我查看了一下沒錯,但偶然發現一個寄存器的映射地址是錯的,再看其他寄存器也是錯的,因為用C編程寄存器的映射頭文件要重新定義,我採用的是賦值語句,定義指針,程序中用指針尋址,這種方法應該是沒有問題的,但我知道有它的不方便的地方,不利於我的程序中在每個需要的文件中包含這個頭文件,於是就改成宏定義寄存器,這樣改過之後地址映射就正確了。我把程序從頭到尾的看了很多遍,包括反彙編的代碼都仔細去找錯,倒是有點小bug被找出來,但都不是致命的那個。在我身心俱憊時,我決定放鬆一下,這時是七月上旬,非典的陰影快要消失了,算算我也有三個多月沒出門了,這中間同學沒有聯繫,沒有看一部電影,沒有上網聊天,程序完全佔據了我的頭腦,現在隱約覺得看到了勝利的曙光,我要放鬆一下。放風了兩天,仍然心有不甘,就又著手我的程序。這次我把所有的寄存器配置從操作系統中拿開,在彙編中檢驗,終於找到了那個bug:SYSCR的配置錯誤,這個常用的寄存器不用看幫助就知道怎樣配置,但我寫錯了,可能是最初的筆誤,直接導致了系統的復位。系統終於可以運行了!從我的第一個程序到這時有兩個多月的時間,我學業生涯中最不平坦的兩個多月。

我換了一塊板子,以前不能顯示輸出語句結果的問題也解決了,於是我又寫了比較複雜的測試程序測試系統運行情況,雖然不是一次就可以寫成功的,但這次調試用了很短的時間。還有下面的總結,是C語言幾個關鍵字有關的內容,雖然較為基礎,在操作系統中可能用到的,對透徹把握程序很有幫助。

1.volatile類型限定符。用它修飾的對象叫易變對象,用於告訴編譯程序它所修飾的對象(可以是變量或常量)的值可能會以程序中未顯式指定的方式發生變化,即不是由程序中的賦值、初始化等顯式指定的方式發生變化。如,其變化可能式由中斷程序或IO端口所施加的。再如,在程序中可能會把某個全局變量的地址傳送給操作系統的時鐘並用於存放系統的實際時間,儘管程序中沒有對這個變量使用賦值語句賦值,但它的內容還是變了。舉個例子:

volatile int ticks;interrupt timer( ){ticks++;}wait (int time){ticks = 0;while (ticks < time ) ;}

如果ticks沒有聲明為易失變量,編譯程序可能會把它當作寄存器變量分配,從而wait函數執行永遠不會中止。上例中的interrupt 關鍵字是修飾中斷函數的,表示改函數與中斷相聯繫。

2.typedef 定義新的數據類型。例:

typedef unsigned int INT16U;

定義了類型INT16U,這樣做可以提高程序的可移植性。例如,有些C編譯系統沒有提供無符號短整數unsigned short int類型,這樣,在其他編譯系統整運行的使用了這種類型的編譯器就要把程序中出現的unsigned short int都換成另一種合適的類型如unsigned int,這樣改動比較大。使用類型定義可以解決這個問題,這時,在允許使用unsigned short int 的編譯系統中編寫程序時,先使用一個類型定義:

typedef unsigned short int USINT ;

以後在其他編譯系統運行時,只要把改類型定義改成:

typedef unsigned int USINT ;

其他地方不動就可以了。

還有一點就是可以避免因不同編譯系統實現上的差別而帶來的可移植性問題。例在某些編譯系統中char、short(有無符號)用8位表示,而在 TMS320C2xx的系統中,都用16位表示,為使在前者系統中運行的程序在C2xx中運行,必須把char、short改成int,這時,使用類型定義可以減少修改。

3.register存儲類區分符。用它修飾說明的變量叫寄存器變量,它的用途一是與auto一樣,使它所修飾的對象成為自動的,二是建議編譯程序在存儲分配使盡可能的把它分配到機器存儲器中,以便用時能快速存取。使用寄存器變量最常見的情況時把循環控制變量說明成寄存器變量,因為這個變量在每次循環時都要至少訪問一次,例:

register int I ;fun( ){for ( I =0 ;I <1000;I ++){ ...... }}

理論上,一個程序中可說明任意多個寄存器變量,實際上,由於寄存器數目的物理限制,編譯系統只把最前面的有限數目的寄存器變量分配在寄存器中,而把其餘的當作普通自動變量處理。在F240中,編譯系統允許把AR6、AR7用了存儲寄存器變量,使用優化選項是還允許使用AR5(見 spru024D4.2.4)。

以上是我學習中的一些總結,有很多是說自己的感受和心情的,可能不太值得一提,最初接觸硬件的人應該都有強烈的感受。但我相信有很多像我這樣從零開始的後來者,從不知到知,會遇到很多困難,我希望能給他們一點信心和勇氣,讓他們知道有我這樣一個人也經歷許多鬱悶的日子,最終解決了當時覺得解決不了的問題。如果看到我用了"幸運"這個詞,不要羨慕我的運氣,不要怪自己得問題為什麼不能"幸運"的解決(我就曾這樣痛苦的想過),因為可能你更幸運,根本就不會像我這麼"倒黴"遇到它。但總會有問題發生,如果解決了,你更幸運的獲得了一個寶貴的經驗、非常難得的財富。萬事開頭難,入了門腳下的路就就會逐步平坦了。

問題, 代碼, 函數, 程序, 寄存器,
相關問題答案