I’ve been working on a grade crossing controller for a friend back in Iowa lately, and that nagging question of “what about the bell?” kept coming up. I remembered somebody posted on the model railroad electronics group about attiny85 at one point, but I couldn’t find the details or any source code, so I just figured I’d sit down and crank one out myself between putting coats of paint on the guest bathroom walls. (Electronics, always a good distraction from remodeling, right?) Thought I’d share the results if anybody else needed to build something similar.
For those who don’t know, the attiny85 has an internal PLL that will take the internal 8MHz RC clock and multiply it up to 64MHz. That can then be fed into some of the timers (in this case, timer1) to create a very high speed PWM that can be used to create an 8-bit DAC for analog output. If you then drive timer0 to fire an interrupt at your sound sample frequency, and use the ISR to load the next sound sample value into the timer1 PWM, you can very easily create audio output. Technically you can use both high speed PWMs and some interesting resistor combinations to make a 16-bit PWM DAC, but I chose to stick to 8 bit.
The other challenge is that uncompressed PCM sound data chews up a lot of memory to store. Fortunately for us, the “preferred” ring rate for a modern US crossing bell is ~180 times/minute, meaning each ding is only ~0.333 seconds long. The attiny85 has 8kwords of program memory, meaning if I stuck to 8 bit sound and the playback program stayed very small, I could store up to 1second of 8bit, 8kHz sound or half a second of 8bit, 16kHz (or 16bit, 8kHz) sound. Given this is a bell, having a wider range of frequencies seemed more important than bit resolution, so I opted for half a second of 8 bit, 16kHz sound. A third of a second would be slightly under 5k, so plenty small to fit in the tiny85’s program space. (Turns out in this case, the ring rate was a bit lower, as the sample was 0.384s long.)
I went out to the internet and found a good audio recording of a crossing bell. I stripped out a single ding with Audacity, saved it as a 16kHz, 8 bit wave file, and turned that into a ginormous header file (for those who don’t know about the unix xxd utility, have a look, it’s awesome…) that would put it in program memory rather than RAM.
Figuring why reinvent the wheel (aka write all the same bugs), a quick look around the internet showed a project by David Johnson-Davies doing much the same thing. So I borrowed a lot of his register setup code just to be lazy and avoid figuring it out myself. So props and credit to him for that. His example works a bit differently from a triggering perspective, and works on 8kHz samples, but it saved me finding all the critical PWM registers again.
My code and the attiny85 alone will give you a 5Vpp signal on pin 6 of the tiny85 at <15mA of current. Not enough to drive a decent speaker. So I scratched around in my parts bin of audio amps and found an LM4864 power amp. It’s still a current part, although the DIP package is now obsolete. There’s nothing special about it, it’s just what I had handy. The frequency response of the amp and of the physical speaker on the output is sufficient to get rid of the high frequency PWM for the most part. If you decide to feed it into some other amp instead, you may want to tack a low pass filter on the output with a knee at ~12kHz or so to knock down the PWM frequency noise.
Net result – bell circuit with amplifier consisting of 4 caps, 3 resistors, and two cheap ICs that plays back an audio recording of an actual crossing bell, not some synthesized alternative. Total cost of parts is probably in the $4 range, though you may have to substitute a different amp if you can’t find any DIP LM4864s. If I build the real PCB version of this, it’ll be surface-mount anyway, so the lack of DIPs isn’t so much an issue for me. However, honestly I’m thinking about going to an ARM Cortex-M series part and using I2S to a class D amp for the real crossing controller. This was more just because I could and to see how well it would work.
And here’s an example of it on a breadboard working (eek, there’s a clean spot to the left of the breadboard – I’ll ruin my reputation for messiest workbench in the universe):