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):

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.

    }

    newAdc=ADC;

    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.

References

Sampling Projects: