開發(fā)環(huán)境:
MDK:Keil 5.30
開發(fā)板:GD32F207I-EVAL
MCU:GD32F207IK
1 呼吸燈的工作原理
呼吸燈,就是指燈光設備的亮度隨著時間由暗到亮逐漸增強,再由亮到暗逐漸衰減,很有節(jié)奏感地一起一伏,就像是在呼吸一樣,因而被廣泛應用于手機、電腦等電子設備的指示燈中。
要使用數(shù)字器件控制燈光的強弱,我們很自然就想到 PWM(脈沖寬度調制)技術。假如以LED 作為燈光設備,且由控制器輸出的 PWM 信號可以直接驅動 LED,PWM 信號中的低電平可點亮 LED 燈。當 LED 以較高的頻率進行開關(亮滅)切換時,由于視覺暫留效應,人眼是看不到 LED 燈的閃爍現(xiàn)象的,反映到人眼中能感覺到的是亮度的差別。即以一定的時間長度為周期,LED 燈亮的平均時間越長,亮度就越高,反之越暗。因此,我們可以使用高頻率的 PWM 信號,通過調制信號的占空比,控制 LED 燈的亮度。
那么具體我們應該控制 LED 燈以怎樣的亮度曲線變化能夠達到最好的效果呢?亮度隨著時間逐漸變強再衰減,可以用兩種常見的數(shù)學函數(shù)表示,分別是半個周期的正弦函數(shù)與指數(shù)上升曲線及其對稱得到的下降曲線。
相對來說,使用下凹函數(shù)曲線燈光處于暗的狀態(tài)更長,所以指數(shù)函數(shù)的曲線更符合我們呼吸燈的亮度變化要求。
2 呼吸燈實現(xiàn)
2.1 簡單方式
筆者先用最簡單的方式來實現(xiàn),也就是定時改變比較寄存器的值。
1.初始化 GPIO
下面分析具體的定時器配置代碼。本實驗使用 PB0 作為定時器 PWM 輸出通道,先對它進行初始化。作 PWM 輸出通道的引腳需要被配置為復用推挽輸出模式。
/*
brief configure PWM GPIO
param[in] none
param[out] none
retval none
*/
static void timer_gpio_init(void)
{
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
/* Configure PB0 (TIMER2 CH2) as alternate function */
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
}
2.配置定時器模式
在timer2_init()函數(shù)中,完成了呼吸燈所需要的定時器 PWM 輸出模式配置。
/*
brief configure the Breath LED peripheral
param[in] none
param[out] none
retval none
*/
void breath_led_init(void)
{
/* TIMER2 configuration: generate PWM signals with different duty cycles:
TIMER2CLK = SystemCoreClock / 120 = 1MHz */
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
/* configure the GPIO ports */
timer_gpio_init();
rcu_periph_clock_enable(RCU_TIMER2);
timer_deinit(TIMER2);
/* TIMER1 configuration */
timer_initpara.prescaler = 119;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 250;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER2, &timer_initpara);
/* CH0 configuration in PWM mode 0 */
timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
timer_channel_output_config(TIMER2, TIMER_CH_2, &timer_ocintpara);
/* CH0 configuration in PWM mode 0,duty cycle 25% */
timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_2, 0);
timer_channel_output_mode_config(TIMER2, TIMER_CH_2, TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER2, TIMER_CH_2, TIMER_OC_SHADOW_DISABLE);
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER2);
/* TIMER2 enable */
timer_enable(TIMER2);
}
這個定時器的模式配置主要分為三個部分,分別為時基初始化,輸出模式初始化。
- 時基初始化
代碼中前面的部分是定時器的時基初始化,這部分主要負責配置定時器的定時周期、時鐘頻率、計數(shù)方式等。它使用到庫函數(shù)timer_init()函數(shù),利用結構體timer_parameter_struct進行配置,該結構體有以下成員:
- period
定時周期,實質是存儲到重載寄存器CAR的數(shù)值,脈沖計數(shù)器從 0 累加到這個值上溢或從這個值自減至 0 下溢。這個數(shù)值加 1 然后乘以時鐘源周期就是實際定時周期。
本實驗中向該成員賦值為 255,即定時周期為(255+1)* T ,T 為定時器的時鐘周期。
- prescaler
對定時器時鐘CLK 的預分頻值,分頻后作為脈沖計數(shù)器TIMERx_CNT的驅動時鐘,得到脈沖計數(shù)器的時鐘頻率為:CNT=CLK/(N+1),其中 N 為即為賦給本成員的時鐘分頻值。
本實驗給 prescaler 成員賦值為 119,即對時鐘 120 分頻,所以定時器的時鐘周期 T 為 120/120000000。
- clockdivision
時鐘分頻因子。怎么又出現(xiàn)一個配置時鐘分頻的呢?要注意這個clockdivision和上面的 prescaler 是不一樣的。prescaler 預分頻配置是對CLK進行分頻,分頻后的時鐘被輸出到脈沖計數(shù)器CNT。
本實驗中是使用內(nèi)部時鐘CLK 作為定時器時鐘源的,沒有進行濾波所以配置clockdivision為任何數(shù)值都沒有影響。
- alignedmode
本成員配置的為脈沖計數(shù)器 CNT 的計數(shù)模式,分別為向上計數(shù),向下計數(shù),及中央對齊模式。向上計數(shù)即 CNT 從 0 向上累加到 period 中的值,(重載寄存器 CAR 的值),產(chǎn)生上溢事件;向下計數(shù)則 CNT 從period 的值累減至0,產(chǎn)生下溢事件。而中央對齊模式則為向上、向下計數(shù)的合體,CNT 從 0 累加到period 的值減 1 時,產(chǎn)生一個上溢事件,然后向下計數(shù)到 1 時,產(chǎn)生一個計數(shù)器下溢事件,再從 0 開始重新計數(shù)。
- 輸出模式配置
在本函數(shù)代碼的后面是關于定時器的輸出模式配置的。通用定時器的輸出模式由 timer_oc_parameter_struct類型結構體的主要有以下幾個成員:
- outputstate
配置輸出模式的狀態(tài)使能或關閉輸出。
- outputnstate
本成員的參數(shù)值即為比較寄存器 CH2CV的數(shù)值,當脈沖計數(shù)器CNT與CH2CV的比較結果發(fā)生變化時,輸出脈沖將發(fā)生跳變。
- ocpolarity
有效電平的極性,把 PWM 模式中的有效電平設置為高電平或低電平。
本實驗中向該成員賦值為 TIMER_OC_POLARITY_LOW (有效電平為低電平),因為在上面把輸出模式配置為 PWM0 模式,向上計數(shù),所以在 CNT< CH0CV 時,通道 n 輸出為低電平,否則為高電平。
- ocnpolarity
用于比較有效電平的極性。
本實驗中就是通過不斷改變比較寄存器CH2CV的值,達到控制 PWM 信號的占空比呈指數(shù)曲線變化的目的。在本函數(shù)代碼中,我們對該成員賦予初始為 0,而改變比較寄存器 CH0CV 值的操作是在中斷服務函數(shù)中修改的。填充完輸出模式初始化結構體后,調用輸出模式初始化函數(shù) timer_channel_output_config()對通道進行初始化。
以上是最基本的PWM輸出調制實現(xiàn)呼吸燈。
筆者接下來還要講解一下重映射的輸出配置。在這里講解的是通過重映射 TIMER2_CH2到 PB0 上,由 TIMER2_CH2 輸出 PWM 來控制LED的亮度。下面我們介紹通過庫函數(shù)來配置該功能的步驟。
1)開啟 TIMER2時鐘以及復用功能時鐘,配置 PB0為復用輸出。
要使用 TIMER2,我們必須先開啟 TIMER2的時鐘,這點相信大家看了這么多代碼,應該明白了。這里我們還要配置 PB0為復用輸出,此時,PB0屬于復用功能輸出。在此只列出庫函數(shù)設置 AFIO 時鐘的方法。
rcu_periph_clock_enable(RCU_AF);
其余的和前面的配置一樣,就不再列出了。
2)初始化 TIMER2,設置 TIMER2的 CAR 和 PSC。
3)設置 TIMER2_CH2 的 PWM 模式,使能 TIMER2的 CH2 輸出。
4)使能 TIMER2。
在完成以上設置了之后,我們需要使能 TIMER2。 使能 TIMER2的方法前面已經(jīng)講解過:
timer_enable(TIMER2);
5)修改 TIMER2_ CH2CV來控制占空比。
最后,在經(jīng)過以上設置之后, PWM 其實已經(jīng)開始輸出了,只是其占空比和頻率都是固定的,而我們通過修改 TIMER2_CH2CV則可以控制 CH2 的輸出占空比。繼而控制LED的亮度。在庫函數(shù)中,修改 TIMER2_CH2CV占空比的函數(shù)是:
void timer_channel_output_pulse_value_config(uint32_t timer_periph, uint16_t channel, uint32_t pulse)
通過以上5個步驟,我們就可以控制 TIMER2的 CH2 輸出 PWM 波了。
接下來看看主函數(shù)的代碼:
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
uint16_t i = 0;
FlagStatus breathe_flag = SET;
//systick init
sysTick_init();
/* configure the Breath LED peripheral */
breath_led_init();
while(1)
{
/* delay a time in milliseconds */
delay_ms(5);
if(SET == breathe_flag)
{
i++;
}
else
{
i--;
}
if(250 < i)
{
breathe_flag = RESET;
}
if(0 >= i)
{
breathe_flag = SET;
}
/* configure TIMER channel output pulse value */
//timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_2, i);
TIMER_CH2CV(TIMER2) = (uint32_t)i;
}
}
代碼很簡單,就是不斷改變CH2CV的值從而控制 CH2 的輸出占空比。
2.2 中斷方式
1.生成指數(shù)曲線 PWM 數(shù)據(jù)
要實現(xiàn) LED 亮度隨著指數(shù)曲線變化,我們需要使用占空比呈指數(shù)曲線變化的 PWM 信號,而這樣的信號由定時器經(jīng)過查表產(chǎn)生。這個表的數(shù)據(jù)存儲在程序中的數(shù)組 indexWave中。
uint8_t indexWave[] = {1,1,2,2,3,4,6,8,10,14,19,25,33,44,59,80,
107,143,191,255,255,191,143,107,80,59,44,33,25,19,14,10,8,6,4,3,2,2,1,1};
這個表有 40 個數(shù)字,從圖中可以看到這些數(shù)字呈指數(shù)上升再衰減,正好是呼吸燈的一個控制周期。數(shù)字的大小范圍是 0255,即把 LED 的亮度分為了 0255 個等級。
假如我們把定時器的脈沖計數(shù)器 CNT 上限設置為 255,把這個表的數(shù)據(jù)一個一個地賦值到定時器的比較寄存器CH2CV中,那么在每個 PWM 周期中,當 CNT的計數(shù)值小于比較寄存器 CH2CV的值時, 就會在通道中輸出低電平,點亮 LED,而隨著 CCR 的值由 LED 亮度表得來,所以 LED 點亮的時間就會呈圖中的曲線變化,實現(xiàn)呼吸燈的功能。
這個表的數(shù)據(jù)是使用 matlab 軟件生成的。該代碼運行后會生成一個“index_wave.c”的文件,用戶把該文件中的數(shù)據(jù)復制到工程中的數(shù)組中即可。
%本代碼用于產(chǎn)生呼吸燈使用的指數(shù)函數(shù)數(shù)據(jù)
clear;
x = [0 : 8/19 : 8]; %設置序列 ,指數(shù)上升
up = 2.^x ; %求上升指數(shù)序列
up = uint8(up); %化為8位數(shù)據(jù)
y = [8: -8/19 :0]; %設置序列 ,指數(shù)下降
down = 2.^y ; %求下降指數(shù)序列
down = uint8(down); %化為8位數(shù)據(jù)
line = [[0:8/19:8],[8:8/19:16]] %拼接序列
val = [up , down] %拼接輸出序列
dlmwrite('index_wave.c',val); %輸出到文件index_wave.c
plot(line,val,'.'); %顯示波形圖
2.初始化 GPIO
這部分和前面的一樣,沒啥好說的。
3.配置定時器模式
這里也差不多,只是將分頻系數(shù)設置的稍微大些,另外開啟了中斷。
/*
brief configure the Breath LED peripheral
param[in] none
param[out] none
retval none
*/
void breath_led_init(void)
{
/* TIMER2 configuration: generate PWM signals with different duty cycles*/
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
/* configure the GPIO ports */
timer_gpio_init();
rcu_periph_clock_enable(RCU_TIMER2);
timer_deinit(TIMER2);
/* TIMER2 configuration */
timer_initpara.prescaler = 3999;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 255;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER2, &timer_initpara);
/* CH2 configuration in PWM mode 0 */
timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
timer_channel_output_config(TIMER2, TIMER_CH_2, &timer_ocintpara);
/* CH2 configuration in PWM mode 0,duty cycle 25% */
timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_2, 0);
timer_channel_output_mode_config(TIMER2, TIMER_CH_2, TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER2, TIMER_CH_2, TIMER_OC_SHADOW_DISABLE);
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER2);
/* Timer2 interrupt setting, preemptive priority 0, sub-priority 2 */
nvic_irq_enable(TIMER2_IRQn, 0, 2);
/* Enable Timer2 update interrupt */
timer_interrupt_enable(TIMER2, TIMER_INT_UP);
/* TIMER2 enable */
timer_enable(TIMER2);
}
配置好中斷,下面就要編寫中斷服務函數(shù)。
/*!
\\brief this function handles TIMER2 exception
\\param[in] none
\\param[out] none
\\retval none
*/
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) //若輸出的周期數(shù)大于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ù)標志
}
}
}
本中斷服務函數(shù)在每次定時器更新事件發(fā)生時執(zhí)行一次(即 256 個定時器時鐘周期)。函數(shù)中使用了靜態(tài)變量 pwm_index 和 period_cnt,它們分別用來查找 PWM 表元素和記錄同樣占空比的脈沖輸出了多少次。
本代碼的目的是每 10 次定時器中斷更新一次 PWM 表中的數(shù)據(jù)到比較寄存器中,當遍歷完 PWM 表的 40 個元素時,再重頭開始遍歷 PWM 表,周而復始,重復 LED 的呼吸過程。
整個呼吸過程的時間計算方法如下:
因為定時器的 prescaler 設置為 3999;
所以定時器的時鐘頻率:fTIMER = 120000000/(prescaler+1) = 30000 Hz
即定時器的時鐘周期為:tTIMER = 1/fTIMER = 1/30000 s
因為定時器的 period 設置為 255;
所以定時器的中斷周期為:tint= tTIMER * (period+1) =0.00753s
因為 PWM 表有 pwm_index = 40 個亮度占空比數(shù)據(jù),同種占空比信號輸出 period_cnt =10 次
所以一個呼吸周期 T = tint *40 *10 = 3.41s
3 呼吸燈的實驗現(xiàn)象
將程序編譯好下載到板子中,將PF6接到PB0上,可一看到LED1像呼吸一樣漸漸變明或者漸漸變暗,但是方法二明顯比方法一更流暢,效果更好。
-
Cortex-M
+關注
關注
2文章
227瀏覽量
29709 -
呼吸燈
+關注
關注
10文章
109瀏覽量
42688 -
GD32
+關注
關注
7文章
403瀏覽量
24113
發(fā)布評論請先 登錄
相關推薦
評論