Skip to main content
Arduino

You are not logged in. Your edit will be placed in a queue until it is peer reviewed.

We welcome edits that make the post easier to understand and more valuable for readers. Because community members review edits, please try to make the post substantially better than how you found it, for example, by fixing grammar or adding additional resources and hyperlinks.

Required fields*

Required fields*

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.

Answer*

Draft saved
Draft discarded
Cancel
6
  • All the avr-gcc, avr-g++, avr-objcopy, etc. are just symlink'd from Arduino's IDE. I'm not using some other variant of linker/compiler than the one that Arduino uses, so this is pretty weird - especially if the object code seems correct but the linked code seems wrong (which would've happened with the help of some AVR linker). Commented Aug 18, 2024 at 10:45
  • @zombiesauce: See amended answer. Commented Aug 18, 2024 at 12:33
  • I was not claiming that Arduino comes with a Rust compiler. Now I'm not exactly sure how Rust compiles to AVR, but it really offloads a lot of architecture-specific compilation to the GCC AVR toolchain. I think it generates MIR (middle intermediate representation) from the pure Rust toolchain, and the rest of the AVR stuff including linking is done via the those avr-gcc, avr-objcopy and the whole shebang. Now, instead of downloading it from Ubuntu's repository, since I already have Arduino; I have instead just exported their PATH. Hence maybe problem is in Rust <-> AVR toolchain interaction. Commented Aug 19, 2024 at 7:49
  • 1
    @zombiesauce: Re "I'm not exactly sure...": Maybe you could launch the Rust compiler driver in verbose mode in order to see exactly how it interacts with the GCC toolchain. If the compiler proper generates an object file to be linked by avr-gcc, you could check whether this file contains intermediate language or machine code. If it's machine code, you could disassemble it and see whether it has the faulty relative jumps. Commented Aug 19, 2024 at 10:15
  • 1
    Nicely explained, although I wonder how this compiler ever got released if it compiles branches incorrectly. It also seems odd that if it can generate the correct assembler code, that this code is not used in the next step (ie. by the gcc assembler). Commented Aug 20, 2024 at 0:37

lang-cpp

AltStyle によって変換されたページ (->オリジナル) /