Конечный цикл работает бесконечно
Чтобы лучше понять Rust и Arduino (Uno), я пытаюсь написать аппаратный код для Arduino на Rust. Вот очень простой пример мигания светодиода, который я попытался реализовать.
Я использовал одну библиотеку (crate) под названием avrd, которая обеспечивает отображение адресов только для микроконтроллера ATMega328P.
#![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);
}
}
}
(разборка вышеупомянутого фрагмента вставлена в конец)
По какой-то причине, если значение задержки равно 2 или больше, светодиод никогда не перестаёт мигать. Думаю, проблема в функции задержки, поскольку размещение delay(2) над строкой кода, включающей светодиод, приводит к тому, что светодиод никогда не включается. Ещё одна странность возникает, если я немного изменю код следующим образом:
#[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;
}
}
}
Но на этот раз светодиод включается и выключается, но только один раз (??!!). Бесконечный цикл становится конечным и выполняется только один раз. Не уверен, что генерируемый код неверен или что-то ещё.
Вот файл .cargo/config.toml:
[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"
Инструментарий AVR, который использует Rust, тот же самый, что идет в комплекте с Arduino. Я не устанавливал его отдельно (упоминаю на случай, если этот инструментарий вызовет проблемы на платформах, отличных от C).
Вот дизассемблированный фрагмент кода (RUSTFLAGS="--emit asm" cargo run --release, а не окончательная связанная сборка):
.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
А вот и окончательный (связанный) дизассемблированный файл (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
ДОПОЛНЕНИЕ: Возникает некоторая путаница с тулчейном и тем, кто может быть ответственен за эту проблему. Итак, вот файл atmega328p.json, который сообщает компилятору Rust, как компилировать этот бэкенд (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"
}
Насколько я понимаю, всё, вплоть до линковки, выполняется компонентом Rust/LLVM. Линковка выполняется через avr-gcc. Как сказал @EdgarBonet, похоже, что код, сгенерированный Rust (в сгенерированном --emit asm), правильный, но конечный линкованный вывод неверен. По сути, я хочу сообщить об этой ошибке, а для этого мне нужно понять, кому её следует сообщить.
@Vivek Yadav, 👍7
Обсуждение2 ответа
Лучший ответ:
Похоже, это ошибка в цепочке инструментов: ошибка на единицу во всех Относительные прыжки. Например, основной цикл:
loop {
write_reg(portB, 1 << LED_BUILTIN, 1 << LED_BUILTIN);
delay(500_0000);
write_reg(portB, 0, 1 << LED_BUILTIN);
delay(500_0000);
}
было переведено на это с помощью RUSTFLAGS="--emit asm":
.LBB1_1:
sbi 5, 5
call _ZN13arduino_blink5delay17h9627a982856e7dadE
cbi 5, 5
call _ZN13arduino_blink5delay17h9627a982856e7dadE
rjmp .LBB1_1
Мне кажется, что это правильно. Однако, при разборке финальной версии программа выглядит так:
b0: 2d 9a sbi 0x05, 5 ; PORTB |= (1<<5);
b2: 0e 94 40 00 call 0x80 ; loop: delay();
b6: 2d 98 cbi 0x05, 5 ; PORTB &= ~(1<<5);
b8: 0e 94 40 00 call 0x80 ; delay();
bc: fa cf rjmp .-12 ; goto loop; // 0xb2
Инструкция, включающая светодиод, теперь вынесена из цикла.
другие подобные ошибки, превышающие единицу, в пределах функции задержки, на
инструкции brcs, breq и rjmp.
Я понятия не имею, как это решить, кроме как посмотреть в баг-трекере. инструментария AVR Rust.
Дополнение: Arduino IDE не поставляется с компилятором Rust. Вы
Таким образом, используется отдельный, предположительно экспериментальный, компилятор. Я предполагаю,
что, если явно не указано --emit asm, этот компилятор Rust
Компилируется непосредственно в машинный код. В наши дни довольно часто пропускают
язык ассемблера и компилировать из языка высокого уровня непосредственно в
машинный код.
Первый фрагмент ассемблера был получен с помощью --emit asm. Он не
на самом деле «разборка», а результат прямого Rust → asm
Перевод. Второй фрагмент, сгенерированный avr-objdump, — это правильный
Дизассемблирование: результат Rust → машинный код → asm
перевод.
Я предполагаю, что ошибка кроется в машинном коде Rust →
перевод, поэтому он не влияет на вывод --emit asm.
Относительные переходы на AVR немного не интуитивны: они относительны
счетчик программ, который представляет собой регистр ЦП, содержащий адрес
Следующая инструкция в программе. Если вы неправильно предположите эти
переходы выполняются относительно текущей инструкции, вы получаете точно
Неправильный код второго фрагмента.
Все avr-gcc, avr-g++, avr-objcopy и т. д. — это просто символические ссылки из IDE Arduino. Я не использую какой-либо другой вариант линкера/компилятора, кроме того, что используется в Arduino, так что это довольно странно, особенно если объектный код кажется правильным, но линкованный код — неправильным (что случилось бы с помощью какого-нибудь линкера AVR)., @Vivek Yadav
@zombiesauce: См. исправленный ответ., @Edgar Bonet
Я не утверждал, что Arduino поставляется с компилятором Rust. Я не совсем уверен, как Rust компилируется в AVR, но он действительно перекладывает большую часть архитектурно-специфичной компиляции на набор инструментов GCC AVR. Полагаю, он генерирует MIR (промежуточное представление) из чистого набора инструментов Rust, а остальная работа с AVR, включая линковку, выполняется через avr-gcc, avr-objcopy и всё остальное. Теперь, вместо того, чтобы скачать его из репозитория Ubuntu, поскольку у меня уже есть Arduino, я просто экспортировал их PATH. Возможно, проблема во взаимодействии набора инструментов Rust и AVR., @Vivek Yadav
@zombiesauce: Re “_Я не совсем уверен..._”: Возможно, вам стоит запустить драйвер компилятора Rust в режиме расширенного вывода, чтобы увидеть, как именно он взаимодействует с инструментами GCC. Если компилятор генерирует объектный файл для линковки avr-gcc, можно проверить, содержит ли этот файл промежуточный язык или машинный код. Если это машинный код, можно дизассемблировать его и проверить, есть ли в нём ошибочные относительные переходы., @Edgar Bonet
Отлично объяснено, хотя интересно, как этот компилятор вообще вышел, если он неправильно компилирует ветви. Также странно, что, если он может генерировать правильный ассемблерный код, этот код не используется на следующем этапе (то есть ассемблером gcc)., @Nick Gammon
@NickGammon Согласен, это похоже на грубую ошибку. Хотя ежедневные релизы и не предназначены для массового потребления, они (должны быть таковыми, но я с этим раньше не сталкивался) очень нестабильны., @Vivek Yadav
@EdgarBonet был прав. Оказывается, в последней ночной версии компилятора Rust действительно есть проблема с генерацией кода для AVR. Установка версии ночного компилятора на 2023-12-11 (11 декабря 2023 г.) позволяет программе работать безупречно.
EDIT: Забыл упомянуть, но я уже отправил отчёт об ошибке/проблеме на GitHub Rust.
ДОПОЛНЕНИЕ №2: Ошибка была в тулчейне LLVM для AVR (помните, компиляция всё ещё выполняется LLVM). Ошибка в исходном коде исправлена.
По крайней мере, это дало Эдгару Бонету возможность объяснить, как работает генерация кода. Очень интересная тема., @Nick Gammon
- Я закирпичил свой Arduino Uno? Проблемы с загрузкой скетчей на плату
- Не удается снова загрузиться после смены платы
- Как работают прерывания на Arduino Uno и аналогичных платах?
- В чем разница между ATMEGA32 и ATMEGA328?
- Генерация стабильной частоты
- Arduino UNO для получения подписи чипа ATmega328P-PU
- Последовательная связь ESP8266 с ATMega328P
- Каково время нарастания выходного вывода atmega328, изменяющего свое состояние?
Почему вы вызываете
loop()внутриmain?, @Nick GammonПоскольку сигнатура функции
fn main() -> !подразумевает, что функция никогда не должна завершаться. Для этого я добавил бесконечный цикл в конце, чтобы эта функция никогда не возвращалась., @Vivek YadavЯ понятия не имею, как работает инструментарий Rust, но, судя по дизассемблированию, он предполагает, что регистр
r1загружен значением0, как и инструментарий C/C++. Очисткаr1— задача среды выполнения C. Связывает ли инструментарий Rust среду выполнения C? Не могли бы вы дизассемблировать программу целиком и убедиться, чтоr1действительно очищается перед вызовомmain?, @Edgar Bonet@EdgarBonet Для кода Linux он действительно линкуется с C-RT, но я почти уверен, что для кода на физическом сервере это не сработает. Сейчас проверю и добавлю последний линкованный код., @Vivek Yadav
@EdgarBonet Я добавил последний связанный дизассемблированный код. Не могли бы вы проверить? Я совершенно не разбираюсь в ассемблере, не говоря уже об ассемблере AVR., @Vivek Yadav
Пожалуйста, не публикуйте [ваш вопрос на StackOverflow](https://stackoverflow.com/questions/78882263/finite-loop-runs-infinitely)). Решите, какой из них оставить, а другой удалить., @the busybee