2011-03-10

Atmel AVR USB - First app

Time to get our toolchain installed then. You'll see plenty of posts mentioning the need to install WinAVR around, but with the latest toolchain from Atmel, it looks like a separate installation of WinAVR has become a thing of the past, and we won't actually need to install anything but latest official Atmel toolchain, which is called AVR Studio.

Now, Atmel did the smart thing here: They picked up the Visual Studio 2010 base from Microsoft which they wrapped around avr-gcc and avr-libc. We've come a long way from substandard proprietary toolchains...

So off you go to download AVR Studio 5 (free, but registration required). Installation takes a little while but is a no brainer. Sadly, no Flip integration is provided yet, so we'll have to generate a .hex and still use Flip manually to transfer the files. On the other hand, it should be possible to port the libusb 1.0 and commandline based dfu-programmer to Windows, to provide a flashing tool that integrates with AVR Studio.


Creating a project for Minimus USB

Alrighty, let's create our first LED blinking app then. Creatively, I'm going to call it 'Blinky'.
In AVR Studio, click "New Project" and use the "AVR GCC", "Empty AVR GCC Project" template (which should be selected by default). The Minimus USB, or any AT90USB162 based board don't seem to exist in the Atmel/User Boards lists, so don't bother trying to pick a template there. Change the default Name to 'Blinky' and click OK.
On the next screen, in the Device Family, select "megaAVR, 8-bit", and pick up AT90USB162 (or any other Atmel chip you might be using). Click OK and you're done. AVR Studio will even create a source template for you, how nice!

One thing you'll notice when compiling a new project is that the default settings are not to generate a .hex. To fix that, simply right click on the project, in the solution explorer, and in the Build tab, on the "Generate Files" line make sure the ".hex" checkbox is set (for both Release and Debug).


Blinking code

Armed with the knowledge of where the LEDs and HWB button are connected (PD5 and PD6 for the LEDs, PD7 for HWB), let's write us some code to blink the LEDs. We might as well look at the samples for inspiration.

First thing we'll do, since we need to add some delay when toggling the LEDs, is use some of the handy functions from the avr-libc. util/delay.h provides a _delay_ms() call that should do the trick. The only thing is you need to define F_CPU to your board's frequency, before the #include, for the delay calls to be accurate. The Minimus USB uses a 16 MHz Xtal, therefore our blinking code can be:

#include <avr/io.h>
#define F_CPU 16.000E6
#include <util/delay.h>

void init_ports(void)
{
    PORTB = 0b00000000;
    DDRB = 0b00000000;

    PORTC = 0b00000000;
    DDRC = 0b00000000;

    PORTD = 0b00000000;
    DDRD = 0b01100000;      // Set LEDs as output
}

int main(void)
{
    init_ports();
    
    while(1)
    {
        if (PIND & 0b10000000) {
            PORTD = PORTD & ~0b01000000;
            PORTD = PORTD |  0b00100000;
            _delay_ms(200);
        
            PORTD = PORTD & ~0b00100000;
            PORTD = PORTD |  0b01000000;
            _delay_ms(200);
        } else {
            PORTD = PORTD | 0b01100000;
        }
    }
}


The (loose) PIND & 0b10000000 test is added to turn off both LEDS when HWB is pressed.This should compile alright.
Note that you might want to pay attention to the warnings generated when compiling (on the "Errors" tab), especially the one that says, when using delay.h in Debug mode:
"Compiler optimizations disabled; functions from won't work as designed". Using delay.h will be fine for Release, but if you find that your delays are off in Debug, don't be surprised.


That friggin' watchdog timer

OK then, let's use Flip to transfer the hex, then click "Start Application" and have a satisfied look at our red and blue LEDs alternating... except they don't! Only the red LED blinks, and definitely not at 200 ms. AVR Dude, what the hell?!?

Oh, and it gets better, if you unplug the board, and plug it back, then suddenly everything works. What's going on here?

Part of the answer can be found in the Flip user guide:


4. What is the difference between starting the application with the Reset box checked off or not?
• If you start the application with the Reset box checked, the device will start after a Watchdog reset and this watchdog will remain active. You need to disable the Watchdog in your application. Resetting the device, ensures there is no side effect from resources used by the boot-loader before starting the user application.

Lo and behold, if you uncheck the Reset box and reflash/restart the app, everything works as expected. Of course, if you're unaware that the Flip Reset box is the culprit, you're bound to run into this issue, eventually figure out that it has to do with the WatchDog, and want to disable it in your program, so we might as well take care of that little bugger.

Now, I found that the code samples from all the LED blinkers out there, that are supposed to disable the watchdog, as well as the watchdog disable code from the AT90USB162 datasheet didn't work at all. Even including <avr/wdt.h> and calling wdt_disable() alone didn't work (but at least you could do something like wdt_enable(WDTO_8S); to make the delay before reset much longer, in order to give you a chance to issue a wdt_reset() before it expires).
There have been a few discussions on the AVRFreaks forums related to this, and the first way I found to disable watchdog and make it work with a Minimus USB, for programs transferred using Flip with Reset enabled, was the one provided by curtvm's from this post.

If you include wd.h and call WD_DISABLE(), the Watchdog timer finally gets disabled. But then, looking at what the LUFA samples were doing, it appears that using avr/wdt.h also works, provided that you issue and MCUSR &= ~(1 << WDRF) before calling wdt_disable().
Thus, our final code, which now works regardless of whether the Watchdog is initially enabled or not, becomes:
#include <avr/io.h>
#define F_CPU 16.000E6
#include <util/delay.h>
#include <avr/wdt.h>

void init_ports(void)
{
    PORTB = 0b00000000;
    DDRB = 0b00000000;

    PORTC = 0b00000000;
    DDRC = 0b00000000;

    PORTD = 0b00000000;
    DDRD = 0b01100000;      // Set LEDs as output
}

int main(void)
{
    MCUSR &= ~(1 << WDRF);
    wdt_disable();
    init_ports();
    
    while(1)
    {
        if (PIND & 0b10000000) {
            PORTD = PORTD & ~0b01000000;
            PORTD = PORTD |  0b00100000;
            _delay_ms(200);
        
            PORTD = PORTD & ~0b00100000;
            PORTD = PORTD |  0b01000000;
            _delay_ms(200);
        } else {
            PORTD = PORTD | 0b01100000;
        }
    }
}

Tested and confirmed. Great, now we're all set to produce more interesting AVR USB apps, like ones using LUFA for instance...

1 comment:

  1. Hey pete, finally someone who teaches men how to fisch! Your blog is awesome and was the perfect help understanding how to deal with at90usb chips! Thanks for this great article mate

    ReplyDelete