Skip to main content
Graduate II
March 7, 2025
Question

Atomic variable for scheduler question

  • March 7, 2025
  • 17 replies
  • 4035 views

I traditionally use a volatile type for my schedulers. Very simple like so

 

volatile uint8_t myflag;
uint8_t ticks;
void Func(void){
 ++ticks;
 if(!(ticks % 5)){
 myflag = true;
 }
}

void User(void){
 if(myflag) {
 myflag = false;
 //DO Something
 }
}

I'm trying to update my knowledge. The online thought seems to be that this isn't really the safe way as this does not have any memory barrier. So the proper way is to use an atomic. So I tried an atomic.

atomic_bool myflag = ATOMIC_VAR_INIT(false);
uint8_t ticks;
void Func(void){
 ++ticks;
 if(!(ticks % 5)){
 atomic_store(&myflag ,true);
 }
}

void User(void){
 if(atomic_load(&myflag)) {
 atomic_store(&myflag ,false);
 //DO Something
 }
}

However, this definitely does not work. The User misses flag settings regularly. Its not just a delay. But per the scope its like a complete flag setting is missed. What have I done wrong here? I even tried to make it volatile and also tried atomic_flag but they both miss settings but the old school way does not miss any settings.

    This topic has been closed for replies.

    17 replies

    Carl_GAuthor
    Graduate II
    March 7, 2025

    Theoretically, as Func is called by interrupt, it could set the flag true while User is at POINT_A. This would cause the loss of the value. In practice this isn't a major issue because User is called continuously in the main while loop and will act on the flag immediately. Anyway, there are ways to handle that. Such as using a counter instead of a flag. I guess the atomics being heavier and slower are why I am missing with the atomic bool? Since being atomic does not prevent this problem.

    void User(void){
     if(myflag) {
     //POINT_A
     myflag = false;
     //DO Something
     }
    }

    . I tried test_and_set technique but it behaved just as the atomic bool.

    Super User
    March 8, 2025

    Can you provide a complete buildable example? It's not clear how exactly the Func() function is called... Bugs in the compiler or library are very unlikely. Also, is your chip CM0 or CM3 or better?

     

    Carl_GAuthor
    Graduate II
    March 8, 2025

    I guess I need to get more fluent with Github so I can easily share code. I created an extremely simple project for the STM32G071RB Nucleo board.

    Tested both ways. With volatile and with atomic_flag. volatile works, atomic_flag does not.

    I've only added the few lines of code here to do the scheduler but I have included the complete files.

    STM32CubeIDE 1.17.0

    main.c

    /* USER CODE BEGIN Header */
    /**
     ******************************************************************************
     * @file : main.c
     * @brief : Main program body
     ******************************************************************************
     * @attention
     *
     * Copyright (c) 2025 STMicroelectronics.
     * All rights reserved.
     *
     * This software is licensed under terms that can be found in the LICENSE file
     * in the root directory of this software component.
     * If no LICENSE file comes with this software, it is provided AS-IS.
     *
     ******************************************************************************
     */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "stdbool.h"
    #include "stdatomic.h"
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    UART_HandleTypeDef huart2;
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    static void MX_GPIO_Init(void);
    static void MX_USART2_UART_Init(void);
    /* USER CODE BEGIN PFP */
    //extern atomic_bool fivems;
    //extern atomic_bool onems;
    extern atomic_flag onems;
    extern atomic_flag fivems;
    extern volatile uint8_t fivems_flag;
    extern volatile uint8_t onems_flag;
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
     * @brief The application entry point.
     * @retval int
     */
    int main(void)
    {
    
     /* USER CODE BEGIN 1 */
    
     /* USER CODE END 1 */
    
     /* MCU Configuration--------------------------------------------------------*/
    
     /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
     HAL_Init();
    
     /* USER CODE BEGIN Init */
    
     /* USER CODE END Init */
    
     /* Configure the system clock */
     SystemClock_Config();
    
     /* USER CODE BEGIN SysInit */
    
     /* USER CODE END SysInit */
    
     /* Initialize all configured peripherals */
     MX_GPIO_Init();
     MX_USART2_UART_Init();
     /* USER CODE BEGIN 2 */
    
     /* USER CODE END 2 */
    
     /* Infinite loop */
     /* USER CODE BEGIN WHILE */
     while (1)
     {
     /* USER CODE END WHILE */
    
     /* USER CODE BEGIN 3 */
    	 if(/*onems_flag */!atomic_flag_test_and_set(&onems)) {
    		//atomic_store(&onems,false);
    		onems_flag = false;
    		//HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    		//HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_4);//up
    
    		//HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    		//HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_4);//up
    		 HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8);
    	 }
    
    
    	 if(/*fivems_flag*/ !atomic_flag_test_and_set(&fivems)) {
    		fivems_flag = false;
    		//atomic_store(&fivems,false);
    		//HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    		//HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_4);//up
    
    
    		//HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    		HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_9);
    	 }
     }
     /* USER CODE END 3 */
    }
    
    /**
     * @brief System Clock Configuration
     * @retval None
     */
    void SystemClock_Config(void)
    {
     RCC_OscInitTypeDef RCC_OscInitStruct = {0};
     RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
     /** Configure the main internal regulator output voltage
     */
     HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
    
     /** Initializes the RCC Oscillators according to the specified parameters
     * in the RCC_OscInitTypeDef structure.
     */
     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
     RCC_OscInitStruct.HSIState = RCC_HSI_ON;
     RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
     RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
     RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
     RCC_OscInitStruct.PLL.PLLN = 8;
     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
     if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
     {
     Error_Handler();
     }
    
     /** Initializes the CPU, AHB and APB buses clocks
     */
     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
     |RCC_CLOCKTYPE_PCLK1;
     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
    
     if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
     {
     Error_Handler();
     }
    }
    
    /**
     * @brief USART2 Initialization Function
     * @PAram None
     * @retval None
     */
    static void MX_USART2_UART_Init(void)
    {
    
     /* USER CODE BEGIN USART2_Init 0 */
    
     /* USER CODE END USART2_Init 0 */
    
     /* USER CODE BEGIN USART2_Init 1 */
    
     /* USER CODE END USART2_Init 1 */
     huart2.Instance = USART2;
     huart2.Init.BaudRate = 115200;
     huart2.Init.WordLength = UART_WORDLENGTH_8B;
     huart2.Init.StopBits = UART_STOPBITS_1;
     huart2.Init.Parity = UART_PARITY_NONE;
     huart2.Init.Mode = UART_MODE_TX_RX;
     huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
     huart2.Init.OverSampling = UART_OVERSAMPLING_16;
     huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
     huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
     huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
     if (HAL_UART_Init(&huart2) != HAL_OK)
     {
     Error_Handler();
     }
     if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
     {
     Error_Handler();
     }
     if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
     {
     Error_Handler();
     }
     if (HAL_UARTEx_DisableFifoMode(&huart2) != HAL_OK)
     {
     Error_Handler();
     }
     /* USER CODE BEGIN USART2_Init 2 */
    
     /* USER CODE END USART2_Init 2 */
    
    }
    
    /**
     * @brief GPIO Initialization Function
     * @PAram None
     * @retval None
     */
    static void MX_GPIO_Init(void)
    {
     GPIO_InitTypeDef GPIO_InitStruct = {0};
    /* USER CODE BEGIN MX_GPIO_Init_1 */
    /* USER CODE END MX_GPIO_Init_1 */
    
     /* GPIO Ports Clock Enable */
     __HAL_RCC_GPIOC_CLK_ENABLE();
     __HAL_RCC_GPIOF_CLK_ENABLE();
     __HAL_RCC_GPIOA_CLK_ENABLE();
     __HAL_RCC_GPIOD_CLK_ENABLE();
    
     /*Configure GPIO pin Output Level */
     HAL_GPIO_WritePin(GPIOA, LED_GREEN_Pin|GPIO_PIN_8, GPIO_PIN_RESET);
    
     /*Configure GPIO pin Output Level */
     HAL_GPIO_WritePin(GPIOD, GPIO_PIN_9, GPIO_PIN_RESET);
    
     /*Configure GPIO pin : LED_GREEN_Pin */
     GPIO_InitStruct.Pin = LED_GREEN_Pin;
     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
     GPIO_InitStruct.Pull = GPIO_NOPULL;
     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
     HAL_GPIO_Init(LED_GREEN_GPIO_Port, &GPIO_InitStruct);
    
     /*Configure GPIO pin : PA8 */
     GPIO_InitStruct.Pin = GPIO_PIN_8;
     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
     GPIO_InitStruct.Pull = GPIO_NOPULL;
     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
     /*Configure GPIO pin : PD9 */
     GPIO_InitStruct.Pin = GPIO_PIN_9;
     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
     GPIO_InitStruct.Pull = GPIO_NOPULL;
     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    
    /* USER CODE BEGIN MX_GPIO_Init_2 */
    /* USER CODE END MX_GPIO_Init_2 */
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
     * @brief This function is executed in case of error occurrence.
     * @retval None
     */
    void Error_Handler(void)
    {
     /* USER CODE BEGIN Error_Handler_Debug */
     /* User can add his own implementation to report the HAL error return state */
     __disable_irq();
     while (1)
     {
     }
     /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef USE_FULL_ASSERT
    /**
     * @brief Reports the name of the source file and the source line number
     * where the assert_param error has occurred.
     * @PAram file: pointer to the source file name
     * @PAram line: assert_param error line source number
     * @retval None
     */
    void assert_failed(uint8_t *file, uint32_t line)
    {
     /* USER CODE BEGIN 6 */
     /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
     /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    /* USER CODE BEGIN Header */
    /**
     ******************************************************************************
     * @file stm32g0xx_it.c
     * @brief Interrupt Service Routines.
     ******************************************************************************
     * @attention
     *
     * Copyright (c) 2025 STMicroelectronics.
     * All rights reserved.
     *
     * This software is licensed under terms that can be found in the LICENSE file
     * in the root directory of this software component.
     * If no LICENSE file comes with this software, it is provided AS-IS.
     *
     ******************************************************************************
     */
    /* USER CODE END Header */
    
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "stm32g0xx_it.h"
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    #include "stdbool.h"
    #include "stdatomic.h"
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN TD */
    
    /* USER CODE END TD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    /* USER CODE BEGIN PV */
     static uint32_t ticks;
     //atomic_bool fivems = ATOMIC_VAR_INIT(false);
     //atomic_bool onems = ATOMIC_VAR_INIT(false);
     atomic_flag onems = ATOMIC_FLAG_INIT;
     atomic_flag fivems = ATOMIC_FLAG_INIT;
    
     volatile uint8_t fivems_flag=true;
     volatile uint8_t onems_flag=true;
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /* External variables --------------------------------------------------------*/
    
    /* USER CODE BEGIN EV */
    
    /* USER CODE END EV */
    
    /******************************************************************************/
    /* Cortex-M0+ Processor Interruption and Exception Handlers */
    /******************************************************************************/
    /**
     * @brief This function handles Non maskable interrupt.
     */
    void NMI_Handler(void)
    {
     /* USER CODE BEGIN NonMaskableInt_IRQn 0 */
    
     /* USER CODE END NonMaskableInt_IRQn 0 */
     /* USER CODE BEGIN NonMaskableInt_IRQn 1 */
     while (1)
     {
     }
     /* USER CODE END NonMaskableInt_IRQn 1 */
    }
    
    /**
     * @brief This function handles Hard fault interrupt.
     */
    void HardFault_Handler(void)
    {
     /* USER CODE BEGIN HardFault_IRQn 0 */
    
     /* USER CODE END HardFault_IRQn 0 */
     while (1)
     {
     /* USER CODE BEGIN W1_HardFault_IRQn 0 */
     /* USER CODE END W1_HardFault_IRQn 0 */
     }
    }
    
    /**
     * @brief This function handles System service call via SWI instruction.
     */
    void SVC_Handler(void)
    {
     /* USER CODE BEGIN SVC_IRQn 0 */
    
     /* USER CODE END SVC_IRQn 0 */
     /* USER CODE BEGIN SVC_IRQn 1 */
    
     /* USER CODE END SVC_IRQn 1 */
    }
    
    /**
     * @brief This function handles Pendable request for system service.
     */
    void PendSV_Handler(void)
    {
     /* USER CODE BEGIN PendSV_IRQn 0 */
    
     /* USER CODE END PendSV_IRQn 0 */
     /* USER CODE BEGIN PendSV_IRQn 1 */
    
     /* USER CODE END PendSV_IRQn 1 */
    }
    
    /**
     * @brief This function handles System tick timer.
     */
    void SysTick_Handler(void)
    {
     /* USER CODE BEGIN SysTick_IRQn 0 */
    	++ticks;
    	if(!(ticks % 5)){
    		//atomic_store(&fivems,true);
    		atomic_flag_clear(&fivems);
    		fivems_flag = true;
    	}
    	//atomic_store(&onems,true);
    	atomic_flag_clear(&onems);
    	onems_flag = true;
     /* USER CODE END SysTick_IRQn 0 */
     HAL_IncTick();
     /* USER CODE BEGIN SysTick_IRQn 1 */
    
     /* USER CODE END SysTick_IRQn 1 */
    }
    
    /******************************************************************************/
    /* STM32G0xx Peripheral Interrupt Handlers */
    /* Add here the Interrupt Handlers for the used peripherals. */
    /* For the available peripheral interrupt handler names, */
    /* please refer to the startup file (startup_stm32g0xx.s). */
    /******************************************************************************/
    
    /* USER CODE BEGIN 1 */
    
    /* USER CODE END 1 */

     

    stm32g0xx_it.c

     

    Carl_GAuthor
    Graduate II
    March 8, 2025

    The more I poke around the more it seems that cortex M0 does not support the GCC atomic library or any native "atomic" read modify write type of actions in hardware. When I add anything other than a bool it works fine at first because I think the compiler optimizes it out. But as soon as I try to use it I get linker errors. So I think these atomic bool and atomic flag are actually not atomic at all. 

    Indeed. The code below where I disable the IRQ works as expected. Its crude but it makes the point. These instructions are not atomic. Well, strictly speaking the values can't be torn. So they are atomic in that sense. But not in the uninterruptible sense.

     while (1)
     {
     /* USER CODE END WHILE */
    
     /* USER CODE BEGIN 3 */
    	 __disable_irq();
    	 if(!atomic_flag_test_and_set(&onems)) {
    		onems_flag = false;
    		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8);
    	 }
    	 __enable_irq();
    
    	 __disable_irq();
    	 if(!atomic_flag_test_and_set(&fivems)) {
    		fivems_flag = false;
    		HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_9);
    	 }
    	 __enable_irq();
     }

     

    Super User
    March 9, 2025

    Ah so this is Cortex-M0. It does not have ldrex and strex ... so... yes you may be right, stdatomic functions may be too heavy for CM0.

     

    Carl_GAuthor
    Graduate II
    March 9, 2025

    Found a post from 10 years ago recognizing this issue. I think they 1/2 fixed it in that all the other GCC functions won't link. But for some reason this one does link, but its actually NOT atomic.

    https://answers.launchpad.net/gcc-arm-embedded/+question/265649

     

    Graduate
    March 9, 2025

    Why would you need a memory barrier and/or atomic access for setting/clearing boolean variables? Your original code is almost correct. I may only suggest a little cleanup:

     

    volatile bool myflag;
    
    void Func(void){
     static uint8_t ticks;
     if (++ticks == 5) {
     ticks = 0;
     myflag = true;
     }
    }

    Atomic implementation is environment-specific. It may be void = non implemented. Have you checked it?

     

    Carl_GAuthor
    Graduate II
    March 9, 2025

    "Func" operates within an interrupt. User is within the function main(). So there needs to be some synchronization.

    I'm not sure how to check if its void. The other calls don't link so that is how I know they are not there. But some of these are supposed to be "lock free" which probably implies they don't need special functions from the MCU.  Anyway, they linked to something. So I had no idea whatever it was did not work. Anyway, if you follow the URL I posted you will see its a legit problem that was apparently 1/2 solved over 10 years ago.

    Also, I suppose this iterate and check technique is faster than the modulus?

    Graduate
    March 9, 2025

    Ok, maybe I am too dumb but what exactly kind of synchronization is needed for bool variable access which is not modified (so no RMW access), just set, reset or tested?

    Carl_GAuthor
    Graduate II
    March 9, 2025

    What do you mean by "not modified"? The bool in my example is modified by both processes.

    Graduate
    March 9, 2025

    No, it is not modified. It is set to 0 or to 1. So, what would "locking" or "atomic access" change? It could maybe be needed if the numeric variable was incremented or decremented, but it is not.

    Carl_GAuthor
    Graduate II
    March 9, 2025

    Per your definition, no boolean ever needs synchronization. Correct?

    Graduate
    March 9, 2025

    Not if it is always set by one party and cleared by another (one only).

    Carl_GAuthor
    Graduate II
    March 9, 2025

    Are you being pedantic about the term "synchronization"? Because clearly in multithreaded environments you need to ensure changes made by one thread are seen by another with some deterministic sequence and timing.