GD32F405时钟配置
1.时钟控制单元(CCTL)
时钟控制单元提供了一系列频率的时钟功能,包括一个内部16M RC振荡器时钟(IRC16M)、一个内部48M RC 振荡器时钟(IRC48M)、一个外部高速晶体振荡器时钟(HXTAL)、一个内部32K RC振荡器时钟(IRC32K)、一个外部低速晶体振荡器时钟(LXTAL)、三个锁相环(PLL)、一个HXTAL时钟监视器、时钟预分频器、时钟多路复用器和时钟门控电路。
AHB、APB和Cortex™-M4时钟都源自系统时钟(CK_SYS),系统时钟的时钟源可以选择IRC16M、HXTAL或PLL。系统时钟的最大运行时钟频率可以达到200MHz,同时MCU上电或复位后默认使用的系统时钟为IRC16M。GD32F405 AHB的时钟最大值为200MHz,APB1的时钟最大值为50MHz,APB2的时钟最大值为100MHz。
表1 最大允许时钟
SYS_CLK | AHB_CLK | APB1_CLK | APB2_CLK | |
---|---|---|---|---|
MAX | 200MHz | 200MHz | 50MHz | 100MHz |
2.时钟配置
GD32F405库函数自带系统时钟配置函数,但其只可配置指定的时钟配置,如果需要定制化需求,则需要自己编写时钟配置函数,自己编写的时钟配置函数结构可参考库函数自带的时钟配置函数。
2.1库函数自带时钟配置函数
GD32F405在启动过程会调用SystemIni函数,SystemInit函数会对MCU的复位和时钟单元RCU进行配置,特别是其中调用的system
clock_config函数,会配置MCU的时钟,因此通过修改system_clock
config函数即可完成芯片的时钟配置。通过对system_ clock_config函数查看可以发现,该函数具体时钟的配置与__SYSTEM_CLOCK_XXXX宏定义有关。因此,如果想在MCU启动过程中完成系统时钟的配置,只需要修改相关宏定义即可。除此之外还需要注意,当使用外部高速晶体振荡器HXTAL作为系统时钟源时,还需要根据使用的HXTAL的具体时钟频率来修改宏定义HXTAL_VALUE的值,该值会影响SystemCoreClock、AHB、APB1、APB2等时钟的计算,最终会影响部分外设的使用,如USART、I2C等。
SystemInit函数代码:
void SystemInit (void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCU clock configuration to the default reset state ------------*/
/* Set IRC16MEN bit */
RCU_CTL |= RCU_CTL_IRC16MEN;
RCU_MODIFY
/* Reset CFG0 register */
RCU_CFG0 = 0x00000000U;
/* Reset HXTALEN, CKMEN and PLLEN bits */
RCU_CTL &= ~(RCU_CTL_PLLEN | RCU_CTL_CKMEN | RCU_CTL_HXTALEN);
/* Reset PLLCFGR register */
RCU_PLL = 0x24003010U;
/* Reset HSEBYP bit */
RCU_CTL &= ~(RCU_CTL_HXTALBPS);
/* Disable all interrupts */
RCU_INT = 0x00000000U;
/* Configure the System clock source, PLL Multiplier and Divider factors,
AHB/APBx prescalers and Flash settings ----------------------------------*/
system_clock_config();
}
system_ clock_config函数代码:
static void system_clock_config(void)
{
#ifdef __SYSTEM_CLOCK_IRC16M
system_clock_16m_irc16m();
#elif defined (__SYSTEM_CLOCK_HXTAL)
system_clock_hxtal();
#elif defined (__SYSTEM_CLOCK_120M_PLL_IRC16M)
system_clock_120m_irc16m();
#elif defined (__SYSTEM_CLOCK_120M_PLL_8M_HXTAL)
system_clock_120m_8m_hxtal();
#elif defined (__SYSTEM_CLOCK_120M_PLL_25M_HXTAL)
system_clock_120m_25m_hxtal();
#elif defined (__SYSTEM_CLOCK_168M_PLL_IRC16M)
system_clock_168m_irc16m();
#elif defined (__SYSTEM_CLOCK_168M_PLL_8M_HXTAL)
system_clock_168m_8m_hxtal();
#elif defined (__SYSTEM_CLOCK_168M_PLL_25M_HXTAL)
system_clock_168m_25m_hxtal();
#elif defined (__SYSTEM_CLOCK_200M_PLL_IRC16M)
system_clock_200m_irc16m();
#elif defined (__SYSTEM_CLOCK_200M_PLL_8M_HXTAL)
system_clock_200m_8m_hxtal();
#elif defined (__SYSTEM_CLOCK_200M_PLL_25M_HXTAL)
system_clock_200m_25m_hxtal();
#endif /* __SYSTEM_CLOCK_IRC16M */
}
__SYSTEM_CLOCK_XXXX宏定义:
/* select a system clock by uncommenting the following line */
#define __SYSTEM_CLOCK_IRC16M (uint32_t)(__IRC16M)
//#define __SYSTEM_CLOCK_HXTAL (uint32_t)(__HXTAL)
//#define __SYSTEM_CLOCK_120M_PLL_IRC16M (uint32_t)(120000000)
//#define __SYSTEM_CLOCK_120M_PLL_8M_HXTAL (uint32_t)(120000000)
//#define __SYSTEM_CLOCK_120M_PLL_25M_HXTAL (uint32_t)(120000000)
//#define __SYSTEM_CLOCK_168M_PLL_IRC16M (uint32_t)(168000000)
//#define __SYSTEM_CLOCK_168M_PLL_8M_HXTAL (uint32_t)(168000000)
//#define __SYSTEM_CLOCK_168M_PLL_25M_HXTAL (uint32_t)(168000000)
//#define __SYSTEM_CLOCK_200M_PLL_IRC16M (uint32_t)(200000000)
//#define __SYSTEM_CLOCK_200M_PLL_8M_HXTAL (uint32_t)(200000000)
//#define __SYSTEM_CLOCK_200M_PLL_25M_HXTAL (uint32_t)(200000000)
注:(1)库函数自带的时钟配置函数位于system_gd32f4xx.c文件。
(2)宏定义HXTAL_VALUE位于gd32f4xx.h文件。
2.2 用户自编写时钟配置函数
由于GD32F405的库函数自带时钟配置函数,因此当用户自编写时钟配置函数时,可以借鉴库函数的时钟配置函数。用户只需要根据需求修改AHB、APB1、APB2的分频系数以及PLL的配置值即可,在配置时需要注意各部分时钟的允许范围,避免配置的时钟大于最大允许时。除此之外,与库函数自带时钟配置函数一样,也需要修改宏定义HXTAL_VALUE的值,同时还需用增加调用SystemCoreClockUpdate()函数,用来更新系统时钟SystemCoreClock的值。
除了上述程序的修改外,还需要对SystemInit()函数进行修改,由于SystemInit()调用了system_clock_config()函数,因此可能存在MCU上电完成后使用的系统时钟为非IRC16M时钟,这有可能会导致用户自编写的时钟配置函数出错,从而导致系统卡死。因此,需要手动将__SYSTEM_CLOCK_ XXXX宏定义修改为__SYSTEM_CLOCK_ IRC16M,从而保证上电后的系统时钟为IRC16M。(这样配置是为了保证程序具有通用性,当使用其它时钟作为系统时钟因该也是可以的,但是用户编写的时钟配置函数需要根据上电后时钟使用状况增加相应的处理程序。)
代码如下(示例):
static void system_clock_200m_hxtal(void)
{
uint32_t hxtal_temp; //XHTAL临时值
uint32_t PSC_temp; //PSC临时值
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
hxtal_temp = HXTAL_VALUE;
if(hxtal_temp == 8000000)
{
PSC_temp = 8U;
}
else if(hxtal_temp == 16000000)
{
PSC_temp = 16U;
}
/* enable HXTAL */
RCU_CTL |= RCU_CTL_HXTALEN;
/* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
do {
timeout++;
stab_flag = (RCU_CTL & RCU_CTL_HXTALSTB);
} while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)) {
while(1) {
}
}
RCU_APB1EN |= RCU_APB1EN_PMUEN;
PMU_CTL |= PMU_CTL_LDOVS;
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
/* APB2 = AHB/2 */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
/* APB1 = AHB/4 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV4;
/* Configure the main PLL, PSC = PSC_temp, PLL_N = 400, PLL_P = 2, PLL_Q = 9 */
RCU_PLL = (PSC_temp | (400U << 6U) | (((2U >> 1U) - 1U) << 16U) |
(RCU_PLLSRC_HXTAL) | (9U << 24U));
/* enable PLL */
RCU_CTL |= RCU_CTL_PLLEN;
/* wait until PLL is stable */
while(0U == (RCU_CTL & RCU_CTL_PLLSTB)) {
}
/* Enable the high-drive to extend the clock frequency to 200 Mhz */
PMU_CTL |= PMU_CTL_HDEN;
while(0U == (PMU_CS & PMU_CS_HDRF)) {
}
/* select the high-drive mode */
PMU_CTL |= PMU_CTL_HDS;
while(0U == (PMU_CS & PMU_CS_HDSRF)) {
}
/* select PLL as system clock */
RCU_CFG0 &= ~RCU_CFG0_SCS;
RCU_CFG0 |= RCU_CKSYSSRC_PLLP;
/* wait until PLL is selected as system clock */
while(0U == (RCU_CFG0 & RCU_SCSS_PLLP)) {
}
SystemCoreClockUpdate(); //更新系统时钟
}