Struggling with my Plasma 2040

I’m a real rookie and I’ve had a good look through the Support Forum posts for similar problems without finding any, so I hope I have only made a simple error!
First thing to say is that I have copied the following .uf2 file to the Plasma 2040:
pico-v1.23.0-1-pimoroni-micropython.uf2. If I’m lucky, that was the wrong one and installing the correct one will solve all my problems.
I’ve installed Thonny and have selected MicroPython (Raspberry Pi Pico) - Board CDC @ COMx (and not RP2040- the Pi Pico is specified in the Getting Started with Plasma 2040 tutorial).
My problems are either:

  • Lock-up on loading code to the Plasma 2040 or

  • NotImplementedError: opcode

These errors generally relate to me adding debugging print() statements, typically one at the end of my code.
Any help or suggestions gratefully received- if the installation of the .uf2 file and selection of the board are both correct, I’ll make a further post with code and results.

Selecting
MicroPython (Raspberry Pi Pico) - Board CDC @ COMx
or
MicroPython (RP2040) - Board CDC @ COMx
should both work.
I can pick either one with my Plasma Stick and run my code without issues. And that should be the correct uf2 file. It’s the PicoW uf2 for me as mine has a PicoW Onboard.
Posting your code may help. Use the Preformated text button, the </>. It will wrape your code in code tags. That will retain all the indents etc.

Hello, Alphanumeric- thank you for your reply- good to know that I’ve got the basics right. Because I want to drive 150 or 300 LEDs I’m using the current sensing capabilities of the Plasma 2040 to limit the current drawn by adjusting the brightness. Here’s my code:

import plasma
from plasma import plasma2040
from time import sleep
import gc
import micropython
#import machine

# Import helper for Analog
from pimoroni import Analog

NUM_LEDS = 300
FPS = 60
speed = 10
sleep_time = 0.1  # was: 0.25
offset = 0.0
brightness = 0.5
saturation = 1
num_reps = 100     # Number of times to repeat function
debug = False

# Maximum current
MAX_CURRENT = 1.5

# Define function to display current consumption
sense = Analog(plasma2040.CURRENT_SENSE, plasma2040.ADC_GAIN, plasma2040.SHUNT_RESISTOR)

led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT, rgbw=True)
led_strip.start(FPS)

NUM_PIXELS = int(NUM_LEDS / 10)

# Function to check current and adjust brightness accordingly
def check_current(bright, max_current):
    current = sense.read_current()

    if current <= max_current and brightness <= 0.99:
        bright += 0.01
    elif current > max_current:
        bright -= 0.1

    if  debug == True:
        print("Brightness =", bright, "Current (A) =", current, "A")

    return bright


while True:
    for i in range(NUM_LEDS):
        for j in range(NUM_PIXELS):
            pix_num = (i + (j * 10)) % NUM_LEDS
            hue = (float(i) / NUM_LEDS)

            # Adjust hue and set HSV values
            offset = (offset + float(speed) / 20000.0) % 1
            
            for k in range(5):
                led_strip.set_hsv(pix_num + (k - 5), (hue + offset) % 1, saturation, brightness)
            
            # Turn off trailing pixel
            trailer = ((pix_num + NUM_LEDS) - 6) % NUM_LEDS
            led_strip.set_hsv(trailer, 0, 0, 0)
            gc.collect()

        brightness = check_current(brightness, 2)

        sleep(sleep_time)
        gc.collect()
        
    # Report on storage/memory usage

    #mem_use = gc.mem_alloc()
    #mem_free = gc.mem_free()
    #print("Heap allocated to Python:", mem_use, ", available:", mem_free, "(bytes)")
    
    #print("Reversing...")

    # Reverse direction
    for i in range(NUM_LEDS):
        for j in range(NUM_PIXELS):
            pix_num = (i + (j * 10)) % NUM_LEDS
            hue = (float(i) / NUM_LEDS)

            # Adjust hue and set HSV values
            offset = (offset + float(speed) / 20000.0) % 1

            for k in range(5):
                led_strip.set_hsv(NUM_LEDS - (pix_num + k), (hue + offset) % 1, saturation, brightness)

            # Turn off trailing pixel
            trailer = ((2 * NUM_LEDS - (pix_num - 1))) % NUM_LEDS
            led_strip.set_hsv(trailer, 0, 0, 0)

        brightness = check_current(brightness, 2)

        sleep(sleep_time)
        gc.collect()

    # Report on storage/memory usage
    #print("Reversing back")
    mem_use1 = gc.mem_alloc()
    mem_free1 = gc.mem_free()
    
    print("Heap allocated to Python:", mem_use1, ", available:", mem_free1, "(bytes)")
    micropython.mem_info(1)
    print("Reversing back...")

On running the code from Thommy the LEDs illuminate as per the first iteration only and these stay on. The shell responds thus when I attempt to stop the backend:

Unable to connect to COM5: could not open port 'COM5': PermissionError(13, 'Access is denied.', None, 5)

If you have serial connection to the device from another program, then disconnect it there first.

Process ended with exit code 1.

If I comment the final print():

    # Report on storage/memory usage
    #print("Reversing back")
    mem_use1 = gc.mem_alloc()
    mem_free1 = gc.mem_free()
    
    print("Heap allocated to Python:", mem_use1, ", available:", mem_free1, "(bytes)")
    micropython.mem_info(1)
    #print("Reversing back...")

The code will run fine.

If I comment the block of code setting and reporting the allocated and free heap (and leave the final print() commented:

    # Report on storage/memory usage
    #print("Reversing back")
    #mem_use1 = gc.mem_alloc()
    #mem_free1 = gc.mem_free()
    
    #print("Heap allocated to Python:", mem_use1, ", available:", mem_free1, "(bytes)")
    micropython.mem_info(1)
    #print("Reversing back...")

I get the following:

stack: 556 out of 7936
GC: total: 228608, used: 8992, free: 219616
 No. of 1-blocks: 91, 2-blocks: 22, max blk sz: 75, max free sz: 13257
GC memory layout; from 20008300:
00000000: h=MLhh..Dhh....Dh..DBDBh===BFB=.h====B=BBBBB.B=B.B=BBB.B=.B.B=Bh
00000400: ===DB=h===========h===================BBB.MDh=.h========h=======
00000800: ==========h=====================================================
00000c00: ==========h=====================================================
00001000: ==========h=hhhB...BDhDhh.hB..hDhDh.DB..B=.DBh==.DB.B=h=.h===...
00001400: .B..B=.BBB=Fh=======h========.DBh===BFFFh===..B=Bhh==h===h======
00001800: =====h===D.Bh=.FB=h===D.BF.h=B=h===FShSh.DhBBBB.DBh===h===.B=BBB
00001c00: h=.h===h===h===......................................h==========
00002000: ========...............................h====....................
00002400: ...........h========............................................
00002800: ........................h===================================h===
00002c00: ================================================================
00003000: =======.........................................................
       (3 lines all free)
00004000: .F....F.........................................................
       (206 lines all free)
00037c00: ................
NotImplementedError: opcode

In this case, stopping the backend from Thommy works OK with the response MPY: soft reboot.

The gc.collect() together with reporting on heap and the micropython.mem_info(1) were inserted because I wondered if my problems were associated with memory exhaustion or fragmentation. You’ll see that the code is peppered with commented print() statements- typically, uncommenting some of them will result in any of the three cases above.
In my case, “a little knowledge is a dangerous thing” and I’m hoping that my error(s) will be obvious to a practised eye.
Thank you for any suggestions offered!

Afterthought: I wondered if using double quotes (") instead of (') in print() might be causing the problem; I replaced them all with the same results.

Wow, there is a lot going on. I don’t see anything obvious but to be honest, my python skills are pretty basic. If I get a chance latter on today I’ll copy your code and try running it on my 144 RGB LED string. I’ll have to omit the current sensing as the Plasma Stick doesn’t have that option.

Thank you, Kerry. Good luck- hope you find what I’m missing!

The following code ran OK for me. The pattern went right to left and then left to right. The brightness looked like it was going up and down “slightly” too.

import plasma
from plasma import plasma_stick
from time import sleep
import gc
import micropython

from pimoroni import Analog

NUM_LEDS = 144
FPS = 60
speed = 10
sleep_time = 0.1  # was: 0.25
offset = 0.0
brightness = 1.0
saturation = 1
num_reps = 100     # Number of times to repeat function

led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, rgbw=True)
led_strip.start(FPS)

NUM_PIXELS = int(NUM_LEDS / 10)

while True:
    for i in range(NUM_LEDS):
        for j in range(NUM_PIXELS):
            pix_num = (i + (j * 10)) % NUM_LEDS
            hue = (float(i) / NUM_LEDS)

            # Adjust hue and set HSV values
            offset = (offset + float(speed) / 20000.0) % 1
            
            for k in range(5):
                led_strip.set_hsv(pix_num + (k - 5), (hue + offset) % 1, saturation, brightness)
            
            # Turn off trailing pixel
            trailer = ((pix_num + NUM_LEDS) - 6) % NUM_LEDS
            led_strip.set_hsv(trailer, 0, 0, 0)
        
        sleep(sleep_time)
        
    # Reverse direction
    for i in range(NUM_LEDS):
        for j in range(NUM_PIXELS):
            pix_num = (i + (j * 10)) % NUM_LEDS
            hue = (float(i) / NUM_LEDS)

            # Adjust hue and set HSV values
            offset = (offset + float(speed) / 20000.0) % 1

            for k in range(5):
                led_strip.set_hsv(NUM_LEDS - (pix_num + k), (hue + offset) % 1, saturation, brightness)

            # Turn off trailing pixel
            trailer = ((2 * NUM_LEDS - (pix_num - 1))) % NUM_LEDS
            led_strip.set_hsv(trailer, 0, 0, 0)

        
        sleep(sleep_time)        

What are you using for a power supply? I’m using a 5.1V 4A regulated switching power supply. Just curious as your watching the current draw. I’m powering things via the screw terminals, no 3A limit if you do it that way.

Hello, @alphanumeric- thank you for your response, for tidying up my code, and your time. My apologies for my delay in responding.
I’ve copied your code and changed the driver type from plasma_stick.DAT to plasma2040.DAT. I also changed the brightness to 0.5 to limit any and the code runs absolutely fine. I then changed NUM_LEDS = 144 to NUM_LEDS = 300 and the first for i in range(NUM_LEDS): loop completed successfully, but the first iteration of the "# Reverse direction loop fails thus:

Traceback (most recent call last):
  File "<stdin>", line 58, in <module>
TypeError: 'int' object isn't callable

Line 58 is:

        sleep(sleep_time)

At the end of the "# Reverse direction loop.
I’m powering the Plasma 2040 and its two strips from a port extender with a 20V @ 2A power supply and the rainbow.py sample works perfectly with NUM_LEDS = 300, so I don’t think that power is a problem.
I appreciate that you have already spent quite some time on this- I’m very grateful to you- but I would be interested to know the result if you tried running your Plasma Stick with NUM_LEDS = 300, even without than number of LEDs connected.
Thank you once more!

I get that same error if I set NUM_LEDS=300
Actually for me its GRB_LEDS
For my strip its as follows.
led_strip = plasma.WS2812(GRB_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_GRB)
My strip isn’t rgbw. Technically it’s not even rgb, its grb.

1 Like

Interesting- thanks, @alphanumeric. A shame not to be able to chain two 5-metre/150 LED strips together and to get consistent results (the rainbow example working, this cleaned-up code not). Do you think that Pimoroni would review their code, and do you know how to request this?
As ever, thank you for your time and interest.

One option is to post it as an issue on github.
pimoroni-pico/micropython/examples/plasma2040 at main · pimoroni/pimoroni-pico (github.com)
Another option is to contact tech support via e-mail. And put a link in it to this thread.
Contact Us for Raspberry Pi Technical Support - Pimoroni

What happens if you replace
sleep(sleep_time)
with just?
time.sleep(0.1)

You will also have to add an import time above the while true.

Just tied it and got that same

>>> %Run -c $EDITOR_CONTENT

MPY: soft reboot
Traceback (most recent call last):
  File "<stdin>", line 58, in <module>
TypeError: 'int' object isn't callable
>>> 

I don’t think its actually line 58 though, I’m thinking its something in the turn off trailer part of the code? GRB_LEDS = 144 works OK but 300 doesn’t?

EDIT: I suspect trailer is becoming a negative number or maybe 301? Might want to add a print(trailer) and monitor it.

            trailer = ((2 * GRB_LEDS - (pix_num - 1))) % GRB_LEDS
            print(trailer)
            led_strip.set_hsv(trailer, 0, 0, 0)

Hello, @alphanumeric - thank you, as ever for your suggestion. I put the print(trailer) as you suggested- for both occurrences of the variable- and it never goes negative. I had really hoped that was the problem!
I wondered where the value of NUM_LEDS started to cause a failure and you were very lucky with your 144! Changing the setting to NUM_LEDS = 145 caused the failure where the LEDs illuminated once and I would need to press the Reset button to get the Plasma 2040 to respond.
Thanks for letting me know how to escalate to Pimoroni- I will do so.

I had tried 145 and it ran just fine? I was also hoping it would go negative, did it reach 0?

The value of trailer did reach 0.
Interesting regarding 145- this was after I added the reporting on trailer.

Did it glitch when it hit 0? It may be looking for a number between 1 and NUM_LEDS.
I don’t know what the code is actually doing. A lot of the time I know what said code does, but have no clue how it actually does what it does? No formal training in Python or Micro Python. It’s been learn as you go for me.

My Plasma 2040 is setup as desktop lighting. I use an Encoder Wheel Breakout, the buttons set the color / pattern, and rotating the wheel adjusts the brightness. Normally its set to white, the patterns are just for showing off. =)

Hi, @alphanumeric- just a few updates.

  • What does the code do (or what is it meant to do)? Create 10 sequences of 5 LEDs, each of the same colour, distributed along a LED strip, move each sequence along one LED whilst incrementing the hue. After looping through the number of LEDs in the strip, reverse the direction; repeat.
  • for i in range(NUM_LEDS): will iterate over sequence of number starting at 0 and stopping before the specified number (NUM_LEDS), incrementing by 1. The first LED in a strip is no. 0. I’ve copied code that uses this, so I’m confident that it’s fine.
  • In my original code, I found a LED number set to -1 so I’ll check the simplified code to make sure the error isn’t in there, too.
  • I’ve removed many commented lines in my original code and that seems to have improved reliability!
  • The number (or length) of print() statements seems to have a significant effect on reliability. This makes putting diagnostic statements into my code tricky. There’s an element of Quantum Science here- observing something changes its outcome! An example is that running code with the debugging clause commented out runs OK whilst if it’s uncommented, the code fails when it isn’t
  • I’ll have a look at the GitHub repository to see if there are similar issues described- the answers may well be there.
    I’ll review all of the simplified code that you kindly provided and try to provide a succinct summary of my problem before reporting to Pimoroni.
    Thanks, as ever, for your help.

I was trying to remember if the first LED was 0 or 1. That’s why I wasn’t sure if 0 was OK or not. I’m pretty sure its the led_strip.set_hsv(trailer, 0, 0, 0) line of code that’s erroring out. But I think that is to do with something happening in the trailer = ((2 * GRB_LEDS - (pix_num - 1))) % GRB_LEDS line of code.
What is the last number shown for trailer when it glitches?

To be fair to Pimoroni you are not struggling with the Plasma 2040 but with your MicroPython code. You seem to have subverted Pimoroni’s attempt to supply a library that makes things easier than using standard MicroPython (which is what I use so I cannot comment on the Pimoroni library).

All I can suggest is that you start again and implement one output action at a time, wrapping your code in a function or class method. Incremental development in other words. Once you have fully tested classes or functions then you can build on top of those knowing you have a sound foundation.

I have no idea why you are printing out heaps or other diagnostic stuff like that. A few hundred pixels will not stress the RP2040 chip. The only time I have pushed the RP2040 RAM capacity is when building HTML pages on a Pico W. So trust the technology and review your methodology.