Last edited 2 years ago

STM32CubeMP1 development guidelines

Applicable for STM32MP15x lines

1. Introduction[edit source]

To simplify the development of the STM32MP1 ecosystem coprocessor firmware, the Cortex®-M4 firmware can be developed in a stand-alone « MCU Single Core like » way called « Engineering mode». It removes the complexity of multiprocessor system development such as inter processor communication, system resources management...; and focuses on « real-time » application development.
An application can dynamically detect if the STM32MP1 is running in engineering or production mode through the SYSCFG_BOOTR register.

  • A macro IS_ENGINEERING_BOOT_MODE() is provided in CMSIS device file for that purpose.
  • Most of available projects support both engineering and production modes.


This article describes how to initialize the STM32CubeMP1 Cortex-M4 Software in Engineering mode and when ready move to Production mode.
Finally, detailed description of each step is given together with sample code.

Info white.png Information
Notes:

2. Cortex-M4 Startup Engineering mode (“MCU Single Core like”)[edit source]

The Engineering mode allows the user to connect a debugger on a open chip, load and debug the CORTEX-M4 firmware in an “MCU like world”. In this mode, STM32CubeMP1 initialization steps are similar to a single CPU MCU device.
There are some rules that need to be followed in order to develop in engineering mode:

  • Peripheral assignment request (ResMgr_Request) is a multicore instruction and can be integrated into the code when developing the engineering mode firmware.
ResMgr_Request is based on a ETZPC peripheral features. In engineering mode, ETZPC configuration makes all resources accessible to the Cortex-M4 and therefore ResMgr_Request will return True in any case.
  • OpenAMP_Init for RPMsg service (MX_OPENAMP_Init) is NOT executed in engineering mode as there are blocking step in RPMsg framework initialization code running on the Cortex-A7 (rproc_virtio_wait_remote_ready). The call to MX_OPENAMP_Init can masked by the macro call if (!IS_ENGINEERING_BOOT_MODE())


CortexM4StartuEngiMode.png

3. Cortex-M4 Startup in Production mode[edit source]

The Cortex-M4 STM32CubeMP1 firmware is started by running OpenSTLinux on Cortex-A7.
It can be started by a U-Boot with the remoteproc feature (rproc command) or, later, by Linux remoteproc framework, depending on the application startup time targets.
In order to move the Cortex-M4 application from engineering to production mode, the following 4 items need to be addressed:

  • System clock configuration (SystemClock_Config()) is "done" by running the FSBL on Cortex-A7. Beware, this function call shall NOT be executed in production mode. SystemClock_Config() shall be under if(IS_ENGINEERING_BOOT_MODE()).
  • Clock source of each peripheral is also done by running the FSBL on Cortex-A7 and the function call (HAL_RCCEx_PeriphCLKConfig()) shall be done under if(IS_ENGINEERING_BOOT_MODE()).
  • GPIO configuration PERIPH_LOCK service (hardware semaphore) can be used to protect the (HAL_GPIO_Init/HAL_GPIO_DeInit()).
If the GPIO bank is used by both Cortex-A7 and Cortex-M4, this GPIO bank is seen as a shared resource. Some GPIO registers are not designed to support concurrent core accesses and they need be protected by hardware semaphore.
  • EXTI configuration (HAL_EXTI_SetConfigLine()) for configurable events shall be protected by PERIPH_LOCK service (hardware semaphore).
EXTI is a shared resource and registers used for configurable events are not designed to support concurrent core accesses and they have to be protected by hardware semaphore.


CortexM4StartuProdMode.png

4. Develop a firmware on Cortex®-M4 with OpenSTLinux distribution on Cortex®-A7[edit source]

1. Create your project for your board
A project is board and application specific. To create a new project, you start either from
  • the Template project
  • or any available project from Examples or Applications
Info white.png Information
Note:
The Template project provides empty main loop function, it is also a good starting point to get familiar with the STM32CubeMP1 project settings. The template has the following characteristics:
  • It contains the source code of HAL, CMSIS and BSP drivers which are the minimum components required to develop a code for a given board.
  • It contains the include paths for all the firmware components.
  • It defines the STM32MP1 supported device, which defines the CMSIS and HAL driver configuration settings.
  • It provides pre-configured user files which are ready-to-use as shown below:
  • HAL initialized with default time base with ARM Core SysTick.
  • SysTick ISR implemented for delay management purpose (HAL_Delay()).
Info white.png Information
Note:
  • Make sure to update the include paths when copying an existing project to another location


2. Adding middleware to your project (optional)
Your project may need to support additional middleware. This support is available from these middleware stacks: OpenAMP and FreeRTOS.
Refer to the documentation to understand what which middleware source files are to be used.
If you have the experience, it is also possible to look in the application source code to find out which source files and which include paths must be added.


3. Configure the firmware components
The HAL and middleware components offer a set of build time configuration options using macros #define declared in a header file.
A template configuration file is provided for each component. It has to be copied to the project folder (usually the configuration file is named xxx_conf_template.h, and the word ‘_template’ needs to be removed when copying it to the project folder).
The configuration file provides enough information to understand the impact of each configuration option. More detailed information is available in the documentation provided with each component.


4. Start the HAL Library
After jumping to the main program, the application code must call the function HAL_Init() to initialize the HAL Library. It includes the following tasks:
a) Configuration of the SysTick to generate an interrupt every 1 millisecond with the SysTick interrupt priority TICK_INT_PRIO defined in the file stm32mp1xx_hal_conf.h.
  • In engineering mode, the SysTick clock is not configured and so the system is running from the internal 64 MHz HSI.
  • In production mode, the SysTick clock is already configured by FSBL running on Cortex-A7.
b) Set of NVIC Group Priority to 0.
c) Call to the HAL_MspInit() function defined in the stm32mp1xx_hal_msp.c user file performs global low-level hardware initializations like HSEM clock enable.


5. Configure the system clock (Engineering mode only)
The system clock configuration is done by calling the two APIs described below:
a) HAL_RCC_OscConfig(): this API configures the internal and/or external oscillators, as well as the PLL source and factors. The user chooses to configure either one oscillator or all oscillators. The PLL configuration can be skipped if there is no need to run the system at high frequency.
b) HAL_RCC_ClockConfig(): this API configures the system clock source and bus prescalers


6. Initialize the peripheral
a) Edit the file stm32mp1xx_hal_msp.c to write to the peripheral HAL_PPP_MspInit() function.
Proceed as follows:
  • Enable the peripheral clock.
  • Configure the peripheral GPIOs.
  • Configure the DMA channel and enable DMA interrupt (if needed).
  • Enable peripheral interrupt (if needed).
b) Edit the stm32xxx_it.c file to call on the required interrupt handlers (peripheral and DMA), if needed.
c) Write process complete callback functions if you plan to use peripheral interrupt or DMA.
d) In the main.c file, initialize the peripheral handler structure then call the function HAL_PPP_Init() to initialize your peripheral.


7. Develop your application
At this stage, your system is ready and you can start developing your application code.
  • The HAL provides intuitive and ready-to-use APIs to configure the peripheral. It supports polling, interrupts and a DMA programming model, to accommodate any application requirement.
  • For more details on how to use each peripheral and how to manage real-time constraints, refer to the full set of examples and applications provided in the STM32CubeMP1 Package (List of availables projects)


Info white.png Information
Caution:
  • In the default HAL implementation, SysTick timer is used as timebase: it generates interrupts at regular time intervals. If HAL_Delay() is called from the peripheral ISR process, make sure that the SysTick interrupt has higher priority (numerically lower) than the peripheral interrupt. Otherwise, the ISR caller process will be blocked. Functions affecting timebase configurations are declared as __weak to make it possible to override in case of other user implementations (using a general purpose timer for example or other time source).

5. Cortex-M4 User Sample Code[edit source]

The following code samples are extracted from the ADC_SingleConversion_TriggerTimer_DMA example and adapted for this article. ADC is used to convert a single channel at each timer trigger, then conversion data is transferred by DMA into an array, then a waveform is generate indefinitely (circular mode).

This example deals with the following resources and access attribute for each CPU in when used in production mode:

  • Resources fully managed by Cortex-M4 (write/read access rights): ADC2, DAC1, TIM2, DMA2, RCC/PWR dedicated MCU registers
  • Resources fully Managed by Cortex-A7 (write/read access rights): ETZPC, VREFBUF, I2C4 for PMIC configuration and RCC/PWR common registers
  • Shared resources managed by Cortex-M4 and Cortex-A7 with Hardware Semaphore: GPIOA and EXTI
  • Shared resources managed by Cortex-M4 and Cortex-A7: IPCC and HSEM
  • Resources managed by Cortex-M4 with read access only: ETZPC, VREFBUF and RCC/PWR common registers (to calculate frequency for example)
  • In Engineering Mode : To improve successive debug session, it is recommended to add HAL_RCC_DeInit() before SystemClock_Config().


5.1. main(void)[edit source]

  • Source code available in file main.c
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */

  /* Initialize the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  if(IS_ENGINEERING_BOOT_MODE())
  {
    HAL_RCC_DeInit();
    /* Configure the system clock */
    SystemClock_Config();
  }
  /* USER CODE END Init */

  /* IPCC initialisation (used by OpenAMP)*/  
   MX_IPCC_Init();

  /* OpenAmp initialisation ---------------------------------*/
  if(!IS_ENGINEERING_BOOT_MODE())
  {
    MX_OPENAMP_Init(RPMSG_REMOTE, NULL);
  }

  /* Resource Manager Utility initialisation ---------------------------------*/
  MX_RESMGR_UTILITY_Init();

  /* USER CODE BEGIN SysInit */
  if(IS_ENGINEERING_BOOT_MODE())
  {
    /* Configure PMIC */
    BSP_PMIC_Init();
    BSP_PMIC_InitRegulators();

    /* Configure VREFBUF */
    __HAL_RCC_VREF_CLK_ENABLE();
    HAL_SYSCFG_VREFBUF_HighImpedanceConfig(SYSCFG_VREFBUF_HIGH_IMPEDANCE_DISABLE);
    HAL_SYSCFG_EnableVREFBUF();
  }
  /* USER CODE END SysInit */

/* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_DAC1_Init();
  MX_TIM2_Init();
  MX_ADC2_Init();

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
  /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

5.2. HAL_MspInit(void)[edit source]

void HAL_MspInit(void)
{
  /* USER CODE BEGIN MspInit 0 */

  /* USER CODE END MspInit 0 */

  /*HW semaphore Clock enable*/
  __HAL_RCC_HSEM_CLK_ENABLE();

  /* USER CODE BEGIN MspInit 1 */

  /* USER CODE END MspInit 1 */
}

5.3. HAL_MspDeInit(void)[edit source]

  • This function is not present for the ADC example.
    In production mode, Cortex-M4 firmware could be restarted without platform system reset and in that case, it is necessary to de-initialize the peripherals, clocks or interrupts used by Cortex-M4 firmware before stopping the example. For example , FreeRTOS_ThreadCreation example where the timer (TIM2) must be stopped in order to be able to restart the example.
    This function of HAL_MspDeInit can be called following a stop notification from the Cortex-A to the Cortex-M of an imminent stop via CoproSync. In this case, the function CoproSync_ShutdownCb is called, and HAL_MspDeInit executed.
void HAL_MspDeInit(void)
{
  /* USER CODE BEGIN MspDeInit 0 */

  /* USER CODE END MspDeInit 0 */

  /* Disable IRQ */
  HAL_NVIC_DisableIRQ(TIM2_IRQn);

  /* Disable SYSCFG clock */
  __HAL_RCC_SYSCFG_CLK_DISABLE();

  /* Disable TIM2 clock */
  __HAL_RCC_TIM2_CLK_DISABLE();

  /* USER CODE BEGIN MspDeInit 1 */

  /* USER CODE END MspDeInit 1 */
}

5.4. HAL_PPP_MspInit(PPP_HandleTypeDef *hppp)[edit source]

  • Source code available in file stm32mp1xx_hal_msp.c
  • Only HAL_ADC_MspInit() and HAL_DAC_MspInit() are shown to cover all cases (Clock source, GPIO, DMA, NVIC)
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
  RCC_PeriphCLKInitTypeDef  PeriphClkInit;
  if(hadc->Instance==ADC2)
  {
  /* USER CODE BEGIN ADC2_MspInit 0 */
  if(IS_ENGINEERING_BOOT_MODE())
  {
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PER;
    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
  }
  /* USER CODE END ADC2_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_ADC12_CLK_ENABLE();
  
    /* ADC2 DMA Init */
    /* ADC2 Init */
    hdma_adc2.Instance = DMA2_Stream0;
    hdma_adc2.Init.Request = DMA_REQUEST_ADC2;
    hdma_adc2.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc2.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc2.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc2.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc2.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc2.Init.Mode = DMA_CIRCULAR;
    hdma_adc2.Init.Priority = DMA_PRIORITY_LOW;
    hdma_adc2.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_adc2) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc2);

    /* ADC2 interrupt Init */
    HAL_NVIC_SetPriority(ADC2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(ADC2_IRQn);
  /* USER CODE BEGIN ADC2_MspInit 1 */

  /* USER CODE END ADC2_MspInit 1 */
  }

}
  • PERIPH_LOCK/PERIPH_UNLOCK usage for shared GPIOA resource below
void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  if(hdac->Instance==DAC1)
  {
  /* USER CODE BEGIN DAC1_MspInit 0 */

  /* USER CODE END DAC1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_DAC12_CLK_ENABLE();
	
    /**DAC1 GPIO Configuration    
    PA4     ------> DAC1_OUT1 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    PERIPH_LOCK(GPIOA);
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    PERIPH_UNLOCK(GPIOA);

  /* USER CODE BEGIN DAC1_MspInit 1 */

  /* USER CODE END DAC1_MspInit 1 */
  }

}

5.5. HAL_PPP_MspDeInit(PPP_HandleTypeDef *hppp)[edit source]

  • In production mode, as Cortex-M4 firmware can be restarted without platform system reset, It is recommended to de-initialize the peripheral. This is the case, for the ADC example : reset the peripheral, disable the GPIO Clock, disable the dma and the NVIC configuration.
    This function of HAL_ADC_MspDeInit can be called before the HAL_ADC_MspInit to secure a clean state before the initialization.
  • Source code available in file stm32mp1xx_hal_msp.c
  • Only HAL_ADC_MspDeInit() is shown to cover all cases (Clock source, GPIO, DMA, NVIC)
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* hadc)
{
  /*##-1- Reset peripherals ##################################################*/
  __HAL_RCC_ADC12_FORCE_RESET()
  __HAL_RCC_ADC12_RELEASE_RESET()

  /*##-2- Disable peripherals and GPIO Clocks ################################*/
  /* De-initialize GPIO pin of the selected ADC channel */
  PERIPH_LOCK(GPIOA);
  HAL_GPIO_DeInit(GPIOA, GPIO_PIN_4);
  PERIPH_UNLOCK(GPIOA);

  /*##-3- Disable the DMA ####################################################*/
  /* De-Initialize the DMA associated to the peripheral */
  if(hadc->DMA_Handle != NULL)
  {
    HAL_DMA_DeInit(hadc->DMA_Handle);
  }

  /*##-4- Disable the NVIC ###################################################*/
  /* Disable the NVIC configuration for ADC interrupt */
  HAL_NVIC_DisableIRQ(ADC2_IRQn);
  
  /* Disable the NVIC configuration for DMA interrupt */
  HAL_NVIC_DisableIRQ(DMA2_Stream1_IRQn);

}

5.6. HAL_PPP_Init(PPP_HandleTypeDef *hppp)[edit source]

  • Source code available in file main.c
static void MX_ADC2_Init(void)
{

  if (ResMgr_Request(RESMGR_ID_ADC2, RESMGR_FLAGS_ACCESS_NORMAL | \
                   RESMGR_FLAGS_CPU_SLAVE , 0, NULL) != RESMGR_OK)
  {
    /* USER CODE BEGIN RESMGR_UTILITY_ADC2 */
    Error_Handler();
    /* USER CODE END RESMGR_UTILITY_ADC2 */
  }
  /* USER CODE BEGIN ADC2_Init 0 */

  /* USER CODE END ADC2_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC2_Init 1 */

  /* USER CODE END ADC2_Init 1 */
  /** Common config 
  */
  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc2.Init.Resolution            = ADC_RESOLUTION_12B;
  hadc2.Init.ScanConvMode          = ADC_SCAN_DISABLE;
  hadc2.Init.EOCSelection          = ADC_EOC_SINGLE_CONV;
  hadc2.Init.LowPowerAutoWait      = DISABLE;
  hadc2.Init.ContinuousConvMode    = DISABLE;
  hadc2.Init.NbrOfConversion       = 1;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.NbrOfDiscConversion   = 1;
  hadc2.Init.ExternalTrigConv      = ADC_EXTERNALTRIG_T2_TRGO;
  hadc2.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc2.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
  hadc2.Init.Overrun               = ADC_OVR_DATA_OVERWRITTEN;
  hadc2.Init.OversamplingMode      = DISABLE;
  if (HAL_ADC_DeInit(&hadc2) != HAL_OK)                    {{Red|<--- will call HAL_ADC_MSPDeInit}}
  {
    /* ADC Deinitialization error */
    Error_Handler();
  }
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    /* ADC initialization error */
    Error_Handler();
  }

  /** Configure Regular Channel 
  */
  sConfig.Channel      = ADC_CHANNEL_16;    /* ADC channel selection */
  sConfig.Rank         = ADC_REGULAR_RANK_1;    /* ADC group regular rank in which is mapped the selected ADC channel */
  sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;    /* ADC channel sampling time */
  sConfig.SingleDiff   = ADC_SINGLE_ENDED;  /* ADC channel differential mode */
  sConfig.OffsetNumber = ADC_OFFSET_NONE;    /* ADC channel affected to offset number */
  sConfig.Offset       = 0;     /* Parameter discarded because offset correction is disabled */

  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC2_Init 2 */

  /* USER CODE END ADC2_Init 2 */

}

5.7. PERIPH_LOCK(__Periph__)[edit source]

PERIPH_LOCK/PERIPH_UNLOCK service is a mandatory user code service in production mode
This service is used and available in STM32CubeMP1 examples (src/inc directories) but it is not part of a dedicated component (HAL, Utilities, Middleware, CMSIS Device).

  • in the file lock_resource.h , the macro PERIPH_LOCK is exported for user code usage and mapped to an user function
#define PERIPH_LOCK(__Periph__)       Periph_Lock(__Periph__, LOCK_RESOURCE_TIMEOUT)
  • in the file lock_resource.c , the source code of Periph_Lock function using HSEM HAL
LockResource_Status_t Periph_Lock(void* Peripheral, uint32_t Timeout)
{
  uint32_t tickstart = 0U;
  LockResource_Status_t ret = LOCK_RESOURCE_STATUS_OK;

  /* Init tickstart for timeout management*/
  tickstart = HAL_GetTick();

  /* Try to Take HSEM  assigned to the Peripheral */
  while (HAL_HSEM_FastTake(GET_HSEM_SEM_INDEX(Peripheral)) != HAL_OK)
  {

    if ((Timeout == 0U) || ((HAL_GetTick() - tickstart) > Timeout))
    {
       ret = LOCK_RESOURCE_STATUS_TIMEOUT;
       Error_Handler();
    }
  }

  return ret;
}
  • Hardware semaphore index versus peripheral shall be aligned with software running on Cortex-A
  • Below, the configuration done in the file lock_resource.c to work with OpenSTLinux
#define GET_HSEM_SEM_INDEX(__Peripheral__)   (uint8_t)(((GPIO_TypeDef *)(__Peripheral__) == (GPIOA))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOB))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOC))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOD))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOE))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOF))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOG))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOH))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOI))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOJ))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOK))? 0U :\
                                              ((GPIO_TypeDef *)(__Peripheral__) == (GPIOZ))? 0U :\
                                              ((EXTI_TypeDef *)(__Peripheral__) == (EXTI))?  1U : HSEM_SEMID_MAX + 1U)