This article explains how to structure the software to develop an STM32Cube example.
1. Introduction
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 Arm® Cortex®-M4 (write/read access rights): ADC2, DAC1, TIM2, DMA2, RCC/PWR dedicated MCU registers
- Resources fully Managed by Arm® Cortex®-A7 (write/read access rights): ETZPC, VREFBUF, I2C4 for PMIC configuration and RCC/PWR common registers
- Shared resources managed by Arm® Cortex®-M4 and Arm® Cortex®-A7 with Hardware Semaphore: GPIOA and EXTI
- Shared resources managed by Arm® Cortex®-M4 and Arm® Cortex®-A7: IPCC and HSEM
- Resources managed by Arm® 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().
2. Recommended software structure
2.1. main(void)
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 */
}
2.2. HAL_MspInit(void)
Source code available in file stm32mp1xx_hal_msp.c
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 */
}
2.3. HAL_MspDeInit(void)
This function is not present for the ADC example.
In production mode, Arm® 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 Arm® 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 Arm® Cortex®-A to the Arm® Cortex®-M of an imminent stop via CoproSync. In this case, the function CoproSync_ShutdownCb is called, and HAL_MspDeInit executed.
Source code available in file stm32mp1xx_hal_msp.c
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 */
}
2.4. HAL_PPP_MspInit(PPP_HandleTypeDef *hppp)
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 */
}
}
Example of 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 */
}
}
2.5. HAL_PPP_MspDeInit(PPP_HandleTypeDef *hppp)
In production mode, as Arm® 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);
}
2.6. HAL_PPP_Init(PPP_HandleTypeDef *hppp)
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 */
}
2.7. PERIPH_LOCK(__Periph__)
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 Arm® 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)