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 hidden or 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