Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
Diff: main.cpp
- Revision:
- 59:94eb9265b6d7
- Parent:
- 58:523fdcffbe6d
- Child:
- 60:f38da020aa13
--- a/main.cpp Wed May 11 05:28:04 2016 +0000 +++ b/main.cpp Thu May 12 05:57:53 2016 +0000 @@ -276,31 +276,125 @@ // -------------------------------------------------------------------------- // -// Custom memory allocator. We use our own version of malloc() to provide -// diagnostics if we run out of heap. +// Custom memory allocator. We use our own version of malloc() for more +// efficient memory usage, and to provide diagnostics if we run out of heap. // +// We can implement a more efficient malloc than the library can because we +// can make an assumption that the library can't: allocations are permanent. +// The normal malloc has to assume that allocations can be freed, so it has +// to track blocks individually. For the purposes of this program, though, +// we don't have to do this because virtually all of our allocations are +// de facto permanent. We only allocate dyanmic memory during setup, and +// once we set things up, we never delete anything. This means that we can +// allocate memory in bare blocks without any bookkeeping overhead. +// +// In addition, we can make a much larger overall pool of memory available +// in a custom allocator. The mbed library malloc() seems to have a pool +// of about 3K to work with, even though there's really about 9K of RAM +// left over after counting the static writable data and reserving space +// for a reasonable stack. I haven't looked at the mbed malloc to see why +// they're so stingy, but it appears from empirical testing that we can +// create a static array up to about 9K before things get crashy. + void *xmalloc(size_t siz) { - // allocate through the normal library malloc; if that succeeds, - // simply return the pointer we got from malloc - void *ptr = malloc(siz); - if (ptr != 0) - return ptr; + // Dynamic memory pool. We'll reserve space for all dynamic + // allocations by creating a simple C array of bytes. The size + // of this array is the maximum number of bytes we can allocate + // with malloc or operator 'new'. + // + // The maximum safe size for this array is, in essence, the + // amount of physical KL25Z RAM left over after accounting for + // static data throughout the rest of the program, the run-time + // stack, and any other space reserved for compiler or MCU + // overhead. Unfortunately, it's not straightforward to + // determine this analytically. The big complication is that + // the minimum stack size isn't easily predictable, as the stack + // grows according to what the program does. In addition, the + // mbed platform tools don't give us detailed data on the + // compiler/linker memory map. All we get is a generic total + // RAM requirement, which doesn't necessarily account for all + // overhead (e.g., gaps inserted to get proper alignment for + // particular memory blocks). + // + // A very rough estimate: the total RAM size reported by the + // linker is about 3.5K (currently - that can obviously change + // as the project evolves) out of 16K total. Assuming about a + // 3K stack, that leaves in the ballpark of 10K. Empirically, + // that seems pretty close. In testing, we start to see some + // instability at 10K, while 9K seems safe. To be conservative, + // we'll reduce this to 8K. + // + // Our measured total usage in the base configuration (22 GPIO + // output ports, TSL1410R plunger sensor) is about 4000 bytes. + // A pretty fully decked-out configuration (121 output ports, + // with 8 TLC5940 chips and 3 74HC595 chips, plus the TSL1412R + // sensor with the higher pixel count, and all expansion board + // features enabled) comes to about 6700 bytes. That leaves + // us with about 1.5K free out of our 8K, so we still have a + // little more headroom for future expansion. + // + // For comparison, the standard mbed malloc() runs out of + // memory at about 6K. That's what led to this custom malloc: + // we can just fit the base configuration into that 4K, but + // it's not enough space for more complex setups. There's + // still a little room for squeezing out unnecessary space + // from the mbed library code, but at this point I'd prefer + // to treat that as a last resort, since it would mean having + // to fork private copies of the libraries. + static char pool[8*1024]; + static char *nxt = pool; + static size_t rem = sizeof(pool); + + // align to a 4-byte increment + siz = (siz + 3) & ~3; + + // If insufficient memory is available, halt and show a fast red/purple + // diagnostic flash. We don't want to return, since we assume throughout + // the program that all memory allocations must succeed. Note that this + // is generally considered bad programming practice in applications on + // "real" computers, but for the purposes of this microcontroller app, + // there's no point in checking for failed allocations individually + // because there's no way to recover from them. It's better in this + // context to handle failed allocations as fatal errors centrally. We + // can't recover from these automatically, so we have to resort to user + // intervention, which we signal with the diagnostic LED flashes. + if (siz > rem) + { + // halt with the diagnostic display (by looping forever) + for (;;) + { + diagLED(1, 0, 0); + wait_us(200000); + diagLED(1, 0, 1); + wait_us(200000); + } + } - // failed - display diagnostics - for (;;) - { - diagLED(1, 0, 0); - wait_us(200000); - diagLED(1, 0, 1); - wait_us(200000); - } + // get the next free location from the pool to return + char *ret = nxt; + + // advance the pool pointer and decrement the remaining size counter + nxt += siz; + rem -= siz; + + // return the allocated block + return ret; } -// overload operator new to call our custom malloc +// Overload operator new to call our custom malloc. This ensures that +// all 'new' allocations throughout the program (including library code) +// go through our private allocator. void *operator new(size_t siz) { return xmalloc(siz); } void *operator new[](size_t siz) { return xmalloc(siz); } +// Since we don't do bookkeeping to track released memory, 'delete' does +// nothing. In actual testing, this routine appears to never be called. +// If it *is* ever called, it will simply leave the block in place, which +// will make it unavailable for re-use but will otherwise be harmless. +void operator delete(void *ptr) { } + + // --------------------------------------------------------------------------- // // Forward declarations @@ -3959,7 +4053,8 @@ int main(void) { printf("\r\nPinscape Controller starting\r\n"); - // memory config debugging: {int *a = new int; printf("Stack=%lx, heap=%lx, free=%ld\r\n", (long)&a, (long)a, (long)&a - (long)a);} + // {int *a = new int; printf("Stack=%lx, heap=%lx, free=%ld\r\n", (long)&a, (long)a, (long)&a - (long)a);} // memory config info + // -> no longer very useful, since we use our own custom malloc/new allocator (see xmalloc() above) // clear the I2C bus (for the accelerometer) clear_i2c();