GD32F450时钟树分析
文章目录
1. 系统环境
-
系统:win10
-
IDE:keil5
-
开发板:GD官方评估板GD32450Z_EVAL
-
用户手册版本:GD32F4xx_yonghushouce_Rev2.6.pdf
-
标准库版本:GD32F4xx_Demo_Suites_V2.5.0
2. 时钟树图
对于任何的一款MCU,熟悉它的时钟树,熟悉mcu时钟的配置过程,会修改时钟配置都是非常有必要的。
对于GD32F4来讲,它的时钟控制单元提供了一系列频率的时钟功能,包括:
- 一个外部高速晶体振荡器时钟(HXTAL)。
- 一个外部低速晶体振荡器时钟(LXTAL),一般接32.768K晶振。
- 一个内部48M RC振荡器时钟(IRC48M),这个48M时钟是专门为时钟校准控制器(CTC)和USB提供时钟的。
- 一个内部16M RC振荡器时钟(IRC16M。
- 一个内部32K RC振荡器时钟(IRC32K)。
- 三个锁相环(PLL)、一个HXTAL时钟监视器(CKM)、时钟预分频器、时钟多路复用器和时钟门控电路。
GD32F4系列MCU的时钟树如下图:
AHB、APB1、AHB2和Cortex-M4时钟都源自系统时钟(CK_SYS),系统时钟的时钟源可以选择IRC16M、 HXTAL或PLL。
系统时钟的最大运行时钟频率可以达到240MHz,但数据手册显示GD32F450最高可达到200M,GD32F470最高到达240M。
独立看门狗定时器有独立的时钟源(IRC32K),实时时钟(RTC)使用IRC32K、 LXTAL或HXTAL的分频(通过配置RCU_CFG0寄存器的RTCDIV位)作为时钟源。
3. GD32F450的时钟配置函数
首先我们先简要的叙述GD32F450的启动过程,关于GD32F450编译和启动过程的详细分析,我将另外写一篇文章,本文重点是记录官方带的demo程序是如何将GD32F450时钟配置到200M的。
在startup_gd32f450.s
文件中,有如下一段汇编代码:
;/* reset Handler */
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit ;将SystemInit函数地址加载到R0寄存器
BLX R0 ;执行SystemInit进行时钟设置,执行完后返回
LDR R0, =__main ;将__main函数地址加载到R0寄存器
BX R0 ;执行__main函数,不再返回,__main会再调用用户的main函数,从此进入用户编写的程序
ENDP
和STM32的启动过程一样,芯片上电先调用Reset_Handler,Reset_Handler会调用两个函数,一个是SystemInit配置系统时钟,另一个是__main,本章重点介绍SystemInit函数;
时钟的配置,由3个函数来完成,SystemInit
执行会调用system_clock_config
,system_clock_config
执行在调用system_clock_200m_25m_hxtal
,最后完成200M时钟的配置,具体的时钟配置函数和内部代码逻辑如下:
3.1 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 */
//其实这个CP10、CP11并不是真正的寄存器,它其实是协处理器的名字,ARM拥有16个协处理器,常被命名为CP0—CP15,其中CP0—CP7由厂家定义协处理功能,而CP8—CP15预留给ARM使用,CP15提供一些系统控制功能,这包括体系结构和特征识别,以及控制、状态信息和配置支持,还提供了性能监视器寄存器。CP14主要提供debug系统的控制、Thumb执行环境、字节码执行。CP10、CP11 两个协处理器一起,提供了浮点运算和向量操作,以及高级的 SIMD 指令扩展。协处理器8、9、12和13预留给ARM将来使用。因为协处理器是协助内核的,16个协处理器共提供出16*32位,基本每个位都有其相应的功能(不使用的位除外),我们平时都接触不到,因此我们平时都忽略它。
#endif
/* Reset the RCU clock configuration to the default reset state ------------*/
/* Set IRC16MEN bit */
//选择内部16M时钟
RCU_CTL |= RCU_CTL_IRC16MEN;
//将时钟进行2分频,等待时钟稳定,这里会等待的久一点,
RCU_MODIFY
//上面程序的目的是设置当前系统内核的时钟,此时系统的时钟为8M
//下面的Reset都是关闭操作,将各种时钟模块的使能全部关闭,变成失能
/* Reset CFG0 register */
RCU_CFG0 = 0x00000000U;
/* Reset HXTALEN, CKMEN and PLLEN bits */
//关闭外部高速时钟、高速时钟时钟监视器(CKM)、PLL锁相环使能
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();
}
3.2 system_clock_config函数
static void system_clock_config(void)
{
...
#elif defined (__SYSTEM_CLOCK_200M_PLL_25M_HXTAL)
//利用外部高速时钟(评估板带的是25M晶振),配置系统时钟为200M
system_clock_200m_25m_hxtal();
#endif
}
3.3 system_clock_200m_25m_hxtal函数
static void system_clock_200m_25m_hxtal(void)
{
uint32_t timeout = 0U;
uint32_t stab_flag = 0U;
/* 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 */
//若外部高速时钟异常,上面等待超时,则进入这里永远等待,系统会卡在while(1)里面
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
while(1){
//我感觉这里不合理,按理说外部时钟异常,应该触发相应的中断,并自动切换到内部高速时钟运行,而不是卡在这里。以后我有时间会把这个函数改改,现在先这样用吧。
}
}
//运行到这里,说明外部高速时钟正常启动,下面就要按照时钟树,来配置系统和各个模块的时钟
RCU_APB1EN |= RCU_APB1EN_PMUEN;//使能APB1总线
PMU_CTL |= PMU_CTL_LDOVS;//配置使能内部电压调节器1.2V域供电
//时钟的配置原则应该是从内向外,对照时钟树图,就可以非常清楚的明白时钟配置过程
/* HXTAL is stable */
/* AHB = SYSCLK */
RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;//系统SYS时钟到AHB总线时钟不进行分频
/* APB2 = AHB/2 */
RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;//APB2是AHB的2分频
/* APB1 = AHB/4 */
RCU_CFG0 |= RCU_APB1_CKAHB_DIV4;//APB1是AHB的4分频
//GD32F450有3个PLL(PLL、PLLI2S、PLLSAI),这里只配置了一个PLL,
//现在外部晶振时钟是25M,通过PSC的25分频,变成1M,然后再PLL内倍频400倍,变成400M,然后再经过P进行2分频,变成200M,传给系统时钟。同时400M还进行了Q的9分频,产生大约48M的CK48M给USB提供时钟去了。
//当更换外部晶振的频率后的时候,通过修改PSC就可以进行匹配了
/* Configure the main PLL, PSC = 25, PLL_N = 400, PLL_P = 2, PLL_Q = 9 */
RCU_PLL = (25U | (400U << 6U) | (((2U >> 1U) - 1U) << 16U) |
(RCU_PLLSRC_HXTAL) | (9U << 24U));
/* enable PLL */
RCU_CTL |= RCU_CTL_PLLEN;//上面配置完了,就要使能PLL
/* wait until PLL is stable */
//等待PLL配置时钟稳定下来
while(0U == (RCU_CTL & RCU_CTL_PLLSTB)){
}
//在用户手册3.3.4章节,说如果1.2V电源域工作在高频状态下,且打开了多种功能,建议进入高驱动模式。
/* Enable the high-drive to extend the clock frequency to 200 Mhz */
//使能高驱动模式
PMU_CTL |= PMU_CTL_HDEN;
//等待PMU_CS寄存器的HDRF被置位
while(0U == (PMU_CS & PMU_CS_HDRF)){
}
/* select the high-drive mode */
//将LDO切换到高驱动模式
PMU_CTL |= PMU_CTL_HDS;
//等待PMU_CS寄存器的HDSRF被置位。进入高驱动模式
while(0U == (PMU_CS & PMU_CS_HDSRF)){
}
/* select PLL as system clock */
//设置SCS[1:0],将时钟切换到刚配置好的PLL这条系统时钟线路
RCU_CFG0 &= ~RCU_CFG0_SCS;
RCU_CFG0 |= RCU_CKSYSSRC_PLLP;
/* wait until PLL is selected as system clock */
//等待PLL这条系统时钟配置能稳定给系统提供时钟
while(0U == (RCU_CFG0 & RCU_SCSS_PLLP)){
}
//到此处,系统完成了从8M到200M的切换
}
3.4 根据程序中的配置,绘制配置后的时钟路线图
最后,我们来绘制一下程序中配置的路径,其中绿色的是开始配置8M的时钟线路,红色的是配置200M的时钟线路,如下图:
4. 更换晶振后时钟配置
上面分析了时钟配置的完整过程,使用的是外部25M晶振,当我们更换晶振,除了修改函数宏之外,还要修改一个参数宏,具体请看我的另一篇文章:GD32F4(6):晶振引发串口乱码。