Embedded Systems Software (ESS), memory.

Rodrigo Peixoto
5 min readJul 15, 2020

Hi everyone, let’s keep it going. Today we talk about ESS memory, which is a crucial topic for this area. I would suggest Elicia’s book¹ [Making Embedded Systems], which I appreciate a lot, for further reading.

Photo by Hello I’m Nik 🎞 on Unsplash

When talking about memory for embedded systems, we go back to the 90s and see the floppy [1.44MB] as a proper measurement of available space. For ESS we usually [almost always] have less than this for Flash [our HD], and for RAM we have few tens of kilobytes. It is essential to know how to use this efficiently. Low-level programming will always be around because it enables us to build cheaper devices with minimum resources.

Let’s talk about static [referring to stack] and dynamic [referring to heap] allocation. I’m not particularly eager to use these terms. I prefer using stack and heap directly instead. We can do a lot of dynamic maneuvers on the stack [you’ll see soon]. Stack follows the data structure storage manner, which comes first goes lastly. The heap [is still a data structure] is a memory’s metadata related to the size of the free allocatable piece of memory at the shared memory pool.

Elicia on her book¹ suggests not using heap [dynamic allocation] for embedded systems for three reasons:

  1. Using heap to allocate memory we a wasting memory with metadata[heap and something more];
  2. Allocation and deallocation of memory on the heap will waste some processor cycles. Remember the metadata must be updated every memory addition or removal;
  3. The last and not less important is the memory fragmentation based on the nature of adding different pieces of data in slots differents. Soon some free memory will be wasted because of that.

I would add other reasons:

  1. If the code is not well-written, memory leaks should happen, which is terrible on ESS thinking about the little memory we have;
  2. In ESS, we cannot merely use malloc. It depends on the RTOS or the compiler. Sometimes you must define the size of the memory pool, who can access it, and other information related to it. On Zephyr [a great RTOS we use at Katla and Edge for several projects], you must reserve the memory telling its size. In the end, we have a static memory pool with a fixed size occupying the memory.

The stack works out of the box and never leaks [I’ll talk later about it]. The performance is the best! There is not metadata [only the stack pointer, it depends on the architecture], no fragmentation, no limitations related to heap happens here. For ESS, it provides the best prediction of the amount of memory used. There will be [almost] no surprises. Remember, we now cut bits in half and try to be as lazy as possible [make the MCU stop working]. We are talking about money because battery and memory are expensive. The less, the better!

For now on, I won’t talk about heap allocation anymore. But I will try to show how to use the stack dynamically. The allocation and deallocation on the stack follow the first in first out approach.

Listing 1 — Allocation and deallocation sequence.

The scope [usually the braces] is the existing boundary of a variable. It becomes available at the declaration, and it goes away when the scope finishes. The code on Listing 1 illustrates that way where the variables started to exist at lines 2, 3, and 4 and destroyed at line 5. The scope can be the braces of a statement like an if or while or even just the braces.

There are some places [maybe global scope] variables can be allocated and never used or used once or twice and then not used during the execution time. We should see it as a memory waste [almots a leak]. We need to avoid it.

Listing 2 — Thread anatomy.

Usually [maybe always], we define threads by some function code. The anatomy of a thread is like the code Listing 2. Threads have the setup and the loop portion [like Arduino do]. Variables allocated at the setup will remain allocated [even if you don’t use them] unless you act! The same happens with unused global variables [avoid them].

Listing 3 — Allocation with memory wasting.

Listing 3 illustrates the way to waste memory on the initialization of a thread. Note, we declare and use c at lines 3 and 4, respectively. After that, the memory will be allocated and not used. An alternative way of doing this is to declare c inside a scope (Listing 4 from lines 4 to 7). At the end of the scope, the c memory is automatically deallocated. This is a silly example, but it shows a potential problem and solution. Now [or before], you know a way of freeing memory from the stack like the free is to malloc.

Listing 4 — Allocation without memory wasting.

Some of the tips I’ll tell you now is GCC only [sorry if you use another one]. It is free, stable, and one of the most used compilers for ESS.

If you need to allocate memory like the malloc do but on the stack, you can using GCC extensions [Compound Literals + Designated Initializers]. Listing 5 illustrates an example of using this approach through a macro.

Listing 5 — Allocating memory dynamically on the stack.

At Listing 5, line 2, we have the (uint8_t[]){} is a Compound Literal statement that enables the compiler to allocate and initialize memory anonymously. In this case, it would allocate an array of bytes. The second part [firstlast] = value initializes a range of elements with the same value. Both extensions together enable us to mimic malloc behavior. It is not so useful in standard C coding but can be handy in ESS for creating static global lookup tables with different row sizes.

Listing 6

A good example where to use a similar approach would be in How to declare a struct with a variable data size on the stack? Listing 6 illustrates that. We use the Compound Literals and Arrays of Length Zero extension to make it possible. It seems to be UFO [for someone or everyone], but it is handy to avoid wasting memory with oversized structs/arrays.

We can improve this by using a macro to hide its complexity and add the flexibility of the Variadic Macros. In summary, we are using the compound literals to create an array of an anonymous struct with a payload size different of zero and manipulate that with the data struct. In C, we have the power to do a lot of “strange things” work gracefully. If we mix it with unions, you’d see the real power! The only thing I always say is: You must know what is going on at the code. If it seems weird or unreadable to you, go looking to other solutions. They certainly exist and maybe are better or more beautiful.

I hope you could learn something useful today. Let me know if something on the text is not right or good. Suggestions and discussions are very welcome!

[1] Elecia White. 2011. Making Embedded Systems: Design Patterns for Great Software. O’Reilly Media, Inc.

--

--