Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created September 14, 2024 06:34
Show Gist options
  • Save samneggs/b3ed8b1d1d5565b3d4e01078667e881d to your computer and use it in GitHub Desktop.
Save samneggs/b3ed8b1d1d5565b3d4e01078667e881d to your computer and use it in GitHub Desktop.
Mandelbrot, Julia and Metaballs Assembly Routines for the RP2350
# template
from lcd_1_8 import LCD_1inch8
import machine, math
from machine import Pin, PWM, SPI
from uctypes import addressof
from time import sleep, ticks_us, ticks_diff, ticks_ms
import gc, _thread, array
from micropython import const
from random import randint
from math import sin,cos,tan,radians,sqrt
from rp2 import bootsel_button as RESET_PB
MAXSCREEN_X = const(160)
MAXSCREEN_Y = const(128)
SCALE = const(13)
BLUE = const(0b000_11111_00000_000)
OVERCLOCK = const(1)
PLAYER_PARAMS = const(10)
X = const(0)
Y = const(1)
VX = const(2)
VY = const(3)
AX = const(4)
AY = const(5)
GAME_PARAMS = const(10)
FPS = const(0)
LIVES = const(1)
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)
@micropython.viper
def show_num_viper(num:int,x_offset:int,y_offset:int,color:int):
char_ptr = ptr8(char_map)
screen_ptr = ptr16(LCD.buffer)
size = 1 # 1,2,3
char = 0
offset = MAXSCREEN_X*y_offset+x_offset
first = 1
while num > 0 or first:
first = 0
total = num//10
digit = num - (total * 10)
num = total
for y in range(8):
row_data = char_ptr[digit*8+y]
for x in range(8):
if row_data & (1<<x) > 0:
addr = size*y*MAXSCREEN_X+x-(char*8)+offset
screen_ptr[addr] = color
if size>1:
screen_ptr[MAXSCREEN_X+addr] = color
if size>2:
screen_ptr[2*MAXSCREEN_X+addr] = color
char += 1
def init_pot():
global POT_X,POT_Y,POT_X_ZERO,POT_Y_ZERO
POT_X = machine.ADC(1) # 27
POT_Y = machine.ADC(0) # 26
POT_X_ZERO = 0
POT_Y_ZERO = 0
for i in range(1000):
POT_X_ZERO += POT_X.read_u16()
POT_Y_ZERO += POT_Y.read_u16()
POT_X_ZERO = POT_X_ZERO//1000
POT_Y_ZERO = POT_Y_ZERO//1000
pot_scale = 12
@micropython.viper
def read_pot():
player = ptr32(PLAYER)
pot_scale = 3 #12
x_inc = int(POT_X.read_u16() - POT_X_ZERO)>>pot_scale
y_inc = int(POT_Y.read_u16() - POT_Y_ZERO)>>pot_scale
if -2 < x_inc < 2:
x_inc=0
if -2 < y_inc < 2:
y_inc=0
if not FIRE_BUTTON.value(): pass
player[0] = x_inc
player[1] = y_inc
def init_player():
global PLAYER
PLAYER = array.array('i',0 for _ in range(PLAYER_PARAMS))
def init_game():
global GAME, FPS_ARRY
GAME = array.array('i',0 for _ in range(GAME_PARAMS))
FPS_ARRY = bytearray(35)
GAME[FPS] = 0
GAME[LIVES] = 3
@micropython.asm_thumb
def julia(r0, r1) -> int:
# r0: pointer to the JULIA array
# r1: pointer to the SCREEN array
# Load parameters from the array
vldr(s0, [r0, 0]) # MAXSCREEN_X
vldr(s1, [r0, 4]) # MAXSCREEN_Y
vldr(s2, [r0, 8]) # X_MIN
vldr(s3, [r0, 12]) # X_MAX
vldr(s4, [r0, 16]) # Y_MIN
vldr(s5, [r0, 20]) # Y_MAX
vldr(s6, [r0, 24]) # MAX_ITERATIONS
vldr(s7, [r0, 28]) # ESCAPE_RADIUS
vldr(s8, [r0, 32]) # C_REAL
vldr(s9, [r0, 36]) # C_IMAG
# Calculate dx and dy
vsub(s10, s3, s2) # X_MAX - X_MIN
vdiv(s10, s10, s0) # dx = (X_MAX - X_MIN) / MAXSCREEN_X
vsub(s11, s5, s4) # Y_MAX - Y_MIN
vdiv(s11, s11, s1) # dy = (Y_MAX - Y_MIN) / MAXSCREEN_Y
# Initialize pixel counters
mov(r2, 0) # x_pixel
mov(r3, 0) # y_pixel
mov(r4, 0) # SCREEN array index
# Main loop
label(outer_loop)
mov(r2, 0) # Reset x_pixel
label(inner_loop)
# Calculate initial z_real and z_imag
vmov(s12, r2)
vcvt_f32_s32(s12, s12)
vmul(s12, s12, s10)
vadd(s12, s12, s2) # z_real = X_MIN + x_pixel * dx
vmov(s13, r3)
vcvt_f32_s32(s13, s13)
vmul(s13, s13, s11)
vadd(s13, s13, s4) # z_imag = Y_MIN + y_pixel * dy
# Initialize iteration counter
mov(r5, 0) # iteration = 0
# Iteration loop
label(iteration_loop)
# Calculate z_real^2 and z_imag^2
vmul(s14, s12, s12) # z_real^2
vmul(s15, s13, s13) # z_imag^2
# Check if (z_real^2 + z_imag^2) > ESCAPE_RADIUS^2
vadd(s16, s14, s15)
vmul(s17, s7, s7) # ESCAPE_RADIUS^2
vcmp(s16, s17)
vmrs(APSR_nzcv, FPSCR)
bgt(escape)
# Calculate new z_imag
vmul(s13, s12, s13)
vadd(s13, s13, s13) # 2 * z_real * z_imag
vadd(s13, s13, s9) # + C_IMAG
# Calculate new z_real
vsub(s12, s14, s15) # z_real^2 - z_imag^2
vadd(s12, s12, s8) # + C_REAL
# Increment iteration counter
add(r5, r5, 1)
vmov(s18, r5)
vcvt_f32_s32(s18, s18)
vcmp(s18, s6)
vmrs(APSR_nzcv, FPSCR)
blt(iteration_loop)
label(escape)
# Write iteration count to SCREEN array
add(r6, r1, r4)
strb(r5, [r6, 0])
add(r4, r4, 1)
# Move to next pixel
add(r2, r2, 1)
vmov(s19, r2)
vcvt_f32_s32(s19, s19)
vcmp(s19, s0)
vmrs(APSR_nzcv, FPSCR)
blt(inner_loop)
# Move to next row
add(r3, r3, 1)
vmov(s20, r3)
vcvt_f32_s32(s20, s20)
vcmp(s20, s1)
vmrs(APSR_nzcv, FPSCR)
blt(outer_loop)
# Return the total number of pixels processed
mov(r0, r4)
@micropython.viper
def generate_mandelbrot_palette(palette: ptr16):
for i in range(256):
r: int = 0
g: int = 0
b: int = 0
if i < 32:
r = i * 8
elif i < 64:
r = 255
g = (i - 32) * 8
elif i < 96:
r = 255 - (i - 64) * 8
g = 255
elif i < 128:
g = 255
b = (i - 96) * 8
elif i < 160:
g = 255 - (i - 128) * 8
b = 255
elif i < 192:
r = (i - 160) * 8
b = 255
elif i < 224:
r = 255
g = (i - 192) * 8
b = 255
else:
r = 255
g = 255
b = 255 - (i - 224) * 8
# Convert RGB888 to RGB565 and swap bytes
color: int = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
palette[i] = ((color & 0xFF) << 8) | ((color >> 8) & 0xFF)
@micropython.viper
def colorize(screen1:ptr8, screen2:ptr16, palette:ptr16):
for i in range(MAXSCREEN_X * MAXSCREEN_Y):
color_255 = screen1[i]
screen2[i] = palette[color_255]
@micropython.viper
def draw():
game = ptr32(GAME)
player = ptr32(PLAYER)
colorize(SCREEN, LCD.buffer, PALETTE)
show_num_viper(game[FPS],120,0,0xff)
LCD.show()
LCD.rect(0,0,MAXSCREEN_X,MAXSCREEN_Y,0,1)
@micropython.asm_thumb
def avg_fps_asm(r0,r1): # r0 = fps[] , r1 = current_fps
ldrb(r2,[r0,0]) # r2 = fps[0]
add(r2,r2,1) # fps[0] += 1
cmp(r2,33)
blt(LT_32) # if fps[0] > 32:
mov(r2,1)
label(LT_32)
strb(r2,[r0,0]) # fps[0] = new index
add(r2,r2,r0)
strb(r1,[r2,0]) # fps[fps[0]] = current_fps
mov(r2,1) # r2 = i
mov(r3,0) # r3 = tot
label(LOOP)
add(r0,r0,1)
ldrb(r4,[r0,0]) # r4 = fps[i]
add(r3,r3,r4) # tot += fps[i]
add(r2,r2,1)
cmp(r2,33) #33
blt(LOOP)
asr(r0,r3,5)
def julia_loop():
step = 50 #50
for i in range(200):
step += 0.3
JULIA[2] = -1.5 + i/step # -2.0
JULIA[3] = 1.5 - i/step # 1.0
JULIA[4] = -1.5 + i/step # -1.5
JULIA[5] = 1.5 - i/step # 1.5
# Call the mandelbrot function
t = ticks_ms()
result = julia(JULIA, SCREEN)
diff = ticks_diff(ticks_ms(),t)+1
#print('frames per sec:',1000//diff)
#print(f"Processed {result} pixels")
colorize(SCREEN, LCD.buffer, PALETTE)
LCD.show()
sleep(0.001)
@micropython.viper
def main():
init_pot()
init_game()
init_player()
g = ptr32(GAME)
julia_loop()
while not EXIT: # and not RESET_PB():
gticks = ticks_ms()
result = julia(JULIA, SCREEN)
sleep(0.001)
#read_pot()
draw()
g[FPS] = int(avg_fps_asm(FPS_ARRY,1_000//int(ticks_diff(ticks_ms(),gticks))))
def shutdown():
global EXIT
EXIT = True
Pin(16,Pin.OUT).low() # buzzer off
pwm.deinit()
Pin(13,Pin.OUT).low() # screen off
gc.collect()
print(gc.mem_free())
print('Core0 Stop')
#exit()
if __name__=='__main__':
machine.freq(300_000_000)
#machine.mem32[0x40008048] = 1<<11 # enable peri_ctrl clock
SCREEN = bytearray(MAXSCREEN_X * MAXSCREEN_Y * 2)
JULIA = array.array('f', (MAXSCREEN_X, MAXSCREEN_Y, -1.5, 1.5, -1.5, 1.5, 100, 2.0, -0.4, 0.6))
PALETTE = array.array('H', [0] * 256)
generate_mandelbrot_palette(PALETTE)
COUNT = 0
FIRE_BUTTON = Pin(22, Pin.IN, Pin.PULL_UP)
pwm = PWM(Pin(13))
pwm.freq(1000)
pwm.duty_u16(0x8fff)#max 0xffff
LCD = LCD_1inch8()
LCD.fill(0)
LCD.show()
EXIT = False
#_thread.start_new_thread(core1, ())
main()
try:
main()
shutdown()
except KeyboardInterrupt :
shutdown()
# template
from lcd_1_8 import LCD_1inch8
import machine, math
from machine import Pin, PWM, SPI
from uctypes import addressof
from time import sleep, ticks_us, ticks_diff, ticks_ms
import gc, _thread, array
from micropython import const
from random import randint
from math import sin,cos,tan,radians,sqrt
from rp2 import bootsel_button as RESET_PB
MAXSCREEN_X = const(160)
MAXSCREEN_Y = const(128)
SCALE = const(13)
BLUE = const(0b000_11111_00000_000)
OVERCLOCK = const(1)
PLAYER_PARAMS = const(10)
X = const(0)
Y = const(1)
VX = const(2)
VY = const(3)
AX = const(4)
AY = const(5)
GAME_PARAMS = const(10)
FPS = const(0)
LIVES = const(1)
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)
@micropython.viper
def show_num_viper(num:int,x_offset:int,y_offset:int,color:int):
char_ptr = ptr8(char_map)
screen_ptr = ptr16(LCD.buffer)
size = 1 # 1,2,3
char = 0
offset = MAXSCREEN_X*y_offset+x_offset
first = 1
while num > 0 or first:
first = 0
total = num//10
digit = num - (total * 10)
num = total
for y in range(8):
row_data = char_ptr[digit*8+y]
for x in range(8):
if row_data & (1<<x) > 0:
addr = size*y*MAXSCREEN_X+x-(char*8)+offset
screen_ptr[addr] = color
if size>1:
screen_ptr[MAXSCREEN_X+addr] = color
if size>2:
screen_ptr[2*MAXSCREEN_X+addr] = color
char += 1
def init_pot():
global POT_X,POT_Y,POT_X_ZERO,POT_Y_ZERO
POT_X = machine.ADC(1) # 27
POT_Y = machine.ADC(0) # 26
POT_X_ZERO = 0
POT_Y_ZERO = 0
for i in range(1000):
POT_X_ZERO += POT_X.read_u16()
POT_Y_ZERO += POT_Y.read_u16()
POT_X_ZERO = POT_X_ZERO//1000
POT_Y_ZERO = POT_Y_ZERO//1000
pot_scale = 12
@micropython.viper
def read_pot():
player = ptr32(PLAYER)
pot_scale = 3 #12
x_inc = int(POT_X.read_u16() - POT_X_ZERO)>>pot_scale
y_inc = int(POT_Y.read_u16() - POT_Y_ZERO)>>pot_scale
if -2 < x_inc < 2:
x_inc=0
if -2 < y_inc < 2:
y_inc=0
if not FIRE_BUTTON.value(): pass
player[0] = x_inc
player[1] = y_inc
def init_player():
global PLAYER
PLAYER = array.array('i',0 for _ in range(PLAYER_PARAMS))
def init_game():
global GAME, FPS_ARRY
GAME = array.array('i',0 for _ in range(GAME_PARAMS))
FPS_ARRY = bytearray(35)
GAME[FPS] = 0
GAME[LIVES] = 3
def mand_loop():
step = 10 #50
for i in range(200):
step += 0.3
MAND[2] = -2.5 + i/step # -2.0
MAND[3] = 1.0 - i/step # 1.0
MAND[4] = -2 + i/step # -1.5
MAND[5] = 1.5 - i/step # 1.5
# Call the mandelbrot function
t = ticks_ms()
result = mandelbrot(MAND, SCREEN)
diff = ticks_diff(ticks_ms(),t)+1
#print('frames per sec:',1000//diff)
#print(f"Processed {result} pixels")
colorize(SCREEN, LCD.buffer, PALETTE)
LCD.show()
sleep(0.001)
@micropython.asm_thumb
def mandelbrot(r0, r1) -> int:
# r0: pointer to the MAND array
# r1: pointer to the SCREEN array
# Load parameters from the array
vldr(s0, [r0, 0]) # MAXSCREEN_X
vldr(s1, [r0, 4]) # MAXSCREEN_Y
vldr(s2, [r0, 8]) # X_MIN
vldr(s3, [r0, 12]) # X_MAX
vldr(s4, [r0, 16]) # Y_MIN
vldr(s5, [r0, 20]) # Y_MAX
vldr(s6, [r0, 24]) # MAX_ITERATIONS
vldr(s7, [r0, 28]) # ESCAPE_RADIUS
# Calculate dx and dy
vsub(s8, s3, s2) # X_MAX - X_MIN
vdiv(s8, s8, s0) # dx = (X_MAX - X_MIN) / MAXSCREEN_X
vsub(s9, s5, s4) # Y_MAX - Y_MIN
vdiv(s9, s9, s1) # dy = (Y_MAX - Y_MIN) / MAXSCREEN_Y
# Initialize pixel counters
mov(r2, 0) # x_pixel
mov(r3, 0) # y_pixel
mov(r4, 0) # SCREEN array index
# Main loop
label(outer_loop)
mov(r2, 0) # Reset x_pixel
label(inner_loop)
# Calculate c_real and c_imag
vmov(s10, r2)
vcvt_f32_s32(s10, s10)
vmul(s10, s10, s8)
vadd(s10, s10, s2) # c_real = X_MIN + x_pixel * dx
vmov(s11, r3)
vcvt_f32_s32(s11, s11)
vmul(s11, s11, s9)
vadd(s11, s11, s4) # c_imag = Y_MIN + y_pixel * dy
# Initialize iteration counter and z values
mov(r5, 0) # iteration = 0
vmov(s12, r5) # z_real = 0
vmov(s13, r5) # z_imag = 0
# Iteration loop
label(iteration_loop)
# Calculate z_real^2 and z_imag^2
vmul(s14, s12, s12) # z_real^2
vmul(s15, s13, s13) # z_imag^2
# Check if (z_real^2 + z_imag^2) > ESCAPE_RADIUS^2
vadd(s16, s14, s15)
vmul(s17, s7, s7) # ESCAPE_RADIUS^2
vcmp(s16, s17)
vmrs(APSR_nzcv, FPSCR)
bgt(escape)
# Calculate new z_imag
vmul(s13, s12, s13)
vadd(s13, s13, s13) # 2 * z_real * z_imag
vadd(s13, s13, s11) # + c_imag
# Calculate new z_real
vsub(s12, s14, s15) # z_real^2 - z_imag^2
vadd(s12, s12, s10) # + c_real
# Increment iteration counter
add(r5, r5, 1)
vmov(s18, r5)
vcvt_f32_s32(s18, s18)
vcmp(s18, s6)
vmrs(APSR_nzcv, FPSCR)
blt(iteration_loop)
label(escape)
# Write iteration count to SCREEN array
add(r6,r1,r4)
strb(r5, [r6, 0])
add(r4, r4, 1)
# Move to next pixel
add(r2, r2, 1)
vmov(s21, r2)
vcvt_f32_s32(s19, s21)
vcmp(s19, s0)
vmrs(APSR_nzcv, FPSCR)
blt(inner_loop)
# Move to next row
add(r3, r3, 1)
vmov(s22, r3)
vcvt_f32_s32(s20, s22)
vcmp(s20, s1)
vmrs(APSR_nzcv, FPSCR)
blt(outer_loop)
# Return the total number of pixels processed
mov(r0, r4)
@micropython.viper
def generate_mandelbrot_palette(palette: ptr16):
for i in range(256):
r: int = 0
g: int = 0
b: int = 0
if i < 32:
r = i * 8
elif i < 64:
r = 255
g = (i - 32) * 8
elif i < 96:
r = 255 - (i - 64) * 8
g = 255
elif i < 128:
g = 255
b = (i - 96) * 8
elif i < 160:
g = 255 - (i - 128) * 8
b = 255
elif i < 192:
r = (i - 160) * 8
b = 255
elif i < 224:
r = 255
g = (i - 192) * 8
b = 255
# else:
# r = 255
# g = 255
# b = 255 - (i - 224) * 8
# Convert RGB888 to RGB565 and swap bytes
color: int = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
palette[i] = ((color & 0xFF) << 8) | ((color >> 8) & 0xFF)
@micropython.viper
def colorize(screen1:ptr8, screen2:ptr16, palette:ptr16):
for i in range(MAXSCREEN_X * MAXSCREEN_Y):
color_255 = screen1[i]
screen2[i] = palette[color_255]
@micropython.viper
def draw():
game = ptr32(GAME)
player = ptr32(PLAYER)
show_num_viper(game[FPS],120,0,0xff)
LCD.show()
LCD.rect(0,0,MAXSCREEN_X,MAXSCREEN_Y,0,1)
@micropython.asm_thumb
def avg_fps_asm(r0,r1): # r0 = fps[] , r1 = current_fps
ldrb(r2,[r0,0]) # r2 = fps[0]
add(r2,r2,1) # fps[0] += 1
cmp(r2,33)
blt(LT_32) # if fps[0] > 32:
mov(r2,1)
label(LT_32)
strb(r2,[r0,0]) # fps[0] = new index
add(r2,r2,r0)
strb(r1,[r2,0]) # fps[fps[0]] = current_fps
mov(r2,1) # r2 = i
mov(r3,0) # r3 = tot
label(LOOP)
add(r0,r0,1)
ldrb(r4,[r0,0]) # r4 = fps[i]
add(r3,r3,r4) # tot += fps[i]
add(r2,r2,1)
cmp(r2,33) #33
blt(LOOP)
asr(r0,r3,5)
@micropython.viper
def main():
init_pot()
init_game()
init_player()
g = ptr32(GAME)
mand_loop()
while not EXIT: # and not RESET_PB():
gticks = ticks_ms()
sleep(0.001)
#read_pot()
draw()
g[FPS] = int(avg_fps_asm(FPS_ARRY,1_000//int(ticks_diff(ticks_ms(),gticks))))
def shutdown():
global EXIT
EXIT = True
Pin(16,Pin.OUT).low() # buzzer off
pwm.deinit()
Pin(13,Pin.OUT).low() # screen off
gc.collect()
print(gc.mem_free())
print('Core0 Stop')
if __name__=='__main__':
MAND = array.array('f', (MAXSCREEN_X, MAXSCREEN_Y, -2.0, 1.0, -1.5, 1.5, 255, 2.0)) #100, 2.0
PALETTE = array.array('H', [0] * 256)
generate_mandelbrot_palette(PALETTE)
SCREEN = bytearray(160 * 128 * 2)
COUNT = 0
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
#_thread.start_new_thread(core1, ())
try:
main()
shutdown()
except KeyboardInterrupt :
shutdown()
# template
from lcd_1_8 import LCD_1inch8
import machine, math
from machine import Pin, PWM, SPI
from uctypes import addressof
from time import sleep, ticks_us, ticks_diff, ticks_ms
import gc, _thread, array
from micropython import const
from random import randint, random
from math import sin,cos,tan,radians,sqrt
from rp2 import bootsel_button as RESET_PB
MAXSCREEN_X = const(160)
MAXSCREEN_Y = const(128)
SCALE = const(13)
BLUE = const(0b000_11111_00000_000)
OVERCLOCK = const(1)
NUM_BALLS = const(20)
BALL_PARAMS = const(10)
X = const(0)
Y = const(1)
DX = const(2)
DY = const(3)
GAME_PARAMS = const(10)
FPS = const(0)
LIVES = const(1)
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)
@micropython.viper
def show_num_viper(num:int,x_offset:int,y_offset:int,color:int):
char_ptr = ptr8(char_map)
screen_ptr = ptr16(LCD.buffer)
size = 1 # 1,2,3
char = 0
offset = MAXSCREEN_X*y_offset+x_offset
first = 1
while num > 0 or first:
first = 0
total = num//10
digit = num - (total * 10)
num = total
for y in range(8):
row_data = char_ptr[digit*8+y]
for x in range(8):
if row_data & (1<<x) > 0:
addr = size*y*MAXSCREEN_X+x-(char*8)+offset
screen_ptr[addr] = color
if size>1:
screen_ptr[MAXSCREEN_X+addr] = color
if size>2:
screen_ptr[2*MAXSCREEN_X+addr] = color
char += 1
def init_pot():
global POT_X,POT_Y,POT_X_ZERO,POT_Y_ZERO
POT_X = machine.ADC(1) # 27
POT_Y = machine.ADC(0) # 26
POT_X_ZERO = 0
POT_Y_ZERO = 0
for i in range(1000):
POT_X_ZERO += POT_X.read_u16()
POT_Y_ZERO += POT_Y.read_u16()
POT_X_ZERO = POT_X_ZERO//1000
POT_Y_ZERO = POT_Y_ZERO//1000
pot_scale = 12
@micropython.viper
def read_pot():
player = ptr32(PLAYER)
pot_scale = 3 #12
x_inc = int(POT_X.read_u16() - POT_X_ZERO)>>pot_scale
y_inc = int(POT_Y.read_u16() - POT_Y_ZERO)>>pot_scale
if -2 < x_inc < 2:
x_inc=0
if -2 < y_inc < 2:
y_inc=0
if not FIRE_BUTTON.value(): pass
player[0] = x_inc
player[1] = y_inc
def init_ball():
global BALL
BALL = array.array('i',0 for _ in range(NUM_BALLS * BALL_PARAMS))
for index in range(NUM_BALLS):
i = index * BALL_PARAMS
BALL[X+i] = randint(0,30)
BALL[Y+i] = randint(0,15)
BALL[DX+i] = randint(0, 1) * 2 - 1
BALL[DY+i] = randint(0, 1) * 2 - 1
def init_game():
global GAME, FPS_ARRY
GAME = array.array('i',0 for _ in range(GAME_PARAMS))
FPS_ARRY = bytearray(35)
GAME[FPS] = 0
GAME[LIVES] = 3
def update_balls():
balls = NUM_BALLS
for index in range(balls):
i = index * BALL_PARAMS
# Update position
BALL[X+i] += BALL[DX+i]
BALL[Y+i] += BALL[DY+i]
# Bounce off edges
if BALL[X+i] <= -10 or BALL[X+i] >= 40:
BALL[DX+i] *= -1
if BALL[Y+i] <= -1 or BALL[Y+i] >= 20:
BALL[DY+i] *= -1
#print(BALL[DX+i],BALL[DY+i],end=' ')
# Update METABALLS array
METABALLS[8+index*3] = BALL[X+i] / 30 * 2 - 1 # Convert to range -1 to 1
METABALLS[8+index*3 + 1] = BALL[Y+i] / 20 * 2 - 1 # Convert to range -1 to 1
# Keep the z-coordinate (size) constant at 1.5
@micropython.viper
def generate_mandelbrot_palette(palette: ptr16):
for i in range(256):
r: int = 0
g: int = 0
b: int = 0
if i < 32:
r = i * 8
elif i < 64:
r = 255
g = (i - 32) * 8
elif i < 96:
r = 255 - (i - 64) * 8
g = 255
elif i < 128:
g = 255
b = (i - 96) * 8
elif i < 160:
g = 255 - (i - 128) * 8
b = 255
elif i < 192:
r = (i - 160) * 8
b = 255
elif i < 224:
r = 255
g = (i - 192) * 8
b = 255
else:
r = 255
g = 255
b = 255 - (i - 224) * 8
# Convert RGB888 to RGB565 and swap bytes
color: int = ((r & 0xf8) << 8) | ((g & 0xFC) << 3) | (b >> 3) # 0xf8, 0xfc
palette[i] = ((color & 0xFF) << 8) | ((color >> 8) & 0xFF)
@micropython.asm_thumb
def metaballs(r0, r1) -> int:
# r0: pointer to the METABALLS array (containing configuration)
# r1: pointer to the SCREEN array (output)
# Load parameters from the array
vldr(s0, [r0, 0]) # MAXSCREEN_X
vldr(s1, [r0, 4]) # MAXSCREEN_Y
vldr(s2, [r0, 8]) # X_MIN
vldr(s3, [r0, 12]) # X_MAX
vldr(s4, [r0, 16]) # Y_MIN
vldr(s5, [r0, 20]) # Y_MAX
vldr(s6, [r0, 24]) # NUM_BLOBS
vldr(s7, [r0, 28]) # THRESHOLD
# Convert NUM_BLOBS to integer and store in r7
vcvt_s32_f32(s31, s6)
vmov(r7, s31)
# Calculate dx and dy
vsub(s8, s3, s2) # X_MAX - X_MIN
vdiv(s8, s8, s0) # dx = (X_MAX - X_MIN) / MAXSCREEN_X
vsub(s9, s5, s4) # Y_MAX - Y_MIN
vdiv(s9, s9, s1) # dy = (Y_MAX - Y_MIN) / MAXSCREEN_Y
# Initialize pixel counters
mov(r2, 0) # x_pixel
mov(r3, 0) # y_pixel
mov(r4, 0) # SCREEN array index
# Main loop
label(outer_loop)
mov(r2, 0) # Reset x_pixel
label(inner_loop)
# Calculate real x and y coordinates
vmov(s10, r2)
vcvt_f32_s32(s10, s10)
vmul(s10, s10, s8)
vadd(s10, s10, s2) # x = X_MIN + x_pixel * dx
vmov(s11, r3)
vcvt_f32_s32(s11, s11)
vmul(s11, s11, s9)
vadd(s11, s11, s4) # y = Y_MIN + y_pixel * dy
# Loop through all blobs
mov(r5, 0) # blob_index
#add(r6, r0, 32) # Start of blob data in METABALLS array
mov(r6,r0)
add(r6,32)
# Initialize sum of blob influences
vmov(s12, r5) # sum = 0
label(blob_loop)
# Load blob parameters
vldr(s13, [r6, 0]) # blob_x
vldr(s14, [r6, 4]) # blob_y
vldr(s15, [r6, 8]) # blob_radius
# Calculate distance squared
vsub(s16, s10, s13)
vmul(s16, s16, s16) # (x - blob_x)^2
vsub(s17, s11, s14)
vmul(s17, s17, s17) # (y - blob_y)^2
vadd(s16, s16, s17) # distance_squared
# Calculate blob influence
vdiv(s17, s15, s16) # radius^2 / distance_squared
vadd(s12, s12, s17) # sum += influence
# Move to next blob
add(r5, r5, 1)
add(r6, 12) # Move to next blob data (3 floats, 4 bytes each)
cmp(r5, r7)
blt(blob_loop)
# Compare sum to threshold
vcmp(s12, s7)
vmrs(APSR_nzcv, FPSCR)
blt(below_threshold)
# Above threshold: set pixel value based on intensity
vmul(s12, s12, s7) # Amplify the effect
vcvt_s32_f32(s12, s12)
vmov(r6, s12)
mov(r5, 63)
cmp(r6, r5)
bls(write_pixel)
mov(r6, 63) # Cap at 63 for ASCII rendering
b(write_pixel)
label(below_threshold)
mov(r6, 0) # Set pixel to 0 if below threshold
label(write_pixel)
# Write value to SCREEN array
push({r4})
add(r4, r4, r1)
#strb(r6, [r1, r4])
strb(r6, [r4, 0])
pop({r4})
add(r4, r4, 1)
# Move to next pixel
add(r2, r2, 1)
vmov(s19, r2)
vcvt_f32_s32(s19, s19)
vcmp(s19, s0)
vmrs(APSR_nzcv, FPSCR)
blt(inner_loop)
# Move to next row
add(r3, r3, 1)
vmov(s20, r3)
vcvt_f32_s32(s20, s20)
vcmp(s20, s1)
vmrs(APSR_nzcv, FPSCR)
blt(outer_loop)
# Return the total number of pixels processed
mov(r0, r4)
def setup_metaballs():
# Configuration: MAXSCREEN_X, MAXSCREEN_Y, X_MIN, X_MAX, Y_MIN, Y_MAX, NUM_BLOBS, THRESHOLD
#config = [MAXSCREEN_X, MAXSCREEN_Y, -2.0, 2.0, -1.0, 1.0, 3, 0.3]
config = [MAXSCREEN_X, MAXSCREEN_Y, -2.0, 2.0, -1.0, 1.0, NUM_BALLS, 1.9] # 1.9
# Blob data: x, y, radius for each blob
blobs = []
for i in range(NUM_BALLS):
blobs.append(random()*1)
blobs.append(random()*1)
blobs.append(random()*2)
METABALLS = array.array('f', config + blobs)
return METABALLS
@micropython.viper
def colorize(screen1:ptr8, screen2:ptr16, palette:ptr16):
for i in range(MAXSCREEN_X * MAXSCREEN_Y):
color_255 = screen1[i]
screen2[i] = palette[color_255]
@micropython.viper
def draw():
game = ptr32(GAME)
result = metaballs(METABALLS, SCREEN)
colorize(SCREEN,LCD.buffer,PALETTE)
#show_num_viper(game[FPS],120,0,0xff)
LCD.show()
LCD.rect(0,0,MAXSCREEN_X,MAXSCREEN_Y,0,1)
@micropython.asm_thumb
def avg_fps_asm(r0,r1): # r0 = fps[] , r1 = current_fps
ldrb(r2,[r0,0]) # r2 = fps[0]
add(r2,r2,1) # fps[0] += 1
cmp(r2,33)
blt(LT_32) # if fps[0] > 32:
mov(r2,1)
label(LT_32)
strb(r2,[r0,0]) # fps[0] = new index
add(r2,r2,r0)
strb(r1,[r2,0]) # fps[fps[0]] = current_fps
mov(r2,1) # r2 = i
mov(r3,0) # r3 = tot
label(LOOP)
add(r0,r0,1)
ldrb(r4,[r0,0]) # r4 = fps[i]
add(r3,r3,r4) # tot += fps[i]
add(r2,r2,1)
cmp(r2,33) #33
blt(LOOP)
asr(r0,r3,5)
@micropython.viper
def main():
init_pot()
init_game()
init_ball()
g = ptr32(GAME)
while not EXIT: # and not RESET_PB():
gticks = ticks_ms()
sleep(0.001)
update_balls()
draw()
g[FPS] = int(avg_fps_asm(FPS_ARRY,1_000//int(ticks_diff(ticks_ms(),gticks))))
def shutdown():
global EXIT
EXIT = True
Pin(16,Pin.OUT).low() # buzzer off
pwm.deinit()
Pin(13,Pin.OUT).low() # screen off
gc.collect()
print(gc.mem_free())
print('Core0 Stop')
#exit()
if __name__=='__main__':
METABALLS = setup_metaballs()
PALETTE = array.array('H', [0] * 256)
SCREEN = bytearray(MAXSCREEN_X * MAXSCREEN_Y)
generate_mandelbrot_palette(PALETTE)
COUNT = 0
FIRE_BUTTON = Pin(22, Pin.IN, Pin.PULL_UP)
machine.freq(300_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
#_thread.start_new_thread(core1, ())
main()
try:
main()
shutdown()
except KeyboardInterrupt :
shutdown()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment