Close
0%
0%

Storing and playing back lofi audio on an MCU

Software and hardware for storing 8-kHz, 8-bit (or less) audio on an AVR MCU, and playing it back

Similar projects worth following
A Unix app that converts a WAV file into a data array in a C header file. A library that plays back the audio on AVR microcontrollers using PCM (Pulse Code Modulation). A quarter-watt power amp to drive a speaker.

This project is about playing back audio snippets, stored in flash memory, on some AVR microcontrollers (ATtiny85 and ATmega328p so far). My original intent was to modify existing software and develop a high-order filter to suppress the 62.5 kHz PWM carrier wave and a simple power amp to drive a speaker. There is a very nice Arduino library, Michael Smith's PCM library, for 8-bit audio samples with 8 kHz sample rate. You can produce input for this library by first converting a WAV file into a raw data array in a header file using Mathieu Brethes' wave2c tool. You can produce discernible sound with no or minimal filtering and amplification of this raw PCM signal. Just apply it across the coil of a loudspeaker and rely on the coil inductance and your ears to do the low-pass filtering.

However, I realized that bitcrushing (reducing the bit depth from 8 to 4, 2 or 1), not only allows longer audio samples to fit in flash memory, it also allows you to operate at higher PWM frequency, which makes filtering out the ultrasonic carrier trivial. I also wanted to be able to play a sample multiple times and to play multiple different samples. I then learned that ATtiny85 is better at PCM than ATmega328p and that's when I decided to write a new PCM library from scratch. It's called pcm-avr, has an MIT license, supports ATtiny85 and ATmega328p (so far) and does 4-bit audio with 4 MHz PWM frequency (on ATtiny85) and 1 MHz PWM frequency (on ATmega328p). I use it with gcc-avr directly and I don't know if it works with the Arduino IDE yet, but I did avoid things that I know break Arduino compatibility (like explicit interrupts on Timer 0).

With MHz PWM frequencies, a simple RC low-pass filter should suffice. I still need to build a power amp, which will probably be Class AB with negative feedback.

tn85-babysteps.tgz

Generation of a 250 kHz PWM carrier wave on Timer 1 and "modulation" on Timer 0, but not at the same time

x-compressed-tar - 2.13 kB - 05/22/2022 at 18:00

Download

t85-pwm-demo.tgz

Demonstrates PWM on ATtiny85 at the frequency extremes of 0.24 Hz and 21.5 MHz

x-compressed-tar - 1.59 kB - 05/22/2022 at 17:59

Download

cardea.tgz

Tarball with the all the source code you need to make some lofi noises. Provide your own avr-gcc and avrdude.

x-compressed-tar - 28.08 kB - 05/02/2022 at 18:52

Download

  • Applications, part 2 (Halloween prop)

    Johan Carlsson11/01/2022 at 00:11 0 comments

    My son and I found a well-preserved deer skull on a nature walk a while ago and immediately decided to bring it home and turn it into a Halloween prop. As usual, I got started too late and we just barely managed to get it done in time. We found a piece of scrap plywood in the garage to mount it on:

    We're using an Arduino Uno clone and a HC-SR501 PIR sensor to make it cackle demonically and blink its red LED eyes. The power source is a 1.3 Ah 12 V lead-acid battery:

    Left to right: battery shelf, Arduino shelf, back of speaker, PIR sensor and skull peg.

    The deer skull seems really brittle, so I will just let it hang from a peg that enters the skull where the spine used to connect.

    The PCB on the back of the PIR sensor has a pretty high profile, so we used some thick cardboard to protect it:

    Wiring on the back is finished:

    The power-amp shield and the speaker are connected with alligator clips. The deadline is looming, so it'll have to do. We'll do better next Halloween. 

    Speaker, Arduino Uno clone with the Mk 2 power-amp shield, and the 12 V lead-acid battery:

    The speaker has a 4" cone and is old enough to be made in Taiwan and to have an Alnico magnet, so it's quite heavy. So is the battery.

    Here's a wider view of the whole shebang, with some impromptu rain proofing:

    A close up during early Halloween dusk:

    The evil cackling is done by the pcm-avr library I developed from scratch for this project. The sound byte is 6 s long so with 8-bit samples and 8 kHz sampling rate it would require 48 kB of flash, 50% more than the available 32 kB. So 4-bit audio it is! Here's the source code:

    /* Code to make a deerskull blink its red LED eyes and cackle creepily when a PIR sensor drives the reset pin high
     *
     * Build command:
     * avr-gcc -c deerskull.c -mmcu=atmega328p -Os -o deerskull.o && avr-gcc -mmcu=atmega328p deerskull.o -o deerskull.elf && avr-objcopy -O ihex -R .eeprom deerskull.elf deerskull.hex && avrdude -v -p atmega328p -c arduino -P /dev/ttyUSB0 -b 115200 -U flash:w:deerskull.hex:i
     *
     * Copyright 2022 Johan Carlsson
     *
     */
    
    #include "pcm-avr.h"
    #include "cackle.h"
    
    int main()
    {
        /* put unused peripherals to sleep to save power */
        PRR |= (1 << PRTWI) | (1 << PRTIM1) | (1 << PRSPI) | (1 << PRUSART0) | (1 << PRADC);
    
        /* make PB1 an output pin (Pin 9 on ATmega328p Arduino Uno clone) */
        DDRB |= (1 << DDB1);
        /* drive PB1 high */
        PORTB |= (1 << PORTB1);
    
        /* cackle creepily */
        pcm_init();
        pcm_play(raudio_data, raudio_length, raudio_bitdepth);
        pcm_exit();
    
        /* blink once after cackling */
        PORTB &= ~(1 << PORTB1); /* drive PB1 low */
        _delay_ms(7.5e2);
        PORTB |= (1 << PORTB1); /* drive PB1 high */
    
        /* stare at traumatized trick-or-treaters until the PIR turns off */
        while (1);
    
        return 0;
    }
    

    As can be seen, the code is super simple. The microcontroller doesn't know that its reset pin is directly connected to the output pin of the HC-SR501 PIR sensor. I set the sensor to "Single Trigger", minimized the sensitivity and set the delay time to about 15 s, long enough to start the microcontroller, play back the 6 s audio clip, and turn the LED eyes on-off-on.

    We were going to shoot a video of this awesomeness in action, but it just started raining, so I'll add it in an edit in a couple of days.

    Edit: Here's the promised video

    Some parting thoughts: the eyes are two red LEDs in series with a 47 Ohm ballast resistor (dialed in for about 20 mA current). The Mk 2 power amp (Class AB made from two TO-92 transistors, putting 0.25 W into an 8-Ohm speaker) is loud enough for outdoor use. For this kind of intermittent use a heatsink might not even be needed. A topic for future experimentation.

    I think I'm now ready to declare this project successfully completed, and move on the next one! Thanks for reading!

  • Power amp, part 2 (Class AB)

    Johan Carlsson10/17/2022 at 16:21 0 comments

    TL;DR

    I'm proposing a minimalist Class AB power amp to drive an 8-Ohm speaker with AVR PCM audio as input. It uses two heatsinked TO-92 complementary BJTs and puts a quarter of  a watt into an 8-Ohm load (the loudspeaker). Distortion is low for an 8-Ohm speaker, but noticeable for a 4-Ohm one. In my opinion it represents a local maximum for performance as a function of complexity. Here's the schematic:

    My version of circuitikz doesn't have a loudspeaker symbol, so you have to imagine the 8-Ohm resistor to be one. Nothing really new about this schematic, of course. You'll find very similar ones in AoE, for example. It does take full advantage of the nicely loud and thick (low-impedance) output signal from ATmega328p and ATtiny85.

    The scenic route

    My old plan for a power amp Mk2 version for this project was a Class B with negative feedback to fix the crossover distortion (inspired by some Soviet toy-synth schematics). So a two-stage power amp with complementary TO-220 BJTs in the output stage feeding back the output signal to the preceding voltage-gain stage. As I was happily spicing away, it occurred to me that the PCM signal might just have low enough impedance and high enough amplitude to allow a power amp stripped down to just an output stage.

    The ATmega328p datasheet does have graphs for output pin DC voltage vs. current at various temperatures:

    So at room temperature (25C, the green graph), drawing 12.5 mA reduces the voltage on the output pin to 4.67 V, as marked by the asterisk. This will cause some audio distortion of course, with soft clipping of the peak of the waveform. According to its datasheet, the ATtiny85 is marginally better at sourcing current.  Assuming audio-frequency AC is close to enough to DC, the asterisk shows the peak source current drawn by the circuit in the schematic above. However, if you use a proper TO-220 power transistor, its low beta will cause it to draw in excess of 30 mA, which is out of spec for the microcontrollers of interest for this project. Hence my decision to use a higher-beta TO-92 BJT. In spice simulations the circuit puts 250 mW into the 8-Ohm speaker (and close to the 230 mW measured), so the collector current and power dissipation in the transistors are highish, but well below the max ratings found in the 2N2222 datasheet (600 mA continuous and 625 mW, respectively).

    So I breadboarded the circuit and fired it up, feeding it a 5 V peak-to-peak, 4-bit, 1-kHz sine wave. It worked, but the transistors quickly became too hot to touch. I could try to source higher-beta TO-220s, but Halloween is near and there are children to terrify. Time for some quick Appalachian Engineering (as a former Knoxvillian I'm allowed to say that). Heatsink material:

    Thermal-adhesive ingredients:

    That's regular epoxy and aluminum filings.

    15 mm x 15 mm aluminum-flashing square, with 2N2222 and 2N2907 (the complementary PNP) attached by 50-50 mixture of epoxy and aluminum filings:

    Do scratch the heatsink surface a bit for good adhesion.

    A bespoke power-amp shield:

    Close up with seductive lighting (the perfboard looks edible, I guess my phone decided it was food):

    Left to right: the input signal to the power amp is PCM audio on digital pin 5 on this Uno clone. An RC low-pass filter to suppress the PCM carrier frequency, input coupling cap, Class AB amplifier stage, output coupling cap and connectors to alligator clip the speaker to.

    The impromptu heatsink does work in some preliminary testing. Anecdotally it allows the power amp to operate at full blast for several minutes, keeping the transistors cool enough to touch. The original, breadboarded version (without heatsink) starts acting flaky after about a minute of operation and the transistors become too hot to touch well before then. The bias diodes do a good job of preventing crossover distortion. My scope tells me that the amp puts about 230 mW into the speaker, and the waveform looks pretty good, with less distortion than I expected. The output is surprisingly...

    Read more »

  • PCM on ATtiny85 and ATmega328p

    Johan Carlsson08/15/2022 at 00:59 0 comments

    I ported my pcm-tn85 library to ATmega328p (m328p) and changed the name to pcm-avr. I gave it an MIT license. I have yet to test it with the Arduino IDE, but I made an effort not to break Arduino compatibility. I think Arduino uses some Timer 0 interrupt handlers internally so I put the sample loader in a Timer 2 'rupt handler in the pcm-avr m328p code.

    Despite having less flash memory, I prefer tn85 for PCM. The main reason is that tn85 Timer 1 can run at 64 MHz, independent of the system clock frequency CK. I use CK = 1 MHz for pcm-avr on tn85 and PCK = 64 MHz as the clock source for T1. On m328p the best I can do is CK = 16 MHz for everything (no m328p timer can run faster than CK). As a result, the PWM frequency is four times higher on tn85 than on m328p, 4 MHz vs. 1 MHz for 4-bit audio and 250 kHz vs. 62.5 kHz for 8-bit audio.

    Unless you do 8-bit audio on m328p, filtering out the PWM carrier wave from the PCM output is then not that hard. In most cases a simple RC low-pass filter at 4 kHz should suffice. So one hardware problem (filter to remove very loud 62.5 kHz noise from audio signal) solved by software (that does PCM with higher-frequency carrier). The second hardware problem remains (of pulling a good fraction of an ampere through a voice coil with tolerable distortion). 

  • PCM on ATtiny85, part 3

    Johan Carlsson06/25/2022 at 05:41 0 comments

    I have cleaned up my code for PCM on ATtiny85 and made it available as the pcm-tn85 library. It supports both synchronous and asynchronous playback, and sample bit depths of 8, 4, 2 or 1. It was written from scratch to take advantage of the high-frequency (64 MHz, which was good enough for me, or even a bit higher) ATtiny85 Timer 1. I implemented all the functionality in a header file, so no need to link an object or library file. Here's a short video showing a very simple circuit, and what it sounds like:

    I have been pontificating on the evils of not low-pass filtering the PCM output signal, and here I do exactly that. In my defense, this is 4-bit audio, with a PWM frequency of 4 MHz, which I doubt will affect the speaker. I was surprised how well an ATtiny85 can directly drive an 8 Ω speaker. I think the DC current sourced by a pin can be as high as 40 mA at 5 V. Sounds like AC current gets close to that (the nice and loud chirping is in the range of 300 to 3,000 Hz and I use Vcc = 4 V here).

    This will probably be my last post on this project for a while. I plan to do a Halloween post and I might port the PCM library to ATmega328P for that application. It should be possible to do 4-bit audio with 1 MHz PWM frequency on the ATmega328P. Using ATtiny85 Timer 1 for ultrasound transmission is another possibility that's fermenting in my mind. But before then I want to do some high-voltage analog stuff!

  • Applications, part 1 (Do androids dream?)

    Johan Carlsson06/22/2022 at 23:33 0 comments

    My son and his friend brought me onboard as a contractor on this project to do the electronics and software. Specifically my task was to make a 3D printed sheep bleat and blink. I found a suitable bleat and converted it into mono, 8-kHz, 8-bit wav. Here's what the audio data looks like:

    It's exactly one second long (8,000 samples) with 8 kB of data, which would leave zero bytes of flash memory for instructions on an ATtiny85. Challenge accepted! Praise serendipity that my little library for PCM on ATtiny85 is now pretty much finished. With the software taken care of, here's my inept attempt at a schematic with a power amp for the PCM output, and some blinkenlights:

    The LED ballast resistors were chosen to make the four LEDs about equally bright. There's a 4 kHz low-pass filter on pin 6 to suppress the 4 MHz PWM frequency. The power amp is just an emitter follower to lower the signal impedance. It does manage to drive a 4 Ω speaker, but with significant distortion. Fortunately a distorted bleat still sounds like a bleat. Vcc is about 4 V (from a Li-ion cell).

    Here's the 3D-printed sheep after the cheap contractor did his damage:

    Perfboard and hot glue, in 2022, sheesh. Well, you get what you pay for. And from the other side, showing the the 14500 Li-ion cell:

    MCU in the lower left corner of the perfboard. Red, yellow, green and blue LEDs (with ballast resistors) to the right of the MCU. Low-pass filter in the upper left corner with the power amp to its right (10 μF yellow coupling cap, voltage divider for biasing, grid stopper, emitter resistor and a 2N3904 BJT) and finally a 1 mF black electrolytic cap to keep the DC off the speaker voice coil.

    Last, but not least, some artistic touches added by my son:

    And here it is in action: 

    An even worse power amp!

    There's pretty limited space on the back of a toy sheep, but as I was breadboarding the power amp I did try a variation with a small audio transformer with its primary coil between the collector and Vcc (and the speaker across the secondary). In principle this can both keep the DC off the voice coil, and lower the signal impedance. Here's the audio transformer I tried (with the sheep for scale):

    Here's what that power-amp variation did to a 1 kHz sine wave:

    Yikes, that's some interesting distortion! Looks like the collector current was more than sufficient to saturate the core of the audio transformer. So even for sub-watt audio power you'd need a bigger output transformer, or one for single-ended use (with an airgapped core), neither of which seem very practical. As a note to my future self, I might want to try a small transformer with two secondaries, one for the signal and the other for DC current to cancel the flux created by the DC collector current through the primary. A bit convoluted, but fun! Or try a Class B amp next time...

  • PCM on ATtiny85, part 2

    Johan Carlsson05/26/2022 at 03:32 0 comments

    The raudio format

    The format of the audio played back is very simple, as it has to be to be unpacked in the interrupt handler on a low-end MCU. Bit crushing is done by using a single byte (a variable of type unsigned char) to store 2, 4 or 8 audio samples. For example, here's a complete raudio file called sine1kHz4b.h:

    /* Single-period 1 kHz sine wave (w/ 4 bits per sample), made by hand */
    
    #ifndef RAUDIO_PREFIX
    #define RAUDIO_PREFIX
    #endif
    
    #define KONK(a, b) KONK_(a, b)
    #define KONK_(a, b) a ## b
    
    const unsigned char KONK(RAUDIO_PREFIX, raudio_bitdepth) = 4;
    const unsigned int KONK(RAUDIO_PREFIX, raudio_length) = 4;
    
    const unsigned char KONK(RAUDIO_PREFIX, raudio_data)[] PROGMEM = {216, 223, 56, 49};
    /* {255, 255, 17, 17}; */  /* square wave (for debugging) */
    

    There are some ugly preprocessor macros that allow you to add a prefix to the default variable names (raudio_bitdepth, raudio_length and raudio_data). KONK is short for konkatenera (Swedish for concatenate). The audio samples are stored in the array raudio_data. The eight 4-bit samples that define one period of a sine wave are {8, 13, 15, 13, 8, 3, 1, 3}. These are pairwise stuffed into a single byte: for {8, 13} we get 8+ 16 x 13 = 216, et cetera. The sample values can then be quickly unpacked by some bit twiddling in the interrupt handler (ISR):

    if (!nsample) databyte = pgm_read_byte(raudio_data + nbyte++);
    OCR1A = (databyte >> (nsample << 2)) & 15;
    nsample = ++nsample % 2;
    

    OCR1A is the register that determines the duty cycle of the PWM carrier wave. The byte counter nbyte is incremented every time a new byte of raudio data is loaded from flash. Its type is now an unsigned int, which takes up two bytes with avr-gcc, the same as memory pointers. For a while I used a four-byte integer type (unsigned long) for a sample counter, but since it's used for pointer arithmetic it is less bug-prone to use pointers that won't overflow memory addresses so easily. The two-byte counter is supplemented by a single-byte (unsigned char) sample counter nsample that counts the samples within a byte. The code snippet above is for four-bit audio, with the first sample (nsample == 0) stored in the low four bits of the byte, and the second sample (nsample == 1) stored in the high four bits. So for the first sample we just have to use the mask decimal 15 (equals binary 1111) to zero out the four high bits. For the second sample we first have to shift the bits four (nsample << 2) positions to the right. The PWM counter is running at 64 MHz and the PWM period is 256 counts for 8-bit raudio, 16 counts for 4-bit, 4 counts for 2-bit and 2 counts for 1-bit.

    The interrupt handler is called with a frequency of fS = 8 kHz (every 125 us) so even at 1 MHz system clock there is ample time to perform the operations needed. There's 125 cycles between interrupts, minus overhead and cycles spent servicing the intrinsic PWM interrupts. Most (or all?) of the operations should be single-cycle ones, so there's plenty of headroom.

    The tools sox and wav2h can be used to convert an mp3 file into an approriate wav file (single channel, 8 kHz, 8 bit) and convert the wav to raudio, respectively. I'm currently migrating away from wav2h to python so that I can use scipy to do some audio processing.

    Measured output signals

    Below are the measured signals with just a first-order low-pass filter (R = 390 Ω, C = 100 nF) and a coupling cap (C = 10 μF) in the signal path between the tn85 output pin and the 'scope. First, for 8-bit raudio the output signal looks like this:

    The carrier frequency fPWM = 64 MHz / 256 counts = 250 kHz.

    Next, 4-bit raudio:

    The carrier frequency fPWM = 64 MHz / 16 counts = 4 MHz.

    2-bit raudio:

    The carrier frequency fPWM = 64 MHz / 4 counts = 16 MHz.

    Finally, for 1-bit raudio:

    One would think that the carrier frequency fPWM = 64 MHz / 2 counts = 32 MHz, but for Timer 1 on ATtiny85 setting the clear-on-compare-match value (register OCR1C)...

    Read more »

  • PCM on ATtiny85, part 1

    Johan Carlsson05/23/2022 at 15:41 0 comments

    I now have code that does PCM on an ATtiny85! So far I've only tested 8-bit audio at 250 kHz PWM frequency. The sample frequency is still 8 kHz. With an RC low-pass filter (R = 3.9 kΩ and C = 10 nF) on PB1 (the output pin), this is what the measured signal looks like for a 1 kHz sine wave:

    There's a lot of harmonics (the 7th one being most prominent). This is because the PCM output gets really blocky with the wave frequency being exactly 1/8 of the sample frequency. The 8-bit sine wave is then represented by the numbers 128, 218, 255, 218, 128, 38,  1,  38. With only five unique amplitude values, the effective bit depth is only 2.3. Hence the "low-pass filtered square wave" characteristics of the signal. The good news is that the carrier frequency is now high enough to be well suppressed by a passive, first-order filter. Depending on the application, you might or might not want to use a more effective filter to reduce these discretization-error harmonics. This digital distortion might even add color, it is symmetric.

    I mentioned duty-cycle corner cases before and that is an issue here. On the megas, the minimal duty cycle for PWM is 1/256 (as was explained by the blog post by @Ken Shirriff that I have linked to before). For tinys, the minimal duty cycle is zero, but the second smallest possible value is 2/256, not 1/256. You won't find this mentioned in the datasheet. When you do PCM on tinys, this duty-cycle behavior can cause asymmetric distortion. It might not be a big deal for 8-bit audio, but will be audible at 4 bits and less. I avoid this by only using positive values for audio samples (zero is excluded). So 1-255 for 8-bit audio, 1-15 for 4-bit, etc.

    I've decided to go Agile on the pcm-tn85 library and let the API evolve a bit as I do one or two projects myself using this code (synchronous and/or asynchronous calls, compatibility with the Arduino IDE, etc.). It should be possible to use even higher PWM frequencies for bit-crushed audio (4 MHz for 4-bit), but I want to experiment with that before deciding. With 250 kHz PWM, I did see some odd self-modulation at 2 or 3 kHz, but as soon as I put a bit of load on the output pin (in the former of the RC filter), it went away. There might be more serious issues with MHz PWM, I just haven't tested yet. I also want to understand how nested interrupts would affect PCM quality.

  • Flash-memory usage for PCM

    Johan Carlsson05/17/2022 at 23:37 0 comments

    Out-of-order log here. I thought I had posted this a while ago, but I only made a plot. Anyway, the results found inspired me to try to do PCM on an ATtiny85.

    The topic if this log is simply "how many seconds of 8-bit audio can you fit into a given amount of flash memory". To answer that, below is a plot of the output from avr-size (the dec column). The input to avr-size is the executable built from a code snippet that includes a raudio header file and plays it back using PCM. Code built with avr-gcc v. 11.2.1 using options "-Os -mmcu=atmega328p".

    So for an ATmega328 (with 32 kB of flash) you can play 3.89 s of 8-bit audio and for an ATtiny85 (with 8 kB of flash) 0.89 s. The exact formula is flash usage in bytes m = 859 + 8,000 x s, where s is the length of the 8-bit audio sample in seconds. Of the 859 bytes 5 are raudio meta data (bit depth and sample length) and 854 are the compiled code that does PCM.

    For bit-crushed raudio you can obviously fit longer samples into flash, 2x more for 4-bit, 4x more for 2-bit and 8x more for 1-bit. So for 1-bit raudio you could fit a 31.1 s sample into the flash of an ATmega328 and 7.1 s on an ATtiny85. Generalized formula: m = 859 + 1,000 x d x s, where d is the bit depth.

    So you won't be putting your music collection in flash, but there should be enough space to add an aural modality to your AVR MCU project.

  • PWM on ATtiny85

    Johan Carlsson05/16/2022 at 23:55 0 comments

    I originally used an AVR ATmega328 for my PCM lofi shenanigans. It has 32 kB of flash, 2 kB of SRAM, 3 timers and runs at 16 MHz. For various reasons I got curious how an ATtiny85 would fare for the task with 8 kB of flash, 512 B of SRAM, 2 timers and running at 8 MHz.

    I've only tested the PCM library on an ATmega328, but it should supposedly also work on ATmega128. How about on ATtiny85? The PCM library uses Timer 2 to generate the carrier and Timer 1 to modulate it. A quick peak in the ATtiny85 datasheet  informed me that there's only Timer 0 and Timer 1. Maybe I can do a quick port of the library by migrating the carrier-generation to Timer 0? I started doing just that, but when I was halfway done I had figured out that tn85 (that's what us cool kids call the ATtiny85) T1 (Timer 1) was not meant for modulation, but was born for PWM (generating the carrier wave used for PCM).

    With my original plan, with PWM on T0, the carrier frequency would only be 31.25 kHz, half as high as on ATmega328. However, with PWM done by T1, you can get the carrier frequency up to a whopping 250 kHz! That should make low-pass filtering a cinch. For bit-crushed audio you can get an even higher frequency. So I'm developing a PCM library for ATtiny85 from scratch. The carrier generation (on T1) is done, but modulation (on T0) must still be implemented. The timers on ATtiny85 and ATmega328 are very different and not everything is documented in the datasheet, including the duty cycle you get with particular register values. I might do a log on the gotchas I've encountered at some point.

    95% of the effort so far has been either digesting the datasheet (figuratively speaking) or fighting with programmers. My two USBasp dongles both broke the same weekend. I made an impromptu programmer from a Uno clone and a lash-up Gammon cable. So far it's holding up. The 5% of the time spent coding and debugging has been thoroughly enjoyable though.

    Here's some demo code (C source file and Makefile) for T1 PWM. You'll need an ATtiny85 (or 45 or 25), a programmer, avr-gcc and avrdude to use it. Using just the intrinsic timer interrupts, you can dial in PWM frequencies from 0.24 Hz to 21.5 MHz, or a range of almost eight decades. However, frequencies above 250 kHz only allow bit-crushed PCM. For example, at 21.5 MHz the bit depth is only 1.585.

    So far so good for PCM on ATtiny85. PWM on Timer 1 has excellent performance and was easy to implement. For lower PWM frequencies (with the timer running at 1 MHz) the implementation is only eight lines of C code. With just 8 kB of flash only bit-crushed audio seems feasible (4 bits or less per sample). It will be fun to try to do something useful with these limited resources.

  • Power amp, part 1 (and first demo)

    Johan Carlsson05/02/2022 at 19:09 0 comments

    I have not yet converged on a reasonable power amp for this project. This post is about the two options I've looked at so far. There's also a link to the first tech demo.

    Power amp Mk0, no power amp!

    That's right, the gain device used for the Sallen-Key low-pass filter is a single-BJT emitter follower (common-collector amplifier) that has output impedance low enough for the signal to directly drive an 8Ω speaker. So the filter does double duty also as a bargain-basement power amp.

    As a reminder, here's the schematic:

    By adding a single passive component, in the form of a 470μF coupling cap, to the smoothed-signal filter output, we get a signal capable of driving a speaker. The output is not terribly loud, but it's definitely audible. Here's what it sounds like.

    This might be an acceptable budget option for a power amp, especially for driving 32Ω headphones.

    Power amp Mk1, using LM386

    To make my noise louder, with at least a watt into the speaker, I breadboarded the "typical application" schematic from the LM386 datasheet, using 12V supply voltage. With the filter on the same breadboard, everything was peachy. Here's the signal measured across the voice coil of the speaker:

    About 8V peak-to-peak, but with some additional distortion compared to the filter output signal. I then migrated the filter to perfboard, but left the power amp on the breadboard, connecting the two with alligator leads:

    This is what the signal now looks like:

    OMG!1!! Kill it with fire! Severe asymmetric clipping, and 115 kHz noise due to the power amp being unstable. It fried my 32Ω speaker, which fortunately is a recycled headphone speaker cone in a laminated TP roll. The LM386 power amp might be salvageable. The datasheet does warn of long leads, so migrating the amp from breadboard to perfboard should help its stability. However, I was already having doubts about using an LM386 chip for this application. I had noticed that the output signal was pretty load dependent. That's not good because I want to be able to use any junk speaker and get more or less reproducible results. So I"ll think I'll cut my losses on the Mk1 (LM386-based) power amp.

    Conclusions

    The Mk0 power amp (Sallen-Key filter doing double duty as power amp) works surprisingly well. I'll see if it can make audible sound when it drives a tiny 32Ω speaker from an earbud. Mk1 is abandoned. Mk2 will either be a Class A or Class AB, built from discrete components.

View all 12 project logs

Enjoy this project?

Share

Discussions

dearuserhron wrote 04/15/2022 at 09:36 point

Hint: use more than one AVR to increase quality. Try stereo

  Are you sure? yes | no

Johan Carlsson wrote 04/16/2022 at 05:56 point

Thanks! Stereo on a single AVR would be a fun challenge! However, for this project my goal is to get better audio quality from bit-crushed (4-bit) samples with the help of a reasonable low-pass filter and amplifier than from 8-bit samples with no filter and an iffy amp.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates