Libby8‎ > ‎

Avr Firmware

Part of the intention for Libby8 is to release Avr firmware updates. This will be necessary because Libby8 will be released before it's complete, so users will need to be able to upgrade the firmware as the project progresses. I intend to do this via audio files. I want to do this because the goal is to develop Libby8 with the minimum number of components, that is 4 chips. If users need a substantial development board on top of that then that somewhat defeats the purpose.

My own development system is completely homebrew. I have a Cypress Semiconductor EZ-USB based hobby board from BrainTechnology in Germany. The USB end interfaces to my own Cocoa application on a Mac; which can download firmware to the EZ-USB's RAM; maintain a terminal window with the EZ-USB and /or the AVR; upload files via the EZ-USB to a target device and program an AVR (there's also a PIC programmer, but it's not tested).

The firmware on the EZ-USB itself is a homebrew Forth kernel in a little over 2.5Kb which supports real-time debugging - i.e. you can send USB packets which set breakpoints, while the system is running. EMIT dumps text to USB, but there's words to support loading and saving USB packets. The Forth Kernel is extended to support an SPI interface (for programming the AVR) and serial port 0 (for communicating with the AVR). I have a rudimentary Forth compiler running on the Mac which generates Forth definitions and TIL code, though the original goal was to have the Forth compiler running interactively in the Cocoa application so that whenever you defined something and ran it, it would download the dependencies to the EZ-USB and then execute the definition. That way I could get around the EZ-USB's RAM limitations (8Kb). That's just to give you some of the background, I don't really want Libby8 developers to have to need a USB development environment.

The first stage for the Avr firmware is therefore to get the AudioLoader working to make simple firmware updates possible. This section documents the audio interface; which is also simple. The basic decision is to use the ADC to read analogue inputs. That might seem obvious to some, but retro computers don't actually load from audio that way; they tend to use a zero-crossing technique; which is why the download rates are so slow, in the region of <200bytes/s for a ZX Spectrum (around 30 to 40bytes per second on a ZX81). Zero-crossing isn't such a great technique for us, because the AVR inputs aren't so great with -ve digital inputs and rectifying the signals pushes against the component constraint mentioned earlier.

So, we're using Adc, There's quite a number of microcontroller adc projects but most of them connect the Adc to a non-audio source or to a microphone source with associated Op-Amps to amplify and bias the audio. The impedences for line-in are different. I found an interesting Adc project for the wonderfully retro Dragon 64, but this requires an additional 19 components including an Op-Amp.

This technique uses much simpler hardware. First let's look at some of the pitfalls when sampling Line-Out audio (i.e. from a computer):
  • Audio signals have a high-pass filter (at around 20Hz) as well as a low-pass filter (around 20KHz). This means you can't dump DC signals to an Audio out, they always end up being biased around 0 so you have to deal with -ve and +ve voltages.
  • Audio Jacks come as mono and stereo. A stereo lead has a tip, then a plastic separator; then a metal 'ring' followed by a plastic separator; finally there's the sleeve. The Left signal is on the tip, the right signal on the ring; the sleeve is ground. It's designed that way so that you can connect mono equipment to stereo, but only in certain ways. For example, you can connect a mono-mic to a stereo input and the RH signal will be grounded, which is OK. However, if you output a mono audio file through a stereo interface (so the audio is duplicated on both channels), but connect mono jacks then the RH signal gets connected to ground which I think, based on experimentation, messes up the Adc if you measure it between ground and LH signal. On the other hand, connecting a stereo lead to a mono jack socket does work - the ring signal doesn't get connected to the ground connector on the socket.
  • You might think that diodes can easily be used for rectifying the audio voltage, but it won't work. Diodes have a forward bias of typically 0.7v (which means they don't even register the input voltage unless it's >0.7v) and since audio signals are 1v RMS, that leaves almost no bandwidth for sampling audio - you might as well have the diode and measure the signal digitally.
In the end I found I could use a pretty simple voltage divider for the circuit. The circuit couldn't be much simpler:

@TODO: Publish the correct circuit and initialisation code.

Setting up the Adc is then a doddle:

void AdcInit(void)
     * Explanation: At 16MHz, a prescale of 32 = 500KHz / Adc bit with reduced accuracy.
     * We only need to support 5 levels, so the reduced accuracy is fine. The actual
     * sampling frequency is then 35.714KHz; which means that we get 4.049 samples per cycle
     * at the given the input audio frequency of 8820Hz (44100/5).
    ADCSRA=0x85;    // Enable Adc , don't convert, no ints, prescale 32 (500KHz)
    ADMUX=0xf0;    // 11 10 Internal VRef, Left adjust, Diff Adc0 .. Adc1.
    ADCSRA |=0x40;    // start an initial conversion. it'll take a while.

In the firmware uploader I initially polled the adc and used the value to display a pattern on a default output Led depending on the level:

ushort AdcGet1(byte pwmPattern)
    ushort newAdc;
    while(!(ADCSRA&0x10)) {
        SetLed(pwmPattern&1);    // wait for a conversion to complete.
        pwmPattern=(pwmPattern>>1)|(pwmPattern<<7);    // rot the pattern.
    ADCSRA |=0x50;    // clear the flag & start a new conversion asap, it'll take a while.
    return newAdc;

I did this so that the initial stage of using the AudioLoader will be to make sure the user has a sensible audio level.

I've done quite a lot of work on my AudioLoader - far too much work really. In the end, for performance reasons I used interrupts for both the Adc and the Led. I intend to document this better in the near future.


Sampling Projects:

Julian Skidmore,
2 Dec 2009, 03:08