Memory Analysis for Microcontroller-Based Projects - Page 1
Forum section: electronics / general

author: Dadu@
author_rank: Contributor
timestamp: February 10, 2024, 11:18:01 am
content: I had an interview where the interviewer asked me how I analyze memory usage, including the text, data, stack, and heap for my project. Since I was unfamiliar with memory analysis, I admitted that I didn't have knowledge about it. I never considered analyzing how much memory each section consumesI used a PIC microcontroller for my project, and I wasn't concerned about memory usage. I did Google and found that there are tools available to identify memory consumption for each section (for Linux PC).Can anyone explain why memory analysis is important in a project and share your techniques or methods for analyzing memory usage, including text, data, stack, and heap, for microcontroller- ( ARM, PIC, ATMEL) based projects? How do you analysis memory usage for your projects ?

author: tggzzz
author_rank: Super Contributor
timestamp: February 10, 2024, 11:41:49 am
content: Quote from: Dadu@ on February 10, 2024, 11:18:01 amLook at the linkshere, and then we will be able to give more useful answers.

author: jpanhalt
author_rank: Super Contributor
timestamp: February 10, 2024, 11:49:53 am
content: That seems like a tall order given the variety of microcontrollers available I am only familiar with 8-bit PICs and vaguely with the PIC24F line. With the 8-bit Harvard structure, I check memory usage while programming using a "disassembly" option or "watch" tab in simulation. With PIC 8-bit devices paging and banking come into play a lot. However, once I am done with coding, I can't see much use for monitoring memory during operation. (I code in Assembly.)Did the question only relate to micros or to any computer?

author: jwet
author_rank: Frequent Contributor
timestamp: February 11, 2024, 04:27:31 am
content: That's a bit pedantic I think. It really depends on what you're developing and what the platform is. If you're using a "real" OS, even some lean embedded Linux, it does all this stuff internally and there are calls to get to it.For deeply embedded stuff in C or assembly on microcontrollers, you have "complete" control of memory use either with statically defined allocation or run time with malloc type functions. Its just paying attention to defining default, static, etc.In the smallest systems, maximum stack size needs some attention at least during development. Its wise to put a few guard bytes with a known pattern just out of your expected stack span and check to see if they get stepped on. Usually, you can look at how many levels deep, your call stack goes. Interrupts can make this a little harder but you don't re-enable interrupts until the last is services. Watch out for IRQ proliferation.In the small embedded world, an easy problem. In heavier apps, this is one of many reasons you use a real OS.

author: hans
author_rank: Super Contributor
timestamp: February 11, 2024, 09:53:34 am
content: You can use a linker's Map file to see which functions have large FLASH usage.It could be that some call accidently includes printf, memory allocation or floating point. It can tell a lot about how a program is constructed. However, don't overdo these things.. getting something to work (or fail) fast can be more important than doing it 100% clean straight away.(Premature optimization)RAM usage is a bit harder to quantify, and depends on the architecture and compiler settings. For PICs, these MCUs have compiled stacks since the core uses a hardware callstack and can't allocate a register as stackpointer. For more modern architectures, there are also tools to analyze that. For example if you run a RTOS you will need to preallocate stack sizes for each task, and so it can become necessary to measure the "stack water level" to quantify how much space is left.It can even be a fun exercise to write your own call stack analysis tool based on extra compile outputs you can enable in GCC ..

author: iMo
author_rank: Super Contributor
timestamp: February 11, 2024, 01:31:27 pm
content: That was a good question as the answer indicates whether the applicant has ever worked with MCUs and compilers, like C etc.Especially with MCUs you do mess with the linker scripts pretty often, telling the "linker" how to organize the memory and its usage..https://medium.com/@csrohit/writing-linker-script-for-stm32-arm-cortex-m3-%EF%B8%8F-fdc2acaaddcchttps://blog.thea.codes/the-most-thoroughly-commented-linker-script/..

author: radiolistener
author_rank: Super Contributor
timestamp: February 11, 2024, 04:20:24 pm
content: Quote from: Dadu@ on February 10, 2024, 11:18:01 amit is important because MCU has limited memory and if your code consume a lot of stack, it can overwrite some data on data memory sections. I analyze memory usage with filling stack with 0xff and then checking memory state after execution of code through different path and conditions a lot of times. If it overwrite last bytes in stack area then there is a problem with not enough space for the stack. There is always needs some reserve to allow at least interrupt with pushing registers at any point of your code execution...

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 12, 2024, 02:15:32 am
content: When usingELF-based toolchains (AVR, ARMs; whenever compiling clang or GCC, really), the concept of asectionis key. (In the simplest of terms, it is just a container, for code/data/stuff; each section does have a set of flags to describe what it contains. In object files –– those compiled but not linked yet ––, sections do not have specific memory addresses yet; everything is relative to the beginning of the/a section.)Linker script is what describes where each section will end up, and can even discard entire sections. (It can also pull out specific symbols from specific sections.)objdump -hobject-file.owill describe the size of each section, alignment, flags, and even where the data starts in the object file. For linked files (executables, libraries, firmware images before conversion to hex/raw format), it also tells where in the process memory address space that section will reside in.Because relocation references –– for example, a function name your code calls or takes the address of –– are based on sections in ELF files, and compilers can automatically put each function in their own section (-ffunction-sectionsfor gcc and clang), unused functions can be automatically pruned (left out) by linkers because their sections won't occur in any relocation record. In other words, the "include only used functions from a statically compiled library" feature relies on the library being compiled with each individual function in their own section. These will be named using a pattern, so that the linker will simply hoover them all up just as efficiently as if they were all in a single section. Similar option (-fdata-sections) exists for data (global variables and structures).The programmer can also specify a section for each function or data object, via a compiler-specificsection attribute(even in assembly language; this is not specific to C, and only relies on the ELF object file format). This means that one can construct an array (of pointers, or of structures) that is automatically collected from all object files when linking into a linear array. The downside is that there is no way to force a specific order for the elements from different files. (From a single file, their order is fixed.) But, when you have say a hardware interrupt table, just declaring it as a suitable structure or pointer array (depending on exactly how the hardware implements the table), and putting it into a specific section, lets the linker script put it in the exact correct location in memory in the firmware image.Similarly, if you have compiled your object file with each function in their own section, you can write a simple script to parse the output ofobjdump -rtobject-file(s), and generate the possible call graph of those functions, including via function pointers in unmodified structures. (Again, because ELF relocation records are based on sections, the relocation record section name identifies the target function, so no disassembly or memory lookups is needed.)I've found this extremely useful with state machines, for example visualizing exactly the menu structure, when the menu entries are defined as structures in flash pointing to each other and/or to transition functions and event handlers.Because of all this, Iclaimthat analysing memory use based on the division to code, data, uninitialized/zero-initialized data, stack, and heap, is an oversimplification. It is better to understand the ELF object file properties and section shenanigans underneath, because they can provide exact statistics and interrelationships (like why calling some function drags inprintf()even whenprintf()will never be called in practice). Stack is a bit different, because real world sampling can tell thetypicalstack use, you kinda-sorta need static analysis to find out the worst case; and add the worst case interrupt chain stack frame sizes on top –– making stack the most annoying to estimate/analyze, unless you have a package that can do it at the source level. (The fact that the typical effect of stack overflow is corruption of unrelated variables, and that even canaries only provide a (high) statistical detection probability, makes stack size one of the most annoying problems to optimize.)If you have Linux or WSL2 available, you might findthis answer and example coderegarding the section array use informative. From there, I recommend you simply dive in to existing linker scripts (those used in Arduino cores for various microcontrollers, for example, you can find Teensy LC (ARM Cortex-M0+)here), keeping a browser window open to thebinutils linker script documentation. The learning curve is steep, but worth it, if you want to do serious embedded or microcontroller development.