Task doesn't run without taskYield()

Hello, I have 5 tasks, all priority 1. Some run on a timed basis using vTaskDelayUntil() while others block waiting for a Semaphore. The 5th task is short and just runs free. It will not run however unless I put either a vTaskDelay(1) or a taskYield() at the end.

I’m new to FreeRTOS and don’t understand why this is? I would expect a forced context switch regardless since the tasks all have equal priority.

FYI, this is FreeRTOS v10.4.6 running on an Atmel/Microchip ATSAMC21J18A

void vTask_HS_Drivers(void *pvParameters)
{
    while (1)
    {

	/* Do Stuff */
	
	taskYIELD(); // Needs this or won't execute

    }
}

You need to have PREEMPTION enabled with the statement

#define configUSE_PREEMPTION 1 

in your FreeRTOSConfig.h file to allow context switches at the tick interrupt.

Sorry, forgot to mention in my original post but I did have that set.

Do you have configUSE_TIME_SLICING set to 1?

I wasn’t aware of that setting and thought for sure that would be the problem but unfortunately that is also set to 1.

I did a test by having that task do nothing but toggle a GPIO port and checking with an oscilloscope. Without out the taskYield(), the port toggles every 100ns. With taskYield() it toggles every 1ms which would be expected.

Here is my full FreeRTOSConfig.h

/*
 * FreeRTOS Kernel V10.3.0
 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://www.FreeRTOS.org
 * http://aws.amazon.com/freertos
 *
 * 1 tab == 4 spaces!
 */


#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/*-----------------------------------------------------------
 * Application specific definitions.
 *
 * These definitions should be adjusted for your particular hardware and
 * application requirements.
 *
 * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
 * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
 *
 * See http://www.freertos.org/a00110.html
 *----------------------------------------------------------*/
#define configUSE_PREEMPTION                    1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_TICKLESS_IDLE                 0
#define configCPU_CLOCK_HZ                      ( 48001024UL )
#define configTICK_RATE_HZ                      ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES                    ( 5UL )
#define configMINIMAL_STACK_SIZE                ( 128 )
#define configSUPPORT_DYNAMIC_ALLOCATION        1
#define configSUPPORT_STATIC_ALLOCATION         0
#define configTOTAL_HEAP_SIZE                   ( ( size_t ) 8192 )
#define configMAX_TASK_NAME_LEN                 ( 16 )
#define configUSE_16_BIT_TICKS                  0
#define configIDLE_SHOULD_YIELD                 1
#define configUSE_MUTEXES                       1
#define configUSE_RECURSIVE_MUTEXES             0
#define configUSE_COUNTING_SEMAPHORES           1
#define configUSE_TASK_NOTIFICATIONS            1
#define configQUEUE_REGISTRY_SIZE               0
#define configUSE_QUEUE_SETS                    0
#define configUSE_TIME_SLICING                  1
#define configUSE_NEWLIB_REENTRANT              0
#define configUSE_TASK_FPU_SUPPORT              0


/* Hook function related definitions. */
#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0
#define configCHECK_FOR_STACK_OVERFLOW          2
#define configUSE_MALLOC_FAILED_HOOK            1

/* Run time and task stats gathering related definitions. */
#define configGENERATE_RUN_TIME_STATS           0
#define configUSE_TRACE_FACILITY                0
#define configUSE_STATS_FORMATTING_FUNCTIONS    0

/* Co-routine related definitions. */
#define configUSE_CO_ROUTINES                   0
#define configMAX_CO_ROUTINE_PRIORITIES         2

/* Software timer related definitions. */
#define configUSE_TIMERS                        1
#define configTIMER_TASK_PRIORITY               3
#define configTIMER_QUEUE_LENGTH                10
#define configTIMER_TASK_STACK_DEPTH            256
#define configUSE_DAEMON_TASK_STARTUP_HOOK      0

/* Misc */
#define configUSE_APPLICATION_TASK_TAG          0


/* Interrupt nesting behaviour configuration. */
/* The priority at which the tick interrupt runs.  This should probably be kept at lowest priority. */
#define configKERNEL_INTERRUPT_PRIORITY         (3 << (8 - 2))
/* The maximum interrupt priority from which FreeRTOS.org API functions can be called.
 * Only API functions that end in ...FromISR() can be used within interrupts. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    (1 << (8 - 2))


/* Optional functions - most linkers will remove unused functions anyway. */
#define INCLUDE_vTaskPrioritySet                1
#define INCLUDE_uxTaskPriorityGet               1
#define INCLUDE_vTaskDelete                     0
#define INCLUDE_vTaskSuspend                    1
#define INCLUDE_vTaskDelayUntil                 1
#define INCLUDE_vTaskDelay                      1
#define INCLUDE_xTaskGetSchedulerState          0
#define INCLUDE_xTaskGetCurrentTaskHandle       0
#define INCLUDE_uxTaskGetStackHighWaterMark     1
#define INCLUDE_xTaskGetIdleTaskHandle          0
#define INCLUDE_eTaskGetState                   0
#define INCLUDE_xTimerPendFunctionCall          0
#define INCLUDE_xTaskAbortDelay                 1
#define INCLUDE_xTaskGetHandle                  0
#define INCLUDE_xQueueGetMutexHolder            0
#define INCLUDE_xSemaphoreGetMutexHolder        0
#define INCLUDE_uxTaskGetStackHighWaterMark2    0
#define INCLUDE_xTaskResumeFromISR              0


#endif /* FREERTOS_CONFIG_H */

Unrelated, but I’d recommend
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
assuming your application won’t have more than 32 tasks.

Could that be a compiler optimization issue? Can we see the entire task’s code?

I’ve changed some names but here is the full code of that task. It is just checking bits of a globally defined bitfield structure and setting or clearing GPIO ports accordingly. Originally I thought it wasn’t executing at all until I realized with the oscilloscope that it executes continuously starving the other tasks.
Just as a note, I do plan on adding some synchronization to this later with a binary semaphore or event group but I’m just getting the ground level established.

void vTask_HS_Drivers(void *pvParameters)
{
    while (1)
    {	

	GlobalStructure.s1 ? S1_Set() : S1_Clear();
	GlobalStructure.s2 ? S2_Set() : S2_Clear();
	GlobalStructure.s3 ? S3_Set() : S3_Clear();
	GlobalStructure.s4 ? S4_Set() : S4_Clear();
	GlobalStructure.s5 ? S5_Set() : S5_Clear();
	GlobalStructure.s6 ? S6_Set() : S6_Clear();
	GlobalStructure.s7 ? S7_Set() : S7_Clear();
	GlobalStructure.s8 ? S8_Set() : S8_Clear();
	GlobalStructure.s9 ? S9_Set() : S9_Clear();
	GlobalStructure.s10 ? S10_Set() : S10_Clear();
	GlobalStructure.s11 ? S11_Set() : S11_Clear();
	GlobalStructure.s12 ? S12_Set() : S12_Clear();

	taskYIELD();  // Needs this or executes continuously

    }
}

There is nothing in this task which causes it to block - so it will run continuously. This means that other lower priority tasks wont get a chance to run and the tasks at the same priority will run in a round robin fashion.

Which other tasks do you think are starved and how do you verify that those are starved?

All 5 tasks have the same priority of 1. Right now I’m assuming the other tasks are starved because the data that this task uses to change my GPIO ports is updated in a CAN receive task (polled). The ports do not change without the taskYield() present in this task. An experiment I did last week was to simply toggle a port in this task with no portYield() and it toggled every 100ns. I plan on setting up some counters in my tasks and look at them with the debugger.

You may want to check that the Tick interrupt is running, by checking if the tick count is increasing. Without a tick interrupt, the round robin won’t ever run.

Ok… I decided to create a new stripped down project to help get a handle on this and then applied it my actual project. With the help of my oscilloscope I think I understand most of what is going on now, but I’m still have some confusion.

What I learned is that my non blocking tasks were indeed toggling the ports very fast with a period of 312nS (3.2Mhz, but the task itself was only executing every 1ms which is the TickRate and to be expected with time slicing enabled.
With taskYield() at the end of the non blocking tasks, the ports toggle much slower with a period of about 23uS (43.5Khz). What I’m confused about is that the task still appears to run for the 1ms. Wouldn’t taskYield() switch context after just one port toggle?
My full project has CANbus receive and Transmit. Receive polls every 2ms. I think whatever is going on is taking too much time away from the CANbus tasks causing things to crash?

#include <stddef.h>                     // Defines NULL
#include <stdbool.h>                    // Defines true
#include <stdlib.h>                     // Defines EXIT_FAILURE
#include "definitions.h"
#include "task.h"
#include "projdefs.h"                // SYS function prototypes



void Task1(void *pvParameters);
void Task2(void *pvParameters);
void Task3(void *pvParameters);

TaskHandle_t task1_handle;
TaskHandle_t task2_handle;
TaskHandle_t task3_handle;

volatile UBaseType_t  task1_counter = 0;
volatile UBaseType_t  task2_counter = 0;
volatile UBaseType_t  task3_counter = 0;


int main(void)
{
    /* Initialize all modules */
    SYS_Initialize(NULL);

    xTaskCreate(Task1,
	"Task1",
	256,
	NULL,
	1,
	&task1_handle);

    xTaskCreate(Task2,
	"Task2",
	256,
	NULL,
	1,
	&task2_handle);
    
    xTaskCreate(Task3,
	"Task3",
	256,
	NULL,
	1,
	&task3_handle);
    
    vTaskStartScheduler ();

    while (true)
    {
	
    }

    /* Execution should not come here during normal operation */

    return( EXIT_FAILURE);    
    
} /* End of main() */


void Task1(void *pvParameters)
{
    while (1)
    {
	GPIO_PA08_Toggle();
	task1_counter++;
	//taskYIELD();
    }
}

void Task2(void *pvParameters)
{
    while (1)
    {
	GPIO_PA10_Toggle();
	task2_counter++;
	//taskYIELD();
    }
}

void Task3(void *pvParameters)
{
    while (1)
    {
	LED_Toggle();
	task3_counter++;	
	
	vTaskDelay(50);
    }
}


How do you determine that?

Assuming that there are other tasks at the same priority ready to run, the scheduler will pick those. This will be done in a round robin fashion. Looking at the example code you posted, it will behave the following way with taskYIELD calls uncommented -

  1. Task 1 will toggle PA08 and then yield.
  2. Task 2 will toggle PA10 and then yield.
  3. Task 3 will toggle LED and then get blocked for 50 ticks.
  4. Task 1 will run again.

Are you observing a crash? Can you describe the problem that you are trying to solve?