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()
@wayned2
Copy link

wayned2 commented Sep 10, 2023

Thanks for all your impressive contributions regarding Pico and LCD displays.
Obviously you are digging very deep into the architecture and accomplishing high performance.

Unfortunately I'm quite new to this and I am searching now for quite some time - without success - for a way to display bitmap files (.bmp or .jpg) on an ILI9488 device.
The thing which seemed closest is: https://www.instructables.com/RPi-Pico-35-Inch-320x480-HVGA-TFT-LCD-ILI9488-Bitm/
but this code simply does not lead to any success on my setup.

Could you please give a hint where to look?

Cheers, W.

@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