Pfad: Home => AVR-EN => Beginner's Intro => Port register access    (Diese Seite in Deutsch: Flag DE) Logo

Address modes in AVRs

  1. accessing SRAM locations,
  2. accessing portregister locations,
    1. Accessing port registers,
    2. Accessing classical port registers,
    3. Accessing extended port registers,
    4. Accessing port registers with pointers (circular LED light),
  3. accessing EEPROM loactions, and
  4. accessing flash memory locations.

2.1 Accessing port registers

Address spaces in AVRs Again, we have to be aware of the two address types here:
  1. Physical addresses, and
  2. Pointer addresses.
Reading from and Writing to port registers use both address types, which can confuse the beginner.

2.2 Accessing classical port registers

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:

  in R16,port ; Read the port
  ; Setting
  sbr R16,1<<bit ; Setting the bit, or:
  ori R16,1<<bit ;   alternative for setting the bit in the register
  ; Clearing
  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

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.

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:

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

  ldi R16,(1<<1)|(1<<3)
  out PINA,R16

2.3 Access to extended port registers

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

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.

2.4 Access with pointers, example: the circular LED light

Devices with 32 I/O pins in 40-pin DIL packages 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.

Schematic of the 32 circular LED light 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:

  ldi R16,0xFF ; All bits as output 
  out DDRA,R16
  out DDRB,R16
  out DDRC,R16
  out DDRD,R16
  clr R16 ; All upper 24 bits clear
  out PORTD,R16
  out PORTC,R16
  out PORTB,R16
  ldi R16,0x01 ; Lowest bit set
  out PORTA,R16

The location of I/O port registers 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.

The version with a pointer would use this displacements and would look like this:

  ldi R16,0xFF ; All bits as output
  ldi YH,High(PINA+0x20) ; Point Y to PINA's pointer address
  ldi YL,Low(PINA+0x20)
  std Y+1,R16 ; Access the DDR port registers
  std Y+4,R16
  std Y+7,R16
  std Y+10,R16
  clr R16 ; The upper 24 bits clear
  std Y+5,R16 ; Access the PORT port registers
  std Y+8,R16
  std Y+11,R16
  ldi R16,0x01 ; The lowest bit set
  std Y+2,R16

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.

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:

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

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

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:

  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

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:

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

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.

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

©2021 by http://avr-asm-tutorial.net