Forum: Compiler & IDEs Rust auf AVR: AVR-Rust -> So geht's


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Hallo Forum.

Programmiersprachen fuer uC gibt es ja eher wenige, verglichen zu der 
Masse an Programmiersprachen fuer den PC.

Von der Programmiersprache Rust gibt es den Ableger AVR-Rust, der Rust 
fuer die AVRs verfuegbar macht.

AVR-Rust:
https://github.com/avr-rust

Das Projekt muss man selber compilieren. Wie das geht hab ich mal in 
einem Bugreport beschrieben:
https://github.com/avr-rust/rust/issues/108#issuecomment-419130948

Als zusaetzliche Abhaengigkeiten sind avr-gcc (zum Linken) und avr-libc 
zu installieren.

Ich hab ein kleines Testprojekt fuer den AT90USB1286 (Teensy++ 2.0) 
gemacht um zu zeigen wie das geht, auch mit Interrupts.

avr-rust-teensy-blink
https://gitlab.com/Bloody_Wulf/avr-rust-teensy-blink

Ist noch nicht wirklich schoen der Code, sollte aber reichen um zu 
zeigen wie es geht.

Zu dem Code:
Datei: std.rs
1
#[lang = "eh_personality"]
2
#[no_mangle]
3
pub unsafe extern "C" fn rust_eh_personality(
4
    _state: (),
5
    _exception_object: *mut (),
6
    _context: *mut (),
7
) -> () {
8
}
9
10
#[lang = "panic_fmt"]
11
#[unwind]
12
pub extern "C" fn rust_begin_panic(_msg: (), _file: &'static str, _line: u32) -> ! {
13
    loop {}
14
}
Dieser Code ist nur fuer den Compiler, deswegen hab ich ihn in eine 
extra Datei ausgelagert, damit main nicht so zugemuellt aussieht.


Datei: main.rs
1
#![feature(abi_avr_interrupt)]
2
#![feature(asm)]
3
#![feature(lang_items)]
4
#![feature(unwind_attributes)]
5
#![no_main]
6
#![no_std]
7
8
extern crate avrd;
9
10
pub mod led;
11
pub mod std;
12
pub mod timer;
13
14
use avrd::at90usb1286::*;
15
use core::ptr::write_volatile;
16
17
#[no_mangle]
18
pub extern "C" fn main() -> ! {
19
    set_output();
20
21
    timer::init();
22
23
    enable_interrupts();
24
25
    loop {
26
27
    }
28
}
29
30
fn enable_interrupts() {
31
    unsafe {
32
        asm!("SEI");
33
    }
34
}
35
36
fn set_output() {
37
    unsafe {
38
        write_volatile(DDRD, 0x40);
39
    }
40
}


Datei: led.rs
1
use avrd::at90usb1286::*;
2
use core::ptr::write_volatile;
3
4
pub fn on() {
5
    unsafe {
6
        write_volatile(PORTD, 0x40);
7
    }
8
}
9
10
pub fn off() {
11
    unsafe {
12
        write_volatile(PORTD, 0x00);
13
    }
14
}


Datei: timer.rs
1
use avrd::at90usb1286::*;
2
use core::ptr::write_volatile;
3
use led;
4
5
static mut FLAG: bool = false;
6
7
8
#[no_mangle]
9
//  Datasheet start counting interruptvectors at 1. But we
10
//  have to decrease that number by 1 to get the right interrupt
11
//  TIMER1 OVF = Vector 21 --------------------+
12
//                                             v
13
pub unsafe extern "avr-interrupt" fn __vector_20() {
14
    // disable the timer
15
    write_volatile(TCCR1B, 0x00);
16
17
    // set timer back to start value
18
    write_volatile(TCNT1, 0x0B_DB);
19
20
    FLAG = !FLAG;
21
22
    if FLAG {
23
        led::on();
24
    } else {
25
        led::off();
26
    }
27
28
    // set prescaler to 256 and start the timer
29
    write_volatile(TCCR1B, 0x04);
30
}
31
32
pub fn init() {
33
    unsafe {
34
        // timer start value so we should get close to 1 sec
35
        // with a prescaler of 256
36
        write_volatile(TCNT1, 0x0B_DB);
37
38
        // enable overflow interrupt
39
        write_volatile(TIMSK1, 0x01);
40
41
        // set prescaler to 256 and start the timer
42
        write_volatile(TCCR1B, 0x04);
43
    }
44
}

Vielleicht hat ja der ein oder andere mal Lust AVR-Rust auszuprobieren, 
ist ja Wochenende :)

Gruesse

von m.n. (Gast)


Lesenswert?

Bevor mein Spieltrieb geweckt wird eine kurze Zwischenfrage:
Werden bei AVR-Rust die Typen f32 und f64 unterstützt?

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

m.n. schrieb:
> Werden bei AVR-Rust die Typen f32 und f64 unterstützt?
Ich hab jetzt spasseshalber mal einfach sowas gemacht:
1
#[no_mangle]
2
pub extern "C" fn main() -> ! {
3
    let mut var1: f32 = 0.0;
4
    let mut var2: f64 = 0.0;
5
6
    loop {
7
        var1 = var1 + 0.37;
8
        var2 = var1 as f64 + 3.21;
9
    }
10
}
Es compiliert zumindestens ohne Fehler.

von m.n. (Gast)


Lesenswert?

Danke.
Aber Compilieren macht der AVR-GCC auch, ohne double entsprechend zu 
würdigen.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

m.n. schrieb:
> Danke.
> Aber Compilieren macht der AVR-GCC auch, ohne double entsprechend zu
> würdigen.
Ach so. Ich dachte Du beziehst dich auf diesen Bug:

Compiler uses f64 literals by default; both float and double are 32 bits 
on AVR #76
https://github.com/avr-rust/rust/issues/76#issue-258761696

Da wird auch ueber die breite von f32 und f64 diskutiert.

von m.n. (Gast)


Lesenswert?

Gut, f64 geht dann wohl nicht.
Die Argumente "ist zu langsam", "braucht man nicht" oder "schreib es Dir 
doch selber" kenne ich von diesem Forum. Das sind Ausreden aber keine 
Lösung. Schade.

von Karl K. (karl2go)


Lesenswert?

Kaj G. schrieb:
> fn enable_interrupts() {
>     unsafe {
>         asm!("SEI");
>     }
> }

Das ist aber nicht ernstgemeint, oder?

Muss man im Jahr 2018 immer noch so eine bescheidene ASM-Einbindung 
machen wie C im letzten Jahrhundert?

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Karl K. schrieb:
> Muss man im Jahr 2018 immer noch so eine bescheidene ASM-Einbindung
> machen wie C im letzten Jahrhundert?
Noe, muss man nicht.
1
fn enable_interrupts() {
2
    unsafe {
3
        //asm!("SEI");
4
        write_volatile(SREG, 0x80);
5
    }
6
}

Ist nur zum Zeigen das man auch sowas machen kann, wenn man meint das 
man es braucht. Ich werde es im Repo ergaenzen. Danke.

Oder wenn es C aehnlicher sein soll:
1
fn enable_interrupts() {
2
    unsafe {
3
        //asm!("SEI");
4
        //write_volatile(SREG, 0x80);
5
        *SREG = 0x80;
6
    }
7
}

Letzteres macht den Code aber um 2 Byte groesser, was wohl aber nur fuer 
SREG gilt. Spreche ich andere Register auf diese weise an, dann wird der 
Code nicht groesser. :-/

: Bearbeitet durch User
von Karl K. (karl2go)


Lesenswert?

Kaj G. schrieb:
> Ist nur zum Zeigen das man auch sowas machen kann, wenn man meint das
> man es braucht.

Das habe ich nicht gemeint. Es ist durchaus sinnvoll, für µC mal ASM mit 
einzubinden, wenns schnell gehen soll.

Aber doch bitte nicht durch seitenweise asm!("..."); Das ist doch 
peinlich.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Karl K. schrieb:
> Das habe ich nicht gemeint. Es ist durchaus sinnvoll, für µC mal ASM mit
> einzubinden, wenns schnell gehen soll.
>
> Aber doch bitte nicht durch seitenweise asm!("..."); Das ist doch
> peinlich.
Das asm-Makro ist nicht spezifisch fuer AVR-Rust. Das Makro ist ein 
Bestandteil der Sprach-Features.

Siehe hier, da ist die Beschreibung mit Beispielen:
https://doc.rust-lang.org/unstable-book/language-features/asm.html

Aber ja, ich gebe dir Recht, sieht nicht schoen aus. Aber dafuer kann 
das AVR-Rust Projekt halt auch nichts. Das ist dem LLVM geschuldet.

https://doc.rust-lang.org/unstable-book/language-features/asm.html#more-information
1
The current implementation of the asm! macro is a direct binding to
2
LLVM's inline assembler expressions, so be sure to check out their
3
documentation as well for more information about clobbers,
4
constraints, etc.
5
6
If you need more power and don't mind losing some of the niceties of
7
asm!, check out global_asm.

: Bearbeitet durch User
von S. R. (svenska)


Lesenswert?

Karl K. schrieb:
> Muss man im Jahr 2018 immer noch so eine bescheidene ASM-Einbindung
> machen wie C im letzten Jahrhundert?

Hardware-Interrupts existieren in allgemeinen Hochsprachen eher nicht, 
also werden sie immer als ASM-Einbindung gemacht. Man kann das natürlich 
auch in eine Bibliothek auslagern, wenn man will... es bleibt trotzdem 
ein ASM.

von Karl K. (karl2go)


Lesenswert?

S. R. schrieb:
> Hardware-Interrupts existieren in allgemeinen Hochsprachen eher nicht,
> also werden sie immer als ASM-Einbindung gemacht.

Das ist Quatsch, in Freepascal schreibe ich Hardware-Interrupts genauso 
wie normale Prozeduren. Theoretisch brauche ich keinen ASM. Praktisch 
ist er manchmal ganz nützlich.

Der Unterschied ist der:
1
// exakte Pause msec, 1 bis 255 msec
2
3
procedure delay_ms(time : uint8); assembler; nostackframe;
4
// Rein: r24 Dauer
5
const
6
  fmul = 1 * Cfcpu div 1000000;
7
label
8
  loop1, loop2, loop3;
9
asm
10
  push r23
11
  push r22
12
  loop1:
13
    ldi r23, fmul
14
    loop2:  // 1000 * fmul = 1000 * 1 * 8 = 8000 cycles / 8MHz
15
      ldi r22, 250
16
      loop3:  // 4 * 250 = 1000 cycles
17
        nop
18
        dec r22
19
        brne loop3
20
      dec r23
21
      brne loop2
22
    dec r24
23
    brne loop1
24
  pop r22
25
  pop r23
26
end;
27
28
fn delay_ms(time : u8) {
29
    let fmul = 1 * Cfcpu div 1000000;
30
    unsafe {
31
        asm!("  push r23");
32
        asm!("  push r22");
33
        asm!("  loop1:");
34
        asm!("    ldi r23, fmul");
35
        asm!("    loop2:");  // 1000 * fmul = 1000 * 1 * 8 = 8000 cycles / 8MHz
36
        asm!("      ldi r22, 250");
37
        asm!("      loop3:");  // 4 * 250 = 1000 cycles
38
        asm!("        nop");
39
        asm!("        dec r22");
40
        asm!("        brne loop3");
41
        asm!("      dec r23");
42
        asm!("      brne loop2");
43
        asm!("    dec r24");
44
        asm!("    brne loop1");
45
        asm!("  pop r22");
46
        asm!("  pop r23");
47
    }
48
}

Oder so... Letzteres kann in Detais falsch sein.

Fällt Dir was auf?

: Bearbeitet durch Admin
von Balatan (Gast)


Lesenswert?

@ Karl K. (karl2go)

"Wenn der Weise auf den Mond zeigt,
sieht der Affe nur den Finger."

(Sprichwort, China)

von Bernd K. (prof7bit)


Lesenswert?

Karl K. schrieb:

> fn delay_ms(time : u8) {
>     let fmul = 1 * Cfcpu div 1000000;
>     unsafe {
>         asm!("  push r23");
>         asm!("  push r22");
>         asm!("  loop1:");
>         asm!("    ldi r23, fmul");
>         asm!("    loop2:");  // 1000 * fmul = 1000  1  8 = 8000 cycles
> / 8MHz
>         asm!("      ldi r22, 250");
>         asm!("      loop3:");  // 4 * 250 = 1000 cycles
>         asm!("        nop");
>         asm!("        dec r22");
>         asm!("        brne loop3");
>         asm!("      dec r23");
>         asm!("      brne loop2");
>         asm!("    dec r24");
>         asm!("    brne loop1");
>         asm!("  pop r22");
>         asm!("  pop r23");
>     }
> }
>
> Oder so... Letzteres kann in Detais falsch sein.
>
> Fällt Dir was auf?

Wahrschenlich kann man auch alle Befehle in ein einziges asm Makro 
schreiben und wahrscheinlich kann man auch clobber Listen und dergleiche 
nutzen und wahrscheinlich müßte man sich auch keine hartcodierten 
Register aus den Fingern saugen sondern könnte sich die vom Compiler 
zuweisen lassen. Schreib also das zweite Beispiel mal entsprechend um 
bevor Du einen Vergleich ziehst.
















[obiger Bereich absichtlich leer wegen bescheuerter Forensoftware]

: Bearbeitet durch User
von S. R. (svenska)


Lesenswert?

Karl K. schrieb:
> Fällt Dir was auf?

Nur, dass du mich vollständig bestätigt hast, obwohl meine 
Ursprungsaussage falsch war. Ich bin mir nicht sicher, was du eigentlich 
sagen wolltest.

"Interrupts ein- und ausschalten" sind sehr spezielle 
Hardwarefunktionen, die in allgemeinen Hochsprachen nichts zu suchen 
haben. Daher nutzt man dafür direkt Assembler, oder ein Makro/eine 
Bibliotheksfunktion, die das irgendwie versteckt.

Zur Korrektur: Ich meinte nicht, dass Interrupt-Handler grundsätzlich in 
Assembler geschrieben sein müssen. Mein eigener Code widerspräche dem 
schon.

Aber ein asm("sti") ist schlicht keine Gotteslästerung.

von asm (Gast)


Lesenswert?

Gibts beim AVR-Rust auch die, IMHO, recht gelungenen 'Verzahnung' 
zwischen Variablen und Asm Argumenten wie in AVR-C/AVR-Ada?

von Carl D. (jcw2)


Lesenswert?

Kaj G. schrieb:
> Karl K. schrieb:
>> Muss man im Jahr 2018 immer noch so eine bescheidene ASM-Einbindung
>> machen wie C im letzten Jahrhundert?
> Noe, muss man nicht.
.
>
1
> fn enable_interrupts() {
2
>     unsafe {
3
>         //asm!("SEI");
4
>         write_volatile(SREG, 0x80);
5
>     }
6
> }
7
>
.
> Ist nur zum Zeigen das man auch sowas machen kann, wenn man meint das
> man es braucht. Ich werde es im Repo ergaenzen. Danke.

Wobei SEI nicht alle anderen Flags löscht!

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Carl D. schrieb:
> Wobei SEI nicht alle anderen Flags löscht!
Das ist ein sehr guter Einwand! Danke.

asm schrieb:
> Gibts beim AVR-Rust auch die, IMHO, recht gelungenen 'Verzahnung'
> zwischen Variablen und Asm Argumenten wie in AVR-C/AVR-Ada?
Was genau meinst du? Kannst du vielleicht ein kleines Beispiel geben?

: Bearbeitet durch User
von Karl K. (karl2go)


Lesenswert?

Bernd K. schrieb:
> Wahrschenlich kann man auch alle Befehle in ein einziges asm Makro
> schreiben...

Was dann so aussieht?
1
      asm volatile (
2
         "ror %3"                          "\n"
3
         "BIT_FF_LOOP%=:"                  "\n"
4
         "ld %0,  %a1"                     "\n"
5
         "adc %0, %0"                      "\n"
6
         "st %a1+, %0"                     "\n"
7
         "dec %2"                          "\n"
8
         "brne BIT_FF_LOOP%="              "\n"
9
            : "=&r" (shifted)                               // outputs
10
            : "e"  (buffer), "r" (length), "r" (newbit)     // inputs
11
            : "memory"                                      // clobbered
12
      );

Auch nicht besser.

> ... und wahrscheinlich kann man auch clobber Listen und dergleiche
> nutzen und wahrscheinlich müßte man sich auch keine hartcodierten
> Register aus den Fingern saugen sondern könnte sich die vom Compiler
> zuweisen lassen.

Das sind mir zu viele Wahrscheinlichs. Komm bitte wieder, wenn Du es 
weißt, nicht nur vermutest.

von Eric B. (beric)


Lesenswert?

Kaj G. schrieb:
> Von der Programmiersprache Rust gibt es den Ableger AVR-Rust, der Rust
> fuer die AVRs verfuegbar macht.

**thumbs up!** Jetzt muss ich mir nur noch ein Projekt ausdenken wofür 
ich ein AVR brauche :-D

von Eric B. (beric)


Lesenswert?

Kaj G. schrieb:
> Das Projekt muss man selber compilieren. Wie das geht hab ich mal in
> einem Bugreport beschrieben:
> https://github.com/avr-rust/rust/issues/108#issuecomment-419130948

Als Anregung: für das Patchen der "OrcRemoteTargetClient.h" Datei kannst 
du das patch commando verwenden; das spart das manuelle Hin- und her 
kopieren und du kriegst trotzdem Updates in vom Repository in der 
Datei mit.

https://www.thegeekstuff.com/2014/12/patch-command-examples

HTH HAND

von Christopher J. (christopher_j23)


Lesenswert?

Eric B. schrieb:
> **thumbs up!** Jetzt muss ich mir nur noch ein Projekt ausdenken wofür
> ich ein AVR brauche :-D

Alternativ läuft das natürlich auch auf Cortex-M oder MSP430 ;)

https://blog.japaric.io/quickstart/
http://namniart.com/programming/rust/msp430/2018/06/13/msp430-rust.html

Der Blog-Artikel zum Cortex-M ist schon etwas veraltet aber unter der 
Hauptseite blog.japaric.io gibt es Nachschub von aktuellen Infos.

von Eric B. (beric)


Lesenswert?

Christopher J. schrieb:
> Eric B. schrieb:
>> **thumbs up!** Jetzt muss ich mir nur noch ein Projekt ausdenken wofür
>> ich ein AVR brauche :-D
>
> Alternativ läuft das natürlich auch auf Cortex-M oder MSP430 ;)

Mein Ziel wäre eher Z80. Es gibt dafür aber kein funktionierendes 
gcc/llvm...

von Christopher J. (christopher_j23)


Lesenswert?

Ein llvm-Backend scheint es für Z80 zu geben: 
https://github.com/jacobly0/llvm-z80 . Alternativ kann man eventuell das 
C-Backend von llvm nutzen und über den Umweg Rust->LLVM-IR->C das ganze 
dann mit dem sdcc kompilieren. Auf dem gleichen Weg kann man auch C++ 
mit dem sdcc realisieren, siehe z.B. 
http://www.colecovision.eu/llvm+sdcc/ aber das ist jetzt endgültig OT.

Hier findet man eine Übersicht wo derzeit so Rust im Embedded-Bereich 
eingesetzt wird: https://github.com/rust-embedded/awesome-embedded-rust

von Sebastian (Gast)


Lesenswert?

http://llvm.org/doxygen/md_lib_Target_AVR_README.html

Das AVR Backend ist experimental, und es gibt eine beunruhigend 
ausehende Liste von Fehlern. Aber vielleicht betreffen die rust ja nicht 
weil die entsprechenden LLVM Funktionen nicht genutzt werden?

von Wilhelm M. (wimalopaan)


Lesenswert?

Christopher J. schrieb:
> Ein llvm-Backend scheint es für Z80 zu geben:
> https://github.com/jacobly0/llvm-z80 . Alternativ kann man eventuell das
> C-Backend von llvm nutzen und über den Umweg Rust->LLVM-IR->C das ganze
> dann mit dem sdcc kompilieren. Auf dem gleichen Weg kann man auch C++
> mit dem sdcc realisieren, siehe z.B.
> http://www.colecovision.eu/llvm+sdcc/ aber das ist jetzt endgültig OT.
>

Das C-Backend existiert nicht mehr (leider) im aktuellen llvm.

von Christopher J. (christopher_j23)


Lesenswert?

Wilhelm M. schrieb:
> Das C-Backend existiert nicht mehr (leider) im aktuellen llvm.

Danke für die Info. Ist irgendwie an mir vorbeigegangen.

von Verrostete Bits (Gast)


Lesenswert?

30 Minuten für die leichte Sonntagnachmittag Unterhaltung:

https://www.youtube.com/watch?v=t99L3JHhLc0&start=20

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.