Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created May 9, 2022 01:33
Show Gist options
  • Save samneggs/60b287cc879aed762e530acac5e62777 to your computer and use it in GitHub Desktop.
Save samneggs/60b287cc879aed762e530acac5e62777 to your computer and use it in GitHub Desktop.
Skiing game on Pi Pico in inline assembly with gc9a01 display
import gc9a01
from machine import Pin, SPI, PWM, WDT
import framebuf
from usys import exit
import _thread
import array, gc
from time import sleep_ms, sleep_us, ticks_diff, ticks_us, sleep
from uctypes import addressof
from micropython import const
print('allocated memory',gc.mem_alloc())
print('free memory', gc.mem_free())
MAXSCREEN_X = const(240)
MAXSCREEN_Y = const(240)
GPIO_START = const(0x40014000)
TIMER_BASE = const(0x40054000)
# http://www.penguintutor.com/programming/picodisplayanimations
def blit_image_file(filename,width,height,cw,ch): # file width, file height, char width, char height
with open (filename, "rb") as file:
file_position = 0
char_position = 0
ecount = 0
current_byte = file.read(4) # header
while file_position < (width * height * 2):
current_byte = file.read(1)
if len(current_byte) == 0:
break
sprite_buf[char_position] = ord(current_byte)
char_position += 1
file_position += 1
file.close()
#screen.blit(sprites,50,50)
#tft.blit_buffer(screen, 20, 0, MAXSCREEN_X, MAXSCREEN_Y)
#exit()
from ski_subs import game, char_map
def core1():
global DONE, screen, control
#print(game(control))
#DONE=True
#exit()
ret = 0
gticks=ticks_us()
while 1:
ret = (game(control))
tft.blit_buffer(screen, 0, 0, MAXSCREEN_X, MAXSCREEN_Y)
fps=1_000_000//ticks_diff(ticks_us(), gticks)
#print(fps)
gticks=ticks_us()
DONE = True
exit()
if __name__=='__main__':
spi = SPI(1, baudrate=63_000_000, sck=Pin(10), mosi=Pin(11))
tft = gc9a01.GC9A01(
spi,
MAXSCREEN_X,
MAXSCREEN_Y,
reset=Pin(12, Pin.OUT),
cs=Pin(9, Pin.OUT),
dc=Pin(8, Pin.OUT),
backlight=Pin(13, Pin.OUT),
rotation=0)
tft.init()
tft.rotation(0)
tft.fill(gc9a01.BLACK)
DONE=False
display_buffer=bytearray(240*240*2)
screen=framebuf.FrameBuffer(display_buffer, MAXSCREEN_X , MAXSCREEN_Y, framebuf.RGB565)
sprite_buf=bytearray(32*32*2*12)
trees=array.array('I', 0 for _ in range(50))
control = array.array('I',(addressof(screen),GPIO_START,100,200,0,1,addressof(sprite_buf),1,0xffff,addressof(trees),
1,0,TIMER_BASE,0,addressof(char_map),0,3,0,0,4,0,0,255))
sprites=framebuf.FrameBuffer(sprite_buf,32 , 32*11, framebuf.RGB565)
blit_image_file('skier2.bin',32,32*11,32,32)
gc.collect()
print('remaining',gc.mem_free())
sleep(.5)
_thread.start_new_thread(core1, ())
sleep(.5)
while not DONE:
pass
exit()
#ski subs
from micropython import const
import array
MAXSCREEN_X = const(240)
MAXSCREEN_Y = const(240)
JOY_RIGHT = const(17*8)
JOY_DOWN = const(18*8)
JOY_SEL = const(19*8)
JOY_LEFT = const(20*8)
JOY_UP = const(21*8)
GPIO_START = const(0x40014000)
SCRN = const(0)
GPIO = const(4)
X_POS = const(8)
Y_POS = const(12)
X_VEL = const(16)
Y_VEL = const(20)
SPRITES = const(24)
PLAYER_NUM = const(28)
SCRN_COLOR = const(32)
TREES = const(36)
SPEED = const(40)
COLLIDE = const(44)
TIMER = const(48)
START_TIME = const(52)
CHAR_MAP = const(56)
ROW_ASM = const(0)
COL_ASM = const(2)
OFFSET_ASM = const(4)
COLOR_ASM = const(6)
PRINT_POS = const(60)
LIVES = const(64)
TUMBLE = const(68)
TREE_SCROLL= const(72)
NUM_TREES = const(76)
DISTANCE = const(80)
TREE_ADD = const(84)
SEED = const(88)
#control = array.array('I',(addressof(screen),GPIO_START,100,200,0,1,addressof(sprite_buf),1,0xffff,addressof(trees),1,0,TIMER_BASE,0,addressof(char_map)))
@micropython.asm_thumb
def game(r0):
ldr(r1,[r0,SEED])
cmp(r1,255)
bne(CLEAR_SCREEN)
bl(RESET_TIME)
label(CLEAR_SCREEN)
ldr(r1,[r0,SCRN]) # screen base address
ldr(r2,[r0,SCRN_COLOR]) # white background
mov(r3,240)
mul(r3,r3) # 240*240
add(r3,r3,r3) # (240*240)+(240*240)
add(r1,r1,r3)
label(CLEAR_LOOP)
strh(r2,[r1,0])
sub(r1,2)
sub(r3,2)
bne(CLEAR_LOOP)
ldr(r1,[r0,Y_POS])
mov(r2,210) # increase speed based on ypos
sub(r1,r2,r1)
asr(r1,r1,5)
str(r1,[r0,SPEED])
ldr(r1,[r0,SPEED])
ldr(r2,[r0,DISTANCE])
add(r2,r2,r1)
str(r2,[r0,DISTANCE]) # total distance
ldr(r2,[r0,TREE_ADD])
add(r2,r2,r1)
str(r2,[r0,TREE_ADD]) # distance to add a tree
cmp(r2,255) # add tree every 255 counts
blt(NO_ADD_TREE)
mov(r2,0)
str(r2,[r0,TREE_ADD])
ldr(r6,[r0,NUM_TREES])
add(r6,4)
str(r6,[r0,NUM_TREES]) # r6=new tree address
mov(r4,220) # max tree x-pos
bl(RANDOM) # r2=random number
lsl(r2,r2,8)
ldr(r1,[r0,TREES]) # address of tree array
add(r1,r1,r6)
str(r2,[r1,0])
label(NO_ADD_TREE)
label(MOVE_PLAYER)
ldr(r1,[r0,X_POS])
ldr(r2,[r0,X_VEL])
asr(r2,r2,2)
add(r1,r1,r2)
cmp(r1,20) # min x limit
blt(X_LIM_LEFT)
cmp(r1,190) # max x limit
bgt(X_LIM_RIGHT)
str(r1,[r0,X_POS])
mov(r1,0)
str(r1,[r0,TREE_SCROLL])
b(TURNING)
label(X_LIM_LEFT)
mov(r1,2)
str(r1,[r0,TREE_SCROLL])
mov(r1,0)
str(r1,[r0,X_VEL])
b(TURNING)
label(X_LIM_RIGHT)
mov(r1,0)
sub(r1,2)
str(r1,[r0,TREE_SCROLL])
mov(r1,0)
str(r1,[r0,X_VEL])
label(TURNING)
ldr(r1,[r0,X_VEL]) # player turn sprites
add(r1,37)
mov(r2,5) # ->>
cmp(r1,55)
bgt(TURN_DONE)
mov(r2,4) # ->
cmp(r1,45)
bgt(TURN_DONE)
mov(r2,3) # -
cmp(r1,35)
bgt(TURN_DONE)
mov(r2,2) # <-
cmp(r1,25)
bgt(TURN_DONE)
mov(r2,1) # <<-
label(TURN_DONE)
str(r2,[r0,PLAYER_NUM]) # sprite number
label(JOYSTICK)
ldr(r1,[r0,GPIO]) # r1 = GPIO start address
mov(r2,JOY_RIGHT)
add(r2,r2,r1)
ldr(r2,[r2,0]) # read joy right
cmp(r2,0)
bne(CHECK_LEFT)
ldr(r2,[r0,X_VEL])
add(r2,1)
str(r2,[r0,X_VEL])
b(CHECK_DOWN)
label(CHECK_LEFT)
mov(r2,JOY_LEFT)
add(r2,r2,r1)
ldr(r2,[r2,0]) # read joy left
cmp(r2,0)
bne(NO_RIGHT_LEFT)
ldr(r2,[r0,X_VEL])
sub(r2,1) # player left
str(r2,[r0,X_VEL])
b(CHECK_DOWN)
label(NO_RIGHT_LEFT)
mov(r3,0)
str(r3,[r0,X_VEL]) # straight when no left/right
label(CHECK_DOWN)
mov(r2,JOY_DOWN)
add(r2,r2,r1)
ldr(r2,[r2,0])
cmp(r2,0)
bne(CHECK_UP)
ldr(r3,[r0,Y_POS])
cmp(r3,200) # bottom limit
bgt(CHECK_UP)
ldr(r2,[r0,Y_VEL])
add(r3,r3,r2)
str(r3,[r0,Y_POS]) # move down
mov(r4,0)
str(r4,[r0,PLAYER_NUM]) # player slow
b(DRAW)
label(CHECK_UP)
mov(r2,JOY_UP)
add(r2,r2,r1)
ldr(r2,[r2,0])
cmp(r2,0)
bne(DRAW)
ldr(r3,[r0,Y_POS])
cmp(r3,20) # top limit
blt(DRAW)
ldr(r2,[r0,Y_VEL])
sub(r3,r3,r2)
str(r3,[r0,Y_POS]) # move up
mov(r4,8)
str(r4,[r0,PLAYER_NUM]) # player fast
label(DRAW)
ldr(r6,[r0,NUM_TREES]) # r6=tree loop counter
label(TREE_LOOP)
ldr(r7,[r0,TREES]) # tree data base address
add(r7,r7,r6)
ldr(r2,[r7,0]) # tree data
ldr(r3,[r0,SPEED])
mov(r4,0xff)
mov(r1,r2)
and_(r1,r4) # r1=y
cmp(r1,200) # y > 200?
blt(MOVE_TREE)
mov(r1,0) # reset y position
label(MOVE_TREE)
add(r1,r1,r3) # move tree down
asr(r2,r2,8) # move x to low byte
and_(r2,r4) # r2=x
ldr(r3,[r0,TREE_SCROLL])
add(r3,r3,r2) # x+1 or x-1 scroll
cmp(r3,220)
blt(TREE_RIGHT)
mov(r3,2)
label(TREE_RIGHT)
cmp(r3,1)
bgt(TREE_LEFT)
mov(r3,219)
label(TREE_LEFT)
lsl(r3,r3,8) # move x to high byte
add(r3,r3,r1) # combine x and y
str(r3,[r7,0]) #
mov(r7,MAXSCREEN_X)
mul(r1,r7) # y*max_x
add(r1,r1,r2) # y*max_x + x
add(r1,r1,r1) # double for strh
ldr(r2,[r0,SCRN]) # base screen address
add(r1,r1,r2) # r1=address at x,y
mov(r2,9)
bl(DRAW_SPRITE)
sub(r6,r6,4)
cmp(r6,0)
bgt(TREE_LOOP)
# ------------------ Draw Player -------------
ldr(r1,[r0,Y_POS]) # ypos
mov(r2,MAXSCREEN_X)
mul(r1,r2) # ypos*max_x
ldr(r2,[r0,X_POS])
add(r1,r1,r2) # ypos*max_x+xpos
add(r1,r1,r1) # double for strh
ldr(r2,[r0,SCRN]) # screen address
add(r1,r1,r2) # r1=address at x,y
ldr(r2,[r0,TUMBLE])
cmp(r2,0)
beq(NO_TUMBLE)
add(r2,10)
str(r2,[r0,TUMBLE])
cmp(r2,255)
blt(DO_TUMBLE)
b(COLLIDE_EXIT)
label(DO_TUMBLE)
asr(r2,r2,5)
b(DRAW_TUMBLE)
label(NO_TUMBLE)
ldr(r2,[r0,PLAYER_NUM]) # sprite number
label(DRAW_TUMBLE)
mov(r7,0)
str(r7,[r0,COLLIDE]) # clear collision
bl(DRAW_SPRITE)
ldr(r6,[r0,SPEED])
cmp(r6,0)
beq(NO_COLLIDE) # no collision at zero speed
ldr(r6,[r0,COLLIDE])
cmp(r6,50) # points to determine collision
blt(NO_COLLIDE)
ldr(r6,[r0,TUMBLE])
add(r6,1)
str(r6,[r0,TUMBLE])
label(NO_COLLIDE)
label(SHOW_LIVES) # show sprites for lives
ldr(r6,[r0,LIVES])
label(LIVES_LOOP)
ldr(r1,[r0,SCRN])
mov(r3,MAXSCREEN_X)
lsl(r3,r3,5)
add(r3,r3,r1)
add(r3,80)
lsl(r2,r6,5)
add(r1,r3,r2)
mov(r2,3)
bl(DRAW_SPRITE)
sub(r6,1)
bne(LIVES_LOOP)
mov(r5,15) # time position
lsl(r5,r5,4) # offset
ldr(r2,[r0,SCRN]) # screen address
add(r2,r2,r5) # screen+offset
str(r2,[r0,PRINT_POS])
bl(DISPLAY_TIME) # r2=elapse time
bl(DISPLAY_NUMBER)
mov(r5,240) # distance position
lsl(r5,r5,5) # offset
sub(r5,200)
ldr(r2,[r0,SCRN]) # screen address
add(r2,r2,r5) # screen+offset
str(r2,[r0,PRINT_POS])
ldr(r2,[r0,DISTANCE]) # r2=distance
bl(DISPLAY_NUMBER)
b(EXIT)
# -------------------Subroutines--Draw Sprite-------------
#
label(DRAW_SPRITE) # r2=player number, r1=screen address, uses r1-r5,r7
lsl(r2,r2,11)
ldr(r3,[r0,SPRITES]) # sprites base address
add(r2,r2,r3) # r2=sprite address
mov(r3,0) # r3=y
label(SPRITE_Y)
mov(r4,0) # r4=x
label(SPRITE_X)
ldrh(r5,[r1,0]) # check collision
ldr(r7,[r0,SCRN_COLOR])
cmp(r5,r7)
beq(NO_COLLISION)
ldrh(r5,[r2,0]) # get player sprite
cmp(r5,r7)
beq(OVERLAP)
ldr(r7,[r0,COLLIDE])
add(r7,1)
str(r7,[r0,COLLIDE])
label(NO_COLLISION)
ldrh(r5,[r2,0]) # get player sprite
strh(r5,[r1,0]) # write to screen
label(OVERLAP)
add(r1,r1,2)
add(r2,r2,2)
add(r4,r4,1)
cmp(r4,32)
blt(SPRITE_X)
sub(r1,64)
mov(r5,240)
lsl(r5,r5,1)
add(r1,r1,r5)
add(r3,r3,1)
cmp(r3,32)
blt(SPRITE_Y)
bx(lr)
#-----------------------TIME -------------------
label(RESET_TIME)
ldr(r1,[r0,TIMER])
ldr(r1,[r1,0xc]) # low word of running timer
str(r1,[r0,START_TIME])
bx(lr)
label(DISPLAY_TIME) # returns time in r2, uses r1
ldr(r2,[r0,TIMER])
ldr(r2,[r2,0xc]) # low word of running timer
ldr(r1,[r0,START_TIME])
sub(r2,r2,r1) # elapsed = now - start
asr(r2,r2,16) # 10ths of seconds-ish
bx(lr)
#--------------------Display Number---disply r2---uses r1,r2,r3,r4,r5,r6,r7----------
label(DISPLAY_NUMBER)
mov(r4,pc) # r4=control data
b(SKIP2)
data(2,0,2,4,0xff00 ) #row,col,offset,color
align(2)
label(SKIP2)
label(PRINT_LOOP)
push({r0,r1})
mov(r0,r2) # num
mov(r1,10) # num//10
mov(r7,lr)
bl(DIVIDE10) # do divide
mov(lr,r7)
mov(r6,r0) # r6=num//10
pop({r1, r0})
mov(r5,10)
mul(r5,r6) # 10*(num//10)
sub(r3,r2,r5) # r3=num-(10*(num//10))
label(CHAR_LOOP)
mov(r2,r6) # r2=num//10
mov(r5,7)
strh(r5,[r4,ROW_ASM]) # row=8
label(ROW)
mov(r5,7)
strh(r5,[r4,COL_ASM]) # col=8
label(COL)
push({r2})
mov(r2,lr)
bl(PRINT) # print
mov(lr,r2)
pop({r2})
ldrh(r5,[r4,COL_ASM])
sub(r5,1) # col-=1
strh(r5,[r4,COL_ASM])
bpl(COL)
ldrh(r5,[r4,ROW_ASM])
sub(r5,1)
strh(r5,[r4,ROW_ASM])
bpl(ROW)
ldr(r5,[r0,PRINT_POS])
sub(r5,16) # add one char
str(r5,[r0,PRINT_POS])
cmp(r2,0)
beq(DONE_PRINT) # if num=0, exit
b(PRINT_LOOP)
label(DONE_PRINT)
bx(lr)
# ------------------ PRINT NUMBERS -------------------
label(PRINT) # r3=digit, r4=control, uses r1,r5,r6,r7
ldrh(r5,[r4,ROW_ASM])
lsl(r7,r3,3) # r7=num*8
add(r7,r7,r5) # r7=num*8 + row
ldr(r1,[r0,CHAR_MAP])
add(r7,r7,r1) # c_map[(num*8)+row]
ldrb(r7,[r7,0]) # r7=c_map[(num*8)+row]
mov(r5,1) # r5=1
ldrh(r6,[r4,COL_ASM]) # r6=col
lsl(r5,r6) # r5=1<<col
and_(r5,r7) # c_map[(num*8)+row] & 1<<col
beq(NOBIT) # no pixel, skip
ldrh(r5,[r4,ROW_ASM])
mov(r7,MAXSCREEN_X) # screen width
add(r7,r7,r7)
mul(r7,r5) # row*width
add(r7,r7,r6) # r7 = row*width+col
add(r7,r7,r7)
ldr(r6,[r0,PRINT_POS]) # screen+offset
add(r7,r7,r6) # r7 = dest[row*200+col]
ldrh(r5,[r4,COLOR_ASM])
strh(r5,[r7,0]) # dest[row*200+col] = white
add(r7,MAXSCREEN_X)
add(r7,MAXSCREEN_X)
strh(r5,[r7,0]) # dest[row*200+col] = white
label(NOBIT)
bx(lr)
# -------------------divide routine-----r0=r0//r1----uses r0,r1,r6--
label(DIVIDE10)
mov(r6,0xd0)
lsl(r6,r6,24) # 0d0000000
add(r6,0x60) # offset so strh will work
str(r0, [r6, 8]) # SIO_DIV_SDIVIDEND_OFFSET _u(0x00000068)8
str(r1, [r6, 12]) # SIO_DIV_SDIVISOR_OFFSET _u(0x0000006c)12
b(DELAY1)
label(DELAY1)
b(DELAY2)
label(DELAY2)
b(DELAY3)
label(DELAY3)
b(DELAY4)
label(DELAY4)
ldr(r1, [r6, 20]) #SIO_DIV_REMAINDER_OFFSET _u(0x00000074)20
ldr(r0, [r6, 16]) #SIO_DIV_QUOTIENT_OFFSET _u(0x00000070)16
bx(lr)
#----------------end divide----------------------------------------
#---------------Initialize Player---------------
label(INIT_PLAYER)
mov(r1,100) # reset x-pos
str(r1,[r0,X_POS])
mov(r1,200) # reset y-pos
str(r1,[r0,Y_POS])
mov(r1,1)
str(r1,[r0,X_VEL]) # reset velocities
str(r1,[r0,Y_VEL])
mov(r1,0)
str(r1,[r0,TUMBLE]) # reset tumbling
mov(r3,lr) # save lr for double branch
bl(RESET_TIME)
mov(lr,r3)
bx(lr)
#---------------------# Random mumber ---------------------
label(RANDOM) # uses r1-r5, input=r4, output r2
ldr(r1,[r0,SEED]) # seed
mov(r5,33)
mov(r2,r4) # r4 = max random number
label(HIGHBITS) # counts bits required
sub(r5,r5,1)
lsl(r2,r2,1)
bcc(HIGHBITS) # r5 = max bits
label(RANDOM_LOOP)
add(r1,13) # scramble seed
mov(r2,r1) #
mov(r3,9)
ror(r2,r3)
eor(r1,r2) # end scramble
mov(r2,r1) # r2 = random 32 bit number
str(r1,[r0,SEED])
mov(r3,32)
sub(r3,r3,r5)
asr(r2,r3) # scale down to max random
mov(r3,1)
sub(r5,1)
lsl(r3,r5) # shift 17 bits to get offset
add(r2,r2,r3) # add offset for positive
#bmi(RANDOM_LOOP) # sometimes get negative?
cmp(r2,r4) # get another if too high
bgt(RANDOM_LOOP)
bx(lr)
# -------------------EXITS------------------
label(COLLIDE_EXIT)
bl(INIT_PLAYER)
ldr(r1,[r0,LIVES])
sub(r1,1)
cmp(r1,0)
bgt(LOSE_LIFE)
mov(r1,0)
str(r1,[r0,DISTANCE])
str(r1,[r0,NUM_TREES])
str(r1,[r0,TREE_ADD])
mov(r1,3) # game over
label(LOSE_LIFE)
str(r1,[r0,LIVES])
mov(r1,255)
lsl(r1,r1,18)
label(CRASH_PAUSE)
sub(r1,r1,1)
bne(CRASH_PAUSE)
mov(r0,1)
label(EXIT)
char_map=array.array('b',(
0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00, # U+0030 (0)
0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00, # U+0031 (1)
0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00, # U+0032 (2)
0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00, # U+0033 (3)
0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00, # U+0034 (4)
0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00, # U+0035 (5)
0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00, # U+0036 (6)
0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00, # U+0037 (7)
0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00, # U+0038 (8)
0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00)) # U+0039 (9)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment