0

Unfreezing libraries
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Unfreezing libraries

by geekguy on Thu Apr 01, 2021 5:31 pm

Hi,

I am using the Feather nRF52840 Sense, and all I am interested in are the onboard sensors and, bitmap_font, sdcard, displayio, and display_text libraries plus any that they require.

Frozen libraries are _bleio, _pixelbuf, aesio, analogio, audiobusio, audiocore, audiomixer, audiomp3, audiopwmio, bitbangio, board, busio, digitalio, displayio, framebufferio, gamepad, math, microcontroller, msgpack, neopixel_write, nvm, os, pulseio, pwmio, random, rgbmatrix, rotaryio, rtc, sdcardio, sharpdisplay, storage, struct, supervisor, terminalio, time, touchio, ulab, usb_hid, usb_midi, vectorio, and watchdog.

Is there any way I can "unfreeze" libraries that I do not need? My current script needs every byte of RAM for code and variables, and it is already right on the edge. I have already had to remove features I really want in the script because I run out of RAM. I believe that libraries like ulab (and possibly others) should never be frozen just because an MCU can support them. I would rather take a memory hit for loading unfrozen libraries if I need them instead of taking memory away from code and variables.

As far as I can tell, I have already removed all the code and associated variables I can remove to gain as much memory as possible for my script. In fact, I have removed stuff I really do not want to remove.

Is there any help here? Can I unfreeze libraries I do not need or want?

8-Dale

geekguy
 
Posts: 302
Joined: Tue Sep 10, 2013 2:43 pm
Location: Beaverton, OR

Re: Unfreezing libraries

by tannewt on Thu Apr 01, 2021 5:59 pm

Those are native modules and not "frozen" libraries. Frozen libraries have a Python source that is compiled into the firmware binary. You can disable them by rebuilding CircuitPython yourself (https://learn.adafruit.com/building-circuitpython) but that won't save much RAM if any. Native modules are stored in flash and have minimal RAM impact when you use them.

I suspect your code and the libraries you importing are taking the bulk of RAM and will need to be optimized. I'd recommend posting all of your code so that others can provide specific optimization suggestions.

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

Re: Unfreezing libraries

by geekguy on Thu Apr 01, 2021 8:20 pm

tannewt wrote:Those are native modules and not "frozen" libraries. Frozen libraries have a Python source that is compiled into the firmware binary. You can disable them by rebuilding CircuitPython yourself (https://learn.adafruit.com/building-circuitpython) but that won't save much RAM if any. Native modules are stored in flash and have minimal RAM impact when you use them.

I really do not want to do that.

tannewt wrote:I suspect your code and the libraries you importing are taking the bulk of RAM and will need to be optimized. I'd recommend posting all of your code so that others can provide specific optimization suggestions.

OK, here is what I am currently running.
Code: Select all | TOGGLE FULL SIZE
"""
  This script is based on the example script in the Adafruit Learn Guide at
    https://learn.adafruit.com/using-circuitpython-displayio-with-a-tft-featherwing/3-5-tft-featherwing

  This is a greatly expanded version of that script that reads all the sensors of the Feather
    nRF52840 Sense and displays everything on the 3.5" TFT Featherwing.

  Adafruit invests time and resources providing this open source code, please support Adafruit
    and open-source hardware by purchasing products from Adafruit!
"""

import board
from time import sleep, time
#import array
from math import atan, atan2, pi, sqrt
import audiobusio
import terminalio
from displayio import Bitmap, FourWire, Group, Palette, TileGrid, release_displays
import storage
from neopixel import NeoPixel
from digitalio import DigitalInOut
from adafruit_display_text import label
from adafruit_hx8357 import HX8357
from adafruit_bitmap_font import bitmap_font
from adafruit_sdcard import SDCard
from adafruit_stmpe610 import Adafruit_STMPE610_SPI
import adafruit_bmp280
import adafruit_lis3mdl
import adafruit_lsm6ds.lsm6ds33
import adafruit_sht31d

#   For the Real Time Clock on the Adalogger FeatherWIng
import adafruit_pcf8523

TEXT_X = 10
TEXT_Y = 15
TEXT_INCREMENT = 28

DATA_X = 150
DATA_Y = 15
DATA_INCREMENT = 28

TEXT_SCALE = 2

IMAGES_PATH = "/sd/images/"
SOUNDS_PATH = "/sd/sounds/"
FONTS_PATH = "/sd/fonts/"

TFT_CS_PIN = board.D9
TFT_DC_PIN = board.D10

TOUCH_CS_PIN = board.D6

#   Task check intervals
UPDATE_TEMP_INTERVAL_MIN = 0.5          # Once every 6.5 minutes
UPDATE_BARO_INTERVAL_MIN = 2.5          # Once every 2.5 minutes
UPDATE_MAG_INTERVAL_MIN = 0.001         #167        # Once every second
UPDATE_TIME_INTERVAL_MIN = 1.0          # Once every minute
UPDATE_SHT_INTERVAL_MIN = 0.3333        # Once every 20 seconds
CHECK_SD_INTERVAL_MIN = 2.0             # Every 2 minutes
LOGGING_INTERVAL_MIN = 10.0             # Every 10 minutes
LOGGING_PATH = "/sd/data/"

#FONT_FILE = "Helvetica-Bold-16.bdf"
#FONT_FILE = "Arial-16.bdf"
LOGGING_FILE = "sense.log"

#   Standard RGBW colors - taken from https://www.rapidtables.com/web/color/RGB_Color.html
#RED           = (0xFF, 0x00, 0x00, 0x00)
#GREEN         = (0x00, 0x3C, 0x00, 0x00)
BLUE          = (0x00, 0x00, 0x4B, 0x00)
#CHOCOLATE     = (0xD2, 0x69, 0x1E, 0x00)

#BUTTER_YELLOW = (0x7F, 0xFD, 0x74, 0x00)
#BRIGHT_GREEN  = (0x00, 0xFF, 0x00, 0x00)
#MAGENTA       = (0XFF, 0x00, 0xFF, 0x00)
#YELLOW        = (0xFF, 0xFF, 0x00, 0x00)

MEDIUM_GREEN  = (0x00, 0x5F, 0x00, 0x00)
#BRIGHT_BLUE   = (0x00, 0x00, 0xFF, 0x00)
#GOLDENROD     = (0xDA, 0xB9, 0x20, 0x00)
#OLIVE         = (0x80, 0x80, 0x00, 0x00)
#ORANGE        = (0xFF, 0xB9, 0x00, 0x00)

BRIGHT_YELLOW = (0xFF ,0xFF ,0x00, 0x00)
PURPLE        = (0x80, 0x00, 0x80, 0x00)
#LIME          = (0xC8, 0xC8, 0x00, 0x00)
#ROYAL_BLUE    = (0x41, 0x69, 0xE1, 0x00)

#PINK          = (0xC8, 0x6E, 0xAF, 0x00)
#MIDNIGHT_BLUE = (0x19, 0x19, 0x70, 0x00)
#CYAN          = (0x00, 0xFF, 0xFF, 0x00)

WHITE         = (0x00, 0x00, 0x00, 0x7F)
BLACK         = (0x00, 0x00, 0x00, 0x00)

BACKGROUND_COLOR = MEDIUM_GREEN
TEXT_COLOR = BRIGHT_YELLOW

# Release any resources currently in use for the displays
release_displays()

# Initialize the onboard NeoPixel
status_light = NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
status_light.fill(BLACK)

#print("Initializing busses and sensors")
# Initialize the I2C bus
i2c = board.I2C()

# Initialize the SPI bus
spi = board.SPI()

#   Initialize the Real Time Clock on the Adalogger FeatherWIng
rtc = adafruit_pcf8523.PCF8523(i2c)

# Initialize the TFT display and touch screen
display_bus = FourWire(spi, command=TFT_DC_PIN, chip_select=TFT_CS_PIN)
display = HX8357(display_bus, width=480, height=320)
# This error occurs when initializing the touch screen. What type of touch screen
#   does the TFT Featherwing have??
#
#Traceback (most recent call last):
#  File "code.py", line 139, in <module>
#  File "adafruit_stmpe610.py", line 281, in __init__
#RuntimeError: Failed to find STMPE610! Chip Version 0x0
#
#screen = Adafruit_STMPE610_SPI(spi, TOUCH_CS_PIN)

# Initialize the sensors
bmp280 = adafruit_bmp280.Adafruit_BMP280_I2C(i2c)

# Set this to sea level pressure in hectoPascals at your location for accurate altitude reading.
bmp280.sea_level_pressure = 1013.25

lis3mdl = adafruit_lis3mdl.LIS3MDL(i2c)
lsm6ds33 = adafruit_lsm6ds.lsm6ds33.LSM6DS33(i2c)
sht31d = adafruit_sht31d.SHT31D(i2c)
#microphone = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
#  sample_rate=16000, bit_depth=16)

# Make the display context
splash = Group(max_size=20)

# Draw the TFT background
color_bitmap = Bitmap(480, 320, 1)
color_palette = Palette(1)
color_palette[0] = BACKGROUND_COLOR

bg_sprite = TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)

display.show(splash)

def minutes(seconds, start_seconds, decimal=0):
  #   60 seconds * 1000 ms = 1 minute
 
  return (seconds - start_seconds) / 60

def check_update(seconds, start_seconds, check_min):
  update = False
  curr_min = minutes(seconds, start_seconds, 2)

  if curr_min >= check_min:
    update = True 

  return update, curr_min

def blink_NeoPixel(pixel, color, wait=0.2, cycles=1):
  for cy in range(cycles):
    #if DEBUG:
    #  print("Cycle #{0}".format(cy))

    pixel.fill(color)
    sleep(wait)
    pixel.fill(BLACK)
    sleep(wait)

def make_time_stamp(hr24=False):
  ampm = ""
  r = rtc.datetime

  hours = r.tm_hour

  if hr24:
    date_stamp = "{:0>4}{:0>2}{:0>2}".format(r.tm_year, r.tm_mon, r.tm_mday)
    time_stamp = "{:2d}{:0>2}{:0>2} {}".format(hours, r.tm_min, r.tm_sec, ampm)
  else: 
    date_stamp = "{:0>2}/{:0>2}/{:0>4}".format(r.tm_mon, r.tm_mday, r.tm_year)
    if hours < 12:
      ampm = " AM"
    else:
      ampm = " PM"
     
    if hours > 12:
      hours = hours - 12

    if hours == 0 and not hr24:
      hours = 12

    date_stamp = "{:0>2}/{:0>2}/{:0>4}".format(r.tm_mon, r.tm_mday, r.tm_year)
    time_stamp = "{:2d}:{:0>2}:{:0>2}{}".format(hours, r.tm_min, r.tm_sec, ampm)

  return date_stamp, time_stamp, ampm

#def normalized_rms(values):
#  minbuf = int(sum(values) / len(values))
#  return int(sqrt(sum(float(sample - minbuf) *
#    (sample - minbuf) for sample in values) / len(values)))

# Set display labels
def set_labels():
  temperature_label.text = "Temperature:"
  humidity_label.text = "Humidity   :"
  barometric_label.text = "Barometric :"
  altitude_label.text = "Altitude   :"
  magnetic_label.text = "Magnetic   :"
  acceleration_label.text = "Accel      :"
  gyroscope_label.text = "Gyroscope  :"
  sound_label.text = "Sound level:"

def update_temperature(update_console=True):
  celsius = sht31d.temperature
  fahrenheit = celsius * 1.8 + 32
  temperature_data.text = temperature_format.format(fahrenheit, celsius)
  humidity = sht31d.relative_humidity
  humidity_data.text = humidity_format.format(humidity)

  if update_console:
    print("Temperature :" + temperature_format.format(fahrenheit, celsius))
    print("Humidity    :" + humidity_format.format(humidity))

def update_barometric(update_console=True):
  barometric = bmp280.pressure
  barometric_data.text = barometric_format.format(barometric)
  altitude = bmp280.altitude
  altitude_data.text = altitude_format.format(altitude)

  if update_console:
    print("Barometric  :" + barometric_format.format(bmp280.pressure))
    print("Altitude    :" + altitude_format.format(altitude))

def update_imu(update_console=True):
  mag_x, mag_y, mag_z = lis3mdl.magnetic
  acc_x, acc_y, acc_z = lsm6ds33.acceleration
  gyr_x, gyr_y, gyr_z = lsm6ds33.gyro

  # Convert to Gauss
  mag_x_gs = mag_x / 100
  mag_y_gs = mag_y / 100
  mag_z_gs = mag_z / 100

  magnetic_data.text = triple_format.format(mag_x, mag_y, mag_z)
  acceleration_data.text = triple_format.format(acc_x, acc_y, acc_z)
  gyroscope_data.text = triple_format.format(gyr_x, gyr_y, gyr_z)

  if update_console:
    print("Magnetic    :" + triple_format.format(mag_x, mag_y, mag_z) + " uTesla")
    print("Magnetic    :" + triple_format.format(mag_x_gs, mag_y_gs, mag_z_gs) + " gauss")
    print()
    print("Acceleration:" + triple_format.format(acc_x, acc_y, acc_z) + " m/s^2")
    print("Gyroscope   :" + triple_format.format(gyr_x, gyr_y, gyr_z) + " dps")

#def update_sound_level(samples, update_console=True):
#  microphone.record(samples, len(samples))
#  sound_level = normalized_rms(samples)
#  sound_data.text = sound_format.format(sound_level)
#
#  if update_console:
#    print("Sound level :" + sound_format.format(sound_level))

def update_clock(update_console=True):
  date_stamp, time_stamp, ampm = make_time_stamp()
  new_time_stamp = time_stamp[0:5] + time_stamp[len(time_stamp):] + ampm
  datetime_data.text = datetime_format.format(date_stamp, new_time_stamp.strip())

  if update_console:
    print()
    print(datetime_format.format(date_stamp, time_stamp))

# Initialize our font
#font_file = FONTS_PATH + FONT_FILE

#if DEBUG and sd_card_present:
#  print("font_file = '{0}'".format(font_file))

font = terminalio.FONT
#font = bitmap_font.load_font(font_file)
#font.load_glyphs(b'abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ()')

#
#   Labels
#
temperature_label = label.Label(font, text="Temperature:", color=TEXT_COLOR, max_glyphs=25, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y)
splash.append(temperature_label)

humidity_label = label.Label(font, text="Humidity   :", color=TEXT_COLOR, max_glyphs=25, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT)
splash.append(humidity_label)

barometric_label = label.Label(font, text="Barometric :", color=TEXT_COLOR, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT * 2)
splash.append(barometric_label)

altitude_label = label.Label(font, text="Altitude   :", color=TEXT_COLOR, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT * 3)
splash.append(altitude_label)

magnetic_label = label.Label(font, text="Magnetic   :", color=TEXT_COLOR, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT * 4)
splash.append(magnetic_label)

acceleration_label = label.Label(font, text="Accel      :", color=TEXT_COLOR, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT * 5)
splash.append(acceleration_label)

gyroscope_label = label.Label(font, text="Gyroscope  :", color=TEXT_COLOR, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT * 6)
splash.append(gyroscope_label)

#sound_label = label.Label(font, text="Sound level:", color=TEXT_COLOR, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT * 7)
#splash.append(sound_label)

#
#   Data
#
temperature_format = "{0:7.1f}°F / {1:7.1f}°C"
temperature_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=25, scale=TEXT_SCALE, x=DATA_X, y=DATA_Y)
splash.append(temperature_data)

humidity_format = "{0:7.1f}%"
humidity_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=25, scale=TEXT_SCALE, x=DATA_X, y=DATA_Y + DATA_INCREMENT)
splash.append(humidity_data)

barometric_format = "{0:7.1f} millibars"
barometric_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=25, scale=TEXT_SCALE, x=DATA_X, y=DATA_Y + DATA_INCREMENT * 2)
splash.append(barometric_data)    # Subgroup for text scaling

altitude_format = "{0:7.1f} m"
altitude_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=25, scale=TEXT_SCALE, x=DATA_X, y=DATA_Y + DATA_INCREMENT * 3)
splash.append(altitude_data)

#   Magnetic, Accelration, and Gyroscope data are all triples, so use the same format
triple_format = " {0:8.3f} {1:8.3f} {2:8.3f}"

magnetic_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=50, scale=TEXT_SCALE, x=DATA_X, y=DATA_Y + DATA_INCREMENT * 4)
splash.append(magnetic_data)

acceleration_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=50, scale=TEXT_SCALE, x=DATA_X, y=DATA_Y + DATA_INCREMENT * 5)
splash.append(acceleration_data)

gyroscope_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=50, scale=TEXT_SCALE, x=DATA_X, y=DATA_Y + DATA_INCREMENT * 6)
splash.append(gyroscope_data)

#sound_format = "{0:5d}"
#sound_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=50, scale=TEXT_SCALE, x=DATA_X, y=DATA_Y + DATA_INCREMENT * 7)
#splash.append(sound_data)

datetime_format = "It is {0} at {1}"
datetime_data = label.Label(font, text=" ", color=TEXT_COLOR, max_glyphs=50, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y - 5 + (TEXT_INCREMENT * 10) + 20 - 9)
splash.append(datetime_data)

#samples = array.array('H', [0] * 160)

startBaroSeconds = time()
startMagSeconds = startBaroSeconds
startSHTSeconds = startBaroSeconds
startTempSeconds = startBaroSeconds
#startLoggingSeconds = startBaroSeconds
startUpdateTimeSeconds = startBaroSeconds
#startSDCardSeconds = startBaroSeconds

first_run = True
looper = 0

log_file_name = LOGGING_PATH + LOGGING_FILE

try:
  while True:
    blink_NeoPixel(status_light, BLUE) 

    # Check if it is time to do screen and console updates
    seconds = time()
   
    update_temp, temp_min = check_update(seconds, startTempSeconds, UPDATE_TEMP_INTERVAL_MIN)
    update_baro, baro_min = check_update(seconds, startBaroSeconds, UPDATE_BARO_INTERVAL_MIN)
    update_mag, mag_min = check_update(seconds, startMagSeconds, UPDATE_MAG_INTERVAL_MIN)
    update_sht, sht_min = check_update(seconds, startSHTSeconds, UPDATE_SHT_INTERVAL_MIN)
    #update_logging, logging_min = check_update(seconds, startLoggingSeconds, LOGGING_INTERVAL_MIN)
    update_time, time_min = check_update(seconds, startUpdateTimeSeconds, UPDATE_TIME_INTERVAL_MIN)
    #update_sdcard, sdcard_min = check_update(seconds, startSDCardSeconds, UPDATE_SDCARD_INTERVAL_MIN)
 
    looper += 1

    '''   
    if DEBUG:
      print()
      print("Elapsed times: temp_min = {0:4.2f} ({1:4.2f}), baro_min = {2:4.2f} ({3:4.2f}), mag_min = {4:4.2f} ({5:4.2f}), sht_min = {6:4.2f} ({7:4.2f})".format(temp_min, UPDATE_TEMP_INTERVAL_MIN, baro_min, UPDATE_BARO_INTERVAL_MIN, mag_min, UPDATE_MAG_INTERVAL_MIN, sht_min, UPDATE_SHT_INTERVAL_MIN))
      print("    sdcard_min = {0:4.2f}, ({1:4.2f}), time_min = {2:4.2f} ({3:4.2f})".format(sdcard_min, UPDATE_SDCARD_INTERVAL_MIN, time_min, UPDATE_TIME_INTERVAL_MIN))
      print("Updates: temp = {0}, baro = {1}, mag = {2}, sht = {3}".format(update_temp, update_baro, update_mag, update_sht))
      print("    sdcard = {0}, update_time = {1}".format(update_sdcard, update_time))
      # sleep(3.0)
     
      if first_run or update_mag:
        if update_mag:
          print()
          print("===========================================================================================================")
          print("Loop {0:12d}".format(looper))
       
        if update_baro:
          print()
          sleep(2)
    '''

    '''
    if first_run or update_temp or update_baro or update_mag or update_time:
      # Write everything to the console
      print()
      print("Readings from the Feather nRF52840 Sense Sensors")
      print("------------------------------------------------")
    '''

    if first_run or update_temp:
      startTempSeconds = time()
      update_temperature()

    if first_run or update_temp or update_baro:
      startBaroSeconds = time()
      update_barometric()

    if first_run or update_temp or update_baro or update_mag:
      startMagSeconds = time()
      update_imu()
    #  update_sound_level(samples)

      # Update console
      if update_temp:
        print()

    if first_run or update_sht:
      startSHTSeconds = time()
         
      print()
      print("Turning the SHT31-D heater on")

      sht31d.heater = True
      sleep(0.5)
      print("SHT31-D Heater status = {0}".format(sht31d.heater))
      sht31d.heater = False
      sleep(0.5)
      print("SHT31-D Heater status = {0}".format(sht31d.heater))

    if first_run or update_time:
      startUpdateTimeSeconds = time()
      update_clock()

    if first_run:
      first_run = False
except KeyboardInterrupt:
  # Release any resources currently in use for the displays
  release_displays()
  print()
  print("Exiting")
  print()

8-Dale

geekguy
 
Posts: 302
Joined: Tue Sep 10, 2013 2:43 pm
Location: Beaverton, OR

Re: Unfreezing libraries

by danhalbert on Thu Apr 01, 2021 8:53 pm

Some space-saving ideas:

`sdcardio` is built into the firmware. That is a replacement for the `adafruit_sdcard` module.

For the repetitive code, you could write a routine to do the long calls, and only pass in what's changed, e.g.:

Code: Select all | TOGGLE FULL SIZE
def make_label(text, index):
    new_label = label.Label(font, text=text, color=TEXT_COLOR, max_glyphs=25, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT * index)   
    splash.append(new_label)
    return new_label

temperature_label = make_label("Temperature:", 0)
humidity_label = make_label("Humidity   :", 1)
# etc.


I do suspect that one problem here might be that you are importing so many sensor libraries. Some of these libraries are quite complete, and contain a lot of code to support all the functionality of a particular sensor. That makes them large. You could see how big their source code is, and/or import them one at a time in the REPL, and see how `gc.mem_free()` changes at each import. Make sure you are importing the .mpy versions, so CircuitPython doesn't have to compile the .py files. You will get the RAM back, but it can get fragmented during compilation.

If some libraries are large, you could consider trimming them down and including only the functionality you need.

danhalbert
 
Posts: 2427
Joined: Tue Aug 08, 2017 12:37 pm

Re: Unfreezing libraries

by geekguy on Mon Apr 05, 2021 4:36 pm

Hi,

OK, I see where using a function for making my labels and such might help. I will try that. It looks like a routine with five parameters would work fine. This is more of a learning project because I wanted to get familiar with displayio. It has served that purpose very well! Now, I can move on to more optimizations and more serious (as serious as tinkering can be) stuff.

Thanks!

8-Dale

geekguy
 
Posts: 302
Joined: Tue Sep 10, 2013 2:43 pm
Location: Beaverton, OR

Re: Unfreezing libraries

by geekguy on Tue Apr 06, 2021 6:16 pm

danhalbert wrote:For the repetitive code, you could write a routine to do the long calls, and only pass in what's changed, e.g.:

Code: Select all | TOGGLE FULL SIZE
def make_label(text, index):
    new_label = label.Label(font, text=text, color=TEXT_COLOR, max_glyphs=25, scale=TEXT_SCALE, x=TEXT_X, y=TEXT_Y + TEXT_INCREMENT * index)   
    splash.append(new_label)
    return new_label

temperature_label = make_label("Temperature:", 0)
humidity_label = make_label("Humidity   :", 1)
# etc.

I did implement this idea. It definitely saved some memory! I was able to add the code to handle the sound level back into the script, including importing array and audiobusio. I had to remove that code before.

Now, I have 16 calls to my new_label(font, text, text_color, xPos, yPos) routine to create all the text labels and data places. Thanks much for this great idea! I am sure I will find many more places I can use it.

My next task will be to add the SD Card stuff back in for logging readings. I will see how far I can push memory. :)

8-Dale

geekguy
 
Posts: 302
Joined: Tue Sep 10, 2013 2:43 pm
Location: Beaverton, OR

Re: Unfreezing libraries

by geekguy on Wed Apr 07, 2021 7:59 am

Hi again,

danhalbert wrote:Some space-saving ideas:

Since I was able to recover some RAM, of course, I thought of something else (loop count) it would be very nice to have displayed. :) ;)

Here is my current script:
Code: Select all | TOGGLE FULL SIZE
"""
  This script is based on the example script in the Adafruit Learn Guide at
    https://learn.adafruit.com/using-circuitpython-displayio-with-a-tft-featherwing/3-5-tft-featherwing

  This is a greatly expanded version of that script that reads all the sensors of the Feather
    nRF52840 Sense and displays everything on the 3.5" TFT Featherwing.

  Adafruit invests a lot of time and resources providing this open source code.
    Please support Adafruit and Open Source Hardware by purchasing products from Adafruit!
"""

import board
import array
from time import sleep, time
from math import atan, atan2, pi, sqrt
from audiobusio import PDMIn
from terminalio import FONT
from displayio import Bitmap, FourWire, Group, Palette, TileGrid, release_displays
from neopixel import NeoPixel
from digitalio import DigitalInOut
from adafruit_display_text import label
from adafruit_hx8357 import HX8357
from adafruit_bitmap_font import bitmap_font
from adafruit_bmp280 import Adafruit_BMP280_I2C
from adafruit_lis3mdl import LIS3MDL
from adafruit_lsm6ds.lsm6ds33 import LSM6DS33
from adafruit_sht31d import SHT31D

#   For the Real Time Clock on the Adalogger FeatherWIng
from adafruit_pcf8523 import PCF8523

# For the SD Card
#from sdcardio import SDCard
#from storage import VfsFat

TEXT_X = 10
TEXT_Y = 15
TEXT_INCREMENT = 28

DATA_X = 150
DATA_Y = 15
DATA_INCREMENT = 28

TEXT_SCALE = 2

TIME_FORMAT_24 = False                  # True = 24 hour, False = 12 hour
WRITE_TO_CONSOLE = True                 # True = To write to the REPL console

TFT_CS_PIN = board.D9
TFT_DC_PIN = board.D10

#   Task check intervals
UPDATE_TEMP_INTERVAL_MIN = 0.5          # Once every 6.5 minutes (0.5 is for development testing)
UPDATE_BARO_INTERVAL_MIN = 2.5          # Once every 2.5 minutes
UPDATE_MAG_INTERVAL_MIN = 0.001         #167        # Once every second
UPDATE_TIME_INTERVAL_MIN = 1.0          # Once every minute
UPDATE_SHT_INTERVAL_MIN = 0.3333        # Once every 20 seconds
CHECK_SD_INTERVAL_MIN = 2.0             # Every 2 minutes

#   Standard RGBW colors - taken from https://www.rapidtables.com/web/color/RGB_Color.html
BLUE          = (0x00, 0x00, 0x4B, 0x00)
PURPLE        = (0x80, 0x00, 0x80, 0x00)
MEDIUM_GREEN  = (0x00, 0x5F, 0x00, 0x00)
BRIGHT_YELLOW = (0xFF ,0xFF ,0x00, 0x00)

WHITE         = (0x00, 0x00, 0x00, 0x7F)
BLACK         = (0x00, 0x00, 0x00, 0x00)

BACKGROUND_COLOR = MEDIUM_GREEN
TEXT_COLOR = BRIGHT_YELLOW

# Release any resources currently in use for the displays
release_displays()

# Initialize the onboard NeoPixel
status_led = NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
status_led.fill(BLACK)

# Initialize the I2C bus
i2c = board.I2C()

# Initialize the SPI bus
spi = board.SPI()

#   Initialize the Real Time Clock on the Adalogger FeatherWIng
rtc = PCF8523(i2c)

# Initialize the TFT display and touch screen
display_bus = FourWire(spi, command=TFT_DC_PIN, chip_select=TFT_CS_PIN)
display = HX8357(display_bus, width=480, height=320)

# Initialize the sensors
bmp280 = Adafruit_BMP280_I2C(i2c)

# Set this to sea level pressure in hectoPascals at your location for accurate altitude reading.
bmp280.sea_level_pressure = 1013.25

lis3mdl = LIS3MDL(i2c)
lsm6ds33 = LSM6DS33(i2c)
sht31d = SHT31D(i2c)

microphone = PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
  sample_rate=16000, bit_depth=16)

samples = array.array('H', [0] * 160)

# Make the display context
splash = Group(max_size=20)

# Draw the TFT background
color_bitmap = Bitmap(480, 320, 1)
color_palette = Palette(1)
color_palette[0] = BACKGROUND_COLOR

bg_sprite = TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)

display.show(splash)

def minutes(seconds, start_seconds, decimal=0):
  #   60 seconds * 1000 ms = 1 minute
 
  return (seconds - start_seconds) / 60

def check_for_update(seconds, start_seconds, check_min):
  update = False
  curr_min = minutes(seconds, start_seconds, 2)

  if curr_min >= check_min:
    update = True 

  return update, curr_min

def blink_NeoPixel(pixel, color, wait=0.2, cycles=1):
  for cy in range(cycles):
    #if DEBUG:
    #  print("Cycle #{0}".format(cy))

    pixel.fill(color)
    sleep(wait)
    pixel.fill(BLACK)
    sleep(wait)

def new_label(lFont, lText, lColor, lX, lY):
  new_label = label.Label(lFont, text=lText, color=lColor, max_glyphs=25, scale=TEXT_SCALE, x=lX, y=lY)

  return new_label
 
def create_time_stamp(hr24=False):
  ampm = ""
  r = rtc.datetime

  hours = r.tm_hour

  if hr24:
    date_stamp = "{0:0>4}{1:0>2}{2:0>2}".format(r.tm_year, r.tm_mon, r.tm_mday)
    time_stamp = "{0:2d}{1:0>2}{2:0>2}".format(hours, r.tm_min, r.tm_sec)
  else: 

    if hours < 12:
      ampm = "AM"
    else:
      ampm = "PM"
     
    if hours > 12:
      hours = hours - 12

    if hours == 0 and not hr24:
      hours = 12

    date_stamp = "{0:0>2}/{1:0>2}/{2:0>4}".format(r.tm_mon, r.tm_mday, r.tm_year)
    time_stamp = "{0:2d}:{1:0>2}{2}".format(hours, r.tm_min, ampm)

  return date_stamp, time_stamp, ampm

def normalized_rms(values):
  minbuf = int(sum(values) / len(values))
  return int(sqrt(sum(float(sample - minbuf) *
    (sample - minbuf) for sample in values) / len(values)))

def update_temperature(write_to_console=True):
  celsius = sht31d.temperature
  fahrenheit = celsius * 1.8 + 32
  temperature_data.text = temperature_format.format(fahrenheit, celsius)
  humidity = sht31d.relative_humidity
  humidity_data.text = humidity_format.format(humidity)

  if write_to_console:
    print("Temperature :" + temperature_format.format(fahrenheit, celsius))
    print("Humidity    :" + humidity_format.format(humidity))

def update_barometric(write_to_console=True):
  barometric = bmp280.pressure
  barometric_data.text = barometric_format.format(barometric)
  altitude = bmp280.altitude
  altitude_data.text = altitude_format.format(altitude)

  if write_to_console:
    print("Barometric  :" + barometric_format.format(bmp280.pressure))
    print("Altitude    :" + altitude_format.format(altitude))

def update_imu(write_to_console=True):
  mag_x, mag_y, mag_z = lis3mdl.magnetic
  acc_x, acc_y, acc_z = lsm6ds33.acceleration
  gyr_x, gyr_y, gyr_z = lsm6ds33.gyro

  # Convert to Gauss
  mag_x_gs = mag_x / 100
  mag_y_gs = mag_y / 100
  mag_z_gs = mag_z / 100

  magnetic_data.text = triple_format.format(mag_x, mag_y, mag_z)
  acceleration_data.text = triple_format.format(acc_x, acc_y, acc_z)
  gyroscope_data.text = triple_format.format(gyr_x, gyr_y, gyr_z)

  if write_to_console:
    print("Magnetic    :" + triple_format.format(mag_x, mag_y, mag_z) + " uTesla")
    print("Magnetic    :" + triple_format.format(mag_x_gs, mag_y_gs, mag_z_gs) + " gauss")
    print("---")
    print("Acceleration:" + triple_format.format(acc_x, acc_y, acc_z) + " m/s^2")
    print("Gyroscope   :" + triple_format.format(gyr_x, gyr_y, gyr_z) + " dps")
    print()

def update_sound_level(samples, write_to_console=True):
  microphone.record(samples, len(samples))
  sound_level = normalized_rms(samples)
  sound_data.text = sound_format.format(sound_level)

  if write_to_console:
    print("Sound level :" + sound_format.format(sound_level))

def update_clock(write_to_console=True, hour24=False):
  date_stamp, time_stamp, ampm = create_time_stamp(hour24)
  new_time_stamp = time_stamp[0:5] + time_stamp[len(time_stamp):]
  datetime_data.text = datetime_format.format(date_stamp, new_time_stamp.strip(), ampm)

  if write_to_console:
    print(datetime_format.format(date_stamp, time_stamp, ampm))
    print()

def update_looper(looper, write_to_console=True):
  looper_data.text = looper_format.format(looper)

  if write_to_console:
      print("Loop: {0:6d} ++++++++++++++++++++++++++++++++++++".format(looper))
      print()

# This is the stock terminalio font
font = FONT

#
#   Labels
#
temperature_label = new_label(font, "Temperature:", TEXT_COLOR, TEXT_X, TEXT_Y)
splash.append(temperature_label)

humidity_label = new_label(font, "Humidity   :", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT)
splash.append(humidity_label)

barometric_label = new_label(font, "Barometric :", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT * 2)
splash.append(barometric_label)

altitude_label = new_label(font, "Altitude   :", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT * 3)
splash.append(altitude_label)

magnetic_label = new_label(font, "Magnetic   :", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT * 4)
splash.append(magnetic_label)

acceleration_label = new_label(font, "Accel      :", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT * 5)
splash.append(acceleration_label)

gyroscope_label = new_label(font, "Gyroscope  :", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT * 6)
splash.append(gyroscope_label)

sound_label = new_label(font, "Sound level:", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT * 7)
splash.append(sound_label)

datetime_label = new_label(font, "Date / Time:", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT * 8)
splash.append(datetime_label)

looper_label = new_label(font, "Loop Count :", TEXT_COLOR, TEXT_X, TEXT_Y + TEXT_INCREMENT * 10)
splash.append(looper_label)

#
#   Data
#
temperature_format = "{0:7.1f}°F / {1:7.1f}°C"
temperature_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y)
splash.append(temperature_data)

humidity_format = "{0:7.1f}%"
humidity_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT)
splash.append(humidity_data)

barometric_format = "{0:7.1f} millibars"
barometric_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT * 2)
splash.append(barometric_data)    # Subgroup for text scaling

altitude_format = "{0:7.1f} m"
altitude_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT * 3)
splash.append(altitude_data)

#   Magnetic, Accelration, and Gyroscope data are all triples, so use the same format
triple_format = " {0:8.3f} {1:8.3f} {2:8.3f}"

magnetic_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT * 4)
splash.append(magnetic_data)

acceleration_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT * 5)
splash.append(acceleration_data)

gyroscope_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT * 6)
splash.append(gyroscope_data)

sound_format = "{0:5d}"
sound_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT * 7)
splash.append(sound_data)

datetime_format = "  {0} at {1} {2}"
datetime_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT * 8)
splash.append(datetime_data)

looper_format = "{0:7d}"
looper_data = new_label(font, " ", TEXT_COLOR, DATA_X, DATA_Y + DATA_INCREMENT * 10)
splash.append(looper_data)

startBaroSeconds = time()
startMagSeconds = startBaroSeconds
startSHTSeconds = startBaroSeconds
startTempSeconds = startBaroSeconds
startUpdateTimeSeconds = startBaroSeconds

first_run = True
looper = 0

if WRITE_TO_CONSOLE:
  print()

try:
  while True:
    blink_NeoPixel(status_led, BLUE) 

    # Check if it is time to do screen and console updates
    seconds = time()
   
    update_temp, temp_min = check_for_update(seconds, startTempSeconds, UPDATE_TEMP_INTERVAL_MIN)
    update_baro, baro_min = check_for_update(seconds, startBaroSeconds, UPDATE_BARO_INTERVAL_MIN)
    update_mag, mag_min = check_for_update(seconds, startMagSeconds, UPDATE_MAG_INTERVAL_MIN)
    update_sht, sht_min = check_for_update(seconds, startSHTSeconds, UPDATE_SHT_INTERVAL_MIN)
    update_time, time_min = check_for_update(seconds, startUpdateTimeSeconds, UPDATE_TIME_INTERVAL_MIN)
 
    looper += 1

    if first_run or update_temp:
      startTempSeconds = time()
      update_temperature(WRITE_TO_CONSOLE)

    if first_run or update_temp or update_baro:
      startBaroSeconds = time()
      update_barometric(WRITE_TO_CONSOLE)

    if first_run or update_temp or update_baro or update_mag:
      startMagSeconds = time()
      update_imu(WRITE_TO_CONSOLE)
      update_sound_level(samples, WRITE_TO_CONSOLE)
      update_looper(looper, WRITE_TO_CONSOLE)

    if first_run or update_time:
      startUpdateTimeSeconds = time()
      update_clock(WRITE_TO_CONSOLE, TIME_FORMAT_24)

      print()

    if first_run or update_sht:
      startSHTSeconds = time()

      if WRITE_TO_CONSOLE:
        print("Turning the SHT31-D heater on")

      sht31d.heater = True
      sleep(0.5)

      if WRITE_TO_CONSOLE:
        print("SHT31-D Heater status = {0}".format(sht31d.heater))

      sht31d.heater = False
      sleep(0.5)

      if WRITE_TO_CONSOLE:
        print("SHT31-D Heater status = {0}".format(sht31d.heater))
        print()

    if first_run:
      first_run = False
except KeyboardInterrupt:
  # Release any resources currently in use for the displays
  release_displays()
#  print()
#  print("Exiting")
#  print()
Can you see any place where I can recover a bit more RAM? I have removed all the imports I do not need, but can not see anything else I can remove. I am out of ideas.

8-Dale

geekguy
 
Posts: 302
Joined: Tue Sep 10, 2013 2:43 pm
Location: Beaverton, OR

Please be positive and constructive with your questions and comments.