【先楫HPM6750测评】PWM控制蜂鸣器发声
<p> </p><p>本篇将介绍如何使用HPM6750输出PWM信号,并使用PWM信号驱动蜂鸣器发声。</p>
<h2>PWM简介</h2>
<p>PWM 的全称是 脉冲宽度调制 ( Pulse-width modulation ),常用于模拟设备的控制,例如驱动舵机、电机等等,以及控制LED的亮度,控制无源蜂鸣器发声等等。</p>
<h2>HPM6750EVKMINI蜂鸣器相关电路分析</h2>
<p>通过查阅开发板原理图,可以找到蜂鸣器相关电路原理图如下:</p>
<p></p>
<p>可以看到:</p>
<ul>
<li>蜂鸣器BZ1是无源蜂鸣器</li>
<li>PWM3.P4引脚输出高电平时,有电流通过蜂鸣器</li>
<li>PWM3.P4引脚,也是HPM6750芯片的PE05引脚</li>
</ul>
<h2>HPM6750芯片的PWM模块及编程接口</h2>
<h3>HPM6750芯片PWM模块简介</h3>
<p>在《HPM6750 系列超高性能微控制器用户手册》(HPM6750_UM__V1_0.pdf文件)中,我们可以找到HPM6750芯片PWM模块的介绍。</p>
<p></p>
<p>HPM6750手册中PWM模块相关的介绍前后一共有二十几页,如果需要使用HPM6750芯片的PWM功能的话,建议耐心读完这个章节,以及hpm_pwm_drv.h和hpm_pwm_drv.c的代码。因为HPM SDK的PWM编程接口和STM32的HAL编程接口差别较大,如果不阅读这部分文档,可能会对PWM接口有无从下手的感觉。</p>
<p>PWM模块框图:</p>
<p></p>
<p>可以看到,PWM整体上由时间基准、比较器、输出通道、输出控制几个部分组成。</p>
<p>对于每一路PWM波形,主要由计数寄存器(XCNT)、起始寄存器(XSTA)、重载寄存器(XRLD)、比较寄存器(XCMP)控制。具体细节参见HPM6750用户手册相关章节,这里不作详细介绍。</p>
<p>手册中的一个PWM生成例子:</p>
<p></p>
<h3>HPM SDK中PWM API接口简介</h3>
<p>HPM SDK文档中可以找到PWM相关的API介绍,具体文件为:sdk_env_v0.9.0/hpm_sdk/doc/output/api_doc/html/group__pwm__interface.html</p>
<p>这个文档是从源码生成的,内容和hpm_pwm_drv.h中的注释是一致的,如下图所示:</p>
<p></p>
<h2>使用HPM6750输出PWM信号</h2>
<p>有了上面的知识之后,我们就可以开始用HPM6750输出PWM信号,驱动蜂鸣器发声了。</p>
<h3>设置蜂鸣器控制引脚为PWM功能</h3>
<p>要驱动蜂鸣器发声,首先我们需要将蜂鸣器的控制引脚PE05设置为PWM功能。</p>
<p>通过翻阅SDK代码,可以发现SDK中已经实现了将蜂鸣器引脚设置为PWM的函数:</p>
<ul>
<li>board.h和board.c中的board_init_beep_pwm_pins()函数;</li>
<li>pinmux.h和pinmux.c中的init_beep_pwm_pins()函数;</li>
</ul>
<p>这两个函数的实现都比较简单,我们后续的程序中直接调用即可。</p>
<pre>
<code>void board_init_beep_pwm_pins(void)
{
init_beep_pwm_pins();
}
void init_beep_pwm_pins(void)
{
HPM_IOC->PAD.FUNC_CTL = IOC_PE05_FUNC_CTL_PWM3_P_4;
}
</code></pre>
<h3>输出PWM波函数</h3>
<p>接下来我们实现如下函数:</p>
<pre>
<code>void gen_pwm_to_beep(uint32_t pwm_freq, uint32_t duration_ms);
</code></pre>
<p>两个参数分别为:</p>
<ul>
<li>pwm_freq是PWM输出波形的频率</li>
<li>duration_ms是输出的持续时间</li>
</ul>
<p>实现这个函数需要使用到HPM SDk中的一些函数,这些函数及其功能如下:</p>
<ul>
<li>pwm_get_default_pwm_config 填充 pwm_config_t 默认值</li>
<li>pwm_get_default_cmp_config 填充 pwm_cmp_config_t 默认值</li>
<li>pwm_set_reload 设置RLD寄存器</li>
<li>pwm_set_start_count 设置STA寄存器</li>
<li>pwm_setup_waveform 使用给定参数对PWM通道进行设置</li>
<li>pwm_start_counter 开始计数</li>
<li>pwm_issue_shadow_register_lock_event 锁定影子寄存器</li>
<li>pwm_disable_output 停止输出PWM信号</li>
</ul>
<h3>完整蜂鸣器发声代码</h3>
<p>好了,了解了上面这些背景知识之后,就可以实现使用PWM驱动蜂鸣器发声了,完整代码如下:</p>
<pre>
<code>#include <stdio.h>
#include "board.h"
#include "hpm_pwm_drv.h"
#define BEEP_PWM BOARD_BEEP_PWM
#define BEEP_OUT BOARD_BEEP_PWM_OUT
#define BEEP_CLK BOARD_BEEP_PWM_CLOCK_NAME
void gen_pwm_to_beep(uint32_t pwm_freq, uint32_t duration_ms)
{
uint8_t cmp_index = 0;
bool increase_duty_cycle = true;
pwm_cmp_config_t cmp_config = {0};
pwm_config_t pwm_config = {0};
uint32_t clk_freq = 0;
uint32_t pwm_reload = 0;
// 计算reload值
// 因为, pwm_freq = clk_freq / (reload + 1)
// 所以, reload = clk_freq / pwm_freq - 1
clk_freq = clock_get_frequency(BEEP_CLK);
pwm_reload = clk_freq / pwm_freq - 1;
printf("clk_freq = %d, pwm_reload = %d\\n", clk_freq, pwm_reload);
// 准备pwm_config
pwm_get_default_pwm_config(BEEP_PWM, &pwm_config); // 填充 pwm_config_t 默认值
pwm_config.enable_output = true;
pwm_config.dead_zone_in_half_cycle = 0;
pwm_config.invert_output = false;
// 准备cmp_config
pwm_get_default_cmp_config(BEEP_PWM, &cmp_config); // 填充 pwm_cmp_config_t 默认值
cmp_config.mode = pwm_cmp_mode_output_compare; // 设置PWM工作模式为输出
cmp_config.cmp = pwm_reload / 2;// 设置初始CMP值,这样直接设置为 1/2 则后续不需要更新即可生成 50% 占空比
// cmp_config.cmp = pwm_reload + 1; // CMP > RLD, 由于计数器值 CNT 始终达不到 CMPx,比较器输出 OCx 会保持逻辑 0
cmp_config.update_trigger = pwm_shadow_register_update_on_modify; // 设置CMP影子寄存器值生效时刻为 更新后的下一个周期
// pwm_shadow_register_update_on_modify 这种方式下一个指令周期就会重装CMP,可能会导致PWM波形不完整,手册不推荐这种方式
//pwm_stop_counter(BEEP_PWM); // 停止计数(没有也可以)
pwm_set_reload(BEEP_PWM, 0, pwm_reload); // 设置RLD寄存器
pwm_set_start_count(BEEP_PWM, 0, 0); // 设置STA寄存器
// 使用给定参数对PWM通道进行设置
if (status_success != pwm_setup_waveform(BEEP_PWM, BEEP_OUT, &pwm_config, cmp_index, &cmp_config, 1)) {
printf("failed to setup waveform\\n");
while(1);
}
pwm_start_counter(BEEP_PWM); // 开始计数(PWM输出开始)
pwm_issue_shadow_register_lock_event(BEEP_PWM); // 锁定影子寄存器
// 和 cmp = pwm_reload + 1 一起使用,也可以得到 50% 占空比的 PWM波形
// 在这里更新CMP影子寄存器,下一个周期CMP寄存器会得到更新,这种方式便于动态更新PWM波形
// pwm_update_raw_cmp_edge_aligned(BEEP_PWM, cmp_index, pwm_reload / 2); // 50 % HIGH
board_delay_ms(duration_ms); // 按参数延时一段时间,这段时间一直会由PWM波形输出
pwm_disable_output(BEEP_PWM, BEEP_OUT); // 停止输出PWM信号
}
int main(void)
{
board_init();
board_init_beep_pwm_pins();
printf("pwm_beep start!\\n");
printf("Generate PWM waveform to BEEP...\\n");
gen_pwm_to_beep(440, 1000); // 440Hz, 1秒
printf("pwm_beep done!\\n");
while(1);
return 0;
}
</code></pre>
<p>代码里以及加了很多注释,这里就不作详细解释了。当然,更多细节需要参考HPM6750用户手册里面的寄存器说明,以及hpm_pwm_drv.h里面的注释。</p>
<p>效果演示:</p>
<p>d330c6792b4bef34e1a705a733d6ee67<br />
</p>
弄点音乐出来玩玩就好了。 <p>你的东方红咋没放上,哈哈</p>
<p>来放个葫芦娃</p>
<p>用HPM6750蜂鸣器播放《东方红》的视频:</p>
<p><iframe allowfullscreen="true" frameborder="0" height="450" src="//player.bilibili.com/player.html?bvid=1Ya411j7aB&page=1" style="background:#eee;margin-bottom:10px;" width="700"></iframe><br />
</p>
lugl4313820 发表于 2022-5-29 18:51
弄点音乐出来玩玩就好了。
<p>弄了个《东方红》</p>
nmg 发表于 2022-5-30 10:55
你的东方红咋没放上,哈哈
<p>放上来了</p>
xusiwei1236 发表于 2022-6-4 21:39
用HPM6750蜂鸣器播放《东方红》的视频:
<p>优秀!找到当时中国的东方红的感觉!</p>
页:
[1]