Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created April 20, 2024 03:36
Show Gist options
  • Save samneggs/b0c46d6418c697ea922c555fe7c5fd35 to your computer and use it in GitHub Desktop.
Save samneggs/b0c46d6418c697ea922c555fe7c5fd35 to your computer and use it in GitHub Desktop.
Classic Snake Game In Assembly on Pi Pico
# snake_asm
from lcd_1_8 import LCD_1inch8
import machine, math
from machine import Pin, PWM
from uctypes import addressof
from time import sleep, ticks_us, ticks_diff, ticks_ms
import gc, _thread, array
from sys import exit
from micropython import const
from random import randint
from rp2 import bootsel_button as RESET_PB
MAXSCREEN_X = const(160)
MAXSCREEN_Y = const(128)
POT_MIN = const(15_000)
POT_MAX = const(50_000)
TIMELR = const(0x4005400c)
MAX_SEGS = const(5000)
FOOD_SIZE = const(5)
TAIL_GROW = const(20)
START_LENGTH = const(30)
FOOD_COLOR = const(0xffff)
SNAKE_PARAMS = const(10)
POS = const(0)
LEN = const(1)
NEW_LEN = const(2)
FOOD_X = const(3)
FOOD_Y = const(4)
POT_PARAMS = const(5)
X = const(0)
Y = const(1)
XV = const(2)
YV = const(3)
P_TOG = const(4)
GAME_PARAMS = const(10)
FPS = const(0)
LIVES = const(1)
ADC_BASE = const(0x4004c000)
ADC_DONE = const(8)
ADC_START0 = const(0b0_0000_0000_0101)
ADC_START1 = const(0b1_0000_0000_0101)
ADC_RESULT = const(4)
#ASM_CONTROL = array.array('i',(addressof(LCD.buffer),addressof(GAME),addressof(SNAKE),addressof(BODY),
# addressof(POT),POT_MIN,POT_MAX,ADC_BASE,ADC_START1,FOOD_COLOR,CTL_SEED))
CTL_SCREEN = const(0)
CTL_GAME = const(4)
CTL_SNAKE = const(8)
CTL_BODY = const(12)
CTL_POT = const(16)
CTL_P_MIN = const(20)
CTL_P_MAX = const(24)
CTL_ADC_BASE = const(28)
CTL_ADC_ST1 = const(32)
CTL_FODCOLOR = const(36)
CTL_SEED = const(40)
@micropython.asm_thumb
def snake_asm(r0):
mov(r7,r0) #r7 = control
ldr(r0,[r7,CTL_POT])
ldr(r1,[r0,P_TOG*4]) # pot read toggle X/Y
mov(r2,1)
eor(r1,r2)
str(r1,[r0,P_TOG*4]) # pot write toggle X/Y
cmp(r1,1)
beq(POT_TOGGLE)
bl(READ_XPOT)
b(POT_DONE)
label(POT_TOGGLE)
bl(READ_YPOT)
label(POT_DONE)
bl(CLEAR_SCREEN) # clear screen
bl(UPDATE_XY) # move head, check screen bounds
bl(DRAW_FOOD) # draw food circle
bl(EAT_FOOD) # check collision with food
bl(MOVE_BODY) # move snake body, return r0=1 game over
cmp(r0,1)
bne(NO_COLL)
b(GAME_OVER)
label(NO_COLL)
bl(DRAW_BODY) # draw snake body
b(EXIT)
label(READ_YPOT) # ---------------------------------------
ldr(r6,[r7,CTL_POT]) # r6 = POT address
ldr(r5,[r6,YV*4]) # r5 = y_inc = int(pot[YV])
ldr(r0,[r7,CTL_ADC_BASE]) # r0 = ADC_BASE address
ldr(r1,[r7,CTL_ADC_ST1]) # start 0b1_0000_0000_0101
str(r1,[r0,0]) # start ADC1
mov(r2,1)
lsl(r2,r2,ADC_DONE)
label(CHECK_ADC0) # is ADC done?
ldr(r3,[r0,0]) # r3 = status
and_(r3,r2)
bne(CHECK_ADC0)
ldr(r1,[r0,ADC_RESULT]) # r1 = raw_y
mov(r2,55)
mul(r2,r2) # 3025
cmp(r1,r2) # raw_y < 3025
blt(DIR1)
cmp(r5,0)
blt(DIR1) # y_inc < 0
mov(r4,0)
str(r4,[r6,XV*4]) # x_inc = 0
add(r4,1)
str(r4,[r6,YV*4]) # y_inc = 1
label(DIR1)
mov(r2,32)
mul(r2,r2) # 1024
cmp(r1,r2) # raw_y < 1024
bgt(Y_DONE)
cmp(r5,0)
bgt(Y_DONE) # y_inc > 0
mov(r4,0)
str(r4,[r6,XV*4]) # x_inc = 0
sub(r4,1)
str(r4,[r6,YV*4]) # y_inc = -1
label(Y_DONE)
bx(lr)
label(READ_XPOT) # ------------------------------------
ldr(r6,[r7,CTL_POT]) # r6 = POT address
ldr(r5,[r6,XV*4]) # r5 = x_inc = int(pot[XV])
ldr(r0,[r7,CTL_ADC_BASE]) # r0 = ADC_BASE address
mov(r1,ADC_START0)
str(r1,[r0,0]) # start ADC0
label(CHECK_ADC1) # is ADC done?
mov(r2,1)
lsl(r2,r2,ADC_DONE) #
ldr(r3,[r0,0]) # r3 = status
and_(r2,r3)
bne(CHECK_ADC1)
ldr(r1,[r0,ADC_RESULT])
mov(r2,55)
mul(r2,r2) # 3025
cmp(r1,r2) # raw_y < 3025
blt(DIR2)
cmp(r5,0)
bgt(DIR2) # x_inc > 0
mov(r4,0)
str(r4,[r6,YV*4]) # y_inc = 0
sub(r4,1)
str(r4,[r6,XV*4]) # x_inc = -1
label(DIR2)
mov(r2,32)
mul(r2,r2) # 1024
cmp(r1,r2) # raw_y < 1024
bgt(X_DONE)
cmp(r5,0)
blt(X_DONE) # x_inc < 0
mov(r4,0)
str(r4,[r6,YV*4]) # y_inc = 0
add(r4,1)
str(r4,[r6,XV*4]) # x_inc = -1
label(X_DONE)
bx(lr)
label(UPDATE_XY) # -------------------------------------------
ldr(r6,[r7,CTL_POT]) # r6 = POT address
ldr(r5,[r6,X*4]) # r5 = pot[X]
ldr(r4,[r6,Y*4]) # r4 = pot[Y]
ldr(r3,[r6,XV*4]) # r3 = pot[XV]
ldr(r2,[r6,YV*4]) # r2 = pot[YV]
add(r5,r5,r3) # pot[x]+=pot[XV]
cmp(r5,0)
bgt(X_LOW)
add(r5,MAXSCREEN_X) # if x < 0 : x += MAXSCREEN_X
label(X_LOW)
cmp(r5,MAXSCREEN_X)
blt(X_HIGH)
sub(r5,MAXSCREEN_X) # if x > MAXSCREEN_X : x -= MAXSCREEN_X
label(X_HIGH)
str(r5,[r6,X*4]) #
add(r4,r4,r2)
cmp(r4,0)
bgt(Y_LOW)
add(r4,MAXSCREEN_Y) # if y < 0 : y += MAXSCREEN_Y
label(Y_LOW)
cmp(r4,MAXSCREEN_Y)
blt(Y_HIGH)
sub(r4,MAXSCREEN_Y) # if y > MAXSCREEN_Y : y -= MAXSCREEN_Y
label(Y_HIGH)
str(r4,[r6,Y*4]) #
ldr(r0,[r7,CTL_BODY])
mov(r1,MAXSCREEN_X)
mul(r4,r1)
add(r4,r4,r5) # y * MAXSCREEN_X + x
str(r4,[r0,0]) # body[0] = y * MAXSCREEN_X + x
bx(lr)
label(RANDINT) # r0: randint(r0:min,r1:max) ----------------------
push({lr})
sub(r1,r1,r0) # max = max - min
mov(r5,r0) # r5: min
ldr(r2,[r7,CTL_SEED]) # seed
add(r2,13) # scramble seed
mov(r4,r2)
mov(r3,9)
ror(r4,r3)
eor(r2,r4) # end scramble
str(r2,[r7,CTL_SEED])
cmp(r2,0)
bge(NOT_NEG)
neg(r0,r2)
label(NOT_NEG)
bl(DIVIDE) # r1: seed % max
add(r0,r1,r5) # r0: randint(min,max)
pop({pc})
#--divide routine-----r0=r0//r1----uses r0,r1,r6----------
label(DIVIDE)
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)
label(DRAW_FOOD) # r7 = control, r0 = center x, r1 = center y, r2 = radius ---------------
push({lr})
ldr(r3,[r7,CTL_SNAKE]) # SNAKE address
ldr(r0,[r3,FOOD_X*4]) # r0 = x
ldr(r1,[r3,FOOD_Y*4]) # r1 = y
mov(r2,FOOD_SIZE) # r2 = radius
mov(r3,r2) # r3: x = radius
mov(r4,0) # r4: y = 0
mov(r5,1) # r5: p = 1
sub(r5,r5,r2) # p = 1 - radius
label(CIR_LOOP)
cmp(r3,r4) # x,y
ble(CIR_LOOP_DONE)
add(r4,r4,1) # y += 1
cmp(r5,0)
bgt(P_GT_ZERO)
add(r5,r5,r4) # p = p + y
add(r5,r5,r4) # p = p + 2y
add(r5,r5,1) # p = p + 2y +1
b(IF_DONE)
label(P_GT_ZERO)
sub(r3,r3,1) # x -= 1
add(r5,r5,r4) # p = p + y
add(r5,r5,r4) # p = p + 2y
sub(r5,r5,r3) # p = p + 2y - x
sub(r5,r5,r3) # p = p + 2y - 2x
add(r5,r5,1) # p = p + 2y - 2x + 1
label(IF_DONE)
cmp(r3,r4) # x,y
blt(CIR_LOOP_DONE) # if x < y: break
#plot_line(x0 - x, y0 + y, x0 + x, y0 + y)
push({r0,r1,r2,r3,r4,r5,r6})
mov(r5,r0) # r2: x0
mov(r6,r1) # r3: y0
sub(r0,r0,r3) # r0: x0 - x
add(r1,r1,r4) # r1: y0 + y
add(r2,r5,r3) # r2: x0 + x
add(r3,r6,r4) # r3: y0 + y
bl(DRAW_LINE)
#plot_line(x0 - y, y0 + x, x0 + y, y0 + x)
push({r0,r1,r2,r3,r4,r5,r6})
mov(r5,r0) # r2: x0
mov(r6,r1) # r3: y0
sub(r0,r0,r4) # r0: x0 - y
add(r1,r1,r3) # r1: y0 + x
add(r2,r5,r4) # r2: x0 + y
add(r3,r6,r3) # r3: y0 + x
bl(DRAW_LINE)
#plot_line(x0 - x, y0 - y, x0 + x, y0 - y)
push({r0,r1,r2,r3,r4,r5,r6})
mov(r5,r0) # r2: x0
mov(r6,r1) # r3: y0
sub(r0,r0,r3) # r0: x0 - x
sub(r1,r1,r4) # r1: y0 - y
add(r2,r5,r3) # r2: x0 + x
sub(r3,r6,r4) # r3: y0 - y
bl(DRAW_LINE)
#plot_line(x0 - y, y0 - x, x0 + y, y0 - x)
push({r0,r1,r2,r3,r4,r5,r6})
mov(r5,r0) # r2: x0
mov(r6,r1) # r3: y0
sub(r0,r0,r4) # r0: x0 - y
sub(r1,r1,r3) # r1: y0 - x
add(r2,r5,r4) # r2: x0 + y
sub(r3,r6,r3) # r3: y0 - x
bl(DRAW_LINE)
b(CIR_LOOP)
label(CIR_LOOP_DONE)
#plot_line(x0 - radius, y0, x0 + radius, y0)
push({r0,r1,r2,r3,r4,r5,r6})
mov(r5,r0) # r2: x0
sub(r0,r5,r2) # r0: x0 - radius
add(r2,r5,r2) # r2: x0 + radius
mov(r3,r1) # r3: y0
bl(DRAW_LINE)
pop({pc})
label(DRAW_LINE) # r0,r1,r2,r3 = x1,y1,x2,y2 ----circle sub--------------
cmp(r1,r3) #
bne(Y_LOOP)
label(X_LOOP) # if y1 == y2:
mov(r4,r0) # r4 : x = x1
label(FOR_X)
mov(r5,MAXSCREEN_X)
mul(r5,r1) # y1 * MAXSCREEN_X
add(r5,r5,r4) # y1 * MAXSCREEN_X + x
add(r5,r5,r5) # double for strh
ldr(r6,[r7,CTL_SCREEN]) # screen address
add(r5,r5,r6) #
ldr(r6,[r7,CTL_FODCOLOR]) # food color
strh(r6,[r5,0]) # draw pixel
add(r4,r4,1)
cmp(r4,r2)
ble(FOR_X)
pop({r0,r1,r2,r3,r4,r5,r6})
bx(lr)
label(Y_LOOP) # if y1 != y2:
mov(r4,r1) # r4 : y = y1
label(FOR_Y)
mov(r5,MAXSCREEN_X)
mul(r5,r4) # y * MAXSCREEN_X
add(r5,r5,r0) # y * MAXSCREEN_X + x1
add(r5,r5,r5) # double for strh
ldr(r6,[r7,CTL_SCREEN]) # screen address
add(r5,r5,r6) #
ldr(r6,[r7,CTL_FODCOLOR]) # food color
strh(r6,[r5,0]) # draw pixel
add(r4,r4,1)
cmp(r4,r3)
ble(FOR_Y)
pop({r0,r1,r2,r3,r4,r5,r6})
bx(lr)
label(EAT_FOOD) # -----------------------------------------
push({lr})
ldr(r0,[r7,CTL_BODY]) # BODY address
ldr(r0,[r0,POS*4]) # BODY position
mov(r1,MAXSCREEN_X)
bl(DIVIDE) # r0 = y_head = pos//MAXSCREEN_X, r1 = x_head = pos % MAXSCREEN_X
ldr(r2,[r7,CTL_SNAKE]) # SNAKE address
ldr(r3,[r2,FOOD_X*4]) # r3: x_food
ldr(r2,[r2,FOOD_Y*4]) # r2: y_food
sub(r1,r1,r3) # x_head - x_food
mul(r1,r1) # ((x_head - x_food) * (x_head - x_food))
sub(r0,r0,r2) # y_head - y_food
mul(r0,r0) # ((y_head - y_food) * (y_head - y_food))
add(r0,r0,r1) # ((x_head - x_food) * (x_head - x_food)) + ((y_head - y_food) * (y_head - y_food))
mov(r1,FOOD_SIZE) #
mul(r1,r1) # FOOD_SIZE * FOOD_SIZE
cmp(r0,r1) # distance_squared <= radius_squared
ble(COLLISION)
pop({pc})
label(COLLISION) # new food
mov(r0,FOOD_SIZE*2) # min x
mov(r1,MAXSCREEN_X-FOOD_SIZE*2)# max x
bl(RANDINT)
ldr(r2,[r7,CTL_SNAKE]) # SNAKE address
str(r0,[r2,FOOD_X*4]) # x_food
mov(r0,FOOD_SIZE*2) # min y
mov(r1,MAXSCREEN_Y-FOOD_SIZE*2)# max y
bl(RANDINT)
ldr(r2,[r7,CTL_SNAKE]) # SNAKE address
str(r0,[r2,FOOD_Y*4]) # y_food
ldr(r3,[r2,NEW_LEN*4]) # new length
add(r3,TAIL_GROW)
str(r3,[r2,NEW_LEN*4])
pop({pc})
label(DRAW_BODY) # -------------------------------------------
ldr(r2,[r7,CTL_SNAKE]) # SNAKE address
ldr(r2,[r2,LEN*4]) # r2: segments
ldr(r3,[r7,CTL_BODY]) # r3: BODY address
ldr(r4,[r7,CTL_SCREEN]) # r4: SCREEN address
mov(r5,0xff) # r5: body color
label(SEG_LOOP)
ldr(r0,[r3,0]) # r0: BODY[index] (position)
add(r0,r0,r0) # double for strh
add(r0,r0,r4) # screen + offset
strh(r5,[r0,0])
add(r3,r3,4) # next address
sub(r2,r2,1) # seg-=1
cmp(r2,0)
bgt(SEG_LOOP)
bx(lr)
label(CLEAR_SCREEN) # ------------------------
ldr(r0,[r7,CTL_SCREEN]) # r0: SCREEN address
mov(r1,MAXSCREEN_X)
mov(r2,MAXSCREEN_Y)
mul(r1,r2) # r1: screen size
mov(r3,0) # r3: color
label(CLEAR_LOOP)
strh(r3,[r0,0])
add(r0,r0,2)
sub(r1,r1,1)
cmp(r1,0)
bgt(CLEAR_LOOP)
bx(lr)
label(MOVE_BODY) #-------------------------------------------
ldr(r0,[r7,CTL_SNAKE]) # SNAKE address
ldr(r1,[r0,LEN*4]) # r1: segments = snake[LEN]
ldr(r2,[r0,NEW_LEN*4]) # new_length = snake[NEW_LEN]
cmp(r1,r2)
bge(NO_GROW) # segments => new_length
add(r2,r1,1) # snake[LEN] += 1
str(r2,[r0,LEN*4])
label(NO_GROW)
ldr(r0,[r7,CTL_BODY]) # r0: BODY address
ldr(r2,[r0,0]) # r2: head = body[0]
mov(r3,4)
mul(r3,r1) # segments * 4
add(r3,r3,r0) # r3: body[segments] address
label(GROW_LOOP)
sub(r4,r3,4) # r4: body[segments-1]
ldr(r5,[r4,0]) # r5: body[segments-1] value
str(r5,[r3,0]) # body[i] = body[i-1]
sub(r3,r3,4)
cmp(r2,r5)
bne(NO_TAIL)
cmp(r1,1)
ble(NO_TAIL)
ldr(r0,[r7,CTL_SNAKE])
mov(r1,1)
str(r1,[r0,LEN*4])
mov(r0,1)
bx(lr) # exit game over
label(NO_TAIL)
sub(r1,r1,1)
cmp(r1,0)
bgt(GROW_LOOP)
mov(r0,0)
bx(lr)
label(EXIT) # Game exits ---------------------------
mov(r0,0)
label(GAME_OVER)
mov(r1,1)
def init_game():
global GAME, SNAKE, BODY, POT, ASM_CONTROL
GAME = array.array('i',0 for _ in range(GAME_PARAMS))
SNAKE = array.array('i',0 for _ in range(SNAKE_PARAMS))
BODY = array.array('i',0 for _ in range(MAX_SEGS))
POT = array.array('i',0 for _ in range(POT_PARAMS))
ASM_CONTROL = array.array('i',(addressof(LCD.buffer),addressof(GAME),addressof(SNAKE),addressof(BODY),
addressof(POT),POT_MIN,POT_MAX,ADC_BASE,ADC_START1,FOOD_COLOR,CTL_SEED))
ASM_CONTROL[CTL_SEED//4] = machine.mem32[TIMELR]
GAME[FPS] = 0
GAME[LIVES] = 3
POT[X] = MAXSCREEN_X//2
POT[Y] = MAXSCREEN_Y//2
POT[XV] = 1
SNAKE[LEN] = START_LENGTH
SNAKE[NEW_LEN] = START_LENGTH
SNAKE[FOOD_X] = randint(FOOD_SIZE*2,MAXSCREEN_X-FOOD_SIZE*2)
SNAKE[FOOD_Y] = randint(FOOD_SIZE*2,MAXSCREEN_Y-FOOD_SIZE*2)
for i in range(5):
BODY[i] = MAXSCREEN_Y//2 * MAXSCREEN_X + MAXSCREEN_X//2 - i
@micropython.viper
def main():
global OVER
init_game()
while not RESET_PB() and not OVER:
sleep(0.001)
OVER = int(snake_asm(ASM_CONTROL))
LCD.show()
exit()
def shutdown():
global EXIT
EXIT = True
Pin(13,Pin.OUT).low() # screen off
gc.collect()
print(gc.mem_free())
print('Core0 Stop')
exit()
if __name__=='__main__':
COUNT = 0
OVER = 0
machine.mem32[0x4004c000+0] = 0b11_0000_0000_0000_1001
FIRE_BUTTON = Pin(22, Pin.IN, Pin.PULL_UP)
#machine.freq(200_000_000)
#machine.mem32[0x40008048] = 1<<11 # enable peri_ctrl clock
pwm = PWM(Pin(13))
pwm.freq(1000)
pwm.duty_u16(0x8fff)#max 0xffff
LCD = LCD_1inch8()
LCD.fill(0)
LCD.show()
EXIT = False
try:
main()
shutdown()
except KeyboardInterrupt :
shutdown()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment