天天看點

利用 Ophis 編寫 Commodore 64 programs PRG 程式(三)字元映射data段實戰應用前往下一節

在上一節中,我們了解了标簽、别名、頭檔案/庫以及宏定義的用法。接下來我們将看看另外兩個好用的功能:字元映射與

.data

本節參考了Character maps、Local variables and memory segments

字元映射

C64使用

PETSCII

(CBM ASCII)編碼。不同于

ASCII

編碼,其最顯著的差別是,大小寫字母相較于

ASCII

碼是完全颠倒的。

利用 Ophis 編寫 Commodore 64 programs PRG 程式(三)字元映射data段實戰應用前往下一節
PETSCII - Wikipedia

是以,Ophin提供了字元映射功能以解決該問題。我們可以使用

.charmap

定義一個新的字元映射關系。它有兩個參數,分别是按

ASCII

排列順序的開始映射的字元值+一個映射字元串。例如下面這兩行代碼将會颠倒大小寫,是以能夠得到正常的輸出結果。

.charmap 'A, "abcdefghijklmnopqrstuvwxyz"
.charmap 'a, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
           

或者也可以使用

.charmapbin

直接替換整個字元表(256個字元)。它接受一個參數,指向一個256位元組的檔案,用以說明字元映射關系。

以下是使用base16384編碼的256Byte

ASCII

->

PETSCII

映射關系。

一帠娐匆係亐瘬娍冃缁剈愔卅潱湤栛唇忡誀漢囉偒暜瘩墊胂芸細婌焳廔萷導憣竰謾巐吖垍牥枙蒆玩祬楛瓷俅聳欝敧氈蝺擗叕蝽湁廬艄挙啈恒犴缵屏戔揮孑捖揖廁短詽漣蘈吊冄潡癸瀦墋焣曨豂徒狥坙桞暙璦蟉葺涠癨砺悖璧砪梪粲箮秬夛壎芵箭見瓪覼絯秼儇僃缱橬洣埊胳嫜褿廑芴譍斂旘葶箽腷泟蘸氮嶓珦蟺岞禯竭覻贏嗋緻譽絿燧裻賄淯言㸾㴽㴽㴽
           

用C語言列印檔案二進制與十六進制編碼如下

00000000 00000001 00000010 00000011 00000100 00000101 00000110 00000111 00 01 02 03 04 05 06 07 
00001000 00001001 00001010 00001011 00001100 00001101 00001110 00001111 08 09 0a 0b 0c 0d 0e 0f 
00010000 00010001 00010010 00010011 00010100 00010101 00010110 00010111 10 11 12 13 14 15 16 17 
00011000 00011001 00011010 00011011 00011100 00011101 00011110 00011111 18 19 1a 1b 1c 1d 1e 1f 
00100000 00100001 00100010 00100011 00100100 00100101 00100110 00100111 20 21 22 23 24 25 26 27 
00101000 00101001 00101010 00101011 00101100 00101101 00101110 00101111 28 29 2a 2b 2c 2d 2e 2f 
00110000 00110001 00110010 00110011 00110100 00110101 00110110 00110111 30 31 32 33 34 35 36 37 
00111000 00111001 00111010 00111011 00111100 00111101 00111110 00111111 38 39 3a 3b 3c 3d 3e 3f 
01000000 01100001 01100010 01100011 01100100 01100101 01100110 01100111 40 61 62 63 64 65 66 67 
01101000 01101001 01101010 01101011 01101100 01101101 01101110 01101111 68 69 6a 6b 6c 6d 6e 6f 
01110000 01110001 01110010 01110011 01110100 01110101 01110110 01110111 70 71 72 73 74 75 76 77 
01111000 01111001 01111010 01011011 01011100 01011101 01011110 01011111 78 79 7a 5b 5c 5d 5e 5f 
01100000 01000001 01000010 01000011 01000100 01000101 01000110 01000111 60 41 42 43 44 45 46 47 
01001000 01001001 01001010 01001011 01001100 01001101 01001110 01001111 48 49 4a 4b 4c 4d 4e 4f 
01010000 01010001 01010010 01010011 01010100 01010101 01010110 01010111 50 51 52 53 54 55 56 57 
01011000 01011001 01011010 01111011 01111100 01111101 01111110 01111111 58 59 5a 7b 7c 7d 7e 7f 
10000000 10000001 10000010 10000011 10000100 10000101 10000110 10000111 80 81 82 83 84 85 86 87 
10001000 10001001 10001010 10001011 10001100 10001101 10001110 10001111 88 89 8a 8b 8c 8d 8e 8f 
10010000 10010001 10010010 10010011 10010100 10010101 10010110 10010111 90 91 92 93 94 95 96 97 
10011000 10011001 10011010 10011011 10011100 10011101 10011110 10011111 98 99 9a 9b 9c 9d 9e 9f 
10100000 10100001 10100010 10100011 10100100 10100101 10100110 10100111 a0 a1 a2 a3 a4 a5 a6 a7 
10101000 10101001 10101010 10101011 10101100 10101101 10101110 10101111 a8 a9 aa ab ac ad ae af 
10110000 10110001 10110010 10110011 10110100 10110101 10110110 10110111 b0 b1 b2 b3 b4 b5 b6 b7 
10111000 10111001 10111010 10111011 10111100 10111101 10111110 10111111 b8 b9 ba bb bc bd be bf 
11000000 11000001 11000010 11000011 11000100 11000101 11000110 11000111 c0 c1 c2 c3 c4 c5 c6 c7 
11001000 11001001 11001010 11001011 11001100 11001101 11001110 11001111 c8 c9 ca cb cc cd ce cf 
11010000 11010001 11010010 11010011 11010100 11010101 11010110 11010111 d0 d1 d2 d3 d4 d5 d6 d7 
11011000 11011001 11011010 11011011 11011100 11011101 11011110 11011111 d8 d9 da db dc dd de df 
11100000 11100001 11100010 11100011 11100100 11100101 11100110 11100111 e0 e1 e2 e3 e4 e5 e6 e7 
11101000 11101001 11101010 11101011 11101100 11101101 11101110 11101111 e8 e9 ea eb ec ed ee ef 
11110000 11110001 11110010 11110011 11110100 11110101 11110110 11110111 f0 f1 f2 f3 f4 f5 f6 f7 
11111000 11111001 11111010 11111011 11111100 11111101 11111110 11111111 f8 f9 fa fb fc fd fe ff 
           

由此,我們可以編寫列印小寫字母的代碼

lda #lower'case		; 切換大小寫
jsr chrout
           

完整的代碼如下。值得注意的是,代碼中使用

nop

指令定義了

delay

宏,進而實作延時。

.include "c64-1.oph"
.outfile "hello.prg"

.macro print
	ldx #0
_loop:	lda _1, x
	beq _done
	jsr chrout
	inx
	bne _loop
_done:
.macend

.macro greet
	lda #30
	jsr delay
	`print hello1
	`print _1
	`print hello2
.macend

	lda #147
	jsr chrout
	lda #lower'case
	jsr chrout
	`greet target1
	`greet target2
	`greet target3
	`greet target4
	`greet target5
	`greet target6
	`greet target7
	`greet target8
	`greet target9
	`greet target10
	rts

.charmap 'A, "abcdefghijklmnopqrstuvwxyz"
.charmap 'a, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

hello1: .byte "Hello, ",0
hello2: .byte "!", 13, 0

target1:  .byte "programmer", 0
target2:  .byte "room", 0
target3:  .byte "building", 0
target4:  .byte "neighborhood", 0
target5:  .byte "city", 0
target6:  .byte "nation", 0
target7:  .byte "world", 0
target8:  .byte "Solar System", 0
target9:  .byte "Galaxy", 0
target10: .byte "Universe", 0

; DELAY routine.  Executes 2,560*(A) NOP statements.
delay:	tax		; x = a
	ldy #00		; y = 0
*	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	iny			; y++
	bne -
	dex			; x--
	bne -
	rts
           

data段

實際上,在記憶體的

BASIC ROM

I/O ROM

之間有一段空閑空間,位于

0xC000

-

0xCFFF

,我們可以利用這部分空間儲存一些臨時變量。于是Ophis設計了

.data

指令,用以自動管理這部分空間。但是,它并不知道空餘空間從何處開始,是以需要我們手動聲明

.data
.org $C000
           

另外,由于不知道空餘空間的大小,它也不會檢查邊界,需要我們在程式的最後使用如下指令通知編譯器進行檢查

.checkpc $A000	; text段邊界
.data
.checkpc $D000	; data段邊界
           

接下來即可用

.space

指令友善地管理和使用這部分空間

.data			; 切換到data段
.space _a 1		; 為_a配置設定1位元組空間
.space _b 4		; 為_b配置設定4位元組空間

.text			; 切換回text段,即機器代碼所在段
           

值得注意的是,雖然上面的注釋提到了"配置設定",但是就和C語言一樣,程式或編譯器并不會檢查通路是否越界。實際上,編譯器隻是将現有空閑空間的首位址配置設定給一個"alias",并将"空閑空間标記"(PC)向後移動聲明的長度而已。

實戰應用

接下來我們将介紹一個不同于用

nop

指令定義的

delay

子程式的更好的實作。該實作利用了

KERNAL

庫的

rdtim

子程式。該子程式傳回一個

24bit

的整數,表示系統目前開機秒數的

60

倍。

首先,我們來分析一下

rdtim

傳回值的存放位置。你可以執行如下代碼,該代碼将循環列印運作

rdtim

a

x

y

的二進制值。

.include "Ophis-2.1/platform/c64_0.oph"
.include "Ophis-2.1/platform/c64kernal.oph"
.outfile "clock.prg"

.data zp
.space _na 1	; a的臨時存放處
.space _nx 1	; x的臨時存放處
.space _ny 1	; y的臨時存放處

.text
*   jsr rdtim
    stx _nx
    sty _ny
    jsr printbyte
    jsr printspace
    lda _nx
    jsr printbyte
    jsr printspace
    lda _ny
    jsr printbyte
    lda #13     ; 換行
    jsr chrout
    jmp -       ; 死循環

printbyte:
    sta _na
    txa
    pha
    ldx #8	; 列印8bit
*   lda #$30	; a = '0'
    asl _na	; 左移一位,溢出到c
    adc #0	; a = a + c + 0
    jsr chrout	; putchar(a)
    dex		; x--
    bne -	; if(x != 0) goto 上個星号
    pla
    tax
    rts

printspace:
    lda #$20
    jsr chrout
    rts

.checkpc $A000
.data
.checkpc $D000
           

可以觀察到如下結果

利用 Ophis 編寫 Commodore 64 programs PRG 程式(三)字元映射data段實戰應用前往下一節
利用 Ophis 編寫 Commodore 64 programs PRG 程式(三)字元映射data段實戰應用前往下一節
利用 Ophis 編寫 Commodore 64 programs PRG 程式(三)字元映射data段實戰應用前往下一節

可見該

24bit

整數将按照小端序存放在

{a, x, y}

中。我們可以根據這個原理,通過統計

a

的值,制作最大延時不超過

255/60=4.25s

delay

子程式。

; DELAY routine.  Takes values from the Accumulator and pauses
; for that many jiffies (1/60th of a second).
.scope
.data
.space _tmp 1
.space _target 1

.text

delay:  sta _tmp        ; save argument (rdtim destroys it)
        jsr rdtim
        clc
        adc _tmp        ; add current time to get target
        sta _target
*       jsr rdtim
        cmp _target
        bmi -           ; Buzz until target reached
        rts
.scend
           

值得注意的是,我們之是以不在程式中使用如下聲明來建立臨時存儲空間,是為了節省程式

text

段的空間

.scope
        ; data used by the delay routine
        _tmp:    .byte 0
        _target: .byte 0

delay:  sta _tmp        ; save argument (rdtim destroys it)
        jsr rdtim
        clc
        adc _tmp        ; add current time to get target
        sta _target
*       jsr rdtim
        cmp _target
        bmi -           ; Buzz until target reached
        rts
.scend
           

另外,如果程式中沒有其它

.space

,則上面程式的兩行

.space

語句與如下語句等價

.alias _tmp    $C000
.alias _target $C001
           

前往下一節

繼續閱讀