前言:第一次开发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上,可以用镊子夹住复位电容,相当于按下复位按键。