Please note: Friday June 18 is a holiday celebrating Juneteenth, please allow extra time for your order to arrive and plan accordingly.
0

Help w/ saving memory
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Help w/ saving memory

by burrito_poots on Fri Jun 04, 2021 5:59 pm

Hi gang, I'm running into a memory allocation error on my M4 express for the below code. I would love to know what things I could do to trim it down. This is basically a UI along with the full pogram for running some solenoids.

Will deleting libraries on my M4 open that up more, or do they not share space where the code runs vs where the libraries are stored? I also don't know how much my print_range snippet of code is using up, that's also something I'll remove at the very end since this hardware won't be connected to my laptop but freestanding when using after I finalize it. I've not a single clue how most of this stuff works and have put this together with tons of help from a buddy of mine plus you all and the discord. So any advice at all would be appreciated.

Thanks in advance!

Code: Select all | TOGGLE FULL SIZE
import board
import displayio
import terminalio
import busio
import digitalio
import adafruit_displayio_sh1107
from adafruit_display_shapes.roundrect import RoundRect
from adafruit_display_shapes.rect import Rect
from adafruit_display_shapes.circle import Circle
from adafruit_display_shapes.triangle import Triangle

from adafruit_display_text import label
import adafruit_displayio_sh1107

displayio.release_displays()
# old_reset = board.D9

# Use for I2C
i2c = board.I2C()
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)

# SH1107 is vertically oriented 64x128
WIDTH = 128
HEIGHT = 64
BORDER = 2

display = adafruit_displayio_sh1107.SH1107(display_bus, width=WIDTH, height=HEIGHT)

color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF # White

# Make the display context
splash = displayio.Group()


display.show(splash)


fillscreen = displayio.Group()
CIPscreen = displayio.Group()
dropleticon = displayio.Group()
bubbleicon = displayio.Group()

import rotaryio
import time
from adafruit_motorkit import MotorKit
import adafruit_vl6180x
# from adafruit_debouncer import Debouncer

sensor = adafruit_vl6180x.VL6180X(i2c)

encoderB = rotaryio.IncrementalEncoder(board.D12, board.D11)
button_e = digitalio.DigitalInOut(board.D10)
button_e.direction = digitalio.Direction.INPUT
button_e.pull = digitalio.Pull.UP

encoderA = rotaryio.IncrementalEncoder(board.A4, board.A3)
button_d = digitalio.DigitalInOut(board.A2)
button_d.direction = digitalio.Direction.INPUT
button_d.pull = digitalio.Pull.UP

range_mm = sensor.range

kit = MotorKit()

buttond_was_pushed = False
buttone_was_pushed = False
current_program = "Monarch"
program_running = False

lastA_position = None
lastB_position = None

text_area = label.Label(terminalio.FONT, text="monarch", scale=3, color=0xFFFFFF, x=3, y=12)
splash.append(text_area)
text_area2 = label.Label(terminalio.FONT, text="FLUID SYSTEMS", scale=1, color=0xFFFFFF, x=28, y=36)
splash.append(text_area2)
text_area3 = label.Label(terminalio.FONT, text="nano MK.I", scale=1, color=0xFFFFFF, x=70, y=55)
splash.append(text_area3)

color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF  # White

# Draw some white squares
sm_bitmap = displayio.Bitmap(20, 12, 1)
sm_square = displayio.TileGrid(sm_bitmap, pixel_shader=color_palette, x=83, y=0)
fillscreen.append(sm_square)

# Draw some white squares
sm_bitmapCIP = displayio.Bitmap(20, 12, 1)
sm_squareCIP = displayio.TileGrid(sm_bitmapCIP, pixel_shader=color_palette, x=83, y=0)
CIPscreen.append(sm_squareCIP)

triangle = Triangle(40, 18, 37, 25, 43, 25, fill=0x000000, outline=0xFFFFFF)
dropleticon.append(triangle)
circle = Circle(40, 25, 3, fill=0x000000, outline=0xFFFFFF)
dropleticon.append(circle)
rectDroplet = Rect(39, 22, 3, 1, fill=0x000000)
dropleticon.append(rectDroplet)

dropleticon.x = 27
dropleticon.y = -17

fillscreen.append(dropleticon)

circle2 = Circle(68, 43, 3, fill=0x000000, outline=0xFFFFFF)
bubbleicon.append(circle2)
circle3 = Circle(64, 46, 4, fill=0x000000, outline=0xFFFFFF)
bubbleicon.append(circle3)
circle4 = Circle(70, 49, 3, fill=0x000000, outline=0xFFFFFF)
bubbleicon.append(circle4)
#circle4 = Circle(69, 42, 2, fill=0x000000, outline=0xFFFFFF)
#bubbleicon.append(circle4)

bubbleicon.x = 3
bubbleicon.y = -40

CIPscreen.append(bubbleicon)

rectTime = Rect(3, 29, 58, 34, outline=0xFFFFFF)
fillscreen.append(rectTime)
rectFoam = Rect(77, 29, 43, 34, outline=0xFFFFFF)
fillscreen.append(rectFoam)

roundrectTime = RoundRect(0, 22, 33, 15, 7, fill=0xFFFFFF, stroke=1)
fillscreen.append(roundrectTime)
roundrectFoam = RoundRect(74, 22, 38, 15, 7, fill=0xFFFFFF, stroke=1)
fillscreen.append(roundrectFoam)

text_area4 = label.Label(terminalio.FONT, text="Fill Mode:", scale=1, color=0xFFFFFF, x=0, y=5)
fillscreen.append(text_area4)
text_area5 = label.Label(terminalio.FONT, text="OFF", scale=1, color=0x000000, x=85, y=5)
fillscreen.append(text_area5)
text_area6 = label.Label(terminalio.FONT, text="ON", scale=1, color=0xFFFFFF, x=110, y=5)
fillscreen.append(text_area6)
text_area7 = label.Label(terminalio.FONT, text="Time", scale=1, color=0x000000, x=5, y=28)
fillscreen.append(text_area7)
text_area8 = label.Label(terminalio.FONT, text="0.0", scale=2, color=0xFFFFFF, x=8, y=47)
fillscreen.append(text_area8)
text_area9 = label.Label(terminalio.FONT, text="Pulse", scale=1, color=0x000000, x=79, y=28)
fillscreen.append(text_area9)
text_area10 = label.Label(terminalio.FONT, text="0", scale=2, color=0xFFFFFF, x=90, y=47)
fillscreen.append(text_area10)

text_area11 = label.Label(terminalio.FONT, text=" CIP Mode:", scale=1, color=0xFFFFFF, x=0, y=5)
CIPscreen.append(text_area11)
text_area12 = label.Label(terminalio.FONT, text="OFF", scale=1, color=0x000000, x=85, y=5)
CIPscreen.append(text_area12)
text_area13 = label.Label(terminalio.FONT, text="ON", scale=1, color=0xFFFFFF, x=110, y=5)
CIPscreen.append(text_area13)

triangle2 = Triangle(26, 22, 6, 62, 46, 62, fill=0xFFFFFF)
CIPscreen.append(triangle2)
triangle3 = Triangle(26, 50, 24, 34, 28, 34, fill=0x000000)
CIPscreen.append(triangle3)
circle5 = Circle(26, 57, 2, fill=0x000000)
CIPscreen.append(circle5)
circle6 = Circle(26, 33, 2, fill=0x000000)
CIPscreen.append(circle6)

text_area15 = label.Label(terminalio.FONT, text="WARNING:", scale=1, color=0xFFFFFF, x=64, y=25)
CIPscreen.append(text_area15)
text_area16 = label.Label(terminalio.FONT, text="Valves will", scale=1, color=0xFFFFFF, x=55, y=41)
CIPscreen.append(text_area16)
text_area17 = label.Label(terminalio.FONT, text="stay open!", scale=1, color=0xFFFFFF, x=59, y=53)
CIPscreen.append(text_area17)



#display.refresh()

while True:
    if button_d.value == 0:
        # Button_d has been pressed
        buttond_was_pushed = True

    if button_e.value == 0:
        # Button_e has been pressed
        buttone_was_pushed = True

    #if current_program == "Monarch" and buttond_was_pushed:

    if current_program != "Fil" and buttond_was_pushed:
        # We are on main screen or CIP, but want Fil.
        # Go there
        display.show(fillscreen)
        current_program = "Fil"
        buttond_was_pushed = False

    if current_program == "Fil" and buttond_was_pushed and not program_running:
        # Button was pushed, but program not yet running.
        # Make it run!
        program_running = True
        buttond_was_pushed = False

    try:
        range_mm = sensor.range
        print('Range: {0}mm'.format(range_mm))
    except RuntimeError:
        print("retrying!")
    # time.sleep(.00001)

    if current_program == "Fil" and not buttond_was_pushed and program_running:
        # Fill program running, but button is not pushed... now what?
        positionA = encoderA.position
        if lastA_position is None or positionA != lastA_position:
            print(positionA)
        lastA_position = positionA
        timingA_value = 0.1*encoderA.position
        print(timingA_value)  # can remove this once verified it works
        sm_square.x = 106
        text_area5.color = 0xFFFFFF
        text_area6.color = 0x000000
        text_area8.text = str(timingA_value)
        triangle = Triangle(40, 18, 37, 25, 43, 25, fill=0xFFFFFF, outline=0xFFFFFF)
        dropleticon.append(triangle)
        circle = Circle(40, 25, 3, fill=0xFFFFFF, outline=0xFFFFFF)
        dropleticon.append(circle)
        rectDroplet = Rect(39, 22, 3, 1, fill=0xFFFFFF)
        dropleticon.append(rectDroplet)

        positionB = encoderB.position
        if lastB_position is None or positionB != lastB_position:
            print(positionB)
        lastB_position = positionB
        timingB_value = encoderB.position
        print(timingB_value)  # can remove this once verified it works
        text_area10.text = str(timingB_value)
        #display.show(fillscreen)
       
       
    if current_program == "Fil" and buttond_was_pushed and program_running:
        # Button was pushed while fill program running so we want it off.
        # Switch it to off!
        sm_square.x = 83
        text_area5.color = 0x000000
        text_area6.color = 0xFFFFFF
        program_running = False
        triangle = Triangle(40, 18, 37, 25, 43, 25, fill=0x000000, outline=0xFFFFFF)
        dropleticon.append(triangle)
        circle = Circle(40, 25, 3, fill=0x000000, outline=0xFFFFFF)
        dropleticon.append(circle)
        rectDroplet = Rect(39, 22, 3, 1, fill=0x000000)
        dropleticon.append(rectDroplet)
        buttond_was_pushed = False

        if (range_mm >= 70): #"if program_running and (range_mm >= 70):" may need to update to this
            kit.motor2.throttle = 1.0       # Load can
            time.sleep(.8)                  # Piston extension time
            kit.motor2.throttle = 0         # Piston retract
            time.sleep(1)                   # need for can to fall into chute
            kit.motor4.throttle = 1.0       # Drop lift piston
            kit.motor1.throttle = 1.0       # Start Purge
            time.sleep(1.5)                 # Hold for 1.5 s
            kit.motor1.throttle = 0         # Stop Purge
            kit.motor3.throttle = 1.0       # Start Fill
            time.sleep(timingA_value)       # Hold for set time
            kit.motor3.throttle = 0         # End fill
            for _ in range(timingB_value):
                time.sleep(0.05)                # Hold for set time
                kit.motor3.throttle = 1.0       # Start Foam Pulse
                time.sleep(0.05)                # Hold for set time
                kit.motor3.throttle = 0         # End Foam Pulse
            kit.motor4.throttle = 0             # Raise lift
            time.sleep(.75)

    try:
        range_mm = sensor.range
        print('Range2: {0}mm'.format(range_mm))
    except RuntimeError:
        print("retrying!")
    # time.sleep(.00001)

    if current_program != "CIP" and buttone_was_pushed:
        # We are on Monarch #or fill, and want CIP.
        # Go there
        display.show(CIPscreen)
        kit.motor4.throttle = 0.0
        kit.motor3.throttle = 0.0
        current_program = "CIP"
        program_running = False
        buttone_was_pushed = False

    if current_program == "CIP" and buttone_was_pushed and not program_running:
        # Button was pushed, but program not yet running.
        # Make it run!
        sm_squareCIP.x = 106
        text_area12.color = 0xFFFFFF
        text_area13.color = 0x000000
        kit.motor4.throttle = 1.0
        time.sleep(.5)
        kit.motor3.throttle = 1.0
        current_program = "CIP"
        program_running = True
        buttone_was_pushed = False

    if current_program == "CIP" and buttone_was_pushed and program_running:
        # Button was pushed and program running.
        # Make it stop!
        sm_squareCIP.x = 83
        text_area12.color = 0x000000
        text_area13.color = 0xFFFFFF
        kit.motor4.throttle = 0.0
        kit.motor3.throttle = 0.0
        current_program = "CIP"
        program_running = False
        buttone_was_pushed = False

burrito_poots
 
Posts: 107
Joined: Thu May 30, 2019 5:02 pm

Re: Help w/ saving memory

by MarksBench on Fri Jun 04, 2021 6:43 pm

Hello!

If you get a chance, can you post what the output of your program is (including the error)? Might make it easier to nail down the problem.

There are a lot of imports in your program. I don't know enough about CircuitPython to tell if it's too much or whether they should be loaded in a different order, but you can try checking the amount of available memory. Unfortunately, that requires another import, but after that you can check as many times as you want throughout your program:

Code: Select all | TOGGLE FULL SIZE
import gc
print(gc.mem_free())


Put one in every time your program does something that you think uses memory and watch the output. The numbers you get back is the number of bytes of memory that aren't currently used, but it doesn't mean they're usable since they may not be in large enough contiguous chunks for CP to fit things into. But, if you're seeing less than 5-10% free, it could be a hint.

You can also use the gc module to get CP to free up any RAM that used to be in use but currently isn't (garbage collection). You can do this as many times as you want throughout your program too, but doing it too often can eat up CPU time. To do garbage collection (assuming you've already imported gc):

Code: Select all | TOGGLE FULL SIZE
gc.collect()


Something that would be interesting to see is if, for testing purposes, you did this in a few spots in your program:

Code: Select all | TOGGLE FULL SIZE
print(gc.mem_free())
gc.collect()
print(gc.mem_free())


Sometimes just putting a few garbage collect statements in there will be enough to get it running happily, but putting the mem_free statements before and after it will give you an idea of whether the garbage collector is able to do anything, and where it's most effective.

MarksBench
 
Posts: 42
Joined: Tue Apr 20, 2021 5:40 pm

Re: Help w/ saving memory

by burrito_poots on Sat Jun 05, 2021 5:28 pm

Thanks for the tips! Right now this is my error code:

Traceback (most recent call last):
File "main.py", line 220, in <module>
MemoryError: memory allocation failed, allocating 2048 bytes

Code done running.


Should I still do that? And as far as implementing it, I'll need to remove my other "print" code since it spams so fast -- that's not possibly causing my issue is it? since its really only in their for the testing side I mean -- unsure how much memory a repeating "print" piece of code would eat up if any, I could be looking at that the wrong way here.

MarksBench wrote:Hello!

If you get a chance, can you post what the output of your program is (including the error)? Might make it easier to nail down the problem.

There are a lot of imports in your program. I don't know enough about CircuitPython to tell if it's too much or whether they should be loaded in a different order, but you can try checking the amount of available memory. Unfortunately, that requires another import, but after that you can check as many times as you want throughout your program:

Code: Select all | TOGGLE FULL SIZE
import gc
print(gc.mem_free())


Put one in every time your program does something that you think uses memory and watch the output. The numbers you get back is the number of bytes of memory that aren't currently used, but it doesn't mean they're usable since they may not be in large enough contiguous chunks for CP to fit things into. But, if you're seeing less than 5-10% free, it could be a hint.

You can also use the gc module to get CP to free up any RAM that used to be in use but currently isn't (garbage collection). You can do this as many times as you want throughout your program too, but doing it too often can eat up CPU time. To do garbage collection (assuming you've already imported gc):

Code: Select all | TOGGLE FULL SIZE
gc.collect()


Something that would be interesting to see is if, for testing purposes, you did this in a few spots in your program:

Code: Select all | TOGGLE FULL SIZE
print(gc.mem_free())
gc.collect()
print(gc.mem_free())


Sometimes just putting a few garbage collect statements in there will be enough to get it running happily, but putting the mem_free statements before and after it will give you an idea of whether the garbage collector is able to do anything, and where it's most effective.

burrito_poots
 
Posts: 107
Joined: Thu May 30, 2019 5:02 pm

Re: Help w/ saving memory

by MarksBench on Sat Jun 05, 2021 7:06 pm

Yep, you can leave your program as is and just sprinkle the memory info print statements around there. You should be seeing some numbers up until the point where it hits that error.

I don't have the hardware you're using, but I took your program, edited out some of the hardware stuff, and ran most of the icon building stuff on a Pi Pico RP2040. Here's what I ran:
Code: Select all | TOGGLE FULL SIZE
import gc
print("Initial free memory (with gc module loaded:", gc.mem_free())
gc.collect()
print("Initial free memory after GC:", gc.mem_free())
import board
import displayio
import terminalio
import busio
import digitalio
import adafruit_displayio_sh1107
from adafruit_display_shapes.roundrect import RoundRect
from adafruit_display_shapes.rect import Rect
from adafruit_display_shapes.circle import Circle
from adafruit_display_shapes.triangle import Triangle

from adafruit_display_text import label
import adafruit_displayio_sh1107
print("Free mem at point A:", gc.mem_free())
gc.collect()
print("Free mem at point A after GC:", gc.mem_free())

displayio.release_displays()


# SH1107 is vertically oriented 64x128
WIDTH = 128
HEIGHT = 64
BORDER = 2


color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF # White

# Make the display context
splash = displayio.Group()


fillscreen = displayio.Group()
CIPscreen = displayio.Group()
dropleticon = displayio.Group()
bubbleicon = displayio.Group()

print("Free mem at point B:", gc.mem_free())
gc.collect()
print("Free mem at point B after GC:", gc.mem_free())

import rotaryio
import time
from adafruit_motorkit import MotorKit
import adafruit_vl6180x

print("Free mem at point C:", gc.mem_free())
gc.collect()
print("Free mem at point C after GC:", gc.mem_free())


text_area = label.Label(terminalio.FONT, text="monarch", scale=3, color=0xFFFFFF, x=3, y=12)
splash.append(text_area)
text_area2 = label.Label(terminalio.FONT, text="FLUID SYSTEMS", scale=1, color=0xFFFFFF, x=28, y=36)
splash.append(text_area2)
text_area3 = label.Label(terminalio.FONT, text="nano MK.I", scale=1, color=0xFFFFFF, x=70, y=55)
splash.append(text_area3)

print("Free mem at point D:", gc.mem_free())
gc.collect()
print("Free mem at point D after GC:", gc.mem_free())

color_bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF  # White

# Draw some white squares
sm_bitmap = displayio.Bitmap(20, 12, 1)
sm_square = displayio.TileGrid(sm_bitmap, pixel_shader=color_palette, x=83, y=0)
fillscreen.append(sm_square)

# Draw some white squares
sm_bitmapCIP = displayio.Bitmap(20, 12, 1)
sm_squareCIP = displayio.TileGrid(sm_bitmapCIP, pixel_shader=color_palette, x=83, y=0)
CIPscreen.append(sm_squareCIP)

triangle = Triangle(40, 18, 37, 25, 43, 25, fill=0x000000, outline=0xFFFFFF)
dropleticon.append(triangle)
circle = Circle(40, 25, 3, fill=0x000000, outline=0xFFFFFF)
dropleticon.append(circle)
rectDroplet = Rect(39, 22, 3, 1, fill=0x000000)
dropleticon.append(rectDroplet)

dropleticon.x = 27
dropleticon.y = -17

fillscreen.append(dropleticon)

print("Free mem at point E:", gc.mem_free())
gc.collect()
print("Free mem at point E after GC:", gc.mem_free())

circle2 = Circle(68, 43, 3, fill=0x000000, outline=0xFFFFFF)
bubbleicon.append(circle2)
circle3 = Circle(64, 46, 4, fill=0x000000, outline=0xFFFFFF)
bubbleicon.append(circle3)
circle4 = Circle(70, 49, 3, fill=0x000000, outline=0xFFFFFF)
bubbleicon.append(circle4)


bubbleicon.x = 3
bubbleicon.y = -40

CIPscreen.append(bubbleicon)

print("Free mem at point F:", gc.mem_free())
gc.collect()
print("Free mem at point F after GC:", gc.mem_free())

rectTime = Rect(3, 29, 58, 34, outline=0xFFFFFF)
fillscreen.append(rectTime)
rectFoam = Rect(77, 29, 43, 34, outline=0xFFFFFF)
fillscreen.append(rectFoam)

roundrectTime = RoundRect(0, 22, 33, 15, 7, fill=0xFFFFFF, stroke=1)
fillscreen.append(roundrectTime)
roundrectFoam = RoundRect(74, 22, 38, 15, 7, fill=0xFFFFFF, stroke=1)
fillscreen.append(roundrectFoam)

print("Free mem at point G:", gc.mem_free())
gc.collect()
print("Free mem at point G after GC:", gc.mem_free())

text_area4 = label.Label(terminalio.FONT, text="Fill Mode:", scale=1, color=0xFFFFFF, x=0, y=5)
fillscreen.append(text_area4)
text_area5 = label.Label(terminalio.FONT, text="OFF", scale=1, color=0x000000, x=85, y=5)
fillscreen.append(text_area5)
text_area6 = label.Label(terminalio.FONT, text="ON", scale=1, color=0xFFFFFF, x=110, y=5)
fillscreen.append(text_area6)
text_area7 = label.Label(terminalio.FONT, text="Time", scale=1, color=0x000000, x=5, y=28)
fillscreen.append(text_area7)
text_area8 = label.Label(terminalio.FONT, text="0.0", scale=2, color=0xFFFFFF, x=8, y=47)
fillscreen.append(text_area8)
text_area9 = label.Label(terminalio.FONT, text="Pulse", scale=1, color=0x000000, x=79, y=28)
fillscreen.append(text_area9)
text_area10 = label.Label(terminalio.FONT, text="0", scale=2, color=0xFFFFFF, x=90, y=47)
fillscreen.append(text_area10)

print("Free mem at point H:", gc.mem_free())
gc.collect()
print("Free mem at point H after GC:", gc.mem_free())

text_area11 = label.Label(terminalio.FONT, text=" CIP Mode:", scale=1, color=0xFFFFFF, x=0, y=5)
CIPscreen.append(text_area11)
text_area12 = label.Label(terminalio.FONT, text="OFF", scale=1, color=0x000000, x=85, y=5)
CIPscreen.append(text_area12)
text_area13 = label.Label(terminalio.FONT, text="ON", scale=1, color=0xFFFFFF, x=110, y=5)
CIPscreen.append(text_area13)

print("Free mem at point I:", gc.mem_free())
gc.collect()
print("Free mem at point I after GC:", gc.mem_free())

triangle2 = Triangle(26, 22, 6, 62, 46, 62, fill=0xFFFFFF)
CIPscreen.append(triangle2)
triangle3 = Triangle(26, 50, 24, 34, 28, 34, fill=0x000000)
CIPscreen.append(triangle3)
circle5 = Circle(26, 57, 2, fill=0x000000)
CIPscreen.append(circle5)
circle6 = Circle(26, 33, 2, fill=0x000000)
CIPscreen.append(circle6)

print("Free mem at point J:", gc.mem_free())
gc.collect()
print("Free mem at point J after GC:", gc.mem_free())

text_area15 = label.Label(terminalio.FONT, text="WARNING:", scale=1, color=0xFFFFFF, x=64, y=25)
CIPscreen.append(text_area15)
text_area16 = label.Label(terminalio.FONT, text="Valves will", scale=1, color=0xFFFFFF, x=55, y=41)
CIPscreen.append(text_area16)
text_area17 = label.Label(terminalio.FONT, text="stay open!", scale=1, color=0xFFFFFF, x=59, y=53)
CIPscreen.append(text_area17)

print("Free mem at point K:", gc.mem_free())
gc.collect()
print("Free mem at point K after GC:", gc.mem_free())


And here's the output:
Code: Select all | TOGGLE FULL SIZE
Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.

code.py output:
Initial free memory (with gc module loaded: 206944
Initial free memory after GC: 206944
Free mem at point A: 188176
Free mem at point A after GC: 188272
Free mem at point B: 186672
Free mem at point B after GC: 186672
Free mem at point C: 174800
Free mem at point C after GC: 174800
Free mem at point D: 163008
Free mem at point D after GC: 169632
Free mem at point E: 165024
Free mem at point E after GC: 168336
Free mem at point F: 163504
Free mem at point F after GC: 167456
Free mem at point G: 139328
Free mem at point G after GC: 165520
Free mem at point H: 149536
Free mem at point H after GC: 158496
Free mem at point I: 150896
Free mem at point I after GC: 155216
Free mem at point J: 137392
Free mem at point J after GC: 153520
Free mem at point K: 142080
Free mem at point K after GC: 148704

Code done running.

Press any key to enter the REPL. Use CTRL-D to reload.


You can see the difference that the garbage collection is making each step along the way. If I comment out all of the gc.collect() statements, this is what happens:
Code: Select all | TOGGLE FULL SIZE
code.py output:
Initial free memory (with gc module loaded: 207888
Free mem at point A: 189120
Free mem at point B: 187520
Free mem at point C: 175744
Free mem at point D: 163952
Free mem at point E: 159344
Free mem at point F: 154512
Free mem at point G: 126384
Free mem at point H: 110400
Free mem at point I: 102800
Free mem at point J: 84976
Free mem at point K: 73536

Code done running.

Press any key to enter the REPL. Use CTRL-D to reload.


This is all pretty new to me so I don't pretend to understand how it works, but without running the garbage collection it's using an extra 75168 bytes and only has 73536 bytes free.

Comparing two boards is kind of apples to oranges, but the M4 express has 192KB of RAM; the Pi Pico has 264KB. 264KB-192KB is 72KB, so that extra bit of RAM could well be the only reason why the program ran on the Pico - and it's not even your whole program.

For interest's sake, I tried running a single garbage collection at the end and got this output:
Code: Select all | TOGGLE FULL SIZE
code.py output:
Initial free memory (with gc module loaded: 207808
Free mem at point A: 189040
Free mem at point B: 187440
Free mem at point C: 175664
Free mem at point D: 163872
Free mem at point E: 159264
Free mem at point F: 154432
Free mem at point G: 126304
Free mem at point H: 110320
Free mem at point I: 102720
Free mem at point J: 84896
Free mem at point K: 73456
Free mem at point K after GC: 149568

Code done running.

Press any key to enter the REPL. Use CTRL-D to reload.


So even just running it once could make a big difference, although if you can afford the delays in incurs, I'd run it at several points throughout your program. As I understand it, doing it that way makes it more likely that you'll have contiguous memory space available and won't have little chunks here and there that are too small for your program to use.

Anyway... sorry for the long post but I hadn't had an opportunity before to see how much of a difference garbage collection can make.

I hope this helps and you get your program running!

MarksBench
 
Posts: 42
Joined: Tue Apr 20, 2021 5:40 pm

Re: Help w/ saving memory

by burrito_poots on Sat Jun 05, 2021 8:53 pm

Sort of. So I plugged that in and only added it at the end of my "while true" loop.

As I watched my code run, it basically starting ticking down every loop by roughly 1000 bytes on free memory until it ran out and the code stopped running.

Is this because of all the graphical elements I have? I'm not really familiar with that side, but I thought this would be able to handle them fairly well? I've seen a couple programs shown with elements like that so, what am I doing wrong here? :( I really want to keep them and figure out how to make this work as the program is for a UI so it really needs to be easy to read/use.

burrito_poots
 
Posts: 107
Joined: Thu May 30, 2019 5:02 pm

Re: Help w/ saving memory

by MarksBench on Sun Jun 06, 2021 1:51 am

Ah! Okay, that was very helpful - I may have an idea of what's going on.

In your while loop, you are appending to dropleticon when the right conditions are met, but I don't see anywhere that dropleticon is being cleared/cleaned up/reinitialized. So I think it's just being appended to over and over until it's using up all the available RAM.

This:
Code: Select all | TOGGLE FULL SIZE
import gc
print(gc.mem_free())

dropleticon = [0]

print(gc.mem_free())
while True:

        triangle = [1,1,1,1,1]
        dropleticon.append(triangle)
        circle = [5,5,5,5,5]
        dropleticon.append(circle)
        rectDroplet = [9,9,9,9,9]
        dropleticon.append(rectDroplet)
        print(gc.mem_free())

ends up doing this on my setup:
Code: Select all | TOGGLE FULL SIZE
Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.

code.py output:
212176
212144
211968
211808
211632
.
[a whole bunch more lines]
.
1056
912
768
624
480
336
192
48
MemoryError:

Code done running.

So I think if you clear anything you're appending to at the beginning of your while loop, you'll see a big difference. Two things you can try to see if this is the case:
- Comment out the dropleticon.append statements in your while loop and see if it keeps running then, or
- Put the following statement at the very top of your while loop:
Code: Select all | TOGGLE FULL SIZE
dropleticon = displayio.Group()


Good luck and let me know how it turns out!

MarksBench
 
Posts: 42
Joined: Tue Apr 20, 2021 5:40 pm

Re: Help w/ saving memory

by burrito_poots on Sun Jun 06, 2021 4:25 pm

Why would that last suggestion work exactly? Just so I understand it?

Also, how would I got about "de-appending" something from a code standpoint? Can I do it within my loops? Its maybe hard to tell from the code but basically I have this as my UI:

Splash screen (name of machine this runs, nothing special):

If you click button D: you go to the filling mode screen
If you click button E: you go to the CIP (stands for Clean In Place) screen.

Once at those screens, clicking their respective button will toggle that specific program tied to that screen either on or off. If you're in filling mode (which is controlled by button D) and you click button E, it will take you right into that sub-program and vice versa.

So if I go from Filling Mode into CIP mode, would my code look something like this?

Code: Select all | TOGGLE FULL SIZE
    if current_program != "Fil" and buttond_was_pushed:
        # We are on main screen or CIP, but want Fil.
        # Go there
        display.show(fillscreen)
        current_program = "Fil"
        buttond_was_pushed = False
        DE-APPEND ALL ICON STUFF FOR CIP


Obviously the all caps would be actual code handling this, but is this how I would accomplish this so it would clear out the non-running sub program's memory tied to the graphical UI elements I'm placing which I assume are some of my main memory consumers? Then I would duplicate this but flipped for the opposite scenario (not CIP program, so when we go to CIP mode, we also de-append all stuff for the filling mode).

burrito_poots
 
Posts: 107
Joined: Thu May 30, 2019 5:02 pm

Re: Help w/ saving memory

by MarksBench on Mon Jun 07, 2021 2:36 am

Why would that last suggestion work exactly? Just so I understand it?

Good question. In the following program, I append an item to a list named foo 5000 times, checking the free RAM along the way:

Code: Select all | TOGGLE FULL SIZE
import gc

print("Start:",gc.mem_free())

# Create list
foo = []
print("Ran foo = []:", gc.mem_free())

print("Starting loop")

# Starting loop

for x in range(0,5000,1):
    foo.append("Sandwiches are beautiful")
    if(x%250 == 0):
        print("Loop",x,":",gc.mem_free())
   
print("Done loop")
#print("Contents of foo:", foo)

print("RAM available:",gc.mem_free())
print("Re-running foo = []")
foo = []
print("Contents of foo are now:",foo)
print("RAM available :", gc.mem_free())
gc.collect()
print("RAM AVAILABLE AFTER GC:",gc.mem_free())

and this is the result (I had to comment out the line that prints the contents of foo because it was crashing out my REPL and 5000 copies of "Sandwiches are beautiful" gets old pretty quick. Anyway:
Code: Select all | TOGGLE FULL SIZE
code.py output:
Start: 211728
Ran foo = []: 211696
Starting loop
Loop 0 : 211680
Loop 250 : 210672
Loop 500 : 209648
Loop 750 : 207600
Loop 1000 : 207600
Loop 1250 : 203504
Loop 1500 : 203504
Loop 1750 : 203504
Loop 2000 : 203504
Loop 2250 : 195312
Loop 2500 : 195312
Loop 2750 : 195312
Loop 3000 : 195312
Loop 3250 : 195312
Loop 3500 : 195312
Loop 3750 : 195312
Loop 4000 : 195312
Loop 4250 : 178928
Loop 4500 : 178928
Loop 4750 : 178928
Done loop
RAM available: 178928
Re-running foo = []
Contents of foo are now: []
RAM available : 178896
RAM AVAILABLE AFTER GC: 211680

Code done running.

So... all that extra stuff is no longer part of foo, so foo can be appended to again (with the same or different information, depending on which buttons are pressed and which image you want shown.

BUUUUUUUUT... this is part of the thing where garbage collection comes in. Notice how after you re-initialize that variable, the available memory doesn't get better. That's because that while it doesn't count toward the variable anymore, the memory hasn't been returned to the system for reallocation. Our buddy the garbage collector will do that (whether it's because it is called by the program or CP itself). That's why the available RAM jumps back up to "I just rebooted it" levels.

Also, how would I got about "de-appending" something from a code standpoint? Can I do it within my loops?


Okay, so I'm not too familiar with the Adafruit graphics library stuff and it's getting pretty late so I can't read up on it but I think the simplest thing is putting
Code: Select all | TOGGLE FULL SIZE
dropleticon = displayio.Group()


as the first item in your while loop, or after every time the dropleticon is written. Of course, you're going to need to do this with the other icons you are displaying in your while loop, too.

I just took a quick search for CircuitPython displayio.Group and found this page:
https://learn.adafruit.com/circuitpython-display-support-using-displayio/group

That may help point you in the right direction for getting dropleticon ready after you reinitialize it. It also mentions delete and pop functions that may be useful. But for me, I think the easiest thing to try would be to just use dropleticon=displayio.Group().

MarksBench
 
Posts: 42
Joined: Tue Apr 20, 2021 5:40 pm

Re: Help w/ saving memory

by MarksBench on Mon Jun 07, 2021 7:00 pm

I just had another thought but I'm not home to try it out right now - instead of building the dropleticon image inside the while loop, create it before the while loop starts (like your splash screen) and just display it when it's needed as you go through the right if statement in your while loop.

That will save you RAM because there's only one copy of it and will speed your program up because the processor doesn't have to build the icon each time it's needed.

MarksBench
 
Posts: 42
Joined: Tue Apr 20, 2021 5:40 pm

Please be positive and constructive with your questions and comments.


cron