Skip to content

Instantly share code, notes, and snippets.

@samneggs
Created September 8, 2023 04:02
Show Gist options
  • Save samneggs/803465980c61a60ef6dd084b00bbc683 to your computer and use it in GitHub Desktop.
Save samneggs/803465980c61a60ef6dd084b00bbc683 to your computer and use it in GitHub Desktop.
Plasma Effect in MicroPython and Assembly
# 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()
@samneggs
Copy link
Author

samneggs commented Sep 11, 2023 via email

@wayned2
Copy link

wayned2 commented Sep 11, 2023

Thanks a lot for your fast response.

First - strange enough - after working for 1 week on the project and finally asking for help now it is working and can display bitmaps.

What happened when I first tried the code from instructables:
No error messages, just a black screen.

With your code samples everything worked like a charm. (like breakout_3_5.py or LCD_3inch5.py)
So wiring was not the problem.

May I ask what this line (from LCD_3inch5.py) is intended to do ? :
"machine.mem32[0x40008048] = 1<<11 # enable peri_ctrl clock"

Thanks again.

@samneggs
Copy link
Author

samneggs commented Sep 11, 2023 via email

@wayned2
Copy link

wayned2 commented Sep 13, 2023

Thanks a lot again for these insights.

Good luck with your great projects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment