Created
September 4, 2022 15:03
-
-
Save samneggs/b31914ce52b0399ac53091be683f0ccc to your computer and use it in GitHub Desktop.
Racing game for PI Pico in MicroPython and Assembly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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