HomeAbout UsContact Us

Fixing FreeRTOS Event Group Overflow in ISR Context

By Jithin Tom
Published in Embedded OS
September 07, 2026
2 min read
Fixing FreeRTOS Event Group Overflow in ISR Context

Table Of Contents

01
Problem Statement: Lost Events in High-Frequency ISRs
02
Root Cause Analysis: 24-bit Counter Limitation
03
Detection Techniques: Monitoring Event Group Values
04
Solution Approach: Thread-Safe Event Group Wrapper
05
Implementation Example: High-Frequency Timer ISR
06
Verification Steps: Testing Overflow Protection
07
Summary
08
Related Reading
09
References
10
Frequently Asked Questions

FreeRTOS event groups are a powerful synchronization primitive for coordinating tasks and interrupt service routines (ISRs). However, their 24-bit internal counter can overflow when too many events are set without being cleared, leading to lost events and unpredictable system behavior. This article explains the root cause of event group overflow, provides detection techniques, and offers a practical solution with a thread-safe wrapper implementation.

Problem Statement: Lost Events in High-Frequency ISRs

Consider a scenario where an ISR sets event group bits at a high frequency (e.g., every microsecond from a timer interrupt), while a task waits for specific bit patterns. Over time, the cumulative number of bits set can exceed 2^24-1 (16,777,215), causing the event group value to wrap around to zero. This overflow resets the event group state, making the task miss events that were set after the overflow point.

Root Cause Analysis: 24-bit Counter Limitation

FreeRTOS stores event group flags in a 24-bit integer (EventBits_t). Each bit set via xEventGroupSetBits() increments the internal representation. When all 24 bits are set (value 16,777,215), the next bit-set operation causes an integer overflow, resetting the value to zero. This behavior is documented in the FreeRTOS API reference but often overlooked in high-frequency interrupt scenarios.

The overflow condition occurs when:

Total events set since last clear >= 2^24

In systems with frequent interrupts (e.g., 1MHz timer ISR setting one bit per interrupt), overflow can occur in under 17 seconds. Even lower-frequency interrupts accumulate over hours or days, making this a latent reliability issue.

Detection Techniques: Monitoring Event Group Values

Early detection prevents field failures. Implement these checks in your application:

  1. Value Monitoring: Periodically read the event group value and log if it approaches 0xFFFFFF (16,777,215).
  2. Delta Checking: Track the number of bits set between reads. If the delta exceeds a threshold (e.g., 10,000 bits), investigate potential overflow.
  3. Wrapper Function Checks: Create a safe wrapper for xEventGroupSetBits() that validates pre- and post-operation values.

Example detection code:

void vCheckEventGroupOverflow(EventGroupHandle_t xEventGroup)
{
EventBits_t uxCurrent = xEventGroupGetBits(xEventGroup);
if (uxCurrent > 0xFFFFF0) { // Within 255 bits of max
log_warning("Event group near overflow: %lu", (unsigned long)uxCurrent);
}
}

Solution Approach: Thread-Safe Event Group Wrapper

The most robust solution combines three strategies:

  1. Atomic Operations: Use critical sections for multi-bit operations to prevent race conditions.
  2. Periodic Clearing: Clear the event group in the task context before it reaches dangerous values.
  3. Overflow Detection: Automatically clear when the value exceeds a safe threshold.

Here’s a complete wrapper implementation:

#include "FreeRTOS.h"
#include "event_groups.h"
#define SAFE_EVENT_GROUP_MAX 0xFFFFF0 // Leave room for 255 bits
#define EVENT_GROUP_CHECK_INTERVAL_MS 1000
typedef struct {
EventGroupHandle_t xEventGroup;
StaticEventGroup_t xEventGroupBuffer;
TimerHandle_t xClearTimer;
} SafeEventGroup_t;
/* Forward declaration */
static void vClearEventGroupTimerCallback(TimerHandle_t xTimer);
/* Create a safe event group */
SafeEventGroup_t* xSafeEventGroupCreate(void)
{
SafeEventGroup_t* pxSafeGroup = pvPortMalloc(sizeof(SafeEventGroup_t));
if (pxSafeGroup != NULL) {
pxSafeGroup->xEventGroup = xEventGroupCreateStatic(&(pxSafeGroup->xEventGroupBuffer));
pxSafeGroup->xClearTimer = xTimerCreateStatic(
"EventGroupClear",
pdMS_TO_TICKS(EVENT_GROUP_CHECK_INTERVAL_MS),
pdTRUE,
(void*)pxSafeGroup,
vClearEventGroupTimerCallback,
&(pxSafeGroup->xClearTimerBuffer));
if (pxSafeGroup->xEventGroup == NULL || pxSafeGroup->xClearTimer == NULL) {
vPortFree(pxSafeGroup);
return NULL;
}
xTimerStart(pxSafeGroup->xClearTimer, 0);
}
return pxSafeGroup;
}
/* Safely set bits from ISR or task */
EventBits_t xSafeEventGroupSetBits(SafeEventGroup_t* pxSafeGroup, const EventBits_t uxBitsToSet)
{
EventBits_t uxReturn;
UBaseType_t uxSavedInterruptStatus;
/* Enter critical section to protect the read-modify-write operation */
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
EventBits_t uxCurrent = xEventGroupGetBits(pxSafeGroup->xEventGroup);
EventBits_t uxNew = uxCurrent | uxBitsToSet;
/* Check for potential overflow */
if (uxNew > SAFE_EVENT_GROUP_MAX) {
/* Clear the group before setting new bits to prevent overflow */
xEventGroupClearBits(pxSafeGroup->xEventGroup, 0xFFFFFFFF);
uxNew = uxBitsToSet; // Only set the requested bits after clear
}
uxReturn = xEventGroupSetBitsFromISR(pxSafeGroup->xEventGroup, uxBitsToSet);
}
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
return uxReturn;
}
/* Safely wait for bits */
EventBits_t xSafeEventGroupWaitBits(SafeEventGroup_t* pxSafeGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait)
{
return xEventGroupWaitBits(pxSafeGroup->xEventGroup,
uxBitsToWaitFor,
xClearOnExit,
xWaitForAllBits,
xTicksToWait);
}
/* Timer callback to periodically check and clear if needed */
static void vClearEventGroupTimerCallback(TimerHandle_t xTimer)
{
SafeEventGroup_t* pxSafeGroup = (SafeEventGroup_t*)pvTimerGetTimerID(xTimer);
if (pxSafeGroup != NULL) {
EventBits_t uxCurrent = xEventGroupGetBits(pxSafeGroup->xEventGroup);
if (uxCurrent > SAFE_EVENT_GROUP_MAX) {
xEventGroupClearBits(pxSafeGroup->xEventGroup, 0xFFFFFFFF);
}
}
}
/* Delete the safe event group */
void vSafeEventGroupDelete(SafeEventGroup_t* pxSafeGroup)
{
if (pxSafeGroup != NULL) {
xTimerDelete(pxSafeGroup->xClearTimer, 0);
vEventGroupDelete(pxSafeGroup->xEventGroup);
vPortFree(pxSafeGroup);
}
}

Implementation Example: High-Frequency Timer ISR

Here’s how to use the wrapper in a 1MHz timer ISR scenario:

/* Global safe event group handle */
SafeEventGroup_t* xTimerEventGroup = NULL;
/* Timer ISR prototype */
void vTimer1ISR(void);
void main(void)
{
/* Initialize hardware */
prvSetupHardware();
/* Create safe event group for timer events */
xTimerEventGroup = xSafeEventGroupCreate();
configASSERT(xTimerEventGroup);
/* Start timer ISR (1MHz frequency) */
vSetupTimer1ISR(vTimer1ISR);
/* Start scheduler */
vTaskStartScheduler();
for(;;);
}
void vTimer1ISR(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* Set bit 0 in the safe event group from ISR */
xSafeEventGroupSetBits(xTimerEventGroup, 0x01);
/* Clear interrupt flag */
CLEAR_TIMER1_INTERRUPT_FLAG();
/* Yield if needed */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void vTimerTask(void* pvParameters)
{
const EventBits_t uxWaitBits = 0x01; // Wait for timer bit
const TickType_t xBlockTime = pdMS_TO_TICKS(10);
for(;;) {
/* Wait for timer event */
xSafeEventGroupWaitBits(xTimerEventGroup,
uxWaitBits,
pdTRUE, // Clear on exit
pdFALSE, // Don't wait for all bits
xBlockTime);
/* Process timer event */
vProcessTimerEvent();
}
}

Verification Steps: Testing Overflow Protection

Verify your implementation with these tests:

  1. Boundary Test: Set bits repeatedly until reaching 0xFFFFFF, then verify the wrapper clears before overflow.
  2. ISR Stress Test: Run a timer ISR at maximum frequency for several minutes and confirm no lost events.
  3. Race Condition Test: Use multiple tasks and ISRs setting bits simultaneously to verify critical section effectiveness.
  4. Value Monitoring: Add logging to confirm the event group value never exceeds SAFE_EVENT_GROUP_MAX.

Summary

FreeRTOS event group overflow is a latent reliability issue in high-frequency interrupt systems. By understanding the 24-bit counter limitation, implementing detection mechanisms, and using a thread-safe wrapper with periodic clearing, you can prevent lost events and ensure robust task-ISR communication. The provided wrapper implementation offers a drop-in replacement for standard event group operations with built-in overflow protection.

  • FreeRTOS Event Groups API Reference
  • Interrupt Safe Queue Operations in FreeRTOS
  • Critical Section Best Practices for ARM Cortex-M

References

  1. FreeRTOS Kernel Reference Manual, V10.5.1, https://github.com/FreeRTOS/FreeRTOS-Kernel/releases/tag/V10.5.1
  2. Richard Barry, “Using the FreeRTOS Real Time Kernel”, 2018.
  3. ARM Cortex-M4 Devices Generic User Guide, https://developer.arm.com/documentation/dui0553/a
  4. “Real-Time Concepts for Embedded Systems”, Qing Li, 2003.
  5. “Mastering the FreeRTOS Real Time Kernel”, Richard Barry, 2021.
  6. STMicroelectronics, “STM32F4xx Reference Manual”, RM0090, https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f425-435-advanced-armbased-32bit-mcus-stmicroelectronics.pdf

Frequently Asked Questions

What causes FreeRTOS event group overflow in ISR context?

FreeRTOS event groups use a 24-bit value to track event bits. When more than 2^24-1 events are set without clearing, the value overflows to zero, causing lost events and potential system malfunction.

How can I detect event group overflow in my FreeRTOS application?

Monitor the event group value after setting bits. If the value becomes unexpectedly low or zero after setting multiple bits, overflow has occurred. Implement a wrapper function that checks for overflow conditions before and after bit operations.

What is the recommended solution to prevent FreeRTOS event group overflow?

Use a combination of periodic clearing in the task context and atomic bit operations in ISRs. Implement a safe event group wrapper that uses critical sections for multi-bit operations and ensures the event group value never reaches its maximum before clearing.

Tags

freertosevent-groupoverflowisr

Share


Previous Article
Accelerating Embedded AI Inference with CMSIS-NN on Cortex-M4
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

Fixing FreeRTOS Software Timer Callback Overruns
Fixing FreeRTOS Software Timer Callback Overruns
August 26, 2026
7 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media