Memory Problems in BLE Nano

09 Jul 2015

Hi,

I am using the Ble Nano in conjunction with 3 sensors to collect and output data via bluetooth. I am attempting to do so at the limit of the speed permitted by the components (1kHz).

To test out the functionality of the various components, I tested out the sensors with the LPC1768 board and it performed well. I got the number of points printed to the screen that I expected from the 1 kHz frequency. Just to have some reference let's assume I run the program for 4 seconds and get 4000 points for each sensor. The memory usage tab in the mbed environment (which you can get to by clicking on your project folder and then going to the right side of your screen in the build tab) showed me that I was using 23.1kB of Flash memory and 1.2kB of RAM. That's all fine.

I then switch out the LPC for the Ble Nano. Some key differences are that the Ble nano only has 16kB of RAM (compared to 32kB) and half of that is used for BLE which leaves only 8kB usable. Using the BLE as a processor first, so not outputting any data through BLE, I was able to print the correct number of data points from my sensors (same 4000 as before). This time the Flash used was 20.9 kB and 1.6kB of RAM. This is with anything related to BLE absent from the code. To get it to output the 4000, I had to clean up my code, pointerize everything that I could and minimize RAM use in general, that is the only way I got it to work.

Finally, I then remove any printing statements and have all the data be passed via BLE. The only situation in which my code will not run out of memory is if I significantly reduce the frequency at which my sensors output data. I only got it to work with 1 sensor at 100Hz. Adding any other sensor or increasing frequency leads to nothing being output. At this current time, my Flash memory use is 21.5 kB and the RAM is 2.4 kB. Using the previous set-up (BLE as a processor only) I also could not get any printed output above 2.4kB of RAM use.

Conclusion: I think the overhead from the BLE_API library and the nRF51822 library are putting me over the top in terms of memory use such that my code cannot run any more.

Main piece of the code:

while(*pt2<=4) {//Run for 4 seconds and tally up how many times it read each sensor and how many loops it did total.
                *t1 = Sensor1newdata() ;//Gives above 16 if there is new data or 0 if there is no new data
                *t2 = Sensor2newdata() ;
                *t3 = Sensor3newdata() ;
                *c1 +=1;
                if (*t1>16) {//Check if there is new data
                    readSensor1();// Read the new data. 
                    *c2 +=1;//increment number of times data has been read
                }
                if (*t2>16) {
                    readSensor2();
                    *c3 +=1;
                }
                if (*t2>16) {
                    readSensor3();
                    *c4 +=1;
                }
                *pt2 = timer2.read();
  }
Some small processing happens to dump the c1,c2,c3,c4 pointers into the sens array

ble.updateCharacteristicValue(Sensor1.getValueAttribute().getHandle(),(uint8_t*)&sens , sizeof(sens));

The code that actually reads the values:

void readSensor1() {
    char val[6];
    if (recv(0x32, OUT_X_L_A, val, rlen)){ 
        *pax = (int16_t) (((int16_t)val[1] << 8) | val[0]) >> 4 ;
        *pay = (int16_t) (((int16_t)val[3] << 8) | val[2]) >> 4 ;
        *paz = (int16_t) (((int16_t)val[5] << 8) | val[4]) >> 4 ;
        free(acc);}
}

Where recv is:

bool recv(char sad, char sub, char *buf, int length) {
    if (length > 1) sub |= 0x80; 
    bool res1 = i2c.write(sad, &sub, 1, true); 
    bool res2 = i2c.read(sad, buf, length);
    return res1 == 0 && res2 == 0;
}

My questions: 1) Assuming my pointer use is done correctly, I don't understand why more data leads to more RAM being used. I thought that the whole point of using pointers was to have a fixed memory length for any number of data points. 2) I'm wondering if the solution described in this post https://redbearlab.zendesk.com/entries/63582519-Access-to-Full-16k-SRAM-on-BLE-Nano could be implemented in mbed. Specifically, removing the nano specs feature. Though I'm not sure it's even being used... 3) Any ideas on how to minimize the overheard so that my code can run. It should have no problems at 2.4 out of a possible 8. I don't understand how I could be running out of RAM even though mbed says I'm only using 2.4kB out of a possible 8kB.

14 Jul 2015

The memory reported doesn't include any dynamically allocated memory on the heap or stack. The stack includes all local variables that aren't static, the heap is anything using new or malloc.

In other words the ram usage numbers are only meaningful if you write your program in a certain way.

Your code calls free(acc); which would indicate that you're using dynamic memory of some sort.

You're freeing memory in a different location to where you allocate it since there is no malloc anywhere in the code posted and only call free in certain situations, that is a recipe for memory leaks.

You should make sure you aren't leaking anything here, this isn't a hand holding language like c#, if you miss a call to free then that memory is never released to be re-used and over time you will run out of memory.

"I thought that the whole point of using pointers was to have a fixed memory length for any number of data points."

No. Fixed memory length for any amount of data would be what is known in computing terms known as magic. You can't get more storage for nothing.

A pointer gives you a lot more flexibility in how you handle memory at a cost of a tiny increase in memory usage and sometimes performance. You need enough memory to hold the value you want to store plus 4 more bytes to hold the pointer to the memory location. There is also an additional overhead of a few bytes for any memory allocated on the heap, you need to keep track of which bits of memory are being used. Generally these penalties are well worth the benefits pointers and dynamic memory bring but if you don't need those benefits then don't use them just for the sake of it.

21 Jul 2015

Thanks for your response. I'm sorry I have incorrectly explained my struggles, so let me try again:

I realize that the numbers given to me by the compiler do not included dynamically allocated memory (since there is no possible way for the compiler to predict that). However, I do not understand how 5.6 kB (8-2.4) of RAM are insufficient for me to run my code under the assumption that I have done things correctly and there is no net growth of my dynamically allocated memory.

With respect to your second points: of course, I do not expect pointers to magically be able to store infinity numbers at a fixed memory cost. After having reread my post, I realize how you could of understood that. What I meant was, if the values stored in *pax, *pay and *paz get refreshed at a rate of 1kHz, every new value should not take an extra memory space right? Any new value should overwrite the previous one and therefore the reading of new values should be done at 0 net memory cost. Just to be crystal clear here, I'm not saying that my pointers will store infinite data, I'm saying they will store a piece of data until it gets replaced by another (1 mili second later).

Finally, I put the free(acc) (which should read free(val)) there because I thought there might be an issue where this array is being created everytime readSensor is invoked, but not freed. From what I understand of compilers, as soon as the program exits that method, the garbage collector should clean up the array. My free command was just there to make sure it was being done. Perhaps that is done incorrectly and I should not do any dynamic memory allocation myself.

So to boil it down a little more, how is it possible that the 5.6kB of RAM that I have remaining are insufficient for me to read and overwrite thousands of points? Do you see any places where my memory leaks might be?

22 Jul 2015

First a basic guide to how memory is used:

free() should only every be used for memory allocated using malloc(). Attempting to free memory that wasn't allocated in this way will result in a crash. Attempting to free memory that has already been freed will also result in a crash. Reusing a memory pointer that was created by malloc() and hasn't had free() called on it will leak memory and eventually result in a crash.

In other words it's very very easy to completely screw things up using dynamic memory. When freeing memory it is normal to then set the pointer to null afterwards as a way of tracking what's going on.

malloc() allocates memory from the area known as the heap. The heap can be allocated and deallocated at random, memory remains allocated until you manually release it.

All of the above is also true if you replace malloc() with new and free() with delete.

Any local variables (e.g. your char val[6];) are created in the memory area known as the stack. This is allocated and deallocated sequentially, when you enter a function the memory needed for the local variables will get allocated, it will remain allocated until you exit the function at which point it will get discarded. Any code with it's own {}'s is handled the same way, any variables local to that section of code will be allocated on the stack at the start of the section and discarded at the end. The stack also holds the location in the code to jump back to when the function exits and any parameters passed to the function.

This means that if you have a lot of function calls nested inside each other with lots of local variables in each of those functions then the stack can get very large.

It also means that if creating pointers to variables on the stack it's possible to end up with the pointer being valid but the memory location getting reused. e.g.

int *myFunction() {
  int x=5;
  return &x;
}

main () {
  int * myIntPtr = myFunction();
  printf("%d\n",*myInPtr);
}

This code will (probably) work exactly as expected, the compiler won't complain and it will print out a 5. But it is also completely wrong. As soon as we exit myFuntion() the memory allocated to hold x is no longer allocated to anything, it will still have the value 5 in it because nothing has happened to change that yet but that is mainly luck. The next time we call a function or do anything else that causes memory to be allocated on the stack that memory location will get re-used and the value will change. If we used myIntPtr to change the value stored we'd cause random memory corruption and if we are lucky crash. If we were unlucky we'd change some variable and get incorrect results without knowing it.

Garbage collection is something very different that doesn't exist in c or c++. Some languages (e.g. Java or c#) have what is known as managed memory, you don't have to call free when you are done with some memory. Instead the system keeps track of the allocated memory and when the program no longer has any valid pointers to a memory location the system automatically frees that memory. It makes for simpler coding and eliminates one of the more common and harder to track down causes of bugs. Unfortunately it also results in a performance hit, the tighter you are on memory the bigger the hit. If you have over about 5 times the amount of RAM than you technically need the performance hit becomes tiny and so for desktops with GB of RAM it's quite a nice feature to have. When you have 8kB it's not such a good idea.

None of which actually helps you with your problem but hopefully helps you understand what is going on a little better.

Your basic understanding of pointers seems to be correct, changing the value won't use up any space. I'm assuming that at some point outside of the posted code you allocate the memory that the pointers point to? Creating a pointer on its own doesn't allocate memory to store any data, it only creates space to store a pointer. If you don't set the pointer to point to a valid location then your data will be stored at some random location in memory and things will crash.

6kB is normally enough space for the stack but as pointed out above if can vary a lot depending on what you are doing.

It's hard to tell exactly what is going on with just code snippets, can you post more of your code or publish it as a project?

26 Jul 2015

Thanks Andy. Your explanations were very useful in helping me separate misconceptions in my head! I'll send you a private message with a link to my code. Perhaps we can continue the conversation from there.