GD32F103开发笔记(1)——解读默认时钟配置

前言:第一次开发GD32,记录一下系统初始化函数具体做了哪些配置,方便以后忘记的时候查看。 

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

上面是启动文件startup_gd32f10x_md.s中的一段代码,右键SystemInit转到定义,如下:

void SystemInit(void)
{
    /* reset the RCC clock configuration to the default reset state */
    /* enable IRC8M */
    RCU_CTL |= RCU_CTL_IRC8MEN;
    
    /* reset SCS, AHBPSC, APB1PSC, APB2PSC, ADCPSC, CKOUT0SEL bits */
    RCU_CFG0 &= ~(RCU_CFG0_SCS | RCU_CFG0_AHBPSC | RCU_CFG0_APB1PSC | RCU_CFG0_APB2PSC |
                  RCU_CFG0_ADCPSC | RCU_CFG0_ADCPSC_2 | RCU_CFG0_CKOUT0SEL);

    /* reset HXTALEN, CKMEN, PLLEN bits */
    RCU_CTL &= ~(RCU_CTL_HXTALEN | RCU_CTL_CKMEN | RCU_CTL_PLLEN);

    /* Reset HXTALBPS bit */
    RCU_CTL &= ~(RCU_CTL_HXTALBPS);

    /* reset PLLSEL, PREDV0_LSB, PLLMF, USBFSPSC bits */
    
#ifdef GD32F10X_CL
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0_LSB | RCU_CFG0_PLLMF |
                  RCU_CFG0_USBFSPSC | RCU_CFG0_PLLMF_4);

    RCU_CFG1 = 0x00000000U;
#else
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0 | RCU_CFG0_PLLMF |
                  RCU_CFG0_USBDPSC | RCU_CFG0_PLLMF_4);
#endif /* GD32F10X_CL */

#if (defined(GD32F10X_MD) || defined(GD32F10X_HD) || defined(GD32F10X_XD))
    /* reset HXTALEN, CKMEN and PLLEN bits */
    RCU_CTL &= ~(RCU_CTL_PLLEN | RCU_CTL_CKMEN | RCU_CTL_HXTALEN);
    /* disable all interrupts */
    RCU_INT = 0x009F0000U;
#elif defined(GD32F10X_CL)
    /* Reset HXTALEN, CKMEN, PLLEN, PLL1EN and PLL2EN bits */
    RCU_CTL &= ~(RCU_CTL_PLLEN | RCU_CTL_PLL1EN | RCU_CTL_PLL2EN | RCU_CTL_CKMEN | RCU_CTL_HXTALEN);
    /* disable all interrupts */
    RCU_INT = 0x00FF0000U;
#endif

    /* Configure the System clock source, PLL Multiplier, AHB/APBx prescalers and Flash settings */
    system_clock_config();
}

第一句:

RCU_CTL就是RCU寄存器中控制寄存器的地址,如下:

 

 然后转到RCU_CTL_IRC8MEN的定义,如下: 

#define RCU_CTL_IRC8MEN        BIT(0)         /*!< internal high speed oscillator enable */

再转到BIT(0)的定义,如下: 

#define BIT(x)                       ((uint32_t)((uint32_t)0x01U<<(x)))

BIT(x)是一个带参宏定义,作用为把1左移x位。

所以RCU_CTL_IRC8MEN = 0x01;

所以第一句代码的作用就是将RCU_CTL寄存器的第一位置1,该位具体功能体如下;

然后看第二句:

 /* reset SCS, AHBPSC, APB1PSC, APB2PSC, ADCPSC, CKOUT0SEL bits */
    RCU_CFG0 &= ~(RCU_CFG0_SCS | RCU_CFG0_AHBPSC | RCU_CFG0_APB1PSC | RCU_CFG0_APB2PSC |
                  RCU_CFG0_ADCPSC | RCU_CFG0_ADCPSC_2 | RCU_CFG0_CKOUT0SEL);

 下面先转到RCU_CFG0_SCS的定义:

#define RCU_CFG0_SCS                    BITS(0,1)              /*!< system clock switch */

然后再转到BITS(0,1)的定义,如下:

#define BITS(start, end)       ((0xFFFFFFFFUL << (start)) & (0xFFFFFFFFUL >> (31U - (uint32_t)(end)))) 

这也是一个带参宏定义,包含两个参数,其作用是将0xFFFFFFFF的低(start)位以及高(31-end)位清零。

BITS(0,1)即把除第一、二位以外的其余位数清零。

下面直接列出第二句代码右边的宏定义的具体数值:

RCU_CFG0_SCS       = BITS(0,1)   = 0x00000003;
RCU_CFG0_AHBPSC    = BITS(4,7)   = 0x000000F0;
RCU_CFG0_APB1PSC   = BITS(8,10)  = 0x00000700;
RCU_CFG0_APB2PSC   = BITS(11,13) = 0x00001C00;
RCU_CFG0_ADCPSC    = BITS(14,15) = 0x0000C000;
RCU_CFG0_ADCPSC_2  = BIT(28)     = 0x08000000;
RCU_CFG0_CKOUT0SEL = BITS(24,26) = 0x03C00000;

上面所有的数做或运算以后按位取反,然后与RCU_CFG0寄存器的数值做按位与运算,再将运算后的数值赋到RCU_CFG0寄存器,即把对应的位清零。

下面就是RCU_CFG0寄存器中上述位的功能描述。

1:0 SCS[1:0] 系统时钟选择
由软件配置选择系统时钟源。由于CK_SYS的改变存在固有的延迟,因此软件应当读
SCSS位来确保时钟源切换是否结束。在从深度睡眠或待机模式中返回时,以及当
HXTAL直接或间接作为系统时钟同时HXTAL时钟监视器检测到HXTAL故障时,强制
选择IRC8M作为系统时钟。
00:选择CK_IRC8M时钟作为CK_SYS时钟源
01:选择CK_HXTAL时钟作为CK_SYS时钟源
10:选择CK_PLL时钟作为CK_SYS时钟源
11:保留

7:4 AHBPSC[3:0] AHB预分频选择
由软件置位或清零,控制AHB时钟分频因子.
0xxx:选择CK_SYS时钟不分频
1000:选择CK_SYS时钟2分频
1001:选择CK_SYS时钟4分频
1010:选择CK_SYS时钟8分频
1011:选择CK_SYS时钟16分频
1100:选择CK_SYS时钟64分频
1101:选择CK_SYS时钟128分频
1110:选择CK_SYS时钟256分频
1111:选择CK_SYS时钟512分频

10:8 APB1PSC[2:0] APB1预分频选择
由软件置位或清零,控制APB1时钟分频因子.
0xx:选择CK_AHB时钟不分频
100:选择CK_AHB时钟2分频
101:选择CK_AHB时钟4分频
110:选择CK_AHB时钟8分频
111:选择CK_AHB时钟16分频

13:11 APB2PSC[2:0] APB2预分频选择
由软件置位或清零,控制APB2时钟分频因子
0xx:选择CK_AHB时钟不分频
100:选择CK_AHB时钟2分频
101:选择CK_AHB时钟4分频
110:选择CK_AHB时钟8分频
111:选择CK_AHB时钟16分频

15:14 ADCPSC[1:0] ADC的时钟分频系数
与寄存器RCU_CFG0的28位共同构成分频因子,由软件置位或清零
000:CK_ADC = CK_APB2 / 2
001:CK_ADC = CK_APB2 / 4
010:CK_ADC = CK_APB2 / 6
011:CK_ADC = CK_APB2 / 8
100:CK_ADC = CK_APB2 / 2
101:CK_ADC = CK_APB2 / 12
110:CK_ADC = CK_APB2 / 8
111:CK_ADC = CK_APB2 / 16

26:24 CKOUT0SEL[2:0] CKOUT0时钟源选择
由软件置位或清零
0xx:无时钟输出
100:选择系统时钟CK_SYS
101:选择内部8M RC振荡器时钟
110:选择高速晶体振荡器时钟(HXTAL)
111:选择(CK_PLL / 2)时钟

28 ADCPSC[2] ADCPSC的第2位
参考寄存器RCU_CFG0的14到15位

下面是第三和第四句代码,逻辑同上,把相应位清零。 

    /* reset HXTALEN, CKMEN, PLLEN bits */
    RCU_CTL &= ~(RCU_CTL_HXTALEN | RCU_CTL_CKMEN | RCU_CTL_PLLEN);

    /* Reset HXTALBPS bit */
    RCU_CTL &= ~(RCU_CTL_HXTALBPS);
RCU_CTL_HXTALEN    = BIT(16) 
RCU_CTL_CKMEN      = BIT(19)  
RCU_CTL_PLLEN      = BIT(24) 
RCU_CTL_HXTALBPS   = BIT(18) 

下面是对应位的功能描述。 

16 HXTALEN 高速晶体振荡器(XTAL)使能
软件置位或复位,如果HXTAL时钟作为系统时钟或者当PLL时钟做为系统时钟时,其
做为PLL的输入时钟,该位不能被复位。进入深度睡眠或待机模式时硬件自动复位
0:高速4 ~ 16 MHz晶体振荡器被关闭
1:高速4 ~ 16 MHz晶体振荡器被打开

19 CKMEN HXTAL时钟监视器使能
0:禁止高速4 ~ 16 MHz晶体振荡器(HXTAL)时钟监视器
1:使能高速4 ~ 16 MHz晶体振荡器(HXTAL)时钟监视器
当硬件检测到HXTAL时钟被阻塞在低或高状态时,内部硬件自动切换系统时钟到
IRC8M时钟。恢复原来系统时钟的方式有以下几种:外部复位,上电复位,软件清
CKMIF位。
注意:使能HXTAL时钟监视器以后,硬件无视控制位IRC8MEN的状态,自动使能
IRC8M时钟

24 PLLEN PLL使能
软件置位或复位,当PLL时钟做为系统时钟时该位不能被复位。当进入深度睡眠或待
机模式时由硬件复位
0:PLL被关闭
1:PLL被打开

18 HXTALBPS 高速晶体振荡器(HXTAL)时钟旁路模式使能
只有在HXTALEN位为0时HXTALBPS位才可写
0:禁止HXTAL旁路模式
1:使能HXTAL旁路模式 HXTAL输出时钟等于输入时钟

接下来是两个选择编译的预处理命令,逻辑同上,也是把对应位清零。 

 /* reset PLLSEL, PREDV0_LSB, PLLMF, USBFSPSC bits */
    
#ifdef GD32F10X_CL
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0_LSB | RCU_CFG0_PLLMF |
                  RCU_CFG0_USBFSPSC | RCU_CFG0_PLLMF_4);

    RCU_CFG1 = 0x00000000U;
#else
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0 | RCU_CFG0_PLLMF |
                  RCU_CFG0_USBDPSC | RCU_CFG0_PLLMF_4);
#endif /* GD32F10X_CL */

#if (defined(GD32F10X_MD) || defined(GD32F10X_HD) || defined(GD32F10X_XD))
    /* reset HXTALEN, CKMEN and PLLEN bits */
    RCU_CTL &= ~(RCU_CTL_PLLEN | RCU_CTL_CKMEN | RCU_CTL_HXTALEN);
    /* disable all interrupts */
    RCU_INT = 0x009F0000U;
#elif defined(GD32F10X_CL)
    /* Reset HXTALEN, CKMEN, PLLEN, PLL1EN and PLL2EN bits */
    RCU_CTL &= ~(RCU_CTL_PLLEN | RCU_CTL_PLL1EN | RCU_CTL_PLL2EN | RCU_CTL_CKMEN | RCU_CTL_HXTALEN);
    /* disable all interrupts */
    RCU_INT = 0x00FF0000U;
#endif

值得一提的是(GD32F10X_MD) ,(GD32F10X_HD) ,(GD32F10X_XD)和(GD32F10X_CL)这几个宏定义,具体描述如下。

根据所选的型号定义即可。

Syetem_Init()的结尾调用了static void system_clock_config(void)函数,如下:

static void system_clock_config(void)
{
#ifdef __SYSTEM_CLOCK_HXTAL
    system_clock_hxtal();
#elif defined (__SYSTEM_CLOCK_24M_PLL_HXTAL)
    system_clock_24m_hxtal();
#elif defined (__SYSTEM_CLOCK_36M_PLL_HXTAL)
    system_clock_36m_hxtal();
#elif defined (__SYSTEM_CLOCK_48M_PLL_HXTAL)
    system_clock_48m_hxtal();
#elif defined (__SYSTEM_CLOCK_56M_PLL_HXTAL)
    system_clock_56m_hxtal();
#elif defined (__SYSTEM_CLOCK_72M_PLL_HXTAL)
    system_clock_72m_hxtal();
#elif defined (__SYSTEM_CLOCK_96M_PLL_HXTAL)
    system_clock_96m_hxtal();
#elif defined (__SYSTEM_CLOCK_108M_PLL_HXTAL)
    system_clock_108m_hxtal();

#elif defined (__SYSTEM_CLOCK_48M_PLL_IRC8M)
    system_clock_48m_irc8m();
#elif defined (__SYSTEM_CLOCK_72M_PLL_IRC8M)
    system_clock_72m_irc8m();
#elif defined (__SYSTEM_CLOCK_108M_PLL_IRC8M)
    system_clock_108m_irc8m();
#endif /* __SYSTEM_CLOCK_HXTAL */
}

下面先看system_gd32f10x.c文件里如下所示的代码,这里根据需要设置默认系统时钟频率,取消注释即可,不用的要注释掉。

/* select a system clock by uncommenting the following line */
/* use IRC8M */
//#define __SYSTEM_CLOCK_48M_PLL_IRC8M            (uint32_t)(48000000)
//#define __SYSTEM_CLOCK_72M_PLL_IRC8M            (uint32_t)(72000000)
#define __SYSTEM_CLOCK_108M_PLL_IRC8M           (uint32_t)(108000000)

/* use HXTAL (XD series CK_HXTAL = 8M, CL series CK_HXTAL = 25M) */
//#define __SYSTEM_CLOCK_HXTAL                    (uint32_t)(__HXTAL)
//#define __SYSTEM_CLOCK_24M_PLL_HXTAL            (uint32_t)(24000000)
//#define __SYSTEM_CLOCK_36M_PLL_HXTAL            (uint32_t)(36000000)
//#define __SYSTEM_CLOCK_48M_PLL_HXTAL            (uint32_t)(48000000)
//#define __SYSTEM_CLOCK_56M_PLL_HXTAL            (uint32_t)(56000000)
//#define __SYSTEM_CLOCK_72M_PLL_HXTAL            (uint32_t)(72000000)
//#define __SYSTEM_CLOCK_96M_PLL_HXTAL            (uint32_t)(96000000)
//#define __SYSTEM_CLOCK_108M_PLL_HXTAL           (uint32_t)(108000000)

我这里把默认系统频率设置为了108M,故在static void system_clock_config(void)函数中会执行如下函数。

static void system_clock_108m_irc8m(void)
{
    uint32_t timeout = 0U;
    uint32_t stab_flag = 0U;
    
    /* enable IRC8M */
    RCU_CTL |= RCU_CTL_IRC8MEN;

    /* wait until IRC8M is stable or the startup time is longer than IRC8M_STARTUP_TIMEOUT */
    do{
        timeout++;        //计时,如果超出限定时间时钟尚未稳定,则跳出循环
        stab_flag = (RCU_CTL & RCU_CTL_IRC8MSTB);
    }
    while((0U == stab_flag) && (IRC8M_STARTUP_TIMEOUT != timeout));
    //等待内部8MHzRC振荡器稳定标志位置位或等待超时之后跳出循环

    /* if fail *///判定内部8MHzRC振荡器稳定标志位是否置位,否则进入死循环
    if(0U == (RCU_CTL & RCU_CTL_IRC8MSTB)){
      while(1){
      }
    }

    /* IRC8M is stable */
    /* AHB = SYSCLK */
    RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;     //设置SYSCLK作为AHB时钟源
    /* APB2 = AHB/1 */
    RCU_CFG0 |= RCU_APB2_CKAHB_DIV1;    //设置AHB作为APB2时钟源
    /* APB1 = AHB/2 */
    RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;    //把AHB 2分频以后作为APB1时钟

    /* CK_PLL = (CK_IRC8M/2) * 27 = 108 MHz */
    RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4);//PLL时钟源为IRC8M/2
    RCU_CFG0 |= RCU_PLL_MUL27;                       //设置PLL倍频因子为27

    /* enable PLL */
    RCU_CTL |= RCU_CTL_PLLEN;                        //使能PLL

    /* wait until PLL is stable */                   //等待PLL时钟稳定
    while(0U == (RCU_CTL & RCU_CTL_PLLSTB)){
    }

    /* select PLL as system clock */                 //设置PLL为系统时钟
    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)){
    }
}

可以看出来这个函数只是对RCU_CTL和RCU_CFG0两个寄存器进行配置,这里给出了中文注释,具体配置了哪些位可以参照上文,结合IC的用户手册自行分析一下。它的配置顺序我们可以记一下,倘若需要自己编写函数配置,需要按照它的顺序来。

如果配置顺序错误,可能会导致IC无法正常工作,同时出现无法下载程序的情况。keil会报错Internal command error,或者其他的,这时候需要先将程序修改正确,然后硬件复位IC(按下复位按键),点击下载,然后松开复位按键。如果IC已经焊接在自己的PCB上,可以用镊子夹住复位电容,相当于按下复位按键。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋至日丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值