Skip to main content
Associate II
August 11, 2026
Solved

STM32N6 Hard Fault from FreeRTOS

  • August 11, 2026
  • 5 replies
  • 118 views

I am attempting to setup a new Hello World project on the STM32N6 using the following:

  1.  FreeRTOS running two tasks, a timer, and a semaphore
  2. A couple of hardware timers
  3. A USART
  4. The PSSI doing 16 bit bi-directional transfers
  5. The BSP package

Running the debugger I get

followed by a Hard Fault:

The code is running on a Nucleo-N657XO-Q development board.  My development tools are:

  1.  STMCube 6.18.1
  2. STM32Cube_FW_N6_V1.4.0
  3. IAR 9.60.4

Something not correctly setup or a bad board?

Best answer by Neuraleanus

This problem has been resolved.  It turns out there is a bug in the freeRTOS port for the STM32N6.

This is what has been done to fix this:

  1.  In main.c:

/* USER CODE BEGIN 4 */
/* ==========================================================================
 * HAL time base - overrides of the __weak HAL implementations.
 *
 * WHY THIS EXISTS
 * ---------------
 * On this port (FreeRTOS ARM_CM55_NTZ) the kernel defines SysTick_Handler
 * itself, as a strong symbol, in port.c. That handler calls
 * xTaskIncrementTick() unconditionally - it has no "is the scheduler
 * running yet?" guard, because the kernel assumes it owns SysTick and only
 * starts it from vPortSetupTimerInterrupt() inside xPortStartScheduler().
 *
 * The stock HAL_InitTick(), however, starts SysTick AND enables its
 * interrupt from HAL_Init() - i.e. right at the top of main(), long before
 * osKernelInitialize(). The first tick then lands in port.c's
 * SysTick_Handler while the kernel's task lists are still all-zero BSS, so
 * xTaskIncrementTick() dereferences pxDelayedTaskList == NULL and the core
 * takes a HardFault somewhere in the middle of the MX_*_Init() calls.
 *
 * Note that cmsis_os2.c does contain a properly guarded SysTick_Handler,
 * but it is compiled out because USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION
 * is 1 - and it has to be, since it would otherwise clash with port.c's
 * handler and reference xPortSysTickHandler(), which this port does not
 * provide. So the guard cannot come from there.
 *
 * WHAT THIS DOES
 * --------------
 * Leaves SysTick's interrupt disabled until the kernel arms it:
 *
 *   before osKernelStart()  SysTick counts, no IRQ. HAL_GetTick() advances
 *                           uwTick by polling the COUNTFLAG bit, which
 *                           latches once per 1 ms wrap.
 *   after  osKernelStart()  vPortSetupTimerInterrupt() reprograms SysTick
 *                           (LOAD/VAL/CTRL) and enables the IRQ, so the
 *                           kernel drives the tick. vApplicationTickHook()
 *                           in app_freertos.c keeps uwTick advancing.
 *
 * uwTick is the single source of truth throughout, so HAL_GetTick() stays
 * monotonic across the handover - no backward jump for any HAL timeout that
 * happens to straddle osKernelStart().
 *
 * A cleaner alternative, if you would rather not carry this code: in CubeMX
 * set SYS -> Timebase Source to a hardware timer instead of SysTick. CubeMX
 * then generates stm32n6xx_hal_timebase_tim.c, adds stm32n6xx_hal_tim.c to
 * the project, and SysTick belongs to FreeRTOS alone. Delete this block if
 * you go that route.
 * ========================================================================== */

/**
  * @brief  Configure the HAL time base without taking the SysTick interrupt.
  * @param  TickPriority Requested tick interrupt priority (recorded only).
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  uint32_t ulReload;

  if ((uint32_t)uwTickFreq == 0UL)
  {
    return HAL_ERROR;
  }

  if (TickPriority >= (1UL << __NVIC_PRIO_BITS))
  {
    return HAL_ERROR;
  }

  ulReload = (SystemCoreClock / (1000UL / (uint32_t)uwTickFreq)) - 1UL;

  /* HAL_RCC_ClockConfig() calls back into HAL_InitTick() after it updates
     SystemCoreClock, which is how the reload value tracks the clock change.
     If that happens once the scheduler is up, only the reload may be touched:
     rewriting CTRL would clear TICKINT and stop the RTOS tick dead. */
  if ((SysTick->CTRL & SysTick_CTRL_TICKINT_Msk) != 0UL)
  {
    SysTick->LOAD = ulReload;
  }
  else
  {
    /* Free-running 1 ms reload, processor clock, interrupt DELIBERATELY off.
       FreeRTOS re-programs all three registers in vPortSetupTimerInterrupt()
       when the scheduler starts. */
    SysTick->LOAD = ulReload;
    SysTick->VAL  = 0UL;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
  }

  uwTickPrio = TickPriority;

  return HAL_OK;
}

/**
  * @brief  Return the current HAL tick in ms.
  * @note   Pre-scheduler this polls SysTick's COUNTFLAG, which is set on each
  *         wrap and cleared by the read. HAL only ever calls HAL_GetTick()
  *         from tight polling loops (HAL_Delay and the various
  *         "while (!ready) { if (HAL_GetTick() - start > TIMEOUT) }" loops),
  *         so wraps are not missed in practice. If one ever were, uwTick
  *         would lag and a timeout would take longer than nominal - it can
  *         never expire early, so the failure mode is safe.
  * @retval Tick count in ms.
  */
uint32_t HAL_GetTick(void)
{
  /* ONE read only - reading CTRL clears COUNTFLAG, so testing the two bits
     with two separate reads would throw the wrap away. TICKINT tells us
     whether the kernel has taken SysTick over yet, which keeps this function
     free of any FreeRTOS dependency and safe to call from an ISR. */
  uint32_t ulCtrl = SysTick->CTRL;

  if (((ulCtrl & SysTick_CTRL_TICKINT_Msk) == 0UL) &&
      ((ulCtrl & SysTick_CTRL_COUNTFLAG_Msk) != 0UL))
  {
    uwTick++;
  }

  return uwTick;
}

/**
  * @brief  No-op. Suspending the tick would disable the kernel's SysTick
  *         interrupt once the scheduler owns it.
  */
void HAL_SuspendTick(void)
{
}

/**
  * @brief  No-op counterpart to HAL_SuspendTick().
  */
void HAL_ResumeTick(void)
{
}
 

  1.  In app_freertos.c:

/**
  * @brief  Called from the kernel's SysTick_Handler on every tick.
  *         Requires configUSE_TICK_HOOK == 1.
  *
  *         This is what keeps the HAL time base alive once the scheduler is
  *         running: port.c owns SysTick_Handler on this port and never calls
  *         HAL_IncTick(), so uwTick would otherwise freeze at osKernelStart()
  *         and every HAL_Delay() / HAL timeout would block forever.
  *         See the HAL_InitTick() / HAL_GetTick() overrides in main.c.
  *
  * @note   Runs in interrupt context at the lowest priority. Keep it short and
  *         use only "FromISR" API here.
  */
void vApplicationTickHook(void)
{
  HAL_IncTick();
}
 

 

5 replies

Andrew Neil
Super User
August 11, 2026

IAR 9.60.4

You’d be better asking IAR for support with their IDE, and what to do about that particular IAR message.

Did you try Tools > Options, as it suggests ?

 

General tips on debugging Cortex-M Hard Faults.

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
Associate II
August 11, 2026

I have been doing the code construction in stages, prior to adding the timers, both soft and hard, the following code worked:

Perhaps this is indication of a memory access issue?

I see nothing obvious in tools-->options.

 

Andrew Neil
Super User
August 11, 2026

I see nothing obvious in tools-->options.

 

Again, that’s an IAR thing: best to ask them about that - it is their product, and you’ve paid for support as part of your licence.

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
ST Technical Moderator
August 12, 2026

Hi ​@Neuraleanus 

I assume ​@Andrew Neil is pointing to this interesting article How to debug a HardFault on an Arm® Cortex®-M STM32 | Community

From the information provided, this does not look like a toolchain issue. I would recommend to comment out all printf() calls first to narrow down the issue stack / lib c / UART problem vs RTOS / peripheral / memory issue

BFAR 0xC usually means NULL pointer + offset.

The value of PC just before the exception has occurred shown in the debug window (can be found in the stack (Exception frame) points to the faulty instruction. 

To give better visibility on the answered topics, please click on "Best answer" on the reply which solved your issue or answered your question.Best regards,FBL
NeuraleanusAuthorBest answer
Associate II
August 21, 2026

This problem has been resolved.  It turns out there is a bug in the freeRTOS port for the STM32N6.

This is what has been done to fix this:

  1.  In main.c:

/* USER CODE BEGIN 4 */
/* ==========================================================================
 * HAL time base - overrides of the __weak HAL implementations.
 *
 * WHY THIS EXISTS
 * ---------------
 * On this port (FreeRTOS ARM_CM55_NTZ) the kernel defines SysTick_Handler
 * itself, as a strong symbol, in port.c. That handler calls
 * xTaskIncrementTick() unconditionally - it has no "is the scheduler
 * running yet?" guard, because the kernel assumes it owns SysTick and only
 * starts it from vPortSetupTimerInterrupt() inside xPortStartScheduler().
 *
 * The stock HAL_InitTick(), however, starts SysTick AND enables its
 * interrupt from HAL_Init() - i.e. right at the top of main(), long before
 * osKernelInitialize(). The first tick then lands in port.c's
 * SysTick_Handler while the kernel's task lists are still all-zero BSS, so
 * xTaskIncrementTick() dereferences pxDelayedTaskList == NULL and the core
 * takes a HardFault somewhere in the middle of the MX_*_Init() calls.
 *
 * Note that cmsis_os2.c does contain a properly guarded SysTick_Handler,
 * but it is compiled out because USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION
 * is 1 - and it has to be, since it would otherwise clash with port.c's
 * handler and reference xPortSysTickHandler(), which this port does not
 * provide. So the guard cannot come from there.
 *
 * WHAT THIS DOES
 * --------------
 * Leaves SysTick's interrupt disabled until the kernel arms it:
 *
 *   before osKernelStart()  SysTick counts, no IRQ. HAL_GetTick() advances
 *                           uwTick by polling the COUNTFLAG bit, which
 *                           latches once per 1 ms wrap.
 *   after  osKernelStart()  vPortSetupTimerInterrupt() reprograms SysTick
 *                           (LOAD/VAL/CTRL) and enables the IRQ, so the
 *                           kernel drives the tick. vApplicationTickHook()
 *                           in app_freertos.c keeps uwTick advancing.
 *
 * uwTick is the single source of truth throughout, so HAL_GetTick() stays
 * monotonic across the handover - no backward jump for any HAL timeout that
 * happens to straddle osKernelStart().
 *
 * A cleaner alternative, if you would rather not carry this code: in CubeMX
 * set SYS -> Timebase Source to a hardware timer instead of SysTick. CubeMX
 * then generates stm32n6xx_hal_timebase_tim.c, adds stm32n6xx_hal_tim.c to
 * the project, and SysTick belongs to FreeRTOS alone. Delete this block if
 * you go that route.
 * ========================================================================== */

/**
  * @brief  Configure the HAL time base without taking the SysTick interrupt.
  * @param  TickPriority Requested tick interrupt priority (recorded only).
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
  uint32_t ulReload;

  if ((uint32_t)uwTickFreq == 0UL)
  {
    return HAL_ERROR;
  }

  if (TickPriority >= (1UL << __NVIC_PRIO_BITS))
  {
    return HAL_ERROR;
  }

  ulReload = (SystemCoreClock / (1000UL / (uint32_t)uwTickFreq)) - 1UL;

  /* HAL_RCC_ClockConfig() calls back into HAL_InitTick() after it updates
     SystemCoreClock, which is how the reload value tracks the clock change.
     If that happens once the scheduler is up, only the reload may be touched:
     rewriting CTRL would clear TICKINT and stop the RTOS tick dead. */
  if ((SysTick->CTRL & SysTick_CTRL_TICKINT_Msk) != 0UL)
  {
    SysTick->LOAD = ulReload;
  }
  else
  {
    /* Free-running 1 ms reload, processor clock, interrupt DELIBERATELY off.
       FreeRTOS re-programs all three registers in vPortSetupTimerInterrupt()
       when the scheduler starts. */
    SysTick->LOAD = ulReload;
    SysTick->VAL  = 0UL;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
  }

  uwTickPrio = TickPriority;

  return HAL_OK;
}

/**
  * @brief  Return the current HAL tick in ms.
  * @note   Pre-scheduler this polls SysTick's COUNTFLAG, which is set on each
  *         wrap and cleared by the read. HAL only ever calls HAL_GetTick()
  *         from tight polling loops (HAL_Delay and the various
  *         "while (!ready) { if (HAL_GetTick() - start > TIMEOUT) }" loops),
  *         so wraps are not missed in practice. If one ever were, uwTick
  *         would lag and a timeout would take longer than nominal - it can
  *         never expire early, so the failure mode is safe.
  * @retval Tick count in ms.
  */
uint32_t HAL_GetTick(void)
{
  /* ONE read only - reading CTRL clears COUNTFLAG, so testing the two bits
     with two separate reads would throw the wrap away. TICKINT tells us
     whether the kernel has taken SysTick over yet, which keeps this function
     free of any FreeRTOS dependency and safe to call from an ISR. */
  uint32_t ulCtrl = SysTick->CTRL;

  if (((ulCtrl & SysTick_CTRL_TICKINT_Msk) == 0UL) &&
      ((ulCtrl & SysTick_CTRL_COUNTFLAG_Msk) != 0UL))
  {
    uwTick++;
  }

  return uwTick;
}

/**
  * @brief  No-op. Suspending the tick would disable the kernel's SysTick
  *         interrupt once the scheduler owns it.
  */
void HAL_SuspendTick(void)
{
}

/**
  * @brief  No-op counterpart to HAL_SuspendTick().
  */
void HAL_ResumeTick(void)
{
}
 

  1.  In app_freertos.c:

/**
  * @brief  Called from the kernel's SysTick_Handler on every tick.
  *         Requires configUSE_TICK_HOOK == 1.
  *
  *         This is what keeps the HAL time base alive once the scheduler is
  *         running: port.c owns SysTick_Handler on this port and never calls
  *         HAL_IncTick(), so uwTick would otherwise freeze at osKernelStart()
  *         and every HAL_Delay() / HAL timeout would block forever.
  *         See the HAL_InitTick() / HAL_GetTick() overrides in main.c.
  *
  * @note   Runs in interrupt context at the lowest priority. Keep it short and
  *         use only "FromISR" API here.
  */
void vApplicationTickHook(void)
{
  HAL_IncTick();
}