STM32L1 - Malloc issue (Beginner Question) - Page 1
Forum section: electronics / general

author: norand
author_rank: Contributor
timestamp: February 20, 2020, 12:40:40 am
content: Hi All,I'm kinda new to C but feel I have a good basic understanding and hence have posted the question here.I am trying to port over some demo code for a Waveshare E-paper display to use for an application in my Graduate Project. For now I just wanted to prove the demo code works but will most likely use aspects of it later on.The issue I am currently having is with use of the following lines:Code:// Create a new image cacheUBYTE *BlackImage;UWORD Imagesize = ((EPD_WIDTH % 8 == 0)? (EPD_WIDTH / 8 ): (EPD_WIDTH / 8 + 1)) * EPD_HEIGHT;if((BlackImage = (UBYTE *)malloc(Imagesize)) == NULL){printf("Failed to apply for memory...\r\n");return -1;}I am pretty sure my understanding of this is correct, but the essentially:A pointer is created for BlackImageThe size of memory to be allocated for Blackimage is decided based of the display dimensionsIn the if statement, BlackImage is assigned the memory size from the above line, and rerturns the pointer value for BlackImage. If this pointer value is NULL (i.e. 0) then the memory has not been allocated.My code is always failing at 'applying this memory'. I assume the reasons are that I do not have enough memory available.. but here are my questions:If I do not have enough memory - where do I look to find this out? (I am using Keil uVision IDE)Can I solve this by shifting memory allocations round? Does malloc store in a specific type of memory? I know the term heap is essientially useable memory, but what type of memory does this correlate to in the uC datasheet?Are there alternative ways to do this? The demo code also includes an array stored in a seperate C file (ImageData.c attached), so why would you use malloc here and not do it the same way?What are the best solutions around this?Thanks in advance for any help! Please correct me where I have stated anything incorrect, I'm here to learn!

author: rstofer
author_rank: Super Contributor
timestamp: February 20, 2020, 01:13:23 am
content: Dynamic allocation occurs on the ‘heap’. A heap handler is not always included. In fact for embedded systems, it is usually left as an exercise for the reader.Look for a function _sbrk() which does the real work. It is sometimes in a file syscalls.cYou should know how much memory your chip has. The heap grows from the bottom up and the stack grows from the top down, they both live in RAM. Most tool chains will display your memory usage. Try Google for help with this.Even if _sbrk() is defined, it is often a ‘weak’ function for your code to overwrite.Try to allocate just s hundred bytes or so and try to use it via the returned pointer.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 20, 2020, 03:29:08 am
content: If malloc() returns NULL, which I kind of assume with the OP's "My code is always failing at 'applying this memory'" (although it's phrased ambiguously), then _sbrk is probably implemented.On systems with just a default _sbrk, malloc() will never return NULL. I've worked on one and that was funny - developers didn't get why malloc() would appear to "succeed", but the code would just crash while corrupting the stack...Of course you need to know how much RAM is available on your particular MCU, that's the very first step here. Take the full reference (STM32L1 is just the family) and look that up.The heap is just free space between the chunk that is statically allocated (for your statically allocated variables), which is the part the linker can tell you about, and the stack.A simple alternative to dynamic allocation is, you guessed it, static allocation. Just declare some variable as an array. But it won't magically give you more space. If there is just not enough RAM to fit your buffer, it won't fit. Period.Then your option is to either go for an MCU with enough RAM, or modify the code so it requires less RAM (you could for instance buffer just part of the display instead of the whole display).

author: norand
author_rank: Contributor
timestamp: February 20, 2020, 02:44:58 pm
content: To clarify, I'm using the STM32L152RB (STM32L-Discovery) which has 16kB of RAM.Code:// Display resolution#define EPD_WIDTH 122#define EPD_HEIGHT 250To me with the function in the original post, this means I need 4kB of RAM? (Unless I'm misunderstood).Looking at the memory map:Total RO Size (Code + RO Data) 10896 ( 10.64kB)Total RW Size (RW Data + ZI Data) 1744 ( 1.70kB)Total ROM Size (Code + RO Data + RW Data) 10924 ( 10.67kB)This shows I'm nowhere near the limit assuming RW is the RAM memory. Assuming the RAM is split between Heep and stack, the startup_stm32xxx.asm files shows:Code:Stack_Size EQU 0x400Heap_Size EQU 0x200This states it is in bytes, and hence there is only 512 bytes available for heap memory? Surely this can't be right.I tried changing replacing the quoted code in the original post with:Code:static UBYTE BlackImage[(((EPD_WIDTH % 8 == 0)? (EPD_WIDTH / 8 ): (EPD_WIDTH / 8 + 1)) * EPD_HEIGHT)];I believe this should give me a zero initialised array (which appears to me the same as what the original code is doing but the display still doesn't seem to do much other than refresh.What are the advantages to it being statically stored as opposed to dynamically?

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 20, 2020, 03:30:57 pm
content: Quote from: norand on February 20, 2020, 02:44:58 pmYep, even a tad less.Quote from: norand on February 20, 2020, 02:44:58 pmYou nailed it! This is an arbitrary choice they made, knowing that most people using those small MCUs will use static rather than dynamic allocation, and if they reserved (by default) more for the heap, that would be less for static allocations. And that would make many people angry as they wouldn't get why they have much less RAM available to them for their variables than expected. See the point...?Quote from: norand on February 20, 2020, 02:44:58 pmYep. Now the display not showing much is probably yet another problem to solve. Could be timing issues, could be incorrect SPI configuration.... (I suppose this display supports SPI?)Quote from: norand on February 20, 2020, 02:44:58 pmAdvantages: 1/ it can't fail at run-time, if you don't have enough RAM, the linker will tell you, 2/ it doesn't entail any extra time for allocation (a malloc() call will take some time to execute), 3/ it's easier to manipulate overall;The advantage of dynamic allocation is that once you don't need that buffer any more, you can free it, thus reclaiming the RAM it takes - that you can further use for other things. With static allocation, the RAM is taken forever. Of course you can mitigate that with static allocation by reusing a given buffer for several uses, which is something that was quite common in the days where RAM was very limited, and dynamic allocation just too expensive/not an option.One drawback of dynamic allocation is that it can end up fragmenting available RAM. There are dynamic allocators that are better than others regarding this, but they always come at a cost.

author: edavid
author_rank: Super Contributor
timestamp: February 20, 2020, 04:19:41 pm
content: The way that array size calculation is written makes my head hurt... an experienced programmer would write it as (EPD_WIDTH + 7)/8 * EPD_HEIGHT.Whenever I see something like that, it's a red flag that the rest of the code will probably be poor...

author: kamtar
author_rank: Regular Contributor
timestamp: February 20, 2020, 04:52:13 pm
content: you really want to try to avoid dynamic allocation in a bare-metal embedded due to various types of runtime bugs. Generally, your embedded program will be working with set hardware and set functions so program design should already fit static-only memory pretty well. Having to use dynamic allocation in this type of settings can be sign of bad design.There are valid reasons when to use dynamic allocation like some device function which you plan to use only sporadically and in a normal mode it can reuse that memory for packet buffer or something. If your design involves some form of pluggable HW extension etc.. but by default its best to try to avoid it.

author: norand
author_rank: Contributor
timestamp: February 20, 2020, 07:06:15 pm
content: >QuoteI like this this is much neater!QuoteThe SPI works as it executes the clearing of the display (first section of code). When I went back and looked - it seems all the functions require a pointer input! So probably why it wasn't getting far. I'm gonna have to have a look at pointers again, as this is something I've never quite got my head around, I'm sure I'll be back if I get stuck!So my takeaways:Dynamic Memory Allocation - generally can be used to free-up precious RAM when not required throughout the program but at the cost of initialisation time at startup, and potential run-time errors. Can be useful to dedicate RAM to a specific task.Static Memory Allocation - Preferred choice for majority of applications, Linker can inform if there will not be enough RAM available, no run time issues, the penalty for this is that the RAM is constantly within use, but can me mitigated by creating a resealable buffer for tasks.Thanks for all the help everyone!

author: rstofer
author_rank: Super Contributor
timestamp: February 21, 2020, 06:38:27 pm
content: A pointer is an address and the address points somewhere. It is the only workable method for using dynamic allocation as malloc() returns a pointer to the block of memory. In the case of static allocation, the pointer points to the first element.unsigned short array;unsigned short *ptr = array; // ptr points to the first element in the arrayhttps://www.geeksforgeeks.org/pointer-array-array-pointer/

author: ajb
author_rank: Super Contributor
timestamp: February 21, 2020, 08:38:40 pm
content: Quote from: norand on February 20, 2020, 07:06:15 pmIf you're really concerned about memory going allocated but unused, there are other ways around that than malloc. As long as certain usages are truly mutually exclusive, it is possible to use a single range of memory for one purpose or another. A reasonable example of this would be supporting two different communication protocols where only one can be used at a time. The cleanest way to do this is probably to encapsulate all of the data (state, buffers, whatever) for each protocol into a struct, and then declare a single union that holds one of each struct.Code:struct {uint32_t txCount;unsigned int rxCount;unsigned char rxBuffer;unsigned char txBuffer;} FooProtocol;struct {uint8_t protocolState;char label;uint8_t nodeAddress;struct endPoints;} BarProtocol;union {struct BarProtocol Bar;struct FooProtocol Foo;} ProtocolData;Your protocol handling code would then access either ProtocolData.Bar.rxCount or ProtocolData.Foo.protocolState (or whatever) depending on which one it handles. Or for convenience you can declare pointers likeCode:struct FooProtocol * FooData = ProtocolData.Foo;All that said, I don't think situations where this is useful are very common, but I would certainly prefer this sort of thing to malloc. In any case, if you are using the same memory region for different purposes, whether explicitly as in my example here or implicitly via malloc you MUST ensure that the different usages are mutually exclusive and that memory is properly initialized when moving from one usage to another.

author: newbrain
author_rank: Super Contributor
timestamp: February 23, 2020, 10:23:14 am
content: Quote from: norand, emphasis mine on February 20, 2020, 07:06:15 pmPlease do both.Pointers are, for C beginners - but sometimes for experts too, one of the hardest and most easily misunderstood topic.In that light, I'd like to underline what seems to be a misconception in your OP:Quote from: norand on February 20, 2020, 12:40:40 amIt seems that you are conflating a pointer (BlackImage, a pointer to UBYTE) with the object it points to (an uninitialized area of memory, if malloc succeeds).This confusion can easily lead to wrong code (or, if one is lucky, code that does not compile).Quote from: norand on February 20, 2020, 12:40:40 amIn your first snippet, BlackImage is an object that can contain addresses of objects of type UBYTE.As such, it can be reassigned to point to different objects (e.g. different screen buffers, in this case) or to none at all.Later, BlackImage is instead declared as an array of UBYTE, in this case "BlackImage" is the array itself, one can not reassign it to a different area in memory.C makes understanding this a bit difficult, as arrays identifiers automatically decay to pointers to their first element in most contexts...and this, possibly, shows in your further comment:QuoteIf you have used the statically declared arrayBlackImageas a function parameter, it has decayed to a pointer of exactly the same kind as the originalUBYTE *BlackImage- so the problem is probably elsewhere.

author: mikerj
author_rank: Super Contributor
timestamp: February 23, 2020, 02:45:47 pm
content: Most likely you heap limit is set lower than the value you are trying to allocate. The total size of the heap is typically fixed at compile time and the default value may be quite small (or even zero).Where this is set will depend on the tools you are using, it could be the in startup assembler file that holds the interrupt vectors, it could be set by a linker script or possibly even in the makefile/project settings.

author: norand
author_rank: Contributor
timestamp: February 28, 2020, 07:12:33 pm
content: Thanks everyone - just an update for anyone who finds this topic useful in the future. I was actually having two issues (one hardware related - as I stupidly forgot that the development board I'm using as a 10uF capacitor on PA4and hence my CS pin was very slow!). So therefore I couldn't test my issues until I figured this out.Quote from: mikerj on February 23, 2020, 02:45:47 pmThis turned out to be the issue in the end as I didn't realise I could re-configure the heap size, and hence it would compile fine as I the linker wouldn't know I need that much Heap until my program was running (well that's my understanding). The demo code then worked from there!Also using the following as a quick test to myself (I know I could have used #define) which worked exactly as expected.Code:UWORD Imagesize = ((EPD_WIDTH + 7)/8 * EPD_HEIGHT);UBYTE Image;UBYTE *BlackImage = Image;The issue I now have is using the STM32L1 in standby mode means that the buffer is wiped, so I can't do a partial refresh on wake-up! I'm thinking of storing it in flash before sleep, and getting it back out on wake-up but that is a separate topic. Thanks for all the help everyone - a happy student.