PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Wiederauferstehung des RP6? -- Erste Erfahrungen und Aufruf zum Austausch!



Pr0gm4n
09.01.2023, 18:00
Hallo zusammen!

[off-topic]
Es ist nun knapp 12 Jahre her, dass ich hier im Roboternetz zuletzt aktiv war, und mangels besserer Ideen um Kontakt aufzunehmen – nun dieser Thread. Ganz schön lange Zeit! Ich hatte immer mit Wehmut daran gedacht, dass ich dieses Elektronik-Basteln nicht weiter verfolgt hab. Inzwischen hab ich mein Abi, sowie einen Bachelor & Master in Informatik gemacht, bin gerade gar sogar an der Promotion. Man könnte also meinen, inzwischen sollte ich genug technisches Verständnis mitbringen, um Projekte auch tatsächlich umsetzen zu können. An dieser Stelle nochmals vielen vielen Dank an all diejenigen, die mein jugendliches, neugieriges und unwissendes Ich hier vor so langer Zeit geduldig ertragen und mir stets mit Rat zur Seite gestanden haben! Zurück zum Thema...
[/off-topic]

Ich habe also am Wochenende mal in meiner alten Bastel-Kiste gestöbert und bin doch gleich beim Robby hängen geblieben. Da war mir schnell klar, der Robby soll wieder Leben eingehaucht bekommen! Also dann, wie ging das nochmal?

Erste Schritte waren (eigentlich) ganz machbar:

Die neuste Software von der Webseite gezogen und die avr-gcc Toolchain für Linux aus den Distro-Packages installiert.
Kompilieren der Examples funktioniert, sofern man die Makefiles leicht anpasst: 'RP6lib' sollte wohl 'RP6Lib' heißen und der neuere Kompiler möchte gern das '-fcommon' Flag gesetzt haben damit er die Dateien linken will.
Der Robotloader erkennt unter Linux (Fedora 37) sofort den USB-Comport und läuft mit openjdk 17.0.5 bislang problemlos. Die LEDs blinken!


Wohin also nun? Erste Ideen gibt es schon:

Ich habe inzwischen einen Co-Bastler gefunden, der auch Interesse hat den Robby zu reaktivieren. Leider hat er noch keine Kenntnisse beim Programmieren, dafür kann er Löten und versteht was von Mechanik und normalen elektronischen Schaltungen. Falls jemand für ihn noch einen zweiten Robby (egal ob erste oder zweite Version) rumstehen hätte den er uns gegen ein paar Bier anbieten möchte, dürft ihr Euch natürlich gerne melden.
Ich arbeite derzeit beruflich mit rust, und da gibt es ja auch ein vielversprechend aussehendes Hardware-Abstraktionslayer (https://docs.rs/avrd/latest/avrd/atmega32/index.html) das unseren Atmega32 unterstützt. Mal sehen ob ich das hinkriege, wäre mein erstes Embedded-Projekt mit rust. Eventuell jemand Lust sich für eine Portierung der RP6Lib zusammenzutun?
Fürs erste würden wir wohl gerne eine Schaufel o.Ä. an den Robby basteln, die dann per Servo abkippen und ein wenig "autonom" herumfahren. Ich denke dann fallen uns eh 1000 neue weitere Dinge ein.


Was machen Eure Robbies denn so inzwischen?


LG Roland

Pr0gm4n
10.01.2023, 01:54
Heut Abend ging es also hoch motiviert ans Werk – mit Erfolg. Nach etwas Kampf mit den verschiedenen mäßig dokumentierten Ressourcen zu Rust Programmierung für AVR Targets läuft es nun also. Alles bisher einfach noch so "zusammengehackt", aber das kann man sicher noch aufräumen. Die Verwendung von Raw-Pointern erfordert in Rust das "unsafe" Keyword und hebelt die Spracheigenen Checks daher an manchen Stellen aus. Das wird sich für den Hardware-Zugriff wohl nicht umgehen lassen, jedoch großteils in Libraries verschieben lassen. Der Code den man als Entwickler dann schlussendlich schreibt, kann sich meiner Meinung nach schon sehen lassen:



#![no_std]
#![no_main]

use rp6::*;

/// entry point for the embedded rust program
#[no_mangle]
pub extern "C" fn main() {
RobotBase::init();
UART::write_str("Hello world!\n");

let init_leds: u8 = 0b001001;
let mut running_light: u8 = init_leds;
loop {
// set LEDs according to the binary number `running_light`
RobotBase::set_leds(running_light);

// sleep for one second
delay_ms(250);

// shift to the left for the 'running' effect
running_light <<= 1;

// reset to the initial LED pattern after the last LED was lit
if running_light > 0b111111 {
running_light = init_leds;
}
}
}


Für mehr Details hab ich das Projekt schon mal auf Github (https://github.com/Pr0gm4n/rust-rp6lib) gepackt. Ich werde sicherlich in den nächsten Tagen noch eine Beschreibung basteln, was man für den Build alles für Dependencies hat. Falls es jemand nicht erwarten kann, schaut mal nach "rustup override set nightly" (verwenden alle Rust AVR Projekte) und verwendet das Makefile im Repository. Was sind denn so generell die Meinungen vom ersten Eindruck? Könnte sich jemand vorstellen, dass Rust auch hier im (Hobby-)Embedded-Bereich eine Rolle spielen könnte? Mir gefällt, dass es eine wirklich moderne und mächtige Sprache ist, und ich freue mich insbesondere darauf ihre Pattern-Matching Features nutzen zu können. Das sorgt in der Regel für echt übersichtlichen Code, bei dem vor allem garantiert wird dass man keinen Fall vergessen hat. Mal sehen, wie gut sich das auf den Embedded-Bereich überträgt.

Nächste Schritte:
Man sieht vlt. im Github Repo schon in welche Richtung ich gerade tendiere. Das Modul "avr" ist aus dem ruduino Crate zusammenkopiert, da ich den leider nicht als Dependency zum laufen bekommen habe. Die Idee wäre jedenfalls, deren Traits für Register und Pins wiederzuverwenden, dann bräuchte ich selbst in der Library kaum mehr "unsafe" Code schreiben. Zusätzlich wollte ich mir gerne noch diese HAL (https://rahix.github.io/avr-hal/atmega_hal/index.html) anschauen. Mal sehen, ob er den Atmega32 richtig abbilden kann, dann käme das auch in Betracht.

So, es ist schon spät. Gute Nacht,
Roland

gunzelg
10.01.2023, 07:53
Hallo Roland
ich habe auch noch einen RP6. Allerdings wartet hier das Fahrgestell auf die Elektronik. Hatte damals -sicher vor mehr als 10 Jahren- mit nem RP5 angefangen, aber nur Fahrwerk, ohne die dazugehörige Elektronik - und diese Elektronik selbergebaut (Lochrasterplatine mit nem Microchip PIC). Dann das Interesse verloren und das Fahrgestell verkauft. Als ich dann in Rente ging (bin schon 60+) kam das Interesse wieder und hab mir nen RP6 gekauft. Möchte da aber wieder meine selbstgebaute Elektronik einbauen. Bin aber im Moment mit anderen Elektronik-Projekten beschäftigt. Aber irgendwann fang ich wieder mit dem RP6 an...

Gruß und weiter viel Spass
Gerhard (Raum München)

Pr0gm4n
10.01.2023, 09:05
Hallo Gerhard,

Das klingt doch super! Ich hoffe auch, dass mein Interesse für die Bastelei noch viele Jahre bestehen bleibt und ich mir mit zunehmender Unsportlichkeit vielleicht ja auch immer mehr Zeit dafür nehmen kann... ;-)
Lass uns wissen wenn du dich wieder dran setzt, ich fand das immer ganz toll hier täglich Updates zu den verschiedenen Projekten der Leute zu lesen und aktiv mitzudiskutieren.

Grüße nach Unterschleissheim (hab auch mal in Garching gewohnt & studiert),
Roland

Pr0gm4n
11.01.2023, 13:55
Also ich hab mich nun dafür entschieden, eine eher simple Hardware Abstraktion auf Basis des Crates 'avrd' selbst zu bauen. Die anderen sind zwar vielleicht genereller, aber haben so komplizierte Build Chains dass ich die Einfachheit des RP6 vermisse. Ich möchte das gerne "schrittweise begreifbar" haben, also erst mal verwenden und wenn man dann doch mal in die Funktionen schaut, sollen die auch nicht gerade über 10 Ecken laufen.

Prinzipiell, gibt es für das grundlegendste einfach die Funktionen:


port::c4::set_output();
port::c4::set_high();
port::c4::set_low();


Gibt's erste Meinungen hierzu? Ich glaube eine ähnliche Abstraktion mal beim Arduino-C gesehen zu haben... oder?

Ein Beispiel wie man mit dieser API dann die 'set_leds()' umsetzen könnte gibt's hier:
https://github.com/Pr0gm4n/rust-rp6lib/commit/57972932cda90248b09cafd3491a5134edc189fe

Nun verwende ich also für Register und Pins als Basis mal die Traits 'Pin' und 'Register' vom 'ruduino' Projekt (vermutlich daher die Nähe zur Arduino Abstraktion...), auch wenn ich hier und dort noch etwas daran verändern möchte:
- Es würde eigentlich Sinn machen, wenn der Rust Compiler genutzt wird um zu überprüfen, dass man nur Pins auf high/low setzt, die in dem Moment auch als Output konfiguriert sind. Sollte eigentlich machbar sein.
- Ich werde wohl auch über eine Art 'Pingroup' nachdenken, die in der gleichen Port-Gruppe sind (also z.B. PB7, PB1 und PB0 für die LEDs 4-6). Mit dieser Pin-Abstraktion müsste man die sonst alle nacheinander setzen, muss ja eigentlich nicht sein. Und ich würde hoffen dass man dann solchen Code schreiben könnte:



use crate::ports::*;

pub fn set_leds(value: u8) {
// set LEDs 1-3 connected on PORTC
let pin_group = PinGroup([c6, c5, c4]);
pin_group.set_outputs();
pin_group.set(value);

// set LEDs 4-6 connected on PORTB
let pin_group = PinGroup([b0, b1, b7]);
pin_group.set_outputs();
pin_group.set(value >> 3);
}

Dabei müsste die 'set(...)' Funktion halt automatisch anhand der Anzahl vorhandener Pins maskieren, bzw. einfach die unteren x Bits verwenden.


Liebe Grüße,
Roland

gunzelg
11.01.2023, 15:14
Hallo
bei deinem Code-Beispiel fühl ich mich erst mal als Oldi, da ich ausser Assembler nur in C programmiere (n kann :-) ) Also das ist Rust?
Roboter Programmieren ist für mich auch nur sowas wie Bitschubserei, deine Funktionen sehen aber toll aus, mal sehen, ob ich sowas auch in c zustande bringe...

Gruß
gerhard

Pr0gm4n
11.01.2023, 15:45
Hallo Gerhard,

also jetzt wo du es sagst – der 'rustonian way' das zu implementieren wäre eigentlich eine API in dieser Art:


use crate::ports::*;

pub fn set_leds(value: u8) {
// set LEDs 1-3 connected on PORTC
PinGroup([c6, c5, c4])
.ensure_outputs()
.set(value);

// set LEDs 4-6 connected on PORTB
PinGroup([b0, b1, b7])
.ensure_outputs()
.set(value >> 3);
}


Oder gar das 'ensure_output()' in der '.set(...)' Funktion handhaben... aber da bin ich mir dann nicht sicher ob das nicht "unnötig Zyklen verschwendet"? Was meint Ihr dazu? Sollte so eine API lieber priorisieren dass der Pin auf jeden Fall als Output gesetzt ist und dazu das DDR Register jedes mal überschreiben? (So hat das jedenfalls die RP6Lib implementiert bei den LEDs.)

Und wenn man dann schon dabei ist, könnte man die LEDs die an verschiedenen PORTs angehängt sind ja auch gleich noch als 'MixedPinGroup' Typ implementieren..., dann bekäme man direkt eine Funktion '.set(...)' die dann die 6 verschiedenen Bits richtig ansteuert. Also (ein etwas vollständigeres Beispiel):



use crate::ports::*;

pub struct RobotBase {
LEDS: MixedPinGroup;
}

impl RobotBase {
const LEDS = MixedPinGroup([b0, b1, b7, c6, c5, c4]);
}


Und in der main dann:


use rp6::*;

pub extern "C" fn main() {
RobotBase::init();

loop {
RobotBase::LEDS.set(0b111111);
delay_ms(1000);
RobotBase::LEDS.set(0b000000);
delay_ms(1000);
}
}


Möglichkeiten über Möglichkeiten... was meint Ihr? ;-)

Grüße,
Roland

Pr0gm4n
15.01.2023, 15:07
Hallo zusammen!

Nach einigem Rumprobieren und Ausloten was denn nun möglich ist, und was nicht, habe ich nun also ein solches Makro hinbekommen:



/// Set the LEDs on the `RobotBase` to the least significant 6 bits of the provided value
pub fn set_leds(value: u8) {
// set LEDs SL1-SL3
set_pins!([c6, c5, c4], value);
// set LEDs SL4-SL6
set_pins!([b0, b1, b7], value >> 3);
}

https://github.com/Pr0gm4n/rust-rp6lib/blob/13e785c6c6963e3c349be4418e6466ddbc6750e4/src/rp6/robot_base.rs#L132

Das mit den LEDs über mehrere Register hinweg hab ich mir dann nochmal überlegt und befunden, dass mir dazu gar nicht so viele Anwendungsfälle einfallen. Naja, das schöne an der Lösung mit dem Makro ist jedenfalls, dass das alles bereits zur Compile-Zeit übersetzt und entsprechend vom Compiler optimiert werden kann. Ich denke damit bin ich jetzt einfach mal zufrieden.

Den Rest vom heutigen Nachmittag wollte ich gern damit verbringen, mehr von der RP6 Library zu portieren um so weitere Features "freizuschalten".

Grüße aus dem verregneten Zürich,
Roland

Pr0gm4n
22.01.2023, 22:41
Hallo zusammen,

mal wieder ein kleines Update von mir! Ich lerne bei dem Projekt doch auch einiges über die low-level Features von Rust – ganz anders als in der Arbeit wo es eher um parallele Datenstrukturen und Skalierbarkeit geht. Wirklich spannend, aber ich möchte mir auch die Zeit nehmen die Dinge richtig zu implementieren!

Ich habe nun schon mal die UART Schnittstelle angebunden, zumindest schreibend. Ein paar Dinge habe ich dabei zum Beispiel gelernt:
- Es wäre ja zu schade das Rust-Typensystem nicht vernünftig zu nutzen. Ich musste etwas rumprobieren, aber nun gibt es eine vernünftige API die etwa so benutzt werden kann:



Serial::write("Hier kann man einfach irgendetwas übergeben.");
Serial::write('E');
Serial::write('i');
Serial::write('n');
Serial::write('e');
Serial::write("Zahl: ");
Serial::write(1337);
Serial::write(" oder was auch immer das den Trait `SerialWritable` implementiert.");
Serial::new_line();

// Alternativ gibt es auch ein Macro für Convenience. Bei Zeiten möchte ich mir auch
// mal proc_macros ansehen und damit ein schöneres `format!` Macro bauen.
println!("Counter: ", counter, " (DEC) | ", counter => hex, "(HEX)");

- Rust's eingebaute Formatter sind wahre Monster (zumindest für den Embedded-Einsatz): https://jamesmunns.com/blog/fmt-unreasonably-expensive/
- Ich habe also auf `ufmt` umgestellt – das kann leider nur Formatierung als Dezimal- und Hexadezimal Werte, da bin ich also mit einem Pull Request dran das hoffentlich mittelfristig zu ändern: https://github.com/japaric/ufmt/pull/54

Demnächst steht dann an, sich damit auseinanderzusetzen wie ich die Hardware-Interrupts in Rust verarbeiten kann, um auch vernünftige Receive-Funktionen bauen zu können. Mal sehen wann ich wieder Zeit finde :-)


LG Roland

- - - Aktualisiert - - -

Ach ja, ich denke ich hatte noch gar nichts über die Fortschritte bei der sonstigen Library geredet. Es gibt jedenfalls nun eine Hardware-Abstraktion für alle Pins, basierend auf dem `avrd` crate (auch wenn der Bitmasken leider seltsam definiert...: https://github.com/avr-rust/avrd/issues/22)
https://github.com/Pr0gm4n/rust-rp6lib/blob/main/src/rp6/robot_base/port.rs

Und damit sieht die Implementierung von Basis-Funktionen meiner Meinung nach schon sehr lesbar aus:
https://github.com/Pr0gm4n/rust-rp6lib/blob/main/src/rp6/robot_base/mod.rs#L86

Was meint Ihr dazu? Ein paar Namen habe ich nach wie vor einfach aus der RP6Lib übernommen, da ich mich noch nicht mit den zugehörigen Pins beschäftigt habe.


LG und Gute Nacht,
Roland

fabqu
28.01.2023, 18:53
Wow, gleich zwei Leute, die hier über den RP6 quatschen, da muss ich doch gleich mal mitschnuppern!
Schön, dass noch mehr Menschen den RP6 nicht weglegen, sondern wiederauferstehen lassen wollen. Danke euch! Ich würde noch ein zweites Thema aufmachen, RP6 und ESP32, da ich leider rust nicht kann, aber ich beteilige mich hier gern nach meinen Möglichkeiten.

Schöne Grüße in die (kleine) Runde!
- fabqu

gunzelg
28.01.2023, 19:06
Hallo
bei mir gibts nur das RP6-Fahrwerk, die Elektronik ist selbst gebaut, basiert auf einem PIC (dsPIC30F4013), programmiert wird in klassischem C, so neumodisches Zeug wie Rost, sorry Rust, kenn ich nur vom Hörensagen.. :-)

Gruß
Gerhard

fabqu
28.01.2023, 19:54
Oje, dann kommst du ja gar nicht in den Genuss dieser schönen Hardware/Elektronik des RP6 samt seinen guten C-Bibliotheken! Schade ;)
Ich kaufe gerade auch noch einige RP6 auf eBay etc, ich würde die gerne mit dem ESP32 fit machen für Programmierkurse für Kinder/Jugendliche. Also: es gibt noch welche ;)

gunzelg
29.01.2023, 07:40
Ja, als Elektroniker war das eigentlich auch mein Ziel, Hardware und Software alles selber zu bauen, auch für den Preis, dass ich da nur ne Lochrasterplatine habe und auch nicht soviel Features, wie da auf der Platine so drauf sind. Da ich aber öfter mal was mit PIC's mache, ist das nicht so dramatisch, auf unterster Ebene zu programmieren, also auch ohne Bibliotheken auskommen zu müssen. Oft haben die ja auch Einschränkungen in dem Sinne, dass die jetzt nicht genau das machen, was ich will. Aber dass du Kurse für Kids machst, finde ich toll. Des ESP32 kenn ich auch nicht. 32-Bit-Prozessor gegen meinen 16-Bitter, wobei ich meist eh nur die 8-Bitter von Microchip verwende. Das bisschen Bit-Schupserei braucht noch keinen 16-Bitter. Für den RP6 werd ich vielleicht ne neue Hardware bauen, und da dann nen anderen 16-Bitter von Microchip verwenden, nen PIC24. Aber auch nur aus Neugierde, und weniger weil es für den Roboter notwendig wäre...

Gruß
Gerhard

Pr0gm4n
29.01.2023, 15:57
Hey super, hier kommt ja richtig Bewegung in die Sache! :-)
Die Idee den RP6 für Programmierkurse zu nutzen finde ich super! Ich fand eben C zu... schwierig? Klar, die RP6Lib nimmt einem schon einiges ab, aber wenn man dann doch mal schauen geht was die Library-Funktionen dahinter so machen bzw. wie die implementiert sind... dann findet man dort schon eher "kryptische" Funktionen. Aber klar, wenn man jemanden zur Seite hat der sich damit auskennt, kann man dann schon gut etwas basteln! Das ist auch eine Motivation für mich um das in Rust zu übertragen. Und eben einiges über Rust zu lernen.

LG Roland

gunzelg
29.01.2023, 16:41
Als 60+ Elektroniker frag ich mich jetzt, welche Bedeutung Rust hat? Bei meinem letzten Job vor der Rente waren in meinem Betrieb drei Programmiersprachen gefragt: C/C++, LabView und Phyton. Rust ist wohl was neueres, und wofür wird das verwendet? Im PC-Bereich oder auch/nur für uC?

Gruß
Gerhard

Pr0gm4n
29.01.2023, 19:38
Hallo Gerhard,

also prinzipiell ist Rust eine sehr moderne Sprache mit vielen High-Level Konstrukten und einem sehr fähigen Paketmanager (cargo) wie man sie aus Python/Java/Javascript kennt, aber gleichzeitig ist Rust hinsichtlich der Performanz vergleichbar mit C/C++. Da es eine kompilierte Sprache ist, braucht es für jegliche Plattformen einfach entsprechende Compiler. Der für AVRs ist als Standard bereits mitinstalliert.

Designt wurde Rust von Mozilla als moderne Sprache für System-Programmierung. Rust basiert auf 2-3 Grundprinzipien:
- einem starken Typsystem, und davon dass sie einen zwingt jegliche Fehler die auftreten könnten bereits im Code handzuhaben. Ein Programm läuft dafür oft direkt ohne jemals groß Probleme zu machen.
- dem Ownership-Prinzip. Zu jeder Variable (Memory-Adresse) darf es nur eine exklusive Referenz geben. Man kann von vielen Programmteilen (oder Threads) von einer Zelle lesen, solange der Compiler garantieren kann dass während dieser Zeit kein Schreibvorgang stattfindet. Was erst mal kompliziert klingt, ist am Schluss beim Programmieren eigentlich recht logisch. Dadurch gibt es keine Memory-Leaks, aber der Compiler ist auch relativ strikt und es kann durchaus dauern bis man Code mal zum kompilieren bringt.
- Dokumentation ist integrierter Bestandteil des Paket- und Buildtools (cargo). Man hat also direkt hübsche (und einheitliche!) Doku für alle Pakete die man in seinem Projekt benutzt. Siehe auch: https://doc.rust-lang.org/std/macro.println.html als Beispiel.

Naja also zusammengefasst kann man sagen, die Zielgruppe ist jeglicher Code bei dem es auf Performance ankommt, aber auch dass der Code stabil läuft. Auf einem AVR wird man aber wohl kaum die Vielfalt an Features ausnutzen können. Ich denke aber dass die modernen Programmierkonzepte es erlauben, den Code übersichtlicher und langfristig wartbarer zu gestalten.


Als Beispiel für den Einsatzzweck von Rust könnte man das wohl prominenteste open-source Projekt anführen: den Linux-Kernel. Gerade vor ein paar Monaten wurde bekanntgegeben, dass in Zukunft mehr und mehr vom Linux Kernel in Rust implementiert werden soll. Macht aus meiner Sicht auch Sinn.


Hilft das soweit? Hier noch ein Link mit ein paar Beispielen zu ein paar Features von Rust.
https://learnxinyminutes.com/docs/rust/


LG Roland

gunzelg
29.01.2023, 19:43
Dank für die Einführung ! :-)

inka
31.01.2023, 12:22
hallo in die runde!

habe bis jetzt nur mitgelesen, mein RP6 ruht seit jahren (2012?) unter einer "glasglocke", habe mich seitdem "nur" mit arduino und speziell mit ESP8266 und ESP32 beschäftigt. Finde es aber super, dass man hier versucht den RP6 wiederzubeleben....
Wieviel ähnlichkeit hat rust mit arduino c/c++ ?

so viel erstmal aus dem warmen und sonnigen süden, melde mich wieder wenn ich nicht nur mein smartphone zur verfügung hab :-)

Pr0gm4n
01.02.2023, 17:07
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:


set_pins!([Led3, Led2, Led1], value);


Ausgeschrieben generiert das (etwas bereinigt für Lesbarkeit) den 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:



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:


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

Dirk
03.02.2023, 18:21
Ja, Leute,
da lese ich doch auch mal interessiert mit!
inka, fabq: euch kenne ich ja noch aus unserer grauen Vergangenheit hier im Forum ...
Toll, was du Roland da auf die Beine stellst. Hut ab!

Pr0gm4n
03.02.2023, 22:15
Na Dirk, ich erinnere mich durchaus an deine vielen hilfreichen Beiträge. Aber ich scheine wohl (noch?) nicht genug vernünftiges beigetragen zu haben, um in Erinnerung zu bleiben. Nun ja, bitte keine vorschnellen Lorbeeren – ich versuche dran zu bleiben aber es ist noch ein weiter Weg.

PS: Bei einem "Dirk" aus NRW muss ich nun fragen... du bist nicht zufällig begeistertes Mitglied eines Modellfliegerclubs in der Eifel? Dann kenne ich dich nämlich inzwischen persönlich... ;-)

Grüsse aus Zürich,
Roland

Dirk
05.02.2023, 10:27
Hi Roland,
sorry, die Aufzählung von RP6-Aktivisten war nicht diskriminierend gemeint: du bist js auch schon ein "Oldie" hier, aber in meiner morschen Erinnerung von immerhin schon fast 19 Jahren früher eher allgemein so mit AVR und GCC unterwegs, und nicht speziell RP6, ... ???
Nein, zum Modellfliegerclub in der Eifel gehöre ich nicht, obwohl auch sehr reizvoll! Immerhin habe ich mal Schiffsmodellbau (z.B. den Hamburger Bugsier 3) gemacht.
Thema Rust: ist das eigentlich eine noch "erfolgreiche" Programmiersprache? Ich habe gelesen, dass sie in Form von Stiftungen finanziell am Leben gehalten wird. In welchen ausserakademischen Bereichen wird die denn angewendet?

Pr0gm4n
05.02.2023, 12:31
Hab sie auch nicht als diskriminierend aufgenommen. Schade dass du keine Modellfliegerei in der Eifel betreibst, aber dann kenne ich halt jetzt 2 Dirks in NRW. :-)

Rust wird meines Wissens kommerziell bei Dropbox, Microsoft, Cloudflare, Facebook, ... produktiv eingesetzt. Ursprünglich getrieben und entwickelt von der Mozilla Foundation, also sogesehen von einer "Stiftung" wie du sagst. Empfinde ich hier aber eher von Vorteil, als wenn das jetzt eine einzige Firma prägend in der Hand hätte. Wie oben beschrieben, das prominenteste Open-Source Projekt ist meines Wissens der Linux Kernel, der nun nach und nach mehr Rust bekommen soll. Da Rust noch sehr jung ist, sehe ich die Sprache aber eher noch auf dem aufsteigenden Ast. Ein Beispiel von Features die ich so toll finde habe ich erst heute morgen wieder entdeckt: Eine Funktion kann als Rückgabetyp "endet nie" haben, dargestellt mit einem Ausrufezeichen "!".

Immerhin hab ich Neuigkeiten: ich hab die ersten Interrupts zum Laufen bekommen!

Der Code beim Anwender sieht dann etwa so aus:


#[interrupt]
fn INT0() {
// do some stuff on hardware interrupt INT0
}


Natürlich muss man den Interrupt trotzdem erst mal aktivieren. Und ich muss noch ein bisschen rausbekommen wie man dann ordentlich zurückspringt und so... aber das kommt schon noch zusammen ;-)


LG Roland

Pr0gm4n
07.02.2023, 23:00
So Ihr Lieben, da bin ich wieder!

Inzwischen hab ich mich mit allen möglichen Arten von atomischen Operationen, Mutexen, und anderen Ideen, wie man möglichst statisch (= zur Compilezeit) absichern kann dass nichts schief geht, herumgeschlagen. Ich denke ich bin soweit ganz happy, aber was meint Ihr denn so?

Hier mal ein kleines Beispiel, das nun einen Ringbuffer für USART_RXC implementiert:


use rp6::{interrupt::mutex::Mutex, *};

// Shared data: While constants can be accessed safely (since they are never modified, it is
// recommended to wrap your mutable data in an `rp6::interrupt::Mutex`. Note that a `Mutex` can
// only be used inside a `CriticalSection`, e.g., by calling `interrupt::without_interrupts`.
const USART_BUFFER_SIZE: usize = 32;
static USART_BUFFER: Mutex<[u8; USART_BUFFER_SIZE]> = Mutex::new([' ' as u8; USART_BUFFER_SIZE]);
static USART_WRITE_PTR: Mutex<usize> = Mutex::new(0);

#[interrupt]
fn USART_RXC() {
interrupt::without_interrupts(|cs| {
let buffer = USART_BUFFER.lock(cs);
let write_ptr = USART_WRITE_PTR.lock(cs);

// save newly received byte to the ringbuffer
buffer.update(|mut b| {
b[write_ptr.get()] = Serial::read_raw();
b
});

// increment USART write pointer and wrap around if necessary
write_ptr.update(|x| if x + 1 < USART_BUFFER_SIZE { x + 1 } else { 0 });
});
}


Eventuell besser zu lesen mit Code-Highlighting auf Github (https://github.com/Pr0gm4n/rust-rp6lib/blob/main/examples/02_serial_interface.rs#L8)!

In Anlehnung an das RP6Example gibt das Programm (weiter unten in der main (https://github.com/Pr0gm4n/rust-rp6lib/blob/main/examples/02_serial_interface.rs#L48)) dann einfach regelmässig einen hochzählenden Counter aus, sowie den aktuellen Inhalt des Ringbuffers.

Na, wie sieht das für Euch (noch-)nicht-Rustaceans aus?


LG und gute Nacht,
Roland

Pr0gm4n
08.02.2023, 08:52
Und heute morgen hat's mich direkt nochmal kurz gepackt: Es gibt jetzt automatisch generierte API Docs (https://pr0gm4n.github.io/rust-rp6lib/rp6/) auf GitHub!

LG Roland