【GD32】系统时钟解析

一 时钟控制单元

1. HXTAL:高速外部时钟,4到32MHz的外部振荡器可为系统提供更为精确的主时钟。带有特定频率的晶体必须靠近两个HXTAL的引脚。和晶体连接的外部电阻和电容必须根据所选择的振荡器来调整。

2. IRC8M:高速内部8MHz时钟,内部8MHz RC振荡器时钟,简称IRC8M时钟,拥有8MHz的固定频率,设备上电后CPU默认选择的时钟源就是IRC8M时钟。

3. IRC28M:高速内部28MHz时钟,内部28MHz RC振荡器时钟 (IRC28M) 有一个固定的频率28MHz,专门用作ADC时钟。

4. IRC48M:高速内部48MHz时钟,内部48M RC振荡器时钟(IRC48M) 有一个固定的频率48MHz,用作USB时钟或者PLL时钟源。

5. LXTAL:低速外部时钟,LXTAL晶体是一个32.768kHz的低速外部晶体或陶瓷谐振器。它为实时时钟电路提供一个低功耗且精确的时钟源。

6. IRC40K:低速内部时钟,IRC40K RC振荡器时钟担当一个低功耗时钟源的角色,它的时钟频率大约40 kHz,为独立看门狗定时器和实时时钟电路提供时钟。

7. PLL:锁相环,内部锁相环PLL通过对输入参考频率为4~32MHz的时钟基准2 ~64倍频,可以提供16~108 MHz的时钟输出。
 

二 系统时钟架构

以GD32F330系列为例,Cortex-M4架构,最高时钟频率为84MHz。

 通过固件库的system_gd32f10x.c可知:

    /* HXTAL is stable */
    /* AHB = SYSCLK */
    RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
    /* APB2 = AHB/2 */
    RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
    /* APB1 = AHB/2 */
    RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;

AHB总线为系统时钟的1分频,即最高频率为84MHz;APB1总线为系统时钟的2分频,即最高频率为42MHz;APB2总线为系统时钟的2分频,即最高频率为42MHz;

另外,通过该时钟架构图可以清楚的了解该芯片外设所挂载的时钟总线(AHB、APB1、APB2),以TIMER2为例,挂载的是APB1总线,USART0挂载APB2总线。

三 时钟树分析

 

① 以HXTAL为例,外部HXTAL时钟源提供系统时钟频率。

②  PLLPRESEL选择外部晶振作为PLL时钟源,经过PREDV分频,PLLSEL选择外部晶振作为PLL时钟源,经过PLL倍频得到CK_PLL,再通过SCS选择PLL时钟源作为系统时钟。

③ CK_SYS系统时钟再通过分频得到CK_AHB,即为AHB总线的时钟频率。

④ 以TIMER2为例,TIMER2挂载APB1总线,假如APB1分频系数为1,则TIMER2频率为AHB,否则TIMER2频率为AHB/(APB1的分频系数/2)。

 ⑤ 以USART0为例,USART0挂载APB2总线,通过APB2分频得到CK_APB2频率直接提供USART0使用。

四 时钟初始化

1. 系统时钟初始化,在startup_gd32f3x0.s文件中找到如下代码段,进入SystemInit()接口

/* reset Handler */
Reset_Handler   PROC
                EXPORT  Reset_Handler                     [WEAK]
                IMPORT  SystemInit
                IMPORT  __main
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

2. 是RCU相关寄存器初始化,在system_gd32f3x0.c找到如下代码,进入system_clock_config()函数。

void SystemInit(void)
{
#if (defined(GD32F350))
    RCU_APB2EN |= BIT(0);
    CMP_CS |= (CMP_CS_CMP1MSEL | CMP_CS_CMP0MSEL);
#endif /* GD32F350 */
    if(((FMC_OBSTAT & OB_OBSTAT_PLEVEL_HIGH) != OB_OBSTAT_PLEVEL_HIGH) &&
            (((FMC_OBSTAT >> 13) & 0x1) == SET)) {
        FMC_KEY = UNLOCK_KEY0;
        FMC_KEY = UNLOCK_KEY1 ;
        FMC_OBKEY = UNLOCK_KEY0;
        FMC_OBKEY = UNLOCK_KEY1 ;
        FMC_CTL |= FMC_CTL_OBER;
        FMC_CTL |= FMC_CTL_START;
        while((uint32_t)0x00U != (FMC_STAT & FMC_STAT_BUSY));
        FMC_CTL &= ~FMC_CTL_OBER;
        FMC_CTL |= FMC_CTL_OBPG;
        if((FMC_OBSTAT & OB_OBSTAT_PLEVEL_HIGH) == OB_OBSTAT_PLEVEL_NO) {
            OB_SPC = FMC_NSPC;
        } else if((FMC_OBSTAT & OB_OBSTAT_PLEVEL_HIGH) == OB_OBSTAT_PLEVEL_LOW) {
            OB_SPC = FMC_LSPC;
        }
        OB_USER = OB_USER_DEFAULT & ((uint8_t)(FMC_OBSTAT >> 8));
        OB_DATA0 = ((uint8_t)(FMC_OBSTAT >> 16));
        OB_DATA1 = ((uint8_t)(FMC_OBSTAT >> 24));
        OB_WP0 = ((uint8_t)(FMC_WP));
        OB_WP1 = ((uint8_t)(FMC_WP >> 8));
        while((uint32_t)0x00U != (FMC_STAT & FMC_STAT_BUSY));
        FMC_CTL &= ~FMC_CTL_OBPG;
        FMC_CTL &= ~FMC_CTL_OBWEN;
        FMC_CTL |= FMC_CTL_LK;
    }
    /* FPU settings */
#if (__FPU_PRESENT == 1U) && (__FPU_USED == 1U)
    SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); /* set CP10 and CP11 Full Access */
#endif

    /* enable IRC8M */
    RCU_CTL0 |= RCU_CTL0_IRC8MEN;
    while(0U == (RCU_CTL0 & RCU_CTL0_IRC8MSTB)) {
    }
    RCU_CFG0 &= ~(RCU_CFG0_SCS);
    RCU_CTL0 &= ~(RCU_CTL0_HXTALEN | RCU_CTL0_CKMEN | RCU_CTL0_PLLEN | RCU_CTL0_HXTALBPS);

    /* reset RCU */
    RCU_CFG0 &= ~(RCU_CFG0_SCS | RCU_CFG0_AHBPSC | RCU_CFG0_APB1PSC | RCU_CFG0_APB2PSC | \
                  RCU_CFG0_ADCPSC | RCU_CFG0_CKOUTSEL | RCU_CFG0_CKOUTDIV | RCU_CFG0_PLLDV);
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLDV);
#if (defined(GD32F350))
    RCU_CFG0 &= ~(RCU_CFG0_USBFSPSC);
    RCU_CFG2 &= ~(RCU_CFG2_CECSEL | RCU_CFG2_USBFSPSC2);
#endif /* GD32F350 */

    RCU_CFG1 &= ~(RCU_CFG1_PREDV | RCU_CFG1_PLLMF5 | RCU_CFG1_PLLPRESEL);
    RCU_CFG2 &= ~(RCU_CFG2_USART0SEL | RCU_CFG2_ADCSEL);
    RCU_CFG2 &= ~RCU_CFG2_IRC28MDIV;
    RCU_CFG2 &= ~RCU_CFG2_ADCPSC2;
    RCU_CTL1 &= ~RCU_CTL1_IRC28MEN;
    RCU_ADDCTL &= ~RCU_ADDCTL_IRC48MEN;
    RCU_INT = 0x00000000U;
    RCU_ADDINT = 0x00000000U;

    /* configure system clock */
    system_clock_config();

#ifdef VECT_TAB_SRAM
    nvic_vector_table_set(NVIC_VECTTAB_RAM, VECT_TAB_OFFSET);
#else
    nvic_vector_table_set(NVIC_VECTTAB_FLASH, VECT_TAB_OFFSET);
#endif
}

3. 选择系统时钟频率84MHZ,在system_gd32f3x0.c找到如下代码,进入system_clock_84m_hxtal()。

static void system_clock_config(void)
{
#ifdef __SYSTEM_CLOCK_8M_HXTAL
    system_clock_8m_hxtal();
#elif defined (__SYSTEM_CLOCK_72M_PLL_HXTAL)
    system_clock_72m_hxtal();
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2)
    system_clock_72m_irc8m();
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC48M_DIV2)
    system_clock_72m_irc48m();
#elif defined (__SYSTEM_CLOCK_84M_PLL_HXTAL)
    system_clock_84m_hxtal();
#elif defined (__SYSTEM_CLOCK_84M_PLL_IRC8M_DIV2)
    system_clock_84m_irc8m();
#elif defined (__SYSTEM_CLOCK_96M_PLL_HXTAL)
    system_clock_96m_hxtal();
#elif defined (__SYSTEM_CLOCK_96M_PLL_IRC8M_DIV2)
    system_clock_96m_irc8m();
#elif defined (__SYSTEM_CLOCK_96M_PLL_IRC48M_DIV2)
    system_clock_96m_irc48m();
#elif defined (__SYSTEM_CLOCK_108M_PLL_HXTAL)
    system_clock_108m_hxtal();
#elif defined (__SYSTEM_CLOCK_108M_PLL_IRC8M_DIV2)
    system_clock_108m_irc8m();
#else
    system_clock_8m_irc8m();
#endif /* __SYSTEM_CLOCK_8M_HXTAL */
}

4. 根据系统时钟频率,去配置其他外设时钟源,在system_gd32f3x0.c找到如下代码。

static void system_clock_84m_hxtal(void)
{
    uint32_t timeout = 0U;
    uint32_t stab_flag = 0U;
    /* enable HXTAL */
    RCU_CTL0 |= RCU_CTL0_HXTALEN;

    /* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
    do {
        timeout++;
        stab_flag = (RCU_CTL0 & RCU_CTL0_HXTALSTB);
    } while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));
    /* if fail */
    if(0U == (RCU_CTL0 & RCU_CTL0_HXTALSTB)) {
        return;
    }
    /* HXTAL is stable */
    /* AHB = SYSCLK */
    RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
    /* APB2 = AHB/2 */
    RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
    /* APB1 = AHB/2 */
    RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;

    /* PLL = HXTAL /4 * 21 = 84 MHz */
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLPREDV); //16,27|(18,21),27,17
    RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PLLMF5 | RCU_CFG1_PREDV);     //30,31,(0,3)
    RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | (RCU_PLL_MUL21 & (~RCU_CFG1_PLLMF5))); //16,
    RCU_CFG1 |= (RCU_PLLPRESEL_HXTAL | RCU_PLL_PREDV4); //设置RCU_CFG1寄存器PREDV[3:0]预分频系数
    RCU_CFG1 |= (RCU_PLL_MUL21 & RCU_CFG1_PLLMF5); //RCU_CFG1_PLLMF5实际表示的是 :RCU_CFG0 的位 27,21:18
    /* enable PLL */
    RCU_CTL0 |= RCU_CTL0_PLLEN;

    /* wait until PLL is stable */
    while(0U == (RCU_CTL0 & RCU_CTL0_PLLSTB)) {
    }

    /* select PLL as system clock */
    RCU_CFG0 &= ~RCU_CFG0_SCS;
    RCU_CFG0 |= RCU_CKSYSSRC_PLL;

    /* wait until PLL is selected as system clock */
    while(0U == (RCU_CFG0 & RCU_SCSS_PLL)) {
    }
}

五 代码修改

 1. 设置外部晶振频率,在gd32f3x0.h文件,找到如下代码段,修改外部晶振为16MHZ。

    /* define value of high speed crystal oscillator (HXTAL) in Hz */
#if !defined  (HXTAL_VALUE)
#define HXTAL_VALUE    ((uint32_t)16000000)
#endif /* high speed crystal oscillator value */

2. 选择外部时钟源,在system_gd32f3x0.c文件,找到如下代码段,_SYS_OSC_CLK为系统时钟主频率选择宏定义,使用外部晶振选择__HXTAL。

/* system frequency define */
#define __IRC8M           (IRC8M_VALUE)            /* internal 8 MHz RC oscillator frequency */
#define __HXTAL           (HXTAL_VALUE)            /* high speed crystal oscillator frequency */
#define __SYS_OSC_CLK     (__HXTAL)                /* main oscillator frequency */

3. 设置系统主频率大小,在system_gd32f3x0.c文件中找到如下代码段,选择外部时钟源通过PLL配置为84MHZ系统时钟频率。

#if defined (GD32F330)
//#define __SYSTEM_CLOCK_8M_HXTAL              (__HXTAL)
//#define __SYSTEM_CLOCK_8M_IRC8M              (__IRC8M)
//#define __SYSTEM_CLOCK_72M_PLL_HXTAL         (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC8M_DIV2    (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC48M_DIV2     (uint32_t)(72000000)
#define __SYSTEM_CLOCK_84M_PLL_HXTAL           (uint32_t)(84000000)
//#define __SYSTEM_CLOCK_84M_PLL_IRC8M_DIV2    (uint32_t)(84000000)
#endif /* GD32F330 */

 4. 修改PLL分频倍频系数

修改前:系统外部晶振HXTAL默认为8MHZ,带入公式:

CK_PLL = (CK_HXTAL/2) * 21 = 84 MHz,得到PLL分频系数为2,PLL倍频系数为21

    /* PLL = HXTAL /2 * 21 = 84 MHz */
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PLLMF | RCU_CFG0_PLLMF4 | RCU_CFG0_PLLPREDV);
    RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PLLMF5 | RCU_CFG1_PREDV);
    RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | (RCU_PLL_MUL21 & (~RCU_CFG1_PLLMF5)));
    RCU_CFG1 |= (RCU_PLLPRESEL_HXTAL | RCU_PLL_PREDV2);
    RCU_CFG1 |= (RCU_PLL_MUL21 & RCU_CFG1_PLLMF5);

    /* enable PLL */
    RCU_CTL0 |= RCU_CTL0_PLLEN;

修改后:系统外部晶振HXTAL为16MHZ,带入公式:

CK_PLL = (CK_HXTAL/4) * 21 = 84 MHz,得到PLL分频系数为4,PLL倍频系数为21,修改如下

    RCU_CFG1 |= (RCU_PLLPRESEL_HXTAL | RCU_PLL_PREDV4);

六 PLL相关寄存器配置

前面介绍时钟树的时候说过,选择PLL作为系统时钟源需要设置四个地方,PLLPRESEL选择器,PREDV分频系数,PLLSEL选择器,PLL倍频系数。RCU_CFG1寄存器决定分频系数,RCU_CFG0寄存器决定倍频系数。查看数据手册,可以知道:

1. PLLPRESEL选择器

由 RCU_CFG1寄存器的第30位 PLLPRESEL决定 ,或由RCU_CFG0寄存器的第17位PLLPREDV决定。

2.PREDV分频系数

由RCU_CFG1寄存器3-0位的PREDV[3,0]决定,同时CU_CFG0寄存器的第17位和PREDV的第0位功能相同。

3. PLLSEL选择器

由RCU_CFG0寄存器的第16位决定

 

4. PLL倍频系数

由RCU_CFG0寄存器21-18位的PLLMF[3,0],RCU_CFG0寄存器的27位 PLLMF[4],RCU_CFG1寄存器的31位 PLLMF[5]共同决定。

七 仿真验证

源码链接:GD32时钟配置代码学习工程-嵌入式文档类资源-CSDN下载 

参考文章:

http://t.csdn.cn/JHan5

http://t.csdn.cn/k0m3W

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值