Created
September 14, 2024 06:34
-
-
Save samneggs/b3ed8b1d1d5565b3d4e01078667e881d to your computer and use it in GitHub Desktop.
Mandelbrot, Julia and Metaballs Assembly Routines for the RP2350
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
# 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() | |
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
# 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() | |
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
# 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