STM32 clock and delay helper functions
For the whole STM32 family there is a longstanding issue of providing a proper delay after enabling the peripheral clocks. As reported and explained in this topic, since the the release of STM32 in 2007, ST has still failed to provide both - a correct documentation and code. To ensure a proper peripheral clock bus synchronization, the following has to be done:
- Enable the peripheral clock in RCC register.
- Read back any RCC register to ensure that the previous RCC write is complete.
- Wait for 2 peripheral (bus) clock cycles.
Here I am presenting a helper functions, which solve this issue and provide other useful functionality:
#define DIVU_ROUND(n, d) ( ((n) + (d) / 2) / (d) )
/*============================================================================*/
uint32_t DRV_RCC_BusPrescGet(const void *hPeriph)
{
// Always read an RCC register to ensure that previous RCC accesses are complete
uint32_t rCFGR = RCC->CFGR;
if ((uintptr_t)hPeriph >= AHB1PERIPH_BASE) {
return 1;
}
uint32_t iPresc;
if ((uintptr_t)hPeriph >= APB2PERIPH_BASE) {
iPresc = _FLD2VAL(RCC_CFGR_PPRE2, rCFGR);
} else { // APB1PERIPH_BASE
iPresc = _FLD2VAL(RCC_CFGR_PPRE1, rCFGR);
}
return 1ul << APBPrescTable[iPresc];
}
/*============================================================================*/
uint32_t DRV_RCC_BusClockGet(const void *hPeriph)
{
uint32_t nPresc = DRV_RCC_BusPrescGet(hPeriph);
return DIVU_ROUND(SystemCoreClock, nPresc);
}
/*============================================================================*/
void DRV_RCC_BusSync(const void *hPeriph)
{
// Synchronization needs a delay of 2 peripheral clock cycles
volatile uint32_t n = DRV_RCC_BusPrescGet(hPeriph);
while (--n); // At least 2 CPU clock cycles per iteration
}
/*============================================================================*/
void DRV_RCC_BusDelay(const void *hPeriph, uint32_t nCycles)
{
nCycles *= DRV_RCC_BusPrescGet(hPeriph);
DRV_RCC_CoreDelay(nCycles);
}
/*============================================================================*/
void DRV_RCC_CoreDelay(uint32_t nCycles)
{
volatile uint32_t n = nCycles / 2 + 1;
while (--n); // At least 2 CPU clock cycles per iteration
}
Using these functions, correctly enabling the peripheral clock becomes as easy as this:
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
DRV_RCC_BusSync(TIM2);
The DRV_RCC_CoreDelay() and DRV_RCC_BusDelay() functions provide a delay of a number of CPU or bus clock cycles respectively. Take a note that all three of those sync/delay functions do not provide an accurate delays, but rather a minimum guaranteed delays. The DRV_RCC_BusClockGet() function returns the bus clock frequency and the DRV_RCC_BusPrescGet() returns the bus prescale ratio for the peripheral.
This particular implementation is for L4 and F7 series. For other series the function DRV_RCC_BusPrescGet() has to be adapted according to the memory map of the MCU.
