Microcontroller Class D Amplifier, Rev2

11 March 2012

A problem of the first revision of this amplifier was the limited precision at low volumes. Although the PWM of the ATtiny45 is just 8-bit, I realized that it can produce arbitrary precision output. Timer1 can be clocked at 64 MHz from the high frequency PLL, which combined with the double-buffered counter compare value and the overflow interrupt, can get 9 and even 10-bit precision in the audible range. Lets say we want to DAC the value 300/512. We set OCR1A to 255 (which will hold the output high) and on the timer overflow, we write 300-255 = 45. Writing this 9-bit value takes 2 timer cycles.

I made a few other small improvements:

  • Replaced the BJTs with MOSFETs. This reduces ADC noise by removing the loads at the output pins of the MCU and also drops the component count because we no longer need the base resistors.
  • Added a filtering capacitor at the ADC ARef port (which is now available after replacing timer0 with timer1). This allows us to use the internal 2.54V ARef voltage for 2.54Vpp input.
  • Added a better output LC filter to protect the speakers from switching noise.

I experimented with 2 output stage driving strategies:

  • 0% duty cycle at idle. This is the most efficient driving method because only one side of the H-bridge is switching per cycle. It didn't work too well in practice because at low volumes we might get just 1 pulse in 512. The raise and fall times of the drivers were not fast enough for such a short pulse introducing error (this pulse has too high frequency harmonics for my implementation).
  • 50% duty cycle at idle. It might seem as a waste, but note that at idle both sides raise and fall simultaneously, so no current goes through the speaker. A small downside is a little power loss at the cross-over Vgs of the power MOSFETs. This strategy sounds perceivably better, though.

Dithering is no longer required, but still the output interrupt got a bit longer.

// 2x 9-bit PWM. 50% duty cycle at idle.
u8  time;
u16 pos;
u16 neg;

ISR(TIMER1_OVF_vect) {
    if(1 & ++time) { // every odd cycle
        u16 adc  = ADC;
        s16 val1 = (adc >> 1) - 256;
        u8  val2 = (u8)(adc & 1);

        pos = 256 + val1 + val2;
        neg = 256 - val1;

        if(val1 >= 254) // prevent wrap-around
            pos = 510;

        if(val1 <= -255)
            neg = 510;

        if(pos > 255) {
            DACP = 255;
            pos -= 255;
        } else {
            DACP = pos;
            pos = 0;

        if(neg > 255) {
            DACN = 255;
            neg -= 255;
        } else {
            DACN = neg;
            neg = 0;
    } else { // every even cycle
        DACP = pos;
        DACN = neg;
where DACP and DACN are the two PWM registers.

The LEDs are all functionally required - I use them as zener diodes.

The following links contain a picture of the schematic and the eagle schematic + C source code.

Circuit diagram.

See the first revision for a video.

Update May 2014

I had a few hours to revisit the amplifier. I replaced the LEDs with zener diodes, exchanged the resistors to clean up the switching characteristics, and replaced the filtering inductor to reduce it's resistance. It works more efficiently now (FETs aren't as warm) and is more powerful (less energy lost in the small inductors I had before).

Comments for Microcontroller Class D Amplifier, Rev2

Post a new comment

Name: Comment:
3 + 2:
3 characters word: