0% found this document useful (0 votes)
33 views115 pages

unit-05-avr

The document provides an in-depth study of the Atmega328P microcontroller, detailing its structure, programming in AVR assembly and C, and various peripherals. It emphasizes the importance of microcontrollers in modern technology, particularly in IoT applications, and outlines the objectives of understanding low-level programming and development tools. Additionally, it includes a bibliography for further reference on AVR programming and architecture.

Uploaded by

jjchico
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views115 pages

unit-05-avr

The document provides an in-depth study of the Atmega328P microcontroller, detailing its structure, programming in AVR assembly and C, and various peripherals. It emphasizes the importance of microcontrollers in modern technology, particularly in IoT applications, and outlines the objectives of understanding low-level programming and development tools. Additionally, it includes a bibliography for further reference on AVR programming and architecture.

Uploaded by

jjchico
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 115

Contents

Unit 5. Study of a real computer:


Atmega328P microcontroller

Computer Structure
E.T.S.I. Informática
Universidad de Sevilla

Jorge Juan-Chico <[email protected]> 2021


This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this
license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View,
CA 94042, USA.
Some images and other objects are used here according to their license. Follow the reference to the original work for details.
Images not referenced are either original work of the author or are in the public domain.

Departamento de Tecnología Electrónica – Universidad de Sevilla 1


Contents

Contents

Introduction
– What is a microcontroller?
– The Atmel AVR family

The ATmega328P
– Block diagram and CPU core
– Registers
– Program memory
– Data memory
– Digital I/O ports
– Development boards

Departamento de Tecnología Electrónica – Universidad de Sevilla 2


Contents

Contents

AVR assembly programming
– Requierements
– AVR instruction set
– Toolchains, platforms, frameworks and IDE’s
– The GNU AVR toolchain
– C pre-processor macros
– GNU assembler directives and expressions
– Assembling, uploading and testing
– AVR programming (uploading) alternatives
– Debugging programs
– AVR IDEs and PlatformIO
– Assembly subroutines
– Reading and writing program memory
– Working with data other than 8 bits

Departamento de Tecnología Electrónica – Universidad de Sevilla 3


Contents

Contents

AVR C programming
– C program example
– Code optimization
– Compiling and disassembling
– C language functions
– Wait: why learning assembly?
– Where and why is assembly used?
– AVR C calling convention
– Calling assembly subroutines from C
– Calling C functions from assembly.

Departamento de Tecnología Electrónica – Universidad de Sevilla 4


Contents

Contents

Interrupts
– Interrupt vectors

Timers
– AVR timers
– Timer/Counter 1
– Example: blink with interrupts.

Other peripherals
– Analog inputs
– SPI
– TWI (I2C)

Departamento de Tecnología Electrónica – Universidad de Sevilla 5


Contents

Objectives

Understand the structure of a real computer.

Understand typical peripherals present in real coputers.

Understand the importance of small computers in today’s computer
technology (from sensor networks to the IoT).

Learn to program a computer at the lowest level (assembly).

Understand the low-level programming conventions.

Learn the basic tools involved in computer programming.

Understand the role of programming frameworks and Integrated
Development Environments (IDE’s).

Departamento de Tecnología Electrónica – Universidad de Sevilla 6


Contents

Bibliography

ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet – Technical
information about ATmegaX8 chips. Use as reference.

AVR Instruction Set Manual – Detailed description of AVR assembly
instructions. Use as reference.

AVR microcontroller programming with avr-libc and the GNU toolchai
n
– AKA the “avr-bare” repo. Must read.

AVR Libc Home Page and documentation – Definitions for C and
assembly programming. Use as reference.

AVR Libc source code – Use as reference.

Departamento de Tecnología Electrónica – Universidad de Sevilla 7


Contents
What is a microcontroller?
(or µC or MCU )

It is a System on Chip that includes a simple CPU core, a program
and data memory, and a variety of input/output peripherals that make
them suitable to be applied to a wide range of software-controlled
applications.

Typical peripherals:
– Generic input and output digital ports (GPIO).
– Analog inputs and/or outputs.
– Serial interfaces (SPI, USART, etc.).
– Timers.
– Clock generators.

MCU → MicroController Unit

Departamento de Tecnología Electrónica – Universidad de Sevilla 8


Contents

What is a microcontroller?

Typical characteristics
– Low cost.
– Robust.
– Small size.
– Low power consumption.
– Low processing power.
– Harvard architecture.
– Non-volatile program memory (EEPROM/Flash).

Typical applications.
– Embedded systems in general.
– Sensor networks.
– Robotics.
– Control systems: small appliances, automotive, remotes, heating, etc.

Departamento de Tecnología Electrónica – Universidad de Sevilla 9


Contents

Typical applications

Microcontrollers drive the IoT revolution.

Departamento de Tecnología Electrónica – Universidad de Sevilla 10


Contents

IoT devices forecast

https://iot-analytics.com/state-of-the-iot-2020-12-billion-iot-connections-surpassing-non-iot-for-the-first-time/

Departamento de Tecnología Electrónica – Universidad de Sevilla 11


Contents

The Atmel AVR microcontroller family



Created by Norwegian students Alf-Egil
History of the AVR
Bogen and Vegard Wollan.

Developed by Atmel since 1997, sold to
Microchip in 2016

One of the most successful microcontrollers
ever.

Supported by Free Software tools and an
open community (AVRFreaks).

Main series
– ATtiny: low-end devices with fewer pins and
peripherals.
– ATmega: mid-range devices with complete
set of peripherals.
– ATxmega: high-end devices with increased
performance and set of peripherals.

Departamento de Tecnología Electrónica – Universidad de Sevilla 12


Contents
The ATmega328/P and fiends
(48, 88, and 168)

8-bit RISC architecture.
ATmegaX8 data sheet

Clock frequency up to 20MHz @4.5-5.5V

Up to 20MIPS
– Two stages pipeline.
– Many instructions execute in 1 clock cycle. 653 pages.

Flash program memory. Not to read from tip to toe.
Use it as reference.

SRAM and EEPROM data memory.

Device Flash EEPROM RAM Int. vector size


ATmega48A/PA 4KB 256B 512B 1 inst./vector
ATmega88A/PA 8KB 512B 1KB 1 inst./vector
ATmega168A/PA 16KB 512B 1KB 2 inst./vector
ATmega328/P 32KB 1KB 2KB 2 inst./vector

Departamento de Tecnología Electrónica – Universidad de Sevilla 13


Contents

ATmega328P and the Arduino UNO



The core of the Arduino UNO board.

Arduino is Framework to build control systems easily (more of this
later in the unit).

ATmega328P

Departamento de Tecnología Electrónica – Universidad de Sevilla 14


Contents

ATmegaX8 block diagram



AVR CPU core.

Supporting circuits
– Clock generator.
– Watchdog timer.
– Debug unit.
– Power management unit.

Peripherals
– PORTB, PORTC, PORTD: digital
input/output ports.
– SPI, USART0, TWI: serial
interfaces.
– T/C 0, 1, 2: timer/counters.
– A/D: analog to digital converters.

Which of those did we have in the YASAC system?

Departamento de Tecnología Electrónica – Universidad de Sevilla 15


Contents

The AVR CPU core

Quick exercise:
1) Find 3 things that are similar to the YASAC.
2) Find 3 things that are different from the YASAC.

Departamento de Tecnología Electrónica – Universidad de Sevilla 16


Contents

General purpose registers

Departamento de Tecnología Electrónica – Universidad de Sevilla 17


Contents

Pointer registers

Departamento de Tecnología Electrónica – Universidad de Sevilla 18


Contents

Stack and stack pointer

Departamento de Tecnología Electrónica – Universidad de Sevilla 19


Contents

Program memory

16-bit wide.

All instruction codes are 16 bit.
– Some instructions use an
additional 16-bit immediate
value in the next position.

Also byte-addressable by
program memory load and
store instructions (will see
later).

ATmega328P memory is:
– 16k x 16 (word addressable)
– 32k x 8 (byte addressable)

Departamento de Tecnología Electrónica – Universidad de Sevilla 20


Contents

SRAM data memory


Byte addressable.

All general-purpose registers and I/O registers are mapped to data
memory.
– First 64 I/O registers can also be accessed by specific input/output
instructions (faster).

Real usable data memory starts at 0x100
– ATmega328P data memory goes from 0x100 to 0x8FF (2kB).

Departamento de Tecnología Electrónica – Universidad de Sevilla 21


Contents

Digital I/O ports

Departamento de Tecnología Electrónica – Universidad de Sevilla 22


Contents

Digital I/O ports

Writing a port takes one cycle after the “out” instruction:


wait a cycle (nop) before reading (in) from the port.

Departamento de Tecnología Electrónica – Universidad de Sevilla 23


Contents

Digital I/O ports

Departamento de Tecnología Electrónica – Universidad de Sevilla 24


Contents

Digital I/O ports

PORTC

PORTD

Departamento de Tecnología Electrónica – Universidad de Sevilla 25


Contents

Development boards

Microcontrollers are autonomous
– Only need a supply voltage and a connection to a programmer to be
used.

Development boards simplify doing projects with microcontrollers by
including additional circuitry and devices:
– Supply voltage connections and regulation.
– Easy to use connection pins.
– Easy to use programming pins.
– Simple peripherals: LED’s, switches, push buttons, etc.
– Programming interface (e.g. USB): no need of an external programmer.
– BUT: not all pins of the MCU may be available, some MCU resources
may be used by the board’s hardware or software, etc.

Arduino boards are the most popular boards built around the AVR
platform.

Departamento de Tecnología Electrónica – Universidad de Sevilla 26


Contents

Arduino UNO

Arduino UNO specs

Departamento de Tecnología Electrónica – Universidad de Sevilla 27


Contents

AVR programming

AVR microcontrollers are programmed in AVR assembly. There are
tools that translate assembly code to machine code (assemblers).

Most AVR microcontrollers can also be programmed in C and C++.
There are programs that translate C/C++ to machine code
(compilers) and a library of standard C functions.

All YASAC assembly instructions can be used with AVR’s (we copied
from them :)
– AVR assembly has many more instructions.
– AVR instructions have different restrictions.
– AVR instruction format is different (not binary compatible).
– AVR peripherals are different.

Departamento de Tecnología Electrónica – Universidad de Sevilla 28


Contents
What do you need to write assembly
programs (AVR or other)?

Knowledge of the processor architecture.

An instruction set summary (included with the data sheet).

An instruction set manual, with the detailed instructions operation, in
case the summary is not enough (see the bibliography).

The basic structure of an assembly program (segments).

A summary of the assembler directives.

The pin-out and characteristics of the board you are using (if any).

How to use the tools to produce and upload an executable program.

You do not need to know all the details from the beginning.
Start with a basic set and learn more as you need.

AVR microcontroller programming with avr-libc and the GNU toolchain

Departamento de Tecnología Electrónica – Universidad de Sevilla 29


Contents

AVR instruction set overview



Arithmetic and logic instructions.

Branch instructions.

Bit and bit-test instructions.

Data transfer instructions.

MCU control instructions.

Details are in the ATmega328P Data Sheet (instructions


summary) and in the AVR Instruction Set Manual.

Departamento de Tecnología Electrónica – Universidad de Sevilla 30


Contents

Arithmetic and logic instructions



YASAC-like:
– ADD, SUB, AND, OR, EOR.

Immediate versions:
– SUBI, ANDI, ORI (but not ADDI!).
– Immediate addressing can only be used with R16 to R31!

16-bit instructions:
– ADIW, SBIW.

Multiply instructions:
– MUL, MULS, MULSU.

Increment and decrement instructions:
– INC, DEC.

Register clear and set instructions:
– CLR, SER

Departamento de Tecnología Electrónica – Universidad de Sevilla 31


Contents

Branch instructions

YASAC-like:
– BRBS, BRBC, and derivatives.

Relative jump and call:
– RJMP, RCALL.
– Faster and uses less program memory.

Indirect call (to address in Z):
– ICALL.

Skip and bit test instructions: easy decisions on single bits.
– Compare and skip if equal: CPSE.
– Skip if bit in register is cleared/set: SBRC/SBRS
– Skip if bit in I/O register is cleared/set: SBIC/SBIS

Return from interrupt:
– RETI.

Departamento de Tecnología Electrónica – Universidad de Sevilla 32


Contents

Bit and bit-test instructions



Status register bit change:
– BSET, BCLR and derivatives (like in YASAC).

Logic and arithmetic shift, in addition to rotation:
– Logic shift left/right: LSL, LSR
– Arithmetic shift right: ASR
– Rotate left/right: ROL, ROR (like in YASAC).

Set and clear bits in I/O registers:
– SBI, CBI.
– Easy single bit I/O manipulation.

Swap nibbles:
– SWAP
– Useful to handle BCD numbers.

Departamento de Tecnología Electrónica – Universidad de Sevilla 33


Contents

Data transfer instructions



YASAC-like:
– MOV, LDI, LD, ST, LDS, STS, PUSH, POP.
– LDI only for registers R16 to R31!
– Register indirect addressing (LD, ST) only with pointer registers X, Y, Z.

Post-increment and pre-decrement indirect load and stores
– With LD and ST.

Indirect loads and stores with displacement:
– LDD, STD.

Program memory load and store:
– Only indirect through Z pointer: LPM, SPM.
– LPM has optional post-increment.
– Note: Z holds “byte” addresses, not instruction word addresses.

I/O port input and output instructions:
– IN, OUT
– Complemented with SBIC, SBIS, CBI, SBI.

Departamento de Tecnología Electrónica – Universidad de Sevilla 34


Contents

MCU control instructions



No operation:
– NOP
– Useful to insert delay:

Finish and OUT instruction.

Implement delay loops

Take the processor to bed:
– SLEEP
– Put the processor in an sleep (low power consumption) mode.
– Necessary for battery-powered applications.
– Combined with interrupts.
– Needs previous interrupt and sleep mode configuration.

Departamento de Tecnología Electrónica – Universidad de Sevilla 35


Contents
Toolchains, platforms, frameworks and
IDE’s

Toolchain
– Set of software tools that automate the process of converting source code in executable
programs for some computer processor or family: compilers, assemblers, linkers,
dissemblers, etc.
– Every tool have some specific tasks and one is normally executed after another in an
specific order (the “chain” in “toolchain”).

Platform
– The toolchain extended with additional supporting tools and libraries together with the
hardware it supports. E.g. “Developing for the AVR platform”.

Framework
– Set of software and hardware (board) resources for a given platform that simplifies
development by providing solutions to common problems: programming libraries, program
templates, board configuration, etc. E.g. the Arduino Framework.

Integrated Development Environment (IDE)
– Software tool that works as user interface to the underlying framework, platform and
toolchain in order to make software and/or hardware development easier.
– Tasks: code editing, project management, tool invocation, debugger interface, etc.
– Typically built around some advanced code editor.

Departamento de Tecnología Electrónica – Universidad de Sevilla 36


Contents

AVR toolchains
Tools for embedded systems are cross-

Atmel’s AVR assembler tools: the tools run in a different
architecture than the code they produce:
– Free licensed assembler.

Cross-compiler
– Supports basic assembly programming.

Cross-assembler

AVR GNU toolchain ●
Etc.
– The toolchain that drives GNU/Linux and thousands of Free Software
projects around the world (including Arduino).
– Complete set of tools to program AVR MCU’s in assembly, C and C++.

Compiler, assembler, linker, etc.
– Complete programming platform together with external tools:

Programmer (avrdude), in-hardware debugger (avarice), simulators (simavr), etc.
– Most AVR projects use this.

MPLAB XC compiler
– Non-free C compiler from Microchip.
– Supports AVR MCU’s since Microchip acquired Atmel.

Departamento de Tecnología Electrónica – Universidad de Sevilla 37


Contents

Typical C code toolchain

main.c C Pre- C main.s


processor compiler

Assembly code
assembler main.o
C source code Object (compiled)
code

delay.o
main.elf Executable
Linker program

libc.a

Programmer
Compiled
library Loaded
program

Departamento de Tecnología Electrónica – Universidad de Sevilla 38


Contents

The GCC compiler front-end

C source main.c
code

Assembly delay.S GCC compiler main.elf


code front-end

Object wait.o
code Executable
program

pre-
compiler assembler linker
processor

$ avr-gcc -mmcu=atmega328p -lm -o main.elf main.c delay.S wait.o

Note: assembly .S files will be pre-processed, .s files will not.

Departamento de Tecnología Electrónica – Universidad de Sevilla 39


Contents

C pre-processor macros

Every assembly or C AVR source file should include the avr/io.h
header file that defines standard pre-processor macros for AVR.

The appropriate macros for the MCU in use will be loaded
automatically (from the -mmcu paramter to avr-gcc).

All peripheral registers are defines, like PORTB, DDRB, PINB, PB5,
etc.

Some convenient macro functions are also defined:
– _SRF_IO_ADDR(x): converts x from memory to I/O address.

_SRF_IO_ADDR(x) = x - 32 = x - 0x20.
– _BV(x): Bit Value for number x.

_BV(x) = (1 << x)
Pre-processor macros are text transformations run before actual
compilation/assembly.

Useful to define names for constants.

Some macros may take a parameter making them look like functions.

Departamento de Tecnología Electrónica – Universidad de Sevilla 40


Contents

GNU assembler directives



Directives:
– .text: assemble in code segment.
– .data / .bss: assemble in the .data/.bss data segments. In the AVR the .data segment cannot
be initialized so it is equivalent to the .bss segment (more details later on).
– .skip <n>: reserves <n> bytes in memory.
– .lcomm <label>, <number>: reserves (and label) <number> bytes in the .bss data memory
segment (uninitialized data).
– .byte <b1>, <b2>, ...: defines bytes in memory.
– .2byte <n1>, <n2>, …: Defines 2 bytes data words in memory. Uses the endianess of the
target architecture (little endian in the AVR). .4byte and .8byte also available. .word is
equivalent to .2byte in the AVR.
– .align n / .balign n: align code or data to multiples of 2^n (.align) or n (.balign) bytes. “.align
1” is equivalent to “.balign 2”.

Program instructions should be aligned to even addresses. Always use “.balign 2” after bytes lists to
avoid misalignments.
– .equ / .set <symbol>, <expression>: assign a symbol to an expression.
– .include <file>: includes another <file>.

Assembler directives instruct the assembler on how to create object


code, both program instructions and data.

Departamento de Tecnología Electrónica – Universidad de Sevilla 41


Contents

GNU assembler expressions



Constants
– 10, -17, 0x7a, 0b01111010, “Hello!\n”, 'a', …

Operators:
– ~, -, +, *, /, %, <<, >>, &, |, ^, etc.

Functions
– lo8(x): lowest significant byte of x.
– hi8(x): second lowest significant byte of x.
– hlo8(x), hhi8(x): third and fourth significant bytes of x.
– pm_lo8(x), pm_hi8(x): like lo8 and hi8 but returns program memory
“word” addresses.
– exp2(x), log2(x): base 2 exponential and logarithm.
– abs(x): absolute value.

Assembler expressions are evaluated at assembly time so they can


only use constant data.

Departamento de Tecnología Electrónica – Universidad de Sevilla 42


Contents

A first AVR assembly program

Example 1
a) Write an assembly program for the ATmega328P in an Arduino UNO board that reads
PORTD7 (UNO D7) and write its value to PORTB5 (UNO D13 -LED-). Use load and
store instructions to access port data and logic instructions to alter their bits.
b) Modify the program to use the more efficient bit and bit-branch instructions.
c) Modify the program to activates PORTB5 only when PORTD6 and PORTD7 are one
(emulates an AND gate).
d) Estimate the delay from an input change to an output change if the MCU runs at
16MHz. Compare with the delay of a 74HC08 logic gate.

Departamento de Tecnología Electrónica – Universidad de Sevilla 43


Contents

A first AVR assembly program


; Read macros with port numbers (and more)
#include <avr/io.h>
The code is similar to YASAC's,
.text ; Program code starts here
but digital ports are different
.global main
main:
; "main" identifies program starting point here.
ldi r16, 0b00100000 ; configure PB5 as output
sts DDRB, r16
ldi r16, 0 ; configure PORTD as input
sts DDRD, r16
ldi r16, 0b10000000 ; activate PD7 pull-up
sts PORTD, r16
loop:
lds r16, PORTB ; read PORTB (PORTB is the output data)
lds r17, PIND ; read PORTD (PIND is the external input)
andi r17, 0b10000000 ; check bit 7
breq is_zero
ori r16, 0b00100000 ; force PB5 to 1
jmp continue
is_zero:
andi r16, 0b11011111 ; force PB5 to 0
continue:
sts PORTB, r16 ; write new value in PORTB
jmp loop

Load/store instructions do the job but the AVR has more powerful instructions
for the task: bit set/clear instructions and bit test and skip instructions.

Departamento de Tecnología Electrónica – Universidad de Sevilla 44


Contents

A first AVR assembly program


; Read macros with port numbers (and more) Note the use of the _SFR_IO_ADDR()
#include <avr/io.h>
macro. It converts memory addresses
; The _SFR_IO_ADDR(x) macro converts from memory addresses to I/O addresses so that the address
; to i/o addresses. can be used with I/O instructions.
.text ; Program code starts here

.global main ; "main" is program's starting point


main: We have also substituted jmp
sbi _SFR_IO_ADDR(DDRB), DDB5 ; configure PB5 as output
ldi r16, 0 ; configure PORTD as input
instructions by rjmp. rjmp uses
out _SFR_IO_ADDR(DDRD), r16 less memory and is faster, but
sbi _SFR_IO_ADDR(PORTD), PD7 ; activates PD7 pull-up cannot jump very far away.
loop:
sbis _SFR_IO_ADDR(PIND), PIND7 ; skip next instruction if PIND7 is 1
rjmp is_zero
sbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to 1
rjmp continue
is_zero:
cbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to 0
continue:
rjmp loop

No. instructions load/store bit test


Initialization 6 4
Main loop 9 6
Program 15 10 Program size reduction: 33%

Departamento de Tecnología Electrónica – Universidad de Sevilla 45


Contents

Assembling, uploading and testing


Assemble
$ avr-gcc -mmcu=atmega328p -o main.elf main.S
and link

$ avr-objcopy -j .text -j .data -O ihex main.elf main.hex


Convert to Intel
$ avrdude -c arduino -p atmega328p -P /dev/ttyACM0 -b 115200 \ hex format
-D -U flash:w:main.hex:i

avrdude: AVR device initialized and ready to accept instructions Upload to the
microcontroller
Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e950f (probably m328p)


avrdude: reading input file "main.hex"
avrdude: writing flash (152 bytes):

Writing | ################################################## | 100% 0.04s

avrdude: 158 bytes of flash written


avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 152 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.03s

avrdude: verifying ...


avrdude: 152 bytes of flash verified

avrdude: safemode: Fuses OK (E:00, H:00, L:00)

avrdude done. Thank you.

Departamento de Tecnología Electrónica – Universidad de Sevilla 46


Contents
Arduino UNO and AVR MCU's programming
(uploading) alternatives
USB port
The A. UNO has an USB to RS-232 serial port
converter in the board. The programming
software communicates with the Arduino
bootloader in the MCU through the serial port
and the bootloader writes the code to program
memory and executes it.
Needs the Arduino bootloader pre-loaded.

ICSP (In-Chip Serial Programming)


The ICSP port is connected to the SPI interface
in the ATmega328p. The programming logic in
the chip can read instructions from the SPI and
write it directly to program memory.
Needs an external programmer board (or another
Arduino board running a programming software).

debugWIRE
Single pin protocol (connected to RESET) that
allows programming and in-chip debugging.
See the AVR block diagram Useful for MCU's that have a few pins only.
Needs an external control board.

Departamento de Tecnología Electrónica – Universidad de Sevilla 47


Contents

Debugging programs

Programmers (humans) are not perfect, thus programs have errors
(bugs).

Finding and correcting errors in programs (debugging) can be really
tough.
– Just executing and watching the program failing does not help very
much.

Debuggers can execute programs in a controlled way:
– Single instruction execution.
– Execution until a break point.
– Stop the program when a condition is met.
– Inspect registers/variables as the program runs.
– Change the value of registers/variables as the program runs.
– Etc.

Departamento de Tecnología Electrónica – Universidad de Sevilla 48


Contents

Debugging programs

Native vs non-native debuggers
– Native: the debugger runs in the same computer than the program being
debugged.
– Non-native: the debugger runs in a different computer than the program
being debugged.

The program being debugged runs in the target system.

Non-native debuggers
– The target system may be a real computer or a simulated one.
– Microcontroller development uses non-native debuggers (the target system
is not powerful enough to run the debugger).
– MCU's with a debug unit can be used as target systems: in-chip debugging.

Debug symbols
– The compiler can include debug symbols in the object code with links to the
original source code (-g option in gcc).
– It makes debugging much more powerful and easy.

Departamento de Tecnología Electrónica – Universidad de Sevilla 49


Contents
Debugging AVR programs with
avr-gdb and simavr

avr-gdb is the GNU debugger (GDB) for the AVR platform.
– GDB is one of the most powerful debuggers around.
– It's command-line based.
– Can be used directly in a the terminal window or through a front-end.
– Most Integrated Development Environments (IDEs) have a front-end to
GDB.

simavr is a simulator for the AVR family.
– Can simulate most AVR MCUs.
– Can work as a GDB's remote target

Departamento de Tecnología Electrónica – Universidad de Sevilla 50


Contents
Debugging AVR programs with
avr-gdb and simavr
$ avr-gcc -mmcu=atmega328p -g -o main.elf main.S

$ avr-gdb main.elf $ simavr -g -m atmega328p main.elf


GNU gdb (GDB) 8.1.0.20180409-git Loaded 152 .text at address 0x0
Copyright (C) 2018 Free Software Foundation, Inc. Loaded 0 .data
[...] avr_gdb_init listening on port 1234
This GDB was configured as "--host=x86_64-linux-gnu --target=avr". gdb_network_handler connection opened
[...]
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x00000000 in __vectors ()
(gdb) tui enable ┌──Register group: general─────────────────────────────────────────────────────┐
│r0 0x0 0 │
│r1 0x0 0 │
│r2 0x0 0 │
│r3 0x0 0 │
│r4 0x0 0 │
│r5 0x0 0 │
┌──main.S───────────────────────────────────────────────────────────────────┐
│19 .global main ; "main" is program's start│
│20 main: │
>│21 sbi _SFR_IO_ADDR(DDRB), DDB5 ; configure PB5 as output │
│22 ldi r16, 0 ; configure PORTD as input │
│23 out _SFR_IO_ADDR(DDRD), r16 │
│24 sbi _SFR_IO_ADDR(PORTD), PD7 ; activates PD7 pull-up │
└───────────────────────────────────────────────────────────────────────────┘
remote Thread <main> In: main L21 PC: 0x80
Single stepping until exit from function __vectors,
which has no line number information.
0x00000068 in __trampolines_start ()
(gdb) step
Single stepping until exit from function __trampolines_start,
which has no line number information.
main () at main.S:21
(gdb)

See the avr-bare repo for a quick introduction to avr-gdb and simavr simulation.
Departamento de Tecnología Electrónica – Universidad de Sevilla 51
Contents
Integrated Development Environments
for AVR microcontrollers

PlatformIO
– Free Software IDE integrated with VSCode editor.
– Multi-platform.
– Supports many embedded systems platforms, including the AVR.
– Uses the GNU toolchain, avrdude and simavr as back-end.
– Supports the Arduino Framework.

Can be used to develop Arduino projects (with Arduino libraries).

Microchip Studio (former Atmel Studio)
– Official IDE from the AVR foundry.
– Supports other MCUs from Microchip (PIC, etc.).
– Supported toolchains:

Basic Atmel assembler.

GNU toolchain.

Microchip's own C compiler and toolchain.
– Based on MS Visual Studio (MS Windows only).
– Nice peripheral interface within the debugger.

Departamento de Tecnología Electrónica – Universidad de Sevilla 52


Contents

PlatformIO tips. Setup



Create a new project using the AVR template instructions.

In the PlatformIO tab, project taks menu, use:
– Build, to compile/assemble your project and check erros.
– Upload, to upload your program to the board.
– Clean, to clean generated code.

Debugging
– Set option “debug.allowBreakpointsEverywhere” to TRUE in File →
Preferences → Settings → …
– In Ubuntu (as of 2021), install the libncurses5 package.
– From the “Run and Debug” tab, use “PIO Debug” task to start
debugging.

Departamento de Tecnología Electrónica – Universidad de Sevilla 53


Contents

PlatformIO tips. Debug control



Debug control: from “Run” menu or debug panel on top of the tab.
– Right-click on code line to add/remove break points.
– Continue (F5): run program until next break point or end of program.
– Step over (F10): execute one instruction but “step over” subroutines:
executes subroutines in one step.
– Step into (F11): execute one instruction and “step into” subroutines:
execute subroutines step by step.
– Step out (Shift-F11): continue execution until leaving the current
subroutine.
– Stop (Shift-F5): stop the program and leave the debugging session.

Departamento de Tecnología Electrónica – Universidad de Sevilla 54


Contents

PlatformIO tips. Inspecting data



From debug console (GDB interface): use any GDB command. E.g.:
– Print register r16: p $r16
– Display PINB in binary format: display /t PINB
– Print register ‘y’ in hexadecimal format: p /x 256*$r29 + $r28
– Print 10 bytes starting at ‘data_list’ in hexadecimal format: x /10xb &data_list

Add expression to the “watch” sidebar. Use any GDB-compatible expr. E.g.:
– Register value: $r16
– Register expression (e.g. ‘y’ register value): $r28+256*$r29
– I/O register: PORTB, PIND, etc.
– Address of symbol ‘data_list’: &data_list
– Value at address ‘result’ (little-endian, 16-bit integer): result
– 10 values of type ‘int’ at address ‘data_list’: (int [10])data_list

Memory: add memory start and range to the “memory” side bar (note: data
memory starts at 0x800000, data segment at 0x800100):
– First 10 bytes of the data segment: 0x800100, 10

Departamento de Tecnología Electrónica – Universidad de Sevilla 55


Contents

PlatformIO tips. Modifying data



From debug console (GDB interface): use any GDB command. E.g.:
– Set register r16 to 17: set $r16=17
– Set value at memory address ‘result’ (16-bit, little endian): set
result=0x3a
– Set PIND5 to 1, others to 0: set PIND=0b00100000

From peripherals panel
– Select the register.
– Right-click → Update value

Departamento de Tecnología Electrónica – Universidad de Sevilla 56


Contents

A first AVR assembly program

Example 1
c) Modify the program to activate PORTB5 only when PORTD6 and PORTD7 are one
(emulates an AND gate).

; A software AND gate


; Inputs: PD6, PD7 (active pull-up)
; Output: PB5

; Read macros with port numbers (and more)


#include <avr/io.h>

.text ; Program code starts here

.global main ; "main" is program's starting point


main:
sbi _SFR_IO_ADDR(DDRB), DDB5 ; configure PB5 as output
ldi r16, 0 ; configure PORTD as input
out _SFR_IO_ADDR(DDRD), r16
sbi _SFR_IO_ADDR(PORTD), PD6 ; activates PD6 and PD7 pull-up
sbi _SFR_IO_ADDR(PORTD), PD7
loop:
sbis _SFR_IO_ADDR(PIND), PIND6 ; skip next instruction if PIND7 is 1
rjmp is_zero
sbis _SFR_IO_ADDR(PIND), PIND7 ; skip next instruction if PIND7 is 1
rjmp is_zero
sbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to 1
rjmp continue
is_zero:
cbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to 0
continue:
rjmp loop

Write the code and debug it in PlatformIO.

Departamento de Tecnología Electrónica – Universidad de Sevilla 57


Contents

A first AVR assembly program

Example 1
d) Estimate the delay from an input change to an output change if the MCU runs at
16MHz. Compare with the delay of a 74HC08 logic gate.


Worst case is when the input changes right after it has been checked:
the whole loop have to be executed before activating the output.

Input changes from 10 to 11 (output changes from 0 to 1)
– rjmp (2), cbi (2), rjmp (2), sbis-true (3), sbis-true (3), sbi (2), input sync
(1), output sync (1)
– 16 cycles at 16MHz = 1 microsecond.

Input changes from 11 to 10 (output changes from 1 to 0)
– Fewer cycles because one sbis instruction is false (1 cycle only).

Typical 74HC delay: 10ns = 0,01us (100 times faster).
– NOTE: the actual gate is much faster. This delay includes the input and
output buffers in the chip.

Departamento de Tecnología Electrónica – Universidad de Sevilla 58


Contents

Assembly subroutines

Programs in high-level languages use functions, objects and
methods extensively. Assembly language programs use subroutines
for the same purpose:
– Code re-usability, program modularization, easier development, easier
debugging, etc.

Arguments can be passed to subroutines using registers or the
stack.
– Which option is better?
– Which registers may be used?

Subroutines will use registers for their calculations.
– Will a subroutine clobber my used registers?
– Who is responsible for saving register values, caller or callee?

Departamento de Tecnología Electrónica – Universidad de Sevilla 59


Contents

Assembly subroutines

Example 2
Write a subroutine in assembly that adds up a list of 8-bit number in data memory. The
subroutine should add all the numbers up to the first one that is zero.
The memory address where the list starts is passed to the subroutine in registers r25:r24.
The sum should be left in register r24 before returning from the subroutine.
Leave the subroutine in a separate file so that it can be re-used in other projects, and write
a simple program to test it.

Departamento de Tecnología Electrónica – Universidad de Sevilla 60


Contents
Assembly subroutines
Add list main (test) program
; File: main.S

#include <avr/io.h>

.data ; Work on the data segment (SRAM memory)


.lcomm data_list, 10 ; Save 10 bytes to store data

.text ; Program code starts here


.global main
main:
; initialize the output pin
sbi _SFR_IO_ADDR(DDRB), PB5 ; set PB5 as output
cbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to zero

; load some data in the data list


ldi yl, lo8(data_list) ; use Y register as pointer to the data
ldi yh, hi8(data_list)
ldi r16, 1
st y+, r16 ; indirect store with post-increment
ldi r16, 2
st y+, r16
ldi r16, 3
st y+, 16
ldi r16, 5
st y+, r16
ldi r16, 8
st y+, r16
clr r16 ; the last value is 0
st y, r16 ; no need to post-increment this time

; prepare arguments and call the subroutine


ldi r24, lo8(data_list)
ldi r25, hi8(data_list)
call add_list

; check the return value (should be 19) and activate PB5 if correct
cpi r24, 19
brne finish ; not correct. finish the program
sbi _SFR_IO_ADDR(PORTB), PB5 ; activate PB5
finish: ; just stay here for a while
rjmp finish
Departamento de Tecnología Electrónica – Universidad de Sevilla 61
Contents
Assembly subroutines
Add list subroutine
; File: add_list.S
; Description: Subroutine to add a list of bytes in memory up to the first
; byte that is zero.
; Input: list address in r25:r24.
; Return: list sum in r24
; Author: Jorge Juan-Chico
; Date: 2021-05-27

; Read macros with port numbers (and more)


#include <avr/io.h>

.text ; this goes to the code segment

.global add_list ; must be global to bee seen from other files


add_list:
; use Z as pointer and r24 as accumulator
mov zl, r24
mov zh, r25
clr r24

; main loop: read data from the list, return if zero or accumulate and
; read the next value.
loop:
ld r0, z+
tst r0 ; test r0 (equivalent to and r0,r0)
breq finish
add r24, r0
rjmp loop
finish:
ret

Quick exercise
Take a look at the main loop. Can it be improved to run faster?

Departamento de Tecnología Electrónica – Universidad de Sevilla 62


Contents

AVR handling of static data



Regular programs use different types of data:
– Constant data: unmodifiable data that stays during the whole the program
lifetime.

Stored with program code in the “.text” segment or another read-only segment.
– Static data: modifiable data that stays during all the program.

Stored in the “.data” segment if it initialized.

Stored in the “.bss” segment (from “block starting symbol”) if it is not initialized.
– Dynamic data: created and destroyed as the program runs.

AVR programmers cannot initialize RAM memory so data in the .data
segment cannot be initialized.
– Constant data can be stored in the .text segment, together with the program
code.

Use “.balign 2” after defining bytes to avoid code misalignment.
– Initialized static data can be emulated by copying constant data from program
memory (.text) to data memory (.data) at the beginning of the program.

GCC does it automatically for C programs.

Departamento de Tecnología Electrónica – Universidad de Sevilla 63


Contents

Reading and writing program memory



AVR MCUs can load and store data from/to program
Program memory
memory.

LPM (Load Program Memory) 0000 0001 0000
0001 0003 0002
– Load bytes from program memory.
0002 0005 0004
– Uses program memory “byte address” instead of “word
0003 0007 0006
address”.
– Uses the Z register as pointer.

SPM (Store Program Memory) 3FFF 7FFF 7FFE
– Not supported by all AVR MCUs.
– Flash memory blocks need to be erased before writing.
word address
– Flash blocks have to be written at once (no byte or word byte address
write).
– Mostly useful in bootloader code.

Branch instructions use word addresses. GNU tools use byte


addresses for display, like in avr-gdb or avr-objdump.
Departamento de Tecnología Electrónica – Universidad de Sevilla 64
Contents

Reading and writing program memory

Example 3
a) Write a test program for the add list subroutine of example 2 that creates the list in data
memory by copying a constant list stored in program memory.
b) Write a similar subroutine to the one in example 2 but operating on constant data stored
in program memory instead of data memory. Modify the test program to test it.

Departamento de Tecnología Electrónica – Universidad de Sevilla 65


Contents

Reading and writing program memory


Example 3 ; Input:
; Output:
none.
Activates PB5 if the subroutine is correct.
a) Write a test program for ; Read macros with port numbers (and more)
the add list subroutine of #include <avr/io.h>
example 2 that creates .data ; Work on the data segment (SRAM memory)
the list in data memory .lcomm data_list, 10

by copying a constant .text ; Program code starts here


list stored in program data_list_pm:
.byte 1, 2, 3, 5, 8, 0
memory. .balign 2

.global main
main:
; initialize the output pin
sbi _SFR_IO_ADDR(DDRB), PB5 ; set PB5 as output
cbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to zero

; copy data list from program to data memory


ldi zl, lo8(data_list_pm) ; setup pointers
ldi zh, hi8(data_list_pm)
ldi yl, lo8(data_list)
ldi yh, hi8(data_list)
loop:
lpm r0, z+ ; << NOTE: this is "lpm" not "ld"
st y+, r0
tst r0
brne loop

; prepare arguments and call the add_list subroutine


ldi r24, lo8(data_list)
ldi r25, hi8(data_list)
call add_list

; check the return value (should be 19) and activate PB5 if correct
cpi r24, 19
brne finish ; not correct. finish the program
sbi _SFR_IO_ADDR(PORTB), PB5 ; activate PB5
finish: ; just stay here for a while
Departamento de Tecnología Electrónica – Universidad de Sevilla
rjmp finish 66
Contents

Reading and writing program memory


Example 3 ;
;
Subroutine:
File:
add_list_pm
add_list_pm.S
; Description: Subroutine to add a list of bytes in program memory.
b) Write a similar ; Author: Jorge Juan-Chico
subroutine to the one in ; Date: 2021-05-27
;
example 2 but operating ; Arguments: r25:r24 - address of the list (byte address in program mem.)
on constant data stored ; Return: r24 - result of the sum.
; Description: Add all number up to the first zero byte.
in program memory
instead of data memory. ; Read macros with port numbers (and more)
#include <avr/io.h>
Modify the test program
.text ; this goes to the code segment
to test it.
.global add_list_pm ; must be global to bee seen from other files
add_list_pm:
; use Z as pointer and r24 as accumulator
mov zl, r24
mov zh, r25
clr r24

; main loop: read data from the list, return if zero


; or accumulate and read the next value.
loop:
lpm r0, z+
tst r0 ; test r0 (equivalent to and r0,r0)
breq finish
add r24, r0
rjmp loop
finish:
ret

Departamento de Tecnología Electrónica – Universidad de Sevilla 67


Contents

Reading and writing program memory


Example 3 ; Input:
; Output:
none.
Activates PB5 if the subroutine is correct.
b) Write a similar ; Read macros with port numbers (and more)
subroutine to the one in #include <avr/io.h>
example 2 but operating .text ; Program code starts here
on constant data stored data_list_pm:
.byte 1, 2, 3, 5, 8, 0
in program memory .balign 2
instead of data memory. .global main
Modify the test program main:
; initialize the output pin
to test it. sbi _SFR_IO_ADDR(DDRB), PB5 ; set PB5 as output
cbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to zero

; prepare arguments and call the add_list subroutine


ldi r24, lo8(data_list_pm)
ldi r25, hi8(data_list_pm)
call add_list_pm

; check the return value (should be 19) and activate PB5 if correct
cpi r24, 19
brne finish ; not correct. finish the program
sbi _SFR_IO_ADDR(PORTB), PB5 ; activate PB5
finish: ; just stay here for a while
rjmp finish

Departamento de Tecnología Electrónica – Universidad de Sevilla 68


Contents

Working with data other than 8 bits



The AVR core is an 8-bit processor, but includes some instructions to
make it easier to operate with 16-bit data (words) or even wider or
narrower numbers.

Instruction that operate with words:
– ADIW, SBIW: add/subtract a constant to a word (pair of registers).
– MUL, MULS, MULSU: multiply instructions write the result to R1:R0.
– MOVW: copy register word.
– SPM: store data words in program memory (R1:R0).

Instructions that support multi-byte operations:
– ADC, SBC, SBCI: addition and subtraction with carry.
– CPC: compare with carry.

Instructions that operate on 4-bit data (nibbles).
– SWAP: swap nibbles in a register. Useful with BCD numbers.

Departamento de Tecnología Electrónica – Universidad de Sevilla 69


Contents

Working with data other than 8 bits

Example 4 (16-bit version of example 2)


Write a subroutine in assembly that adds up a list of 8-bit number in data memory. The
subroutine should add all the numbers up to the first one that is zero and return the result
as a 16-bit word.
The memory address where the list starts is passed to the subroutine in registers r25:r24.
The sum should be left in register r25:r24 before returning from the subroutine.
Leave the subroutine in a separate file so that it can be re-used in other projects, and write
a simple program to test it.

Departamento de Tecnología Electrónica – Universidad de Sevilla 70


Contents

Working with data other than 8 bits


; Arguments:
; Return:
r25:r24 - address of the list.
r25:r24 - result of the sum (16 bits).
Subroutine
; Description: Add all numbers (bytes) up to the first zero byte.

; Read macros with port numbers (and more)


#include <avr/io.h>

.text ; this goes to the code segment

.global add_list_w ; must be global to bee seen from other files


add_list_w:
; use Z as pointer and r25:r24 as accumulator
mov zl, r24 Add the LSB first and
mov zh, r25
clr r25 the MSB next with the
clr r24 carry bit.
; main loop: read data from the list, return if zero
; or accumulate and read the next value.
loop:
ld r0, z+
tst r0 ; test r0 (equivalent to and r0,r0)
breq finish
add r24, r0
adc r25, r1 ; add carry to r25. r1=0 under GNU toolchain
rjmp loop
finish:
ret

LSB: Least Significant Byte.


MSB: Most Significant Byte.
Departamento de Tecnología Electrónica – Universidad de Sevilla 71
Contents

Working with data other than 8 bits


; Input:
; Output:
none.
Activates PB5 if the subroutine works correctly.
Test program
; Description: Adds the prime numbers that are lower than 256.

; Read macros with port numbers (and more)


#include <avr/io.h>

; pre-calculated result
.equ RESULT, 6081

.data ; Work on the data segment (SRAM memory)


.lcomm data_list, 100

.text ; Program code starts here


; prepare arguments and call the add_list subroutine
; Static data is at the end of the text segment. ldi r24, lo8(data_list)
; Putting too much data before "main" may prevent ldi r25, hi8(data_list)
; the initialization code to "rjmp" to "main". call add_list_w
.global main ; check the return value and finish if not correct
main: cpi r24, lo8(RESULT) ; compare and skip if equal
; initialize the output pin brne finish
sbi _SFR_IO_ADDR(DDRB), PB5 ; set PB5 as output cpi r25, hi8(RESULT)
cbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to zero brne finish
sbi _SFR_IO_ADDR(PORTB), PB5 ; activate PB5
; copy data list from program to data memory finish: ; just stay here for a while
ldi zl, lo8(data_list_pm) ; setup pointers rjmp finish
ldi zh, hi8(data_list_pm)
ldi yl, lo8(data_list) ; sequence of prime numbers that can be represented with 8 bits.
ldi yh, hi8(data_list) data_list_pm:
loop: .byte 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, \
lpm r0, z+ 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, \
st y+, r0 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, \
tst r0 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, \
brne loop 199, 211, 223, 227, 229, 233, 239, 241, 251, 0
.balign 2

Departamento de Tecnología Electrónica – Universidad de Sevilla 72


Contents

Controlling time

Controlling time is important in many MCU applications:
– Wait an amount of time before taking an action.

E.g. triggering an alarm only when the sensor is active for at least 3
seconds.
– Do something regularly with a fixed time lapse.

E.g. blinking a LED or incrementing a counter.
– Waiting some time after a button press to avoid bounces.

Two ways to control time:
– Waiting loops

Execute a loop a number of times.

The processor does not stop (bad for energy consumption).

Elapsed time can be calculated counting the number of cycles and using the
processor’s clock frequency.
– Using a timer peripheral and possible interrupts (will see later).

Departamento de Tecnología Electrónica – Universidad de Sevilla 73


Contents

Controlling time
Example 5
Write a subroutine in assembly that waits for a number of milliseconds. The number of
milliseconds to wait is passed in registers r25:r24. Returns r25:r24=0.
Using the subroutine, write an assembly program that makes an LED to blink once every
second: it is 500ms on and 500ms off. The LED is connected to pin PB5.

Subroutine ; Define the board's CPU frequency in Hz. Depends on the board and chip
; configuration.
.equ F_CLK, 16000000

; Number of passes of the 1ms delay loop.


.equ NPASS_1MS, F_CLK/1000/4
; ^ ^
; | |
; change units to ms No. of cycles of the 1ms loop (see below)

; Code segment
;
.text
;
; Subroutine: delay_ms
;
; Input: wait time in ms (r25:r24)
; Return: 0 (r25:r24 = 0)
;
.global delay_ms
delay_ms:
ldi r27,hi8(NPASS_1MS) ; initialize 1ms loop
1ms loop. ldi r26,lo8(NPASS_1MS)
wait_1ms_loop: ; 1ms loop (see AVR Instruction Set Manual)
sbiw r26,1 ; substract 1 (2 cycles)
brne wait_1ms_loop ; compare (1 cycle if false, 2 if true)
Repeat r25:r24 sbiw r24,1 ; main loop check
times. brne delay_ms
ret

Departamento de Tecnología Electrónica – Universidad de Sevilla 74


Contents

Controlling time
Test program ;
;
Commutes
blink in
bit 5 in PORTB (PB5) every 500ms. It makes the built-in LED to
the Arduino UNO board that uses an ATmega328p. Can be easily
; modified to us a different output bit or blinking frequency.
; Uses the delay_ms subroutine in delay_ms.S.

; AVR i/o symbols definitions. Defines port and memory names for easier
; programming. Do not write AVR programs without it
#include <avr/io.h>

; Blinking delay in ms (half blinking period).


.equ DELAY, 500

;
; Code segment
;
.text ; text (code) segment starts here

;
; Main program
;
.global main
main: ; our program starts here
; Initialization code
ldi r16, _BV(PB5) ; set bit 5 in PORTB as output
out _SFR_IO_ADDR(DDRB), r16 ; (the built-in LED in Arduino UNO is here)
cbi _SFR_IO_ADDR(PORTB), PB5 ; clear PB5
ldi r16, lo8(DELAY) ; preload delay
ldi r17, hi8(DELAY)

; Main loop
loop:
movw r24, r16 ; argument to the delay_ms subroutine
In output mode, call delay_ms ; wait
sbi _SFR_IO_ADDR(PINB), PB5 ; toggle the LED
setting PINB toggles rjmp loop ; repeat forever
the corresponding pin.

Departamento de Tecnología Electrónica – Universidad de Sevilla 75


Contents

AVR C/C++ programming



Most AVR microcontrollers can be programmed in C/C++ (like the
Atmega series).
– The C compiler needs a minimum set of characteristics: data memory,
stack, etc.

The AVR C library provides lots of useful functions
– Strings, floating point maths, serial port printing, etc.

Supported by the GNU compiler and other GNU tools
– Free software

A major reason for the success of the AVR family.

Foundation of the Arduino framework.

Departamento de Tecnología Electrónica – Universidad de Sevilla 76


Contents

C program example

Example 6
a) Write a C program for the ATmega328P in an Arduino UNO board that reads PORTD7
(UNO D7) and write its value to PORTB5 (UNO D13 -LED-).
b) Debug the program using a debugger and simulator. Test the program in the board if
you have one.

Departamento de Tecnología Electrónica – Universidad de Sevilla 77


Contents

C program example
// Trivial AVR C program
// Read bit 7 of PORTD and write bit 5 of PORTB

// Read macros with port numbers (and more)


#include <avr/io.h> Same as:
void main (void)
DDRB = 0b00100000 Our assembly version
{
unsigned int data; (example 1)
DDRB = _BV(PB5); // configure PB5 as output
DDRD = 0; // configure PORTD as input
PORTD = _BV(PD7); // activate PD7 pull-up

while (1) { ; Read macros with port numbers (and more)


data = PIND & _BV(7); #include
// read PORTD (PIND<avr/io.h>
is the external input)
if (data)
PORTB |= _BV(5); ; The
// force PB5 to _SFR_IO_ADDR(x)
1 macro converts from memory addresses
else ; to i/o addresses.
PORTB &= ~_BV(5); // force PB5 to 0
} .text ; Program code starts here
}
.global main ; "main" is program's starting point
main:
sbi _SFR_IO_ADDR(DDRB), DDB5 ; configure PB5 as output
ldi r16, 0 ; configure PORTD as input
out _SFR_IO_ADDR(DDRD), r16
sbi _SFR_IO_ADDR(PORTD), PD7 ; activates PD7 pull-up
Same as: loop:
sbis _SFR_IO_ADDR(PIND), PIND7 ; skip next instruction if PIND7 is 1
PORTB = PORTB & 0b11011111 rjmp is_zero
sbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to 1
rjmp continue
is_zero:
cbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to 0
continue:
rjmp loop

Departamento de Tecnología Electrónica – Universidad de Sevilla 78


Contents

C compiler code optimization



The C compiler can optimize the resulting assembly code for:
– Code size, speed or both.

Compiler optimization may:
– Change order in which instructions are executed.
– Alter the assembly instructions used to implement the C code.
– Remove portions of code that the compiler thinks are useless.

More optimization means more compilation time and memory usage.

Typical optimization options (full list here)
– -O<level>: <level> goes from 0 (no optimization) to 3 (maximum
optimization). Default is no optimization (-O0).
– -Os: optimize for size. Like -O2 but disables any optimization that may
increase object code size.
– -Og: optimize for debugging. Like -O1 but disables any optimization that
may affect debugging.

Departamento de Tecnología Electrónica – Universidad de Sevilla 79


Contents

Compiling only and disassembling



To see what the compiler have done we have two options:
– Disassembling the object code in the executable program.
– Telling the compiler to just compile to assembly (no assembling or
linking).

Why disassembling?
– Low level debugging, inspecting malicious code, etc.
– Reverse engineering.

Why compile only?
– Debug the compiler.
– Decide about optimization levels.
– Learn how a compiler works.

Departamento de Tecnología Electrónica – Universidad de Sevilla 80


Contents

Compiling only and disassembling

Example 7
a) Compile the program in example 5 to assembly language and compare the generated
assembly code with that of example 1. Try different options for compiler optimization:

-Os, -Og, etc.
b) Imagine you do not have the original C code anymore. You can still list the assembly
code by disassembling the executable file. Disassemble the code compiled with size
optimization “-Os” and compare with the assembly code generated in (a). Pay attention
to the extra code introduced by the linker.

Departamento de Tecnología Electrónica – Universidad de Sevilla 81


Contents

Compile without optimization (-O0)


avr-gcc -mmcu=atmega328p -S -O0 -o main_Os.s main.c .L4:
ldi r24,lo8(41)
ldi r25,0
// Trivial AVR C program .file "main.c" movw r30,r24
// Read bit 7 of PORTD and __SP_H__ = 0x3e ld r24,Z
// write bit 5 of PORTB __SP_L__ = 0x3d mov r24,r24
__SREG__ = 0x3f ldi r25,0
// Read macros with port numbers __tmp_reg__ = 0 andi r24,128
#include <avr/io.h> __zero_reg__ = 1 clr r25
.text std Y+2,r25
void main (void) .global main std Y+1,r24
{ .type main, @function ldd r24,Y+1
unsigned int data; main: ldd r25,Y+2
push r28 or r24,r25
DDRB = _BV(PB5); push r29 breq .L2
DDRD = 0; rcall . ldi r24,lo8(37)
PORTD = _BV(PD7); in r28,__SP_L__ ldi r25,0
in r29,__SP_H__ ldi r18,lo8(37)
while (1) { /* prologue: function */ ldi r19,0
data = PIND & _BV(7); /* frame size = 2 */ movw r30,r18
if (data) /* stack size = 4 */ ld r18,Z
PORTB |= _BV(5); .L__stack_usage = 4 ori r18,lo8(32)
else ldi r24,lo8(36) movw r30,r24
PORTB &= ~_BV(5); ldi r25,0 st Z,r18
} ldi r18,lo8(32) rjmp .L4
} movw r30,r24 .L2:
st Z,r18 ldi r24,lo8(37)
ldi r24,lo8(42) ldi r25,0
ldi r25,0 ldi r18,lo8(37)
movw r30,r24 ldi r19,0
st Z,__zero_reg__ movw r30,r18
ldi r24,lo8(43) ld r18,Z
ldi r25,0 andi r18,lo8(-33)
ldi r18,lo8(-128) movw r30,r24
movw r30,r24 st Z,r18
st Z,r18 rjmp .L4
.size main, .-main
.ident "GCC: (GNU) 5.4.0"

Departamento de Tecnología Electrónica – Universidad de Sevilla 82


Contents

Compile with size optimization (-Os)


avr-gcc -mmcu=atmega328p -S -Os -o main_Os.s main.c

.file "main.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0 ; Read macros with port numbers
__zero_reg__ = 1 #include <avr/io.h>
.section .text.startup,"ax",@progbits
.global main .text
.type main, @function
main: .global main
/* prologue: function */ main:
/* frame size = 0 */ sbi _SFR_IO_ADDR(DDRB), DDB5
/* stack size = 0 */ ldi r16, 0
.L__stack_usage = 0 out _SFR_IO_ADDR(DDRD), r16
ldi r24,lo8(32) sbi _SFR_IO_ADDR(PORTD), PD7
out 0x4,r24 loop:
out 0xa,__zero_reg__ sbis _SFR_IO_ADDR(PIND), PIND7
ldi r24,lo8(-128) rjmp is_zero
out 0xb,r24 sbi _SFR_IO_ADDR(PORTB), PB5
.L2: rjmp continue
sbis 0x9,7 is_zero:
rjmp .L3 cbi _SFR_IO_ADDR(PORTB), PB5
sbi 0x5,5 continue:
rjmp .L2 rjmp loop
.L3:
cbi 0x5,5
rjmp .L2
.size main, .-main
.ident "GCC: (GNU) 5.4.0"

The compiler is good at optimizing code

Most code is compiled with optimization

Departamento de Tecnología Electrónica – Universidad de Sevilla 83


Contents

Disassembling
main.elf: file format elf32-avr
avr-objdump -dr main.elf > main.dis
Disassembly of section .text:
.file "main.c" 00000000 <__vectors>:
__SP_H__ = 0x3e 0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
__SP_L__ = 0x3d 4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
__SREG__ = 0x3f 8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
__tmp_reg__ = 0 [...]
__zero_reg__ = 1 64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
.section .text.startup,"ax",@progbits
.global main 00000068 <__ctors_end>:
.type main, @function 68: 11 24 eor r1, r1
main: 6a: 1f be out 0x3f, r1 ; 63
/* prologue: function */ Linker 6c: cf ef ldi r28, 0xFF ; 255
/* frame size = 0 */ 6e: d8 e0 ldi r29, 0x08 ; 8
/* stack size = 0 */ 70: de bf out 0x3e, r29 ; 62
.L__stack_usage = 0 72: cd bf out 0x3d, r28 ; 61
ldi r24,lo8(32) 74: 0e 94 40 00 call 0x80 ; 0x80 <main>
out 0x4,r24 78: 0c 94 4b 00 jmp 0x96 ; 0x96 <_exit>
out 0xa,__zero_reg__
ldi r24,lo8(-128) 0000007c <__bad_interrupt>:
out 0xb,r24 7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
.L2:
sbis 0x9,7 00000080 <main>:
rjmp .L3 80: 80 e2 ldi r24, 0x20 ; 32
sbi 0x5,5 82: 84 b9 out 0x04, r24 ; 4
rjmp .L2 84: 1a b8 out 0x0a, r1 ; 10
.L3: 86: 80 e8 ldi r24, 0x80 ; 128
cbi 0x5,5 88: 8b b9 out 0x0b, r24 ; 11
rjmp .L2 8a: 4f 9b sbis 0x09, 7 ; 9
.size main, .-main 8c: 02 c0 rjmp .+4 ; 0x92 <main+0x12>
.ident "GCC: (GNU) 5.4.0" 8e: 2d 9a sbi 0x05, 5 ; 5
90: fc cf rjmp .-8 ; 0x8a <main+0xa>
92: 2d 98 cbi 0x05, 5 ; 5
94: fa cf rjmp .-12 ; 0x8a <main+0xa>
Our code is here.
00000096 <_exit>:
96: f8 94 cli

00000098 <__stop_program>:
Departamento de Tecnología Electrónica – Universidad
98: ff cf rjmp .-2 de Sevilla
; 0x98 84
<__stop_program>
Contents
Structure of an executable program
(generated by the GNU toolchain)
main.elf: file format elf32-avr

Disassembly of section .text:

00000000 <__vectors>: Interrupt vectors


0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt> (we’ll see later on)
8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
[...]
64: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>

00000068 <__ctors_end>: Initialization code:


68: 11 24 eor r1, r1 - make r1=0
6a: 1f be out 0x3f, r1 ; 63
6c: cf ef ldi r28, 0xFF ; 255
- clear the status register (0x3F)
6e: d8 e0 ldi r29, 0x08 ; 8 - set the stack pointer to 0x08FF
70: de bf out 0x3e, r29 ; 62 - call the main code
72: cd bf out 0x3d, r28 ; 61
74: 0e 94 40 00 call 0x80 ; 0x80 <main> - call the exit code
78: 0c 94 4b 00 jmp 0x96 ; 0x96 <_exit>

0000007c <__bad_interrupt>: Bad interrupt handling code:


7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
just reset the processor
00000080 <main>:
80: 80 e2 ldi r24, 0x20 ; 32
82: 84 b9 out 0x04, r24 ; 4
84: 1a b8 out 0x0a, r1 ; 10
86: 80 e8 ldi r24, 0x80 ; 128 Main (user) code
88: 8b b9 out 0x0b, r24 ; 11
8a: 4f 9b sbis 0x09, 7 ; 9
8c: 02 c0 rjmp .+4 ; 0x92 <main+0x12>
8e: 2d 9a sbi 0x05, 5 ; 5
90: fc cf rjmp .-8 ; 0x8a <main+0xa>
92: 2d 98 cbi 0x05, 5 ; 5
94: fa cf rjmp .-12 ; 0x8a <main+0xa>

00000096 <_exit>:
96: f8 94 cli Exit code: disable
interrupts and block
00000098 <__stop_program>: the processor.
98: ff cf rjmp .-2 ; 0x98 <__stop_program>
Departamento de Tecnología Electrónica – Universidad de Sevilla 85
Contents
Wait!
Why learning assembly?

If we have C and C compilers, why learning assembly?
– The academic reason: Software Engineers supposed to fully
understand how software works.
– The obvious reason: someone have to write the compilers.

Not only C, but any system-level language compiler: C++, Rust, etc.
– The practical reason: some operating system code and C library
functions are actually written in assembly (see _delay_ms() in avr-libc
for example).

Is assembly and low-level programming that useful?
– Embedded systems and the IoT.
– Operating systems.
– Device drivers.
– Game engines.
– It’s fun!

Departamento de Tecnología Electrónica – Universidad de Sevilla 86


Contents

Where and why is assembly used?



Only assembly.
– In embedded systems that cannot be programmed otherwise.
– Some very simple AVR MCUs, for example.
– E.g.: tiny embedded systems, toaster, battery controller, light controller,
etc.

High-level programs like C and C++ calling assembly subroutines.
– Maximum performance is needed.
– Complete control of the hardware is needed.
– Critical parts where exact timing is needed.
– E.g.: compilers, operating systems, real-time code, device drivers,
graphics engines, etc.

Departamento de Tecnología Electrónica – Universidad de Sevilla 87


Contents

C language functions

Example 8
a) Write a C language function that adds up a list of 8-bit number in data memory. The
function should add all the numbers up to the first one that is zero. The sum should be
returned by the function as a 16-bit integer (type int).
b) Write a simple C program to test the function.
c) Take a look at the assembly code generated by the compiler and compare with the add
list assembly version in example 4.

C language functions are the equivalent to subroutines in


assembly and procedures or methods in other languages.
Functions in C may return a value of some type.

Departamento de Tecnología Electrónica – Universidad de Sevilla 88


Contents

C language functions
// File: add_list.c
// Add bytes in a list up to the first zero

int add_list(char *data_list)


{ add_list function
int accum = 0; // accumulator
char val; // current value
int i = 0; // index

val = data_list[i]; // take first value


while (val != 0) { // accumulate as long as
accum = accum + val;
i++;
// the value is not zero
compile with optimization
val = data_list[i];
}
return accum; // return the result
}

// File: main.c
// Test add_list

#include <avr/io.h>

test code // Declare the add_list function


int add_list (char *);

int main(void)
{
char data_list[] = {1, 2, 3, 5, 8, 0}; // test list to add
int sum; // store the sum here
DDRB = _BV(PB5); // set PB5 as output

sum = add_list(data_list); // sum the list

if (sum == 19) // activate PB5 if the


PORTB |= _BV(PB5); // sum is correct
else
PORTB &= ~_BV(PB5);
}

Departamento de Tecnología Electrónica – Universidad de Sevilla 89


Contents

C language functions
avr-gcc -mmcu=atmega328p -Os -S -o add_list_Os.s add_list.c

.file "add_list.c" ; Arguments: r25:r24 - address of the list.


__SP_H__ = 0x3e ; Return: r25:r24 - result of the sum (16 bits).
__SP_L__ = 0x3d ; Description: Add all bytes up to the first zero byte.
__SREG__ = 0x3f
__tmp_reg__ = 0 ; Read macros with port numbers (and more)
__zero_reg__ = 1 #include <avr/io.h>
.text
.global add_list .text
.type add_list, @function .global add_list_w
add_list: add_list_w:
/* prologue: function */ ; use Z as pointer and r25:r24 as accumulator
/* frame size = 0 */ mov zl, r24
/* stack size = 0 */ mov zh, r25
.L__stack_usage = 0 clr r25
movw r30,r24 clr r24
ld r18,Z+
ldi r24,0 ; main loop
ldi r25,0 loop:
.L2: ld r0, z+
tst r18 tst r0 ; test r0
breq .L5 breq finish
add r24,r18 add r24, r0
adc r25,__zero_reg__ adc r25, r1 ; add carry to r25
sbrc r18,7 rjmp loop
dec r25 finish:
ld r18,Z+ ret
rjmp .L2
.L5:
/* epilogue start */
ret
.size add_list, .-add_list
.ident "GCC: (GNU) 5.4.0"
The compiler did a good job!

Departamento de Tecnología Electrónica – Universidad de Sevilla 90


Contents
AVR C calling convention
Application Binary Inrterface (ABI)

Open questions when programming in assembly:
– Which option is better for argument passing, registers or the stack?
– Which registers may be used?
– Will a subroutine overwrite my used registers?
– Who is responsible for saving register values, caller or callee?

The C compiler uses a function calling convention that answers
these questions.

The calling convention is part of the Application Binary Interface.

It is a very good idea to use the C calling convention in our assembly
programs in order to be compatible with C programs so that:
– C programs can call our assembly subroutines.
– Assembly programs can call C language library functions.

Departamento de Tecnología Electrónica – Universidad de Sevilla 91


Contents
AVR C calling convention
AVR libc data types

The C language does not define a fixed size for its basic data types.

Knowing the type sizes is necessary to interact with C programs from
assembly.

Types in the AVR libc:
– Character (`char`): 8 bits.
– Integer (`int`): 16 bits.
– Long integer (`long`): 32 bits.
– Extra long integer (`long long`): 64 bits.
– Floating point (`float` and `double`): 32 bits.
– Pointers: 16 bits.

Pointers to functions contain addresses to 16-bit program memory words.

Departamento de Tecnología Electrónica – Universidad de Sevilla 92


Contents
AVR C calling convention
Register usage

r0: temporal register.
– Can be used for intermediate calculations and discarded later.

r1: zero register.
– It is assume its value is always zero. Can be used temporarily but returned
to zero afterwards (`clr r1`).

r18-r25, x (r27:r26), z (r31:r30): caller-saved registers.
– The caller is responsible of saving their values before calling a subroutines.
– Subroutines can use these without having to restore their original values
before returning.

r2-r17, y (r29:r28): callee-saved registers.
– The callee (subroutine being called) can use these registers but have to
restore them to their initial value before returning.
– The caller can use them without worrying about a subroutine changing their
value.

Departamento de Tecnología Electrónica – Universidad de Sevilla 93


Contents
AVR C calling convention
Argument passing and return value

Function arguments are passed from left to right using registers r25
to r8 in little-endian format.
– All arguments align to start in even registers. E.g. two 8-bit arguments
will be passed in register r24 (first one) and r22 (second one).
– Extra arguments are passed through the stack.

Return value:
– 8 bits (char) in r24
– 16 bits (int) in r25:r24,
– Up to 32 bits in r25:r22 and up to 64 bits in r25:r18.
– 8-bit data are extended to 16 bits using zeros.

Functions with variable number of arguments (e.g. printf)
– All arguments are passed through the stack.
– char arguments are extended to int.

Departamento de Tecnología Electrónica – Universidad de Sevilla 94


Contents

Calling assembly subroutines from C

Example 9
a) Check if the add list subroutine in example 4 adheres to the C language calling
convention. If not, modify the subroutine to make it compliant with the convention.
b) Write a simple C program to test that calling the subroutine from C actually works. You
may adapt the test program in example 8.

Solution:
a)Yes, the add_list_w subroutine adheres to the C calling convention:

Argument in r25:r24.

Return value in r25:r24

Use registers correctly: r0 (scratch register), r24, r25, z (r31:r30).
b)Just use add_list_w() instead of add_list().

Departamento de Tecnología Electrónica – Universidad de Sevilla 95


Contents

Calling assembly subroutines from C

Example 10
We want to make an LED to blink for 0.1s every second. Write a C program for the AVR to do the
task.
a) Use the _delay_ms() function from the C library to control the time.
b) Write another version that uses the delay_ms assembly subroutine of example 5 to control the
time.
c) (Optional) Disassemble both versions and compare the generated code.
d) (Optional) Find the source code of the _delay_ms() function in the avr-libc distribution. Compare
to the delay_ms assembly subroutine.

Departamento de Tecnología Electrónica – Universidad de Sevilla 96


Contents

Calling C functions from assembly

Example 11
Write a subroutine that adds the absolute values of a list of 16-bit words in data memory.
Use the abs function in the standard C library to calculate the absolute value. Write a test
program for the subroutine.
; Subroutine: add_list_abs
;
; Arguments: r25:r24 - address of the list.
; r22 - number of elements.
; Return: r25:r24 - result of the sum (16 bits). GCC will automatically
; Description: Adds the absolute value of all the
; elements in the list. link to the C library and
; Returns 0 if the number of elements is zero.
; Uses the abs(int) function from the C library. find the “abs” function.
; Read macros with port numbers (and more)
#include <avr/io.h>

.text
; main loop: read data from the list,
.global add_list_abs ; calculate abs, accumulate and repeat.
add_list_abs: loop:
tst r22 ld r24, z+
brne init ld r25, z+
clr r25 call abs
clr r24 add r18, r24
ret adc r19, r25
init: subi r22, 1
; use Z as pointer and brne loop
; r19:r18 as accumulator
mov zl, r24 ; save result and return
mov zh, r25 movw r24, r18
clr r19 ret
clr r18

Departamento de Tecnología Electrónica – Universidad de Sevilla 97


Contents

Interrupts

Interrupts are activated by peripherals or external signals.

Interrupts cause the processor to jump to a service routine depending on
the interrupt line activated.
– Similar to a “call” but triggered by hardware.
– Service routines return with “reti” instruction.

Interrupts allow doing things “just in time” and avoid continuous “polling”.
– An input bit in a digital port changes.
– Data arrives to a serial port.
– A timer has finished, etc.

While not “interrupted” the processor can (should) be in a “sleep” mode,
not executing instructions, and saving power.
– Most real-life programs execute most or all of their functions in interrupt
service routines.
– Essential in battery-powered systems.

Departamento de Tecnología Electrónica – Universidad de Sevilla 98


Contents

Interrupts

Interrupts can be selectively enabled or disabled.

The “I” flag in the status register enables (1) or disables (0) interrupts
globally. Use SEI instruction to enable and CLI instruction to disable.

Most peripherals may generate interrupts and there are also external
interrupt lines.

A peripheral may generate an interrupt when configured to do so.
– Check the peripheral’s control registers for bits that enable interrupts.

Departamento de Tecnología Electrónica – Universidad de Sevilla 99


Contents

Interrupt vectors

An interrupt activation will make the processor to jump to an interrupt
vector.
– The specific executed interrupt vector depends on the source of the
interrupt .

Interrupt vectors are located at the beginning of program memory.

Each vector uses two word addresses in the ATmega328P.

Each vector will typically contain a “jmp” instruction to the interrupt
services routine. This way:
– Interrupt service routines can be easily defined.
– Interrupts can be easily disabled by software.

The GNU GCC compiler will create the interrupt vector in program
memory automatically.
– The programmer just have to define the interrupt vector destination
routines using standard labels: “<interrupt_source_name>_vect:”

Departamento de Tecnología Electrónica – Universidad de Sevilla 100


Contents

Interrupt vectors

Departamento de Tecnología Electrónica – Universidad de Sevilla 101


Contents

Sleep modes

The processor core is the most energy consuming device in the
MCU.

Ideally, the processor core should be stopped while there is nothing
useful to do (just wait for interrupts).
– It is essential in battery-powered devices.

The SLEEP instruction will stop the processor and it will be waken up
when necessary (an interrupt occurs).

Sleep mode have to be enabled for the SLEEP instruction to do
something useful.

The AVR has different sleep modes, with different power savings and
wake up conditions.

In addition, unused peripherals can be shut down to save even more
power by programming the Power Reduction Register (PRR).

Departamento de Tecnología Electrónica – Universidad de Sevilla 102


Contents

Sleep modes

Typical “sleepy”
main loop

; Main program
sei ; enable interrupts
ldi r16, _BV(SE) ; enable "idle"
out _SFR_IO_ADDR(SMCR), r16 ; sleep mode
loop:
sleep ; wait for interrupts
rjmp loop

Departamento de Tecnología Electrónica – Universidad de Sevilla 103


Contents

Sleep modes

Typical “sleepy”
main loop

Departamento de Tecnología Electrónica – Universidad de Sevilla 104


Contents

Interrupt on pin change (PCINT)



When enabled, generate interrupts on MCU pin change.

Pin change signals are shared with other pins of the MCU.
– E.g.: PCINT[7:0] are PB[7:0] in the ATmega328P

3 interrupt vectors cover 24 pin change signals:
– Vector PCINT2: PCINT[23:16]
– Vector PCINT1: PCINT[15:8]
– Vector PCINT0: PCINT[7:0]

Procedure
– Write an interrupt service routine for the desired vector.
– Enable the required interrupt in the PCICR.
– Select the active pins with the PCMSK[2:0] registers.
– Make sure global interrupts are enabled (SEI).
– Alternatively, check if a PCINT has been requested in the PCIFR

Departamento de Tecnología Electrónica – Universidad de Sevilla 105


Contents

Pin change control registers

Enable this to enable


vector PCINT0.
PCINT[7:0] = PB[7:0]

Enable this to trigger


PCINT0 vector on
PCINT1=PB1 change.

Departamento de Tecnología Electrónica – Universidad de Sevilla 106


Contents

Pin change control registers

This is enabled whenever


PCINT0 is requested,
even if PCINT0 interrupt
is not enabled.

Departamento de Tecnología Electrónica – Universidad de Sevilla 107


Contents

Interrupt on pin change


Example 12
Activate PB5 whenever PB0 is activated using pin change interrupts.
Activate PB1 while the MCU is not sleeping (just to check sleep mode).

#include <avr/io.h> ;
.text ; PCINT0 interrupt handler
.global main ;
main: .global PCINT0_vect
; PCINT0 configuration: PCINT0_vect:
ldi r16, _BV(PCIE0) ; enable PCINT0 interrupt ; set PB5 to PB0
sts PCICR, r16 sbis _SFR_IO_ADDR(PINB), PB0
ldi r16, _BV(PCINT0) ; enable interrupt on rjmp int_clear_output
sts PCMSK0, r16 ; PCINT0 (PB0) change sbi _SFR_IO_ADDR(PORTB), PB5
rjmp int_continue
; PORTB configuration int_clear_output:
ldi r16, _BV(PB5) | _BV(PB1) ; PB1 and PB5 as output cbi _SFR_IO_ADDR(PORTB), PB5
out _SFR_IO_ADDR(DDRB), r16 ; rest of PORTB as input int_continue:
ldi r16, ~_BV(PB5) ; activate pull-ups reti
out _SFR_IO_ADDR(PORTB), r16

; Main program
sei ; enable interrupts globally
ldi r16, _BV(SM1) | _BV(SE) ; enable "power-down"
out _SFR_IO_ADDR(SMCR), r16 ; sleep mode
loop:
cbi _SFR_IO_ADDR(PORTB), PB1 ; clear PB1 before sleep
sleep ; wait for interrupts “Power-down” mode is good
sbi _SFR_IO_ADDR(PORTB), PB1 ; set PB1 at wake-up here because it switches off
rjmp loop
most devices.

Departamento de Tecnología Electrónica – Universidad de Sevilla 108


Contents

AVR Timers

AVR timers applications:
– Measure time.
– PWM generation.
– Execute periodic tasks (using interrupts).
– ...

Timer/Counter1 in the AVR has the following registers:
– Control registers: TCCR1A, TCCR1B, TCCR1C.
– Timer/Counter registers: TCNT1(H/L).
– Output compare registers: OCR1A(H/L), OCR1B(H/L).
– Input capture register: ICR1(H/L).
– Interrupt mask register: TIMSK1.
– Interrupt flag register: TIFR1

Departamento de Tecnología Electrónica – Universidad de Sevilla 109


Contents

Timer/Counter 1

Bit 3 – WGM12: Waveform Generation Mode


Used together with WGM11:0 in TCCR1A and WGM13. When all the other bits are 0:

WGM12=0: Normal mode. The counter counts the full range.

WGM12=1: CTC mode. The counter is cleared when it matches the compare register (Clear Timer on Compare match).

Departamento de Tecnología Electrónica – Universidad de Sevilla 110


Contents

Timer/Counter 1

Departamento de Tecnología Electrónica – Universidad de Sevilla 111


Contents

Timer/Counter 1

Departamento de Tecnología Electrónica – Universidad de Sevilla 112


Contents
Timer/Counter 1
Example: periodic interrupts

An interrupt may be executed when the timer counter TCNT1
matches the output compare register OCR1A.

The timer counter may be reset afterwards.

Periodic interrupts procedure with Timer/Counter 1:
– Configure CTC mode: reset timer after counter match.

Set WGM12 in TCCR1B.
– Select the prescaler factor depending on the time scale you need.

Define CS12, CS11 and CS10 in TCCR1B.
– Calculate the OCR1A value for the required period/frequency.

Load OCR1AH and OCR1AL in that order.
– Enable compare A match.

Set OCIE1A in TIMSK1.
– Enable interrupts globally.

Departamento de Tecnología Electrónica – Universidad de Sevilla 113


Contents

Timer/Counter 1
Example 13
Blink a LED every 250ms using interrupts.
Modify it to blink only when a button connected to PB0 is pressed (PB0 = 0).

#include <avr/io.h> .global main


main:
; Define the board's CPU frequency in Hz ; TIMER1 configuration:
.equ F_CLK, 16000000 ; - CTC mode: WGM12=1
; - Timer frequency is F_CLK/1024: CS1[2:0] = 0b101
; Blinking duration in milliseconds ldi r16, _BV(WGM12) | _BV(CS12) | _BV(CS10)
DELAY = 250 sts TCCR1B, r16 ; TCCR1B = 0b00001101
ldi r16, hi8(EOCVAL) ; load end-of-count register
; Calculated end-of-count counter (OCR1A) value sts OCR1AH, r16 ; OCR1AH always first
; The timer will runt at F_CLK/1024 ldi r16, lo8(EOCVAL)
EOCVAL = F_CLK/1024/2/1000*DELAY sts OCR1AL, r16
ldi r16, _BV(OCIE1A) ; enable interrupt when
.text sts TIMSK1, r16 ; compare A matches

; Timer1 interrupt vector, A data compare match ; PORTB configuration


; It just invert the value of PB5 ldi r16, _BV(PB5) ; PB5 as output
; (is there an easier way to do it?) out _SFR_IO_ADDR(DDRB), r16 ; rest of PORTB as input
.global TIMER1_COMPA_vect ldi r16, ~_BV(PB5) ; pull-ups active in all pins
TIMER1_COMPA_vect: out _SFR_IO_ADDR(PORTB), r16 ; except PB5
sbis _SFR_IO_ADDR(PORTB), PB5 ; skip if PB5 is 1
rjmp set_output ; Main loop
cbi _SFR_IO_ADDR(PORTB), PB5 ; set PB5 to 0 sei ; enable interrupts globally
reti ldi r16, _BV(SE) ; enable "idle" sleep mode
set_output: ; set PB5 to 1 out _SFR_IO_ADDR(SMCR), r16
sbi _SFR_IO_ADDR(PORTB), PB5 loop:
reti sleep ; wait for interrupts
rjmp loop
Why not
”power-down” mode?

Departamento de Tecnología Electrónica – Universidad de Sevilla 114


Contents

Other peripherals

Departamento de Tecnología Electrónica – Universidad de Sevilla 115

You might also like