0

I'm trying to synthesize sound on the Arduboy, which is a handheld gaming device with an AVR ATMega32u4 microcontroller and a speaker attached between its pins C6 and C7.

My plan is to use timer 4 to generate a high-frequency PWM signal on C7, and then use timer 3 to change timer 4's duty cycle. For a "hello world"-level program, I'm trying to read 3906 8-bit samples per second from PROGMEM.

First of all, to make sure my sample file is really in the format I think it is, I have used SoX to play it on a computer:

$ play -e unsigned-integer -r 3906 -b 8 sample2.raw 

Here's the relevant parts of my code:

pub fn setup() {
    without_interrupts(|| {
        PLLFRQ::unset(PLLFRQ::PLLTM1);
        PLLFRQ::set(PLLFRQ::PLLTM0);
        TCCR4A::write(TCCR4A::COM4A1 | TCCR4A::PWM4A); // Set output C7 to high between 0x00 and OCR4A
        TCCR4B::write(TCCR4B::CS40); // Enable with clock divider of 1
        TCCR4C::write(0x00);
        TCCR4D::write(0x00);
        TC4H::write(0x00);
        OCR4C::write(0xff); // One full period = 256 cycles
        OCR4A::write(0x00); // Duty cycle = OCR4A / 256

        TCCR3B::write(TCCR3B::CS32 | TCCR3B::CS30); // Divide by 1024
        OCR3A::write(3u16); // 4 cycles per period => 3906 samples per second
        TCCR3A::write(0);
        TCCR3B::set(TCCR3B::WGM30); // count up to OCR3A
        TIMSK3::set(TIMSK3::OCIE3A); // Interrupt on OCR3A match

        // Speaker
        port::C6::set_output();
        port::C6::set_low();
        port::C7::set_output();
    });
}

progmem_file_bytes!{
    static progmem SAMPLE = "sample2.raw"
}

// TIMER3_COMPA
#[no_mangle]
pub unsafe extern "avr-interrupt" fn __vector_32() {
    static mut PTR: usize = 0;

    // This runs at 3906 Hz, so at each tick we just replace the duty cycle of the PWM
    let sample: u8 = SAMPLE.load_at(PTR);
    OCR4A::write(sample);
    PTR += 1;
    if PTR == SAMPLE.len() {
        PTR = 0;
    }
}

The basic problem is that it just doesn't work: instead of hearing the audio sample, I just hear garbled noise from the speaker.

Note that it is not "fully wrong", there is some semblance of the intended operation. For example, I can hear that the noise has a repeating structure with the right length. If I set the duty cycle sample to 0 when PTR < SAMPLE.len() / 2, then I can clearly hear that there is no sound for half of my sample length. So I think timer 3 and its interrupt handler are certainly working as intended.

So this leaves me thinking either I am configuring timer 4 incorrectly, or I am misunderstanding the role of OCR4A and how the duty cycle needs to be set, or I could just have a fundamentally wrong understanding of how PWM-based audio synthesis is supposed to work.

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • 1
    What frequency is the "high frequency" timer 4 set to be? (If it is too low (too close to audible range), that would explain the problem.) – Kevin Reid Jan 17 '23 at 16:39
  • @KevinReid the MCU runs at 16 MHz, the prescaler is set to 1, and the counter goes to 255, so it should be 16 MHz / 256 = 62.5 KHz. For testing purposes, I tried cranking up the prescaler to 8 or 16 and then I can hear the carrier. – Cactus Jan 18 '23 at 00:44
  • @KevinReid actually, I forgot that I'm using the "fast PWM" in the code sample, so it should run the pwm at 64 MHz / 256 = 250 KHz, so even more safely inaudible. – Cactus Jan 18 '23 at 00:46

1 Answers1

-2

Irrespective of the code you have written above, it seems your design suffers from several flaws:

a) speakers require alternating current to flow through it; it is achieved by varying the tension between a positive and a negative voltage. In your design, the output of your PWM is likely to be between 0 and 5V. If your PWM has a duty cycle of 50%, it turns into an average offset voltage of 2.5V constantly applied to your speaker. This could be fatal to it (the saving grace potentially being flaw 3 below, i.e. not enough current). A common way to fix this is to cut off DC current by connecting the speaker through a capacitor, to filter out any DC current. The value of the capacitor must be adjusted such as it doesn't filter out the signal you wish to use on low frequencies.

b) you shouldn't connect a speaker directly to your microcontroller PINs, for several reasons:

  1. Your microcontroller is likely the expensive device of your board, and should be preserved from devices malfunctions (like shortcuts) that could lead to destroy it.

  2. Your microcontroller can drive max 40mA per io PIN, as described in datasheet, 29.1 Absolute Maximum Ratings . A typical speaker has a resistance varying between 8 Ohms and 32 Ohms, and if we consider a duty cycle of 50%, you are passing in average 20mA across it. To drive your HP efficiently, you would need much more current to flow through. That's why speakers are generally driven by an amplifier stage.

  3. Speakers are devices with coils inside. As such, they store magnetic energy that they yield back to the circuit when the voltage drops, injecting reverse currents. These reverse currents can easily destroy transistor gates if not properly handled. In your case, luckily, the amount of inverse current is low, since you don't drive the HP enough. But you should avoid that situation at all times.

c) Using a PWM to produce sound seems awkward. Since they produce square waves , these are plenty of harmonics that will add a lot of parasitic noises to the sound. But more importantly, PWM output does not allow you to vary the intensity (voltage) of the output, which is normally needed to reproduce sound properly.

To produce sound, you need an digital to analogue converter (DAC), then the proper circuitry to condition that signal and amplify it for a speaker. Unfortunately, the ATMega32u4 doesn't seem to have that feature. You should pick another microcontroller.

For questions on electronic designs, I suggest you to search on https://electronics.stackexchange.com.

keldonin
  • 314
  • 1
  • 10
  • 1
    This is not accurate. The speaker of the Arduboy and many other microcontroller devices is a _piezo_ speaker, not a magnetic coil speaker; it is inherently capacitor-like and will not be harmed by DC offset or present a short-circuit load. Additionally, the reactance and physical mass of either kind of speaker will act as a low-pass filter on the signal, so feeding **sufficiently high frequency** PWM to it is a possible way to produce audio, though not ideal — the filtering effectively converts PWM to voltage. – Kevin Reid Jan 17 '23 at 21:11
  • OK, wrong assumption from me, although you didn't specify it is a piezo in your question. Agreed also for the second remark, even if that becomes a moot point. – keldonin Jan 17 '23 at 22:00