AVR & g++

Schnelle objektorientierte, kompilierende Programmiersprache.
Leverator
Beiträge: 32
Registriert: Mo Jan 30, 2012 9:28 pm
Wohnort: ::1

AVR & g++

Beitrag von Leverator » Fr Okt 19, 2018 10:06 am

Hallo Profies,

ich habe folgende Idee, jedoch keine Ahnung, wie sich das umsetzen lässt.
Ich arbeite öfter mit den 8-Bit AVR µC. Alle diese Controller besitzen den identischen Rechenkern, der mit unterschiedlicher weiterer interner "Periferie" ausgestattet wird.
Beispiel: Ich habe einen ATmega168, der drei unabhängige Timer besitzt. Zwei davon sind nahezu identisch in der Funktion und einer ist ein 16-Bit Timer.

Ziel:
Ich möchte gerne die Funktionen der Timer in C++ in der Art nutzen, sodass ich eine Art Timer-Klasse habe, die ich beim Instantiieren - wie auch immer - konfiguriere (auf den speziellen Timer) und später im Code nutzen kann. Will ich einen weiteren Timer nutzen, so muss ich nur eine weitere Klasse instantiieren und konfigurieren.
Die Konfiguration zunächst statisch gehalten, um dem Compiler Möglichkeiten zur Optimierung zu lassen.

Frage:
Wie setzt man das um? Es gibt ja viele von diesen ATmegas (z.B. ATmega64, ATtiny, usw.), die alle irgendwie innen gleich funktionieren. Z.B. eine Klasse für RS232, I2C und natürlich die Timer ließen sich bestimmt entsprechend realisieren. Unterschiede ergeben sich nur in der Benamsung der entsprechenden Register (die sich in der Dokumentation und in Beispiel C-Code finden lässt).


Vielen Dank, dass Ihr mich an Eurer Weisheit teilhaben lasst.

Viele Grüße,
Lev

Leverator
Beiträge: 32
Registriert: Mo Jan 30, 2012 9:28 pm
Wohnort: ::1

Re: AVR & g++

Beitrag von Leverator » Fr Okt 19, 2018 10:08 am

Upps, vergessen, ganz wichtig:
Die Timer können auch Interrups auslösen, die eine ISR benötigen...

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: AVR & g++

Beitrag von Xin » Fr Okt 19, 2018 10:21 am

Leverator hat geschrieben:
Fr Okt 19, 2018 10:06 am
Hallo Profies,
Hey, Lev.
Leverator hat geschrieben:
Fr Okt 19, 2018 10:06 am
Ich möchte gerne die Funktionen der Timer in C++ in der Art nutzen, sodass ich eine Art Timer-Klasse habe, die ich beim Instantiieren - wie auch immer - konfiguriere (auf den speziellen Timer) und später im Code nutzen kann. Will ich einen weiteren Timer nutzen, so muss ich nur eine weitere Klasse instantiieren und konfigurieren.
Die Konfiguration zunächst statisch gehalten, um dem Compiler Möglichkeiten zur Optimierung zu lassen.
Du möchtest, dass die Klasse zur Compilezeit instanziiert wird, um Rechenzeit zu sparen?
Zur Laufzeit sehe ich da wenig Chancen, viel zu optimieren. Außerdem ist es Konfiguration - da würde ich bei einem Microcomputer bestenfalls auf Speicherverbrauch optimieren. Oder was ist das Optiermierungsziel?
Leverator hat geschrieben:
Fr Okt 19, 2018 10:06 am
Frage:
Wie setzt man das um? Es gibt ja viele von diesen ATmegas (z.B. ATmega64, ATtiny, usw.), die alle irgendwie innen gleich funktionieren. Z.B. eine Klasse für RS232, I2C und natürlich die Timer ließen sich bestimmt entsprechend realisieren. Unterschiede ergeben sich nur in der Benamsung der entsprechenden Register (die sich in der Dokumentation und in Beispiel C-Code finden lässt).
Ich verstehe von den ATmega-Microcomputern eigentlich nix. ^^

Wenn sich RS232, I2C und Timer vergleichbar ansteuern lassen, könnte man eine gemeinsame Basisklasse schaffen, die diese Gemeinsamkeiten kapselt.
Leverator hat geschrieben:
Fr Okt 19, 2018 10:08 am
Upps, vergessen, ganz wichtig:
Die Timer können auch Interrups auslösen, die eine ISR benötigen...
Die Interrupt Service Routine wird vermutlich kein Element einer Klasseninstanz sein können, sondern eine statische Funktion. Da wäre dann die Frage, ob diese statische Funktion einen Parameter hat, der erlaubt den Absender des Interrupts zu identifizieren. Ansonsten müsste man halt entsprechend viele Mini-Funktionen bauen, die diesen Parameter erzeugen und so wieder in die entsprechende Klasse zurückfinden.

Mit wieviel Interrupts ist denn da maximal zu rechnen?
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Leverator
Beiträge: 32
Registriert: Mo Jan 30, 2012 9:28 pm
Wohnort: ::1

Re: AVR & g++

Beitrag von Leverator » Fr Okt 19, 2018 10:28 am

Hi Xin,

da die µC fast keinen Speicher haben und auch die Rechenpower arg begrenzt ist, möchte ich sowohl die Größe des zu flashenden Binärfiles optimieren als auch die Ausführungsgeschwindigkeit der einzelnen Routinen (soweit das überhaupt geht).
Die Timer, Schnittstellen und beinahe alles im µC können Interrupts auslösen, sodass man hier tatsächlich eine abstrakte Basisklasse bereits weit von der eigentlichen Funktionalität entfernt nutzen könnte.

Viele Grüße,
Lev

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: AVR & g++

Beitrag von Xin » Fr Okt 19, 2018 10:38 am

Leverator hat geschrieben:
Fr Okt 19, 2018 10:28 am
da die µC fast keinen Speicher haben und auch die Rechenpower arg begrenzt ist, möchte ich sowohl die Größe des zu flashenden Binärfiles optimieren als auch die Ausführungsgeschwindigkeit der einzelnen Routinen (soweit das überhaupt geht).
Logisch... die Konfiguration findet aber ja nur bei der Startphase statt. Wenn das Ding natürlich fertig konfiguriert vorliegt und Du zur Laufzeit da nichts mehr ändern musst, dann sollte Deine Klasse constexpr-Konstruktoren haben. Die fertig konfigurierte Klasse wird dann direkt im Flashfile abgelegt und nur noch in den RAM kopiert. Schneller und platzsparender geht es dann nicht.
Leverator hat geschrieben:
Fr Okt 19, 2018 10:28 am
Die Timer, Schnittstellen und beinahe alles im µC können Interrupts auslösen, sodass man hier tatsächlich eine abstrakte Basisklasse bereits weit von der eigentlichen Funktionalität entfernt nutzen könnte.
Wie gesagt: Der Aufruf der Callback-Routine ist (vermutlich) eine statische Funktion, muss also eher außerhalb der Klasse liegen (bzw. als statische Memberfunktion ist sie nur im Namensraum der Klasse).
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: AVR & g++

Beitrag von cloidnerux » Fr Okt 19, 2018 2:13 pm

An sich ist deine Idee super und wurde so auch schon öfters versucht umzusetzten, aber leider stößt du an die selben Probleme, wie alle anderen.
Wenn du die Funktionalität der Peripherie unterschiedlicher µC anschaust, wirst du feststellen, dass es da teilweise sehr drastische Unterschiede gibt, was möglich ist und wie es eingestellt werden muss. Das bedeutet nun, dass du für jeden Prozessor alle Funktionen getrennt implementieren musst. Zusätzlich musst du dem Nutzer deiner Lib dann auch mitteilen, was geht und was nicht. Was dann im Endeffekt passiert ist, dass der Programmierer erneut im Datenblatt nachlesen muss was geht und wie es eingestellt werden muss.

Auf der anderen Seite wechselst du den Prozessor nur sehr selten, sodass die 10-60min die du potenziel spahren kannst, nicht so sehr ins Gewicht fallen.

ST hat z.B mit der CMSIS sowas versucht und es funktioniert leider nur sehr dürftig.
Microchip hat da hingegen in seinem MPLABX mit dem MCC ein recht cooles Tool, mit dem man die Hardware graphisch Konfiguriert und dann automatisch Code generiert wird.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Benutzeravatar
Xin
nur zu Besuch hier
Beiträge: 8858
Registriert: Fr Jul 04, 2008 11:10 pm
Wohnort: /home/xin
Kontaktdaten:

Re: AVR & g++

Beitrag von Xin » Fr Okt 19, 2018 3:02 pm

cloidnerux hat geschrieben:
Fr Okt 19, 2018 2:13 pm
Wenn du die Funktionalität der Peripherie unterschiedlicher µC anschaust, wirst du feststellen, dass es da teilweise sehr drastische Unterschiede gibt, was möglich ist und wie es eingestellt werden muss. Das bedeutet nun, dass du für jeden Prozessor alle Funktionen getrennt implementieren musst.
Was hindert ihn, entsprechende über Templates mittels Traits entsprechende Spezialisierungen zu erstellen?
cloidnerux hat geschrieben:
Fr Okt 19, 2018 2:13 pm
Zusätzlich musst du dem Nutzer deiner Lib dann auch mitteilen, was geht und was nicht. Was dann im Endeffekt passiert ist, dass der Programmierer erneut im Datenblatt nachlesen muss was geht und wie es eingestellt werden muss.
Wenn er einen Timer einstellt, der jede tausenstel Sekunde feuert und der Timer kann das nicht, kann man für diesen MicroKontoller doch die Kompilation über eine Assertion verweigern!?
Somit hat der Entwickler das Feedback: So geht's auf diesem Rechner nicht.
Merke: Wer Ordnung hellt ist nicht zwangsläufig eine Leuchte.

Ich beantworte keine generellen Programmierfragen per PN oder Mail. Dafür ist das Forum da.

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: AVR & g++

Beitrag von cloidnerux » Fr Okt 19, 2018 6:34 pm

Was hindert ihn, entsprechende über Templates mittels Traits entsprechende Spezialisierungen zu erstellen?
Nichts natürlich. Aber eine Timer initialisierung kann ungefähr so aussehen:

Code: Alles auswählen

TCCR0A = 0x00;
TCCR0B = 0x02;	//fclk/64
TIMSK0 = 0x01;	//overflow interrupt enable
OCR0A = 0x00;
OCR0B = 0x00;
Das hab ich aus dem Datenblatt für den Atmega328p, Seite 141 ff.
Mit etwas Erfahrung hat man das in 2 min zusammengesucht. Man schreibt tatsächlich die Werte einfach nur an bestimmte Speicherstellen, die Peripherie ist elektrisch hart mit diesen Speicherstellen verknüpft.
Aber auch hier gibt es mehrere Sachen, die man beachten kann und sollte. Der Timer bezieht seinen Takt von der globalen Taktquelle, daher muss erstmal der Systemtakt bekannt sein, sonst kann der Compiler auch nicht die Zeiten checken. Das wird typisherweiser mit einem globalen define XTAL_FREQ gemacht. Dann hat der Timer einen Prescaler, man kann also den Takt durch 1, 8, 64, 256 oder 1024 teilen...oder ihn gleich von einem Pin beziehen, was durchaus auch Sinn macht. Dann gibt es natürlich den Overflow interrupt, der Eintritt, wenn der Zähler des Timers wieder auf 0 zurück gesprungen ist, daher kann man also nur alle 256 Counts einen Interrupt auslösen. Das ist aber gar nicht so geschickt, weil man damit wrsl nicht auf 1ms kommt als Beispiel. Aber auch dafür gibt es eine Lösung, indem man die Compare Interrupts verwendet. Dabei wird der Wert in OCR0A/B mit dem Counter-Wert TCNT0 verglichen und der Intterupt ausgelöst, wenn diese gleich sind. Im Interrupt kann man dann TCNT0 wieder auf 0 setzen und kann somit freier die Wiederholrate einstellen.

Dabei muss man aber sagen, dass das noch ein sehr einfacher Timer ist, es gibt die in unterschiedlichen Registerbreiten(8, 16 und 32 Bit), up und downcounting mit unterschiedlichen Gating Funktionen, Timer-Kaskadierung, unterschiedlichen Pre- und Postscalern und noch das ein oder andere Detail. Zudem möchte man Timer nicht nur als Interruptquelle verwenden. Man kann sie z.B als SystemCounter verwendet, der einfach nur jede Millisekunde hochzählt, um PWM zu generieren, Puls-Längen messen, z.B bei RC-Empfängern, Pulse Zählen um Geschwindigkeiten oder Distanzen zu messen und vieles mehr.
Zudem gibt es halt auch 70 unterschiedliche AVRs, die momentan angeboten werden:
https://www.microchip.com/paramChartSea ... chID=30047

Daher würde ich behaupten, dass der Aufwand, sich eine einigermaßen allgemeingültige Klasse zu schreiben, deutlich über dem Aufwand liegt, sich die paar Zeilen Initialisierung zusammen zu suchen.
Zudem ist da noch keine Verifikation der Klasse mit berücksichtigt. Man muss ja auch beachten, dass diese Hardwareinitialisierung nur sehr schwer per Unit-Test oder ähnliches zu Verfizieren ist.
Wenn er einen Timer einstellt, der jede tausenstel Sekunde feuert und der Timer kann das nicht, kann man für diesen MicroKontoller doch die Kompilation über eine Assertion verweigern!?
Somit hat der Entwickler das Feedback: So geht's auf diesem Rechner nicht.
Wie schon geschildert, ist es ja nicht so einfach. Der Timer könnte evt alle 0,99ms oder 1,03ms Feueren und es könnte für die Anwendung vollkommen in Ordnung sein. Manchmal ist es aber halt nicht in Ordnung. Im Endeffekt muss dann aber der Programmierer wieder ins Datenblatt schauen und die selbe Zeit investieren, sodass die Nutzung einer solchen Abstratkion auch wieder absurd wird.
Redundanz macht wiederholen unnötig.
quod erat expectandum

koshamo
Beiträge: 13
Registriert: Sa Aug 11, 2018 5:38 pm
Wohnort: Heidelberg

Re: AVR & g++

Beitrag von koshamo » Sa Okt 20, 2018 7:40 am

cloidnerux hat geschrieben:
Fr Okt 19, 2018 6:34 pm

Wie schon geschildert, ist es ja nicht so einfach. Der Timer könnte evt alle 0,99ms oder 1,03ms Feueren und es könnte für die Anwendung vollkommen in Ordnung sein. Manchmal ist es aber halt nicht in Ordnung. Im Endeffekt muss dann aber der Programmierer wieder ins Datenblatt schauen und die selbe Zeit investieren, sodass die Nutzung einer solchen Abstratkion auch wieder absurd wird.
Naja, nicht unbedingt. Wenn man eine einigermaßen allgemeingültige Klasse schreibt, kann man die Instantiierung wiederum in eine Factory-Methode kapseln, so dass dem User eine brauchbare Schnittstelle angeboten wird, der Rest passiert dann im Hintergrund. Der User muss in diesem Falle kein Datenblatt lesen, sondern kann - wenn er sich drundlegend auskennt - angeben, welchen µC er hat, welchen Timer (1,2,3), Flanke, evtl. welcher Interrupt ausgelöst werden soll (Overflow, Compare,...) usw.

Aber, so einen Code-Overhead würde ich auf einem µC nie verwenden wollen. Ich würde noch nicht mal mit Objektorientierung auf einen µC gehen. Die Komplexität der Software, die auf den µCs ist, ist überschaubar, weshalb plain old C durchaus ausreicht, um den Code gut zu struturieren und lesbar zu gestalten und die direkte Übersetzbarkeit trägt durchaus zu geringem Speicherverbrauch und hoher Geschwindigkeit bei - genau was man auf dem µC will. OO sehe ich da eher als Kanone an, die auf einen Spatz gerichtet wird -- und OO ist bei weitem nicht die tolle Allzweckwaffe, für die es oft vermarktet wird ;)

Benutzeravatar
cloidnerux
Moderator
Beiträge: 3123
Registriert: Fr Sep 26, 2008 4:37 pm
Wohnort: Ram (Gibts wirklich)

Re: AVR & g++

Beitrag von cloidnerux » Sa Okt 20, 2018 12:07 pm

Naja, nicht unbedingt. Wenn man eine einigermaßen allgemeingültige Klasse schreibt, kann man die Instantiierung wiederum in eine Factory-Methode kapseln, so dass dem User eine brauchbare Schnittstelle angeboten wird, der Rest passiert dann im Hintergrund. Der User muss in diesem Falle kein Datenblatt lesen, sondern kann - wenn er sich drundlegend auskennt - angeben, welchen µC er hat, welchen Timer (1,2,3), Flanke, evtl. welcher Interrupt ausgelöst werden soll (Overflow, Compare,...) usw.
Es dreht sich halt im Kreis. Weil statt eine Klasse zu schreiben, kann ich auch ein Code-Generator schreiben, wie ja bereits gemacht wird.
Aber, so einen Code-Overhead würde ich auf einem µC nie verwenden wollen. Ich würde noch nicht mal mit Objektorientierung auf einen µC gehen. Die Komplexität der Software, die auf den µCs ist, ist überschaubar, weshalb plain old C durchaus ausreicht, um den Code gut zu struturieren und lesbar zu gestalten und die direkte Übersetzbarkeit trägt durchaus zu geringem Speicherverbrauch und hoher Geschwindigkeit bei - genau was man auf dem µC will. OO sehe ich da eher als Kanone an, die auf einen Spatz gerichtet wird -- und OO ist bei weitem nicht die tolle Allzweckwaffe, für die es oft vermarktet wird ;)
OO macht schon sinn, auch für µC. Aber erst ab einer gewissen Komplexität. So ein STM32 kann auch mal 1MB Flash haben, da passt schon einiges an Code rein.
Redundanz macht wiederholen unnötig.
quod erat expectandum

Antworten