2025-03-07 8:19 AM
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.
2025-03-07 8:46 AM
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.
2025-03-08 11:52 AM - edited 2025-03-08 11:54 AM
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?
2025-03-08 2:44 PM
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
2025-03-08 3:35 PM
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();
}
2025-03-08 4:01 PM
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.
2025-03-08 6:28 PM
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
2025-03-09 12:11 AM - edited 2025-03-09 12:14 AM
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?
2025-03-09 6:56 AM
"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?
2025-03-09 9:02 AM - edited 2025-03-09 9:12 AM
> Atomic implementation is environment-specific. It may be void = non implemented.
Hmm... if you just #include <stdatomic.h> and declare some atomic variable and it compiles without errors, it means that the compiler (for the given target arch) understands the _Atomic keyword.
Next, probe implementation of sized types: #if ATOMIC_INT_LOCK_FREE , #if ATOMIC_BOOL_LOCK_FREE , #if ATOMIC_POINTER_LOCK_FREE and so on. Additional test for boolean "flags": __GCC_ATOMIC_TEST_AND_SET_TRUEVAL tells if the type for "flags" is _Bool or uint8_t.
Then, we see in the stdatomic.h that the atomic functions are defined via set of other functions with __ names. Either these are compiler intrinsics (again for the given target arch) or they should be provided as real external functions (slow).
Hate to say this, but from that launchpad link it looks indeed like a compiler bug ((
What to advice to the OP? Old is gold.