Notes about AVR development that I have found useful. great reference I have found for working with AVR. http://web.engr.oregonstate.edu/~traylor/ece473/lectures/
add more also include warning for the spi fuse
fuses can be included in the ELF executable using <avr/io.h> like so
#include <avr/io.h>
FUSES =
{
LFUSE_DEFAULT, // .low
(FUSE_BOOTSZ0 & FUSE_BOOTSZ1 & FUSE_EESAVE & FUSE_SPIEN & FUSE_JTAGEN), // .high
EFUSE_DEFAULT, // .extended
};
int main(void)
{
return 0;
}the <avr/io.h> lib already includes all the fuse info
a useful fuse calculator is at FuseCalc
to read the fuses on a ELF file use avr-objdump -s -j .fuse <ELF file>
Basic IO is easier if you use <avr/io.h>, This allow access to ports on specific chips without worrying about the address. To set a register as input or output use DDRx where x is the port. To assert a value use PORTx. Read pins by simply assigning from PIND.
- Output is set with a high bit (male) and input is set with a low bit.
- To set PortB to have output on pins P3 and P7 you would DDRB = 0b10001000;.
- To then assert a low on pin7 and a high on pin3 use PORTB = 0b00001000;.
- Output integers with uint8_t i= 0x08; PORTB = i;
- Set port D as input and read values into to i. DDRD = 0b00000000 uint8_t i = PIND
- PINx will read inputs including telling you what you set for output values
- PA0 PB1 PC2 are all defined as 0 1 2 in portpins.h which is included with io.h also PIN3 = 3
interact with individual pin on the port by using the << bit shift operator. | Say you need 0,2,4,6 pins to be as input and 1,3,5,7 as output. Then we do like this:
DDRD=0; //reset all bits to zero
//using bit shift "<<" operation and logical OR to set bits 1,3,5,7 to "1"
DDRD |=(1<<1)|(1<<3)|(1<<5)|(1<<7);
//Then we can set them high
PORTD |=(1<<1)|(1<<3)|(1<<5)|(1<<7);
//or clear them
PORTD &=~((1<<1)|(1<<3)|(1<<5)|(1<<7));
//Old way was `void sbi(uint8_t port, uint8_t bit)` or `cbi(port,bit)` to clear the bit
//Read input on pins
DDRD &=~((1<<1)|(1<<3)); //This clears bits 1 and 3 of port direction register
i=PIND; //reads all 8 pins of port D
//Old way was `uint8_t bit_is_clear(uint8_t port, uint8_t bit)` or `bit_is_set`
//Define a custom function to turn on and off an LED on pin number nr on PORTB
#define LED_ON(nr) PORTB &= ~(1 << (nr))
#define LED_OFF(nr) PORTB |= (1 << (nr))http://winavr.scienceprog.com/avr-gcc-tutorial/avr-usart-explained.html
See :ref:`serial` to connect to a serial port.
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106&sid=20d788f89772f7a108c5d6a8f8655654 http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=68302
Pulse Width Modulation (PWM) is very useful for many applications, particularly motor control. Again this comes down to setting registers. These registers are TCCRxA, TCCRxB, OCRx.
Warning
don't forget to still set the DDRx register for output.
| TCCRxA | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | |||||
| COMxA1 | COMxA0 | COMxB1 | COMxB0 | FOCxA | FOCxB | WGM11 | WGM10 | |||||
- COMxA1/A0 and COMxB1=B0 control the output compare pins OCxA and OCxB
| COMxA1/B1 | COMxA0/B0 | Description |
|---|---|---|
| 0 | 0 | Normal port operation, no PWM |
| 0 | 1 | if WGM modes are set, Toggle on OCxA on compare match, OCxB normal operation. |
| 1 | 0 | Clear OCxA/B on upcount,(0x00 gives 0pwm) Set OCxA/B on downcount |
| 1 | 1 | Set OCxA/B on upcount, (0xFF gives 0pwm) Clear OCxA/B on downcount |
- FOCxA, FOCxB are not needed for phase correct PWM (motor control)
- WGM explained later
| TCCRxB | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | |||||
| ICNCx | ICESx | xxxxx | WGM13 | WGM12 | CS12 | CS11 | CS10 | |||||
- ICNCx, ICESx, xxxxx are not used in PWM mode
- WGM wave generation mode, for motor control set (1<<WGM10)
- CS12-10 sets the prescaler according to the following
| CS12 | CS11 | CS10 | Scaling factor |
|---|---|---|---|
| 0 | 0 | 0 | PWM off |
| 0 | 0 | 1 | clk_io/1 |
| 0 | 1 | 0 | clk_io/8 |
| 0 | 1 | 1 | clk_io/64 |
| 1 | 0 | 0 | clk_io/256 |
| 1 | 0 | 1 | clk_io/1024 |
| 1 | 1 | 0 | ext clk |
| 1 | 1 | 1 | ext clk |
This is the output compare register value. This is the register that sets the PWM value, for example OCR0A = 255; will either turn it full on or full off depending on settings.
// init the PWM for attiny2313
DDRB|=(1<<PB2); // set output
DDRD|=(1<<PD5); // set output
TCCR0A|= (1<<COM0A1)|(1<<COM0A0) // OC0A with inverse style PWM
|(1<<COM0B1)|(0<<COM0B0);// OC0B with regular (0xFF = full power) PWM
TCCR0A|= (1<<WGM10); // Set up wave generation for motors
TCCR0B|= (1<<CS11); // Set Prescaler to CLK/8
while(1)
{ // Cycle through different brightnesses on two LED's
int i;
int delay = 10;
for(i=0;i<256;i++){
OCR0A = i;
OCR0B = i;
_delay_ms(delay);
}
}Most chips come with built in of ADC. Setting these up one needs to know the special names that are defined in io.h or from the datasheet. The ADCSRA sets things like voltage refs and prescalers. ADMUX selects things like the ADC channel and justification. The following shows the values that can be set for these ADC registers.
| ADMUX | |||||||
|---|---|---|---|---|---|---|---|
| Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| REFS1 | REFS0 | ADLAR | MUX4 | MUX3 | MUX2 | MUX1 | MUX0 |
| REFS1 | REFS0 | Voltage Reference Selection |
|---|---|---|
| 0 | 0 | ARef internal Vref Turned off |
| 0 | 1 | AVCC |
| 1 | 0 | Reserved |
| 1 | 1 | Internal 2.56 Voltage Reference |
- REFSx sets the voltage reference source, for measuring 0-5v use ADMUX|=(1<<REFS0)
- ADLAR sets the justification of the data, useful for using only 8bit data or the full xxbits.
- Read only ADCH (8bit resolution) use ADMUX|=(1<<ADLAR);
- Read ADCH and ADCL (full resolution) by shifting into a word or simply with ADCW.
- MUXx sets the channel selection pins. This is a BCD number so for channal 5 set ADMUX|=0d05;
- Individual MUX pins may be toggled by already defined names like ADMUX|=(1<<MUX0); selects an odd ADC
- Clear Selections/Select ADC0 with ADMUX &= 0xF8;
| ADCSRA | |||||||
|---|---|---|---|---|---|---|---|
| Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| ADEN | ADSC | ADATE | ADIF | ADIE | ADPS2 | ADPS1 | ADPS0 |
- ADEN enables the ADC. This must be set to turn the ADC on.
- ADSC starts the single conversion process, Also flags a completion.
- Start the single conversion with ADCSRA|=(1<<ADSC)
- wait until conversion is done while (ADCSRA & (1<<ADSC)){/* wait */};
- Read value now that the bit is cleared val = ADCH
- This bit is ignored/doesn't change when in Free Running mode
- ADATE or ADFR Enable autotriggering,
- ADFR used for :ref:`Free Running` mode of the ADC
- ADATE used for more generic things like analog comparators or counter overflows.
- ADIF flags the completion of on interrupt.
- May not work on some chips. Not really needed if ADSC is used properly.
- ADIE will enable interrupts for the ADC.
- ADPSx sets the prescaling factor for the ADC.
- Should be between 50khz and 200khz for full resolution
- Clock derived from system clock. 16Mhz clock/128 factor = 125khz ADC clock
- May substitute resulution for a faster ADC clock
| ADPS2 | ADPS1 | ADPS0 | Division Factor |
|---|---|---|---|
| 0 | 0 | 0 | 2 |
| 0 | 0 | 1 | 2 |
| 0 | 1 | 0 | 4 |
| 0 | 1 | 1 | 8 |
| 1 | 0 | 0 | 16 |
| 1 | 0 | 1 | 32 |
| 1 | 1 | 0 | 64 |
| 1 | 1 | 1 | 128 |
This mode is easier to operate and deal with for many reasons, however it is not as fast (but still by no means slow).
Sample Code:
int readADC10bit(word channel)
{
ADMUX|=channel; // Pick a channel
ADCSRA |= (1<<ADSC); // Start the conversion
while(ADCSRA & (1<<ADIF)){}; // wait for the conversion to finish
return(ADC);
}This mode allows autoupdating of values in the ADC but requires much more care in dealing with the interrupts and the timing.
memory can be SRAM (variables) or FLASH and EEPROM You can access the program memory segments (FLASH) using PROGMEM. useful for large lookup tables and the like. see http://winavr.scienceprog.com/avr-gcc-tutorial/working-with-avr-microcontroller-flash-memory-using-winavr-gcc.html
also you can use EEPROM segments for that is nonvolatile see http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=38417
basically to use EEPROM you must include <avr/eeprom.h> This exposes the following routines to you:
uint8_t eeprom_read_byte (const uint8_t *addr)
void eeprom_write_byte (uint8_t *addr, uint8_t value)
uint16_t eeprom_read_word (const uint16_t *addr)
void eeprom_write_word (uint16_t *addr, uint16_t value)
void eeprom_read_block (void *pointer_ram, const void *pointer_eeprom, size_t n)
void eeprom_write_block (void *pointer_eeprom, const void *pointer_ram, size_t n)fairly straight forward to use just do this
#include <avr/eeprom.h>
void main(void)
{
uint8_t ByteOfData;
ByteOfData = eeprom_read_byte((uint8_t*)46);
}This will read out location 46 of the EEPROM and put it into our new variable named "ByteOfData".
this uses a C90 standard such that.
- int8_t - Signed Char
- uint16_t - Unsigned Int
- uint32_t - Unsigned Long
- int64_t - Signed Long Long
- etc etc
you could also use the usual float int def's however there are a few very minor subtleties about type definition, like inside loops.
Warning
int must be declared outside of loops. for(int i = 0;;) is invalid since it is declared in the loop.
To use these include avr/interrupt.h. | Interrupts are used by calling the special function ISR(). This function takes as input the specified peripheral. Take the following example for the ADC interrupt:
#include <avr/interrupt.h>
ISR(ADC_vect)
{
// user code here
}This is asserted when an A2C conversion completes.
Normally an uncaught interrupt will call the reset interrupt vector, this can be overridden with the BADISR_vect variable as follows.
#include <avr/interrupt.h>
ISR(BADISR_vect)
{
// user code here
}refer to http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html for more details, and to see the peripheral ID for any device (huge table).
Warning
Do not use SIGNAL() in new code. Use ISR() instead.
Here is a basic blinking code
#define F_CPU 1000000UL // Clock Frequency = 1Mhz
#include <inttypes.h>
#include <avr/io.h>
#include <util/delay.h>
int main(){ // The main function
DDRB = 0b11111111; // Set all the pins of PortB as output
while (1) { // Set up an infinite loop
PORTB |= (1 << PB0); // Turn on LED1 (0b10000000)
_delay_ms(50); // Wait
PORTB &=~ (1 << PB0); // Turn off LED1
_delay_ms(50); // Wait
}In Ubuntu you need
- gcc-avr
- avr-libc
- binutils-avr
- gdb-avr
- avrdude
- all-Packages
Ladyada has an excellent programmer in the USBtinyISP. There are permission problems at first but this is easily remedied with the following code (creates a udev rule).
subsys="SUBSYSTEM==\"usb\""
ID="SYSFS{idVendor}==\"1781\",SYSFS{idProduct}==\"0c9f\""
groups="GROUP=\"adm\", MODE=\"0666\""
sudo echo $subsys, $ID, $groups > /etc/udev/rules.d/90-usbtinyisp.rules
sudo restart udevAnd thats it, no more root needed for programming anymore, may need to pick a different value for group="adm" to incorporate all users based on your setup.
The programmer pinout is a standard but double check orientation is correct. Also MISO connects to MISO on the chip not a MISO to MOSI situation, just match the names with the names from the data sheet. Also note that the rectangle notch (clip) intended to ensure polarity and as a clip is along the odd numbered side of the board.
This is not so helpful for breadboarding however so I like to make an adaptor that looks something like the following.
The last important part of the compilation is the Makefile. This is set up for the USBTinyISP, attiny2313 and the source file helloworld.c.
The Arduino is an Atmega168 or Atmega328 or there are a few other derivatives. It has a large user base and a good amount of predeveloped code making it easy to prototype a design. The software can be downloaded from there website http://www.arduino.cc .
In ubuntu it is nice to set up a udev rule that keeps it persistent as a USB/Serial device. I like to map it to /udev/Arduino with the following (works for most boards).
kern="kernal==\"ttyUSB*\""
ID="SYSFS{idVendor}==\"0403\",SYSFS{idProduct}==\"6001\""
groups="GROUP=\"adm\", MODE=\"0666\", SYMLINK=\"Arduino\""
sudo echo $kern, $ID, $groups > /etc/udev/rules.d/95-arduino.rules
sudo restart udevEverything should work now assuming you select the correct board. The board can also be reprogrammed with the bootloader from the GUI. The bootloader adds alot of overhead with its libraries although you can still bit twiddle like normal. The pin out maps as follows for direct port access.



