Created
September 8, 2023 04:02
-
-
Save samneggs/803465980c61a60ef6dd084b00bbc683 to your computer and use it in GitHub Desktop.
Plasma Effect 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
# plasma | |
from lcd_1_8 import LCD_1inch8 | |
import machine | |
from machine import Pin, PWM | |
from uctypes import addressof | |
from time import sleep, ticks_us, ticks_diff, ticks_ms | |
import gc, _thread, array | |
from sys import exit | |
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) | |
# PLAYER_PARAMS = const(10) | |
# X = const(0) | |
# Y = const(1) | |
# VX = const(2) | |
# VY = const(3) | |
# AX = const(4) | |
# AY = const(5) | |
DARK_GREY = const(0x4711) #pico_display.create_pen(20, 40, 60) | |
BLACK = const(0) #pico_display.create_pen(0, 0, 0) | |
DARK_GREEN = const(0x0024) #pico_display.create_pen(32, 128, 0) | |
LT_BLUE = const(0x3e44) #pico_display.create_pen(66, 133, 244) | |
LT_YELLOW = const(0x33ff) #pico_display.create_pen(255, 229, 153) | |
LT_GREEN = const(0xe007) #pico_display.create_pen(0, 255, 0) | |
BROWN = const(0xe079) #pico_display.create_pen(120, 63, 4) | |
WHITE = const(0xffff) #pico_display.create_pen(255, 255, 255) | |
SKY_BLUE = const(0x1f66) #pico_display.create_pen(96, 192, 255) | |
BLUE = const(0x1f00) | |
RED = const(0xf8) # rrrrr_gggggg_bbbbb | |
LT_BROWN = const(0x52de) # ggg_bbbbb_rrrrr_ggg | |
GAME_PARAMS = const(10) | |
FPS = const(0) | |
LIVES = const(1) | |
PATTERN = const(2) | |
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 | |
while num > 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_imath(): #integer math scaled 0-360 to 0-256 | |
global ISIN,ICOS | |
ISIN = array.array('i', int(sin(radians(i* 1.40625)) * (1 << SCALE)) for i in range(256)) | |
ICOS = array.array('i', int(cos(radians(i* 1.40625)) * (1 << SCALE)) for i in range(256)) | |
def init_pot(): | |
global POT_X,POT_Y,POT_X_ZERO,POT_Y_ZERO | |
POT_X = machine.ADC(27) | |
POT_Y = machine.ADC(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(): | |
game = ptr32(GAME) | |
pot_scale = 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 x_inc < 2 and x_inc > -2: x_inc=0 | |
if y_inc < 2 and y_inc > -2: y_inc=0 | |
if y_inc > 0 and game[PATTERN] < 10<<4: | |
game[PATTERN] += 1 | |
if y_inc < 0 and game[PATTERN] > 1: | |
game[PATTERN] -= 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 | |
GAME[PATTERN] = 10 << 4 | |
MAX_LUT = const(255) | |
@micropython.viper | |
def plasma_viper(): | |
game = ptr32(GAME) | |
screen = ptr16(LCD.buffer) # pointer to write to screen memory 160x128, rgb565 | |
isin = ptr32(ISIN) # pointer to sine table, 0-359 degrees returns sin(radius(deg))*(1<<13) | |
icos = ptr32(ICOS) # pointer to cosine table | |
pattern = game[PATTERN]>>4 | |
t = int(ticks_ms())>>5 # get the current time in milliseconds | |
for y in range(MAXSCREEN_Y): #128 | |
for x in range(MAXSCREEN_X): #160 | |
# calculate the red, green and blue components using fixed point math and look up tables | |
r = 15-((isin[(x + t) & MAX_LUT] + isin[(y + t) & MAX_LUT]) >> pattern) # shift right by 10 to get a value between 0 and 31 | |
g = 15-((isin[(x - t) & MAX_LUT] + icos[(y + t) & MAX_LUT]) >> pattern) | |
b = 15-((icos[(x + t) & MAX_LUT] + isin[(y - t) & MAX_LUT]) >> pattern) | |
if r<0: r *= -1 | |
if g<0: g *= -1 | |
if b<0: b *= -1 | |
color = (r<<11) | (g << 5) | b # combine the components into a 16-bit color value | |
color_swap = (color ^ (color << 16)) >> 8 # swap the bytes to match the screen format | |
screen[y * MAXSCREEN_X + x] = color_swap # write pixel | |
def init_plasma(): | |
global PLASMA_ARY | |
TIMERAWL = const(0x40054000+0x28) | |
PLASMA_ARY = array.array('I',(addressof(LCD.buffer),addressof(ISIN),addressof(ICOS),TIMERAWL,0,0,0,0)) | |
ASM_SCREEN = const(0) | |
ASM_ISIN = const(4) | |
ASM_ICOS = const(8) | |
ASM_TIMER = const(12) | |
ASM_RED = const(16) | |
ASM_BLUE = const(20) | |
ASM_GREEN = const(24) | |
@micropython.asm_thumb | |
def plasma_asm(r0): | |
ldr(r1,[r0,ASM_TIMER]) # r1 = t | |
ldr(r1,[r1,0]) | |
asr(r1,r1,15) | |
mov(r2,0) # r2 = y | |
label(LOOP_Y) | |
mov(r3,0) # r3 = x | |
label(LOOP_X) | |
# r = 15-((isin[(x + t) & MAX_LUT] + isin[(y + t) & MAX_LUT]) >> 10) | |
add(r4,r3,r1) # x + t | |
mov(r5,0xff) | |
and_(r4,r5) # (x + t) & MAX_LUT | |
ldr(r6,[r0,ASM_ISIN]) # isin[] | |
lsl(r4,r4,2) # x4 for 32-bit | |
add(r4,r4,r6) | |
ldr(r4,[r4,0]) # isin[(x + t) & MAX_LUT] | |
add(r7,r2,r1) # (y + t) | |
and_(r7,r5) # (y + t) & MAX_LUT] | |
lsl(r7,r7,2) # x4 for 32-bit | |
add(r7,r7,r6) # | |
ldr(r7,[r7,0]) | |
add(r4,r4,r7) | |
asr(r4,r4,10) # red | |
mov(r7,15) | |
sub(r4,r7,r4) | |
cmp(r4,0) # check if negative | |
bge(RED_POS) | |
neg(r4,r4) | |
label(RED_POS) | |
str(r4,[r0,ASM_RED]) | |
# g = 15-((isin[(x - t) & MAX_LUT] + icos[(y + t) & MAX_LUT]) >> 10) | |
sub(r4,r3,r1) # x - t | |
#mov(r5,0xff) | |
and_(r4,r5) # (x - t) & MAX_LUT | |
#ldr(r6,[r0,ASM_ISIN]) # isin[] | |
lsl(r4,r4,2) # x4 for 32-bit | |
add(r4,r4,r6) | |
ldr(r4,[r4,0]) # isin[(x - t) & MAX_LUT] | |
add(r7,r2,r1) # (y + t) | |
and_(r7,r5) # (y + t) & MAX_LUT] | |
ldr(r6,[r0,ASM_ICOS]) | |
lsl(r7,r7,2) # x4 for 32-bit | |
add(r7,r7,r6) # | |
ldr(r7,[r7,0]) | |
add(r4,r4,r7) | |
asr(r4,r4,10) # green | |
mov(r7,15) | |
sub(r4,r7,r4) | |
cmp(r4,0) # check if negative | |
bge(GREEN_POS) | |
neg(r4,r4) | |
label(GREEN_POS) | |
str(r4,[r0,ASM_GREEN]) | |
# b = 15-((icos[(x + t) & MAX_LUT] + isin[(y - t) & MAX_LUT]) >> 10) | |
add(r4,r3,r1) # x + t | |
#mov(r5,0xff) | |
and_(r4,r5) # (x + t) & MAX_LUT | |
#ldr(r6,[r0,ASM_ICOS]) # icos[] | |
lsl(r4,r4,2) # x4 for 32-bit | |
add(r4,r4,r6) | |
ldr(r4,[r4,0]) # icos[(x + t) & MAX_LUT] | |
sub(r7,r2,r1) # (y - t) | |
and_(r7,r5) # (y - t) & MAX_LUT] | |
ldr(r6,[r0,ASM_ISIN]) # isin[] | |
lsl(r7,r7,2) # x4 for 32-bit | |
add(r7,r7,r6) # | |
ldr(r7,[r7,0]) | |
add(r4,r4,r7) | |
asr(r4,r4,10) # blue | |
mov(r7,15) | |
sub(r4,r7,r4) | |
cmp(r4,0) # check if negative | |
bge(BLUE_POS) | |
neg(r4,r4) | |
label(BLUE_POS) | |
str(r4,[r0,ASM_BLUE]) | |
# color = (r<<11) | (g << 5) | b | |
ldr(r5,[r0,ASM_RED]) | |
lsl(r5,r5,11) | |
orr(r4,r5) | |
ldr(r5,[r0,ASM_GREEN]) | |
lsl(r5,r5,5) | |
orr(r4,r5) | |
data(2,0b1011_1010_01_100_100) # r4=swap color bytes | |
mov(r5,160) | |
mov(r6,r2) # y | |
mul(r6,r5) # y * 160 | |
add(r6,r6,r3) # y * 160 + x | |
add(r6,r6,r6) # x2 for 16-bit | |
ldr(r5,[r0,ASM_SCREEN]) # screen[] | |
add(r5,r5,r6) # | |
strh(r4,[r5,0]) # screen[y * 160 + x] = color | |
add(r3,r3,1) | |
cmp(r3,160) | |
blt(LOOP_X) | |
add(r2,r2,1) | |
cmp(r2,128) | |
blt(LOOP_Y) | |
label(EXIT) | |
@micropython.viper | |
def draw(): | |
g = ptr32(GAME) | |
LCD.text('FPS',0,0,0xff) | |
show_num_viper(g[FPS],20,7,0xff) | |
#show_num_viper(gc.mem_free(),40,17,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_imath() | |
init_plasma() | |
g = ptr32(GAME) | |
#_thread.start_new_thread(core1, ()) | |
while not EXIT and not RESET_PB(): | |
gticks = ticks_ms() | |
#sleep(0.001) | |
read_pot() | |
plasma_viper() | |
#plasma_asm(PLASMA_ARY) | |
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() | |
def core1(): | |
global PLASMA_ARY | |
print(plasma_asm(PLASMA_ARY)) | |
exit() | |
if __name__=='__main__': | |
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(10000) #1000 | |
pwm.duty_u16(0x8fff)#max 0xffff | |
LCD = LCD_1inch8() | |
LCD.fill(0) | |
LCD.show() | |
EXIT = False | |
try: | |
main() | |
shutdown() | |
except KeyboardInterrupt : | |
shutdown() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks a lot again for these insights.
Good luck with your great projects.