Skip to main content

Development boards

Board-specific settings that are not derivable from the part number. Panel resolutions and TouchGFX configuration are on the Display and TouchGFX page.

External loaders

Boards with external QSPI or Octo-SPI flash need an external loader before STM32CubeProgrammer or the debugger can write to it. Without one, downloads to addresses at or above 0x90000000 silently do nothing, and the application appears to flash successfully but has no assets.

BoardExternal loader
STM32F746G-DISCOW25Q128JVEIQ_STM32F746G-DISCO.stldr
STM32F769I-DISCOMX25L512G_STM32F769I-DISCO.stldr
STM32H7B3I-EVALMX25LM51245G_STM32H7B3I-EVAL-REVB.stldr
STM32H7B3I-DKMX25LM51245G_STM32H7B3I-DISCO.stldr

The -EVAL and -DK variants of the same part carry different loader files — STM32H7B3I-EVAL-REVB versus STM32H7B3I-DISCO above — because the flash is wired to different pins on each board. Picking the loader by part number rather than by board is a common reason a download appears to succeed but leaves the external flash blank. This bites ST demos that run from external flash, such as ClockAndWeather on the STM32H7B3I-DK.

In STM32CubeIDE the loader is set under Debug Configurations ▸ Debugger ▸ External Loaders. Leave Initialize unchecked unless the flash needs setting up before the first access — enabling it on a board that does not need it can hang the download.

STM32F746G-DISCO

Project creation from Board Selector ▸ STM32F746G-DISCO:

Project Name: new-project
Targeted Language: C++
Initialize all peripherals with their default Mode: Yes
Enable Memory Protection Unit (MPU): true

FREERTOS:
Interface: CMSIS_V1
Advanced settings:
USE_NEWLIB_REENTRANT: Enabled

Pinout:
- Search: PI1 # LD1, the user LED
Define: GPIO_Output

USE_NEWLIB_REENTRANT matters as soon as more than one thread calls into the C library — printf, malloc, strtok. Without it they share a single reentrancy structure and corrupt each other's state, producing failures far from the call site.

Blink, from the default FreeRTOS task:

void StartDefaultTask(void const * argument)
{
/* init code for USB_HOST */
MX_USB_HOST_Init();
/* USER CODE BEGIN 5 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_1, GPIO_PIN_SET);
osDelay(500);
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_1, GPIO_PIN_RESET);
osDelay(500);
}
/* USER CODE END 5 */
}

Use osDelay rather than HAL_Delay inside a task. HAL_Delay busy-waits and never yields, so it starves every lower-priority thread for the full duration.

STM32F769I-DISCO

  • Flashing takes several minutes. This is the external loader programming QSPI flash, not a hang.
  • Panel depends on the board revision — see Panel variants.
Board MB1166 revision A-09 corresponds to:
LCD FRIDA FRD397B25009-D-CTK replaced by FRIDA FRD400B25025-A-CTK

STM32H7B3I-EVAL

Hardware:
LCD: ROCKTEK RK070ER9427-CT # 800 x 480

Board Selector: STM32H7B3I-EVAL

FREERTOS:
Interface: CMSIS_V1
Advanced settings:
USE_NEWLIB_REENTRANT: Enabled

SYS:
Timebase Source: TIM7 # keep SysTick for the RTOS

Computing:
CRC: Activated # required by TouchGFX

Debug Configurations:
External Loader:
Loader: MX25LM51245G_STM32H7B3I-EVAL-REVB.stldr
Enabled: true
Initialize: false

Open TouchGFX/ApplicationTemplate.part and generate code from it.

SD card initialisation on this board has been unreliable across MCU Package versions. If HAL_SD_Init fails or returns intermittently, check the release notes for your package version before debugging the driver.

Project templates

CubeMX offers three starting points, and the difference matters more than it first appears.

MCU-based versus board-based

A board-based project pre-fills everything the schematic determines. A MCU-based project starts empty and you supply it all.

Core/Inc/main.h gains the board's pin names:

/* Private defines -----------------------------------------------------------*/
#define LCD_B0_Pin GPIO_PIN_4
#define LCD_B0_GPIO_Port GPIOE
// ...

Core/Inc/stm32f7xx_hal_conf.h sets the tick priority:

#define TICK_INT_PRIORITY ((uint32_t)0U) /*!< tick interrupt priority */

Priority 0 is the highest on Cortex-M. The HAL tick needs it so that HAL_Delay and HAL timeouts still advance when called from another interrupt handler. Under FreeRTOS this is the one interrupt that is allowed above configMAX_SYSCALL_INTERRUPT_PRIORITY, precisely because it never calls kernel APIs — see Interrupts.

Core/Src/main.c gets a clock configuration matching the fitted crystal:

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);

/* Initialize all configured peripherals */
MX_GPIO_Init();

void SystemClock_Config(void)
{
// ...
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = 10;
RCC_OscInitStruct.PLL.PLLN = 210;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 2;

The project file *.ioc records which board it came from:

# ...
board=STM32F746G-DISCO
boardIOC=true
# ...

This is the CubeMX project file — extension .ioc, not .ico. Keep it in version control; it is the source of truth for everything CubeMX regenerates.

Initialising all peripherals

Answering yes to Initialize all peripherals with their default Mode adds every peripheral the board exposes. main.c then holds the full list:

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void PeriphCommonClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC3_Init(void);
static void MX_CRC_Init(void);
static void MX_DCMI_Init(void);
static void MX_DMA2D_Init(void);
static void MX_ETH_Init(void);
static void MX_FMC_Init(void);
static void MX_I2C1_Init(void);
static void MX_I2C3_Init(void);
static void MX_LTDC_Init(void);
static void MX_QUADSPI_Init(void);
static void MX_RTC_Init(void);
static void MX_SAI2_Init(void);
static void MX_SDMMC1_SD_Init(void);
static void MX_SPDIFRX_Init(void);
static void MX_SPI2_Init(void);
static void MX_TIM1_Init(void);
/* ... TIM2, TIM3, TIM5, TIM8, TIM12 ... */
static void MX_USART1_UART_Init(void);
static void MX_USART6_UART_Init(void);
void StartDefaultTask(void const * argument);

and the corresponding calls, followed by the RTOS start-up:

/* Configure the peripherals common clocks */
PeriphCommonClock_Config();

MX_ADC3_Init();
MX_CRC_Init();
/* ... */
MX_FATFS_Init();

/* Create the thread(s) */
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 4096);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

/* Start scheduler */
osKernelStart();

/* We should never get here as control is now taken by the scheduler */

Additional files appear alongside:

PathAdded for
Core/Inc/FreeRTOSConfig.hRTOS configuration
Core/Src/freertos.cTask definitions and hooks
Core/Src/stm32f7xx_hal_timebase_tim.cHAL tick moved off SysTick
Core/Src/stm32f7xx_hal_msp.cPer-peripheral MSP init
Core/Src/stm32f7xx_it.cInterrupt handlers
FATFS/App, FATFS/TargetFile system
Middlewares/, USB_HOST/Middleware stacks

stm32f7xx_it.c declares the handles the handlers dispatch to:

/* External variables --------------------------------------------------------*/
extern HCD_HandleTypeDef hhcd_USB_OTG_FS;
extern DMA2D_HandleTypeDef hdma2d;
extern LTDC_HandleTypeDef hltdc;
extern TIM_HandleTypeDef htim6;

main.h also picks up the post-init callback for peripherals whose GPIO setup must run after the peripheral itself is configured:

void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);

Initialising everything is convenient for exploring a board and wasteful for a product: each peripheral costs flash, RAM and start-up time, and every unused one is a pin you cannot repurpose without regenerating. Start narrow for real work.