Interrupts on AVR8

Hi.

I’ve dug out Arduino Nano from my stash and now I’m trying to re-implement one of my old pet project in Zig. After some lurking I wrote this:

const PORTBDATA: u8 = 0x25;
const PORTBDIR: u8 = 0x24;

inline fn cli() void {
    asm volatile ("cli");
}

inline fn sei() void {
    asm volatile ("sei");
}

inline fn setBit(port: *u8, n: u4) void {
    port.* |= (1 << n);
}

export fn __vector_14() callconv(.Signal) void {
    const x: u8 = 9;
    const y: u8 = 8;
    _ = x + y;
}

export fn main () void {

    asm volatile ("cli");
    cli();

    const pbd: *u8 = @ptrFromInt(PORTBDIR);
    const pb: *u8 = @ptrFromInt(PORTBDATA);

    //pbd.* = 0xff;
//    pbd.* |= (1 << 5); // -> sbi 4, 5
    setBit(pbd, 5);
    //pb.*  &= ~(@as(u8, 1) << 5);
    pb.* |= (1 << 5);
    // yessss, same code as with avr-gcc
    // pb.*  ^= (1 << 5); // too long
    while(true) {}
}

There is no much sense in this code, but it does not matter.
The problem is in generated code, which I obtained like this:

zig build-obj -femit-asm -O ReleaseSmall -target avr-freestanding-eabi 2.zig
avr-ld -o 2.elf 2.o
avr-objcopy -j .text -j .data -O ihex 2.elf 2.hex

Looking at disassembled code

Disassembly of section .text:

00000000 <__vector_14>:
   0:   0f 92           push    r0
   2:   0f b6           in      r0, 0x3f        ; 63
   4:   0f 92           push    r0
   6:   0f 90           pop     r0
   8:   0f be           out     0x3f, r0        ; 63
   a:   0f 90           pop     r0
   c:   18 95           reti

we see that it starts from that ISR and this is completely wrong.

For comparison, avr-gcc produces the jump (interrupt vector) table in the very beginning:

00000000 <__vectors>:
       0:       0c 94 3a 00     jmp     0x74    ; 0x74 <__ctors_end>
       4:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
       8:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
       c:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      10:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      14:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      18:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      1c:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      20:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      24:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      28:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      2c:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      30:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      34:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      38:       0c 94 5c 00     jmp     0xb8    ; 0xb8 <__vector_14>
      3c:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      40:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      44:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      48:       0c 94 a9 00     jmp     0x152   ; 0x152 <__vector_18>
      4c:       0c 94 7e 00     jmp     0xfc    ; 0xfc <__vector_19>
      50:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      54:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      58:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      5c:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      60:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>
      64:       0c 94 57 00     jmp     0xae    ; 0xae <__bad_interrupt>

Is there some way to make zig/llvm do the same?

Also, avr-gcc generates some code for copying global variables (from flash to RAM):

00000080 <__do_copy_data>:
      80:       12 e0           ldi     r17, 0x02       ; 2
      82:       a0 e0           ldi     r26, 0x00       ; 0
      84:       b1 e0           ldi     r27, 0x01       ; 1
      86:       e6 eb           ldi     r30, 0xB6       ; 182
      88:       f2 e1           ldi     r31, 0x12       ; 18
      8a:       02 c0           rjmp    .+4             ; 0x90 <__do_copy_data+0x10>
      8c:       05 90           lpm     r0, Z+
      8e:       0d 92           st      X+, r0
      90:       ae 30           cpi     r26, 0x0E       ; 14
      92:       b1 07           cpc     r27, r17
      94:       d9 f7           brne    .-10            ; 0x8c <__do_copy_data+0xc>

Zig doesn’t :frowning:
Does this mean that currently Zig is not completely ready for AVR8?
Or maybe I do not understand something?

1 Like

Aha! I’ve managed to setup interrupt vectors table manually, like this:

// NOTE: this must be the first function in the source!
export fn _vectors() void {
    asm volatile ("jmp main");
    asm volatile ("jmp 0");
    asm volatile ("jmp 0");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp __vector_13");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
    asm volatile ("jmp stub");
} 

....
export fn __vector_13() callconv(.Signal) void {
    flipLed();
    avr.tcnt1.* = one_second;
}

I used intblink.zig from here as my starting point. Original version just does not work because of the absence of that jump table.
And now the LED is cheerfully blinking, hooray!

4 Likes