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() | |
That Instructable is very detailed and sounds like just what you need. What exactly is not working? Do you get any errors? First is the wiring, does the Pico plug into the display like a hat or is it discretely wired with jumpers? Did you solder your own headers?Sam On Sep 10, 2023, at 6:32 AM, wayned2 ***@***.***> wrote:Re: ***@***.*** commented on this gist.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.—Reply to this email directly, view it on GitHub or unsubscribe.You are receiving this email because you authored the thread.Triage notifications on the go with GitHub Mobile for iOS or Android.
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.
That line goes with machine.freq() When the clock is changed the SPI frequency also changes. This line separates the SPI which drives the LCD from the main clock. Otherwise the display will not work correctly. You if you overclock you may also have to lower the display SPI frequency. I’m not sure why as they should be independent with that line. SamOn Sep 11, 2023, at 12:40 PM, wayned2 ***@***.***> wrote:Re: ***@***.*** commented on this gist.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.—Reply to this email directly, view it on GitHub or unsubscribe.You are receiving this email because you authored the thread.Triage notifications on the go with GitHub Mobile for iOS or Android.
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
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.