0
\$\begingroup\$

I'm using a Feather M0 Express from Adafruit with a DS3231 Precision RTC FeatherWing for a custom clock project that displays time with LEDs. The clock works as follows:

  1. When the clock gets plugged to a power source, the MCU program gets the time from the RTC shield;
  2. it saves it on the board, and uses from that point onwards the internal clock of the MCU.

The time gets displayed correctly, but after a few hours, the LEDs stop working. I've run tests with the garbage collector and found out the board runs out of memory space. I have to hit the reset button to make it work again.

I've tried to use the garbage collector with gc.mem_free() but it doesn't seem to free up any of the allocated memory.

Any tips on how to cleanly free up memory space on those boards?

Thanks in advance!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ As a general rule on a simple embedded system you don't use dynamic memory allocation for exactly the reason you have found. \$\endgroup\$ – Steve G Jan 16 '20 at 11:48
  • 4
    \$\begingroup\$ Writing code in Python for SAMD21 is madness. The obvious solution is to port the code to C, then get rid of the dynamic allocation. Neither Python nor dynamic allocation fills any purpose in low-end Cortex M applications. \$\endgroup\$ – Lundin Jan 16 '20 at 12:06
7
\$\begingroup\$

I love Python. Most of the programs I write for my own use on my PC at home are written in Python.

That said, I don't use Python in my microcontroller based projects.

Python is intended for use where you have memory and processor horsepower behind it. It dynamically allocates memory, and takes care of cleaning up objects your program doesn't need anymore.

All of that makes Python a poor choice for use on small systems with limited RAM.

CircuitPython presumably tries to get around those limitations, but as you've discovered there are limits to how far that goes.

How you write your software will influence how much RAM your program uses. A change in style so as to prevent wasted RAM will probably be more effective than trying to clean up after a messy program.

You should try to never create new objects while your program is running.

Your biggest culprits are probably strings. Python strings are immutable. If you try to change one, what happens in the background is that a new string is created and assigned to the original variable - the old string stays in memory until the garbage collector gets it. If you are doing any string operations at all, they can easily add up. Worse, they can slice the available RAM into little pieces such that while the total free space may be large enough there might be no place large enough in one piece to hold your string. That'll cause an exception just as surely as when there's no free RAM left at all.

That applies to all objects. Lists, arrays, dictionary, class instances, etc. will all have similar problems.

About the only thing you can trust to not dynamically allocate storage are integers. You'll want to work using integers as much as possible. Read bytes from the RTC, and store them as integers. Use integers to do any processing you need to do in order to generate the clock display data, and use integers while writing to the display.

If you stick to just those things that will work reliably without wasting RAM, then you'll find that most of the things that make Python nice can't be used on your little processor - but, you'll still have all those nice things there while you are programming and they will tempt you to use them. You have to continuously remember what things you can't use. It makes programming tougher.

The idea behind using Python for microprocessors seems to be to make it easier for beginners to get started. It does, sort of. At the beginning, you have a nice flat easy to handle learning curve - until you hit a brick wall that will require either massive tricks to get around or require learning a new language.

Better to switch to a language that works well on the microcontroller to start with. Programs on a microcontroller are generally small, with fairly limited functionality. You don't need all the niceness of Python if all you are doing is reading a few bytes from one piece of hardware and writing a few bytes to another piece of hardware - and that's really all your clock program does.

There's IDEs for most microcontroller families available. They usually include assistance for working directly with the hardware ports. Pick a system you like the looks of, and learn to use it.

The Arduino and related IDE work well for beginners (though it has some traps of its own.) There are plenty of alternatives out there, though.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ At the beginning, you have a nice flat easy to handle learning curve - until you hit a brick wall that will require either massive tricks to get around or require learning a new language. - excellent summary. \$\endgroup\$ – awjlogan Jan 16 '20 at 13:00
  • 1
    \$\begingroup\$ You're perfectly right, as a beginner CircuitPython sounds very attractive if you're already familiar with the language. I noted your remarks about the creation of objects during runtime and the big impact of immutable ones on the RAM. Thanks for the tips! Back to the C++ tutos then... 😁 \$\endgroup\$ – garys Jan 16 '20 at 17:20
  • \$\begingroup\$ C++ is not C, try C for mcus. C++ has its own overhead as well, not as bad as python/java but still... \$\endgroup\$ – old_timer Jan 16 '20 at 19:18
  • \$\begingroup\$ C++ is definitely not C, and has many advantages (and zero overhead) compared to C when used appropriately. \$\endgroup\$ – Wouter van Ooijen Jan 17 '20 at 19:31
0
\$\begingroup\$

gc.mem_free() will "Return the number of bytes of available heap RAM" source

I think you are looking for gc.collect(), which will "Run a garbage collection"

CircuitPython is a fork of MicroPython and uses the same (or very similar) gc module as described in the source link. Heap memory will get collected automatically when needed once there are no more references to the object, but you can accelerate collection at times using gc.collect(). Other than that, keep your code clean, de-init objects you no longer need, and minimize heap memory allocations when possible.

You can also catch memory allocation errors with a try: ... except: construct, and take appropriate action.

\$\endgroup\$
0
\$\begingroup\$

Supporting @pseudon comments here. These are suggested work arounds.

Using try:/except: to deal with the issues and then clean it up with gc.collect etc, can work and often does. I think we should be careful with any potential FUD about tools.

If you are a professional developer and working on production designs I tend to agree that Python on microcontrollers adds an additional layer of uncertainty in development that as described here often can show up late in the process and increase risk. It's not a good strategy in this context.

However as prototyping tool, even for first product prototypes (pre-production) with small interdisciplinary teams where agility is required that Python everywhere (ML, webapp, device, etc.) means that the whole team is able to understand and participate,Python + Arduino is a potential game changer if applied right. This also applies to any discipline that is not specialized and working everyday as a developer.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.