在調試GD32晶片時遇到了時鐘配置錯誤的問題,仔細研究文檔和網上資料,認真調試之後做了以下總結,希望能幫到同樣有類似困惑的你。
**
1.認識時鐘
HXTAL:高速外部時鐘;
LXTAL:低速外部時鐘;
IRC8M:高速内部時鐘;
IRC40K:低速内部時鐘;
其中:
HXTAL高速外部時鐘為闆子焊接的外部晶振,精度高,但同時功耗相較内部時鐘也較高;
IRC8M内部高速時鐘為晶片内部自帶的時鐘,精度較低,可以應用在對時鐘要求不高的場景中;
IRC40K低速内部時鐘用于獨立看門狗的計數。
了解時鐘分類之後,就需要确認闆子所用晶振為内部晶振還是外部晶振。
**
2.了解晶片系統架構
**
(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.分析時鐘樹
分析完系統架構後,要确認具體的外設時鐘就需要去分析時鐘樹。
假設我們的目的是找到定時器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;
這段内容的意思是:
如果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;//系統時鐘為高速外部時鐘倍頻
}
}
執行程式後得到結果如下:
sys_clk_freq系統時鐘頻率為72MHz;
apb1_clk_freq總線APB1時鐘頻率為36MHz;
source_type系統時鐘源為0x03即RCU_SCSS_PLL;
以上與我們期望設定的情況一緻,timer2_run_time即定時器TIMER2的運作時間按配置以1s為周期增加。