Beginner's Intro => Port register access
(Diese Seite in Deutsch:
Address modes in AVRs
Again, we have to be aware of the two address types here:
- accessing SRAM locations,
- accessing portregister locations,
- Accessing port registers,
- Accessing classical port registers,
- Accessing extended port registers,
- Accessing port registers with pointers (circular LED light),
- accessing EEPROM loactions, and
- accessing flash memory locations.
Reading from and Writing to port registers use both address
types, which can confuse the beginner.
- Physical addresses, and
- Pointer addresses.
This type of access uses the two instructions OUT outport,register
to write and IN register,inport to read the content on those
locations. The address type used here are the physical addresses.
Access with OUT and IN is limited to the 64 classical port registers.
The extended port registers are not accessible with those two instructions
(see below on how accessing those works).
If you need to set or clear only one of the bits in a port location, you
can use the SBI port,bit for setting or CBI port,bit for
clearing the bit. These instructions replace the following instructions:
While this (longer) method can be used on all 64 port registers, the SBI
and CBI works for the lower half of the port registers only.
in R16,port ; Read the port
sbr R16,1<<bit ; Setting the bit, or:
ori R16,1<<bit ; alternative for setting the bit in the register
cbr R16,1<<bit ; Clearing the bit, or:
andi R16,256-1<<bit ; alternative for clearing the bit in the register
out port,R16 ; Write to the port
SBI and CBI require two clock cycles, while the alternative single step method
requires three and an additional register.
Toggling a single bit in a port can be done by EXOR-ing the port with a
register that has the bit(s) set that are to be toggled:
If the port to be toggled is an I/O port, you can, in most modern AVR devices,
alternatively write to the I/O's input port to toggle one or more of the bits.
This toggles the bits 1 and 3 of the I/O port PORTA:
in R16,port ; Read the port
ldi R17,1<<bit ; The port bit to be toggled, or
ldi R17,(1<<bit1)|(1<<bit2) ; two bits to be toggled
eor R16,R17 ; Exclusive or
out port,R16 ; Write the toggled port
If the OUT to a port register is ending with the assembler error message
that the port is out of range, this port is in the extended port register
range beyond physical address 0x3F. That is the case in larger ATtiny or
ATmega devices, where 64 port registers did not provide enough address
In that case, you'll have to use the pointer address and either the
instruction STS or ST to write the data to this port. In that case the
address given in the def.inc has already added the 0x20 and is a pointer
address, so that you can simply replace the OUT with STS (or an IN with
LDS) to that location.
Of course, the SBI/CBI and the SBIC/SBIS instructions cannot be used for
these extended port registers. That is why port registers, that require
to be changed with a smaller probability are placed into the exptended
port register area.
Now let us assume you need a 32-bit light row, where one of the 32 LEDs is
pointing towards the next emergency exit. As the whole cycle has to be one
second long, a frequency of 32 Hz increases the the LED and each LED
has to be on for 21.25 ms.
This requires a controller with four complete 8-bit-I/O-ports. The excerpt
from avr_sim's device select window shows all those AVR devices. We can use
one of those, such as the ATmega324PA.
This is the hardware needed. Looks pretty simple, many resistors and LEDs.
If we are sure, that the software will switch only one LED of all 32 at a
time on, we can reduce the number of resistors down to one and connect all
cathodes with that single resistor. If the number of LEDs on is one in each
8-bit-port we can reduce the number of resistors down to four by connecting
all LED cathodes in one 8-bit-port to one resistor.
The first step in software, to make all I/O direction bits output and to
set PORTA's bit 0 to one and all others to zero, can be done with or
without pointers. The version without pointer would be:
These are the register ports controlling I/O ports in an ATmega324PA.
All addresses form a row of three register ports: PIN, DDR and PORT.
The physical as well as the pointer addresses increase by one. So,
with a pointer base address of 0x20 in Y or Z, those register ports
can be accessed with displacements of 0, 3, 6 and 9 for the PIN of the
I/O port, with 1, 4 and 7 the DDR I/O ports are displaced and with
2, 5 and 8 the PORT I/O ports are displaced. If you wonder what is
meant with the term "displacement", see the chapter on
displacement in SRAM.
ldi R16,0xFF ; All bits as output
clr R16 ; All upper 24 bits clear
ldi R16,0x01 ; Lowest bit set
The version with a pointer would use this displacements and would look
Now, that is clearly less efficient, because each STD access consumes
two clock cycles instead of one for an OUT. So we would prefer the
classical OUT method over the pointer method in that case.
ldi R16,0xFF ; All bits as output
ldi YH,High(PINA+0x20) ; Point Y to PINA's pointer address
std Y+1,R16 ; Access the DDR port registers
clr R16 ; The upper 24 bits clear
std Y+5,R16 ; Access the PORT port registers
ldi R16,0x01 ; The lowest bit set
But now, let's write an interrupt service routine for the compare match
interrupt of a timer that controls the speed of our LED row. Quick and
dirty this would be like:
The given clock cycles are for each interrupt. 31 times the interrupt
does not require the restart, consuming 25 clock cycles each, once a
restart is necessary and consumes 27 cycles. This sums up to 802 cycles
in total. Together with 32 wake-ups from sleep and 64 cycles for jumping
back to sleep, we are at approximately at 866 cycles in one second. At
1 MHz clock, that makes a sleep time share of 99.13%.
Tc0CmpIsr: ; 7 clock cycles for int+rjmp
in R16,PORTA ; +1 = 8
lsl R16 ; +1 = 9
out PORTA,R16 ; +1 = 10
in R16,PORTB ; +1 = 11
rol R16 ; +1 = 12
out PORTB,R16 ; +1 = 13
in R16,PORTC ; +1 = 14
rol R16 ; +1 = 15
out PORTC,R16 ; +1 = 16
in R16,PORTD ; +1 = 17
rol R16 ; +1 = 18
out PORTD,R16 ; +1 = 19
brcc Tc0CmpIsrReti ; +1/2 = 20/21
in R16,PORTA ; +1 = 21
rol R16 ; +1 = 22
out PORTA,R16 ; +1 = 23
Tc0CmpIsrReti: ; 21/23 cycles
reti ; +4 = 25/27 cycles
; Total cycles: 31 * 25 + 27 = 802 cycles
But: Three of the four OUT instructions are superfluous, because the
active LED is not in that I/O port. So writing only the one port with
the currently active LED would be sufficient.
In those rare cases, where the active LED changes the I/O port
(every eight's shift), both the old port as well as the next port has
to be written. That brings us to a different algorithm, this time with
pointers. First the initialization:
As these five cycles are to be performed only once during init,
we don't really count them. And the interrupt service routine
with the moving pointer would be like this:
ldi XH,High(PORTA+0x20) ; Pointer address to X, +1 = 1
ldi XL,Low(PORTA+0x20) ; dto. LSB, +1 = 2
ldi rShift,0x01 ; Start shift register, +1 = 3
st X,rShift ; Write this to PORTA, +2 = 5
The number of cycles is slightly more than half of the classical method
without pointers. This is a clear indicator that the method with the moving
pointer is nearly double as efficient, simply because it doesn't waste
time on unnecessary INs and OUTs. That increases the sleep share to
99.43% and also reduces the current consumption of the controller, so
that the emergency power supply lasts longer.
OC0AIsr: ; 7 cycles for int+rjmp
lsl rShift ; +1 = 8
st X,rShift ; +2 = 9
brcc Tc0CmpIsrReti ; +1/2 = 10/11
; Next I/O port
adiw XL,3 ; Point to next channel, +2 = 12
cpi XL,Low(PORTD+3+0x20) ; +1 = 13
brne Tc0CmpIsr1 ; +1/2 = 14/15
ldi XH,High(PORTA+0x20) ; +1 = 16
ldi XL,Low(PORTA+0x20) ; +1 = 17
Tc0CmpIsr1: ; 15/17 cycles
; Restart from the beginning
ldi rShift,0x01 ; Set bit 0, +1 = 16/18
st X,rShift ; +2 = 18/20
Tc0CmpIsrReti: ; 11/18/20
reti ; +4 = 15/22/24
; Total cycles: = 28*15 + 3*22 + 1*24 = 510
Now consider that we connect the LED's cathodes to the I/O pins and the
resistor(s) to plus. There are only a few changes that have to be made in
the software: CLR rShift turns to SER rShift, LDI
rShift,0x01 turns to LDI rShift,0xFE and brcc
Tc0CmpIsrReti turns to brcs Tc0CmpIsrReti. These changes
concern the initiation of the I/O ports as well as the interrupt service
routine. A little more tricky is that the lsl rShift in the ISR
has to shift a one in, e.g. with sec and rol rShift instead
of lsl rShift. Now the software is ready to pull-down the LEDs
to GND instead of driving current into the output pins.
The assembler software for this can be downloaded from
here. It allows to increase or reduce
the circle time between 50 and 2000 milliseconds as well as to select
anodes and cathodes connected to the I/O pins (see the adjustable
constants on top of the source code).
What if we want to switch more than one LED on? If you allow four LEDs
to be on in each LED cycle step, the software is rather simple: just
output the register rShift to each of the four I/O ports, without
the use of pointers. If you want two LEDs to be on at a time (e.g. L1
and L16, L2 and L17, etc.), the algorithm requires pointers and is
a little bit more tricky, because the restart of the two pointers, back
to PORTA, happens in two different port phases. If eight LEDs shall be
on in each phase, consider taking the shift state from a table in flash
memory (see the chapter on
adressing flash) rather than with LSL
or ROL. If more than these LEDs simultanously on, you might run into
current limits of the device: the ATmega324PA can drive only 200 mA
via its VCC and GND pins. So make sure that your LED currents do not
exceed this limit (e.g. with 16 LEDs on - every second LED - drive those
with a maximum current of 12.5 mA per LED, with all LEDs on only
6.25 mA per LED are allowed).
Conclusion: If your program does require the same operation with ports over
and over again, you should consider programming those ports with an algorithm.
In many cases this is more efficient than doing all the same all over again
and wasting typing and assemble time instead of a more intelligent approach.
To the top of this page