Voting resources, early voting, and poll worker information - VOTE. ... Adafruit is open and shipping.
0

Problem with memory allocation
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Problem with memory allocation

by meichr on Thu Sep 07, 2017 5:35 am

I've stacked Feather M0 Express, Featherwing OLED and Featherwing PCF8523 + SD and connected a LIPO 600mA.
My first example was to program a kind of Neopixel Swirl and displaying the current Voltage of the LIPO on the SSD. Running well.
But when I add the PCF8523 library, I receive a memory allocation error:

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:
Traceback (most recent call last):
  File "code.py", line 10, in <module>
  File "libraries/drivers/pcf8523/adafruit_pcf8523.py", line 62, in <module>
MemoryError: memory allocation failed, allocating 96 bytes


If I rearrange imports I get the allocation error at different lines. The error comes with Circuitpython 1.0.0 or 2.0.0-rc2 and the corresponding library bundles. How do I best debug such problems?

The code which created the above error:

Code: Select all | TOGGLE FULL SIZE
import time
import adafruit_ssd1306 as ssd1306
import busio
import analogio
import neopixel
import math
import board
import adafruit_pcf8523
from board import *

# Initialisieren der Zeitklasse
#
# Initiaisieren des M0-I2C, der OLED und des PCF8523-RTC
i2c = busio.I2C(SCL, SDA)
oled = ssd1306.SSD1306_I2C(128, 32, i2c)
#rtc = adafruit_pcf8523.PCF8523(i2c)
oled.fill(0)

# Initialisieren des Neopixel als Stromverbraucher
pixels = neopixel.NeoPixel(board.NEOPIXEL, 1, auto_write=True)

# Initialisieren der horizontalen und vertikalen Min/Max-Positionen
xmin = 0
xmax = 25
ymin = 0
ymax = 24
# Initialisieren der horizontalen und vertikalen Bewegungs-Geschwindigkeit
xvel = 5
yvel = 3
# Initialisieren der horizontalen und vertikalen Start-Position und Start-Bewegungs-Richtung
xpos = xmin
ypos = ymin
xdir = xvel
ydir = yvel

while True:
    # Einlesen des ADC-Wertes des Battery.Pins D9 und Umrechnung in Volt
    pin = analogio.AnalogIn(D9)
    volt = str(((pin.value * 2) * pin.reference_voltage) / 65536)
    volt = volt[0:4]
    pin.deinit()
    # Löschen des gesamten OLED und Anzeige der Spannung mit Text
    oled.fill(0)
    oled.text('Lipoly:', xpos + 0, ypos)
    print(volt)
    oled.text(volt, xpos + 60, ypos)
    oled.text('V', xpos + 96, ypos)
    #t = rtc.datetime
    #print(t)
    #oled.text(t, xpos + 0, ypos + 10)
    oled.show()
    # Bewegung berechnen
    xpos = xpos + xdir
    ypos = ypos + ydir
    if xdir > 0 and xpos > xmax:
        xdir = -xvel
        xpos = xmax + xdir
    if xdir < 0 and xpos < xmin:
        xdir = xvel
        xpos = xmin + xdir
    if ydir > 0 and ypos > ymax:
        ydir = -yvel
        ypos = ymax + ydir
    if ydir < 0 and ypos < ymin:
        ydir = yvel
        ypos = ymin + ydir

    # 180° des Swirls aller drei LED mit 60° Verschiebung je LED durchführen
    for xr in range(0, 180):
        xg=xr+60
        xb=xg+60
        if xg>=180:
            xg-=180
        if xb>=180:
            xb-=180
        yr=math.trunc(255*(1.0-math.sin(xr*math.pi/180.0)))
        yg=math.trunc(255*(1.0-math.sin(xg*math.pi/180.0)))
        yb=math.trunc(255*(1.0-math.sin(xb*math.pi/180.0)))
        print(yg)
        pixels[0] = (yr, yg, yb)
        time.sleep(0.015)


Thanks for any hint.

meichr
 
Posts: 10
Joined: Wed Aug 02, 2017 3:11 pm

Re: Problem with memory allocation

by tannewt on Thu Sep 07, 2017 2:15 pm

Hi,
Unfortunately there isn't an easy answer. Right now we're pretty RAM limited and both the ssd1306 and pcf8523 libraries are kinda big. You may be able to modify each of them to enable just the parts of the driver you will use.

One helpful tool for debugging the memory impact of imports is the gc module. It has a mem_free function that returns how much memory is free.

Here is the top of your example code with prints of the free memory:
Code: Select all | TOGGLE FULL SIZE
import gc
print(gc.mem_free())
import time
print("after time", gc.mem_free())
#import adafruit_ssd1306 as ssd1306
print("after ssd1306", gc.mem_free())
import busio
import analogio
#import neopixel
import math
import board
print("after board", gc.mem_free())
import adafruit_pcf8523
print("after pcf", gc.mem_free())
from board import *


Sorry I can't give you a simple fix.
~Scott

tannewt
 
Posts: 1837
Joined: Thu Oct 06, 2016 8:48 pm

Re: Problem with memory allocation

by meichr on Thu Sep 07, 2017 10:19 pm

Hi Scott,
thanks for the strategy how to debug. I've used your example with gc to look at the free memory. It was also interesting to include a gc.collect() before each gc.mem_free() call to see, what really is available before the automatic garbage collection is done at the out of memory situation during the RTC library import.
I'll experiment with removing the SSD1306.SPI class from the library next, because the framebuf buffer ist also declared in this function. But first I need to learn about how to create the library bundle from the source or git repository.
Thanks,
Christian.

meichr
 
Posts: 10
Joined: Wed Aug 02, 2017 3:11 pm

Re: Problem with memory allocation

by meichr on Fri Sep 08, 2017 10:13 pm

So, finally I did the following steps to make the current libraries fit into the memory:

  • Import gc library at start of code.py.
  • Add gc.collect() after each import to minimize fragmentation of memory.
  • Add print("Some text ", gc.mem_free()) after each import/gc.collect() statement group.
  • Now I let the code run and rearrange the order of imported libaries to maximize free memory displayed in REPL.
  • With adafruit_ssd1306 I experimented with the type of import (see code at this post) to minimize memory allocation.
  • Sometimes gc.collect() even increases memory allocation (see commented gs.collect() in code).
  • With displaying of date and time on SSD1306-OLED I needed to separate date and time to two OLED-text commands to get memory allocation down (see commented original oled.text() command in code).
  • Of course, I edited the source of adafruit_ssd1306 library, completely removed the ssd1306_SPI class to save a lot of memory, copied the py file uncompiled to the bundle library folder and removed the original ssd1603.mpy file.
  • I edited the adafruit_pcf8523 library and removed every part within the class, containing alarm or power management, because not needed in functionality.
  • After having optimized the memory allocation, I commented out the print statements to save further memory.

Only with all these steps I could fit the allocated memory into the available RAM, so that no memory allocation error appears in the REPL terminal. The Feathers stacked are "M0 Express", "SD and PCF8523" and "SSD1306 OLED".

Funny is, that I still want to also use the SD library to log the voltage and timestamp to the SD/RTC featherwing. That's some more playing around to come.

Code: Select all | TOGGLE FULL SIZE
import gc
gc.collect()
#print("After gc: "+str(gc.mem_free()))
import time
gc.collect()
#print("After time: "+str(gc.mem_free()))
import busio
gc.collect()
#print("After busio: "+str(gc.mem_free()))
#import adafruit_ssd1306 as ssd1306
from adafruit_ssd1306 import SSD1306_I2C
gc.collect()
#print("After adafruit_ssd1306: "+str(gc.mem_free()))
import adafruit_pcf8523
gc.collect()
#print("After adafruit_pcf8523: "+str(gc.mem_free()))
import analogio
gc.collect()
#print("After analogio: "+str(gc.mem_free()))
import neopixel
gc.collect()
#print("After neopixel: "+str(gc.mem_free()))
import math
gc.collect()
#print("After math: "+str(gc.mem_free()))
from board import *
gc.collect()
#print("After from board import *: "+str(gc.mem_free()))

# Initialising M0-I2C, OLED and PCF8523-RTC
i2c = busio.I2C(SCL, SDA)
gc.collect()
#oled = ssd1306.SSD1306_I2C(128, 32, i2c)
oled = SSD1306_I2C(128, 32, i2c)
gc.collect()
rtc = adafruit_pcf8523.PCF8523(i2c)
#gc.collect()
oled.fill(0)

# Initialising Neopixel as electricity consumer
pixels = neopixel.NeoPixel(NEOPIXEL, 1, auto_write=True)
gc.collect()
#print("Nach NEOPIXEL: "+str(gc.mem_free()))

# Initialising horizontal and vertical min/max positions
xmin = 0
xmax = 5
ymin = 0
ymax = 16
# Initialising horizontal and vertical movement speed
xvel = 3
yvel = 3
# Initialising horizontal and vertical start positions and start movement direction
xpos = xmin
ypos = ymin
xdir = xvel
ydir = yvel
gc.collect()
print("After Init: "+str(gc.mem_free()))

while True:
    # Reading ADC value of Battery.Pin D9 and calculation of voltage
    pin = analogio.AnalogIn(D9)
    volt = str(((pin.value * 2) * pin.reference_voltage) / 65536)
    volt = volt[0:4]
    pin.deinit()
    # Clearing of OLED and display of voltage value and text
    oled.fill(0)
    oled.text('Lipoly:', xpos + 0, ypos)
    #print(volt)
    oled.text(volt, xpos + 60, ypos)
    oled.text('V', xpos + 96, ypos)
    t = rtc.datetime
    #print(t)
    gc.collect()
    #oled.text("{0:0=2d}".format(t.tm_year)+"-"+"{0:0=2d}".format(t.tm_month)+"-"+"{0:0=2d}".format(t.tm_mday)+" "+"{0:0=2d}".format(t.tm_hour)+":"+"{0:0=2d}".format(t.tm_min)+":"+"{0:0=2d}".format(t.tm_sec), xpos + 0, ypos + 10)
    oled.text("{0:0=2d}".format(t.tm_year)+"-"+"{0:0=2d}".format(t.tm_mon)+"-"+"{0:0=2d}".format(t.tm_mday), xpos + 0, ypos + 10)
    oled.text("{0:0=2d}".format(t.tm_hour)+":"+"{0:0=2d}".format(t.tm_min), xpos + 84, ypos + 10)
    oled.show()
    # Calculate next movement step
    xpos = xpos + xdir
    ypos = ypos + ydir
    if xdir > 0 and xpos > xmax:
        xdir = -xvel
        xpos = xmax + xdir
    if xdir < 0 and xpos < xmin:
        xdir = xvel
        xpos = xmin + xdir
    if ydir > 0 and ypos > ymax:
        ydir = -yvel
        ypos = ymax + ydir
    if ydir < 0 and ypos < ymin:
        ydir = yvel
        ypos = ymin + ydir

    # Swirl of RGB-LED with sin(0:180°) and 60° phase difference of LEDs from each other
    for xr in range(0, 180):
        xg=xr+60
        xb=xg+60
        if xg>=180:
            xg-=180
        if xb>=180:
            xb-=180
        yr=math.trunc(255*(1.0-math.sin(xr*math.pi/180.0)))
        yg=math.trunc(255*(1.0-math.sin(xg*math.pi/180.0)))
        yb=math.trunc(255*(1.0-math.sin(xb*math.pi/180.0)))
        #print(yg)
        pixels[0] = (yr, yg, yb)
        time.sleep(0.015)

meichr
 
Posts: 10
Joined: Wed Aug 02, 2017 3:11 pm

Re: Problem with memory allocation

by tannewt on Sat Sep 09, 2017 3:12 am

Awesome work! It'll be much easier when we launch our M4 boards. They'll have 192k+ RAM instead of only 32k. :-)

tannewt
 
Posts: 1837
Joined: Thu Oct 06, 2016 8:48 pm

Please be positive and constructive with your questions and comments.