Problem: Impact of stack size on task behaviors

Problem: (STM32F103) I’m New to FreeRTOS. I have 5 tasks running: LED1, LED2, BinarySemaphoreSend, BinarySemaphoreReceive, QueueReceive. Symptoms: changing task stack sizes makes some tasks behave differently; sometimes when BinarySemaphoreReceive fails, the whole thing locks up, or caused LED1 to stop blinking, yet LED2 is still alive and blinking.

When calling the binary semaphore receive function immediately after sending (in any of the tasks), sending and receiving are always successful. But if send and receive actions are in different tasks, the receive fails and locks up LED1 task.

The debugging for the infinite loop type software would’ve been much more straight forward. Please let me know where I should look into to understand the very nature of the problem. Thank you in advance!

Make sure you have configASSERT defined in a way that halts the system on a problem, and enable stack overflow checking.

Your symptoms are of programs overwritting memory.

Thanks, I will check.

Sometimes the easiest way to get a system running is to make the stack much larger than necessary. Then you can use the vTaskGetInfo function to get the task statistics including the stack high water mark. With the task statistics, it is easy to optimize the stack level to what each task requires.

Hi Joseph, while keeping the LED1 and LED2 stack sized at 50, yes I once increased the remaining task stack sizes to 512 and ran into problems of tasks misbehaving or causing LED1 to freeze; and sometimes the whole thing just froze. And yet when I decreased the stack size from 512 to 64, it “solved” the problems and everything ran. That’s what baffled me because it appears to be against common sense.

Can you supply the source code? Another possible problem is concurrencies between the tasks, but that depends upon how your test code works.

Hi Joseph,

I had just tested the following source code now and confirmed the problem. In the main.c file, on top, if you define #define QUEUE_RECEIVE_STK_SIZE 64, code would work (keep sending and receiving of binary semaphore in the “queue_receive_task()”. But if you define it to be #define QUEUE_RECEIVE_STK_SIZE 512, everything hangs up and none of the 2 LEDs would even blink; and I got: (from the printf output)

Error:FreeRTOS\queue.c,730
semaphore sent failed in q receive. 1
Error:FreeRTOS\queue.c,1245

I am using uVision5 compiler by the way,and again it’s a STM32f103.
Thank you so much for checking!

------------------ the following is the source code May 16 2024 --------------------------

#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "custom_GPIO.h"
#include "queue.h"
#include "FreeRTOSConfig.h"

//------------------------1A
#include "semphr.h"
//------------------------1A


#define START_TASK_PRIO		1
#define START_STK_SIZE		128
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);

#define LED1_TASK_PRIO				1
#define LED2_TASK_PRIO				2
#define KEY_SCAN_PRIO				3
#define QUEUE_RECEIVE_PRIO			5
#define SEMAPHORE_RECEIVE_PRIO		4

#define LED1_STK_SIZE				50
#define LED2_STK_SIZE				50
#define KEY_SCAN_STK_SIZE			1024
#define SEMAPHORE_RECEIVE_STK_SIZE	512

//------------ PROBLEM POINT --------
//May 16 2024, 64 would work; but
//#define QUEUE_RECEIVE_STK_SIZE			64
//May 16 2024,  but 512 would hang up the whole thing.
#define QUEUE_RECEIVE_STK_SIZE				512
//---------------


TaskHandle_t LED1Task_handler;
TaskHandle_t LED2Task_handler;
TaskHandle_t KeyScanTask_handler;
TaskHandle_t QueueReceiveTask_handler;
//SemaphoreHandle_t BinarySem_Handle=NULL;
SemaphoreHandle_t BinarySem_Handle;



void led1_task(void *pvParameters);
void led2_task(void *pvParameters);
void key_scan_and_send_task(void *pvParameters);
void queue_receive_task(void *pvParameters);			//Apr 23 2024 queue receiver 
void semaphore_receive_task(void *pvParameters);	//Apr 24 2024 semaphore Receiver 
void send_task(void * pvParameters);

QueueHandle_t Test_Queue =NULL;	

#define	QUEUE_LEN	4
#define QUEUE_SIZE	4


/*************************************
*	FN NAME	:main
**************************************/
int main(void)
{

	static uint16_t i;
	LED_Init();
	PB4_PB5_Input_Init();	//added Apr 20 2024

	SysTick_Init(72);			//problem if ran
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	My_USART2_Init(115200);		//note: in lecture, it's USART1_INIT(115200);


	xTaskCreate(	(TaskFunction_t	) start_task,			
					(const char*	) "start_task",			
					(uint16_t		) START_STK_SIZE,		
					(void*			) NULL,					
					(UBaseType_t	) START_TASK_PRIO,		
					(TaskHandle_t*	) &StartTask_Handler
				);
	vTaskStartScheduler();									
}


void start_task(void *pvParameters)
{
	BaseType_t x1stReturnValue=pdPASS;		//May 7 2024. create a return value variable and init it to pdPASS(=pdTRUE).
	uint16_t i;

	taskENTER_CRITICAL();		

	Test_Queue = xQueueCreate	(	(UBaseType_t) QUEUE_LEN,
									(UBaseType_t) QUEUE_SIZE
								);
	BinarySem_Handle = xSemaphoreCreateBinary();
	if(BinarySem_Handle!=NULL)
	{	printf("BinarySem_Handle created successfully \n" );
		printf("BinarySem_Handle's value: %6x\n", BinarySem_Handle   );
		printf("-----------------------------\n"  );
	}


	for (i=0; i<2; i++)
	{

	//---------------1G
	x1stReturnValue=xSemaphoreGive( BinarySem_Handle);  //May 7 2024.
	if(x1stReturnValue==pdPASS)
	{	printf("Initial semaphore sent successfully! %d\n",i);
	}
	else
	{	printf("Initial semaphore sent failed. %d\n",i);
	}
	//--------------1G
	//------------------1H
	x1stReturnValue=xSemaphoreTake( BinarySem_Handle, portMAX_DELAY	);
		if(x1stReturnValue==pdPASS)
	{	printf("Initial semaphore received successfully! %d\n",i);
	}
	else
	{	printf("Initial semaphore received failed. %d\n",i);
	}
	//------------------1H
	}
	
	
	
	

	xTaskCreate(	(TaskFunction_t	) led1_task,
					(const char*	) "led1_task",
					(uint16_t		) LED1_STK_SIZE,
					(void*			) NULL,
					(UBaseType_t	) LED1_TASK_PRIO,
					(TaskHandle_t*	) &LED1Task_handler
				);


	xTaskCreate(	(TaskFunction_t	) led2_task,
					(const char*	) "led2_task",
					(uint16_t		) LED2_STK_SIZE,
					(void*			) NULL,
					(UBaseType_t	) LED2_TASK_PRIO,
					(TaskHandle_t*	) &LED2Task_handler
				);
					
					
	xTaskCreate(	(TaskFunction_t	) queue_receive_task,
					(const char*	) "queue receive_task",
					(uint16_t		) QUEUE_RECEIVE_STK_SIZE,
					(void*			) NULL,
					(UBaseType_t	) QUEUE_RECEIVE_PRIO,
					(TaskHandle_t*	) &QueueReceiveTask_handler
				);

	xTaskCreate(	(TaskFunction_t	) key_scan_and_send_task,
					(const char*	) "key_scan_and_send_task",
					(uint16_t		) KEY_SCAN_STK_SIZE,
					(void*			) NULL,
					(UBaseType_t	) KEY_SCAN_PRIO,
					(TaskHandle_t*	) &KeyScanTask_handler
				);
					
		xTaskCreate(	(TaskFunction_t	) semaphore_receive_task,
					(const char*	) "semphr receive_task",
					(uint16_t		) SEMAPHORE_RECEIVE_STK_SIZE,
					(void*			) NULL,
					(UBaseType_t	) SEMAPHORE_RECEIVE_PRIO,
					(TaskHandle_t*	) &BinarySem_Handle
				);

				

	vTaskDelete(StartTask_Handler);		
	taskEXIT_CRITICAL();				
}



void led1_task(void * pvParameters)
{
	//============================
	BaseType_t x1stReturnValue=pdPASS;		//May 7 2024. 
	uint16_t i;


	while(1)
	{	
		PBout(6) = 1;		//RED LED.
		vTaskDelay(1000);
		PBout(6) = 0;		//RED LED.
		vTaskDelay(1000);
	}
}

void led2_task(void * pvParameters)
{
	while(1)
	{	
	
		PBout(7) = 0;		//BLUE LED.
		vTaskDelay(10);
		PBout(7) = 1;		//BLUE LED.
		vTaskDelay(120);
		PBout(7) = 0;		//BLUE LED.
		vTaskDelay(10);
		PBout(7) = 1;		//BLUE LED.
		vTaskDelay(1500);
		
	}
}
		
void key_scan_and_send_task(void *pvParameters)
{
	BaseType_t xReturnValue=pdPASS;		
                            //Apr 23 2024. create a return value variable and init it to pdPASS(=pdTRUE).
	uint32_t dataPB4_to_send=0xb4;		//send it upon PB4 pin grouding.
	uint32_t dataPB5_to_send=0xb5;		//send it upon PB5 pin grouding.



	while(1)
		{	if(PBin(4)==0)		//semaphore test: manually grounded PB4.
			{	//-------------------1C
				//the following suspension code worked. commented out to make room for semaphore tests.
				//Apr 24 2024
				//printf("PB4 pin depressed. \n");
				//printf("Both blue and red led tasks suspended! \n");
				//vTaskSuspend(LED1Task_handler);
				//vTaskSuspend(LED2Task_handler);
				//-------------------1C
				
				//---------------1E
				xReturnValue=xSemaphoreGive(	BinarySem_Handle);  //did not give successfully. not sure why.Apr 24 2024.
				if(xReturnValue==pdPASS)
				{	printf("PB4 semaphore sent successfully! \n");
				}
				else
				{	printf("Failed sending PB4 semaphore. \n");
				}
				//--------------1E

			}

			/*-------------1J
			if(PBin(5)==0)		//queue test: manually grounded PB5.
			{	printf("PB5 pin from scan task depressed. \n");				
			}
			1J--------*/
			
			
			vTaskDelay(100);	
			
		}
		
}

void queue_receive_task(void *pvParameters)				
//Apr 23 2024 receiver of list messages/items.
{
	BaseType_t xReturnValue=pdTRUE;		//create return value variable and set it to pdTRUE for now.
	uint32_t received_from_queue;		//create a variable for receiving.
	
	BaseType_t x1stReturnValue=pdPASS;		//May 7 2024. 
	uint16_t i=0;

	while(1)
	{	
		i++;
		x1stReturnValue=xSemaphoreGive( BinarySem_Handle);  //May 7 2024.
		if(x1stReturnValue==pdPASS)
		{	printf("semaphore sent ok in q receive! %d \n", i);
		}
		else
		{	printf("semaphore sent failed in q receive. %d \n", i);
		}
		//--------------1G
	
		x1stReturnValue=xSemaphoreTake( BinarySem_Handle, portMAX_DELAY	);
		if(x1stReturnValue==pdPASS)
		{	printf("semaphore received ok in q receive! %d \n", i);
		}
		else
		{	printf("semaphore received failed in q receive. %d \n", i);
		}

		vTaskDelay(1000);

	}	
	
}

void semaphore_receive_task(void *pvParameters)				
//Apr 23 2024 receiver of list messages/items.
{
	BaseType_t xReturnValue=pdTRUE;		//create return value variable and set it to pdTRUE for now.
	//uint32_t received_from_semaphore;	//create a variable for receiving.
	


	while(1)
		{	if(PBin(5)==0)		//queue test: manually grounded PB5.
			{	
				printf("PB5 pin from receive task depressed. \n");
				//xReturnValue=xSemaphoreTake( BinarySem_Handle, portMAX_DELAY	);
				//xReturnValue=xSemaphoreTake( BinarySem_Handle, 0	);
					//will keep waiting until it gets the message. This waiting is equivalent to
					//vTaskDelay(), which introduce a blocked state of this task. a block state
					//for any task must exist otherwise it will consume cpu resources whenever 
					//possible, or keep other tasks from executing if it had a higher priority.
									
				if(xReturnValue==pdTRUE)
				{	printf("-------Semaphore received successfully. \n");
				}
				else
				{	printf("ERROR receiving semaphore.\n");
				}

				vTaskDelay(50);



			}
		}
}
			
//---------------- end of test source code May 16 2024 -----------------------

I’d propose to increase the start task stack size, too.
printf family functions usually require quite a lot of stack.

Thank you for the sample code. I do not have your compiler or an STM32F103 in my junk pile (i ordered one) but I do have an RP2040 which has the same core so I made a few minor changes to run your code. I have seen some errors which may match your experience.

  1. As mentioned by @hs2 I had to increase the size of the stack for the StartTask_Handler. I raised the stack size to 256 as a convenient random number. I do recommend that you start your tasks in main before you start the scheduler rather than springboard your tasks from StartTask_Handler.
  2. I temporarily removed the queue_receive task as I was getting a HARDFAULT as soon as that ran. I will come back to it.
  3. The key_scan_and_send_task is a higher priority than the LED’s. If the buttons are NOT pressed then it will use 100% of the CPU just scanning for a button. This will prevent the LED’s from running. I raised the LED task priority to 6 & 7. The use of the vTaskDelay will give CPU time to other tasks. Now my LED’s are blinking. I think the vTaskDelay in the key_scan task should be moved OUTSIDE of the if(Pin()) statement to make the polling regular.
  4. when I reenable the queue_receive_task it fails on the xSemaphoreGive with an assert that is checking the give parameters. You can see the call stack in the attached image.

    I will look at this handle next.

The issue with the binary semaphore is in the xTaskCreate.

		xTaskCreate(	(TaskFunction_t	) semaphore_receive_task,
					(const char*	) "semphr receive_task",
					(uint16_t		) SEMAPHORE_RECEIVE_STK_SIZE,
					(void*			) NULL,
					(UBaseType_t	) SEMAPHORE_RECEIVE_PRIO,
					(TaskHandle_t*	) &BinarySem_Handle
				);

Note that at the end of the function you pass a pointer to the BinarySem_Handle. This parameter to xTaskCreate is where xTaskCreate will fill a TaskHandle so you can reference it in the future. But in this case you used the same variable that earlier you used to hold the binary semaphore. This overwrote the previous binary semaphore handle, replacing it with the task handle. Then later the queue_receive_task tries to “give” this handle and it breaks. I was getting an assert. If you replace that reference with a NULL.

		xTaskCreate(	(TaskFunction_t	) semaphore_receive_task,
					(const char*	) "semphr receive_task",
					(uint16_t		) SEMAPHORE_RECEIVE_STK_SIZE,
					(void*			) NULL,
					(UBaseType_t	) SEMAPHORE_RECEIVE_PRIO,
					(TaskHandle_t*	) NULL
				);

It should work. The NULL is ok because you never use that task handle so you don’t need a copy.

Good Luck

I don’t know if you have found the book, but it provides a lot of useful information on how FreeRTOS works with many examples. You can locate the book here. Free RTOS Book and Reference Manual

edit: typo

@hs2 @jjulich and richard damon
My little test project seems to work up to this point with all the help provided by you guys, where I am testing the sending and receiving of binary semaphores.

  1. @hs2 I have increased the start task stack size, and it certainly made a difference; yet it seems that it needs to be just at the right size, either too small or too big would cause problems. As of now, it’s set at 900 instead of the earlier 128.

  2. It was a great find @jjulich that I was overwriting the binary semaphore handle using the task handle. I did go through all the code earlier but still missed it. This correction enabled me to send and receive correctly now.

  3. @jjulich It’s great that you spotted the vTaskDelay() is inside the if() instead of while(1).

  4. @jjulich when you said you suggested that I can start the tasks in main() instead of spring boarding off the startTask(), did you mean that I can create all tasks and then start the scheduler all in main(), and skip the startTask() all together?

  5. It appears that if I set the start task stack to be 1024 or 1300, the send and receive tasks are not even entered while the LED1 and LED2 worked fine. 900 seems to be the majic number. Which I don’t know what the inner workings are.

I appreciated all the thoughts, and the help down to the code level! that moved the learning of the FreeRTOS forward!

Using startTask to create the tasks and then exit is not required. If you move all of startTask into main, remove the startTask creation and call vTaskStartScheduler when the tasks are ready, then you will save a bit of memory. If you design your system to use long running tasks rather than short lived workers, the performance will be a little higher and the memory will operate more efficiently with less chance of fragmentation and allocation failure.

The magic memory sizes is suspicious. Check that the tasks are actually getting created. You may be running out of heap. The STM32F103 family is not the most constrained chip I have used but the small ones are pretty small. Allocating everything once at the beginning of operation is a winning strategy for an embedded system.

@jjulich That’s good info to know. I worked with many consultants and none of them skipped the StartTask( ), leading me to the believe that it was required. I worked with assembly and none RTOS c code before and I find the actual writing of code relatively easy, it is alwsys the “suspicious” part of the process that keeps you from making progress. I still would like to understand deeper why the StartTask( ) “requires” a majic number of stack. Before we truly understand it down to the bit level, we don’t know when and how it would show up again. Perhaps it should be posted as a separate topic.

I’d like to thank you so much again for sharing your knowledge by going thru the actual code, and making my learning that much quicker and easier! By the way I looked up how to pronounce your last name :slightly_smiling_face:

As mentioned by @jjulich you really should check the return codes of FreeRTOS API functions like xTaskCreate.
It’s possible that specifying too much stack simply exhausts the heap and the creation fails. Same applies to queue creation etc.

Yes I will check the highest water mark in stack usage and etc to dig in further. Thanks again!