Arduino Nano Every pin access via Assembly

Hello. I am tinkering around with the Nano Every Arduino and write assembly code for the sake of learning. My C skills are ok, but I want to dive into assembler programming. What I accomplished so far: I am able to set the signal of PB0, PB1 and PB2 to either high or low (blinking). But I have no idea how to access the other pins. This is the .S part of my code.

;------------------------
.global test
.global led
;------------------------
test:
    SBI   0x04, 1             ;set bit 1 in DDRB(?), DDRB instead of 0x04 doesnt work though
    SBI   0x04, 0             ;set bit 0 in DDRB(?)
    RET                       ;return to setup() function
;---------------------------------------------------------------------------
led:
    CPI   R24, 0x00           ;value in R24 passed by caller compared with 0
    BREQ  ledOFF              ;jump (branch) if equal to subroutine ledOFF
    SBI   0x05, 1             ;set PB1 to high
    SBI   0x05, 0             ;set PB0 to high
    RCALL myDelay
    RET                       ;return to loop() function
;---------------------------------------------------------------------------
ledOFF:
    CBI   0x05, 1             ;clear PB1
    CBI   0x05, 0             ;clear PB0
    RCALL myDelay
    RET                       ;return to loop() function
;---------------------------------------------------------------------------
myDelay:
...

When I replace 0x04 by DDRB, it doesnt work anymore and I have no clue how to manipulate the data direction of the other ports. "Test" is a setup function called once at the beginning. I couldnt find help in the datasheet of the ATmega4809, so I am asking you for help. The c-part is trivial:

extern "C"
{
  void test();
  void led(byte);
}
//----------------------------------------------------
void setup()
{
  test();
}
//----------------------------------------------------
void loop()
{
  led(1);
  led(0);
}

Is there a memory map/hex number for accessing the other ports or is writing to the other pins much harder to do? I tried to adapt Anas Kuzechies tutorial on youtube to the arduino nano.

Assembly via Arduino (part 1) - Introduction (youtube.com)

Starting with the ATmega4809 (actually, starting with the XMega chips), Atmel substantially changed the way that the SFRs are accessed, as well as their locations in memory. They were made more structure-like, which is nice for C programmers.
In C, instead of DDRB |= 1' you woud write

  PORTB.DIR |= 1;

That gets messy in assembly language, since it doesn't have structures.
Fortunately, the .h or .inc files also have non-structure definitions. In this case, there is PORTB_DIR. Except: remember that I said the locations have changed as well? Most of the peripherals are above the memory limit for the sbi instruction, so you'd have to write something like:

  lds r0, PORTB_DIR
  ori r0, 1
  sts r0, PORTB_DIR

Except there are also not DIRSET registers too, so that could be

   ldi r0, 1
   sts r0, PORTB_DIRSET

This is a bit painful if you were hoping to bit-bang GPIO at full speed, so there is a hack that puts somewhat backward-compatible GPIO SFRs at low addresses, and calls them VPORTs (Virtual Ports), so the preferred assembly language ends up looking like:

  sbi VPORTB_DIR, 0
  sbi VPORTB_DIR, 1
ledon:
  sbi VPORTB_OUT, 1
ledoff:
  cbi VPORTB_OUT, 1

You'll need to look at the datasheet, and probably the iom4809.h include file, to figure out all the new names. (The datasheet mentions the C-style structure and structure-member names, and I wouldn't trust them to be completely consistent with the assembly-compatible underscore-based names.)

1 Like

It worked. Thank you. This is how my code looks like now. The LEDs of PORT D blink in a 0101010/10101010 kind of pattern. Unfortunately pin 6 doesnt exist, but I am happy so far. The clue was to look after the hex numbers of the data direction registers of the VPORTS as you suggested, but to neglect the first two figures in 0x000C for example and to just type in 0x0C (Port D) or 0x04 (Port B).

;------------------------
.global test
.global led
;------------------------
test:
    LDI   R20, 0xFF           ;load "11111111" into register 20
    OUT   0x0C, R20           ;set PORTD to output
    RET                       ;return to setup() function
;---------------------------------------------------------------------------
led:
    CPI   R24, 0x00           ;value in R24 passed by caller compared with 0
    BREQ  ledOFF              ;jump (branch) if equal to subroutine ledOFF
    LDI   R20, 0x55           ;load bit pattern "01010101" into register 20
    OUT   0x0D, R20           ;output of bit pattern to all d-pins 
    RCALL myDelay
    RET                       ;return to loop() function
;---------------------------------------------------------------------------
ledOFF:
    LDI   R20, 0xAA           ;load bit pattern "10101010" into register 20
    OUT   0x0D, R20           ;output of bit pattern to all d-pins 
    RCALL myDelay
    RET                       ;return to loop() function
;---------------------------------------------------------------------------
.equ  delayVal, 20000         ;initial count value for inner loop
;---------------------------------------------------------------------------
myDelay:
    LDI   R20, 100            ;initial count value for outer loop
outerLoop:
    LDI   R30, lo8(delayVal)  ;low byte of delayVal in R30
    LDI   R31, hi8(delayVal)  ;high byte of delayVal in R31
innerLoop:
    SBIW  R30, 1              ;subtract 1 from 16-bit value in R31, R30
    BRNE  innerLoop           ;jump if countVal not equal to 0
    ;--------------
    SUBI  R20, 1              ;subtract 1 from R20
    BRNE  outerLoop           ;jump if R20 not equal to 0
    RET
;---------------------------------------------------------------------------

Here is a screenshot of the content of the iom4809.h header file:

You CAN use the VPORTC_DIR type names, you know...
It's preferred, over the bare numbers.

1 Like

Agreed, makes it much clearer.

#define VPORTD_DIR  0x0C
#define VPORTD_OUT  0x0D
;------------------------
.global test
.global led
;------------------------
test:
    LDI   R20, 0xFF           ;load "11111111" into register 20
    OUT   VPORTD_DIR, R20           ;set PORTD to output
    RET                       ;return to setup() function
;---------------------------------------------------------------------------
led:
    CPI   R24, 0x00           ;value in R24 passed by caller compared with 0
    BREQ  ledOFF              ;jump (branch) if equal to subroutine ledOFF
    LDI   R20, 0x55           ;load bit pattern "01010101" into register 20
    OUT   VPORTD_OUT, R20           ;output of bit pattern to all d-pins 
    RCALL myDelay
    RET                       ;return to loop() function
;---------------------------------------------------------------------------
ledOFF:
    LDI   R20, 0xAA           ;load bit pattern "10101010" into register 20
    OUT   VPORTD_OUT, R20           ;output of bit pattern to all d-pins 
    RCALL myDelay
    RET                       ;return to loop() function
;---------------------------------------------------------------------------
.equ  delayVal, 10000         ;initial count value for inner loop
;---------------------------------------------------------------------------
myDelay:
    LDI   R20, 100            ;initial count value for outer loop
outerLoop:
    LDI   R30, lo8(delayVal)  ;low byte of delayVal in R30
    LDI   R31, hi8(delayVal)  ;high byte of delayVal in R31
innerLoop:
    SBIW  R30, 1              ;subtract 1 from 16-bit value in R31, R30
    BRNE  innerLoop           ;jump if countVal not equal to 0
    ;--------------
    SUBI  R20, 1              ;subtract 1 from R20
    BRNE  outerLoop           ;jump if R20 not equal to 0
    RET
;---------------------------------------------------------------------------

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.