Class D Amp With An H-Bridge

Class D amps are simple – just take an input, and use that to modulate a square wave with PWM. Send this PWM signal to a MOSFET or something, and you have the simplest class D amp in existence. They’re so simple, you can buy a class D amp chip for $3, but [George] thought that would be too easy. Instead, he built his own with an ATTiny and an H-bridge motor driver. No surprise, it works, but what’s interesting is what effect the code on the ATtiny can have on the quality of the audio coming out of the speaker.

The microcontroller chosen for this project was the ATtiny 461, a part we don’t see much, but still exactly what you’d expect from an ATtiny. The heavy lifting part of this build is an L298 chip found on eBay for a few dollars. This dual H-bridge is usually used for driving motors, but [George] found a home for it in the power section of an amplifier.

The ATtiny is clocked at 16 MHz, making the ADC clock run at 1 MHz. A 10-bit precision conversion takes place, and this value sets the PWM duty cycle. Timer1 in the chip is set up to run at 32 MHz, and by counting this timer up to 1023 gives this amp its PWM cycle speed of 31.25 kHz. That’s right in the neighborhood of what a class D amp should run at, and the code is only about 30 lines. It can’t get simpler than that.

[George] put up a video of this amp in operation, and despite not following the standard design of a Class D amp, it sounds pretty good. You can see that video below.

20 thoughts on “Class D Amp With An H-Bridge

  1. Now this is a proper hack, very nice work and well documented. I will leave it to some other expert to spew negative comments about audio fidelity, etc. I especially liked his comment “as the code got easier and smaller, the amp sounded better” a lesson for us all, perhaps.

  2. The code only shows a single PWM output, and I’m not quite sure what logic parts there would be on the L298 board, but I do know the L298 has rather long dead times (>1us), that they are asymmetrical (high-side to low-side dead time is not the same as the reverse), and that it will simply never switch either transistor on if the pulse width is too short (depending on the state of the IN and EN inputs). I don’t see anything in the code to correct for this dead time, so it might cause some cross-over distortion. A simple way to fix this is to connect the speaker between two half bridges, and supply both with a 50% signal at rest, and modulate those complementary.

    Remember that an offset in the ADC input will cause the same offset on the PWM output, which would cause a DC current through the speaker (bad), so it’s important to set the offset correctly before connecting the speaker.

    As for the code: the easier code will probably sound better, because it takes a fixed time to run. If the code between the reading of the ADC input and the setting of the PWM registers contains anything that runs in a variable execution time (branches, software emulated division, etc.) it would mess with the timing and sound bad.

    I’m thinking the sound quality, especially at the higher frequencies, could probably be improved by synchronizing the reading of the ADC with the PWM cycle. If I understand correctly, the ADC is free-running, so the delay between the ADC sample and the actual update of the PWM value is variable. Considering it doesn’t make sense to update the PWM value more than once for each PWM cycle, and the actual delay isn’t really important, as long as the variation is minimal, triggering the ADC with the PWM periferal might improve the sound quality a bit more.

    It’s hard to really get a feel for the sound quality in the video, but it doesn’t sound half bad, considering the setup and the fact that the L298 isn’t meant to do anything like this.

    1. Any thoughts on not filtering the output to the speaker? Seems that 31kHz would be just enough to be out of hearing-range, yet at high-power could be akin to staring at an IR-laser… Maybe more a concern for tiny headphone speakers which would be less-limited by momentum…?

      Anyone else notice this guy’s power-supply started up with “666” on the display?

      An aside, since you seem to know something about Free-Running and variable sample-times… I’ve been trying to google this shizzle for quite some time, I guess I don’t know what to look for. Do you have any references regarding this? According to the documentation from two different AVRs, the best I can determine is that 13 cycles is de-facto except for that first sample… Found out the hard way that wasn’t true. But can’t seem to figure out what to expect, nor anything specific about it.

      1. It’s been years since I used anything from Atmel (only TI and STM professionally for me now), but if I recall correctly, it was indeed 13 cycles of the ADC clock (which usually is derived from the system clock by a prescaler) if it is freerunning. Don’t forget there is also a delay of (up to) several cycles in the interrupt latency. Alternatively, you can trigger the ADC from the counter/timer periferals, or through software. If your experiment doesn’t match the datasheet, chances are you misunderstood the datasheet. Of course there can be errors (check the errata), but actual hardware bugs are very rare, especially for chips based on such old designs.

        I hadn’t even thought about the filtering. I’m not sure what the RL time constant of such a speaker would be, but I’d think it would be fairly short, compared to the 31kHz PWM frequency, meaning a filter would probably be a good idea. You could try just connecting an inductor in series to increase the time constant, but that would of course be detrimental to the high-frequency output.

        1. SparkyGSX: Thanks for taking the time to reply thoughtfully to what musta seemed like a newb question. Apologies, though, I must’ve totally misread your statement (something about being up for 24+ hours can do that, there needs to be an automatic keyboard-cut-off after 16ish hours). I guess I read: “the ADC is free-running, so the delay between the ADC sample … is variable.” I somehow thought that was regarding the 13-cycle measurement time, now I see your comment was regarding the difference between the PWM frequency and the ADC frequency, which makes more sense (especially considering how hard it’s been to find corroboration on the !=13 cycle thing). Makes yah wonder if there’s notable ‘beating’ (in the physics sense) introduced by their frequency-difference.

          The hunt continues for some sort of document or even a person who can verify that 13 cycles (after the first sample) is “normal [but not always]”. In case anyone’s wondering… my experiments suggest the Free-Running ADC takes a different number of cycles depending on the value it’s measuring (possibly the result of some sort of binary-search pattern?). Especially, if the value measures *zero*, the measurements, and therefore interrupts, nearly double in speed (so, like, 7 ADC cycles). If it weren’t for this factor, the ADC in free-running mode could be dual-purposed as a sort of timer-interrupt that always runs at F_CPU/(1<<n)/13; it still can be used as a roughly periodic interrupt, sans-timer, despite my experimental results, just not with that level of precision (and *hardly any* precision if the measurement is 0).

          Heh, you mention RL time constants and not having "even thought about"s… I've been making-use-of RC time-constants quite a bit recently, but somehow the idea of an RL time-constant had completely slipped my mind, even in thinking about this PWM-Frequency question. Thanks for jogging my memory. Contemplations-ahead.

    2. Sparky, the OC1A has a complimentary inverted output pin, so while one is on, the other is off and vice versa. So there is in fact 2 pwm outputs, albeit mono, that are 50% at rest (the pot on the board is to adjust the voltage bias to the input, the LED turns on when ADC reads half its maximum value).

      You are correct in that the switching times were very slow, which is why a version of this code where the duty cycle was 0% at rest did not work; it did in fact cause too much distortion.

      The reading of the ADC synced with the PWM cycle did not work, since the ADC runs off a much slower clock than the TIMER1. It was much more elegant to let the ADC run as fast (and as slow) as it needed to, and update the duty cycle OCR1A at its leisure, all this while TIMER1 focused on the PWM.

      1. Right, I didn’t see that anywhere; the schematic doesn’t show the output signals clearly (I guess it’s the MISO and MOSI pins), and apparently I missed that comment in the code. When driving motors, it’s common to keep one channel at 0% and only modulate the other channel to minimize the switching losses, but clearly you already found and fixed this.

        The thing about the ADC sampling is that the variation in the delay between the sampling and the actual updating of the PWM output value could cause some distortion in the higher frequencies, but it’s quite possible these are not (easily) audible. The PWM output value is double buffered, right? Such that the updated value is only copied into the actual compare register when the timer overflows?

        I’m assuming the ADC runs much faster than 31kHz, so why was there a problem triggering the ADC from the timer? You could also trigger it from software if you enabled the overflow interrupt of the timer, and use the ADC conversion complete interrupt to update the PWM value. Assuming that register is double buffered, the new value would only take effect after the next overflow.

        1. You’re right. ADC is completing conversion, in current setup, at 76.9khz, which is not needed if pwm only updates every ~32khz. Perhaps I should double ADC clock prescaler to increase accuracy. I need to revisit this.

          I’m still not sold on triggering ADC from timer, as that would require more code in main loop for something that could be done automatically, unless, as you say, it could cause distortion. You are correct that compare values are double buffered in PWM mode.

          1. Lots of focus on precision, in these comments… one consideration, of course, is that the ear is better at doing some sorts of filtering than a microcontroller. This might be interesting to yah: “Enhancing ADC resolution by oversampling.” I get the jist that multiple lower-precision samples could in fact be put together to create higher accuracy output.
            Maybe since you had to bump-down the PWM frequency to appease the H-Bridge, you could bump up the PWM resolution by another bit… the ADC’s sampling at roughly twice the PWM frequency, so with twice the samples, you’ve got twice the range… (just add two samples together, and feed that into the 16-bit timer counting to 2048).
            Like you said, the ADC clock prescaler is set a bit fast for most AVR’s; their highest precision sampling occurs at much lower frequencies; but that oversampling document might suggest that while the individual samples may be lacking in precision, the addition of more equally imprecise samples might roughly average that error out.

        2. Inside your PWM irq service routine, you could read the previous ADC to update the PWM, then immediately trigger a new ADC cycle before exiting the irq. No need to put code in the main loop, although you’ll need to initially start the ADC once – to prime the pump to get the first result there.

          Since the ADC conversion takes less time than the PWM period, by the time the next PWM irq arrives again, the ADC result would be in the register already. i.e. no need to poll or anything.

  3. “…this amp its PWM cycle speed of 31.25 kHz. That’s right in the neighborhood of what a class D amp should run at…”,

    As George mention it on his blog, D class amplifiers runs à 100Khz or above.

  4. The write up is inaccurate and without research.
    >gives this amp its PWM cycle speed of 31.25 kHz. That’s right in the neighborhood of what a class D amp should run at, and the code is only about 30 lines.

    31.25kHz is a very low switching frequency, so if you want eliminate that frequency content, you’ll need some very large LC filters. If running without filter, the higher harmonics can damage your speaker if the speaker’s power rating is exceeded.

    Real life class-D runs at much higher frequency. e.g. TI’s LM4673 series runs at 300kHz which allows it to run without bulky output filters because the speaker impedance at that frequency would be very high.

  5. I agree. Most class-D’s today run north of 300kHz so low frequency artifacts don’t distort the signal. And adding an output LC as well as cloning the circuit for high and low sides of a true differential output with 1/2 Vin offset will help both efficiency and speaker life. Of course a true integrated class-D IC will give you nice things like fault detection and shut-off. Some even integrate the error amp DAC directly and take I2S input. But I agree this is a very nice hack! And nicely demonstrates the principles of class-D amps!

  6. The general rule of thumb is to operate the Class D switching frequency at atleast 10x the highest frequency you want to play, for a 20kHz audio signal that would be in the 200kHz range.

    Hint, look at MOSFET based motor drivers, rather than the L298 (bipolar) driver.

    For the cost of these discrete parts you can probably just buy a ready-made part from Maxim or TI.

      1. People get into hacking/making for different reasons: sometimes to solve problems cheaply, or to solve problems in a more satisfying way, or to learn, or sometimes just for fun. It’s funny how often folks don’t understand this and make posts like “but you could have just bought something that does the same thing” or “what’s the point.” Different goals bring different perspectives, and this is a good thing!

      2. Then he should have used discrete parts instead of an L298.

        That is how you learn how to implement a Class D design and all of the associated pit falls.

        Want to learn, thanks for asking–

        Using a higher switching frequency will lower your THD+N Ratio.

        Using a higher switching frequency, >31kHz will likely require MOSFETs.
        -What are the differences between biploars and MOSFETs?

        Using MOSFETs in an H-Bridge configuration, one can do this two ways: P-MOSFETs on the top and N-MOSFETS on the bottom, or all 4 H-Bridge MOSFETS can be N-MOSFETS.
        -What is the benefit / trade off between using P-MOSFETS vs N-MOSFETS?
        -What would be needed to drive the gates of the upper N-MOSFETS in the H-Bridge?
        -What BIG advantage does a full integrated Class D driver chip have that your discrete discrete design not have?
        -Why is dead time between switching cycles needed, how does it contribute to THD+N Ratio?

  7. 31 kHz will indeed damage the speakers if the PWM voltage amplitude is sufficient for any decent baseband power output.

    This can only be used for demonstration purposes or with a proper low-pass filter added, and then it would only be useable for low-to-midrange audio frequencies.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.