S-AVR » Testing/Programming
Step 1: Tool-Check
You will need:
- S-AVR
- A USBtinyISP programmer
- A copy of WinAVR
Step 2: Verify Construction
Power it up first without the micro in its socket, just in case. With the power switch in the off (out) position, connect E27 to ground and D27 to +5v. Now press the power switch so it locks in the on (in) position. With no micro, the board should draw no current. With the reset button pressed it should draw about 0.5mA. Turn it off again and insert the micro. With the micro in, it should draw about 25mA. Turn it off again and connect your programmer. Make sure the notch on the programmer's IDC connector faces towards the micro. Now power up again.
Step 3: Try Programming
In some convenient working directory, create Makefile:
# Makefile
TARGET = firmware
MCU = atmega162
LFUSE = 0xfd
HFUSE = 0xd9
F_CPU = 16000000
SRCS = $(shell ls *.c)
OBJS = $(SRCS:%.c=$(OBJDIR)/%.o)
LIBS =
CC = avr-gcc
OBJDIR = .build
DEPDIR = .deps
CFLAGS = \
-O3 -mmcu=$(MCU) -funsigned-char -funsigned-bitfields -ffunction-sections \
-fpack-struct -fshort-enums -finline-limit=20 -Wall -Wstrict-prototypes \
-Wundef -std=gnu99 -Wall -pedantic
LDFLAGS = -mmcu=$(MCU) -Wl,--relax -Wl,--gc-sections -lm
all: $(TARGET).hex
$(TARGET).hex: $(OBJS)
$(CC) $(LDFLAGS) -Wl,-Map=$(OBJDIR)/$(TARGET).map,--cref -o $(OBJDIR)/$(TARGET).elf $(OBJS) $(LIBS)
avr-objcopy -O ihex -R .eeprom $(OBJDIR)/$(TARGET).elf $(TARGET).hex
$(OBJDIR)/%.o : %.c
$(CC) -c $(CFLAGS) -MMD -MP -MF $(DEPDIR)/$(@F).d -Wa,-adhlns=$(OBJDIR)/$<.lst $< -o $@ -DF_CPU=$(F_CPU)
clean: FORCE
rm -rf $(OBJDIR) $(TARGET).hex $(DEPDIR)
fuse: FORCE
avrdude -c usbtiny -p $(MCU) -U lfuse:w:$(LFUSE):m
avrdude -c usbtiny -p $(MCU) -U hfuse:w:$(HFUSE):m
flash: $(TARGET).hex
avrdude -c usbtiny -p $(MCU) -U flash:w:$<
-include $(shell mkdir -p $(OBJDIR) $(DEPDIR) 2>/dev/null) $(wildcard $(DEPDIR)/*)
FORCE:
Note: The indentation in the Makefile should be tabs, not spaces. If you cut&paste the Makefile directly from this page you will need to search&replace each block of four spaces with a tab character. Alternatively you can fetch the file directly using this link.
Now if you run make fuse, you should see something like this:
C:\temp\try>make fuse
avrdude -c usbtiny -p atmega162 -U lfuse:w:0xfd:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e9404
avrdude: reading input file "0xfd"
avrdude: writing lfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of lfuse written
avrdude: verifying lfuse memory against 0xfd:
avrdude: load data lfuse data from input file 0xfd:
avrdude: input file 0xfd contains 1 bytes
avrdude: reading on-chip lfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of lfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
avrdude -c usbtiny -p atmega162 -U hfuse:w:0xd9:m
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.02s
avrdude: Device signature = 0x1e9404
avrdude: reading input file "0xd9"
avrdude: writing hfuse (1 bytes):
Writing | ################################################## | 100% 0.00s
avrdude: 1 bytes of hfuse written
avrdude: verifying hfuse memory against 0xd9:
avrdude: load data hfuse data from input file 0xd9:
avrdude: input file 0xd9 contains 1 bytes
avrdude: reading on-chip hfuse data:
Reading | ################################################## | 100% 0.00s
avrdude: verifying ...
avrdude: 1 bytes of hfuse verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
C:\temp\try>
You have now set the appropriate fuse bits for this board. The fuse bits are just configuration options for the MCU. You can find a full description of the meaning of these bits in the ATmega162 datasheet.
The default for hfuse is 0x99; we have additionally set bit six to give 0xD9, which disables the JTAG interface. This frees up the four high-order bits of port C (PC4...PC7), which would otherwise be used by the JTAG interface by default. You can also disable JTAG from software by setting bit 7 of the MCUCSR register twice in quick succession, like this:
MCUCSR |= (1<<JTD);
MCUCSR |= (1<<JTD);
The default value for lfuse is 0x62; the change to 0xFD here are to switch the MCU from its fairly conservative clocking defaults and startup time to start very quickly with a full 16MHz clock.
Step 4: Compile Some Code
In your working directory, create main.c:
// main.c
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sleep.h>
#define F_CPU 16000000
ISR(TIMER1_COMPA_vect) {
PORTA = PORTA++;
}
void init(void) {
DDRA = 0xFF;
TCCR1A = 0x00;
//TCCR1B = (1<<WGM12 | 1<<CS10); // Start timer 1: WGM=4 (CTC on OCR1A); no prescalar
//TCCR1B = (1<<WGM12 | 1<<CS11); // Start timer 1: WGM=4 (CTC on OCR1A); prescalar=8
//TCCR1B = (1<<WGM12 | 1<<CS10 | 1<<CS11); // Start timer 1: WGM=4 (CTC on OCR1A); prescalar=64
//TCCR1B = (1<<WGM12 | 1<<CS12); // Start timer 1: WGM=4 (CTC on OCR1A); prescalar=256
TCCR1B = (1<<WGM12 | 1<<CS10 | 1<<CS12); // Start timer 1: WGM=4 (CTC on OCR1A); prescalar=1024
OCR1A = F_CPU/1024;
TIMSK |= (1<<OCIE1A); // Enable timer 1 interrupt
sei();
}
int main(void) {
init();
for ( ; ; ) {
sleep_mode();
}
return 0;
}
...and then compile it:
C:\temp\try>make
avr-gcc -c -O3 -mmcu=atmega162 -funsigned-char -funsigned-bitfields -ffunction-sections -fpack-struct -fshort-enums -finline-limit=20 -Wall -Wstrict-prototypes -Wundef -std=gnu99 -Wall -pedantic -MMD -MP -MF .deps/main.o.d -Wa,-adhlns=.build/main.c.lst main.c -o .build/main.o
avr-gcc -mmcu=atmega162 -Wl,--relax -Wl,--gc-sections -lm -Wl,-Map=.build/firmware.map,--cref -o .build/firmware.elf .build/main.o
avr-objcopy -O ihex -R .eeprom .build/firmware.elf firmware.hex
C:\temp\try>
No errors, so let's write the compiled code to the MCU's flash memory.
Step 5: Upload the Code to the MCU
Upload the code:
C:\temp\try>make flash
avrdude -c usbtiny -p atmega162 -U flash:w:firmware.hex
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.02s
avrdude: Device signature = 0x1e9404
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "firmware.hex"
avrdude: input file firmware.hex auto detected as Intel Hex
avrdude: writing flash (262 bytes):
Writing | ################################################## | 100% 0.72s
avrdude: 262 bytes of flash written
avrdude: verifying flash memory against firmware.hex:
avrdude: load data flash data from input file firmware.hex:
avrdude: input file firmware.hex auto detected as Intel Hex
avrdude: input file firmware.hex contains 262 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.48s
avrdude: verifying ...
avrdude: 262 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done. Thank you.
C:\temp\try>
Now the MCU is programmed, and it is running the program. You can reset it with the reset button to have it start executing from the beginning. You can turn the board off with the power switch, disconnect the programmer and power it up again, and it will start running your code. Your code is stored in flash memory on the MCU, and is semi-permanent (the ATmega162 datasheet quotes a flash data retention of 100 years at 25°C).
But what is it the program doing? And what proof is there that it is actually executing? Well, this board has no indicators on it to allow us to keep track of the code the MCU is running, but if you read through the code you will see that it sets up port A (pins 39...32) as output lines, and then sets up a timer interrupt to fire every second. The Interrupt Service Routine or ISR for that interrupt just increments the value of PORTA, which is initialised to zero on reset. So we can't see the program running, because all it does is counts in binary on port A, which is not connected to anything. But if you measure the voltage on pin 39 (PA0) using a voltmeter, you will see that it changes state every second. It spends one second at +5v, the next at 0v, repeated forever. Pin 38 (PA1) changes every 2s. Pin 37 (PA2) changes every 4s. Pin 36 (PA3) changes every 8s, and so on up to pin 32 (PA7) which changes every 128s (02m08s).
The next step is to build a separate board with eight LEDs, which will display the state of port A visually.