0

I'm trying to translate the following code from the Arduino IDE Into Rust using the avr_hal crate to make a passive buzzer play notes:

#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);
}

I can't figure out how to use a Pwm pin to set the duty and frequency as it only exposes methods to set the duty.

#![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);
    }
}

I'm just starting to learn arduino and electronics in general and honestly I don't understand 100% how the tone function works under the hood.

I would appreciate to get an answer that explains to me how that function works as well as helping me understand the core concepts :D

Jmb
  • 18,893
  • 2
  • 28
  • 55
JasterV
  • 17
  • 5

1 Answers1

0

I achieved the functionality you are looking for. It's not a very elegant solution, and you can no longer use TIMER1, but it's something.

#![no_std]
#![no_main]
#![feature(abi_avr_interrupt)]

use arduino_hal::{
    hal::port::Dynamic,
    port::{mode::Output, Pin},
};
use panic_halt as _;
use core::cell;

static BUZZER: avr_device::interrupt::Mutex<cell::Cell<Option<Pin<Output, Dynamic>>>> =
    avr_device::interrupt::Mutex::new(cell::Cell::new(None));

#[arduino_hal::entry]
fn main() -> ! {
    let dp = arduino_hal::Peripherals::take().unwrap();
    let pins = arduino_hal::pins!(dp);
    //let mut serial = arduino_hal::default_serial!(dp, pins, 57600);

    // Timer Configuration:
    // - WGM = 4: CTC mode (Clear Timer on Compare Match)
    // - Prescaler 256
    // - OCR1A = 31249
    //
    // => F = 16 MHz / (256 * (1 + 237.89)) = 261.626 Hz 
    //     (^ this formula I deduced from reading the datasheet)
    //
    let tmr1 = dp.TC1;
    tmr1.tccr1a.write(|w| w.wgm1().bits(0b00));
    tmr1.tccr1b
        .write(|w| w.cs1().prescale_256().wgm1().bits(0b01));

    // Enable the timer interrupt
    tmr1.timsk1.write(|w| w.ocie1a().set_bit());

    let mut buzzer = pins.d2.into_output().downgrade();
    buzzer.set_low();

    unsafe {
        avr_device::interrupt::enable();
    }

    avr_device::interrupt::free(|cs| BUZZER.borrow(cs).replace(Some(buzzer)));

    loop {
        tmr1.ocr1a.write(|w| w.bits(238));
        tmr1.tcnt1.write(|w|{ w.bits(0) });
        arduino_hal::delay_ms(1000);

        tmr1.ocr1a.write(|w| w.bits(212));
        tmr1.tcnt1.write(|w|{ w.bits(0) });
        arduino_hal::delay_ms(1000);

        tmr1.ocr1a.write(|w| w.bits(189));
        tmr1.tcnt1.write(|w|{ w.bits(0) });
        arduino_hal::delay_ms(1000);

        tmr1.ocr1a.write(|w| w.bits(178));
        tmr1.tcnt1.write(|w|{ w.bits(0) });
        arduino_hal::delay_ms(1000);

        tmr1.ocr1a.write(|w| w.bits(159));
        tmr1.tcnt1.write(|w|{ w.bits(0) });
        arduino_hal::delay_ms(1000);

        tmr1.ocr1a.write(|w| w.bits(141));
        tmr1.tcnt1.write(|w|{ w.bits(0) });
        arduino_hal::delay_ms(1000);

        tmr1.ocr1a.write(|w| w.bits(126));
        tmr1.tcnt1.write(|w|{ w.bits(0) });
        arduino_hal::delay_ms(1000);

    }
}

#[avr_device::interrupt(atmega328p)]
fn TIMER1_COMPA() {
    avr_device::interrupt::free(|cs| {
        if let Some(mut x) = BUZZER.borrow(cs).take() {
            x.toggle();

        BUZZER.borrow(cs).replace(Some(x));
        }
    })
}

Explaining a little bit, after setting up the timer, the tone change is done setting the frecuence of the timer-interruptions. The next line is a timer reset, be cause if not done, the program will halt for a moment at the 10th tone (no idea why)

tmr1.ocr1a.write(|w| w.bits(238));
tmr1.tcnt1.write(|w|{ w.bits(0) });

If you want to stop the buzzer, just set the frecuency to an impossible to hear one.

JM4000
  • 16
  • 1
  • Man thank you so much, you are a genius, I will try this as soon as possible as I'm travelling at the moment, thank you so much! – JasterV Jul 08 '23 at 21:23