Skip to content

Instantly share code, notes, and snippets.

@bertabus-zz
Last active August 29, 2015 14:13
Show Gist options
  • Select an option

  • Save bertabus-zz/d098bccfec6bc2004421 to your computer and use it in GitHub Desktop.

Select an option

Save bertabus-zz/d098bccfec6bc2004421 to your computer and use it in GitHub Desktop.

AVR Programming Notes

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/

fuses

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

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))

USART

http://winavr.scienceprog.com/avr-gcc-tutorial/avr-usart-explained.html

See :ref:`serial` to connect to a serial port.

Timers

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

PWM

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

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

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

OCRxA/B

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.

Sample Code

// 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);
  }
}

Analog Digital Converter

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

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

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

Single Conversion

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);
}

Free Running

This mode allows autoupdating of values in the ADC but requires much more care in dealing with the interrupts and the timing.

Memory

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".

datatypes

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.

Interrupts

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.

I2C two wire

Sample attiny helloworld

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
}

AVR toolchain

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 udev

And 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.

https://raw.githubusercontent.com/bertabus/Gist_Static/master/AVR/ISP.jpg

This is not so helpful for breadboarding however so I like to make an adaptor that looks something like the following.

https://raw.githubusercontent.com/bertabus/Gist_Static/master/AVR/ISP_header_side.jpg

https://raw.githubusercontent.com/bertabus/Gist_Static/master/AVR/ISP_header_bottom.jpg

The last important part of the compilation is the Makefile. This is set up for the USBTinyISP, attiny2313 and the source file helloworld.c.

Arduino

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 udev

Everything 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.

https://raw.githubusercontent.com/bertabus/Gist_Static/master/AVR/ArduinoPinMap.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment