Atmega328 Arduino Uno
Atmega328 Arduino Uno
Atmega328 Arduino Uno
ARDUINO UNO
Microcontroller ATMega328
Microcontrôleurs 2/29
1 Introduction
The UNO model from ARDUINO is an electronic card whose heart is an ATMEL
microcontroller of reference ATMega328. The ATMega328 is an 8-bit microcontroller of the AVR
family which can be programmed in C/C++ language.
The main interest of the ARDUINO cards (other models exist : Mega, Nano...) is their ease
of implementation. A development environment (IDE), based on open-source tools, is provided. In
addition, loading the compiled program into the memory of the microcontroller is very simple (via
USB port). Finally, a lot of function libraries are also provided for the use of common I/Os: logical
I/O, ADC converters, generation of PWM signals, operation of TWI/I2C busses, LCD displays ...
The objective of the Microcontrollers course is not only to know how to use the Arduino
UNO board. It is above all an opportunity to tackle low-level programming problems (the binary
value of the manipulated variables is very important) and to learn how to use the C language for this
low-level programming, in particular by knowing how to manage registers/variables "at bit level".
So, when you're complicating the task, when an Arduino function exists, tell yourself that it's
intentional.
The purpose of this document is to highlight some technical information concerning the
operation of integrated peripherals, especially when you don't use the "turnkey" functions of
ARDUINO, in order to understand how it works!
Microcontrôleurs 3/29
Viewed from above, the picture provides the following information:
WARNING: with the Arduino functions (pinMode, digitalRead, digitalWrite ...), the signals are
marked according to the numbering of the connectors (left part). When programming at low level,
however, the names of the registers/pins of the microcontroller are used (right-hand side).
Microcontrôleurs 4/29
Multi-function pins: all pins have several different functions, chosen by programming. They
therefore have several names on the pinout.
For example, pins PB1, PB2, PB3, PD3, PD5, PD6 can be used as PWM (Pulse Width Modulation)
outputs, i.e. outputs that will act as analog outputs. They correspond to the pins of connectors
3,5,6,9,10 and 11. The other role is linked to the timers and these pins are then called OCxA or
OcxB in the documentation. They are the same pins, but for a different function.
Pins from PORTC can be converted with a ADC device (Analog to Digital Converter).
Analog Comparator = pins AIN0(PD6) and AIN1 (PD7) can trigger an interrupt
Watchdog Timer.
Microcontrôleurs 5/29
4 Programming in the Arduino's IDE
4.1 Introduction
int main(void)
{
init(); // initialisations for delays, PWM ...
setup();
for (;;) loop(); // indefinitely repeated
return 0;
}
-----------------------------
Microcontrôleurs 6/29
4.2 Langage C pour ARDUINO UNO
Variables Types/size :
//Blink (Arduino)
void setup()
{
pinMode(13, OUTPUT); //pin PB5 as output
}
void loop()
{
digitalWrite(13,HIGH); // set PB5
delay(200); // 200 ms
digitalWrite(13,LOW); // clear PB5
delay(1000); // 1 s
}
The simplest example, provided by ARDUINO, consists of flashing the LED (on the UNO board)
connected to pin PB5 of the microcontroller, pin no. 13 on the board connectors.
The setup() function configures pin PB5 (connection n°13 on the board) as an output, using the
Arduino pinMode() function. The loop() function then describes what will be repeated indefinitely:
set PB5 to 1 for 200ms then set PB5 to 0 for 1s, and so on.
Digital I/O
pinMode(pin,mode) : pin, mode = INPUT/OUTPUT/INPUT_PULLUP
digitalWrite(pin,value) : pin, value= HIGH/LOW
int digitalRead(pin) : pin, returns the value
Timers
delay(unsigned long ms) : ms milliseconds (ms encoded with 32 bits)
delayMicroseconds(unsigned int ms) : ms microseconds (ms 16 bits)
unsigned long micros() : time in microseconds from the startup. Reset every 70mn.
unsigned long millis() : time in milliseconds from the startup. Reset every 50 days.
Microcontrôleurs 7/29
Serial communication : sends/receives data : allows the Arduino card to communicate with the
connected computer
//Example :
// prints the duration (millis) when the signal
// on PC5 is equal to 0. This duration is sent
// to the computer via USB and can be displayed
// with the serial monitor
void setup()
{
Serial.begin(9600); // UART microC 9600 bauds
pinMode(A5,INPUT_PULLUP); // PC5 as input (with pull-up)
pinMode(13,OUTPUT); // PB5 (LED) as output
digitalWrite(13,LOW); // PB5 set to 0 (LED off)
}
void loop()
{
unsigned long t1,t2,duree; // local variables
while(digitalRead(A5)==1);
// PC5 goes from 1->0 (falling edge)
t1=millis();
digitalWrite(13,HIGH); // sets LED ON
while(digitalRead(A5)==0);
// PC5 0->1 (rising edge)
t2=millis();
digitalWrite(13,LOW); // resets LED
duree = t2-t1;
Serial.print("Duree="); //duration=
Serial.println(duree,DEC); // sends the duration
}
If you want to control the Arduino peripherals at the lowest level, i.e. without using the - yet very
nice - Arduino functions, you have to read/write to internal registers of the microcontroller. These
registers are detailed, for some devices, in the rest of this document. Note that the complete
technical documentation of this microcontroller is several hundred pages long. This is therefore a
very partial presentation.
For example, making the LED on the Arduino board (PB5) flash, without using the ARDUINO
functions, requires access to the I/O port configuration registers (see section 5.2). For pin PB5, the
registers involved are DDRB, PORTB and PINB. In the C program, the registers are referred to by
their name in UPPERCASE.
Microcontrôleurs 8/29
Low-level control of PB5.
void setup() {
// setting : PB5 as output (see section 5.2)
DDRB |= 0x20; // DDRB.5 <- 1
// or DDRB|=B100000; // B100000 = 0x20
// ou DDRB|=32; // 0x20 = 32
PORTB &= 0xDF; // PORTB.5 <- 0
// or DDRB&= ~0x20; // 0xDF is the complement to 0x20
}
void loop() {
PORTB |= 0x20; // PORTB.5 <- 1
delay(200); // 200 ms
PORTB &= 0xDF;; // PORTB.5 <- 0
delay(1000); // 1s (only Arduino's function used here)
}
Remarks :
The C language examples found on the internet use sometimes confusing writing "styles".
Especially when it comes to managing I/O ports, one often has to perform logical operations
(&,|,~,^) to set bits to 0, 1 or invert bits, i.e. to modify or read one or more bits of an 8-bit register.
Confusing example:
The logical AND operation leaves all PORTB bits unchanged except bit 5 which is set to 0 (due to
the bit at 0 of the binary value (1101 1111)b = ~(1<<PORTB5)).
Microcontrôleurs 9/29
5 Internal structure of ATMega328 (excerpts from ATMEL documentations)
The use of integrated peripherals (digital inputs and outputs, timers, ...) is based on the use
(read/write) of internal registers. These registers, mainly 8 bits, are described by an UPPERCASE
name in C programs. This section provides some important details about the internal registers of the
ATMega328 microcontroller involved in the operation of peripherals. Some parts are excerpts from
the Atmel documentation.
For the complete documentation (442p): search with keywords ATMega328 datasheet
------------------
Notation : thereafter, for a register named R, the notation R.n designates the nth bit of register
R. Be careful, it is only a notation. The C compiler cannot exploit this notation.
Ex: PORTB.5 means "bit number 5 of the register called PORTB".
------------------
The SREG register contains flags and the general interrupt authorization bit. The bits in this register
are : Z (Zero), C (Carry), S (Sign) ... The general interrupt enable bit is bit I (SREG.7).
Note : in C language, bit I is modified with functions sei() (set IT) cli() (Clear IT)
The microcontrollers have logical input/output pins, just like on a PLC. To set the state of an output
to 0 or 1, or to read the state of an input, internal registers described below must be used.
The inputs/outputs are divided into 3 groups of pins called ports. Port B groups the pins marked
PBx, port C the PCx pins and port D the PDx pins (see pinout). Each port is operated by means of 3
registers.
Microcontrôleurs 10/29
Ex: PORTB, DDRB et PINB registers for controlling pins PB0 à PB7
// Example ports
void setup()
{
DDRB |= 0x40; // DDRB.6 <- 1 <-> PB6 as output
DDRD &= ~0x08; // DDRD.3 <- 0 <-> PD3 as input
}
PORTx for writing digital outputs: if a pin is configured as an output (DDRx.n=1) then writing
the PORTx.n bit defines the state of the output (0 or 1).
PINx for reading logical inputs :if a pin is configured as an input (DDRx.n=0) then reading the
PINx.n bit allows knowing the status of the input.
In the documentation, the registers involved are described below (example for pins PB0 to PB7).
Microcontrôleurs 11/29
Activation of internal pull-up resistors (important)
In MOS technology, a not-connected input has an undetermined state. Also, when you want to use
push buttons/switches, you connect them in such a way as to bring the input back to 0 when you
close the contact. Conversely, when the contact is open, the state of the input must be brought to 1
by pull-up resistors. These internal resistors are activated (or not) by programming:
Microcontrôleurs 12/29
6 Interrupts for ATMega328 (Arduino UNO)
Interrupt (IT) = suspension of the program to carry out a particular treatment. But this is different
from a function or a subprogram: it is not explicitly called by the program. You don't write the call
of this specific task. It is the processor that, following the detection of a particular cause, triggers
the interrupt processing. The function related to interrupt processing is called an interrupt function
or interrupt service routine (ISR).
Peripherals can lead to interruptions. This mechanism ensures very short response times between
the cause of the interrupt and its processing.
Important: ISR calls are inserted asychronically (it is not known in advance when they will be
called) into the program execution. For example, an Arduino program is interrupted every
millisecond by an ISR linked to Timer 0. This ISR Timer 0 routine updates the time variables used
by the delay() millis() etc. functions. An Arduino program is therefore cyclically suspended
(interrupted) for the ISR Timer 0, which slows down its execution by about 6%.
Below is the interrupt vector, i.e. all sources (possible causes) on ATMEGA238.
Microcontrôleurs 13/29
6.1 External Interrupts (pins PD2 and PD3)
These are interrupts where the causes are related to levels or changes of state of pins PD2 (INT0) or
PD3 (INT1) of the microcontroller. Please note the name INT0/INT1 in reference to this alternative
function of pins PD2/PD3. For this external interrupt role, the pins must be configured as inputs
(see 5.2 DIGITAL I/O).
Pins INT0 (PD2)/INT1(PD3): Configurable to trigger interrupts (no. 2 and 3 in the vector or
INT0_vect/INT1_vect). The possible causes (selected by programming) are
// INT0/INT1
void setup()
{
cli(); // no IT
EICRA &= 0xF0; // reset bits EICRA.3-EICRA.0
EICRA |=0x09; // ISC11=1 ISC10=0 (falling edge INT1)
// ISC01=0 ISC00=1 (pin change on INT0)
EIMSK |=0x03; // INT0/INT1 enabled
sei(); // IT allowed
}
Microcontrôleurs 14/29
Detail : internal Flags = when the ISR starts, a flag in EIFR is set
Example :
Microcontrôleurs 15/29
For the previous program, a push-button is connected between PD2(/INT0) and ground GND.
When the push button is pressed, the level of input PD2 is set to 0. Here, the internal pull-up
resistor is used to set the level to 1 when the button is released.
Principle: Each time the pushbutton changes the level on input INT0(PD2) from 1 to 0, the
interrupt function associated with INT0 is executed. This action has the effect of reversing the state
of the LED and returning to the main program. It is important to understand that the interrupt only
lasts a few microseconds. Apart from these interrupts, the main program (loop() function) sends the
value of the cpt variable to the serial port every second.
Note: this way of decoupling the processing of the push button from that of the main program is
similar to task parallelization.
[Technical point] Set EIFR register flag to 0: EIFR register bits indicate (flags) that an INT0/INT1
interrupt request is pending: a flag of 1 in this register means that the cause of IT has been detected
but the ISR routine is not yet executed. If you want to cancel an int. request (before it is executed),
you must reset these flags to 0. Strange: To cancel a request (clear flag), you must write 1 (not 0) in
the EIFR register for the flag concerned.
Pins PCINT0 to PCINT23 (alternative name for PBx, PCx and PDx): Configurable to trigger
interrupts (#4, #5 and #6 or PCINT0_vect, PCINT1_vect, PCINT2_vect) following pin changes
(configured as DDRx.n=1 input). The pins are separated into 3 subgroups, there is one interrupt
source per subgroup, and for each pin the "Pin Change Interrupt" system can be activated or not.
Registers PCMSK0, PCMSK1 and PCMSK2 control, for each of these groups (i.e. for each port B,
C D), which pin(s) can lead (or not) to a "pin change" type interrupt.
Microcontrôleurs 16/29
Enable interrupts PCINT0 to PCINT23 if bit SREG.7=1 and set PCIEx to 1
PCICR.0: Activation of IT Pin Change for the pins of port B (PB0 to PB7)
PCICR.1: Activation of IT Pin Change for the pins of port C (PC0 to PC6)
PCICR.2: Activation of IT Pin Change for the pins of port D (PD0 to PD7)
Activation within a group: the PCMSKx register determines which pins in the group are taken into
account for the "pin change" interrupt
Microcontrôleurs 17/29
Flags for ISR "Pin Change"
void setup()
{
Serial.begin(9600);
cli();
PCICR |= 0x06; // Pin Change enabled port D/port C
PCMSK2=0x0C; // Pin Change PD3/PD2
PCMSK1=0x30; // Pin Change PC5/PC4
DDRD&=~0x0C; // PD2/PD3 inputs
PORTD|=0x0C; // pull-up for PD2 PD3
DDRC&=~0x30; // PC4 PC5 inputs
PORTC|=0x30; // pull-up for PC4 PC5
sei();
}
void loop()
{
delay(2000);
Serial.print("cpt1=");
Serial.println(cpt1,DEC);
Serial.print("cpt2=");
Serial.println(cpt2,DEC);
}
Note: On AVR, by default, an interrupt function cannot itself be interrupted. The CPU prevents ISR
from being suspended.
Microcontrôleurs 18/29
6.3 Timers interrupts
Built-in timers can trigger interruptions. A complete section on configuring the built-in timers and
operating the associated interrupts is provided.
7. Timers/Counters on ATMega328
The ATMega328 microcontroller has several internal timer/counter modules (Timers), some with 8-
bit count registers and others with 16-bit count registers. In all cases, each counting event leads to a
change in the count register (+1). The count event can be a "tick" of the microcontroller clock,
which is equivalent to measuring the passage of time. The count event can also be an edge on an
input pin of the microcontroller (pins T0 and T1 can be used as count input).
Timer function: When counting "ticks" of the clock that clocks the microcontroller, the time
elapsed is measured. The Timer/Counter modules provide this function. It is also possible to count
the ticks of a lower frequency signal obtained by dividing the clock frequency by a prescaler.
Note: on the Arduino UNO board, the clock is at 16MHz, or 16,000,000 clock cycles per second, or
16 clock cycles per microsecond. These are the cycles that are counted as a timer function.
16000000 cycles = one second.
Counter function: when counting edges on a counter input (pins T0 or T1), the "counter" function
of the module (not studied here) is used.
The choice between timer function (with prescaler or not) and counter function is made by
configuring registers dedicated to the management of Timer/Counter modules. You will see, it's
technical.
Generating periodic signals: the Timer/Counter modules are quite complex and each of these
modules can generate two PWM (Pulse Width Modulation) signals whose duty cycle can be easily
modified. In this case, use the Arduino analogWrite() function which generates a PWM signal. This
PWM signal is only managed on the outputs linked to integrated Timers i.e. PD6,PD5,PD3,
PB1,PB2 and PB3.
Note: timers are complex embedded devices (about 70 pages of the ATMega datasheet). Only a
simplified view is provided here.
Microcontrôleurs 19/29
7.2 Timer/Counter 2 (comptage 8 bits)
It is a Timer/Counter module with 8-bit count register. The general structure of the Timer/Counter 2
module is shown in the following diagram. The counter register is TCNT2 (8-bit register).
Registers Timer/Counter 2
Microcontrôleurs 20/29
Operating Modes (Table 17-8) :
Normal: Register TCNT2 is incremented by 1 for each counting event. The register only returns to
0 after an overflow (0xFF to 0x00).
CTC (Clear Timer on Compare): Register TCNT2 is incremented at each counting event AND is
reset to 0 if TCNT2=OCR2A.
The choice of the mode is made via the bits WGM22:20 (bits TCR2A and TCR2B).
Microcontrôleurs 21/29
Clear Timer on Compare Match (CTC) Mode
In CTC mode (WGM22:0 = 2), register OCR2A sets the resolution. The counter TCTN2 is reset to
zero after the match TCTN2=OCR2A. Register OCR2A defines the maximum value for the counter
and thus its resolution.
Prescaler : as a timer function, the counter register TCNT2 is incremented according to the clock
cycles. The increment can be at each clock cycle (no prescaling) or at a lower frequency. Remember
that the clock cycle is 1/16 microseconds. The prescaler indicates how many clock cycles are
required for a TCNT2 increment.
Microcontrôleurs 22/29
7.3 Example Timer 2 with Interrupt
For Timer 2, a TIMER2_OVF_vect interrupt can be triggered for each TCNT2 overflow
(change from 0xFF to 0x00). This mechanism has to be activated
Note (volatile variables): When global variables are shared (read/write) between the main
program and an ISR function, it is recommended to label them as volatile. Without this
keyword, the compiler is likely to make code optimizations that would cause variable
sharing to fail.
Microcontrôleurs 23/29
// ARDUINO UNO - IT Timer 2 Overflow
void setup(){
Settings Timer 2
Mode 0 ( Normal) : WGM2=0 WGM1=0 WGM0=0 [TCCR2A=0]
Prescaler = 1024 : CS22=1 CS21=1 CS20=1 [TCCR2B=0x07=(111)b]
Interrupts
Interruption if Overflow = TIMSK2.0 =1
Principle: after the setup() function, the TCNT2 register (8bits) is incremented at each tick of the
periodic clock/1024 signal. Each time register TCNT2 overflows, the overflow triggers interrupt
n°10 called "Timer 2 Over Flow". Every 60 calls of this function, pin PB5 (LED) changes state. The
LED therefore blinks.
Microcontrôleurs 24/29
Another example: timer2 in CTC mode and interrupts
The CTC mode of Timer 2 is used here. Each time TCNT2=OCR2A, TCNT2 is reset to 0 and the
equality triggers an interrupt. Autre exemple : timer2 en mode CTC et interruptions
void setup(){
// Settings Timer 2
TCCR2A=0x02; // Mode CTC (Clear Timer On Compare)
OCR2A=156; // comparison reg A = 156
TCCR2B=0x07; // Prescaler 1024 (Clock/1024)
TIMSK2=0x02; // IT when TCNT2=OCR2A
sei();
}
void loop() { /* */ }
The LED lights up 1/10 of a second and then goes out 4/10 of a second. It lights up
briefly twice a second.
Microcontrôleurs 25/29
Timer2 configuration function in CTC mode
For example:
void setup()
{
Serial.begin(9600);
cli();
SetTimer2CTC(6,100); // prescaler /256 periode 100
TIMSK2|=0x02; //IT Timer2 when TCNT2==OCR2A
sei();
}
void loop()
{
delay(1000);
Serial.println(cpt,DEC);
}
Microcontrôleurs 26/29
7.4 Timer/Counter 1 (16 bits)
The TCNT1 count register, as well as the comparison registers OCR1A and OCR1B, are 16 bits this
time.
Note: in assembly language, two 8-bit accesses are required to read/write these 16-bit registers. In C
language, 16-bit data can be manipulated symbolically via TCNT1, OCR1A and OCR1B without
worrying about how the code will be generated.
Registers Timer/Counter 1
Microcontrôleurs 27/29
Depending on the mode selected by bits WGM10:3, the following options are available (PWM
mode Correct phase not described)
Microcontrôleurs 28/29
Prescaler Timer 1
void setup()
{
Serial.begin(9600);
cli();
SetTimer1CTC(4,10000); // prescaler /256 periode 10000
TIMSK1|=0x02; //IT Timer1 when TCNT1==OCR1A
sei();
}
void loop()
{
delay(1000);
Serial.println(cpt,DEC);
}
Microcontrôleurs 29/29