feilipu / Arduino_FreeRTOS_Library

A FreeRTOS Library for all Arduino ATmega Devices (Uno R3, Leonardo, Mega, etc).
MIT License
841 stars 204 forks source link

How to set idle task stack size? #38

Closed JanKok closed 6 years ago

JanKok commented 6 years ago

I ran the Blink_AnalogRead example on a Arduino Uno and it ran OK. Then I added some code to the loop() function to check the stack space for the idle task. Then I changed configMINIMAL_STACK_SIZE from 192 to 212, and the stack high water mark didn't change.

How can I change the stack space available to the idle process?

Some related questions: is the line

define configMINIMAL_STACK_SIZE ( ( portSTACK_TYPE ) 192 )

correct, in particular, is it correct to cast the number to portSTACK_TYPE, which is #defined to uint8_t in portmacros.h? Should the definition of portSTACK_TYPE be changed to uint16_t? (I've asked similar questions about the types on the FreeRTOS forum.)

Here's my stack testing code: ` volatile int loopcount = 0;

// This function calls itself recursively to a depth of n, and does a simple check // to try to detect stack corruption. int memtest(int n) { Serial.print(n); Serial.print(' '); //%10); if (n%50 == 0) Serial.println(""); int n1; if (n) { n1 = memtest(n-1); if (n1 != (n-1)) { Serial.println(""); Serial.print("n="); Serial.print(n); Serial.print(" n1="); Serial.println(n1); while(1) {}; } } Serial.print(n); Serial.print(' '); if (n%50 == 49) Serial.println(""); return n; }

void MemTest(void pvParameters) { while(1) { configSTACK_DEPTH_TYPE uxHighWaterMark; / Inspect our own high water mark on entering the task. */ uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL ); // not UBaseType_t which is unsigned char! Serial.print(F("HighWaterMark ")); //vTaskDelay( 30 / portTICK_PERIOD_MS ); // wait 2 ticks Serial.println(uxHighWaterMark); //vTaskDelay( 30 / portTICK_PERIOD_MS ); // wait 2 ticks if (++loopcount == 50) while (1);

Serial.println(F("hi1")); memtest(6); // <----- Increase this number to use more of the stack. Serial.println(F("hi2")); } } void loop(void) { MemTest(NULL); } `

feilipu commented 6 years ago

How can I change the stack space available to the idle process?

The stack space available in to the idle process is defined at this line where dynamically allocated RAM is provided (which is the normal case).

Some related questions: is the line #define configMINIMAL_STACK_SIZE ((portSTACK_TYPE)192) correct, in particular, is it correct to cast the number to portSTACK_TYPE, which is defined as uint8_t in portmacros.h?

Yes. This is correct. The stack type relates to the type of machine. The AVR ATmega is an 8 bit machine, and therefore the uint8_t type is correct.

Should the definition of portSTACK_TYPE be changed to uint16_t?

No. See above. The portSTACK_TYPE is not the stack pointer size, but rather the width of the intrinsic data element pointed to in a single stack entry. For ATmega, this is 8 bits.

JanKok commented 6 years ago

Yeah, I saw that line and it certainly looks to me like changing the definition of configMINIMAL_STACK_SIZE should change the amount of stack available to the idle process, but it doesn't work for me.

I started with the Blink_AnalogRead example and added this code to the beginning of TaskAnalogRead:

/*UBaseType_t is wrong!*/ uint16_t uxHighWaterMark;
uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
Serial.print(F("HighWaterMark "));
Serial.println(uxHighWaterMark);
uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
Serial.print(F("HighWaterMark "));
Serial.println(uxHighWaterMark);
while(1){}

When run, the high water marks come back as 90 and 64. If I change the stack size from 128 to 828, the numbers are 790 and 764.

Then, I commented out the xTaskCreate for TaskAnalogRead, and called TaskAnalogRead from loop(). When run, the numbers are 154 and 126. If I change configMINIMAL_STACK_SIZE from 192 to 150, the numbers are still 154 and 126! I added a Serial.print(configMINIMAL_STACK_SIZE); to the program to be sure that the macro was seen correctly, and indeed the value was printed as 150.

I'm using the standard Arduino IDE which doesn't seem to provide many debugging features. If I used Atmel Studio, would that let me set breakpoints, step through code, etc. on an Arduino Uno? If not, how can I debug code like this?

Regarding the type definitions, UBaseType_t doesn't work correctly in the above code. It's defined as uint8_t, but uxTaskGetStackHighWaterMark returns a uint16_t (as it should, since the high water mark may be greater than 255). See the discussion about this at https://sourceforge.net/p/freertos/discussion/382005/thread/b3eb917a/

#define configMINIMAL_STACK_SIZE ((portSTACK_TYPE)192) still looks wrong to me. portSTACK_TYPE is presently defined as uint8_t, but it should be possible to set configMINIMAL_STACK_SIZE to a number greater than 255.

Thanks for your help!

feilipu commented 6 years ago

Try to see if this code fragment works as expected

    void vTask1( void * pvParameters )
    {
    UBaseType_t uxHighWaterMark;

        /* Inspect our own high water mark on entering the task. */
        uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );

        for( ;; )
        {
            /* Call any function. */
            vTaskDelay( 1000 );

            /* Calling the function will have used some stack space, we would 
            therefore now expect uxTaskGetStackHighWaterMark() to return a 
            value lower than when it was called on entering the task. */
            uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
        }
    }

I remember fighting with stack sizing some time ago, but I think my issues passed when I started to emphasise using the heap more often. Each task in freertos uses its own stack, so this space is effectively wasted. On an ATmega328p there is not enough space to make this work beyond 3 or so tasks. Using the heap means that memory can be reused, by free() once the function is done.

JanKok commented 6 years ago

Ok, I found the problem (description below if you're curious). But first I'd like to report another problem:

FreeRTOSConfig.h contains the line #define configMINIMAL_STACK_SIZE ( ( portSTACK_TYPE ) 192 ) If I change 192 to 800, the high water mark comes back as 0. That's because the (portSTACK_TYPE) cast converts 800 to 32 which isn't much stack space! It seems to me that (portSTACK_TYPE) should just be omitted. Without the cast, the high water mark comes back as 762 initially or 730 later, which is just what I expect.

So why couldn't I change the idle process stack size earlier? Because I had copied Arduino_FREERtos.h and FreeRTOSConfig.h to my local directory (with Blink_AnalogRead.ino). Blink_AnalogRead.ino was compiled with the local copy of FreeRTOSConfig.h, but tasks.c (which is still in the libraries/FreeRTOS/src directory) got compiled with the library copy of FreeRTOSConfig.h. So modifying the local value of configMINIMAL_STACK_SIZE didn't affect the value that was used to set the stack size for the idle task.

My intent was to modify some values in FreeRTOSConfig.h without affecting other apps that use the same library. Is there some way to modify local copies of certain library files, without having to copy the whole library to the local directory?

Also, do you know if there is a way to set breakpoints, etc. when debugging Arduino code? I've used Atmel Studio for ARM-based projects. Would it be helpful with Arduino projects? Thanks!

feilipu commented 6 years ago

It seems to me that (portSTACK_TYPE) should just be omitted.

Yes. I agree with you. I'll remove it from the repo. I like to cast constants for my own benefit, but sometimes it is not correct to cast things... This is one of those cases.

Resolved in 10.0.0-10.

Is there some way to modify local copies of certain library files, without having to copy the whole library to the local directory?

Not to my knowledge. This conversation I think has been had on Arduino Developer forum previously. The only way it seems is to modify the values in the installed library found in the Arduino directory.

Also, do you know if there is a way to set breakpoints, etc. when debugging Arduino code?

Again, not to my knowledge. Arduino IDE is pretty basic (for the reasons of supporting absolute beginners). I don't use it for that reason. There is too much going on "under the covers", and not enough support for important things.

tangchihwei commented 5 years ago

@feilipu Can you describe more how to navigate the stack sizing problem in FreeRTOS using heap? I am trying to run more than 3 tasks on a Atmega328, and I have been running into stack sizing problem. The MCU would freeze the minute I add the 4th task. All my tasks are super simple(I2C, GPIO type), no large array. All the tasks are done basically the same fashion as your example code.

feilipu commented 5 years ago

The ATmega328p has only 2048 Bytes of RAM. Compiling the basic Blink_AnalogRead sketch produces the following result.

Sketch uses 8688 bytes (26%) of program storage space. Maximum is 32256 bytes.
Global variables use 358 bytes (17%) of dynamic memory, leaving 1690 bytes for local variables. Maximum is 2048 bytes.

So there are ONLY 1690 Bytes available for the freeRTOS to allocate tasks. We know that the idle task uses at least 192 Bytes of RAM. Each additional stack has a Task Control Block created, together with its own stack.

In practice, about 3 simultaneous "serious" tasks is the maximum that I've ever been able to get to run on a ATmega328p. Simply, it doesn't have enough memory for more. Try using heap_1.c or one of the other heap management options to get more feedback information on how to size your stacks more precisely.