天天看點

史上最詳細的gd32時鐘頻率設定

在調試GD32晶片時遇到了時鐘配置錯誤的問題,仔細研究文檔和網上資料,認真調試之後做了以下總結,希望能幫到同樣有類似困惑的你。

**

1.認識時鐘

HXTAL:高速外部時鐘;

LXTAL:低速外部時鐘;

IRC8M:高速内部時鐘;

IRC40K:低速内部時鐘;

史上最詳細的gd32時鐘頻率設定

其中:

HXTAL高速外部時鐘為闆子焊接的外部晶振,精度高,但同時功耗相較内部時鐘也較高;

IRC8M内部高速時鐘為晶片内部自帶的時鐘,精度較低,可以應用在對時鐘要求不高的場景中;

IRC40K低速内部時鐘用于獨立看門狗的計數。

了解時鐘分類之後,就需要确認闆子所用晶振為内部晶振還是外部晶振。

**

2.了解晶片系統架構

**

史上最詳細的gd32時鐘頻率設定

(1)以GD32F103RC為例,Cortex-M3架構,最高時鐘頻率為108MHz,其中:

AHB總線為系統時鐘的1分頻,即最高頻率為108MHz;

APB1總線為系統時鐘的2分頻,即最高頻率為54MHz;

APB2總線為系統時鐘的1分頻,即最高頻率為108MHz;

上述分頻并非作者杜撰,而是在上面的系統架構圖上可以找到,也可以在固件庫的system_gd32f10x.c中找到,如下代碼段所示:

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

(2)分析系統架構圖不僅僅能找到系統時鐘分頻的情況,更重要的是可以找到所有外設所挂載的總線(AHB、APB1、APB2)情況,由此可進一步确認外設的時鐘。以TIMER2為例,其挂載在APB1總線;

3.分析時鐘樹

分析完系統架構後,要确認具體的外設時鐘就需要去分析時鐘樹。

史上最詳細的gd32時鐘頻率設定

假設我們的目的是找到定時器2(TIMER2)的時鐘頻率。

開始分析時鐘樹,要從左往右分析。左側四個框為四種時鐘源,選擇某一時鐘源。

沿線路往右分析,經過各種倍頻之後得到CK_SYS即系統時鐘。繼續往右分析,找到标注有“TIMER1、2、3…’”之類的框,可以看出其時鐘來源于CK_APB1(最大54MHz),注意 在這個框中可以看到如下内容:TIMER1,2,3,4,5,6, 11,12,13 i f(APB1

prescale =1)x1

else x 2;

史上最詳細的gd32時鐘頻率設定

這段内容的意思是:

如果APB1的分頻系數為1,那麼TIMER2的時鐘頻率=APB1時鐘頻率 x 1;

如果APB1的分頻系數為2,那麼TIMER2的時鐘頻率=APB1時鐘頻率 x 2;

由此可以算出,在GD32F103RC晶片中如果配置系統時鐘CK_SYS=108MHz,則CK_APB1=54MHz,APB1

prescale =2,是以CK_TIMER2 = CK_APB1 *2=CK_SYS=108MHz;

同理,如果配置CK_SYS=72MHz,則CK_TIMER2=72MHz;

4.修改代碼

此處以使用外部12M晶振為例。

(1)設定外部晶振

打開gd32f10x.h檔案,找到如下代碼段:

/* define value of high speed crystal oscillator (HXTAL) in Hz */
#if !defined  HXTAL_VALUE    
#ifdef GD32F10X_CL   
#define HXTAL_VALUE    ((uint32_t)25000000) /*!< value of the external oscillator in Hz */
#else 
#define HXTAL_VALUE    ((uint32_t)12000000) /* !< from 4M to 16M *!< value of the external oscillator in Hz*/
#endif /* HXTAL_VALUE */
#endif /* high speed crystal oscillator value */
           

HXTAL_VALUE即闆子所焊接的外部晶振的宏定義,我所用的外部晶振為12MHz,是以此處為:#define HXTAL_VALUE ((uint32_t)12000000)。如果你 的外部晶振是8MHz,那麼這裡就應改為:#define HXTAL_VALUE ((uint32_t)8000000)。

(2)標明外部晶振作為系統主時鐘

打開system_gd32f10x.c檔案,找到如下代碼段:

#include "gd32f10x.h"

/* 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 */
           

__SYS_OSC_CLK即系統主時鐘的宏定義,因為我選用外部時鐘,是以此處為:#define __SYS_OSC_CLK (__HXTAL)。

(3)配置系統時鐘大小

在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)
           

在代碼中高亮顯示的項即為我選擇配置的72MHz系統時鐘,時鐘來源為外部高速時鐘倍頻。

(4)修改倍頻系數

上述操作隻是選擇了72MHz作為時鐘頻率,但距離真正産生72MHz時鐘還需要先對時鐘源(高速外部時鐘)進行倍頻等處理。

在system_gd32f10x.c檔案中找到如下代碼段:

/*!
    \brief      configure the system clock to 72M by PLL which selects HXTAL(MD/HD/XD:8M; CL:25M) as its clock source
    \param[in]  none
    \param[out] none
    \retval     none
*/
static void system_clock_72m_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 */
    if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
        while(1){
        }
    }

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

#if (defined(GD32F10X_MD) || defined(GD32F10X_HD) || defined(GD32F10X_XD))
    /* select HXTAL/2 as clock source */
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0);
    RCU_CFG0 |= (RCU_PLLSRC_HXTAL | RCU_CFG0_PREDV0);

    /* CK_PLL = (CK_HXTAL/2) * 18 = 72 MHz */
    RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4);
    RCU_CFG0 |= RCU_PLL_MUL12;//RCU_PLL_MUL18

#elif defined(GD32F10X_CL)
    /* CK_PLL = (CK_PREDIV0) * 18 = 72 MHz */ 
    RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4);
    RCU_CFG0 |= (RCU_PLLSRC_HXTAL | RCU_PLL_MUL18);

    /* CK_PREDIV0 = (CK_HXTAL)/5 *8 /10 = 4 MHz */ 
    RCU_CFG1 &= ~(RCU_CFG1_PREDV0SEL | RCU_CFG1_PLL1MF | RCU_CFG1_PREDV1 | RCU_CFG1_PREDV0);
    RCU_CFG1 |= (RCU_PREDV0SRC_CKPLL1 | RCU_PLL1_MUL8 | RCU_PREDV1_DIV5 | RCU_PREDV0_DIV10);

    /* enable PLL1 */
    RCU_CTL |= RCU_CTL_PLL1EN;
    /* wait till PLL1 is ready */
    while((RCU_CTL & RCU_CTL_PLL1STB) == 0){
    }
#endif /* GD32F10X_MD and GD32F10X_HD and GD32F10X_XD */

    /* enable PLL */
    RCU_CTL |= RCU_CTL_PLLEN;

    /* wait until PLL is stable */
    while(0U == (RCU_CTL & RCU_CTL_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)){
    }
}
           

注意該代碼段中的這一段代碼:

#if (defined(GD32F10X_MD) || defined(GD32F10X_HD) || defined(GD32F10X_XD))
    /* select HXTAL/2 as clock source */
    RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0);
    RCU_CFG0 |= (RCU_PLLSRC_HXTAL | RCU_CFG0_PREDV0);

    /* CK_PLL = (CK_HXTAL/2) * 18 = 72 MHz */
    RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4);
    RCU_CFG0 |= RCU_PLL_MUL12;//RCU_PLL_MUL18
           

最後一行是設定時鐘源的倍頻系數。

庫檔案預設的CK_HXTAL(高速外部時鐘)為8MHz,根據計算公式:CK_PLL = (CK_HXTAL/2) * 18 = 72 MHz,得出的倍頻系數為18,即RCU_PLL_MUL18。

而實際上我們用的CK_HXTAL(高速外部時鐘)為12MHz,根據計算公式:CK_PLL = (CK_HXTAL/2) * 12 = 72 MHz,得出的倍頻系數為12,即RCU_PLL_MUL12。是以最後一行應該為:RCU_CFG0 |= RCU_PLL_MUL12,即對時鐘源倍頻12倍。

至此我們真正得到的系統時鐘才為72 MHz。

5.檢驗配置結果

配置TIMER2驅動并查詢系統時鐘頻率、APB1總線時鐘頻率、系統時鐘來源。

/**
 * @brief  TIMER2配置(timebase)
 * 時鐘頻率 = 72000000 / 36000 = 2000Hz
 * 時鐘周期 = 1 / 2000Hz = 0.5ms
 * 更新周期 = 0.5ms * 2000 = 1s
 * @param  None
 * @retval 傳回值
 */
static void timer2_config(uint16_t Prescaler, uint32_t Period)
{
    timer_parameter_struct timer_initpara;
	rcu_periph_clock_enable(RCU_TIMER2);
	
	timer_deinit(TIMER2);
    /* initialize TIMER init parameter struct */
    timer_struct_para_init(&timer_initpara);
    /* TIMER2 configuration */
    timer_initpara.prescaler         = Prescaler - 1;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = Period;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_init(TIMER2, &timer_initpara);
	
    timer_interrupt_enable(TIMER2, TIMER_INT_UP);
    timer_enable(TIMER2);
	
    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
    nvic_irq_enable(TIMER2_IRQn, IRQ_PRIO_TIMEBASE);
    
    sys_clk_freq = rcu_clock_freq_get(CK_SYS);
    apb1_clk_freq = rcu_clock_freq_get(CK_APB1);
    sys_clk_source = rcu_system_clock_source_get();
    if(sys_clk_source == RCU_SCSS_IRC8M)
    {
        source_type = 1;//系統時鐘為高速内部時鐘
    }
    else if(sys_clk_source == RCU_SCSS_HXTAL)
    {
        source_type = 2;//系統時鐘為高速外部時鐘
    }
    else
    {
        source_type = 3;//系統時鐘為高速外部時鐘倍頻
    }  
}
           

執行程式後得到結果如下:

史上最詳細的gd32時鐘頻率設定

sys_clk_freq系統時鐘頻率為72MHz;

apb1_clk_freq總線APB1時鐘頻率為36MHz;

source_type系統時鐘源為0x03即RCU_SCSS_PLL;

以上與我們期望設定的情況一緻,timer2_run_time即定時器TIMER2的運作時間按配置以1s為周期增加。