xusiwei1236 发表于 2022-5-28 22:13

【先楫HPM6750测评】PWM控制蜂鸣器发声

<p>&nbsp;&nbsp;&nbsp;&nbsp;</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-&gt;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 &lt;stdio.h&gt;
#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, &amp;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, &amp;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 &gt; 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, &amp;pwm_config, cmp_index, &amp;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 />
&nbsp;</p>

lugl4313820 发表于 2022-5-29 18:51

弄点音乐出来玩玩就好了。

nmg 发表于 2022-5-30 10:55

<p>你的东方红咋没放上,哈哈</p>

freebsder 发表于 2022-5-31 23:43

<p>来放个葫芦娃</p>

xusiwei1236 发表于 2022-6-4 21:39

<p>用HPM6750蜂鸣器播放《东方红》的视频:</p>

<p><iframe allowfullscreen="true" frameborder="0" height="450" src="//player.bilibili.com/player.html?bvid=1Ya411j7aB&amp;page=1" style="background:#eee;margin-bottom:10px;" width="700"></iframe><br />
&nbsp;</p>

xusiwei1236 发表于 2022-6-4 21:41

lugl4313820 发表于 2022-5-29 18:51
弄点音乐出来玩玩就好了。

<p>弄了个《东方红》</p>

xusiwei1236 发表于 2022-6-4 21:41

nmg 发表于 2022-5-30 10:55
你的东方红咋没放上,哈哈

<p>放上来了</p>

lugl4313820 发表于 2022-6-4 21:45

xusiwei1236 发表于 2022-6-4 21:39
用HPM6750蜂鸣器播放《东方红》的视频:


&nbsp;

<p>优秀!找到当时中国的东方红的感觉!</p>
页: [1]
查看完整版本: 【先楫HPM6750测评】PWM控制蜂鸣器发声