Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created September 4, 2022 15:03
Show Gist options
  • Save samneggs/b31914ce52b0399ac53091be683f0ccc to your computer and use it in GitHub Desktop.
Save samneggs/b31914ce52b0399ac53091be683f0ccc to your computer and use it in GitHub Desktop.
Racing game for PI Pico in MicroPython and Assembly
# Racetrack - based on interpolator code from boochow
# https://github.com/boochow/pico_test_projects/tree/main/interpolator2
# http://blog.boochow.com/
from machine import Pin, SPI, WDT
import gc9a01
import framebuf, array
from time import sleep, ticks_diff, ticks_us
from math import sin,cos,radians
from micropython import const
from uctypes import addressof
import gc, _thread
from usys import exit
from random import randint
from tiles5 import tiles
PicoDisplayWIDTH = const(240)
PicoDisplayHEIGHT = const(240)
M_PI = const(3.14159)
SCALE = const(11)
joyRight = Pin(17,Pin.IN)
joyDown = Pin(18,Pin.IN)
joySel = Pin(19,Pin.IN)
joyLeft = Pin(20,Pin.IN)
joyUp = Pin(21,Pin.IN)
map_width = 6 #
map_height = 6 #
SHOW = 0
from race_texture import map_texture
seed_buffer = array.array('b', randint(0,4) for i in range(1<<map_width * 1<<map_height))
for i in range(len(map_texture)):
t=map_texture[i]
if t == 0:
map_texture[i] = 4
else:
map_texture[i] = t-1
car_texture = bytearray(16*16*2*8)
buffer = bytearray(PicoDisplayWIDTH * PicoDisplayHEIGHT * 2)
dark_grey = 0x4711 #pico_display.create_pen(20, 40, 60)
black = 0 #pico_display.create_pen(0, 0, 0)
dark_green = 0x0024 #pico_display.create_pen(32, 128, 0)
lt_blue = 0x3e44 #pico_display.create_pen(66, 133, 244)
lt_yellow = 0x33ff #pico_display.create_pen(255, 229, 153)
lt_green = 0xe007 #pico_display.create_pen(0, 255, 0)
brown = 0xe079 #pico_display.create_pen(120, 63, 4)
white = 0xffff #pico_display.create_pen(255, 255, 255)
sky_blue = 0x1f66 #pico_display.create_pen(96, 192, 255)
blue = 0x1f00
red = 0xf8
colors = array.array('H',(white,black,red,lt_green, sky_blue,dark_green,dark_grey,sky_blue,blue,white,white,white,white,lt_green,dark_green, sky_blue,blue,black))
colors2 = array.array('H',(black,black,black,black,red,black,black,black,blue,black,black,black,black,black,black, sky_blue,blue,black))
bg_color = dark_grey
INTERP0_ACCUM0 = const(0x080)
INTERP0_ACCUM1 = const(0x084)
INTERP0_BASE0 = const(0x088)
INTERP0_BASE1 = const(0x08c)
INTERP0_BASE2 = const(0x090)
INTERP0_POP_FULL = const(0x09c)
INTERP0_CTRL_LANE0 = const(0x0ac)
INTERP0_CTRL_LANE1 = const(0x0b0)
INTERP1_ACCUM0 = const(0x0c0)
INTERP1_ACCUM1 = const(0x0c4)
INTERP1_BASE0 = const(0x0c8)
INTERP1_BASE1 = const(0x0cc)
INTERP1_BASE2 = const(0x0d0)
INTERP1_POP_FULL = const(0x0dc)
INTERP1_CTRL_LANE0 = const(0x0ec)
INTERP1_CTRL_LANE1 = const(0x0f0)
@micropython.viper
def interp_setup(map_width_bits:int, map_height_bits:int, uv_fractional_bits:int, tile_width_bits:int, tile_height_bits:int):
# texture width
sio=ptr32(0xd000_0000) # shift raw mask MSB LSB
sio[INTERP0_CTRL_LANE0//4] = 16 | 1<<18 | (map_width_bits-1)<<10 | 0<<5
sio[INTERP0_CTRL_LANE1//4] = 16-map_width_bits | 1<<18 | (map_width_bits+map_height_bits-1)<<10 | map_width_bits<<5
sio[INTERP1_CTRL_LANE0//4] = 16-4 | 1<<18 | 3<<10 | 0<<5 #tile
sio[INTERP1_CTRL_LANE1//4] = 16-8 | 1<<18 | 7<<10 | 4<<5
class Camera():
def __init__(self,x,y,z,screen_distance,rot,speed):
self.x = x
self.y = y
self.z = z
self.screen_distance =screen_distance
self.rot = rot
self.speed = speed
self.jump_count = 50
self.off_track = 0
# x y z dist rot jump
camera = Camera(60<<16, 124<<16, 35<<16, 200 , 270 ,(1<<SCALE)+200) # z =3<<16
isin=array.array('i',range(0,360))
icos=array.array('i',range(0,360))
def init_isin(): # integer sin lookup table
for i in range(0,360):
isin[i]=int(sin(radians(i))*(1<<SCALE))
def init_icos(): # integer cos lookup table
for i in range(0,360):
icos[i]=int(cos(radians(i))*(1<<SCALE))
@micropython.viper
def map_fill_line(output_offset:int,u:int,v:int,du:int,dv:int,count:int):
output = ptr16(buffer)
colors_ptr = ptr16(colors)
tiles_ptr = ptr8(tiles)
map_ptr = ptr8(map_texture)
sio = ptr32(0xd000_0000)
sio[INTERP0_ACCUM0//4] = u # map
sio[INTERP0_ACCUM1//4] = v
sio[INTERP0_BASE0//4] = du
sio[INTERP0_BASE1//4] = dv
sio[INTERP1_ACCUM0//4] = u # tile
sio[INTERP1_ACCUM1//4] = v
sio[INTERP1_BASE0//4] = du
sio[INTERP1_BASE1//4] = dv
for i in range(0,count,1):
t = sio[INTERP0_POP_FULL//4] # map
c = sio[INTERP1_POP_FULL//4] # tile
pixel = colors_ptr[tiles_ptr[c+(map_ptr[t]<<8)]]
output[output_offset+i] = pixel
SCREEN_CTL = const(0) #0
SIO_CTL = const(4) #1
U_CTL = const(8) #2
V_CTL = const(12) #3
DU_CTL = const(16) #4
DV_CTL = const(20) #5
COUNT_CTL = const(24) #6
COLORS_CTL = const(28) #7
TILES_CTL = const(32) #8
MAP_CTL = const(36) #9
OFFSET_CTL = const(40) #10
SIN_CTL = const(44) #11
COS_CTL = const(48) #12
CAMERA_X_CTL = const(52) #13
CAMERA_Y_CTL = const(56) #14
CAMERA_Z_CTL = const(60) #15
CAMERA_R_CTL = const(64) #16
END_Y_CTL = const(68) #17
@micropython.asm_thumb
def fill_line_asm(r0,r1,r2):
mov(r7,r1) # r7 = q
str(r2,[r0,END_Y_CTL])
label(LOOP_Q) # height or y loop
ldr(r1,[r0,CAMERA_R_CTL]) # camera.rot
lsl(r1,r1,2) # mul x4 for 4 bytes
ldr(r2,[r0,COS_CTL]) # cos pointer
add(r2,r2,r1) # offset of rot
ldr(r2,[r2,0]) # r2 =cos[rot]
ldr(r3,[r0,SIN_CTL]) # sin pointer
add(r3,r3,r1) # offset of rot
ldr(r1,[r3,0]) # r1 =sin[rot]
push({r0,r1})
ldr(r0,[r0,CAMERA_Z_CTL])
mov(r1,r7)
bl(DIVIDE) # r3 n = camera.z // q
mov(r3,r0)
pop({r0,r1})
mov(r4,200)
lsl(r5,r4,16) # 200<<16
mul(r4,r3) # r4 s = 200 * n
cmp(r4,r5) # if (s > (200<<16)): continue
bgt(SKIP_Q)
mov(r5,120) # 240//2
mul(r5,r3) # r5 t2 = 120 * n
mul(r4,r1) # s * rsin
mov(r6,r5)
mul(r6,r2) # t2 * rcos
add(r6,r6,r4) # t2 * rcos + s * rsin
asr(r6,r6,10) # (t2 * rcos + s * rsin)>>SCALE
ldr(r5,[r0,CAMERA_X_CTL]) # camera.x
add(r6,r6,r5)
#str(r6,[r0,U_CTL]) # x or u = camera.x + (t2 * rcos + s * rsin)>>SCALE
ldr(r4,[r0,SIO_CTL]) # SIO address 0xd000_0080
str(r6,[r4,0]) # I0_ACCUM0 = u
str(r6,[r4,0x40]) # I1_ACCUM0 = u
mov(r4,200)
mul(r4,r3) # r4 s = 200 * n
mov(r5,120) # 240//2
mul(r5,r3) # r5 t2 = 120 * n
mul(r4,r2) # s * rcos
neg(r6,r5) # -t2
mul(r6,r1) # -t2 * rsin
add(r6,r6,r4) # -t2 * rsin + s * rcos
asr(r6,r6,10) # (-t2 * rsin + s * rcos)>>SCALE
ldr(r5,[r0,CAMERA_Y_CTL]) # camera.y
add(r6,r6,r5)
ldr(r4,[r0,SIO_CTL]) # SIO address 0xd000_0080
str(r6,[r4,4]) # I0_ACCUM1 = v
str(r6,[r4,0x44]) # I1_ACCUM1 = v
#str(r6,[r0,V_CTL]) # y or v = camera.y + ((-1*t2) * rsin + s * rcos)>>SCALE
neg(r6,r2) # -rcos
mul(r6,r3) # -rcos * n
asr(r6,r6,10) # (-rcos * n)>>SCALE
#str(r6,[r0,DU_CTL])
str(r6,[r4,8]) # I0_BASE0 = du
str(r6,[r4,0x48]) # I1_BASE0 = du
mul(r3,r1) # rsin * n
asr(r3,r3,10) # (rsin * n)>>SCALE
#str(r3,[r0,DV_CTL])
str(r3,[r4,0xc]) # I0_BASE1 = dv
str(r3,[r4,0x4c]) # I1_BASE1 = dv
mov(r6,240) # PicoDisplayWIDTH
mul(r6,r7) # q * PicoDisplayWIDTH
str(r6,[r0,OFFSET_CTL])
# -----------------------------------------------Fill Line--------
label(LINE)
ldr(r1,[r0,SIO_CTL]) # SIO address 0xd000_0080
# ldr(r2,[r0,U_CTL]) # load u
# str(r2,[r1,0]) # I0_ACCUM0 = u
# str(r2,[r1,0x40]) # I1_ACCUM0 = u
# ldr(r2,[r0,V_CTL]) # load v
# str(r2,[r1,4]) # I0_ACCUM1 = v
# str(r2,[r1,0x44]) # I1_ACCUM1 = v
# ldr(r2,[r0,DU_CTL]) # load du
# str(r2,[r1,8]) # I0_BASE0 = du
# str(r2,[r1,0x48]) # I1_BASE0 = du
# ldr(r2,[r0,DV_CTL]) # load dv
# str(r2,[r1,0xc]) # I0_BASE1 = dv
# str(r2,[r1,0x4c]) # I1_BASE1 = dv
mov(r2,0) # i = 0
label(LOOP_X)
ldr(r3,[r1,0x1c]) # I0_POP, t
ldr(r4,[r1,0x5c]) # I1_POP, c
ldr(r5,[r0,MAP_CTL]) # map address
add(r5,r5,r3)
ldrb(r5,[r5,0]) # map_ptr[t]
lsl(r5,r5,8) # map_ptr[t]<<8
add(r5,r5,r4) # c+(map_ptr[t]<<8)
ldr(r3,[r0,TILES_CTL]) # tiles_ptr[]
add(r3,r3,r5) #
ldrb(r3,[r3,0]) # tiles_ptr[c+(map_ptr[t]<<8)]
ldr(r4,[r0,COLORS_CTL]) # colors_ptr[]
add(r3,r3,r3) # double for ldrh
add(r4,r4,r3)
ldrh(r4,[r4,0]) # colors_ptr[tiles_ptr[c+(map_ptr[t]<<8)]]
ldr(r3,[r0,SCREEN_CTL]) # screen address
ldr(r5,[r0,OFFSET_CTL]) #
add(r5,r5,r2) # offset + i
add(r5,r5,r5) # double for strh
add(r3,r3,r5) # output[output_offset+i]
strh(r4,[r3,0])
add(r2,r2,1)
cmp(r2,240)
blt(LOOP_X)
label(SKIP_Q)
add(r7,r7,1)
ldr(r6,[r0,END_Y_CTL])
cmp(r7,r6)
blt(LOOP_Q)
b(EXIT)
# -------------------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)
#----------------end divide----------------------------------------
label(EXIT)
@micropython.viper
def build_car():
car = ptr8(tiles)
color = ptr16(colors)
car_text = ptr16(car_texture)
size = 4
width = 16 * size
height = 16 * size
offset = 16 * 16 * 11
for y in range(16):
for x in range(16):
car_pos = 16*y+x + offset
for i in range(4):
y1 = y * size + i
dest_pos = (16*size)*(y1)+x*size
#print(dest_pos)
car_text[dest_pos] = color[car[car_pos]]
#car_text[dest_pos+1] = color[car[car_pos]]
#car_text[dest_pos+2] = color[car[car_pos]]
#car_text[dest_pos+3] = color[car[car_pos]]
@micropython.viper
def mini_map()-> int:
screen = ptr16(buffer)
color = ptr16(colors2)
map_text = ptr8(map_texture)
cx = (int(camera.x)>>16) & 63
cy = (int(camera.y)>>16) & 63
if map_text[cy*64 + cx] != 4 and int(camera.off_track) == 0:
off_track = 1
else:
off_track = 0
if int(camera.speed)<2049:
off_track = 0
offset = 240 * 5 + 80
for y in range(64):
for x in range(64):
c = color[map_text[y*64+x]]
if c > 0:
screen[y*240+x+offset] = c
if cx + cy & 1:
dot = (5+cy)*240+cx+80
screen[dot] = 0xffff
screen[dot+1] = 0xffff
screen[dot-1] = 0xffff
screen[dot+241] = 0xffff
screen[dot-241] = 0xffff
return off_track
@micropython.viper
def draw_car():
screen = ptr16(buffer)
car_text = ptr16(car_texture)
size = 4
offset = 240 * 150 + 100
for y in range(16 * size):
for x in range(16 * size):
car_pos = y*(16*size)+x
if car_text[car_pos] > 0:
screen[y*240+x+offset] = car_text[car_pos]
@micropython.viper
def fill_buffer(w:int, h:int):
i_sin = ptr32(isin)
i_cos = ptr32(icos)
rot = int(camera.rot)
#interp_setup(map_width, map_height, 16, 4, 4)
rcos = i_cos[rot]
rsin = i_sin[rot]
control = ptr32(asm_ctl)
control[13] = int(camera.x)
control[14] = int(camera.y)
control[15] = int(camera.z)
control[16] = int(camera.rot)
fill_line_asm(asm_ctl)
#print(control[2],control[3],control[4],control[5])
return
for q in range(1,h):
n = int(camera.z) // q
#s = int(camera.screen_distance) * n
s = 200 * n
if (s > (200<<16)):
continue
t2 = 240//2 * n
x = int(camera.x) + (t2 * rcos + s * rsin)>>SCALE
y = int(camera.y) + ((-1*t2) * rsin + s * rcos)>>SCALE
du = (-1* rcos * n)>>SCALE
dv = (rsin * n)>>SCALE
#map_fill_line(q * PicoDisplayWIDTH, x, y, du, dv, w)
control[2] = x # u
control[3] = y # v
control[4] = du
control[5] = dv
control[10] = q * PicoDisplayWIDTH
fill_line_asm(asm_ctl)
#@micropython.viper
def buttons():
global RESET
if not joyUp.value():
camera.speed = int(camera.speed * (1.02*(1<<SCALE)))>>SCALE
# else:
# camera.speed = int(camera.speed * (0.98*(1<<SCALE)))>>SCALE
if not joyDown.value():
camera.speed = int(camera.speed * (0.98*(1<<SCALE)))>>SCALE
if camera.speed < 1.0*(1<<SCALE):
camera.speed = int(1.0*(1<<SCALE))
if not joyRight.value():
camera.rot -= (3 * camera.speed)>>11
if not joyLeft.value():
camera.rot += (3 * camera.speed)>>11
if not joySel.value():
if (camera.jump_count == 0):
pass
#camera.jump_count = 1
RESET += 1
if (camera.rot >= 360):
camera.rot -= 360
elif (camera.rot < 0):
camera.rot += 360
if camera.off_track:
camera.speed = int(camera.speed * (0.98*(1<<SCALE)))>>SCALE
@micropython.asm_thumb
def cls_asm(r0,r1,r2): # screen, number words, color
label(LOOP)
strh(r2,[r0,0])
add(r0,2)
sub(r1,1)
bne(LOOP)
label(EXIT)
def print_map():
for y in range(1<<map_height):
for x in range(1<<map_width):
print(map_texture[y*(1<<map_width)+x],end='')
print()
print(x,y)
def core1():
global SHOW, RESET, asm_ctl2
interp_setup(map_width, map_height, 16, 4, 4)
while(1):
if RESET > 50:
exit()
if SHOW:
asm_ctl2[13] = camera.x
asm_ctl2[14] = camera.y
asm_ctl2[15] = camera.z + (camera.off_track<<12)
asm_ctl2[16] = camera.rot
fill_line_asm(asm_ctl2,0,120)
camera.off_track = mini_map()
SHOW = False
spi = SPI(1, baudrate=80_000_000, sck=Pin(10), mosi=Pin(11))
tft = gc9a01.GC9A01(
spi,
240,
240,
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(0)
sleep(0.5)
init_isin()
init_icos()
gc.collect()
print(gc.mem_free())
asm_ctl1 = array.array('i',(addressof(buffer),0xd000_0080,8,12,16,20,24,addressof(colors),addressof(tiles),addressof(map_texture),40,
addressof(isin),addressof(icos),52,56,60,64,0))
asm_ctl2 = array.array('i',(addressof(buffer),0xd000_0080,8,12,16,20,24,addressof(colors),addressof(tiles),addressof(map_texture),40,
addressof(isin),addressof(icos),52,56,60,64,0))
interp_setup(map_width, map_height, 16, 4, 4)
RESET = 0
_thread.start_new_thread(core1, ())
#build_car()
while(1):
gticks=ticks_us()
if RESET > 50:
cls_asm(buffer,240*240,black)
tft.blit_buffer(buffer, 0, 0, PicoDisplayWIDTH,PicoDisplayHEIGHT)
exit()
cls_asm(buffer,240*240,sky_blue)
#fill_buffer(PicoDisplayWIDTH-1, PicoDisplayHEIGHT-1)
asm_ctl1[13] = camera.x
asm_ctl1[14] = camera.y
asm_ctl1[15] = camera.z + (camera.off_track<<12)
asm_ctl1[16] = camera.rot
SHOW = True
fill_line_asm(asm_ctl1,120,240)
#draw_car()
while SHOW:
pass
tft.blit_buffer(buffer, 0, 0, PicoDisplayWIDTH,PicoDisplayHEIGHT)
buttons()
step = ((camera.speed - (1<<SCALE))<<4)
camera.x += (step * isin[camera.rot])>>SCALE
camera.y += (step * icos[camera.rot])>>SCALE
if (camera.jump_count > 0):
if (camera.jump_count < 50):
camera.z += int(1 + (camera.jump_count / 5)) << 13
camera.jump_count += 1
elif (camera.jump_count < 99):
camera.z -= int(1 + ((camera.jump_count - 49) / 5)) << 13
camera.jump_count += 1
else:
camera.jump_count = 0
camera.z = 1<<16
RESET = 0
FPS=1_000_000//ticks_diff(ticks_us(),gticks)
#print(FPS)
#exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment