2023-09-23 12:49 PM - edited 2023-09-25 01:16 PM
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:
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.
2023-09-25 12:35 AM
Nice.
Most of this gets eliminated by optimizer, I presume.
Just an idea, would using DWT_CYCCNT be a viable alternative for the delays?
JW
2023-09-25 03:28 PM
A compiler can inline function calls or do whatever it is allowed to do, but it is not allowed to optimize or eliminate volatile accesses and therefore also the loops on volatile variables. The DWT counter is fine, but debuggers disable/enable it when connecting/disconnecting. The beauty of this code is that, apart from being targeted for ARM CPU and using STM32 RCC peripheral, it doesn't depend on any timer, other hardware or compiler specific feature.
If anybody sees a way how these functions can fail, please report it.