MAXQ微控制器架構的表操作
簡介
MAXQ 架構是一種基于標準Harvard結(jié)構、功能強大的單周期RISC微控制器。Harvard結(jié)構與常見的Von Neumann結(jié)構相比,其不同之處在于重要的設計結(jié)構方面:Harvard結(jié)構的指令與數(shù)據(jù)在不同的總線上傳輸。由于不存在單條數(shù)據(jù)總線的沖突問題,MAXQ指令的執(zhí)行時間僅需要單個周期。而傳統(tǒng)的Von
Neumann架構完成相同的操作則需要多個周期。
然而,Harvard結(jié)構中數(shù)據(jù)與代碼的嚴格分離也帶來了一系列的挑戰(zhàn)。Von Neumann結(jié)構的一項通用技術就是可以在代碼空間存儲數(shù)據(jù)表,這對于標準Harvard結(jié)構來說是很難實現(xiàn)的。在給定總線上,單個指令周期內(nèi)只能進行一個操作,因此在同一周期內(nèi),CPU核不可能既從代碼存儲器總線上取指令,又從代碼空間的數(shù)據(jù)表中取出存儲器操作數(shù)。
有人可能會認為采用Harvard結(jié)構的MAXQ微控制器也不能在代碼空間內(nèi)存儲數(shù)據(jù)。實際上,每一款MAXQ器件都內(nèi)嵌了ROM工具,因此很容易實現(xiàn)表項查找操作。
代碼空間的表查找
從代碼空間的MAXQ表中讀取一個數(shù)值看似簡單,然而對于不熟悉MAXQ架構的編程人員來說,第一次嘗試該操作時通常會失敗。
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é)構只有一個單獨的存儲空間,一條指令根據(jù)操作的需要可能花費多個指令周期,而MAXQ與此不同,其move
這個問題剛開始似乎很難解決。畢竟,訪問代碼空間需要一定的時間;CPU核不能將兩次存儲器訪問壓縮在一個時鐘周期內(nèi)完成,即使架構允許這樣。然而,如果我們了解了MAXQ架構的微控制器如何將物理存儲模塊映射到不同存儲空間的一些細節(jié)信息,并借助于固定用途ROM中的一些程序,就可以解決這一問題。
首先,在MAXQ架構中,將物理存儲模塊映射至代碼空間和數(shù)據(jù)空間的方式不是固定的,而是取決于正在訪問的物理存儲模塊。編程人員為大多數(shù)MAXQ微控制器所編寫的代碼都運行于閃存空間內(nèi),通常他們將其軟件連接到代碼空間的地址0處。編程人員會認為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ù)。當然,直接讀取數(shù)據(jù)只占用一個指令周期,而這一操作則占用了四個指令周期:2個周期用于長調(diào)用,1個周期用于讀取數(shù)據(jù),1個周期用于返回操作。
這個代碼例程存在的更大問題是不能進行匯編操作!標記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ù)在表中具有不同的相對偏移量。例如,MAXQ3120在第1個表查找程序之前,有3個與閃存編程有關的固定用途程序。
3個指針寄存器各自都有3個相關的函數(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 MAXQ3120 and 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程序的地址,即可將該地址存放起來并重復使用。上述前5條指令只需要執(zhí)行一次;然后,每次訪問表數(shù)據(jù)操作只需要三個指令周期:調(diào)用,讀取(運行固定用途ROM內(nèi)的程序),返回(也運行固定用途ROM內(nèi)的程序)。
將數(shù)據(jù)表從閃存拷貝到RAM
將整個表從閃存拷貝到RAM的方法之一是利用表的讀函數(shù)實現(xiàn)。例如,如果在BP中給出了目標地址,那么拷貝方法如下:
SlowTableMove:
move dp[0], #0800Dh ; Point to pointer to function table
move acc, @dp[0] ; acc now has pointer to ftable
add #4 ; For MAXQ3120 and 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架構的特殊性要求的。由于數(shù)據(jù)空間只有一條地址總線,因此必須在讀數(shù)據(jù)空間操作的前1個周期先將地址建立起來。在表操作循環(huán)中,對DP[0]給出的地址進行讀操作,然后在總線上放置寫地址。如果沒有move dp[0], dp[0]指令,當讀取表中下一個地址的數(shù)據(jù)時,寫地址會仍然占據(jù)總線。通過插入這條明顯的空指令,可以為預期的下一個讀操作刷新源操作數(shù)地址總線。
然而,還有一個更好的方法完成該拷貝任務。固定用途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 MAXQ3120 and 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 0cdefh
copyBuffer程序?qū)⒚看伪聿僮鞯闹芷跀?shù)減至3個,比之前提到的方法節(jié)省了約一半時間。當從copyBuffer程序返回時,LC[0]清零,OFFS寄存器指向最近一次寫目標地址的下一個位置。因為OFFS是一個8位寄存器,因此用這種方法可以拷貝多達256字的表。
實例:字符串輸出
在許多基于微控制器的應用中,通常都要將預存的消息輸出到控制臺。每條消息都指定了一個編號,必須由一個通用程序?qū)⒃摼幪栟D(zhuǎn)換成消息文本。
完成該任務通常采用每個消息字符串以0結(jié)尾的技術,同時提供一個表,以便將各消息編號轉(zhuǎn)換成消息字符串的首地址。這項技術非??煽亢涂焖?,但必須建立兩個數(shù)據(jù)結(jié)構:地址表及字符串本身。另一項技術是簡單地將以0結(jié)尾的各字符串存入一個大的、毗鄰的存儲器空間,并采用線性查找。雖然該方法比較簡單,但卻是以花費大量執(zhí)行時間為代價的,因為在輸出之前,必須找到目標字符串里的每一個字符。
還有一種較好的折衷辦法,即字符串采用按長度劃界的方法取代以0劃界的方法。采用這種技術,首先給出每個字符串的長度,然后緊接著是該消息的實際字節(jié)信息。這樣一來,可以快速跳過不用的信息,并且該表的長度沒有以0劃定界限的長。這種折衷技術的局限性僅在于表中的每個字符串長度不能超過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, were 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:
評論