Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Table of Contents
maxLevel4


ATtiny 1-Series AVR Microcontrollers [1]

Image of 5 different ATtiny 1 series microcontrollers in different packages


What are AVR Microcontrollers?

AVR is a family of 8-bit microcontrollers (MCUs) developed by Atmel in 1996, but aquired by Microchip in 2016. Although Atmel has stated that AVR is not an acronym and does not stand for anything, it is commonly accepted AVR is named after its creators, Alf-Egil Bogen and Vegard Wollan RISC, or Advanced Virtual RISC (Reduced Instruction Set Computer) [2] [3]. These AVR MCUs executes most instructions in one clock cycle and therefore consume relatively little power for their speed. This makes them very popular in project prototyping such as in the Arduino UNO and even production embedded systems [4].

Starting in 2016, Microchip has been releasing new generations of ATtiny and ATmega MCUs that have drastically changed how they are programed. These new lines, so far in 2021, are the tinyAVR 0-series,1-series, 2-series and megaAVR 0-series [8]. These new lines share many similarities with the ATxmega line by adding new peripherals, and using new arcitecture, register names, and a programing style that was traditionally reserved for the XMEGA devices [9]. This page will mainly focus on developing firmware for the ATtiny 1-series, specifically the ATtiny3217, in Microchip Studio/Atmel Studio 7 (MS/AS7) with the AVR-GCC Toolchain/Compiler.


Basic Types of AVR Microcontrollers

Series NameImagesPinsFlash Memory (KB)Characteristics 

tinyAVR

(ATtiny)


ATtiny85 [5]
Image of ATtiny85 in DIP package typeImage of ATtiny85 in SOP package typeImage of ATtiny85 in QFN package type


6-32

0.5 - 32
  • Small size
  • Less memory
  • Simple applications

megaAVR

(ATmega)


ATmega328P [6]
Image of ATmega328P in DIP package typeImage of ATmega328P in QFP package typeImage of ATmega328P in QFN package type


28-1004 - 256
  • Many inbuilt peripherals
  • More memory 
  • Moderate to complex applications

XMEGA

(ATxmega)


ATxmega256A3U [7]
Image of ATxmega256A3Y in QFP package typeImage of ATxmega256A3Y in QFN package type


44-10016 - 384
  • More commercial usage
  • Complex and compound applications
  • High speed
  • Large memory
  • Direct memory access (DMA)
  • Inbuilt event system 


   

tinyAVR 0/1/2-series and megaAVR 0-series Naming Convention [10] 

Image of new MCU generation's naming convention graphic using the ATtiny3217





Programming Languages

The 3 most widely used languages for AVR MCUs are C, Arduino C/C++, and Assembly which is also known as Assembler Language and often abrreviated as ASM [11].

Embedded C (AVR-C)

The newer generations of MCUs improve on accessibilty by adding and renaming registers and macros so that their names more clearly describe its function. This descreases the learning curve for beginners who may not know what all the acryonyms from previous MCU generations mean. This style of programming with registers directly accessed without any operating systems or abstraction layers is reguarly refered to as "bare-metal programming." Datasheets are an extremely useful resource when developing in this style as having to check them continuously for the register architectures and their bit names is an almost certainty.


Example AVR-C Code Comparision for Simple Blinking LED on PB3 (Port B Pin 3)
ATtiny85ATtiny3217 (tinyAvr 1-series)


Code Block
languagecpp
firstline1
linenumberstrue
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
	/* 	Set the 4th bit (PB3 is 4th PBx pin since pins start at 0) in
	 	Port B's 8-bit Data Direction Register to make PB3 an output.
	 	The hexadecimal equivalent 0x08 can also be used instead.
	*/
	DDRB = 0b00001000;			
    
	while (1) 
    {
		/*	Set the 4th bit in the Port B register to set 
		 	PB3 high to turn on the LED.
		*/
		PORTB = 0b00001000;
		
		//	Wait for 200ms.
		_delay_ms(200);
		
		/*	Clear the 4th bit in the Port B register to set
		 	PB3 low to turn off the LED.
		*/
		PORTB = 0b00000000;
		
		//	Wait for 200ms.
		_delay_ms(200);
    }
}



Code Block
languagecpp
firstline1
linenumberstrue
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
	/*	Set the Pin 3 bit in the Port B's Data Direction Set Register to make PB3 an output.
	 	PIN3_bm is the bit mask macro for the 4th bit which corresponds with Pin 3.
	*/
	PORTB.DIRSET = PIN3_bm;
	
    while (1) 
    {

		/*	Set the Pin 3 bit in Port B's Output Value Set Register that sets the corresponding bit in
	 	 	Port B's Output Value Register which then drives PB3 high and therefore turns on the LED.
	 	*/
		PORTB.OUTSET = PIN3_bm;
		
		//	Wait for 200ms.
		_delay_ms(200);
		
		/*	Set the Pin 3 bit in Port B's Output Value Clear Register that clears the corresponding bit in
		 	Port B's Output Value Register which then drives PB3 low and therefore turns off the LED.
		*/
		PORTB.OUTCLR = PIN3_bm;
		
		//	Wait for 200ms.
		_delay_ms(200);
    }
}



The "iotn*.h" file ("iotn3217.h" for ATtiny3217) includes all the macros definitions. A table below also contains common acronyms in macros.

This file can be found by higlighting the macro Image of PIN3_BM highlighted and pressing the Go button Image of a Go button in the top right. 

After building the solution at least once, the file can also be found in Solution Explorer > [Your Solution] > [Your Project] > Dependencies


Bit Manipulation using Bitwise Operators
Bitwise Operators Table
OperatorDefinitionExamplesApplicationsApplication ExamplesNotes
&

Bitwise AND:

returns 1 for a postion when both binary numbers have 1 at the same postion

0111 & 1100 = 0100

Bitwise AND can be used to clear or isolate specific bits in a binary number or register without affecting other bits.


Code Block
languagecpp
linenumberstrue
/*	Clear Pin 1's output(set low) without 
	affecting the other Pins' output
*/
PORTB.OUT &= 0b11111101;


When using a ATtiny 0/1/2-series microcontroller, in this specific instance with Port registers, this is not the best way to do this. However, the concept the code demonstrates can be applied to other registers.

The best practice would be to use bit masks and the OUTCLR and OUTSET reigsters. An example of this is in the above table.

|

Bitwise OR:

returns 1 for a postion when either binary numbers have 1 at the same postion

0101 | 0110 = 0111Bitwise OR can be used to set specific bits in an binary number or register without affecting other bits.


Code Block
languagecpp
linenumberstrue
/*	Set the data direction of Pin 1 to be out 
	without affecting the other Pins' data direction
*/
PORTB.DIR |= 0b00000010;


When using a ATtiny 0/1/2-series microcontroller, in this specific instance with Port registers, this is not the best way to do this. However, the concept the code demonstrates can be applied to other registers.

The best practice would be to use bit masks and the DIRCLR and DIRSET reigsters. An example of this is in the above or below tables.

^

Bitwise XOR:

returns 1 for a postion when only one of the binary numbers has 1 at the same postion

1001 ^ 1010 = 0011Bitwise XOR can be used to flip or toggle specific bits in an binary number or register without affecting other bits.


Code Block
languagecpp
linenumberstrue
/*	Flip Pin 1's output(set low if high and high 
	if low) without affecting theother Pins'output
*/
PORTB.OUT ^= 0b00000010;


A common application of this code is to place it in a loop so the Pin can continuously toggle a LED on and off.

When using a ATtiny 0/1/2-series microcontroller, in this specific instance with Port registers, this is not the best way to do this. However, the concept the code demonstrates can be applied to other registers.

The best practice would be to use bit masks and the OUTTGL reigster.

~

Bitwise NOT/ One's Complement:

returns the opposite of the value at each postion of the binary number

~0011 = 1100

Bitwise NOT can be used to flip all the bits of a binary number.

Bitwise NOT are commonly used with a bit mask and bitwise AND to clear the register's bits that are in the bit mask.

An example of this is in the group mask row of the table below.


<<

Bitwise Left Shift:

returns the binary number on the left moved left by the number on the right positions 

4-bit Data Type:

1010 << 2 = 1000

8-bit Data Type:

0000 1010 << 2 = 0010 1000

Bitwise left/right shift can be used to shift a value to the correct postion in a register.

Bitwise left/right shift are commonly used with bit and group postions.

An example of this is in the bit and group position rows of the table below.


>>

Bitwise Right Shift:

returns the binary number on the left moved right by the number on the right postions 

4-bit Data Type:

1010 >> 2 = 0010

8-bit Data Type:

1010 0000 >> 2 = 0010 1000


Bit Manipulation Tools' Acronyms Table
AcronymDefinitionExamplesApplicationsApplication ExampleApplication Example ExplanationExplanation Notes
bm

Bit Mask:

encodes 0b1 at the bit that is used to access the setting or value

0x04

0b00000100

A bit mask is often defined for a register where a bit stores a value or controls a setting.

A bit mask can be used to access or change the value of a bit.


Code Block
languagecpp
linenumberstrue
#define PIN2_bm		0x04
PORTB.DIRSET = PIN2_bm;


This defines a bit mask macro for Pin 2 and sets Port B's Data Direction Set register to equal it. That then sets the corresponding Pin 2 bit in the Port B's Data Direction register to make Pin 2 an output.


bp

Bit Position:

the location where a bit starts from when counting right to left in a register

2

A bit position is often defined for a register where a bit stores a value or controls a setting.

A bit position can be used shift a value into the correct bit position in a register.


Code Block
languagecpp
linenumberstrue
#define PIN2_bp		2
PORTB.DIRSET = (1 << PIN2_bp);


This defines a bit position macro for Pin 1 and bitwise left shifts the value 1 to the Pin 1's bit position. Now with 1 in the correct location, it sets Port B's Data Direction Set register to equal it. That then sets the corresponding Pin 1 bit in the Port B's Data Direction register to make Pin 1 an output.


gm

Group Mask:

encodes 0b1 at each of the bits that is used to access the setting or value

0x07

0b00000111

A group position is often defined for a register where multiple bits store a single value or control a single setting.

A group mask can be used to access or change the value at multiple bits.


Code Block
languagecpp
linenumberstrue
#define PORT_ISC_gm		0x07
PORTB.PIN1CTRL &= ~PORT_ISC_gm;


This defines a group mask macro for the bits that control ISC. Then "~" flips all of the group mask's bits and bitwise ANDs it with the existing values in Port B's Pin 1 Control register. This clears all the bits that control ISC to 0 while keeping the other bits in the register the same.

ISC stands for Input/Sense Configuration.

ISC occupies 3 bits and determines how a port interrupt can be triggered.

gp

Group Position:

the location where multiple bits start from when counting right to left in a register 

0

A group position is often defined for a register where multiple bits store a single value or control a single setting.

A group position can be used to shift a value into the correct position of multiple bits in a register.


Code Block
languagecpp
linenumberstrue
#define CLKCTRL_CLKSEL_gp	0
CLKCTRL.MCLKCTRLA = (0x03 << CLKCTRL_CLKSEL_gp);


This defines a group position macro for where the bits that control CLKSEL start. It bitwise left shifts the value 0x03 to the bits that control CLKSEL and sets the Clock Controller's Main Clock Control A register equal to it.

CLKSEL stands for Clock Select

CLKSEL occupies 2 bits and selects the source for the Main Clock

0x03 is the value for an External Clock source.

gc

Group Configuration:

combines a group position and a group mask

(0x03 << 0)

A group configuration is often defined for a register where multiple bits store a single value or control a single setting.

A group configuration can be used to set the multiple bits of setting to a predefined configuration.


Code Block
languagecpp
linenumberstrue
#define CLKCTRL_CLKSEL_EXTCLK_gc	(0x03 << 0 )
CLKCTRL.MCLKCTRLA = CLKCTRL_CLKSEL_EXTCLK_gc;


This defines a group configuration macro for an External Clock source by shifting the value that represents it, to the CLKSEL group position. It then sets the Clock Controller's Main Clock Control A register equal to the group configuration macro.

CLKSEL stands for Clock Select

CLKSEL occupies 2 bits and selects the source for the Main Clock

0x03 is the value for an External Clock source.


Timers/Counters | Getting Started with Timer/CounterA Application Notes

The acronyms for Timer/Counters' names and also used for their respective registers addressing are in the format TCAn [18]. The name starts with "TC'' for Timer/Counter, then a letter corresponding to the Timer/Counter's type, "A" for Timer/Counter type A, then an integer starting from 0 for the Timer instance.

ATtiny3216/3217 Timer/Counter Definitions Table 20-1 [18]

Image of table with Timer terminolgy definitions for BOTTOM, MAX, TOP, UPDATE, CNT, CMP, PER

In the ATtiny 3216/3217, there are four Timer/Counters in total. There is one 16-bit Timer/Counter type A (TCA0), two 16-bit Timer/Counter type B (TCB0,TCB1), and one 12-bit Timer/Counter type D (TCD0). Timer/Counters of type A (TCA) are the only one that will be explored further as its functions are more often used and the other Timer/Counter types’ functions have many overlapping similarities.

Timer/Counters consist of a base counter and different modes of operations that increment, decrement, clear, and fire interrupts in specific conditions. TCA provides program executing timing, frequency and waveform generation, and command execution. The 16-bit TCA with three compare channels can also be operated in a Split mode to create two 8-bit Timer/Counters with three compare channels each.

ATtiny3216/3217 TCA Waveform Generation Modes Table 20-6 [18]

Image of table of register configurations for different waveform generation modes


ATtiny3216/3217 TCA Interrupt Vectors Sources in Normal Mode Table 20-4 [18]

Image of table of descriptions for interrupt vector sources OVF, CMP0, CMP1, and CMP2

The clock for TCA that controls the base counter’s ticks can be generated by a pre-scaler and the peripheral clock, or the event system, but the peripheral clock is the more popular choice.

ATtiny3216/3217 TCA Clock Select Register's Prescaler Table [18]

Image of table of register configurations for different TCA clock prescaler divisions

Applications

A common basic TCA use case is to trigger an interrupt to run a piece of code periodically. In this case, the Normal operation mode with the overflow interrupt would be likely used. In this configuration, TOP is controlled by the value in the Period register. UPDATE and OVF (Overflow interrupt) is set to be equal to TOP when counting up. Therefore, when the base counter ticks up and is equal to the Period register value, an Overflow interrupt is fired and the base counter is reset to 0.

Getting Started with TCA Application Notes' Periodic Interrupt Mode Example [34]


Code Block
languagecpp
titleTCA0 Periodic Interrupt Example
linenumberstrue
#include <avr/io.h>
#include <avr/interrupt.h>

void TCA0_initialization(void)
{
	/*	TCA0 is operating as a SINGLE(Non-Spit Mode) 16-bit Timer/Counter.
		Enable the Overflow interrupt bit in the TCA0's Interrupt Control register.
	*/
	TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; 
	
	//	Set TCA0 to Normal mode of operation. 
	TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc;
	
	/*	TCA0 should count clock ticks, not events,so disable the  
	 	Count on Event input bit in TCA0's Event Control register. 
	*/	
	TCA0.SINGLE.EVCTRL &= ~(TCA_SINGLE_CNTEI_bm);

	/*	In Normal mode, the Period register's value represents the number of
		clock ticks before an interrupt is triggered and the base counter is reset.
		0x0CB6 represents about 3254 clock ticks so when combined with a 
		256 prescaler, the interrupt will fire every 250ms.
		The equations to calculate the required value for specific periods 
		are in the application notes.
	*/
	TCA0.SINGLE.PER = 0x0CB6;

	//	Set the prescaler to 256 and start the counter with the Enable bit.
	TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV256_gc| TCA_SINGLE_ENABLE_bm;
}

ISR(TCA0_OVF_vect)
{

	//	Code that periodically executes
	
	/*	Overflow interrupt flag must be cleared for the program to continue by 
		writing '1' to the respective bit in the Interrupt Flag register.
	*/	
	TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;
}


int main(void)
{
	/*	Must initialize all the Timer/Counter's configurations 
		before enabling global interrupts. 
	*/
	TCA0_initialization();
	
	//	Enables global interrupts to allow the Timer/Counter to start.
	sei();
}   



Contributors:

Contributors Summary
columnslastupdate
orderupdate