服務熱線
0755-88364040
18902464556
EMC單片機編程技巧集錦(轉換)
專業PCB打樣,快速交貨,臺灣品質
EM78XXX單芯片自從問世以來已經陸續推出十余種不同等級的單芯片,小到8Pin的78P152,大到100Pin OTP的78P860,其匯編語言指令都是一樣的,僅有57個,所以反復練習幾次就能熟悉指令的用法。匯編語言用在I/O控制非常容易,也有很高的效率,所以坊間的書籍大部份以討論控制為主顯,顯少專門探討軟件技巧的篇幅,其實老手都知道,關于芯片之控制往往用到時再去翻一翻DATA
BOOK,注意一下TIMING,然后準備一部示波器,三兩下就可以搞定。反倒是算法用的好不好會大大影響產品的穩定度,所以有經驗的程序設計師通常都有自己的一套葵花秘笈,所以要提升自己的功力最好的方式除了多練習之外,看看別人的程序也會使你進步很快。
BCD轉換成Binary
由于EM78XXX是8位的微控器,因此為了節省內存,我們的范例僅以一個BYTE存放兩位BCD數為例,數字的范圍在0~99之間,轉換后的結果放在ACC,如果您需要更多的位數,相信您在看完之后應該不難自行修改才是。
程序一
這個范例程序共花費13個指令CYCLE,需要兩個變量空間,執行后會影響到原BCD的內容。
MOV A,BCD
MOV TMP,A
MOV A,@0x0F
AND TMP,A
SWAP BCD
AND BCD,A
BC PSW,0
RLC BCD ; *2
MOV A,BCD
ADD TMP,A
RLC BCD
RLCA BCD ; *8
ADD A,TMP
說明
在程序一中所采用的方式應該算是最多人知道的方式,也是一種最直覺的方法,先將BCD個位數保存起來,因為十位數必須要乘以10,所以利用移位的技巧乘以10再加上個位數,所得的答案放入ACC。
程序二
在程序一的缺點,就是在執行程序以后,原本BCD的內容已經在移位的過程中被破壞掉了,為了改善這項缺失,我們換一種方式看看。下面這個程序,我們企圖改善前面的缺失,共花費11個指令CYCLE,仍需要兩個變量空間,但是執行后不會破壞原來BCD的內容。
SWAPA BCD
MOV TMP,A
MOV A,@0x0F
AND BCD,A
AND TMP,A
BC PSW,0
RLCA TMP
SWAP TMP
RRC TMP
ADD A,TMP
ADD A,BCD
程序三
對于程序二的結果我們仍然不滿意,似乎稍嫌復雜,雖然速度有所改善,但在內存的分配上仍有余地,所以我們再改善成程序三的型態。轉換過程只花費10個指令CYCLE,而且只需要一個變量空間,執行之后也不會改變原來BCD的內容。
MOV A,@0x0f
AND A,BCD
JBC BCD,4
ADD A,@10
JBC BCD,5
ADD A,@20
JBC BCD,6
ADD A,@40
JBC BCD,7
ADD A,@80
說明
看過以上三個范例,您是否覺得程序三最簡潔而且容易了解?寫程序的確是一項極具挑戰性的工作,而且還可以找到很多靈感及樂趣,想不到吧!
Binary轉換成BCD碼
下面的范例程序會將存放在ACC內的二進制數轉換成兩位BCD碼(Compacted BCD Code),可轉換最大的BCD碼是99。
CLR BCD
DIGIT_HI:
ADD A,@256-10
JBS PSW,FC
JMP DIGIT_LO
INC BCD
JMP DIGIT_HI
DIGIT_LO:
ADD A,@10
SWAP BCD
OR BCD,A
減法的陷阱
EM78系列匯編語言的減法指令是SUB,使用這個指令時您得特別注意,因為ACC永遠都是減數,不可為被減數。SUB指令的語法有以下三種:
SUB A,R (R-A→A)
SUB R,A (R-A→R)
SUB A,K (K-A→A)
也就是說如果我們想計算A-2的值,如果寫成:
SUB A,@2
其實是執行2-A,解決方法如下:
ADD A,@256-2 或
ADD A,@254
交換兩組緩存器的內容
如果你覺得要交換兩組內存的內容一定要借用第三組變量,那么您可以參考以下的方式,只是用了一些數學技巧就變得又快又簡單。
MOV A,REG1
SUB A,REG2
ADD REG1,A
SUB REG2,A
原理說明
A=REG1
A=REG2-REG1
REG1=REG1+A
=REG1+(REG2-REG1)
=REG2
REG2=REG2-(REG2-REG1)
=REG1
若X>Y就交換...
延續上一個例子,此法用應用在Bubble Sort特別管用。
MOV A,X
SUB A,Y
JBC PSW,FC
JMP NO_CHANGE
ADD X,A
SUB Y,A
2補碼
2補碼加法經常代替減法,傳統上的做法是先取1補碼,然后加1。
COM REG
INC REG
或是可以利用另一種方式求得,所不同的是第二種方式會影響PSW緩存器。
ADD A,REG
SUB A,REG
如果您所要求的數已經放在ACC里面,那只要一行就能解決了。
SUB A,@0
旋轉字節運算
在8051指令中位左旋有RLC與RL兩種指令區分,RLC在ACC左旋時會連帶將CY一并旋轉,而RL只會將ACC的MSB旋入LSB。EM78XXX指令只有RLC,那么要如何才能做到不帶CY旋轉呢?答案是旋轉兩次:
RLCA REG1
RLC REG1
如圖1所示,第一次位旋轉并沒有真正改變REG1的內容,目的是將REG1的MSB先放入FC,第二次位旋轉才將剛剛放在FC內的MSB旋入LSB。同理,兩個BYTES不經FC的位旋轉也是相同的原理。
RLCA HI_BYTE
RLC LO_BYTE
RLC HI_BYTE
范圍判斷
寫程序免不了會碰到IF..THEN..的場合,有些人覺得EM78XXX的條件判斷式太過繁瑣,所以筆者也將它們整理歸納一下。條件判斷式可分為開放區間條件式與封閉區間條件式來討論,以圖2來表示。
開放條件式是以N點為出發點,當待測值大于N或是小于等于N時的條件判斷,以C的語法描述如下:
if(number>n)
... /* number大于N */
else
... /* number小于等于N */
EM78XXX匯編語言寫法如下:
MOV A,@N+1
SUB A,Number
JBC PSW,FC
JMP LABEL_1 ; 大于N
JMP LABEL_2 ; 小于等于N
封閉式條件判斷是指待測值N是否在X與Y的范圍之內,若以C的語法描述:
if((number>=x) && (number<=y))
.... /* in range */
else
.... /* False */
如何以EM78匯編語言做到呢?一般做法是以減法后的PSW做條件判斷,程序如下:
MOV A,@2
SUB A,number
JBS PSW,FC
JMP FALSE
MOV A,@y+1
SUB A,SI
JBC PSW,FC
JMP FALSE
IN_RANGE:
; ....
FALSE:
; ....
這個IF條件式要花費8個指令Cycle,還不算太復雜。但是還有個更簡潔的方法,以下用加法后的PSW(R3)做條件判斷,一共只要5行就清潔溜溜了。
MOV A,Number
ADD A,@255-y
ADD A,@y-x+1
JBC PSW,FC
JMP IN_RANGE
FALSE:
; ....
IN_RANGE:
; ....
說明
關鍵就在前三行,x表示條件式的下限值,y表示條件式的上限值,可以看得出仍是利用CY旗標制造的特效,不但精簡而且有點小聰明,許多老手都愛用,這也是他們口袋里的秘密武器之一。如果您覺得不錯,不妨也收入錦囊中,爾后就可以依樣畫葫蘆了。
ACC與緩存器內容交換
這理我們要介紹一種快速的邏輯算法,只需要3個指令CYCLE,就可以將ACC的內容與緩存器的內容交換,不拖泥帶水,Very cute!
XOR Number,A
XOR A,Number
XOR Number,A
請讀者自行在紙上推算一次,就知道答案了。
交換多組緩存器內容
利用上面介紹的方法,可以推廣到多組緩存器交換的例子上,下面的程序將5組DATA內容移位,第一筆緩存器的數據傳到第二筆緩存器內,第二緩存器的數據再傳送到第三筆緩存器內,依此類推,最后一筆數據則傳給第一個緩存器,形成一種字節數據旋轉。
MOV A,@5
MOV COUNT,A
MOV A,@DATA1
MOV RSR,A
MOV A,DATA5
NEXT:
XOR INDIR,A
XOR A,INDIR
XOR INDIR,A
INC RSR
DJZ COUNT
JMP NEXT
計算MOD 2N
假如你剛好需要計算ACC MOD X,且X剛好是2的N次方,使用ACC AND
(X-1)是最快的方法了。例如要判斷YEAR是否為閏年,有個簡單的方法,可以排除一些非閏年的條件,只要不能被4整除者就不是閏年。所以可以用YEAR
AND 3解決。
MOV A,@4-1
AND A,YEAR
JBS PSW,FZ
JMP NOLEAP
清除一段連續的內存
對于連續一段內存做讀寫最好的方式就是使用間接尋址法,但是要注意在一些如M78447/811/860等高階MCU,內存20H~
3FH又可以分成4組BANK,如果之前沒有切換到正確的BANK會造成讀寫錯誤。下面的范例程序會將BANK1內的32個BYTES全部清為0。
INDIR == 0x00
RSR == 0x04
COUNT == 0x10
REG == 0x20
BANK1 == 0x40
BANK2 == 0x80
BANK3 == 0xC0
MOV A,@32
MOV COUNT,A
MOV A,@REG|BANK1
MOV RSR,A
NEXT:
CLR INDIR
INC RSR
DJZ COUNT
JMP NEXT
計算一個BYTE中有多少個"1"
這個小程序可以檢查出在某個BYTE中共有幾個1,在某些算法的過程可能會用得到,計算的結果放在ACC。
RRCA DATA
AND A,@0x55
SUB DATA,A
MOV A,DATA
AND A,@0x33
ADD DATA,A
RRC DATA
ADD DATA,A
RRC DATA
SWAPA DATA
ADD A,DATA
AND A,@0x0F
節省NOP指令的方法
您還在為程序擠不下傷腦筋嗎?NOP指令有時候在延遲指令時間很有用,假如你有連續兩個NOP指令可以用JMP到下一個指令的方式代替,因為這樣可以減少一個指令BYTE,又可以達到相同的效果。
例如:
NOP
NOP
可以寫成:
JMP NEXT_INST
NEXT_INST:
;....
因為一個NOP花費一個指令Cycle,但是一個JMP指令就需要2個指令Cycle,雖然有時候會抱怨JMP指令會多花一點時間,但是想不到它也有如此妙用吧。
LABEL太多?
寫匯編語言最令人傷腦筋的問題之一就是程序中到處是label,這有兩個壞處,第一就是不小心就會造成label重復的問題,第二就是想不出適當的label名稱。如果您已經為label的命名問題腸枯思竭,給您提供一個小方法,程序中如果用「$」可以表示目前PC的地址,依此推論「$+2」表示PC+2,「$-4」表示PC-4,看看底下的例子您立刻就明白:
MOV R,R
JBS PSW,FZ
JMP $+2
JMP $-4
; ....
不過也要給您一個建議,label有個重要的意義就是具有批注的功能,特別是針對一些懶的寫批注的人格外重要。所以這個方法僅適合使用在重復性很高的程序片斷。
SWITCH...CASE敘述
在程序設計的過程中,免不了常常會碰到多重選項的問題,利用EM78XXX的查表指令試試看,所以TBL除了當作一般查表指令以外,還可以當作多重條件判斷之用。
MOV A,CASE
TBL
JMP EVENT1 ; CASE=0
JMP EVENT2 ; CASE=1
JMP EVENT3 ; CASE=2
JMP EVENT4 ; CASE=3
多字節的遞增及遞減運算
因為EM78XXX是8位的單芯片,如果要執行8位以上的計算,必須將多個字節看成是一個變量,以下我們舉例說明如何將一組24位的變量,做到遞增及遞減運算。
遞增(Increment)
MOV A,@1
ADD INT24,A
JBC STATUS,FC
ADD INT24+1,A
JBC STATUS,FC
ADD INT24+2,A
遞減(Decrement)
MOV A,@1
SUB INT24,A
JBS STATUS,FC
SUB INT24+1,A
JBS STATUS,FC
SUB INT24+2,A
判斷多字節變量是否為零
使用簡單的邏輯運算指令,將多字節OR在一起,然后依據Z旗標就可以判斷此多字節變量是否為零了。
MOV A,INT24
OR A,INT24+1
OR A,INT24+2
JBS PSW,FZ
;...
復制某些位
有時候我們需要將一些特定的幾個位由某個緩存器復制給另一組緩存器,由于并非完全復制緩存器的內容,所以會多了一些抽取位的步驟,現在我們找到一個方法,只要四個步驟就可以將指定的位復制到另一組緩存器里面,舉例說明,假設位復制前(SOURCE)=44H,(TARGET)=5AH,如果我們希望將SOURCE的BIT0~BIT2復制到TARGET,則執行程序后(SOURCE)=44H,(TARGET)=5CH。
MOV A,SOURCE
XOR A,TARGET
AND A,@00000111B
XOR TARGET,A
無論您希望復制哪幾個BIT,只要將第三行程序MASK所需的位即可。
奇偶位對調
以下這段程序是根據Dmitry
Kiryashov的算法設計,假設原本ACC內所有位的排列順序為abcdefgh,交換后ACC順序變成badcfehg,程序只有五行,頗耐人尋味。
MOV REG,A
AND A,@0x55
ADD REG,A
RRC REG
ADD A,REG
中斷程序不需保留ACC及PSW的方法
中斷程序一定要保留ACC及PSW嗎?那倒未必!特別是如果您使用的是EM78P152/156之類的迷你級的MCU,RAM
SIZE都特別小,如果您只需要讓TCC中斷做簡單的計數工作,只要小心使用指令,就可以避免中斷程序會破壞到ACC及PSW。原因是有些指令并不會對PSW產生影響,有些指令不需要經過ACC。首先設定好預除器,并且讓TCC
Free Run。下面的例子完全沒用到ACC及PSW。
ORG 0
JMP INIT
ORG 8
TCCINT:
BC RF,TCIF ;清除中斷旗標
INC COUNTER
RETI
Multiple Task管理與狀態機
Multiple Task就是將CPU時間平均分配(也可以是不平均分配)給多個Task,所以在程序中會有一個時間管理者,依照指定的時間對指定的Task服務,沒有分配到時間的Task必需等候時間到來才能執行。
TCCINT:
MOV R10,A
SWAP R10
SWAPA PSW
MOV R11,A
INC TASK
MOV A,@4
SUB A,TASK
JBS PSW,FC
JMP ENDINT
CLR TASK
ENDINT:
BC ISR,TCIF
SWAPA R11
MOV PSW,A
SWAPA R10
RETI
;-------------------------
MAIN:
MOV A,@0x21
CONTW
CLR TCC
CLR ISR
MOV A,@0x01
IOW IOCF
CLR TASK
START:
MOV A,TASK
TBL
JMP TASK0
JMP TASK1
JMP TASK2
JMP TASK3
JMP TASK4
;-------------------------
TASK0:
; ....
JMP START
TASK1:
; ....
JMP START
TASK2:
; ....
JMP START
TASK3:
; ....
JMP START
TASK4:
; ....
JMP START
上面這個程序將TCC規劃為62.5ms中斷一次(系統震蕩選用32.768KHz),所以Task每62.5ms會切換到下一個Task,也就是說每個Task都能夠平均分享CPU的時間,這就是分時多任務的原理。至于中斷程序部分不是必須的,可一情況決定是否要由TCC安排時間的管理。狀態機(State
Machine)是根據目前所在的State所產生的條件,來決定下一個狀態,所以程序原理和上面這個例子大同小異,所不同的是,我們應該把標示為TASKn的Label視為一個單獨的State,然后根據某些條件將最后面的JMP轉移到另外一個State。在這里時間控制也不一定要用到,視需求決定。例如:
TASK1:
MOV A,INPUT
JBS PSW,FZ
JMP TASK2
JMP TASK3
說明
如果INPUT=0的話,將由目前所在的TASK1轉移到TASK3執行,否則狀態轉移到TASK2。
后記
戲法人人會變,只是巧妙各有不同,希望筆者提供的這些小技巧對于喜歡玩單芯片的讀者能夠有所幫助,我們不僅只是強調硬件應該節省,在軟件技巧上也應該多發展一些好的算法,如此才能雙管齊下,對癥下藥。吾人期盼藉此拋磚引玉能激發您更多的創意,寫出更精簡的程序,也期盼您的指教。
竭誠歡迎所有喜愛EM78x系列單芯片的朋友來信和我們一起討論。
筆者E-MAIL:sa2tjw@emc.com.tw
pheavecn 賞析1:
引:
ACC與緩存器內容交換
這理我們要介紹一種快速的邏輯算法,只需要3個指令CYCLE,就可以將ACC的內容與緩存器的內容交換,不拖泥帶水,Very cute!
XOR Number,A
XOR A,Number
XOR Number,A
======================================
析:
這個技巧是所有單片機都通用的。也是我們最多機會用到的。
大多數場合的做法是:
MOV Temp1,A
MOV A,Number
MOV Temp2,A
MOV A,Temp1
MOV Number,A
MOV a,Temp2
用了2個過程變量,6個指令周期。
XOR指令實際上是按位做二進制加法,進位丟棄。
稱Number的0位(bit0)為Bn,A的0位為Ba;
算法的分析如下:
1、XOR Number,A ;Bn+Ba==>Number的0位
2、XOR A,Number
;Bn+Ba+Ba==>ACC的0位,由于Ba+Ba必等于0,所以現在ACC的0位就等于Bn,即原來Number的0位。
3、XOR Number,A
;Bn+Ba+Bn==>Number的0位,同上理,Bn+Bn必等于0,現在Number的0位等于原來ACC的0位。
必須注意,XOR指令影響Z標志位。
HOLTEK芯片的實現指令為:
XORM A,Number
XOR A,Number
XORM A,Number
同樣只影響Z標志位。
pheavecn 賞析2:
引:
節省NOP指令的方法
您還在為程序擠不下傷腦筋嗎?NOP指令有時候在延遲指令時間很有用,假如你有連續兩個NOP指令可以用JMP到下一個指令的方式代替,因為這樣可以減少一個指令BYTE,又可以達到相同的效果。
例如:
NOP
NOP
可以寫成:
JMP NEXT_INST
NEXT_INST:
;....
因為一個NOP花費一個指令Cycle,但是一個JMP指令就需要2個指令Cycle,雖然有時候會抱怨JMP指令會多花一點時間,但是想不到它也有如此妙用吧。
=========================================
析:
我的做法是定義一個預處理宏:
#define nop2 JMP $+1
需要延時2個指令周期時,用NOP2指令就行了。
對EMC、HOLTEK都適用,但是對NTK4位單片機無效,因為NTK所有指令都是單周期的。
注:$表示指令所在PC地址
pheavecn 賞析3:
引:
交換兩組緩存器的內容
如果你覺得要交換兩組內存的內容一定要借用第三組變量,那么您可以參考以下的方式,只是用了一些數學技巧就變得又快又簡單。
MOV A,REG1
SUB A,REG2
ADD REG1,A
SUB REG2,A
原理說明
A=REG1
A=REG2-REG1
REG1=REG1+A
=REG1+(REG2-REG1)
=REG2
REG2=REG2-(REG2-REG1)
=REG1
若X>Y就交換...
延續上一個例子,此法用應用在Bubble Sort特別管用。
MOV A,X
SUB A,Y
JBC PSW,FC
JMP NO_CHANGE
ADD X,A
SUB Y,A
==================================
析:
這個技巧相對來說使用頻率比較少,而且通用性比較差。
對HOLTEK芯片需要更多指令周期:
mov a,Reg1
sub a,Reg2 ;a=reg1-reg2(與EMC指令不同)
addm a,Reg2 ;reg2=reg2+reg1-reg2=reg1(先實現從reg1到reg2)
subm a,Reg1 ;reg1=a-reg1=(reg1-reg2)-reg1=-reg2
cpl reg1
inc reg1 ;這兩句實現reg1=-(-reg2)=reg2
由于減法指令的不同,HOLTEK需要6條指令。
pheavecn 賞析4:
引:
LABEL太多?
寫匯編語言最令人傷腦筋的問題之一就是程序中到處是label,這有兩個壞處,第一就是不小心就會造成label重復的問題,第二就是想不出適當的label名稱。如果您已經為label的命名問題腸枯思竭,給您提供一個小方法,程序中如果用「$」可以表示目前PC的地址,依此推論「$+2」表示PC+2,「$-4」表示PC-4,看看底下的例子您立刻就明白:
MOV R,R
JBS PSW,FZ
JMP $+2
JMP $-4
; ....
不過也要給您一個建議,label有個重要的意義就是具有批注的功能,特別是針對一些懶的寫批注的人格外重要。所以這個方法僅適合使用在重復性很高的程序片斷。
================================
析:
這個問題對匯編程序的可讀性影響很大。有經驗的匯編程序員都應該很好的應用這個符號。掌握使用$的尺度,需要一段時間。
我的原則是:1、必須是RISC單字指令的指令系統,對51系列不適用。
2、跳轉是局部算法內跳轉,即本小段程序在整體程序中可以視作不可分割。
3、小段程序不超過一屏,即在屏幕上可以看到整段程序。
上一條: EMC單片機學習一
下一條: 義隆單片機編程時應注意的幾點