Skip to main content
6 of 6
added atmega328p.json file

Finite loop runs infinitely

To gain better understanding of both Rust and Arduino (Uno), I'm trying to write direct hardware code for Arduino in Rust. Here's a very simple LED blink example, that I've tried to write.

I've made use of one library (crate) called avrd which only provides address mapping for ATMega328P microcontroller.

#![no_std]
#![no_main]
#![feature(asm_experimental_arch)]

use core::{arch::asm, hint::black_box, panic::PanicInfo};

use avrd::atmega328p;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

#[inline(never)]
fn delay(x: u32) {
    for _ in 0..x {
        unsafe {
            asm!("nop");
        }
    }
}

unsafe fn write_reg(reg: *mut u8, val: u8, mask: u8) {
    let reg_val = reg.read_volatile();
    reg.write_volatile((reg_val & !mask) | (val & mask));
}

#[no_mangle]
extern "C" fn main() -> ! {
    const LED_BUILTIN: u8 = 5;
    unsafe {
        let portB_data_direction = atmega328p::DDRB;
        // set it to output mode
        write_reg(portB_data_direction, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
        let portB = atmega328p::PORTB;
        // switch it on, hopefully..
        loop {
            write_reg(portB, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
            delay(500_0000);
            write_reg(portB, 0, 1 << LED_BUILTIN);
            delay(500_0000);
        }
    }
}

(disassembly for the aforementioned snippet pasted at the end)

Now for some reason, if this delay value is 2 or greater, the LED never stops blinking. I think this delay function might be at fault, since putting the delay(2) above the line of code switching on the LED, makes the LED never switch on. Another bizarre thing, is if I change the code up a bit like so:

#[no_mangle]
extern "C" fn main() -> ! {
    const LED_BUILTIN: u8 = 5;
    unsafe {
        let portB_data_direction = atmega328p::DDRB;
        // set it to output mode
        write_reg(portB_data_direction, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
        let portB = atmega328p::PORTB;
        // switch it on, hopefully..
        let mut i = 0;
        loop {
            while i < 1000000 {
                i += 1;
                write_reg(portB, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
            }
            i = 0;
            while i < 1000000 {
                i += 1;
                write_reg(portB, 0, 1 << LED_BUILTIN);
            }
            i = 0;
        }
    }
}

Then though this time the LED switches on, and switches off, but only once (??!!). The infinite loop becomes finite and runs only once. I'm not sure if the code being generated is wrong or what.

Here's the .cargo/config.toml file:

[build]
target = "atmega328p.json" # Plucked from https://github.com/Rahix/avr-hal/

[unstable]
build-std = ["core"]

[target.'cfg(target_arch = "avr")']
runner = "ravedude uno --baudrate 57600"

The AVR toolchain that Rust is consuming is the same one that comes with Arduino, I haven't installed any separately (mentioning in case that toolchain causes problems on non-C platforms).

Here's the disassembly for the first code snippet (RUSTFLAGS="--emit asm" cargo run --release, not the final linked assembly):

    .text
.set __tmp_reg__, 0
.set __zero_reg__, 1
.set __SREG__, 63
.set __SP_H__, 62
.set __SP_L__, 61
    .file   "arduino_blink.caf25912130a4f-cgu.0"
    .section    .text._ZN13arduino_blink5delay17h9627a982856e7dadE,"ax",@progbits
    .p2align    1
    .type   _ZN13arduino_blink5delay17h9627a982856e7dadE,@function
_ZN13arduino_blink5delay17h9627a982856e7dadE:
    ldi r24, 0
    ldi r25, 0
    ldi r18, 75
    ldi r20, 76
    ldi r21, 0
    movw    r22, r24
.LBB0_1:
    ldi r19, 1
    cpi r24, 64
    cpc r25, r18
    cpc r22, r20
    cpc r23, r21
    brlo    .LBB0_3
    mov r19, r1
.LBB0_3:
    andi    r19, 1
    cpi r19, 0
    breq    .LBB0_5
    subi    r24, 255
    sbci    r25, 255
    sbci    r22, 255
    sbci    r23, 255
    ;APP
    nop
    ;NO_APP
    rjmp    .LBB0_1
.LBB0_5:
    ret
.Lfunc_end0:
    .size   _ZN13arduino_blink5delay17h9627a982856e7dadE, .Lfunc_end0-_ZN13arduino_blink5delay17h9627a982856e7dadE

    .section    .text.main,"ax",@progbits
    .globl  main
    .p2align    1
    .type   main,@function
main:
    sbi 4, 5
.LBB1_1:
    sbi 5, 5
    call    _ZN13arduino_blink5delay17h9627a982856e7dadE
    cbi 5, 5
    call    _ZN13arduino_blink5delay17h9627a982856e7dadE
    rjmp    .LBB1_1
.Lfunc_end1:
    .size   main, .Lfunc_end1-main

And here's the final (linked) disassembly (avr-objdump -d binary-name.elf disassembly.s):

Disassembly of section .text:

00000000 <.text>:
   0:   0c 94 34 00     jmp 0x68    ;  0x68
   4:   0c 94 3e 00     jmp 0x7c    ;  0x7c
   8:   0c 94 3e 00     jmp 0x7c    ;  0x7c
   c:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  10:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  14:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  18:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  1c:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  20:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  24:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  28:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  2c:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  30:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  34:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  38:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  3c:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  40:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  44:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  48:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  4c:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  50:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  54:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  58:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  5c:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  60:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  64:   0c 94 3e 00     jmp 0x7c    ;  0x7c
  68:   11 24           eor r1, r1
  6a:   1f be           out 0x3f, r1    ; 63
  6c:   cf ef           ldi r28, 0xFF   ; 255
  6e:   d8 e0           ldi r29, 0x08   ; 8
  70:   de bf           out 0x3e, r29   ; 62
  72:   cd bf           out 0x3d, r28   ; 61
  74:   0e 94 57 00     call    0xae    ;  0xae
  78:   0c 94 5f 00     jmp 0xbe    ;  0xbe
  7c:   0c 94 00 00     jmp 0   ;  0x0
  80:   80 e0           ldi r24, 0x00   ; 0
  82:   90 e0           ldi r25, 0x00   ; 0
  84:   2b e4           ldi r18, 0x4B   ; 75
  86:   4c e4           ldi r20, 0x4C   ; 76
  88:   50 e0           ldi r21, 0x00   ; 0
  8a:   bc 01           movw    r22, r24
  8c:   31 e0           ldi r19, 0x01   ; 1
  8e:   80 34           cpi r24, 0x40   ; 64
  90:   92 07           cpc r25, r18
  92:   64 07           cpc r22, r20
  94:   75 07           cpc r23, r21
  96:   10 f0           brcs    .+4         ;  0x9c
  98:   31 2d           mov r19, r1
  9a:   31 70           andi    r19, 0x01   ; 1
  9c:   30 30           cpi r19, 0x00   ; 0
  9e:   39 f0           breq    .+14        ;  0xae
  a0:   8f 5f           subi    r24, 0xFF   ; 255
  a2:   9f 4f           sbci    r25, 0xFF   ; 255
  a4:   6f 4f           sbci    r22, 0xFF   ; 255
  a6:   7f 4f           sbci    r23, 0xFF   ; 255
  a8:   00 00           nop
  aa:   f1 cf           rjmp    .-30        ;  0x8e
  ac:   08 95           ret
  ae:   25 9a           sbi 0x04, 5 ; 4
  b0:   2d 9a           sbi 0x05, 5 ; 5
  b2:   0e 94 40 00     call    0x80    ;  0x80
  b6:   2d 98           cbi 0x05, 5 ; 5
  b8:   0e 94 40 00     call    0x80    ;  0x80
  bc:   fa cf           rjmp    .-12        ;  0xb2
  be:   f8 94           cli
  c0:   ff cf           rjmp    .-2         ;  0xc0

EDIT: There is some confusion regarding toolchain and what party could be responsible for this issue. So here's the atmega328p.json file which is responsible for telling the Rust compiler how to compile for this backend (AVR): src:atmega32p.json

{
    "arch": "avr",
    "atomic-cas": false,
    "cpu": "atmega328p",
    "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
    "eh-frame-header": false,
    "exe-suffix": ".elf",
    "late-link-args": {
        "gcc": [
            "-lgcc"
        ]
    },
    "linker": "avr-gcc",
    "llvm-target": "avr-unknown-unknown",
    "max-atomic-width": 8,
    "no-default-libraries": false,
    "pre-link-args": {
        "gcc": [
            "-mmcu=atmega328p"
        ]
    },
    "relocation-model": "static",
    "target-c-int-width": "16",
    "target-pointer-width": "16"
}

From what I understand, everything up until the linking is done by the Rust/LLVM part. Linking is done by avr-gcc. As @EdgarBonet said, looks like the code generated by Rust (in the generated --emit asm) is correct but the final linked output is wrong. Basically I'd like to file this bug and for that I need to understand who to file it to.