MAXQ微控制器架構(gòu)的表操作
簡介
MAXQ 架構(gòu)是一種基于標(biāo)準(zhǔn)Harvard結(jié)構(gòu)、功能強(qiáng)大的單周期RISC微控制器。Harvard結(jié)構(gòu)與常見的Von Neumann結(jié)構(gòu)相比,其不同之處在于重要的設(shè)計(jì)結(jié)構(gòu)方面:Harvard結(jié)構(gòu)的指令與數(shù)據(jù)在不同的總線上傳輸。由于不存在單條數(shù)據(jù)總線的沖突問題,MAXQ指令的執(zhí)行時(shí)間僅需要單個(gè)周期。而傳統(tǒng)的Von
Neumann架構(gòu)完成相同的操作則需要多個(gè)周期。
然而,Harvard結(jié)構(gòu)中數(shù)據(jù)與代碼的嚴(yán)格分離也帶來了一系列的挑戰(zhàn)。Von Neumann結(jié)構(gòu)的一項(xiàng)通用技術(shù)就是可以在代碼空間存儲(chǔ)數(shù)據(jù)表,這對于標(biāo)準(zhǔn)Harvard結(jié)構(gòu)來說是很難實(shí)現(xiàn)的。在給定總線上,單個(gè)指令周期內(nèi)只能進(jìn)行一個(gè)操作,因此在同一周期內(nèi),CPU核不可能既從代碼存儲(chǔ)器總線上取指令,又從代碼空間的數(shù)據(jù)表中取出存儲(chǔ)器操作數(shù)。
有人可能會(huì)認(rèn)為采用Harvard結(jié)構(gòu)的MAXQ微控制器也不能在代碼空間內(nèi)存儲(chǔ)數(shù)據(jù)。實(shí)際上,每一款MAXQ器件都內(nèi)嵌了ROM工具,因此很容易實(shí)現(xiàn)表項(xiàng)查找操作。
代碼空間的表查找
從代碼空間的MAXQ表中讀取一個(gè)數(shù)值看似簡單,然而對于不熟悉MAXQ架構(gòu)的編程人員來說,第一次嘗試該操作時(shí)通常會(huì)失敗。
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)只有一個(gè)單獨(dú)的存儲(chǔ)空間,一條指令根據(jù)操作的需要可能花費(fèi)多個(gè)指令周期,而MAXQ與此不同,其move
這個(gè)問題剛開始似乎很難解決。畢竟,訪問代碼空間需要一定的時(shí)間;CPU核不能將兩次存儲(chǔ)器訪問壓縮在一個(gè)時(shí)鐘周期內(nèi)完成,即使架構(gòu)允許這樣。然而,如果我們了解了MAXQ架構(gòu)的微控制器如何將物理存儲(chǔ)模塊映射到不同存儲(chǔ)空間的一些細(xì)節(jié)信息,并借助于固定用途ROM中的一些程序,就可以解決這一問題。
首先,在MAXQ架構(gòu)中,將物理存儲(chǔ)模塊映射至代碼空間和數(shù)據(jù)空間的方式不是固定的,而是取決于正在訪問的物理存儲(chǔ)模塊。編程人員為大多數(shù)MAXQ微控制器所編寫的代碼都運(yùn)行于閃存空間內(nèi),通常他們將其軟件連接到代碼空間的地址0處。編程人員會(huì)認(rèn)為RAM也是從數(shù)據(jù)空間的地址0開始的,事實(shí)也的確如此。
但是,MAXQ微控制器還有另一塊物理存儲(chǔ)器,即固定用途ROM。所有MAXQ微控制器的固定用途ROM都位于代碼空間的地址0x8000。用戶代碼可以調(diào)用固定用途ROM中0x8000頁面的程序,執(zhí)行特定的函數(shù)。并且,只要執(zhí)行固定用途ROM中的程序,用戶代碼存儲(chǔ)器即被重新映射到數(shù)據(jù)空間的一個(gè)新地址上。
開始執(zhí)行固定用途ROM的程序后,可以繼續(xù)訪問數(shù)據(jù)空間以地址0x0000開始的數(shù)據(jù)RAM,而代碼存儲(chǔ)器卻被重新映射到數(shù)據(jù)空間以地址0x8000開始的位置。因?yàn)榇a閃存映射到了數(shù)據(jù)空間,運(yùn)行的固定用途ROM代碼可以像訪問數(shù)據(jù)一樣訪問存儲(chǔ)于用戶代碼中的數(shù)據(jù)信息。通過固定用途ROM函數(shù),可簡單地通過指針寄存器間接讀取數(shù)據(jù)并返回結(jié)果。 因此,將上面給出的程序稍作改動(dòng)后得到:
BetterTableLookup:
move dp[0], #w:StartOfTable + 08000h
call UtilityROMGetDP0
.
.
.
ret
.
.
.
StartOfTable:
dc16 01234h
dc16 05678h
dc16 098abh
dc16 0cdefh
在本例程中,調(diào)整待讀取的地址,以反映執(zhí)行固定用途ROM程序時(shí)閃存的映射地址,然后將其裝入DP[0]。這里采用了調(diào)用固定用途ROM程序的方法,而不是直接讀取數(shù)據(jù)。當(dāng)然,直接讀取數(shù)據(jù)只占用一個(gè)指令周期,而這一操作則占用了四個(gè)指令周期:2個(gè)周期用于長調(diào)用,1個(gè)周期用于讀取數(shù)據(jù),1個(gè)周期用于返回操作。
這個(gè)代碼例程存在的更大問題是不能進(jìn)行匯編操作!標(biāo)記UtilityROMGetDP0沒有定義,造成這一結(jié)果的原因很簡單:各款MAXQ微控制器的實(shí)用程序地址是不同的。事實(shí)上,甚至不能保證這些程序在特定MAXQ器件的不同版本中位于相同的位置!
為解決這一問題,每一款MAXQ微控制器的固定用途ROM都包含一個(gè)固定用途函數(shù)的地址表,以及一個(gè)指向該表的指針,該指針的地址固定為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)至該表。第二,第一個(gè)存儲(chǔ)器函數(shù)也許不是該表的入口。由于每款MAXQ微控制器包含不同類型和容量的存儲(chǔ)器以及不同的外設(shè),每款器件很有可能包含不同的函數(shù)列表,函數(shù)在表中具有不同的相對偏移量。例如,MAXQ3120在第1個(gè)表查找程序之前,有3個(gè)與閃存編程有關(guān)的固定用途程序。
3個(gè)指針寄存器各自都有3個(gè)相關(guān)的函數(shù),總計(jì)有9個(gè)表查找函數(shù)。每個(gè)指針寄存器的第一個(gè)函數(shù)只是提取位于給定地址的數(shù)據(jù),而后兩個(gè)函數(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程序的地址,即可將該地址存放起來并重復(fù)使用。上述前5條指令只需要執(zhí)行一次;然后,每次訪問表數(shù)據(jù)操作只需要三個(gè)指令周期:調(diào)用,讀取(運(yùn)行固定用途ROM內(nèi)的程序),返回(也運(yùn)行固定用途ROM內(nèi)的程序)。
將數(shù)據(jù)表從閃存拷貝到RAM
將整個(gè)表從閃存拷貝到RAM的方法之一是利用表的讀函數(shù)實(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 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個(gè)指令周期外加建立開銷。
加入move dp[0], dp[0]指令是MAXQ架構(gòu)的特殊性要求的。由于數(shù)據(jù)空間只有一條地址總線,因此必須在讀數(shù)據(jù)空間操作的前1個(gè)周期先將地址建立起來。在表操作循環(huán)中,對DP[0]給出的地址進(jìn)行讀操作,然后在總線上放置寫地址。如果沒有move dp[0], dp[0]指令,當(dāng)讀取表中下一個(gè)地址的數(shù)據(jù)時(shí),寫地址會(huì)仍然占據(jù)總線。通過插入這條明顯的空指令,可以為預(yù)期的下一個(gè)讀操作刷新源操作數(shù)地址總線。
然而,還有一個(gè)更好的方法完成該拷貝任務(wù)。固定用途ROM中包括一個(gè)能實(shí)現(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個(gè),比之前提到的方法節(jié)省了約一半時(shí)間。當(dāng)從copyBuffer程序返回時(shí),LC[0]清零,OFFS寄存器指向最近一次寫目標(biāo)地址的下一個(gè)位置。因?yàn)镺FFS是一個(gè)8位寄存器,因此用這種方法可以拷貝多達(dá)256字的表。
實(shí)例:字符串輸出
在許多基于微控制器的應(yīng)用中,通常都要將預(yù)存的消息輸出到控制臺(tái)。每條消息都指定了一個(gè)編號,必須由一個(gè)通用程序?qū)⒃摼幪栟D(zhuǎn)換成消息文本。
完成該任務(wù)通常采用每個(gè)消息字符串以0結(jié)尾的技術(shù),同時(shí)提供一個(gè)表,以便將各消息編號轉(zhuǎn)換成消息字符串的首地址。這項(xiàng)技術(shù)非??煽亢涂焖?,但必須建立兩個(gè)數(shù)據(jù)結(jié)構(gòu):地址表及字符串本身。另一項(xiàng)技術(shù)是簡單地將以0結(jié)尾的各字符串存入一個(gè)大的、毗鄰的存儲(chǔ)器空間,并采用線性查找。雖然該方法比較簡單,但卻是以花費(fèi)大量執(zhí)行時(shí)間為代價(jià)的,因?yàn)樵谳敵鲋埃仨氄业侥繕?biāo)字符串里的每一個(gè)字符。
還有一種較好的折衷辦法,即字符串采用按長度劃界的方法取代以0劃界的方法。采用這種技術(shù),首先給出每個(gè)字符串的長度,然后緊接著是該消息的實(shí)際字節(jié)信息。這樣一來,可以快速跳過不用的信息,并且該表的長度沒有以0劃定界限的長。這種折衷技術(shù)的局限性僅在于表中的每個(gè)字符串長度不能超過255個(gè)字符。
;
; 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:
評論