Before the Raspberry Pi and the Arduino, there was a 'catch 22' situation. An AVR could only be programmed with an already programmed AVR, or with an expensive ($30,-) hardware AVR programmer. The Arduino uses a bootloader that makes programming over serial/USB possible, but you couldn't just buy a $2,- AVR and simply get started. Since Gordon patched avrdude, we can use the Raspberry Pi as an AVR programmer. Not only to program the ATmega328p on the Gertboard, but in fact *any* AVR you fancy.
You'll have to dive in the datasheet (found here: http://www.atmel.com/Images/doc8161.pdf). There isn't much hand-holding, so it's not for the faint of heart. But it's very rewarding, and you'll actually learn something.
Here's how to get started.
No matter what, you need Gordon's patched avrdude. Go to: https://projects.drogon.net/raspberry-p ... ation-isp/ and follow instructions on how to install avrdude. You can safely skip everything else. For Raspbian, I'll repeat it here:
Code: Select all
cd /tmp
wget http://project-downloads.drogon.net/gertboard/avrdude_5.10-4_armhf.deb
sudo dpkg -i avrdude_5.10-4_armhf.deb
sudo chmod 4755 /usr/bin/avrdudeCode: Select all
sudo apt-get install gcc-avr binutils-avr avr-libcConnect the AVR on the Gertboard to the Raspberry Pi GPIO pins as instructed in the 'Gertboard User Manual', Figure 23 (page 31). In anticipation of the blinky 'sketches' I'll post below, also connect 3 wires from AVR pins PD7, PD6 and PD5 to 3 LED's of your choice on the Gertboard. I connected them to buffer U3 (B1, B2, B3) configured as outputs.
In a new directory on the Raspberry Pi, save the following as 'Makefile' (with a capital M). It's not the most flexible Makefile you ever saw, but I tried to keep things as short and simple as possible.
Code: Select all
# ======================================================================
# Makefile for ATmega328p on Gertboard
# ======================================================================
TARGET = blinky
SRC = blinky.c
# ======================================================================
# You shouldn't have to edit below this line
# ======================================================================
AVRDUDE = avrdude
CC = avr-gcc
OBJCOPY = avr-objcopy
MCU = atmega328p
CFLAGS = -Wall -Os -std=gnu99 -mmcu=$(MCU) -I.
AVRDUDE_PROGRAMMER = gpio
AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
AVRDUDE_FLAGS = -p $(MCU) -c $(AVRDUDE_PROGRAMMER) -v
all:
$(CC) $(SRC) -o $(TARGET) $(CFLAGS)
$(OBJCOPY) -O ihex $(TARGET) $(TARGET).hex
rm -f $(TARGET)
clean:
rm -f $(TARGET).hex
install: all
$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH)
fuse:
$(AVRDUDE) $(AVRDUDE_FLAGS) -U lfuse:w:0xE7:m -U hfuse:w:0xD9:m -U efuse:w:0x07:m
.PHONY: all clean install fuseCode: Select all
make fuseNext, copy and paste the following code in a file named 'blinky.c', and save it in the same directory:
Code: Select all
#define F_CPU 12000000L
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRD = 0xFF; // PORTD is output
PORTD = 0x00; // LED's are off
for (;;) {
PORTD ^= 0xFF; // invert the pins
_delay_ms(500); // wait half a second
}
return 0;
}
Code: Select all
make installThat's all nice and well, but also pretty silly. The '_delay_ms(500);' function halts our application completely, and we're not able to do anything else while the LED's are blinking. Let's set up some interrupts, to turn this into something more fancy and flexible.
Copy and paste the following code in 'blinky.c':
Code: Select all
#define F_CPU 12000000L
#include <avr/io.h>
#include <avr/interrupt.h>
#define LED_MASK ((1 << 7) | (1 << 6) | (1 << 5))
// prototypes
void init(void);
void init_timer0(void);
void init_timer1(void);
void init_timer2(void);
// main
int main(void)
{
init();
for (;;) {
; // nop
}
return 0;
}
// CTC interrupt for Timer 0
volatile int interval0;
volatile int counter0 = 0;
float sec0 = 0.1;
ISR(TIMER0_COMPA_vect)
{
interval0 = (int) (sec0 * 7500); // 7500 is 1 second
if (counter0 == interval0) {
PORTD ^= (1 << 7);
counter0 = 0;
}
counter0++;
}
// CTC interrupt for Timer 1
volatile int interval1;
volatile int counter1 = 0;
float sec1 = 0.2;
ISR(TIMER1_COMPA_vect)
{
interval1 = (int) (sec1 * 1000); // 1000 is 1 second
if (counter1 == interval1) {
PORTD ^= (1 << 6);
counter1 = 0;
}
counter1++;
}
// CTC interrupt for Timer 2
volatile int interval2;
volatile int counter2 = 0;
float sec2 = 0.5;
ISR(TIMER2_COMPA_vect)
{
interval2 = (int) (sec2 * 1500); // 1500 is 1 second
if (counter2 == interval2) {
PORTD ^= (1 << 5);
counter2 = 0;
}
counter2++;
}
// Setup
void init(void)
{
// setup outputs for PORTD
DDRD = LED_MASK;
// init the timers
init_timer0();
init_timer1();
init_timer2();
// enable interrupts
sei();
}
void init_timer0(void)
{
// setup timer 0 for CTC
TCCR0A |= (1 << WGM01); // MAX counter = value OCR0A (Mode 2 / CTC)
//TCCR0B |= 0x01; // prescaler = 1; // TCCR0B |= (1 << CS00);
TCCR0B |= 0x02; // prescaler = 8; // TCCR0B |= (1 << CS01);
//TCCR0B |= 0x03; // prescaler = 64; // TCCR0B |= (1 << CS01) | (1 << CS00);
//TCCR0B |= 0x04; // prescaler = 256; // TCCR0B |= (1 << CS02);
//TCCR0B |= 0x05; // prescaler = 1024; // TCCR0B |= (1 << CS02) | (1 << CS00);
// when OCR0A = 200 and prescaler = 8, TIMER0_COMPA_vect interrupt is triggered 7500 times/sec
// because: 12000000 / 8 / 200 = 7500;
OCR0A = 200; // OCR0A is 8 bit, so max 255
// trigger interrupt when Timer0 == OCR0A
TIMSK0 = 1 << OCIE0A;
}
void init_timer1(void)
{
// setup timer 1 for CTC
TCCR1B |= (1 << WGM12); // MAX counter = value OCR1A (Mode 4 / CTC)
//TCCR1B |= 0x01; // prescaler = 1; // TCCR1B |= (1 << CS10);
TCCR1B |= 0x02; // prescaler = 8; // TCCR1B |= (1 << CS11);
//TCCR1B |= 0x03; // prescaler = 64; // TCCR1B |= (1 << CS11) | (1 << CS10);
//TCCR1B |= 0x04; // prescaler = 256; // TCCR1B |= (1 << CS12);
//TCCR1B |= 0x05; // prescaler = 1024; // TCCR1B |= (1 << CS12) | (1 << CS10);
// setup period
// when OCR1A = 2400 and prescaler = 8, TIMER1_COMPA_vect interrupt is triggered 1000 times/sec
// because: 12000000 / 8 / 2400 = 1000;
OCR1A = 2400; // OCR1A is 16 bit, so max 65535
// trigger interrupt when Timer1 == OCR1A
TIMSK1 = 1 << OCIE1A;
}
void init_timer2(void)
{
// setup timer 1 for CTC
TCCR2A |= (1 << WGM21); // MAX counter = value OCR2A (Mode 2 / CTC)
//TCCR2B |= 0x01; // prescaler = 1; // TCCR2B |= (1 << CS20);
//TCCR2B |= 0x02; // prescaler = 8; // TCCR2B |= (1 << CS21);
TCCR2B |= 0x03; // prescaler = 32; // TCCR2B |= (1 << CS21) | (1 << CS20);
//TCCR2B |= 0x04; // prescaler = 64; // TCCR2B |= (1 << CS22);
//TCCR2B |= 0x05; // prescaler = 128; // TCCR2B |= (1 << CS22) | (1 << CS20);
//TCCR2B |= 0x06; // prescaler = 256; // TCCR2B |= (1 << CS22) | (1 << CS21);
//TCCR2B |= 0x07; // prescaler = 1024; // TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
// setup period
// when OCR2A = 250 and prescaler = 32, TIMER2_COMPA_vect interrupt is triggered 1500 times/sec
// because: 12000000 / 32 / 250 = 1500;
OCR2A = 250; // OCR2A is 8 bit, so max 255
// trigger interrupt when Timer2 == OCR2A
TIMSK2 = 1 << OCIE2A;
}
Code: Select all
make installAs it is set up now, the function ISR(TIMER0_COMPA_vect) is being called 7500 times per second. We keep track of our own variable 'counter0', that's incremented every time the function is called. When that counter0 equals 750 (1/10th of 7500) it inverts pin D7. This way you can call any function, any x amount of time by introducing other 'interval' variables.
The same goes for the other 2 'Interrupt Service Routines'. If you need other values, play around with the prescalers in the init_timerx functions, in combination with the values in the OCRxA bits. I set up all 3 timers that are available on the ATmega328p to trigger a 'compare' ISR. As you might realise, you can do a lot with just 1 timer, so it's really overkill, but here you go. I can imagine you'll want to use other timers for things like PWM, so mix and match as you see fit.
For a full (and much better) explanation of how this all works, look here: http://www.engblaze.com/microcontroller ... nterrupts/
I hope this is useful for someone.