I'm new to STM32 microcontrollers. I would like to know how to correctly implement 2-clock-cycle delay after enabling RCC peripheral clock.
Section 5.2.16 (page# 134) of https://www.st.com/resource/en/reference_manual/rm0454-stm32g0x0-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
The enable bit has a synchronization mechanism to create a glitch-free clock for the peripheral. After the enable bit is set, there is a 2-clock-cycle delay before the clock be active, which the software must take into account.
I came across the below explanation and it appears to be correct.
This delay depends on the peripheral mapping. If peripheral is mapped on AHB: the delay is 2 AHB clock cycles after the clock enable bit is set on the hardware register If peripheral is mapped on APB: the delay is 2 APB clock cycles after the clock enable bit is set on the hardware register
I was looking at a couple of examples. In the first example, the RCC peripheral clock enable register is read back.
RCC->AHB1ENR |= (1 << RCC_AHB1ENR_GPIOAEN_Pos);
// do two dummy reads after enabling the peripheral clock, as per the errata
volatile uint32_t dummy;
dummy = RCC->AHB1ENR;
dummy = RCC->AHB1ENR;
In the second example, the peripheral register is read back.
void clock_wait_bus_cycles(enum bus_type bus, uint32_t cycles)
{
volatile uint32_t unused __attribute__((unused));
if (bus == BUS_AHB) {
while (cycles--)
unused = STM32_DMA1_REGS->isr;
} else { /* APB */
while (cycles--)
unused = STM32_USART_BRR(STM32_USART1_BASE);
}
}
Which one is the correct approach?
The first example is clearly for an STM32 part other then the STM32G0x0 which you are referencing, so in that sense it is not correct. The STM32G0x0 which has one AHB so AHBENR
not AHB1ENR
, and the GPIOA clock is enabled via IOPENR
not AHBENR
in any case. However the technique is legitimate, but needs to be specific to the part to be correct.
The function in the second method includes the function call overhead and a number of CPU cycles for the loop, so will be marginally longer, but that would be sweating the small stuff. It is perhaps unnecessarily complex. The choice of peripheral to read in the second seems arbitrary. You could equally read the appropriate RCC enable register as in the first example. The requirement is only to read something that will have wait states dependent on the associated bus or peripheral, so that further instructions are not executed until the read is complete.
ST provide HAL and LL peripheral libraries which are probably good exemplars of recommended methods (or at least methods likely to be valid - and tested) - even if you choose not to use the libraries themselves.
The STM32G0x0 HAL does this for example:
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN); \
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN); \
UNUSED(tmpreg); \
} while(0U)
It reads the individual enable bit that was just written, this guaranteeing to be the correct clock:
#define READ_BIT(REG, BIT) ((REG) & (BIT))
The read will not complete until the clock is active, so a single read is sufficient I think.
The fact that the code in the first example will not work on the part you are using is perhaps an argument for using the ST provided HAL library - it avoids issues of using and porting code across multiple STM32 parts which vary considerable across series.