 Now we're getting to Heap to memory management and what heap to choose. There are actually five types of heap Each one will provide you with a different memory allocation memory freeing and It's usually completely different scheme. So I will try to explain in next slides Yeah, as said before for FreeRTOS, there's a specific memory area in which you have a heap. In this heap There might be some freeRTOS resources that was selected before that you initialized like some tasks, queues Every queue and task has its control block Which is located on its own stack. If you are running your application without freeRTOS You just simply call malloc or free for dynamic allocation But if you are in a operating system, you need some thread safe functions In this case, there are two dedicated for this Operations, pvport malloc and pvport free. You just don't use free or malloc You need to specify total heap size in the FreeRTOS config header file and if you are not sure if There is enough of the space for the heap you can call xport getFreeHeapSize function which will return the value of the heap space unallocated Each task or even queue or other resource need to have its control block and This allocated in the heap you can enable some task notifications Trace facility and the mutex is in the heap Usually this is already predefined and you can define it in a kubemax But if you really want to change it later, you can do it do it in a kubemax and then regenerate or You can go straight to the configuration file and change these values right there But this the change will be not propagated if you regenerate the project the control block size is depends on the option that you enable in the Configuration file when creating a task you need to pass the stack size dedicated for this task as an argument in the Thread def function that you can see at the very beginning at the very end of all the arguments in the function For each task there is allocated the control block size Plus four times task stack size. The reason is that the number is given in words You need to define as well the minimum stack size and this size is taken by idle task When you run your tasks and you need to know how much memory was used by the task the Friartos uses a Method of counting the known keyword in the stack So when the stack is defined in the heap It is filled with a known value that normally should not be present anywhere in the program execution When you want to know how much memory was allocated by your task in its own stack You can test for the watermark this is The last modified word in the memory. So when you call the function of UX task get stack a high watermark Function will take the ID of the task that you want to present or a current one and it will traverse the stack of this task and it will count how many words are still unchanged and This will give you the watermark. Hey my stack allocated only up to this level and everything else is available so if you take from the TCB the length of the stack and The watermark you can subtract them and you automatically know how much space wasn't used So it's a reserve for you or the stack can be reduced by this amount to spare some memory It's a very useful thing because When you define the heap You must define its size so that all the objects fit inside But the whole heap fits as well in your memory, which can be quite complicated when we speak about other objects the Objects take as well some amount of bytes in the heap when their Object is created. So when I create a message queue, it takes number of elements multiplied by element size plus some default thing like 16 bytes for description of the queue and so on Additionally when we operate with software timers then again we have to enable the usage of the timers with a macro and when the timers are added to our Configuration during initialization of the kernel the timer task is created. It's additional task that Is responsible for processing the timing events So if you create a software timer the timer task will take care about the proper scheduling and When the proper time comes for your asynchronous function the timer task will call this callback and Will pass the control to this asynchronous function So you can schedule that For example every five seconds or in 20 seconds or in 50 milliseconds. I want to call this function So you put it into the timer queue the timer task will order the events Depending on the time and it will wait until the proper time comes then it calls the function Be aware That the function is called in the context of this timer task Which means that your function will share The stack with the timer task, which means when you create the timer task You have to define stack big enough to fit as well the variables of your callback functions It's a typical mistake where you leave the timer task Like it is with some small stack and you put a function that uses a lot of local variables And then everything works and suddenly when it comes to this function It still finishes properly But when it returns to the other functions suddenly you get strange crashes of the application why because local variables of the function called in the timer task Overwrote some other data from other tasks or other objects and when it for example overwrote the structure of the queue or a mutex well, then It's destroyed and when you try to use it it has very interesting effects typically It's very hard to debug at that moment. You would need to take the list of the tasks and look at the usage of their stack and If you see that the timer one is completely full and exhausted You have to increase this parameter additionally You can as well play with the priority of this timer task if you want the functions running at a very high priority So at a precise time you should put as well the timer task at high priority but if of course You don't want to disturb your code You can put the timer task to an idle or low priority and then the functions will be executed When other tasks are finished and give up their time Additionally for the timers there is still a Need to define a queue length for the timer because each record means the initial time the length of the delay and the callback function and You if you want to plan a lot of different tasks You have to increase the length of the timer queue to be able to put these events in the queue so that all of them fit and Additionally semaphores require 88 bytes to be allocated in the heap So if you don't want them You can as well exclude mutex's by disabling the use mutex's macro in the free artist configuration The question is how to reduce the usage of RAM RAM Will always be a short or a very expensive thing So when you design your application again, please check how much RAM you use in different objects Some of them are necessary. So you have to count with them however for tasks you should check how much memory you use and Reduce the stack size in the definition It's a little bit dangerous thing you always have to leave some margin because For normal input parameters For normal behavior of your application The task can run in a simple loop or the depth of the function call can be limited can be calculable But in case you get the wrong parameters or Some unknown or unexpected value of parameters It can be by application mistake by a mistake of the incoming data such task can get into the cycles and Especially if it's re-entrant If it calls a function that calls itself or other function and there is a circular loop The stack with the wrong input parameters can be exhausted very quickly so you have to count with that and The trouble with testing is that you can either test everything or Just values you think can happen. You have to be careful how you define your applications when something happens and your stack is corrupted and You get a task switch To the task whose stack is corrupted the Friartos can check this situation and When such situation is detected by the watermark check and by a special area at the bottom of the stack check It will call a hook Called application stack overflow hook This is an empty function but this function can be adapted by you and At the moment when you land in the application stack overflow hook You know that something bad happened and the the stack for a given task is destroyed So what you can do at this moment? first record this event and if The application allows that you can try to restart the microcontroller very important thing is you probably would want to record such and You would as well want to record the parameters from a given task If it started with some values It may be worth putting them in the trace So it's very important to have some lock of your function Especially in case of errors because you can learn from them and you can try to adapt your application later in the next version but Please use the application stack overflow hook because it can tell you that something really happened and You should care about your application again. We can as well check how much empty space is in the heap So there is a function that tells you how much free heap is available and That's important for monitoring how many objects you can still Define and allocate on the heap one more thing this Get free heap size Doesn't give you the biggest block that you can allocate. Do you know why? You can still have 10 kilobytes of the heap available But you can't allocate a block with a 1 kilobyte in size The function returns a sum of all empty blocks But the empty blocks can be between Already allocated parts of the heap and if you sum them and each of them has a half kilobyte in total They can have 10 kilobytes But if the biggest block is half kilobyte You can't allocate a new task with a three kilobytes of the heap knee or the stack needed It's a double-edged sword on one side It gives you the empty space on the other side. It doesn't give you how much you can allocate at once Concerning the heap allocation we will see different strategies for Allocating the heap and you can choose these strategies in the configuration and the cuba mix will reflect it by including One of the five files that implement different heap allocation strategies When you look at the the design of your application if you need to reduce the rum please write down the usage of your stacks using the high watermarks for each of them and Try to reduce the size of the stack for each of your tasks and Try to think as well if you need so many tasks if for example some functionality can be combined together To use just one task and one stack for a given functionality then Think about usage the timers whether you need to use them because this way you can reduce the footprint by By the stack of the timer task by the tcb of the timer task by the q of the timer task So it gives quite a big benefit not using them same is valid for the mutex's and other elements so Reduce from the configuration all the elements not used in your application further we can think about using a Macro maximum number of priorities of the tasks because the higher Amount views the more memory we need and the less efficient algorithm can be used for Distinguishing which of them will run So reducing the number of priorities can help as well. So let's look at the memory allocation We can choose the total heap size in the configuration Which is a parameter that reserves and typically variable in the memory or heap in a memory and This will be passed to the free articles Then you have to choose as well the algorithm how the memory is allocated and Deallocated it's managed by a heap one to heap five and Now let's look how the heap operates. So We have got the stack for the main application So when we start the micro it needs some some stack for calling different functions calling startup functions clock setup and so on Then there is a beginning of our memory where the standard C heap is defined This can be pretty small for example only for a print F because the standard C functions use the system heap and This system heap has to be defined. It can be very small, but it has to be there further and the free articles heap is defined as a variable or block of memory defined in the RAM beyond the main application heap and Before the main stack So it's a block that you can allocate you can say how big it will be and The rest of the memory can be allocated for global variables when you define the heap and The strategy how to use it? You are telling free articles. Hey now you can use this memory and it's under your control so that's how The free articles gets its heap. Just give it. This is your memory. This is this size in case of a strategy heap 3 the free articles uses the standard malloc and free functions so at that moment you are responsible for defining the application heap and The free articles uses standard C functions to allocate and allocate it so let's look at different models of memory allocation and Why the fragmentation can happen? In a heap one strategy This uses a first-fit algorithm. So when you want Allocation when you want a chunk of memory with a given size the heap one will simply take Free first empty part of the heap it will allocate the newly Requested block it will return the pointer, but heap one doesn't offer any Deallocation and here in heap one. What do you allocate? You can't remove from the memory It has got this drawback. You can't reduce The memory footprint if you don't need something So it stays in the heap forever But it has an advantage if you have application with a fixed amount of objects It's very effective Because it doesn't need to search for empty blocks It simply takes the last one and puts your request on top of the last one so it's very fast and For safety applications where you don't want to deal with a dynamic memory allocation This is the best option because if you set up all the objects at the beginning You know that you have all already placed in the heap and there is no danger of running out the second strategy heap 2 uses a very limited way of emptying blocks So you can see I'm allocating at the beginning three objects So it allocates three blocks in the heap then I deallocate two of them and Suddenly these two Become available, but If I deallocate even the last one You can see that there are four blocks that can be allocated. However, they are not Defragmented so you have got four isolated blocks Where you can still allocate less or up to the maximum size of the block, but not more So heap 2 is not recommended because it keeps the memory very fragmented and If you place a lot of small objects and you fill the heap and then remove these objects You can't place anything bigger than heap 3 strategy Uses the C library functions malloc and free So the implementation whether the memory stays allocate Fragmented or the fragmented depends on the implementation of these functions Trouble with that is that you don't have control how these functions malloc and free are Implemented you typically don't have a source code from them and They tend to be rather slow One of the better options is an algorithm implemented in heap 4 Heep 4 uses free space the fragmentation. So whenever you free a block It looks if the surrounding blocks are as well empty blocks and if they are It connects the blocks together so at the end when you deallocate all all the blocks or Couple of them it keeps the blocks in one big chunk that allows again to allocate bigger objects So heap 4 is a preferred strategy yet It has One little disadvantage. It is still defined as one variable So it fits only into one memory. You can find the definition of the heap 4 As a definition of this you see heap variable array. So you can see that It's defined You can place it using linker or different attributes to different addresses or different RAM memories If you look at a memory map of our new microcontrollers, you can realize that There is quite a lot of separate run blocks For example a steam 32 h7 has something like six or seven memory blocks and These memory blocks are in not a continuous space. So one RAM begins at address 20 million hexa the other RAM blocks begins at address 30 million hexa So in total you may have a one megabyte of RAM But it is not a continuous space So if you use heap 4 strategy, you could either choose a half megabyte block or 256 kilobyte block But not one megabyte in total Which is solved by using a heap 5? This is similar to heap 4. However, it defines a table that describes several different blocks each block with its startup address And the size and it finishes with a null and zero vector So when you choose the heap 5 and you call the port define heap regions you put a pointer to this table and Friartos is then able to allocate the requested memory size in any of these blocks So suddenly you can use all the memory available in the microcontroller. Of course the Allocated block will not cross the boundaries Because it's a discontinuous RAM But it can still allocate the blocks in RAM 1 in RAM 2 RAM 3 RAM 5 So it gives you much more RAM for the application usage We can as well use alternate functions to operate with the memory because Friartos Has the option to use a pool. The pool has one advantage. It defines memory blocks with a fixed size and It allocates to you the blocks of a fixed size from a defined pool So the management of these pools is simpler than dynamic memory allocation On the other side It is not a recommended way of operation Apart from some communication tasks for example pools are very effective for TCP IP Where you need to take the packets of the memory and Return them back quickly So pools are much faster for implementation and for runtime But if you need to share different sizes of data dynamic allocation is better