Humanitarian Action via the VMU
Posted: Fri Feb 10, 2023 10:45 pm
Hi all
I have a pretty important message to share.
I have been honing my VMU development skills for good. Unfortunately it requires programming in unsafe assembly language, but I used my skills to send a positive message, for the betterment of the software industry.
Here is an image: The code for the curious:
I have a pretty important message to share.
I have been honing my VMU development skills for good. Unfortunately it requires programming in unsafe assembly language, but I used my skills to send a positive message, for the betterment of the software industry.
Here is an image: The code for the curious:
Code: Select all
; -----------------------------------------------------------------------------
; Simple VMU Hello World
;
;
; Author: Shirobon
; Date: 2023/02/11
; -----------------------------------------------------------------------------
.include "sfr_def.s" ; Contains definitions of hardware registers
; -----------------------------------------------------------------------------
; Interrupt Vectors
; -----------------------------------------------------------------------------
.org $00 ; Reset Vector
jmpf __main ; Jump far past header to entry point
.org $03 ; INT0 Interrupt (External)
jmp __nop_vec ; Nothing to do for this interrupt, just return
.org $0B ; INT1 Interrupt (External)
jmp __nop_vec ; Nothing to do for this interrupt, just return
.org $13 ; INT2 Interrupt (External) or T0L Overflow
jmp __nop_vec ; Nothing to do for this interrupt, just return
.org $1B ; INT3 Interrupt (External) or Base Timer Overflow
jmp __time_vec ; Every time this interrupt is risen, call firmware to update time
.org $23 ; T0H Overflow
jmp __nop_vec ; Nothing to do for this interrupt, just return
.org $2B ; T1H or T1L Overflow
jmp __nop_vec ; Nothing to do for this interrupt, just return
.org $33 ; SIO0 Interrupt
jmp __nop_vec ; Nothing to do for this interrupt, just return
.org $3B ; SIO1 Interrupt
jmp __nop_vec ; Nothing to do for this interrupt, just return
.org $43 ; RFB Interrupt
jmp __nop_vec ; Nothing to do for this interrupt, just return
.org $4B ; P3 Interrupt
jmp __nop_vec ; Nothing to do for this interrupt, just return
__nop_vec:
reti ; Just return, used for unnecessary ISRs
.org $130 ; Firmware Entry Vector - Update System Time
__time_vec:
push ie ; Save current Interrupt Enable settings
clr1 ie, 7 ; Block all maskable interrupts
not1 ext, 0 ; NOT1 EXT, 0, followed by a jmpf to a vector
jmpf __time_vec ; will call the firmware function, and when done, return here
pop ie ; Restore previous Interrupt Enable settings
reti
.org $1F0 ; Firmware Entry Vector - Leave Game Mode
__leave_vec:
not1 ext, 0 ; Same as __time_vec; call the firmware
jmpf __leave_vec
; -----------------------------------------------------------------------------
; VMS File Header
; -----------------------------------------------------------------------------
.org $200 ; For games, header begins at offset $200
; 16 bytes of file description (on the VMS)
.text 16 "Demo by Shirobon"
; 32 bytes of file description (on the Dreamcast)
.text 32 "VMU ASM Hello World by Shirobon "
.string 16 "" ; Identifier of application that created the file (just skip)
; Use nifty Waterbear directive to directly generate the icon data :)
.include icon "icon.png"
; -----------------------------------------------------------------------------
; Main Program Entry Point
; -----------------------------------------------------------------------------
.org $680 ; Main entry point begins at $680
__main:
clr1 ie, 7 ; Disable interrupts while we initialize hardware
mov #$81, ocr ; Setup Oscillation Control Register
; Bit 7 - Clock Divisor. 0=Divide by 12, 1=Divide by 6
; Bit 5 - Set Subclock Mode (32kHz), preserve battery
; Bit 0 - Disable Main Clock. Set to 1 when VMU undocked
mov #$09, mcr ; Setup Mode Control Register (LCD Operation)
; Bit 4 - Set to 0, set refresh rate to 83Hz
; Bit 3 - Set to 1 to enable refresh (0 stops LCD)
; Bit 0 - Set to 1 for graphics mode
mov #$80, vccr ; Setup LCD Contrast Control Register
; Bit 7 - Set to 1 to enable LCD display (after enabling refresh)
clr1 p3int, 0 ; Clear bit 0 in Port 3 Interrupt Control Register
; This register sets up interrupts to happen on button press
; Bit 0 - Enable interrupts (which sets bit 1 of p3int on press)
; But, we won't use it (set to 0), because they suck ass
; and don't report when the button was unpressed
clr1 p1, 7 ; Clear bit 7 in Port 1 Latch
; I don't know why this is necessary, Marcus documents don't
; even show this bit is used for anything. But he does this
; in his example tetris demo.
mov #$FF, p3 ; Port 3 is the buttons on the VMU. 0=pressed, 1=unpressed
; Set all to unpressed
clr1 psw, 4 ; Indirect address register bank bits of PSW register
clr1 psw, 3 ; 00 makes R0-R3 correspond to address 000-003
; Read the docs on indirect addressing
mov #$82, $2 ; Address $2 in RAM stores the offset in which we will
; indirectly address. Address $2 in RAM corresponds to the
; $100 to $1FF range in SFR space.
; Therefore, since we want to write to $182 in XRAM,
; We put $82 in address $2, to designate a write to SFR $182
mov #2, xbnk ; LCD Framebuffer (XRAM) is divided into 3 banks. Bank 0 is the
; upper half of LCD, 1 is the bottom half, and 2 is the
; 4 icons on the bottom of the screen.
xor acc ; I don't like the game icon, so clear it by writing 0
; to address $182 in bank 2
st @R2 ; Write that shit.
set1 ie, 7 ; Restore interrupts after initializing all hardware
.main:
mov #<RUST, trl
mov #>RUST, trh
inc trl
inc trl
call __copytovf
call __commitvf
.loop:
jmp .loop
; -----------------------------------------------------------------------------
; __setvfpixel - Set Virtual Framebuffer Pixel at (x,y)
; Register b = X coordinate
; Register c = Y coordinate
; Clobbered: ACC, B, C, Work RAM Registers Modified
; Screen is 48x32 pixels. 1 bpp. 6 bytes horizontal.
; -----------------------------------------------------------------------------
__setvfpixel: ; Algorithm: Set ((y*6)+(x/8))th byte's (x%8)-1 pixel
push b ; Save X coordinate since B is used for multiplication
xor acc ; Clear accumulator
mov #6, b ; MUL is kinda reest. {ACC, C} form a 16 bit multiplicand
; which is multiplied with register B to form a 24 bit
; result in {B, ACC, C}
mul ; ACC = 0, C = Y coordinate. {B,ACC,C} contains Ycoord*6.
pop acc ; Restore X coordinate in accumulator and keep for transfer
push c ; Ycoord*6 will never be above 256, so just save lowest 8 bits
st c ; Move Xcoord from accumulator to C since it is the dividend
xor acc ; Division is also reest. 16-bit divident in {ACC,C}
mov #8, b ; 8-bit divisor in Register B
div ; Do {ACC,C}/B -> C contains X/8, B contains remainder
xor acc ; Clear accumulator just in case
add c ; Accumulator = X/8
pop c ; Restore multiplication result Ycoord*6
add c ; Accumulator is now ((Y*6) + (X/8)), the correct byte in framebuffer
mov #$0, vrmad2 ; VRMAD (9 bit register) holds address of Work RAM (256 byte area)
st vrmad1 ; which will be accessed through VTRBF (to hold virtual framebuffer)
; Acc holds the byte offset of our virtual pixel group,
; so set VRMAD1 to that
clr1 vsel, 4 ; VSEL Bit 4 - If set autoincrement VRMAD on every VRTBF access.
; Disable it, we don't want autoincrement
ld b ; Move B (contains the bit offset into the group of pixels)
; to accumulator for comparison
; Because this reest processor doesn't have a way to programatically
; set a bit, we gotta get a little stoopid.
; RHYMEはお辞め、博士に任せ I GET STOOPID 涙の出る馬鹿さ加減
.b0:bne #0, .b1 ; If 0, store in bit 7 (MSB, leftmost) , otherwise check again
ld vtrbf ; Load what is already in the virtual framebuffer to acc
set1 acc, 7 ; Set the pixel
st vtrbf ; Store it back into virtual framebuffer
ret
.b1:bne #1, .b2 ; If 1, store in bit 6 (next bit from MSB), otherwise check again
ld vtrbf
set1 acc, 6
st vtrbf
ret
.b2:bne #2, .b3 ; Keep doing this ...
ld vtrbf
set1 acc, 5
st vtrbf
ret
.b3:bne #3, .b4
ld vtrbf
set1 acc, 4
st vtrbf
ret
.b4:bne #4, .b5
ld vtrbf
set1 acc, 3
st vtrbf
ret
.b5:bne #5, .b6
ld vtrbf
set1 acc, 2
st vtrbf
ret
.b6:bne #6, .b7
ld vtrbf
set1 acc, 1
st vtrbf
ret
.b7:
ld vtrbf
set1 acc, 0
st vtrbf
ret
; -----------------------------------------------------------------------------
; __commitvf - Commit Virtual Framebuffer to Real Framebuffer
; Clobbered: Nothing
; -----------------------------------------------------------------------------
__commitvf:
push acc ; Save registers so the application code doesn't need to worry
push xbnk
push $2 ; Will use this as pointer for framebuffer
push vsel
push vrmad1
push vrmad2
.begin:
mov #$80, $2 ; Framebuffer starts at address $180, so we put $80 into $2 for
; Indirect SFR addressing
xor acc ; Set acc to zero
st xbnk ; Select first half of framebuffer
st vrmad1 ; Start at first byte in virtual framebuffer
st vrmad2
set1 vsel, 4 ; Enable autoincrement address for sequential copy
.loop:
ld vtrbf ; Get a byte from Work RAM Virtual Framebuffer
st @R2 ; Store in real framebuffer
inc $2 ; Increment to next framebuffer
ld $2 ; Load value to accumulator for testing
and #$0F ; Test if address is divisible by 12
bne #$0C, .skip ; Since after 2 lines (12 bytes) there are 4 empty bytes need to skip over
ld $2 ; If address is divisible by 12 then we copied 2 lines
add #4 ; So add 4 more to skip over the unused bytes
st $2 ; This is the new address into the framebuffer
bnz .skip ; If the address was 0, we have rolled over past 256 and need to
inc xbnk ; write to the next bank (since 1 XBNK only contains half the framebuffer)
mov #$80, $2 ; Reset framebuffer address to point to beginning of next bank
.skip:
ld vrmad1 ; Get current Work RAM (Virtual Framebuffer) Address
bne #$C0, .loop ; If we haven't copied the whole virtual framebuffer yet, go back and copy more
pop vrmad2 ; Restore clobbered registers
pop vrmad1
pop vsel
pop $2
pop xbnk
pop acc
ret
; -----------------------------------------------------------------------------
; __copytovf - Copy a bitmap from TRH/TRL to Virtual Framebuffer
; Clobbered: Nothing
; -----------------------------------------------------------------------------
__copytovf:
push acc ; Save registers so the application code doesn't need to worry
push c
push vsel
push vrmad1
push vrmad2
xor acc ; Set initial counter to 0
st c ; Register C is counter
st vrmad1 ; Start at first byte in virtual framebuffer
st vrmad2
set1 vsel, 4 ; Enable autoincrement address for sequential copy
.loop:
ldc ; Get a byte from [(trh:trl)+c]
st vtrbf ; Store the byte in virtual framebuffer, and autoincrement
inc c ; Increment counter and check if we copied 6*32 = $C0 bytes
ld c
bne #$C0, .loop ; If not, then copy more
.done:
pop vrmad2 ; Restore clobbered registers
pop vrmad1
pop vsel
pop c
pop acc
ret
RUST:
.include sprite "rust.png"
; -----------------------------------------------------------------------------
; THE END, HOPE YOU ENJOYED! ;)
; -----------------------------------------------------------------------------
.cnop 0, $200 ; Pad binary to an even number of blocks.