簡介
MAXQ架構(gòu)是一種基于標(biāo)準(zhǔn)Harvard結(jié)構(gòu)、功能強大的單周期RISC微控制器。Harvard結(jié)構(gòu)與常見的Von Neumann結(jié)構(gòu)相比,其不同之處在于重要的設(shè)計結(jié)構(gòu)方面:Harvard結(jié)構(gòu)的指令與數(shù)據(jù)在不同的總線上傳輸。由于不存在單條數(shù)據(jù)總線的沖突問題,MAXQ指令的執(zhí)行時間僅需要單個周期。而傳統(tǒng)的Von Neumann架構(gòu)完成相同的操作則需要多個周期。然而,Harvard結(jié)構(gòu)中數(shù)據(jù)與代碼的嚴(yán)格分離也帶來了一系列的挑戰(zhàn)。Von Neumann結(jié)構(gòu)的一項通用技術(shù)就是可以在代碼空間存儲數(shù)據(jù)表,這對于標(biāo)準(zhǔn)Harvard結(jié)構(gòu)來說是很難實現(xiàn)的。在給定總線上,單個指令周期內(nèi)只能進(jìn)行一個操作,因此在同一周期內(nèi),CPU核不可能既從代碼存儲器總線上取指令,又從代碼空間的數(shù)據(jù)表中取出存儲器操作數(shù)。
有人可能會認(rèn)為采用Harvard結(jié)構(gòu)的MAXQ微控制器也不能在代碼空間內(nèi)存儲數(shù)據(jù)。實際上,每一款MAXQ器件都內(nèi)嵌了ROM工具,因此很容易實現(xiàn)表項查找操作。
代碼空間的表查找
從代碼空間的MAXQ表中讀取一個數(shù)值看似簡單,然而對于不熟悉MAXQ架構(gòu)的編程人員來說,第一次嘗試該操作時通常會失敗。IncorrectTableLookup: move dp[0], #w:StartOfTable move acc, @dp[0] . . . ret . . . StartOfTable: dc16 01234h dc16 05678h dc16 098abh dc16 0cdefh上述代碼能很順利地完成匯編,但是執(zhí)行完第二條指令之后,累加器的值幾乎不可能是0x1234。原因很簡單,Von Neumann結(jié)構(gòu)只有一個單獨的存儲空間,一條指令根據(jù)操作的需要可能花費多個指令周期,而MAXQ與此不同,其move
這個問題剛開始似乎很難解決。畢竟,訪問代碼空間需要一定的時間;CPU核不能將兩次存儲器訪問壓縮在一個時鐘周期內(nèi)完成,即使架構(gòu)允許這樣。然而,如果我們了解了MAXQ架構(gòu)的微控制器如何將物理存儲模塊映射到不同存儲空間的一些細(xì)節(jié)信息,并借助于固定用途ROM中的一些程序,就可以解決這一問題。
首先,在MAXQ架構(gòu)中,將物理存儲模塊映射至代碼空間和數(shù)據(jù)空間的方式不是固定的,而是取決于正在訪問的物理存儲模塊。編程人員為大多數(shù)MAXQ微控制器所編寫的代碼都運行于閃存空間內(nèi),通常他們將其軟件連接到代碼空間的地址0處。編程人員會認(rèn)為RAM也是從數(shù)據(jù)空間的地址0開始的,事實也的確如此。
但是,MAXQ微控制器還有另一塊物理存儲器,即固定用途ROM。所有MAXQ微控制器的固定用途ROM都位于代碼空間的地址0x8000。用戶代碼可以調(diào)用固定用途ROM中0x8000頁面的程序,執(zhí)行特定的函數(shù)。并且,只要執(zhí)行固定用途ROM中的程序,用戶代碼存儲器即被重新映射到數(shù)據(jù)空間的一個新地址上。
開始執(zhí)行固定用途ROM的程序后,可以繼續(xù)訪問數(shù)據(jù)空間以地址0x0000開始的數(shù)據(jù)RAM,而代碼存儲器卻被重新映射到數(shù)據(jù)空間以地址0x8000開始的位置。因為代碼閃存映射到了數(shù)據(jù)空間,運行的固定用途ROM代碼可以像訪問數(shù)據(jù)一樣訪問存儲于用戶代碼中的數(shù)據(jù)信息。通過固定用途ROM函數(shù),可簡單地通過指針寄存器間接讀取數(shù)據(jù)并返回結(jié)果。
因此,將上面給出的程序稍作改動后得到:
BetterTableLookup: move dp[0], #w:StartOfTable + 08000h call UtilityROMGetDP0 . . . ret . . . StartOfTable: dc16 01234h dc16 05678h dc16 098abh dc16 0cdefh在本例程中,調(diào)整待讀取的地址,以反映執(zhí)行固定用途ROM程序時閃存的映射地址,然后將其裝入DP[0]。這里采用了調(diào)用固定用途ROM程序的方法,而不是直接讀取數(shù)據(jù)。當(dāng)然,直接讀取數(shù)據(jù)只占用一個指令周期,而這一操作則占用了四個指令周期:2個周期用于長調(diào)用,1個周期用于讀取數(shù)據(jù),1個周期用于返回操作。
這個代碼例程存在的更大問題是不能進(jìn)行匯編操作!標(biāo)記UtilityROMGetDP0沒有定義,造成這一結(jié)果的原因很簡單:各款MAXQ微控制器的實用程序地址是不同的。事實上,甚至不能保證這些程序在特定MAXQ器件的不同版本中位于相同的位置!
為解決這一問題,每一款MAXQ微控制器的固定用途ROM都包含一個固定用途函數(shù)的地址表,以及一個指向該表的指針,該指針的地址固定為0x800D。需明確指出的是,固定用途ROM包含以下代碼:
org 0800Dh dw UtilityFunctionTable . . . UtilityFunctionTable: . . . dw GetDP0 dw GetDP0Inc dw GetDP0Dec dw GetDP1 dw GetDP1Inc dw GetDP1Dec dw GetBP dw GetBPInc dw GetBPDec注意:第一,固定用途函數(shù)表由地址組成,而不是指令。因此,編程人員必須提取地址并call它,而不能簡單地跳轉(zhuǎn)至該表。第二,第一個存儲器函數(shù)也許不是該表的入口。由于每款MAXQ微控制器包含不同類型和容量的存儲器以及不同的外設(shè),每款器件很有可能包含不同的函數(shù)列表,函數(shù)在表中具有不同的相對偏移量。
3個指針寄存器各自都有3個相關(guān)的函數(shù),總計有9個表查找函數(shù)。每個指針寄存器的第一個函數(shù)只是提取位于給定地址的數(shù)據(jù),而后兩個函數(shù)分別采用后遞增和后遞減形式的間接裝載。在每一種情況下,都將提取到的數(shù)據(jù)裝載到GR寄存器中。
現(xiàn)在,代碼可改為如下形式:
CorrectTableLookup: move dp[0], #0800Dh ; Point to pointer to function table move acc, @dp[0] ; acc now has pointer to ftable add #3 ; For 2000, GetDP0 move dp[0], acc ; Load ptr + offset to dp0 move a[1], @dp[0] ; Get address of GetDP0 into A1 move dp[0], #StartOfTable + 08000h call a[1] ; This will call GetDP0, finally! . . . ret . . . StartOfTable: dc16 01234h dc16 05678h dc16 098abh dc16 0cdefh需注意,一旦找到GetDP0程序的地址,即可將該地址存放起來并重復(fù)使用。上述前5條指令只需要執(zhí)行一次;然后,每次訪問表數(shù)據(jù)操作只需要三個指令周期:調(diào)用,讀取(運行固定用途ROM內(nèi)的程序),返回(也運行固定用途ROM內(nèi)的程序)。
將數(shù)據(jù)表從閃存拷貝到RAM
將整個表從閃存拷貝到RAM的方法之一是利用表的讀函數(shù)實現(xiàn)。例如,如果在BP中給出了目標(biāo)地址,那么拷貝方法如下:SlowTableMove: move dp[0], #0800Dh ; Point to pointer to function table move acc, @dp[0] ; acc now has pointer to ftable add #4 ; For 2000, GetDP0Inc move dp[0], acc ; Load ptr + offset to dp0 move a[1], @dp[0] ; Get address of GetDP0 into A1 move dp[0], #StartOfTable + 08000h move bp, #RAMDest ; Set this label to desired dest move offs, #0ffh ; Pre-decremented offset move lc[0], #4 ; Move four words TableMoveLoop: move dp[0], dp[0] ; Set source pointer call a[1] ; This will call GetDP0inc move @bp[++offs], gr ; Store retrieved word to dest djnz lc[0], TableMoveLoop . . . ret . . . StartOfTable: dc16 01234h dc16 05678h dc16 098abh dc16 0cdefh如上文所述,前5條指令只需要執(zhí)行一次,此后可根據(jù)需要多次執(zhí)行表操作,GetDP0inc子程序地址始終保存在A1中。每次執(zhí)行表操作需要6個指令周期外加建立開銷。
加入move dp[0], dp[0]指令是MAXQ架構(gòu)的特殊性要求的。由于數(shù)據(jù)空間只有一條地址總線,因此必須在讀數(shù)據(jù)空間操作的前1個周期先將地址建立起來。在表操作循環(huán)中,對DP[0]給出的地址進(jìn)行讀操作,然后在總線上放置寫地址。如果沒有move dp[0], dp[0]指令,當(dāng)讀取表中下一個地址的數(shù)據(jù)時,寫地址會仍然占據(jù)總線。通過插入這條明顯的空指令,可以為預(yù)期的下一個讀操作刷新源操作數(shù)地址總線。
然而,還有一個更好的方法完成該拷貝任務(wù)。固定用途ROM中包括一個能實現(xiàn)上述相同功能的copyBuffer程序,而且所占用的指令周期更少。copyBuffer程序在固定用途ROM中位于表查找程序的后面。
FasterTableMove: move dp[0], #0800Dh ; Point to pointer to function table move acc, @dp[0] ; acc now has pointer to ftable add #12 ; For 2000, copyBuffer move dp[0], acc ; Load ptr + offset to dp0 move a[1], @dp[0] ; Get address of GetDP0 into A1 move dp[0], #StartOfTable + 08000h move bp, #RAMDest ; Set this label to desired dest move offs, #0 ; No need to pre-decrement offset move lc[0], #4 ; Move four words call a[1] ; This will call copyBuffer . . . ret . . . StartOfTable: dc16 01234h dc16 05678h dc16 098abh dc16 0cdefhcopyBuffer程序?qū)⒚看伪聿僮鞯闹芷跀?shù)減至3個,比之前提到的方法節(jié)省了約一半時間。當(dāng)從copyBuffer程序返回時,LC[0]清零,OFFS寄存器指向最近一次寫目標(biāo)地址的下一個位置。因為OFFS是一個8位寄存器,因此用這種方法可以拷貝多達(dá)256字的表。
實例:字符串輸出
在許多基于微控制器的應(yīng)用中,通常都要將預(yù)存的消息輸出到控制臺。每條消息都指定了一個編號,必須由一個通用程序?qū)⒃摼幪栟D(zhuǎn)換成消息文本。完成該任務(wù)通常采用每個消息字符串以0結(jié)尾的技術(shù),同時提供一個表,以便將各消息編號轉(zhuǎn)換成消息字符串的首地址。這項技術(shù)非常可靠和快速,但必須建立兩個數(shù)據(jù)結(jié)構(gòu):地址表及字符串本身。另一項技術(shù)是簡單地將以0結(jié)尾的各字符串存入一個大的、毗鄰的存儲器空間,并采用線性查找。雖然該方法比較簡單,但卻是以花費大量執(zhí)行時間為代價的,因為在輸出之前,必須找到目標(biāo)字符串里的每一個字符。
還有一種較好的折衷辦法,即字符串采用按長度劃界的方法取代以0劃界的方法。采用這種技術(shù),首先給出每個字符串的長度,然后緊接著是該消息的實際字節(jié)信息。這樣一來,可以快速跳過不用的信息,并且該表的長度沒有以0劃定界限的長。這種折衷技術(shù)的局限性僅在于表中的每個字符串長度不能超過255個字符。
; ; Output String ; ; Enter with ACC=an index value (one based) indicating which ; string to output. ; ; On exit, LC0=0, DPC=0, ACC, A1, A2, DP0 used. ; output_string: move lc[0], acc ;Set LC0 to index of string move dpc, #4 ;Set DP0 to word mode move dp[0], #800dh ;Point to table of pointers move acc, @dp[0] ;Get address of table add #3 ;Offset to GETDP0 routine move dp[0], acc ;Load pointer to table move a[1], @dp[0]++ ;Get GETDP0 move a[2], @dp[0] ;Get GETDP0INC move dpc, #0 ;Set DP0 to byte mode move dp[0], #string_table + 8000h str_search_loop: call a[1] ;Get a string length djnz lc[0], next_str ;If not this string, go to next move lc[0], gr ;Otherwise, put len in LC0 move acc, @dp[0]++ ;...and point past length out_loop: call a[2] ;Get a char and bump pointer call char_out ;Output the character djnz lc[0], out_loop ;If more characters, loop ret ;Otherwise, we're done. next_str: move acc, gr ;GR contains len of this string add dp[0] ;Add current ptr to current len... move dp[0], acc ;...to create a new pointer jump str_search_loop ;Jump back and test index again ; ; Each entry in the string table begins with the string length ; followed by the string characters. ; string_table: dc8 string1 - string_table dc8 "This is the first string." string1: dc8 string2 - string1 dc8 "This is a second example of a string" string2: dc8 string3 - string2 dc8 "A third string." string3: dc8 string4 - string3 dc8 "Finally, a fourth string in the array!!!" string4:
評論
查看更多