PWM(脉冲宽度调制)可用于电机的调速、LED的亮度调节、无源蜂鸣器输出音调等,是嵌入式系统开发中经常采用的方法。本期内容以一个用无源蜂鸣器播放音乐的例子,带领大家了解使用定时器PWM输出功能的方法。
系统环境
Windows 10-64bit
软件平台
NucleiStudio IDE 202102版
或 PlatformIO IDE
硬件需求
RV-STAR开发板
无源蜂鸣器
脉冲宽度调制
脉冲宽度调制(Pulse Width Modulation,PWM)是利用微控制器的数字输出来对模拟电路进行控制的一种非常有效的技术。
PWM的控制方式就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期中产生多个脉冲,使各脉冲的等值电压为正弦波形,所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制,既可改变逆变电路输出电压的大小,也可改变输出频率。其中,一个周期内脉冲时间占总时间的比值称为占空比。
简言之,如果你想让电机的速度变慢或者控制的LED变暗,只需降低PWM的占空比,这使得原本需要控制模拟电压或电流才能达到的效果,通过数字方式也能够实现,这样可以大幅度降低系统的成本和功耗。
GD32VF103的定时器
GD32VF103的定时器分为三种类型,分别是基本定时器、通用定时器和高级定时器,其中普通定时器和高级定时器都支持PWM输出功能,在本次的实验中使用TIMER2(普通定时器)来进行PWM输出实验。
实验部分
首先通过NucleiStudio或者PlatformIO等开发工具创建工程,然后在工程目录中添加“tone.h”和“main.c”文件,然后开始进行代码的编写。
#define NTD0 -1
#define NTD1 293
#define NTD2 329
#define NTD3 368
#define NTD4 390
#define NTD5 438
#define NTD6 492
#define NTD7 554
#define NTDL1 147
#define NTDL2 166
#define NTDL3 185
#define NTDL4 196
#define NTDL5 221
#define NTDL6 248
#define NTDL7 278
#define NTDH1 585
#define NTDH2 657
#define NTDH3 700
#define NTDH4 781
#define NTDH5 882
#define NTDH6 990
#define NTDH7 1112
然后,在“main.c”中,我们要实现三个函数:
void buzzer_init()
{
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
rcu_periph_clock_enable(RCU_TIMER2);
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
}
void buzzer_on(int freq)
{
timer_oc_parameter_struct timer_ocinitpara;
timer_parameter_struct timer_initpara;
timer_deinit(TIMER2);
/* initialize TIMER init parameter struct */
timer_struct_para_init(&timer_initpara);
/* TIMER2 configuration */
timer_initpara.prescaler = 107;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 1000000 / freq;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER2, &timer_initpara);
/* initialize TIMER channel output parameter struct */
timer_channel_output_struct_para_init(&timer_ocinitpara);
/* CH3 configuration in PWM mode */
timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;
timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE;
timer_ocinitpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_ocinitpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
timer_ocinitpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
timer_channel_output_config(TIMER2, TIMER_CH_3, &timer_ocinitpara);
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER2);
timer_enable(TIMER2);
/* CH3 configuration in PWM mode1,duty cycle 50% */
timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_3, 500000 / freq);
timer_channel_output_mode_config(TIMER2, TIMER_CH_3, TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER2, TIMER_CH_3, TIMER_OC_SHADOW_DISABLE);
}
第三个函数buzzer_off(),这个比较简单,只需复位定时器即可。
void buzzer_off()
{
timer_deinit(TIMER2);
}
int notes[] = {
NTD1, NTD1, NTD5, NTD5,
NTD6, NTD6, NTD5,
NTD4, NTD4, NTD3, NTD3,
NTD2, NTD2, NTD1
};
int beats[] = {
1, 1, 1, 1,
1, 1, 2,
1, 1, 1, 1,
1, 1, 2
};
int main()
{
buzzer_init();
int length = sizeof(notes) / sizeof(notes[0]);
while (1) {
for (int i = 0; i < length; i++) {
buzzer_on(notes[i]);
delay_1ms(500 * beats[i]);
buzzer_off();
}
delay_1ms(1000);
}
}
完成代码编写后,将无源蜂鸣器的(+)引脚接到RV-STAR开发板的PB5引脚上,另一个引脚接地,然后编译、上传,就可以听到蜂鸣器播放“小星星”啦~