Documentation:Tutorials gcc AT90UsbKey
From AVRFreaks Wiki
This tutorial is about programing the AT90UsbKey (AT90USB1287) with gcc in the AVR Studio.
Please note that these code examples don't need any other hardware than the AT90UsbKey a usb cable and a PC.
Contents |
[edit] Read this first
[edit] AVR Studio 4
AVR Studio 4 is the official Atmel IDE, as default it only supports assembler but together with WinAVR you can use gcc as your c compiler.
Since WinAVR has official Atmel support the integration of gcc into AVR Studio is very good, to be honest it is transparent. The only thing you need to do is download them both and install them (see links below).
The Studio also has a simple but effective simulator, so it is easy to test the different parts of your program. It tends to have some problems with interrupts and timers but overall it is very nice.
AVR Studio 4:
WinAVR:
[edit] Flip
The easy way to flash the AT90UsbKey is to use the Flip program. Flip programs the AT90UsbKey with a usb cable.
The quick steps
- Build your program in AVR Studio
- Open FLIP and connect the AT90UsbKey.
- Open your hex file
- Make sure the all check boxes are set in the "operation flow area", that is Erase, Blank check, program and verify.
- Press the Run button in the "operation flow area".
- Press Start Application or remove the usb cable, and reinsert the cable if you want to power up your program over usb.
Please note that the AT90UsbKey comes with the look fuses set, so there is no way to read data from the AT90UsbKey. To remove the look you must reprogram the AT90UsbKey with a ISP or JTAG, FLIP can not remove the look fuses. This seems to be a very stupid state to ship demo cards, but this it is the way it is.
If you are using Flip for the first time, you will need to make the AT90UsbKey look like a Flip Programming interface before it installs properly. To do this you need to put it in "programming mode" by executing the following sequence on the AT90USBKEY board before trying to connect from FLIP. - press HWB - press RST - release RST - release HWB.
If that does not work with the message:"AtLibUsbDfu.dll not found". The advice on the Atmel site (http://support.atmel.no/bin/customer?custSessionKey=&customerLang=en&noCookies=true&action=viewKbEntry&id=336) is "The cause can be that the wrong driver is installed for the USB flip device. You must uninstall the USB device and then put the board in programming mode with the reset and HWB jumpers. When windows will discover the new USB device, you will be able to load the correct driver from the ...\filp 3.2.2\usb directory."
[edit] Fundamental code examples
[edit] Blinky
Blinky is the embedded version of Hello World, it flashes with one or more LED for the world....
[edit] Blinky, stupid_sleep
This version has a minimum of dependences the only external thing is the avr/io.h and that is located in the WinAVR directory. If the program dosen't compile make sure that your program knows where to find this file.
#include <avr/io.h>
#include "my_bit.h"
#include "my_types.h"
//300 ~ 3s
//100 ~ 1s
#define SLEEP_TIME 25
void stupid_sleep(uInt time){
uInt i = 0;
uInt j = 0;
for(i=0;i<time;i++){
for(j=0;j<530;j++){
//Do nothing
asm volatile ("NOP");
}
}
}
int main(void){
//PD4..7
DDRD SET_BIT4;
DDRD SET_BIT5;
DDRD SET_BIT6;
DDRD SET_BIT7;
while(1){
PORTD SET_BIT4;
stupid_sleep(SLEEP_TIME);
PORTD CLEAR_BIT4;
PORTD SET_BIT5;
stupid_sleep(SLEEP_TIME);
PORTD CLEAR_BIT5;
PORTD SET_BIT7;
stupid_sleep(SLEEP_TIME);
PORTD CLEAR_BIT7;
PORTD SET_BIT6;
stupid_sleep(SLEEP_TIME);
PORTD CLEAR_BIT6;
}
}
[edit] 1ms or 0.4ms
Or the same thing but with 1ms or 0.4ms delay, and output is PortC bit7 so we can measure the delay i.e. with a oscilloscope. And this example is compiled with -Os optimization, compile with other and the times will be wrong.
#include <avr/io.h>
#include "my_bit.h"
// 150 => 1,05ms
// 145 => 1,04ms
// 140 => 1,00ms
// 138 => 0,99ms
// 130 => 0,92ms
// 120 => 0,86ms
void delay_1ms(){
unsigned int j = 0;
for(j=0;j<140;j++){
//Do nothing
asm volatile ("NOP");
}
asm volatile ("NOP");
}
//55 => 0,39ms
//56 => 0,41ms
void delay_0_4ms(){
unsigned int j = 0;
for(j=0;j<55;j++){
//Do nothing
asm volatile ("NOP");
}
asm volatile ("NOP");
}
int main(void){
//PD4..7
DDRD SET_BIT4;
DDRD SET_BIT5;
DDRD SET_BIT6;
DDRD SET_BIT7;
DDRC SET_BIT0;
while(1){
PORTC SET_BIT0;
delay_0_4ms();
delay_1ms();
PORTC CLEAR_BIT0;
delay_0_4ms();
delay_1ms();
}
}
[edit] Blinky, delay.h
Because the first version dosen't have exact timing you can use the delay.h. delay.h does basically the same thing but in a more precise manner.
The only thing that is really important here is that F_CPU is set to the correct frequency, in this case it should be 8000000 Hz (8 MHz).
Be careful not to have a longer delay than is supported by delay.h
#include <avr/io.h>
#include "my_bit.h"
#include "my_types.h"
//Se project config freq in Hz
//#ifdef F_CPU
// #undef F_CPU
//#endif
//#define F_CPU 8000000UL // 8 MHz
#include <util/delay.h>
#define SLEEP_TIME 250
int main(void){
//PD4..7
DDRD SET_BIT4;
DDRD SET_BIT5;
DDRD SET_BIT6;
DDRD SET_BIT7;
while(1){
PORTD SET_BIT4;
_delay_ms(SLEEP_TIME);
PORTD CLEAR_BIT4;
PORTD SET_BIT5;
_delay_ms(SLEEP_TIME);
PORTD CLEAR_BIT5;
PORTD SET_BIT7;
_delay_ms(SLEEP_TIME);
PORTD CLEAR_BIT7;
PORTD SET_BIT6;
_delay_ms(SLEEP_TIME);
PORTD CLEAR_BIT6;
}
}
[edit] Blinky, interrupt.h
#include <avr/io.h>
#include <avr/interrupt.h>
#include "my_bit.h"
unsigned char blink = 0;
ISR(TIMER2_OVF_vect)
{
// Code to be executed when ISR fires
//
blink++;
switch(blink){
case 1: {
PORTD SET_BIT6;
PORTD CLEAR_BIT7;
break;
}
case 2: {
PORTD CLEAR_BIT6;
PORTD SET_BIT4;
break;
}
case 3: {
PORTD CLEAR_BIT4;
PORTD SET_BIT5;
break;
}
case 4: {
PORTD CLEAR_BIT5;
PORTD SET_BIT7;
blink = 0;
break;
}
}
}
int main(void){
//PD4..7
DDRD SET_BIT4; //Led D2 Red
DDRD SET_BIT5; //Led D2 Green
DDRD SET_BIT6; //Led D5 Green
DDRD SET_BIT7; //Led D5 Red
TCCR2B = 0x07; //When in simulation, use 0x01 up to 0x05.
TIMSK2 = 0x01;
asm("sei");
while(1){
asm("nop");
}
}
[edit] Push the Button
[edit] Button and blinky
This program blinks one LED and turns the other on when the user presses and holds the HWB-button on the AT90UsbKey.
#include <avr/io.h>
#include "my_bit.h"
#include "my_types.h"
//Se project config freq in Hz
//#define F_CPU 1000000UL // 1 MHz
//#define F_CPU 14.7456E6
#include <util/delay.h>
#define SLEEP_TIME 500
int main(void){
DDRD SET_BIT5; //Led D2 Green
DDRD SET_BIT6; //Led D5 Green
//HWB, the button
DDRE CLEAR_BIT2;
uChar button=0;
while(1){
//Has someone pressed the HWB-button?
//Normaly high, button drives level low.
GET_BIT(button, PINE, 2);
if(button==0){
PORTD SET_BIT5;
} else {
PORTD CLEAR_BIT5;
}
//Led D5, green
PORTD SET_BIT6;
_delay_ms(SLEEP_TIME);
PORTD CLEAR_BIT6;
_delay_ms(SLEEP_TIME);
}
}
[edit] Button with background blinky
If this program doesn’t blink with a stable frequency, make sure that you compile with optimization set to -Os.
#include <avr/io.h>
#include <avr/interrupt.h>
#include "my_bit.h"
#include "my_types.h"
uChar blink = 0;
ISR(TIMER2_OVF_vect){
if(blink == 0){
PORTD SET_BIT6; //Led D5, green
blink++;
} else {
PORTD CLEAR_BIT6; //Led D5, green
blink = 0;
}
}
int main(void){
uChar button=0;
DDRD SET_BIT5; //Led D2 Green
DDRD SET_BIT6; //Led D5 Green
//HWB, the button
DDRE CLEAR_BIT2;
TCCR2B = 0x07;
TIMSK2 = 0x01;
asm("sei");
while(1){
//Has someone pressed the HWB-button?
//Normaly high, button drives level low.
GET_BIT(button, PINE, 2);
if(button==0){
PORTD SET_BIT5;
} else {
PORTD CLEAR_BIT5;
}
}
}
[edit] Header files that this tutorial depends on
[edit] my_bit.h
/**
* My typedef:s so I don't have to type unsigned all the time.
*/
#ifndef __MY_BIT
#define __MY_BIT
#define SET_ALL |= 0xFF;
#define SET_BIT0 |= 0x01;
#define SET_BIT1 |= 0x02;
#define SET_BIT2 |= 0x04;
#define SET_BIT3 |= 0x08;
#define SET_BIT4 |= 0x10;
#define SET_BIT5 |= 0x20;
#define SET_BIT6 |= 0x40;
#define SET_BIT7 |= 0x80;
#define CLEAR_ALL &= 0x00;
#define CLEAR_BIT0 &= 0xFE;
#define CLEAR_BIT1 &= 0xFD;
#define CLEAR_BIT2 &= 0xFB;
#define CLEAR_BIT3 &= 0xF7;
#define CLEAR_BIT4 &= 0xEF;
#define CLEAR_BIT5 &= 0xDF;
#define CLEAR_BIT6 &= 0xBF;
#define CLEAR_BIT7 &= 0x7F;
//TO_VAR, FROM_VAR unsigned char
//BIT_NUM the bit to get 0..7
#define GET_BIT(TO_VAR, FROM_VAR, BIT_NUM) {\
TO_VAR=FROM_VAR;\
TO_VAR>>=BIT_NUM;\
TO_VAR&=0x01;\
}
#endif //__MY_TYPES
[edit] my_types.h
/** * My typedef:s so I don't have to type unsigned all the time. */ #ifndef __MY_TYPES #define __MY_TYPES typedef unsigned char uChar; typedef unsigned int uInt; typedef unsigned long uLong; #endif //__MY_TYPES
[edit] Other good programming techniques
[edit] Function, inline function or macro
How to implement our functions is a interesting qustion and it depends on what result you want.
Let's look at the GET_BIT function that we first saw in the "Push the Button" exampels.
The first way is to implement it as normal function.
uint8_t getBit(uint8_t data, uint8_t bitPos);
uint8_t getBit(uint8_t data, uint8_t bitPos){
data >>= bitPos;
data &= 0x01;
return data;
}
But we could also change this into a inline function.
inline uint8_t getBit(uint8_t data, uint8_t bitPos) __attribute__((always_inline));
inline uint8_t getBit(uint8_t data, uint8_t bitPos){
data >>= bitPos;
data &= 0x01;
return data;
}
Or we could do the entire thing as a macro.
#define GET_BIT(TO_VAR, FROM_VAR, BIT_NUM) {\
TO_VAR=FROM_VAR;\
TO_VAR>>=BIT_NUM;\
TO_VAR&=0x01;\
}
Now looking at the three way of doing basicly the same way, one might wonder wy! So let's look at the assembler code that gcc produces from these examples.
[edit] Links
[edit] Function name abstraction with macros
#include <avr/io.h>
int cnt = 0;
//#define DOUBLE_INC
#ifdef DOUBLE_INC
#define function functionNr2
#else
#define function functionNr1
#endif
void functionNr1(){
cnt++;
}
void functionNr2(){
cnt+=2;
}
int main(void){
while(1){
function();
}
}
Note: This can be seen in the USB Firmware in the schedule part, the scheduler.c only calls Scheduler_task_1, Scheduler_task_2 etc but those name is never seen in the code since they are translated in conf_scheduler.h with "#define Scheduler_task_1 usb_task".
[edit] The USB CDC Atmel example
Note: For some reason Atmel has choosed not to publish some of the datasheets regarding their usb-firmware, including a bare bone version of the code. Please also note that the firmware is NOT under an open source license witch probably makes it illegal to make a complete documentation of the code (since that documentation would publish the secret code), so the sections below will focus on how to use the code (only publish half code examples).
[edit] Recommended reading
- doc7603.pdf - AVR329: USB Firmware Architecture, (This document can be found on the UsbKey in dir \starterkits\STK525-USBKEY\Docs)
- doc7619.pdf - AVR271: USB CDC Demonstration UART to USB Bridge
- doc7675.pdf - AVR276: USB Software Library for AT90USBxxx Microcontrollers
- USB 2.0 Specification, (www.usb.org) in file usb_20.pdf read chapter 9
- Universal Serial Bus Class Definitions for Communication Devices, (www.usb.org)
[edit] Test out the USB CDC Demonstration UART to USB Bridge
- Read doc7619.pdf and download at90usb128-demo-cdc-1_0_3.zip
- Burn the UsbKey with the cdc.a90 file
- Start the UsbKey (driver is located in at90usb128-demo-cdc\inf)
- Open the Device Manager and look under the Ports (COM & LPT) section, the device should be there.
- Remember what com-port-number the device got.
- Open HyperTerminal (hypertrm.exe) with the com-port-number found in the device manager
- On the UsbKey, move the joystick around, press the joystick down and press the HWB button.
- Each action produces a different string.
Later, we change and recompile the demo code.
Our change will make "Hello world!" appear when we press the HWB button.
[edit] Rebuild the demo program
- Unzip at90usb128-demo-cdc-1_0_3.zip and put the at90usb128-demo-cdc dir where you want it to be.
- Edit at90usb128-demo-cdc\at90usb128\demo\cdc\gcc\cdc.aps and replace all C:\Atmel\at90usb128-demo-cdc\... to your new path C:\my\own\dir\at90usb128-demo-cdc\...
- Open the cdc.aps with AVR Studio and try to compile (it should complile ok)
- Edit files config.h, Makefile and cdc_task.c (as described below)
Now we have a nice little program as a base to debug with, with a usb printf.
[edit] Makefile
From:
## Objects that must be built in order to link OBJECTS = usb_specific_request.o cdc_task.o usb_descriptors.o main.o power_drv.o usb_drv.o usb_task.o usb_standard_request.o usb_device_task.o scheduler.o stk_525.o uart_lib.o uart_usb_lib.o ... stk_525.o: ../../../lib_board/stk_525/stk_525.c $(CC) $(INCLUDES) $(CFLAGS) -c $<
To:
## Objects that must be built in order to link OBJECTS = usb_specific_request.o cdc_task.o usb_descriptors.o main.o power_drv.o usb_drv.o usb_task.o usb_standard_request.o usb_device_task.o scheduler.o usb_key.o uart_lib.o uart_usb_lib.o ... usb_key.o: ../../../lib_board/usb_key/usb_key.c $(CC) $(INCLUDES) $(CFLAGS) -c $< ##stk_525.o: ../../../lib_board/stk_525/stk_525.c ## $(CC) $(INCLUDES) $(CFLAGS) -c $<
[edit] config.h
From:
//! To include proper target hardware definitions, select //! target board (USBKEY or STK525) #define TARGET_BOARD USBKEY #if (TARGET_BOARD==USBKEY) //! @warning for #define USBKEY_HAS_321_DF, only first prototypes versions have AT45DB321C memories //! should be undefined for std series #define USBKEY_HAS_321_DF #include "lib_board\usb_key\usb_key.h"
To:
//! To include proper target hardware definitions, select //! target board (USBKEY or STK525) //#define TARGET_BOARD STK525 #define TARGET_BOARD USBKEY #if (TARGET_BOARD==USBKEY) //! @warning for #define USBKEY_HAS_321_DF, only first prototypes versions have AT45DB321C memories //! should be undefined for std series //! #define USBKEY_HAS_321_DF //! The memories are AT45DB6420-CNH on my unit -Bob.. #include "lib_board\usb_key\usb_key.h"
[edit] cdc_task.c
This change is just to prove that we really recompiled and changed the program.
From:
while (rx_counter)
{
uart_putchar(uart_usb_getchar()); // loop back USB to USART
Led3_toggle();
}
...
if(Is_hwb())
printf("Hello from AT90USBXXX !\r\n");
To:
while (rx_counter)
{
//uart_putchar(uart_usb_getchar()); // loop back USB to USART
uart_usb_putchar(uart_usb_getchar()); //loop back USB to USB
Led3_toggle();
}
...
if(Is_hwb())
printf("Hello world!\r\n");
[edit] Combining Mass Storage AND CDC Interface
This is described in this turorial found on AVR Freaks.
