Test & Measurement

Find bugs faster: Percepio Tracealyzer user story

13th May 2020
Alex Lynn

Jan Ropek of VNT electronics, a Czech maker of electronic dog training equipment, tells his story of how a development project was plagued by memory leaks. It could take days to locate and fix a single leak, but when they deployed Percepio Tracealyzer the same bug hunt was completed in a matter of hours.

In our device we use FreeRTOS (originally version 9, now upgraded to 10.0.1) running on a STM32L162VCT. We have long been looking for a way to exchange data between different tasks in the application. Of course, if we only need to send a few bytes, we can use a FreeRTOS queue by copying the necessary information directly to the queue structure.

The data structure of our queue in this case would like this:

typedef struct {

  uint32_t Address;

  uint32_t Data1;

  uint32_t Data2;

} tQueueTasks;

When we wanted to show battery voltage on the LCD screen, we could just do something like this:

tQueueTasks myQueue; myQueue.Address = LCD_SHOW_BATT_VOLTAGE; myQueue.Data1 = ADC_GetValue (BatteryPin); xQueueSend (QueueDisplayHandle, &myQueue, portMAX_DELAY);

 

Fig 1: In Tracealyzer’s Trace view, we can see when new data was received in Task RF and then displayed by TaskDisplay.

The situation got more complicated when we wanted to send larger amounts of data (tens of bytes). How should we deal with that? Have statically allocated memory in the queue structure, like this?

typedef struct {

  uint32_t Address;

  uint32_t Data1;

  uint32_t Data2;

  uint32_t Buffer[100];

} tQueueTasks;

This would allow us to fill the Buffer[100] with whatever data we wanted to send. But we would probably not utilise all 100 bytes most of the time, whereas at other times we would need even more. This is why we came up with a dynamic memory allocation solution, where every time we allocate just the amount of memory we need to send.

The steps needed to send data from Task1 to Task2 then becomes:

  • Task1: allocate the memory
  • Task1: send pointer to this allocated area to Task2, through the queue
  • Task2: receive pointer and process data

For dynamic packets, the queue data structure was updated to look like this:

typedef struct {

  uint32_t Address;

  uint32_t Data1;

  void *pointer;

} tQueueTasks;

The dynamic allocation method can be applied to arbitrarily large data packets. Usually we create fixed buffers for specific cases such as sending data to the LCD, sending data over WiFi, et cetera. However, we know that the heap is of limited size, so with this style of sending data it is necessary to watch allocation and freeing of memory. Otherwise, we will run out of heap space – and many times, we did forget to release memory somewhere in the code and the whole system collapsed.

Fig 2: The Memory Heap Utilisation view helped the author of today’s blog post discover several memory leaks.

Reading the code didn’t help

The more code you have to look through, the harder it will be to find the error. When we didn’t know where to look we could spend days searching, until we tried Percepio Tracealyzer. In particular, we used streaming mode in combination with user events, and watched heap allocation and deallocation in a live update view.

Thanks to the heap utilisation feature (above), we found several mistakes in our code, usually within a few hours, where we forgot to deallocate memory. For us, this is one of the most important functions in Tracealyzer.

We know that the newest version of FreeRTOS has something called Streaming buffer that fulfills a similar purpose of sending data between tasks. However, we originally came up with our solution for FreeRTOS version 9 and we like it so we keep using it in version 10.

Featured products

Upcoming Events

View all events
Newsletter
Latest global electronics news