一、基本流程
systick_config(); // 系統(tǒng)時鐘配置
rcu_periph_clock_enable(RCU_GPIOC); // 使能LED對應(yīng)的GPIO時鐘
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13); // 配置LED燈對應(yīng)的端口和引腳
gpio_bit_reset(GPIOC, GPIO_PIN_13); // 重置引腳
while(1){
gpio_bit_set(GPIOC, GPIO_PIN_13); // PC13引腳拉到高電平,打開燈
delay_1ms(500); // 延時500ms
gpio_bit_reset(GPIOC, GPIO_PIN_13); // PC13引腳拉到低電平,關(guān)閉燈
delay_1ms(500); // 延時500ms
}
拿LED閃爍這個例子來說,基本流程是先進行系統(tǒng)時鐘初始化systick_config()。然后根據(jù)我們業(yè)務(wù)需要,去使能和配置對應(yīng)的外設(shè)即可。
像這個例子中就是先打開GPIOC口。因為我們要使用PC13這個GPIO口。然后初始化PC13的引腳,配置為輸出推挽模式,頻率設(shè)置為50MHz。最后復位PC13引腳。最后在死循環(huán)中讓PC13每隔500ms電平翻轉(zhuǎn)一次即可。
二、時鐘
GD32F10X系列的系統(tǒng)時鐘(CK_SYS)有三個可選的時鐘源。一個內(nèi)部8MRC振蕩器時鐘(IRC8M)、一個外部高速晶體振蕩器(HXTAL)、一個鎖相環(huán)(PLL)。
系統(tǒng)時鐘的最大運行頻率可以達到108MHz。

系統(tǒng)總線分為三條。分別是高速總線AHB,兩條低速總線APB1和APB2。APB2最高可以達到108MHz,APB1最高可以達到54MHz。外設(shè)上的時鐘是根據(jù)所在總線的頻率倍頻或者分頻得到。
目前有這么幾個時鐘。三個時鐘源CK_PLL,CK_IRC8M,CK_HXTAL,系統(tǒng)時鐘CK_SYS,高速總線時鐘CK_AHB,低速總線時鐘1(CK_APB1),低速總線時鐘2(CK_APB2)。剩下的就是外設(shè)時鐘,包括ADC時鐘(CK_ADCx),定時器時鐘(CK_TIMERx)等。
這幾個時鐘的轉(zhuǎn)換關(guān)系可以通過觀察對應(yīng)寄存器的值來算出。
2.1 時鐘源
首先三個時鐘源對應(yīng)的頻率是。
2.1.1 CK_IRC8M
這個就是8MHz。
2.1.2 CK_PLL
這個通過觀察時鐘樹,由CK_IRC8M先進行二分頻,然后經(jīng)過PLLMF倍頻得到。只要知道倍頻是多少,即可算出。這兒的倍頻是需要看時鐘配置寄存器 0(RCU_CFG0)的PLLMF[3:0]位來得到。

我查看了板子的寄存器值是11010。查詢手冊可知這是PLL源時鐘 x 27得到。所以
CK_PLL = 8MHz / 2 x 27 = 108MHz

2.1.3 CK_HXTAL
4到16M的外部高速晶體振蕩器時鐘,這個由外界輸入決定。外部的輸入時鐘是多少就是多少。
HXTAL晶體振蕩器可以通過設(shè)置控制寄存器RCU_CTL的HXTALEN位來啟動或關(guān)閉,在控制寄存器RCU_CTL中的HXTALSTB位用來指示外部高速振蕩器是否已
穩(wěn)定。在啟動時,直到這一位被硬件置‘1’,時鐘才被釋放出來。這個特定的延遲時間被稱為振蕩器的啟動時間。當HXTAL時鐘穩(wěn)定后,如果在中斷寄存器RCU_INT
中的相應(yīng)中斷使能位HXTALSTBIE位被置‘1’,將會產(chǎn)生相應(yīng)中斷。此時,HXTAL時鐘可以被直接用作系統(tǒng)時鐘源或者PLL輸入時鐘。
將控制寄存器RCU_CTL的HXTALBPS和HXTAKEN位置‘1’來使能外部旁路模式。旁路輸入時,信號接至OSCIN,OSCOUT保持懸空狀態(tài),如下圖所示。此時,
CK_HXTAL等于驅(qū)動OSCIN管腳的外部時鐘。

2.2 系統(tǒng)時鐘CK_SYS
系統(tǒng)時鐘CK_SYS可選擇的有上面2.1的三個時鐘源。系統(tǒng)復位后,IRC8M時鐘默認做為CK_SYS的時鐘源,改變配置寄存器0(RCU_CFG0)中的系統(tǒng)時鐘變換
位SCS可以切換系統(tǒng)時鐘源為HXTAL或CK_PLL。當SCS的值被改變,系統(tǒng)時鐘將使用原來的時鐘源繼續(xù)運行直到轉(zhuǎn)換的目標時鐘源穩(wěn)定。當一個時鐘源被直接或通
過PLL間接作為系統(tǒng)時鐘時,它將不能被停止。

查看寄存器得到SCS的值10。所以系統(tǒng)時鐘選擇的是CK_PLL。也就是108M。

此時已經(jīng)確定系統(tǒng)時鐘是108MHz。
2.3 總線時鐘
通過觀察時鐘樹我們知道。高速總線AHB上的時鐘由系統(tǒng)時鐘CK_SYS分頻得到,低速總線APB1和APB2上的時鐘由CK_AHB分頻得到。
2.3.1 CK_AHB
CK_AHB由CK_SYS分頻得到,預分頻因子查看時鐘配置寄存器0(RCU_CFG0)的AHBPSC[3:0]位可獲取。最大分頻為512。

查看對應(yīng)寄存器結(jié)果是0x0,此時代表不分頻。所以我自己板子的
CK_AHB=CK_SYS=108MHz

2.3.2 CK_APB1
CK_APB1由CK_AHB分頻得到。預分頻因子查看時鐘配置寄存器0(RCU_CFG0)的APB1PSC[2:0]位可獲取。最大分頻為16。

查看自己寄存器的結(jié)果是0x4,對應(yīng)100,代表選擇CK_AHB時鐘2分頻。所以
CK_APB1 = CK_AHB / 2 = 108MHz / 2 = 54MHz

2.3.3 CK_APB2
CK_APB1由CK_AHB分頻得到。同CK_APB1。預分頻因子查看時鐘配置寄存器0(RCU_CFG0)的APB2PSC[2:0]位可獲取。最大分頻為16。

查看自己板子的寄存器的值為0x0,所以是選擇CK_AHB時鐘不分頻。
CK_APB2 = CK_AHB = 108MHz

2.4 定時器時鐘CK_TIMERx
定時器外設(shè)都是掛靠在低速總線上的,多個定時器掛靠的低速總線不一樣。觀察中密度GD32F10X系列結(jié)構(gòu)??梢钥闯鯰IMER0定時器掛靠在APB2上,TIMER1~3掛在APB1上。

針對TIMER2來分析。

CK_TIMER2時鐘是根據(jù)CK_APB1來得到的。判斷APB1的預分頻因子,如果是1,那么CK_TIMERx = CK_APB1 x 1;如果不是1,那么CK_TIMERx = CK_APB1 x 2。
我們從上面知道APB1的預分頻因子是2。所以CK_TIMERx的時鐘就是2倍的CK_APB1。
CK_TIMERx = CK_APB1 x 2 = 54MHz x 2 = 108MHz
所以TIMER2定時器的時鐘就是108MHz。
三、例程
3.1、燈運行
main.c
#include "gd32f10x.h"
#include "gd32f103b_start.h"
#include "systick.h"
/**
* LED2每隔1s閃爍一次
* PC13引腳是LED2的關(guān)聯(lián)引腳
*/
int main(void)
{
systick_config(); // 系統(tǒng)時鐘配置
rcu_periph_clock_enable(RCU_GPIOC); // 使能LED對應(yīng)的GPIO時鐘
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13); // 配置LED燈對應(yīng)的端口和引腳
gpio_bit_reset(GPIOC, GPIO_PIN_13); // 重置引腳
while(1){
gpio_bit_set(GPIOC, GPIO_PIN_13); // PC13引腳拉到高電平,打開燈
delay_1ms(500); // 延時500ms
gpio_bit_reset(GPIOC, GPIO_PIN_13); // PC13引腳拉到低電平,關(guān)閉燈
delay_1ms(500); // 延時500ms
}
}
上面main函數(shù)中使用的函數(shù)都是調(diào)用的系統(tǒng)函數(shù)或者外設(shè)函數(shù)。
重點:系統(tǒng)時鐘初始化的本質(zhì)
void systick_config(void)
{
/* setup systick timer for 1000Hz interrupts */
if(SysTick_Config(SystemCoreClock / 1000U)){
/* capture error */
while(1){
}
}
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}

系統(tǒng)時鐘初始化其實就是將我們選定的時鐘108M分到我們自己需要的系統(tǒng)定時器,這兒我們需要1KHz。所以需要在這個函數(shù)中除以1000即可。系統(tǒng)時鐘SystemCoreClock = 108M。
然后我們查看中斷源文件gd32f10x_it.c
/*!
\brief this function handles SysTick exception
\param[in] none
\param[out] none
\retval none
*/
void SysTick_Handler(void)
{
delay_decrement();
}
delay_decrement()
void delay_decrement(void)
{
if(0U != delay){
delay--;
}
}
系統(tǒng)定時中斷,這兒的中斷程序執(zhí)行按頻率衰減。一個周期減一,這樣就能實現(xiàn)延時函數(shù)delay_1ms()。
3.2、PMW模式輸出方波
main.c
#include "gd32f10x.h"
#include <stdio.h>
#include "gd32f10x_eval.h"
void gpio_config(void);
void timer_config(void);
/**
* 利用定時器2的通道2(TIMER2_CH2)輸出方波。波形頻率為1KHz。
*/
int main(void)
{
gpio_config(); // GPIO配置
timer_config(); // 定時器TIMER配置
while (1);
}
/**
* GPIO配置,并且初始化GPIO的復用功能。
*/
void gpio_config(void)
{
rcu_periph_clock_enable(RCU_GPIOB); // 配置GPIOB的時鐘
rcu_periph_clock_enable(RCU_AF); // 配置GPIO的AF復用功能,因為這個引腳要作為定時器的通道輸出。
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引腳,AF復用功能的推挽模式
}
/**
* 定時器的配置
*/
void timer_config(void)
{
/* -----------------------------------------------------------------------
TIMER2 配置: 以50的占空比生成一個pwm信號:
CK_TIMER2時鐘 = CK_APB1 / 預分頻因子 = 108MHz / 108 = 1MHz
TIMER2 通道2的占空比 = (500/ 1000)* 100 = 50%
----------------------------------------------------------------------- */
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER2); // 使能TIMER2定時器的時鐘
timer_deinit(TIMER2); // 復位TIMER2
/* TIMER2 配置 */
timer_initpara.prescaler = 108-1; // 預分頻因子。0~65535
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 邊沿計數(shù)對齊
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 上升沿觸發(fā)
timer_initpara.period = 1000-1; // 周期是100
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 時鐘分頻值
timer_initpara.repetitioncounter = 0; // 重復計數(shù)為0
timer_init(TIMER2,&timer_initpara); // 初始化TIMER2
/* PWM模式下的通道2配置 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 通道輸出極性高
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; // 通道輸出狀態(tài)使能
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; // 通道互補輸出極性高
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; // 通道互補輸出狀態(tài)不使能
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; // 通道輸出空閑狀態(tài)低
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; // 通道互補輸出空閑狀態(tài)低
timer_channel_output_config(TIMER2,TIMER_CH_2,&timer_ocintpara); // 定時器通道輸出配置
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,499); // 輸出脈沖值為500
timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0); // 通道輸出模式為PWM0
timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);// 通道輸出影子不使能
timer_auto_reload_shadow_enable(TIMER2); // 啟用自動重新加載陰影功能
timer_primary_output_config(TIMER2,ENABLE); // 啟用TIMER主輸出功能
timer_enable(TIMER2); // 使能TIMER2
}
注意點
-
GPIO使能時的模式
使能PB0作為TIMER2的CH2通道時,要注意同時使能GPIO的AF復用功能。并且選擇引腳為AF(復用)的PP(推挽模式)
rcu_periph_clock_enable(RCU_AF); // 配置GPIO的AF復用功能,因為這個引腳要作為定時器的通道輸出。
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引腳,AF復用功能的推挽模式
-
頻率的計算
計算頻率是這個例子的關(guān)鍵。上面我們已經(jīng)計算得到了當前定時器的時鐘是108MHz。我們最終需要的結(jié)果是1KHz。所以需要108M / 1K = 108000。
這兒需要認識一個新的時鐘。計數(shù)器時鐘(PSC_CLK)。預分頻器可以將定時器的時鐘(TIMER_CK)頻率按1到65536之間的任意值分頻,分頻后的時鐘
PSC_CLK驅(qū)動計數(shù)器計數(shù)。分頻系數(shù)受預分頻寄存器TIMERx_PSC控制,這個控制寄存器帶有緩沖器,它能夠在運行時被改變。新的預分頻器的參數(shù)在下一次
更新事件到來時被采用。

在代碼中我們配置的prescaler = 108-1 = 107。我們檢查一下寄存器的值是0x006B。換算得到的十進制就是107。

所以在代碼中我們得到的計數(shù)器時鐘:
PSC_CLK = CLK_TIMER2 / (prescaler + 1) = 108MHz / 108 = 1MHz
然后使用計數(shù)器時鐘除以(周期+1)即可得到我們最后的方波頻率。我們設(shè)置的周期period是1000-1 = 999。
最終計算結(jié)果為
PSC_CLK / (period + 1) = 1MHz / 1000 = 1KHz
3.3、pwm輸出呼吸燈
main.c
#include "gd32f10x.h"
#include "core_cm3.h"
FlagStatus flag = SET;
void gpio_config(void);
void timer_config(void);
void systick_config(void);
void delay_1ms(uint32_t count);
/**
* 呼吸燈的實現(xiàn)(使用pwm脈沖)
*/
int main(void)
{
uint16_t i = 0;
systick_config();
gpio_config();
timer_config();
while(1)
{
delay_1ms(10);
if (SET == flag)
{
i++;
} else {
i--;
}
if (i > 250)
{
flag = RESET;
}
if (i <= 0 )
{
flag = SET;
}
/** 修改TIMER2的CH2通道的CH2CVL的寄存器值,也就是 **/
/** timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,i);的i值 **/
TIMER_CH2CV(TIMER2) = (uint32_t)i;
}
}
/**
* GPIO配置,配置PC13引腳(LED2的引腳)配置PB0作為PWM輸出的引腳
*/
void gpio_config(void)
{
/** PB0作為timer2通道2的pwm輸出 **/
rcu_periph_clock_enable(RCU_GPIOB); // 配置GPIOB的時鐘
rcu_periph_clock_enable(RCU_AF); // 配置GPIO的AF復用功能,因為這個引腳要作為定時器的通道輸出。
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引腳,AF復用功能的推挽模式
rcu_periph_clock_enable(RCU_GPIOC); // PC13作為LED2的輸出引腳
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);// 初始化PB0的引腳,AF復用功能的推挽模式
gpio_bit_reset(GPIOC,GPIO_PIN_13);
}
/**
* 定時器的配置。配置TIMER2的CH2通道。
*/
void timer_config()
{
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER2); // 使能TIMER2定時器的時鐘
timer_deinit(TIMER2); // 復位TIMER2
/* TIMER2 配置 */
timer_initpara.prescaler = 108-1; // 預分頻因子。0~65535
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 邊沿計數(shù)對齊
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 上升沿觸發(fā)
timer_initpara.period = 1000-1; // 周期是100
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 時鐘分頻值
timer_initpara.repetitioncounter = 0; // 重復計數(shù)為0
timer_init(TIMER2,&timer_initpara); // 初始化TIMER2
/* PWM模式下的通道2配置 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 通道輸出極性高
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; // 通道輸出狀態(tài)使能
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; // 通道互補輸出極性高
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; // 通道互補輸出狀態(tài)不使能
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; // 通道輸出空閑狀態(tài)低
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; // 通道互補輸出空閑狀態(tài)低
timer_channel_output_config(TIMER2,TIMER_CH_2,&timer_ocintpara); // 定時器通道輸出配置
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0); // 輸出脈沖值為500
timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0); // 通道輸出模式為PWM0
timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);// 通道輸出影子不使能
timer_auto_reload_shadow_enable(TIMER2); // 啟用自動重新加載陰影功能
timer_primary_output_config(TIMER2,ENABLE); // 啟用TIMER主輸出功能
timer_enable(TIMER2); // 使能TIMER2
}
首先是系統(tǒng)時鐘的初始化,然后依靠系統(tǒng)時鐘形成延時函數(shù)
systick_config();
然后是gpio的初始化
/**
* GPIO配置,配置PC13引腳(LED2的引腳)配置PB0作為PWM輸出的引腳
*/
void gpio_config(void)
{
/** PB0作為timer2通道2的pwm輸出 **/
rcu_periph_clock_enable(RCU_GPIOB); // 配置GPIOB的時鐘
rcu_periph_clock_enable(RCU_AF); // 配置GPIO的AF復用功能,因為這個引腳要作為定時器的通道輸出。
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引腳,AF復用功能的推挽模式
rcu_periph_clock_enable(RCU_GPIOC); // PC13作為LED2的輸出引腳
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);// 初始化PB0的引腳,AF復用功能的推挽模式
gpio_bit_reset(GPIOC,GPIO_PIN_13);
}
PB0是TIMER2的CH2復用引腳。配置是AF(復用)的PP(推挽)模式。
PC13是LED2的引腳,配置為推挽輸出模式(OUT_PP)
然后是定時器配置
/**
* 定時器的配置。配置TIMER2的CH2通道。
*/
void timer_config()
{
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER2); // 使能TIMER2定時器的時鐘
timer_deinit(TIMER2); // 復位TIMER2
/* TIMER2 配置 */
timer_initpara.prescaler = 108-1; // 預分頻因子。0~65535
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 邊沿計數(shù)對齊
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 上升沿觸發(fā)
timer_initpara.period = 1000-1; // 周期是100
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 時鐘分頻值
timer_initpara.repetitioncounter = 0; // 重復計數(shù)為0
timer_init(TIMER2,&timer_initpara); // 初始化TIMER2
/* PWM模式下的通道2配置 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 通道輸出極性高
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; // 通道輸出狀態(tài)使能
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; // 通道互補輸出極性高
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; // 通道互補輸出狀態(tài)不使能
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; // 通道輸出空閑狀態(tài)低
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; // 通道互補輸出空閑狀態(tài)低
timer_channel_output_config(TIMER2,TIMER_CH_2,&timer_ocintpara); // 定時器通道輸出配置
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0); // 輸出脈沖值為500
timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0); // 通道輸出模式為PWM0
timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);// 通道輸出影子不使能
timer_auto_reload_shadow_enable(TIMER2); // 啟用自動重新加載陰影功能
timer_primary_output_config(TIMER2,ENABLE); // 啟用TIMER主輸出功能
timer_enable(TIMER2); // 使能TIMER2
}
這兒主要考慮的是timer_channel_output_config(TIMER2,TIMER_CH_2,&timer_ocintpara);這句。因為我們要形成呼吸的效果,那這兒的輸出的脈沖值就得發(fā)生變化。其他配置和輸出方波一樣。
然后在主函數(shù)中使用延時,隨著系統(tǒng)時鐘中斷不斷進入,i的值不停增加。當系統(tǒng)時鐘中斷進入到50次時再逐漸將i變小。在這個過程中將i的值不停賦值給CH2CV。這樣占空比就一直在變化。
驗證時,需要將PB0的輸出接到PC13上。LED2燈會有呼吸燈效果。
while(1)
{
delay_1ms(10);
if (SET == flag)
{
i++;
} else {
i--;
}
if (i > 250)
{
flag = RESET;
}
if (i <= 0 )
{
flag = SET;
}
/** 修改TIMER2的CH2通道的CH2CVL的寄存器值,也就是 **/
/** timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,i);的i值 **/
TIMER_CH2CV(TIMER2) = (uint32_t)i;
}
3.4、定時器中斷輸出呼吸燈
利用TIMER2定時器的中斷來生成呼吸燈。
main.c
#include "gd32f10x.h"
#include "core_cm3.h"
void gpio_config(void);
void timer_config(void);
/**
* 呼吸燈的實現(xiàn)(使用TIMER2定時實現(xiàn))
*/
int main(void)
{
gpio_config();
timer_config();
while(1){}
}
void gpio_config(void)
{
/** PB0作為timer2通道2的pwm輸出 **/
rcu_periph_clock_enable(RCU_GPIOB); // 配置GPIOB的時鐘
rcu_periph_clock_enable(RCU_AF); // 配置GPIO的AF復用功能,因為這個引腳要作為定時器的通道輸出。
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引腳,AF復用功能的推挽模式
rcu_periph_clock_enable(RCU_GPIOC); // PC13作為LED2的輸出引腳
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);// 初始化PB0的引腳,AF復用功能的推挽模式
gpio_bit_reset(GPIOC,GPIO_PIN_13);
}
void timer_config()
{
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER2); // 使能TIMER2定時器的時鐘
timer_deinit(TIMER2); // 復位TIMER2
/* TIMER2 配置 */
timer_initpara.prescaler = 3600-1; // 預分頻因子。0~65535
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 邊沿計數(shù)對齊
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 上升沿觸發(fā)
timer_initpara.period = 250-1; // 周期是250
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 時鐘分頻值
timer_initpara.repetitioncounter = 0; // 重復計數(shù)為0
timer_init(TIMER2,&timer_initpara); // 初始化TIMER2
/* PWM模式下的通道2配置 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; // 通道輸出極性高
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; // 通道輸出狀態(tài)使能
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH; // 通道互補輸出極性高
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE; // 通道互補輸出狀態(tài)不使能
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW; // 通道輸出空閑狀態(tài)低
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW; // 通道互補輸出空閑狀態(tài)低
timer_channel_output_config(TIMER2,TIMER_CH_2,&timer_ocintpara); // 定時器通道輸出配置
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0); // 輸出脈沖值為動態(tài)值
timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0); // 通道輸出模式為PWM0
timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);// 通道輸出影子不使能
timer_auto_reload_shadow_enable(TIMER2); // 啟用自動重新加載陰影功能
timer_primary_output_config(TIMER2,ENABLE); // 啟用TIMER主輸出功能
nvic_irq_enable(TIMER2_IRQn, 0, 2); // TIMER2中斷設(shè)置, 搶占優(yōu)先級為0, 次優(yōu)先級為2
timer_interrupt_enable(TIMER2, TIMER_INT_UP); // 使能TIMER2更新中斷
timer_enable(TIMER2); // 使能TIMER2
}
主函數(shù)中,剛開始不用聲明系統(tǒng)時鐘,因為我們不需要。GPIO設(shè)置一樣。還是PB0和PC13。
在定時器設(shè)置中需要開啟中斷
nvic_irq_enable(TIMER2_IRQn, 0, 2); // TIMER2中斷設(shè)置, 搶占優(yōu)先級為0, 次優(yōu)先級為2
timer_interrupt_enable(TIMER2, TIMER_INT_UP); // 使能TIMER2更新中斷
然后在中斷函數(shù)文件中gd32f10x_it.c編寫中斷程序
#include "gd32f10x_it.h"
#include "gd32f10x_eval.h"
uint8_t indexWave[] = {1,1,2,2,3,4,6,8,10,14,19,25,33,44,59,80,
107,143,191,250,250,191,143,107,80,59,44,33,25,19,14,10,8,6,4,3,2,2,1,1};
/**
* TIMER2中斷程序
*/
void TIMER2_IRQHandler(void)
{
static uint8_t pwm_index = 0; //用于PWM查表
static uint8_t period_cnt = 0; //用于計算周期數(shù)
if(timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_UP))
{
/* 清除TIMER2 中斷標志位 */
timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP);
period_cnt++;
if(period_cnt >= 10) //10次定時器中斷更改一次比較寄存器的值,輸出下一種脈沖寬的PWM波
{
//根據(jù)PWM表修改定時器的比較寄存器值
TIMER_CH2CV(TIMER2) = indexWave[pwm_index];
pwm_index++; //標志PWM表的下一個元素
//若PWM脈沖表已經(jīng)輸出完成一遍,重置PWM查表標志
if( pwm_index >= 40)
{
pwm_index=0;
}
period_cnt=0; //重置周期計數(shù)標志
}
}
}
indexWave數(shù)組是占空比呈指數(shù)曲線變化的 PWM 信號。共40個數(shù)字。最大是250。把LED的亮度分成了0~250個等級。
本中斷服務(wù)函數(shù)在每次定時器更新事件發(fā)生時執(zhí)行一次。函數(shù)中使用了靜態(tài)變量 pwm_index 和 period_cnt,它們分別用來查找 PWM 表元素和記錄同樣占空
比的脈沖輸出了多少次。
本代碼的目的是每 10 次定時器中斷更新一次 PWM 表中的數(shù)據(jù)到比較寄存器中,當遍歷完 PWM 表的 40 個元素時,再重頭開始遍歷 PWM 表,周而復始,
重復 LED 的呼吸過程。
整個呼吸過程的時間計算方法如下:
因為定時器的 prescaler 設(shè)置為 3600 -1;
所以定時器的時鐘頻率:CK_TIMER2 = 108M / (prescaler +1) = 108000000 / 3600 = 30000HZ
即定時器的時鐘周期為:T = 1 / CK_TIMER2 = 1/30000 s
定時器的周期period為 250-1。
定時器的中斷周期就是 t = T * ( period + 1 ) = 1/30000 * 250 = 1/120s
pwm表有pwm_index=40個占空比數(shù)據(jù),同一個占空比輸出是10次中斷。
所以一個呼吸周期就是 Tf = t * 40 * 10 = 3.33s