You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

avr_hal中Arduino tone()函数的等效实现及Rust代码改写

问题描述

尝试将Arduino IDE中的C代码转换为使用avr_hal crate的Rust代码,驱动无源蜂鸣器播放音符,但不清楚如何通过PWM引脚设置频率(当前仅能设置占空比),同时希望了解Arduino tone()函数的底层工作原理。

原Arduino C代码:

#include "pitches.h"

// notes in the melody:
int melody[] = {
  NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5, NOTE_C6};
int duration = 500;  // 500 miliseconds
 
void setup() {
 
}
 
void loop() {  
  for (int thisNote = 0; thisNote < 8; thisNote++) {
    // pin8 output the voice, every scale is 0.5 sencond
    tone(8, melody[thisNote], duration);
     
    // Output the voice after several minutes
    delay(1000);
  }
   
  // restart after two seconds 
  delay(2000);
}

当前未完成的Rust代码:

#![no_std]
#![no_main]

mod pitches;

use arduino_hal::simple_pwm::{IntoPwmPin, Prescaler, Timer4Pwm};
use panic_halt as _;
use pitches::{NOTE_A5, NOTE_B5, NOTE_C5, NOTE_C6, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5};

#[arduino_hal::entry]
fn main() -> ! {
    let dp = arduino_hal::Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);

    let timer = Timer4Pwm::new(dp.TC4, Prescaler::Prescale8);

    let mut buzzer = pins.d8.into_output().into_pwm(&timer);

    // notes in the melody:
    let melody: [isize; 8] = [
        NOTE_C5, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5, NOTE_A5, NOTE_B5, NOTE_C6,
    ];

    loop {
        melody.iter().for_each(|note| {
            // TODO: How do I use the PWM buzzer output here???
            arduino_hal::delay_ms(1000);
        });

        arduino_hal::delay_ms(2000);
    }
}

Arduino tone()函数底层原理科普
  • 核心逻辑:tone()通过AVR单片机的定时器模块生成指定频率的50%占空比方波,驱动无源蜂鸣器振动发声。无源蜂鸣器本身没有振荡源,必须依赖外部交替变化的电平才能振动。
  • 底层实现:
    1. 配置定时器的预分频值,把系统时钟(通常16MHz)分频到定时器可处理的范围;
    2. 根据目标频率计算定时器的匹配寄存器值,让定时器按指定周期触发引脚电平翻转;
    3. 当指定duration时,定时器会在对应时间后停止输出,引脚恢复到初始状态。
  • 关键概念:
    • 频率:音符的音调由频率决定,比如C5对应523Hz,意味着引脚每秒完成523次高低电平切换;
    • 50%占空比:方波的高低电平持续时间相等,这是驱动无源蜂鸣器的最优占空比,能让蜂鸣器振动效率最高;
    • 定时器预分频:系统时钟频率过高,直接用定时器无法生成低频信号,预分频就是把时钟频率降低,让定时器可以生成对应频率的周期信号。

Rust代码解决方案(基于avr_hal)

avr_hal的simple_pwm模块需要通过配置定时器的TOP值来设置输出频率,再配合50%占空比实现方波输出。以下是完整可运行代码:

#![no_std]
#![no_main]

mod pitches;

use arduino_hal::simple_pwm::{IntoPwmPin, Prescaler, Timer4Pwm};
use arduino_hal::Delay;
use panic_halt as _;
use pitches::{NOTE_A5, NOTE_B5, NOTE_C5, NOTE_C6, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G5};

#[arduino_hal::entry]
fn main() -> ! {
    let dp = arduino_hal::Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);
    let mut delay = Delay::new();

    // 系统时钟频率(Arduino Uno为16MHz)
    const F_CPU: u32 = 16_000_000;
    // 预分频值,和初始化定时器时的设置一致
    const PRESCALER: u32 = 8;

    // 初始化定时器4,启用PWM功能
    let mut timer = Timer4Pwm::new(dp.TC4, Prescaler::Prescale8);

    // 将D8引脚转为PWM输出,并设置50%占空比(方波)
    let mut buzzer = pins.d8.into_output().into_pwm(&timer);
    buzzer.set_duty(buzzer.max_duty() / 2);

    // 音符数组,转为u32类型用于频率计算
    let melody: [u32; 8] = [
        NOTE_C5 as u32, NOTE_D5 as u32, NOTE_E5 as u32, NOTE_F5 as u32,
        NOTE_G5 as u32, NOTE_A5 as u32, NOTE_B5 as u32, NOTE_C6 as u32,
    ];

    loop {
        for &freq in &melody {
            // 计算定时器TOP值,公式:(系统时钟 / (预分频 * 2 * 目标频率)) - 1
            let ocr = (F_CPU / (PRESCALER * 2 * freq)) - 1;
            // 设置定时器TOP值,决定PWM输出频率
            timer.set_top(ocr as u16);
            
            // 启用PWM输出,开始发声
            buzzer.enable();
            // 持续发声500ms,对应原C代码的duration
            delay.delay_ms(500u16);
            // 禁用PWM输出,停止发声
            buzzer.disable();
            // 等待剩余500ms,总间隔1000ms
            delay.delay_ms(500u16);
        }

        // 循环结束后等待2秒再重启
        delay.delay_ms(2000u16);
    }
}

代码关键点说明:

  1. TOP值计算:通过系统时钟、预分频和目标频率,算出定时器的周期匹配值,直接决定PWM输出的频率;
  2. 50%占空比设置:用max_duty()/2让引脚高低电平持续时间相等,生成标准方波;
  3. 发声/静音控制:通过enable()和disable()控制PWM输出,对应原C代码中tone()的发声和停止逻辑。

内容的提问来源于stack exchange,提问作者JasterV

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 18:23:22