Hallo Inka,
also generell hat Rust von der Syntax schon Ähnlichkeit mit C, aber durchaus auch seine Eigenheiten. Es gibt jedenfalls ebenfalls keine Objekte/Klassen, sondern man packt Daten eben in Structs und Enums. Dazu gibt es dann noch Traits und generische Parameter, die im Wesentlichen zur Abstraktion und Modularisierung dienen.
Meiner bisherigen Erfahrung nach spielt das bei der Interaktion mit einem Mikrocontroller allerdings eine eher nachrangige Rolle. Da geht es ja letztlich, wie Gerhard schon sagte, hauptsächlich ums "Bitschubsen". Ich nutze als Basis der Hardware-Abstraktion bei der Übersetzung der RP6Lib ein Github-Repo das für den Arduino designt war. Vermutlich kommt dadurch schnell das Gefühl auf, die Syntax wäre ähnlich zu Arduino C. Soll mir auch Recht sein, warum nicht. Letztlich möchte herausfinden wie man die RP6Lib so in Rust umsetzen kann, dass einem der Rust Compiler mit seinem starken Typensystem möglichst gut helfen kann, funktionierende Programme zu entwickeln.
Als Beispiel: In C wird meines Wissens nicht groß zwischen einem char, uint8_t oder einem Struct mit 8 single-bit unsigned unterschieden. Man macht im Zweifel einfach einen Cast, fertig. Wenn ich in Rust zwei Datentypen mit der gleichen Größe / Datenlayout anlege, dann muss man den Compiler schon explizit zwingen das einfach zu casten. Das gilt also auch, wenn man z.B. TXEN = 3 vom Typ "Index in Register UCSRB" hat, anders als eine "Bitmaske für Register UCSRB" (also TXEN = 1 << 3; oder TXEN = 0x8; ). Ausserdem könnte man direkt den Compiler vergleichen lassen, ob man die Bitmaske nun wirklich auf das richtige Register anwendet. Bevor man dort also (z.B. Copy&Paste-)Fehler fabriziert, würde man noch einige (durchaus sinnvolle!) Compiler-Rückmeldungen bekommen, die einem dann bewusst machen, dass man da gerade vermutlich was macht, was so gar nicht gedacht ist. Macht das Sinn?
Ob das am Ende wirklich jemandem nützt ausser mir, weil ich beim umschreiben der Library einiges lerne... das sehen wir dann!
LG Roland
- - - Aktualisiert - - -
Ein weiteres Beispiel sieht man vielleicht oben mit dem Makro:
Code:
set_pins!([Led3, Led2, Led1], value);
Ausgeschrieben generiert das (etwas bereinigt für Lesbarkeit) den Code:
Code:
let pin_mask = Led3::MASK | Led2::MASK | Led1::MASK;
Led3::DDR::set_mask_raw(pin_mask);
Led3::PORT::write(
(Led3::PORT::read() & !pin_mask)
| (((value >> 0) & 1) << Led1::OFFSET)
| (((value >> 1) & 1) << Led2::OFFSET)
| (((value >> 2) & 1) << Led3::OFFSET)
);
Also eine kleine Optimierung über eine Funktion, die jeden Pin einzeln setzt (z.B. Arduino-C meines Wissens), da sowohl DDR als auch PORT register nur einmal geschrieben werden. Der passende Code wird zur Compilezeit generiert, also ohne "schlecht wartbare" kopierte Aufrufe. Ich finde es ausserdem deutlich lesbarer als Funktion, als die originale Funktion in der RP6Lib, die die gleiche Optimierung macht. Zum Vergleich:
Code:
void updateStatusLEDs(void)
{
DDRB &= ~0x83;
PORTB &= ~0x83;
if(statusLEDs.LED4){ DDRB |= SL4; PORTB |= SL4; }
if(statusLEDs.LED5){ DDRB |= SL5; PORTB |= SL5; }
if(statusLEDs.LED6){ DDRB |= SL6; PORTB |= SL6; }
DDRC &= ~0x70;
PORTC &= ~0x70;
DDRC |= ((statusLEDs.byte << 4) & 0x70);
PORTC |= ((statusLEDs.byte << 4) & 0x70);
}
void setLEDs(uint8_t leds)
{
statusLEDs.byte = leds;
updateStatusLEDs();
}
Die Umsetzung dieses Makros hat seine Tücken, z.B., dass nur Pins in der gleichen Portgruppe damit angesteuert werden können. Dazu passiert noch etwas mehr als ich oben geschrieben habe. Ich habe einen Typencheck eingebaut, um sicherzustellen dass Led3:: DDR und Led3::PORT auch wirklich die richtigen Register für alle Pins beinhaltet, aber das wird dann durch Code-Eliminierung vom Compiler gar nicht in das fertige Binary übersetzt. Die ganzen Checks finden also zur Compilezeit (!) statt und wirken sich nicht negativ auf das Laufzeitverhalten aus. Ein möglicher Fehler für den Nutzer sähe z.B. so aus:
Code:
error[E0308]: mismatched types
--> src/avr/device/pin.rs:145:24
|
144 | let mut _typecheck = <$base_pin as Pin>::DDR::default();
| ---------------------------------- expected due to this value
145 | $(_typecheck = <$pin as Pin>::DDR::default();)*
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `DDRB`, found struct `DDRC`
|
::: src/rp6/robot_base/mod.rs:129:9
|
129 | set_pins!([Led6, Led5, Led4, Led3, Led2, Led1], value);
| ------------------------------------------------------ in this macro invocation
|
= note: this error originates in the macro `set_pins` (in Nightly builds, run with -Z macro-backtrace for more info)
Das ganze wäre in Realität dann farbig unterlegt und, wenn man die Fehlermeldungen vom Rust Compiler etwas kennt sieht man schnell, dass er einem direkt markiert dass der Fehler die nicht übereinstimmenden DDR Register sind.
LG Roland
Lesezeichen