Where do I get memory for buffers in a library?

mborgerson

Well-known member
I'm working on a generic SD-Card bases circular buffer library for the T4.1. This project is a modification of the SD-based long audio delay class that I have been working on over the last few weeks. The library requires 32 to 64 KBytes of memory as buffers to handle read and write delays up to about 150 milliseconds that can be expected with SD cards. The question is: where do I get that memory? Among the options are:

1. The host main program defines the buffer arrays in its code, then passes a pointer to the memory block to the library begin() function. The memory could be in DTCM, DMAMEM, or EXTMEM.

2. The library begin() function uses malloc() to get a pointer to memory for the buffers. I've long been wary about the use of dynamic memory allocation--but mostly due to heap fragmentation issues that occur when blocks are allocated and freed many times in a program. Allocating buffers once that remain in use for the duration the application is less objectionable.

3. Each instance of the circular buffer class has its own buffers declared as private class variables. This means that each circular buffer object will occupy 40 to 70KBytes of DTCM.

Any advice appreciated.
 
For my updated version of the AudioEffectDelayExternal object I followed the existing idiom of allocating memory at instantiation time, for example AudioEffectDelayExternal myDelay(AUDIO_MEMORY_EXTMEM,44100);. I didn’t allow for using DTCM, but I probably should have, and will do if I get to it before Paul merges my PR.

On the other hand, for my buffered SD playback and record objects, I added a createBuffer() function which can use any memory area, but needs to be called after instantiation, usually in setup(). This was because the existing SD playback objects didn’t visibly allocate buffer memory anyway, and doing so at instantiation time requires editing the output of the Design Tool, which I always thought was a bit messy. But again, I should probably allow for buffer creation at instantiation time, for consistency.

Note that at the moment it’s still possible to create audio objects at runtime with new; removing them with delete is not supported yet, though I’m working on a major update which does support dynamic creation and destruction of audio objects.

As there’s nothing much the library writer can do to prevent fragmentation, I didn’t take any particular steps to avoid it, apart from allowing for passing a pointer and size for the buffer; the application writer can thus allocate a big pool and manage its use themselves, if they want.

All of which is a long-winded way of saying there’s no real “right answer”, but giving the application writer all possible options is probably best, if you can be bothered!
 
Personally I'd define the constructor or init functions so that you have 3 options:
Pass it a pointer and size of buffer to use - It uses the supplied buffer allowing the user to control exactly where the buffer gets placed.
Pass it a size of buffer to use - It attempts to malloc a buffer of the requested size and returns false/error if this fails.
Pass it nothing - It attempts to malloc a buffer of some reasonable default size and returns false/error if this fails.

As you said malloc / dynamic memory allocation is a bad idea for embedded systems if you are constantly allocating / freeing memory. But if it's a one time thing on start up then it's not such a big issue.

All of which is another way of saying that there is no right answer so it's best to code in a way that for minimal extra work gives you flexibility for most normal use cases while keeping things as simple as possible for less demanding uses.
 
Personally I'd define the constructor or init functions so that you have 3 options:
Pass it a pointer and size of buffer to use - It uses the supplied buffer allowing the user to control exactly where the buffer gets placed.
Pass it a size of buffer to use - It attempts to malloc a buffer of the requested size and returns false/error if this fails.
Pass it nothing - It attempts to malloc a buffer of some reasonable default size and returns false/error if this fails.

As you said malloc / dynamic memory allocation is a bad idea for embedded systems if you are constantly allocating / freeing memory. But if it's a one time thing on start up then it's not such a big issue.

All of which is another way of saying that there is no right answer so it's best to code in a way that for minimal extra work gives you flexibility for most normal use cases while keeping things as simple as possible for less demanding uses.

I went with two overloaded options for the begin() function. One allows you to specify the buffer location, the other generates the buffer with malloc(). At this point, I'm hard-coding buffer sizes because using buffers less than 16KB can cause issues with data or timing errors due to SD card write timing variability.
 
Back
Top