Hier gibts ja viele Meinungen zu C++, aber wenn man es selber probieren
will, gibt es große Hürden.
Es gibt kein verstehbares Lehrbuch und wenn man sich anderen Code
ansieht, dann ist der riesen groß und unübersichtlich und geht überhaupt
nicht auf MC-spezifische Abläufe ein (IO-Zugriffe, Statemaschine usw.).
Vielleicht kann ja mal ein C++ Guru mir etwas auf die Sprünge helfen.
In C wird ja für jeden Quark eine Funktion benötigt, was nicht besonders
gut lesbar ist.
In C++ könnte man Zuweisungen nehmen, wenn man nur wüßte, wie man das
implementiert.
Z.B. mal ganz einfach das Setzen von Ausgängen und Entprellen von
Eingängen.
Ich möchte gerne folgendes erreichen:
1
LED0=KEY0.state;// an solange Taste gedrückt
2
LED1.toggle=KEY1.press// wechsel bei jeder Drückflanke
3
LED2.off=KEY2.press_short;// kurz drücken - aus
4
LED2.on=KEY2.press_long;// lang drücken - an
Geht sowas in C++ und wie könnte eine Implementierung aussehen?
Besonders interessant wäre die Entprellung portweise parallel und nur
das Auswerten der Tasten einzeln. Also irgendwie, jede Taste ist Teil
der Klasse entprelle_port, die wiederum für mehre 8Bit-Ports verwendet
werden kann. Z.B. 16 Tasten an 2 Ports.
Irgendwie scheinst du merkwürdige Vorstellungen von C++ zu haben.
Deine Beispiel ist doch nonsense.
Grade bei OOP (was man ja meist will wenn man C++ nimmt) hast du eher
mehr Funktionen weil du Datenkapselung via getter/setter machst.
Wenn man PORT/PINs als Template-Parameter übergeben kann:
Evtl. ein Konstrukt wie:
Entpreller<PINA> KEY0;
Entpreller<PINB> KEY1;
die Entpreller-Klasse enthält dann die Counter etc.
Zum Zugriff dann:
if (KEY0.press.BUTTON1) ...
if (KEY1.long_press.BUTTON4) ...
(mit press/long-Press einfach als Bitfields)
Im Timer muss dann
KEY0.update();
KEY1.update();
aufgerufen werden.
Durch inlining sollte fast dasselbe wie in deinem genialen C-Entpreller
herauskommen...
Die Features von C++ sind hauptsächlich zur Strukturierung & Abstraktion
von Daten, Verwaltung von Speicher gedacht. Sie erlauben nicht magisch
eine parallele Verarbeitung, wie du sie durch diese Zeilen
Peter Dannegger schrieb:> LED0 = KEY0.state; // an solange Taste gedrückt> LED1.toggle = KEY1.press // wechsel bei jeder Drückflanke> LED2.off = KEY2.press_short; // kurz drücken - aus> LED2.on = KEY2.press_long; // lang drücken - an
andeutest. Das sieht mehr nach VHDL oder modellbasierter Programmierung
ala Simulink als nach einer imperativen Sprache wie C(++) aus. Sicher,
man könnte Objekte LED0 und KEY0 so anlegen, dass jede dieser
Anweisungen ein Objekt erzeugt das die angedeutete Funktion erfüllt,
sich in eine Liste einträgt und dann von einem Timer-Interrupt o.ä.
verarbeitet wird. Aber parallele deklarative Verarbeitung dieser Art ist
nicht gerade C++' Kernkompetenz, und es ist fraglich ob man das so
will... Was viel eher in die imperative Struktur von C(++) passt wäre
vielleicht so etwas:
1
voidTIM1_IRQ(){
2
LED0.state=KEY0.state;
3
if(KEY1.state)
4
LED1.state=!LED1.state;
5
if(KEY2.pressedTime>=1000)
6
LED2.state=true;
7
elseif(KEY2.pressedTime>=100)
8
LED2.state=false;
9
}
Mark Brandis schrieb:> Nahezu alles, was man in C machen kann, kann man auch in C++> machen.> Naja, so gut wie alles. 99 Prozent.
Aber auch in Brainfuck. Warum verwendet niemand Brainfuck? Es ist
supereinfach zu lernen, verwenden und implementieren.
cyblord ---- schrieb:> Deine Beispiel ist doch nonsense.
Ich denke aber, es wird klar, was es machen soll.
Ich will mir nicht überall merken müssen, welche Led, Taste an welchem
Port hängt und welche Polarität und welche Aktion.
Ich will das einmal und nur an einer einzigen Stelle angeben müssen und
fürderhin kümmert sich C++ darum, das entsprechend aufzulösen.
C kann das ja nicht, da kann ich schreiben:
1
PORTC|=1<<PB7;
und es meckert in keinster Weise.
Wenn meine Aufruf-Syntax falsch ist, dann bitte korrigieren.
Leider nicht.
C++ wird wie C Zeile für Zeile abgearbeitet. Um die dauer des
Tastendrucks festzustellen benötigt man auch hier eine ISR oder
zumindest eine periodische Schleife.
Der größte Vorteil von C++ ist das Daten und Funktionen zusammen als ein
Objekt gespeichert werden. Man spart sich also viele Parameterübergaben
oder globale Variablen. Wenn das ganze dann noch als "static" definiert
ist, dann erzeugt ein aktueller C++ Compiler Code der genau so schnell
ausgeführt wird wie der C Code...
Eine Klasse ist im einfachsten Fall sozusagen eine struct mit Methoden
drinnen (das kann man auch wirklich so verwenden, wenn man möchte).
Jede Variable die dann den Typ diesen Typ hat wird dann einfach Objekt
genannt...
Es gibt natürlich die ganzen netten Sachen von Objektorientierter
Programmierung, wie Vererbung, Polymorphismus usw... Ob man die im
embedded Bereich sinnvoll verwenden kann hängt sehr stark von der
Anwendung ab.
Im allgemeinen kann man aber viel portableren und wiederverwertbaren
Code schreiben wie in C.
Was mit persönlich an C++ im Embedded Bereich sehr gefällt sind die
Generics (bei C++ Templates genannt). Damit kann man einen großteil der
statischen DEFINES in einem typeischen C Programm weg bringen und das
ganze dazu noch kapseln.
z.B. hab ich Filterklassen als Template realisiert denen man bei der
Instantierung nur die Ordnung und die Charakteristik mitgibt. Der vom
Compiler erzeugte Code ist dann exakt genau so schnell wie C code. Der
Vorteil ist das man mit einer Zeile einen neuen Filter hat - ganz ohne
Code Duplication, globale Variablen, Precompiler DEFINES usw... sauber
halt.
Es geht natürlich auch mit C genau so gut. Nur halt umständlicher und
nicht so sauber, aber für nicht C++ affine Programmier halt dafür viel
verständlicher...
Liebe Grüße,
Sepp
Den Syntax welchen du zu errecihen versuchst ist standard C++. Sowas ist
möglich per "property" implementierung und z.B. Teil von der Borland C++
Spracherweiterung. Normales C++ bietet sowas nicht ohne höheren Aufwand.
Hier müsstest du für jeden Member eine subklasse schreiben und den
"=-Operator" überladen. Möglich ist das, schön aber nicht.
Zu lesen in deiner Fragestellung ist ebenfalls, dass dir sowohl
Programmier- als auch Systemkenntnisse fehlen, um mit OOP überhaupt
etwas zu erreichen.
Dann wüsstest du, dass der Syntaktische Zucker eigentlich keinene
Overhead im Code erzeugt und wie dein gewünschter Syntax auch am PC zu
implementieren wäre -> denn der Unterschied == 0.
Peter Dannegger schrieb:> Ich denke aber, es wird klar, was es machen soll.> Ich will mir nicht überall merken müssen, welche Led, Taste an welchem> Port hängt und welche Polarität und welche Aktion.> Ich will das einmal und nur an einer einzigen Stelle angeben müssen und> fürderhin kümmert sich C++ darum, das entsprechend aufzulösen.
dafür macht man sein in paar makros.
sieht mir mir so aus.
#define LED_ROT_PORT PORTD
#define_LED_ROT_PIN PIN1
#define LOD_ROT_INVERT 1
#define TASTER_EIN_PORT PORTD
#define_TASTER_EIN_PIN PIN2
#define TASTER_EIN_INVERT 0
im code kann ich dann über ein paar makros einfach
EIN( LED_ROT );
oder
if ( IS_ON( TASTER_EIN ) ) ..
schreiben. Dafür braucht man doch kein C++.
Peter Dannegger schrieb:> Ich will das einmal und nur an einer einzigen Stelle angeben müssen und> fürderhin kümmert sich C++ darum, das entsprechend aufzulösen.
Na das ist ja was völlig anderes. Das ist kein Problem (Beispiel
anhand STM32F4):
Sepp schrieb:> Um die dauer des> Tastendrucks festzustellen benötigt man auch hier eine ISR
Das ist klar.
Es ging mir auch haupsächlich darum, wie ich in der Mainloop die
Ereignisse auswerten kann und wie ich die Klassen definieren muß.
Z.B. ich definiere eine Klasse LED und ein Klasse KEY und darin alle
Aktionen und Initialisierungen dafür. Die Aktionen benutze ich dann in
der Mainloop.
Schön wäre es noch, wenn die Klasse automatisch den passenden
Interrupthandler aktiviert.
Füge ich eine neue Taste der Klasse KEY hinzu, wird geprüft, ob deren
Port bereits entprellt wird und wenn es ein neuer Port ist, wird der
Code dafür hinzugefügt.
Peter Dannegger schrieb:> Geht sowas in C++ und wie könnte eine Implementierung aussehen?
Gehen wird sowas schon, aber man muß sicher eine Weile darüber
nachdenken, damit es sinnvoll wird.
Für deine Zuweisungen könnte ich mir vorstellen, daß Funktionsobjekte
zugewiesen werden, und die operator=() entsprechend überschrieben
werden.
Die Funktionsobjekte können dann ggf. mit Parametern wie Frequenzen,
Parameter zum Entprellen etc. manipuliert werden.
(Ein paar Konstanten zu definieren werden dafür nicht reichen, weil du
offenbar erreichen willst, daß nach der Zuweisung noch irgendwas im
Hintergrund weiterläuft).
Für einen kleinen AVR ist das vielleicht etwas viel Code, aber für einen
dickeren atmega oder einen ARM sollte das machbar sein.
Wird dann sicher nicht jeden Puristen überzeugen (siehe den paralellen
Thread mit dem C++-Geprügel), aber könnte nett aussehen.
Wenn es dich dann noch juckt, könnte ich übernächstes WE ein Grundgerüst
als Vorschlag machen - macht bestimmt Spaß.
FH-Student schrieb:> cyblord ---- schrieb:>> via getter/setter machst>> Alt aber nach wie vor aktuell:>> http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html
Der Tag mag kommen an dem FH-Studenten mir Nachhilfe in
Programmierkonzepten erteilen, aber heute ist das sicher noch nicht ;-)
Die Bedenken sind in der Tat alt und wohl bekannt. Und auch korrekt.
Trotzdem erhält man im OOP Umfeld Daten mittels Funktionen und übergibt
Daten mittels Funktionen.(Das man dies nicht durch sture getter/setter
sondern auf höherer Abstraktionsebene machen sollte, das stimmt
natürlich).
Was ich damit eigentlich nur sagen wollte, dass eben bei OOP eher MEHR
Funktionsaufrufe im Code stehen als bei reinem C. Peter meinte ja das
Gegenteil.
Auschnitt aus der Luna Beschreibung:
"Luna ... bietet im Gegensatz zu einfacheren Basic-Sprachen, wie z.Bsp.
BASCOM, wesentlich komplexere Möglichkeiten auf technischer Augenhöhe
mit Pascal, Modula und C/C++."
http://avr.myluna.de/doku.php?id=de:about
Vielleicht ist das ja was für den TO?
Ich meine, die Syntax "name.action" schon recht häufig gesehen zu haben.
Wichtiger ist mir aber, daß ich Schreibarbeit und damit
Fehlerträchtigkeit spare.
In plain C muß ich zu jeder LED auch das Richtungsbit definieren, bzw.
zu jeder Taste das Pullup-Bit.
Daher dachte ich, wenn ich eine Klasse definiere und darin die LED als
Member, wird für jedes Member automatisch die Initialisierung mit
aufgerufen.
Und wenn ich im Main eine Member-Aktion mache, wird der entsprechende
Code der Klasse ausgeführt.
Damit sollte exakt der gleiche Code entstehen, wie in plain C, also kein
Mehrverbrauch.
cyblord ---- schrieb:> Was ich damit eigentlich nur sagen wollte, dass eben bei OOP eher MEHR> Funktionsaufrufe im Code stehen als bei reinem C.
Im Quelltext lassen sich aber viele Funktionsaufrufe verstecken.
Copy-Construktoren, conversion-operatoren, andere overloads,
destruktoren, ...
Peter Dannegger schrieb:> In plain C muß ich zu jeder LED auch das Richtungsbit definieren, bzw.> zu jeder Taste das Pullup-Bit.> Daher dachte ich, wenn ich eine Klasse definiere und darin die LED als> Member, wird für jedes Member automatisch die Initialisierung mit> aufgerufen.
Woher soll die Klasse denn wissen, wie du es eingestellt haben willst,
wenn du das nicht explizit hinschreibst?
@ Peter Dannegger (peda)
>Ich meine, die Syntax "name.action" schon recht häufig gesehen zu haben.>Wichtiger ist mir aber, daß ich Schreibarbeit und damit>Fehlerträchtigkeit spare.
Vielleicht ist dann Arduino dar Richtige für dich?
SCNR
Falk
Ich denke hier versteht gerade niemand niemanden. Ich kann das Ziel im
OP noch nicht so ganz deuten und vor allem kommt es mir so vor, dass in
den Folgeposts was ganz anderes gemeint ist. Hier besteht
Aufkärungsbedarf!
Rolf Magnus schrieb:> Woher soll die Klasse denn wissen, wie du es eingestellt haben willst,> wenn du das nicht explizit hinschreibst?
Ich hatte gedacht, daß eine Klasse Code enthalten kann, der dann für
jedes Member aufgerufen wird.
Ich habe also ein Objekt vom Typ LED und dafür definiere ich die
Initialisierungen und Aktionen in der Klasse LED als Code.
BastiDerBastler schrieb:> Ich kann das Ziel im> OP noch nicht so ganz deuten
Ich möchte z.B. LEDs und Tasten als Objekte behandeln und dafür nur
bestimmte Aktionen zulassen bzw. vorbelegen.
Es kann sein, daß OOP primär was völlig anderes im Sinn hat, aber ich
hatte den Eindruck, daß es mit C++ möglich wäre.
Peter Dannegger schrieb:> Z.B. ich definiere eine Klasse LED und ein Klasse KEY und darin alle> Aktionen und Initialisierungen dafür.
Das kannst du natürlich schon
1
classLED
2
{
3
public:
4
LED(volatileuint8_t*Port,uint8_tBit)
5
:Port_(Port),Bit_(Bit)
6
{DDR_From_Port(Port)|=(1<<Bit_);}
7
8
voidOn(){Port_&=~(1<<Bit_);}
9
voidOff(){Port_|=(1<<Bit_);}
10
11
private:
12
volatileuint8_t*constPort_;
13
constuint8_tBit_;
14
};
15
16
17
intmain()
18
{
19
LEDErrorLed(PORTB,PB0);
20
21
ErrorLed.On();
22
ErrorLed.Off();
23
}
Mit den Tasten ist das dann nicht mehr ganz so einfach. Da müsste es
wohl einen übergeordneten Koordinator geben, der sich um den Timer bzw.
die ISR kümmert und die KEY Objekte da einschleust.
> Füge ich eine neue Taste der Klasse KEY hinzu
Du hast da eine Konzeptdiskrepanz. Eine Klasse ist der Bauplan. Die KEY
Klasse beschreibt daher eine Taste. Erst eine übergeordnete Klasse,
nennen wir sie mal Keyboard, hat dann die Fähigkeit mehere Keys zu
verwalten. Klassen willst du nicht wirklich ändern, nur weil du in
deiner Anwendung mehrere Objekte davon hast.
Hallo Peter,
Peter Dannegger schrieb:> Daher dachte ich, wenn ich eine Klasse definiere und darin die LED als> Member, wird für jedes Member automatisch die Initialisierung mit> aufgerufen.> Und wenn ich im Main eine Member-Aktion mache, wird der entsprechende> Code der Klasse ausgeführt.> Damit sollte exakt der gleiche Code entstehen, wie in plain C, also kein> Mehrverbrauch.
das macht man auch so. Etwas Mehrverbrauch entsteht je nach Compiler.
(schon alleine, da jedes Bit jeder LED einzeln initialisiert wird)
In C++ würde ich das mit Templates umsetzen. Jede LED bekommt damit eine
eigene Klasse. Dann braucht man keine Variable die Pin und Port
speichert und entsprechend viel RAM belegt.
Led<PortA, 7> Led_gruen;
Led_gruen.An(); // oder mit Operatorüberladung Led_gruen = true;
Die Template Parameter verhalten sich ähnlich wie ein Macro, werden also
beim Compilieren in den Quellcode eingefügt. Den Rest macht die
Optimierung.
Wenn du jetzt einen ganzen Port entprellen möchtest und einzeln
auslesen:
Male ein Diagramm, was für Objekte du hast und was diese tun sollen. Das
ist OOP und hat nichts mit C++ zu tun. C++ macht es dir nur einfacher ;)
Grüße Felix
FelixW schrieb:> In C++ würde ich das mit Templates umsetzen. Jede LED bekommt damit eine> eigene Klasse. Dann braucht man keine Variable die Pin und Port> speichert und entsprechend viel RAM belegt.
Wenn man alles const macht, kann man darauf spekulieren, dass das alles
wieder wegoptimiert wird.
Ganz im Gegenteil würde ich als einen der Bausteine eines C++ Frameworks
ein Template ansehen, welches die Kombination Port+Pin kapselt. Das wäre
so ziemlich die unterste Ebene und wäre recht nützlich um genau diese
Einheit an andere Klassen weiterzugeben, wie zb ein LCD, dem man
mitteilt auf welchen 'Anschlüssen' seine Steuerleitungen sitzen. So
wenig ich mich mit der Arduino Philosophie der durchnummerierten
Anschlüsse auch anfreunden kann, eines muss man doch zugeben: diese
Nummerierung vereinfacht vieles.
Peter Dannegger schrieb:>> class LED>> Danke, das sieht schon sehr gut aus.> Wo gibt es das DDR_From_Port()?
Das hab ich gerade eben erst erfunden :-)
Und es ist das leidige Problem, zuverlässig aus der Portangabe die DDR
Adresse zu ermitteln.
Wie ich an Felix auch schon geschrieben habe. Ich denke ein wirklich
nicht unwesentlicher Punkt wäre es, für diese Low-Level Sachen ein gut
aufgebautes Framework zu haben, welches diese ganzen Port, Pin, DDR
Sachen sauber kapselt OHNE dabei Codemässig stark aufzutragen. Gedanken
hab ich mir dazu allerdings noch nie gemacht, so dass ich da auch nichts
aus dem Ärmel schütteln kann.
Karl Heinz schrieb:> Ganz im Gegenteil würde ich als einen der Bausteine eines C++ Frameworks> ein Template ansehen, welches die Kombination Port+Pin kapselt.rechtgeb
Aber ein Framework ist Schritt n+1 ... Peter erst einmal anfangen
lassen.
@Karl Heinz,
das mit der Wegoptimierung der const Variablen habe ich noch nicht
ausprobiert, spekulieren will ich nicht. Ich verstehe davon zu wenig
wie/was der Compiler optimieren kann.
Grüße Felix
Karl Heinz schrieb:> Das hab ich gerade eben erst erfunden :-)
Vielleicht sollte man nur A,B,C usw. übergeben und dann PORT##A, DDR##A,
PIN##A draus basteln.
FelixW schrieb:> Karl Heinz schrieb:>> Ganz im Gegenteil würde ich als einen der Bausteine eines C++ Frameworks>> ein Template ansehen, welches die Kombination Port+Pin kapselt.>> *rechtgeb*>> Aber ein Framework ist Schritt n+1 ...
Ja, ok. Ersetz Framework durch einen anderen Begriff. Der passt wirklich
nicht besonders.
Man bräuchte eine einfache Möglichkeit einen physikalischen Pin in
seinen Eigenschaften zu beschreiben. Welches Port Register? Welches
DDR-Register? Welches Bit?
Klar kann man einem Pin Template zb das DDR Register mitgeben. Aber das
find ich persönlich recht unelegant. Im besten Fall sag ich dem Teil
einfach nur "Am Port B, und dort das Bit x" und damit muss bereits alles
klar sein.
Edit:
Ich würde das gerne So benutzen können
1
intmain()
2
Pin<PortB,3>LedPin;
3
LEDErrorLed(LedPin);
oder dann natürlich auch
1
intmain()
2
{
3
LEDErrorLed(Pin<PortB,3>);
wird klar, wo ich gerne hin möchte?
Mir gehts momentan nur darum, der LED zu beschreiben, wo ihr Anschluss
liegt. Und in dieser Beschreibung soll alles enthalten sein, so dass ich
die Pin Klasse auch um das korrekte DDR Register befragen kann.
Hallo Peter,
ich bin mir nicht ganz sicher, ob ich Dich richtig verstehe, aber ich
meine, Du hast da ein Verständnisproblem:
- Du könntest eine Klasse led_t anlegen.
1
classlet_t{...};
- Und könntest eine Instanz der Klasse anlegen.
1
let_tLED4;// Hängt an Port2.6
Aber:
Um den Port2.6 zu bedienen, muss in die Instanz (!!!) "LED4" das
Wissen hinein, dass diese LED an Port2.6 hängt. Dieses Wissen kannst Du
nicht von vornherein in "led_t" hineinpacken, denn sonst würde "led_t"
ja nur für den Port2.6 funktionieren.
Dies wäre z.B. so möglich:
>> wird klar, wo ich gerne hin möchte?> Mir gehts momentan nur darum, der LED zu beschreiben, wo ihr Anschluss> liegt. Und in dieser Beschreibung soll alles enthalten sein, so dass ich> die Pin Klasse auch um das korrekte DDR Register befragen kann.
Dann könnte die LED Klasse wiederrum so aussehen
1
classLED
2
{
3
public;
4
LED(constPin&pin)
5
:pin_(pin)
6
{pin_.DDr()|=pin_.bitmask()}
7
8
voidOn(){pin_.port()|=pin_.bitmask();}
9
voidOff(){pin_.port()&=~pin.bitmask();}
10
11
private:
12
constPinpin_;
13
};
oder überhaupt gleich das Setzen bzw. Löschen in das Pin Template
verfrachten
1
classLED
2
{
3
public;
4
LED(constPin&pin)
5
:pin_(pin)
6
{pin_.toOutput();}
7
8
voidOn(){pin_.setZero();}
9
voidOff(){pin_.setOne();}
10
11
private:
12
constPinpin_;
13
};
insbesonders letzteres sieht doch schon recht ordentlich aus.
Edit: hab noch ein paar const bzw. die Referenz im Argument nachgetragen
Auweia.. Angriff der C++ Fetischisten Teil II. Da kann ich einfach nicht
anders als meinen Senf dazu geben.
Also erst einmal zur Entzauberung: C++ und OPP ist mit Sicherheit nicht
schwerer als C oder gar Assembler zu programmieren. Im Gegenteil. Aber
offensichtlich diesem Mythos erlegen glauben einige dieses
Sprachkonstruckt nun für alles verwenden zu müssen ob’s nun Sinn macht,
oder nicht. Ist ja im Prinzip auch legitim. Am Ende kommt eh Assembler
bzw. Maschinencode heraus. Die einen können den selber Programmieren und
beeinflussen wie er was macht, die anderen schreiben lieber vorher
Romane und überlassen die Arbeit letzten Endes denen die den Compiler
programmiert haben.
Die Verwendung von C++ auf kleinen µC ist meiner Meinung nach aber
Nonsens –und ich schreibe Nonsens und nicht Verbrechen- weil es
keinerlei Vorteil bringt. Es sei man glaubt die Mär vom kompakteren und
schnelleren Code. Dies kann aber eigentlich nur ein Grenzdebiler
glauben. Hinzu kommt bei den ausgewiesenen Hochsprachen mit starker
Abstrahierung dass der ganze Misst nicht mehr vernünftig zu debuggen
ist. Und das muss jeder der hardwarenah programmiert eben tun und damit
meine ich nicht den eigenen Prozessor im Focus habend, sondern die immer
wieder neue und unbekannte periphere HW mit der ein µC Entwickler zu tun
hat und der vor der Aufgabe steht anhand eines Datenblattes seinen Code
anzupassen.
Ich Entwickle seit über 10 Jahren für OSX und iOS und davor für Windows.
Für solche Systeme führt fast kein Weg an C++ bzw. den
Weiterentwicklungen C“#“ und „Objectice C“ vorbei. Hier stellt quasi OOP
mit all seinen Vorteilen und den >>bereits vorhandenen<< mächtigen und
kaum zu überschauenden Klassen und Schnittstellen die einzige
Möglichkeit dar überhaupt noch mit einem so komplexen System zu
kommunizieren und dessen Funktionen zu verwenden. Dafür ist diese
Sprache das einzige Mittel.
Gäbe es für alle Mikrocontroller und jede aktuelle mögliche periphere
Hardware frei verfügbare und funktionierende Klassen und einem Compiler
der C++ fehlerfrei unterstützt zuzüglich einer funktionierenden
Möglichkeit adäquat zu debuggen dann sähe die Sache für den einen oder
anderen Fall möglicherweise etwas anders aus. Denn dann wäre wie bei
den „Hochbetriebssystemen“, OOP eine Erleichterung und würde seine
eigentliche Stärke ausspielen. Das tut es aber nicht und wird es
vermutlich nie geben. Aus vielen verschiedenen und offensichtlichen
Gründen. Und unter den gegebenen Voraussetzungen ist C++ ein Hemmschuh.
Weil das erstellen, Testen und optimieren der Klassen erst einmal
einfach wahnsinnig viel Mehrarbeit ist.
Kleine Ausnahme: Die kleine Auswahl an „Arduino Kontrollern für die
einige pfiffige ein paar nützliche Kassen geschrieben haben, damit auch
Kid’s und Künstler programmieren können. Sketch LED Blinken: 1KB, Sketch
ADC einlesen und UART ausgeben 4KB, weiter auf die Performance muss ich
an dieser stelle wohl kaum eingehen.
Wer also zur Selbstbestätigung seiner intellektuellen Omnipotenz
trotzdem gerne C++ benutzt um kleine µC zu programmieren, dem wünsche
ich einfach viel Freude an seiner Arbeit oder wie in den meisten Fällen
hier an seinem Hobby. Es reicht ja auch oft einfach den C++ Compiler zu
benutzen und trotzdem plain C zu schreiben. Mutti merkt’s nicht.
Thomas Holmes schrieb:> Da kann ich einfach nicht> anders als meinen Senf dazu geben.
Danke, wäre aber nicht nötig gewesen - es hilft leider nicht, die Frage
zu beantworten.
Peter Dannegger ist wohl alt genug, zu wissen warum er seine Frage
stellt.
Da kann man doch wirklich nicht meckern!
Edit:
Für alle Nörgler: Natürlich bleibt das nicht so. Die einzelnen Klassen
kommen natürlich in ihre Header Files, etc.
Im Endeffekt schreibt man dann nur
Peter Dannegger schrieb:> Geht sowas in C++ und wie könnte eine Implementierung aussehen?
Ich vermute, Du bist definitiv auf C++ festgelegt?
Ansonsten könnte man - völlig unabhängig von der
Implementierungssrache - gewisse Anleihen bei der
SPS-Technik aufnehmen.
Ich denke, man muss PeDa nicht wirklich in Dingen Softwaretechniken
beraten. Der hat mehr als genug auf dem Kasten, um das selbst
einschätzen zu können.
Wenn PeDa nach C++ und seinen Möglichkeiten fragt, dann nur aus einem
Grund: weil er genau wie wir alle in der Situation ist, dass es
bestimmte Fehler in der AVR Programmierung gibt, die einfach nur lästig
sind. Die Kombination aus Register und zugehörigem Bit für eine
bestimmte Funktionalität ist einfach eine fehleranfällige Sache. Je
weiter man die aus der Anwendungsprogrammierung rauskriegt, umso besser.
Auch ist das zusammenkopieren von Codeschnipseln in einem neuen Projekt
eine lästige Arbeit. Auch wenn die PeDa Entprellung in 5 Minuten
eingebaut ist, so ist es doch eine manuelle Arbeit und als solche
fehleranfällig.
PeDa geht es sicher nicht um Code-Techniken sondern darum, wie er sich
in der Entwicklung das Leben leichter machen kann, indem er die
Programmiersprache soweit ausreizt, dass sie ihm Fehlerfälle nach
Möglichkeit abfangen kann. Und sei es nur, dass er die falsche Pinnummer
an eine Funktion übergibt.
Karl Heinz schrieb:> Ich denke, man muss PeDa nicht wirklich in Dingen> Softwaretechniken> beraten. Der hat mehr als genug auf dem Kasten, um das selbst> einschätzen zu können.> Wenn PeDa nach C++ und seinen Möglichkeiten fragt, dann nur aus einem> Grund: weil er genau wie wir alle in der Situation ist, dass es> bestimmte Fehler in der AVR Programmierung gibt, die einfach nur lästig> sind. Die Kombination aus Register und zugehörigem Bit für eine> bestimmte Funktionalität ist einfach eine fehleranfällige Sache. Je> weiter man die aus der Anwendungsprogrammierung rauskriegt, umso besser.> Auch ist das zusammenkopieren von Codeschnipseln in einem neuen Projekt> eine lästige Arbeit. Auch wenn die PeDa Entprellung in 5 Minuten> eingebaut ist, so ist es doch eine manuelle Arbeit und als solche> fehleranfällig.> PeDa geht es sicher nicht um Code-Techniken sondern darum, wie er sich> in der Entwicklung das Leben leichter machen kann, indem er die> Programmiersprache soweit ausreizt, dass sie ihm Fehlerfälle nach> Möglichkeit abfangen kann. Und sei es nur, dass er die falsche Pinnummer> an eine Funktion übergibt.kopfkratz
Also wenn er seine Entprellung in C++ umsetzen möchte sollte er das
ganze nicht nur kapseln sondern auch z.B. mit überladenen Operatoren
arbeiten und für den Fehlerfall eine Exception werfen.
Um die Deklaration der Hardware kommt man damit allerdings nicht herum.
Da wäre ein Ansatz sich in einem .h File genormte Spezifikationen zu
setzen, so wie das ja im AVR-GCC auch gemacht wird.
Die Kardinalfrage ist halt ob es einen Sinn ergibt bei kleinen µCs mit
wenig Ressourcen C++ einzusetzen und sich damit einige Byte oder
Kilobyte an "Overhead" einzuhandeln.
Von der teilweisen schlechten Umsetzung der jeweiligen Compiler mal
abgesehen ...
Karl Heinz schrieb:>> Wo gibt es das DDR_From_Port()?>> Das hab ich gerade eben erst erfunden :-)> Und es ist das leidige Problem, zuverlässig aus der Portangabe die DDR> Adresse zu ermitteln.
Gab es da nicht von Atmel zu den AVR-Typen xml-Files, die die
beschreiben, aus denen dann die Include-Files für C und Assembler
generiert werden? Das könnte man doch für C++ genauso machen.
Thomas Holmes (Firma: CIA) (apostel13) schrieb:
>Kleine Ausnahme: Die kleine Auswahl an „Arduino Kontrollern für die>einige pfiffige ein paar nützliche Kassen geschrieben haben, damit auch>Kid’s und Künstler programmieren können. Sketch LED Blinken: 1KB, Sketch>ADC einlesen und UART ausgeben 4KB, weiter auf die Performance muss ich>an dieser stelle wohl kaum eingehen.
Na dann guck Dir mal diese Lib an
http://sensorium.github.io/Mozzi/
mir scheint die Realisierung in C++ ganz nützlich. Performant ist es auf
jeden Fall programmiert. Auiosignalerzeugung auf einem 8Bit Controller
ist nicht so einfach.
Klaus Wachtler schrieb:> Peter Dannegger ist wohl alt genug, zu wissen warum er seine Frage> stellt.
An Rande:
Seine Frage im übrigen hat mich sehr erfreut. Zum einen weil sie etwas
über C++ aussagt und zum anderen natürlich weil es bald auch endlich
eine Endprellungsversion in C++ gibt, nachdem diese in Assembler und C
ja bereits schon jedem zur Verfügung steht.
Wobei es das eigentlich schon gibt. Die Arduinogemeinde hat etliche
Button Klassen hervorgebracht. Eineige mit und einige ohne Entprellung.
Karl Heinz schrieb:> Ich denke, man muss PeDa nicht wirklich in Dingen Softwaretechniken> beraten. Der hat mehr als genug auf dem Kasten, um das selbst> einschätzen zu können.> Wenn PeDa nach C++ und seinen Möglichkeiten fragt, dann nur aus einem> Grund:
Offensichtlich hat er sogar einen Pressesprecher. Alle Achtung!
:-)
Heiliger Bimbam schrieb:> Ist Euch die Programmierung in den gängigen Sprachen noch nicht> kompliziert genug?
Karl Heinz hat das ganz richtig erkannt, es geht mir vorrangig darum,
das Programmieren einfacher und sicherer zu machen.
Z.B. stört mich auch beim plain C, daß es keine Typprüfung für Enums
gibt. Man kann ganz leicht ein Enum in der falschen Statemaschine
verwenden und der Compiler lacht sich ins Fäustchen.
Und wie es aussieht, erzeugt C++ keinen Overhead, wenn die Argumente
schon zur Compilezeit bekannt sind.
Sind sie es nicht, dann hat man auch unter plain C mit Macros oder
Inlinefunktionen den Overhead.
Und C++ ist ja schon im AVR-GCC includiert, man braucht also an der
Programmierumgebung nichts zu ändern. Einfach nur *.c nach *.C
umbenennen.
Karl Heinz schrieb:> Ich werd verrückt.> Ich hab mal einfach was zusammengebraut.> Siehe Cpp-File.
Wow. Das schaut wirklich so aus, als könnte man auf die Weise ein
"Arduino in Gut" basteln.
Für eine effiziente LCD-Lib bräuchte man als Grundlage noch ein
"Multi-Bit-GPIO" für den Datenbus mit integrierter Maskierung, und ggfs
__builtin_avr_insert_bits zum Umsortieren der Datenleitungen.
Dann ein paar ähnliche (aber umfangreichere) Konstrukte für TWI, SPI,
UART usw.
Für Timer (eine der ganz großen Arduino-Schwachstellen IMHO) fällt mir
grad keine vernünftige Lösung ohne Virtuelle Methoden etc. ein. Evtl
kriegt man mit Templates was gebastelt?
Für Tastenentprellung könnte vmtl. das atomic/locking ein Problem
werden?
Karl Heinz schrieb:> Ich hab mal einfach was zusammengebraut.
Ich hab' mal meine port.h angehängt. Verwendet wird's ungefähr so:
1
#include"port.h"
2
3
typedefPin<PortB,PB0>PinLED;
4
typedefPin<PortD,PD3>PinTaste;
5
6
intmain()
7
{
8
PinTaste::SetPullUp();
9
if(PinTaste::IsSet())
10
PinLED::Set();
11
}
Nachteil meiner Implementierung: Sie verwendet reinterpret_cast<>, um
von der Port-Adresse eine Integer-Konstante für ein Template-Argument zu
machen, was der GCC nur gnadenhalber als Konstante interpretiert; streng
genommen ist es nicht C++-konform.
Ich habe davor auch mit deiner Variante gespielt, da habe ich aber
attribute((force_inline)) benötigt, um GCC das Inlinen schmackhaft zu
machen.
Könnte der unterschiedliche Datentyp durch das Template nicht evtl zum
Problem werden?
Wenn ich z.B an meine LCD Klasse die Pins als Objekte übergebe, wie
könnte man das dann einheitlich definieren?
Interface?
tictactoe schrieb:> Ich habe davor auch mit deiner Variante gespielt, da habe ich aber> attribute((force_inline)) benötigt, um GCC das Inlinen schmackhaft zu> machen.
Das was mich am allermeisten verblüfft hat. Ich hatte eigentlich damit
gerechnet, dass ich da noch ein paar const mit einbauen müsste.
Zumindest hatte ich damit gerechnet, in der Hauptschleife irgendeine
in-or-out Sequenz zu sehen. Das da direkt ein sbi bzw. cbi auftaucht,
damit hab ich nicht gerechnet.
Aber in deinem Header File hab ich mir noch ein paar andere Dinge
abgeschaut, bei denen ich mir auf die Stirne klopfe und mir denke: Mann,
so simpel und ich komm nicht drauf.
> Für eine effiziente LCD-Lib bräuchte man als Grundlage noch ein> "Multi-Bit-GPIO" für den Datenbus mit integrierter Maskierung,> und ggfs __builtin_avr_insert_bits zum Umsortieren der Datenleitungen
Ich hatte in Gedanken schon mit einer Nibble Klasse gespielt. Aber noch
ist mir nicht klar, wie ich die Fälle "Alle 4 Leitungen schön
regelmässig angeordnet" versus "komplettes Durcheinander" im Code so
trennen kann (je nach Konstruktor Aufruf), dass dann nur der Code Teil
übrig bleibt, der wirklich gebraucht wird. P-Fleury hat zwar eine Lösung
in Form einer #ifdef Origie, aber so richtig happy bin ich damit nicht.
Das müsste auch eleganter gehen.
C+++ schrieb:> Für Timer (eine der ganz großen Arduino-Schwachstellen IMHO) fällt mir> grad keine vernünftige Lösung ohne Virtuelle Methoden etc. ein. Evtl> kriegt man mit Templates was gebastelt?
Da hab ich auch noch keine Idee dazu. Mit virtuellen Funktionen könnt
ich (für mich) sogar leben. Da bin ich nicht mehr der Takt-Zähler.
Templates, denke ich, werden dir da nicht helfen. Denn Timer leben
davon, dass ich Funktionalität in die ISR einbringen kann. Das können ja
auch mehrere Funktionale Einheiten sein. Gleichzeitig in der ISR
entprellen und eine Uhr treiben, wobei Entpreller und Uhr
unterschiedliche Klassen sind - ich seh da keinen anderen Ausweg als
virtuelle Funktionen.
Nop schrieb:> Könnte der unterschiedliche Datentyp durch das Template nicht evtl zum> Problem werden?> Wenn ich z.B an meine LCD Klasse die Pins als Objekte übergebe, wie> könnte man das dann einheitlich definieren?> Interface?
Du definierst deine Klasse als Template:
template<class CLOCK_PIN, class DATA_PIN>
class LCD { ... };
Eine Klasse macht man sich ja, weil man irgendwelchen State behalten
will. In der LCD-Klasse wäre z.B. eine Cursor-Position als State
geeignet. Aber die Pins sind kein State: Die werden sich in deiner
Anwendung nie nicht ändern und sind deshalb als (konstante)
Template-Argumente geeignet.
tictactoe schrieb:> Nop schrieb:>> Könnte der unterschiedliche Datentyp durch das Template nicht evtl zum>> Problem werden?>> Wenn ich z.B an meine LCD Klasse die Pins als Objekte übergebe, wie>> könnte man das dann einheitlich definieren?>> Interface?>> Du definierst deine Klasse als Template:>> template<class CLOCK_PIN, class DATA_PIN>> class LCD { ... };
Ich seh schon.
Ich muss auf meine alten Tage doch noch lernen mit Templates umzugehen.
Bisher hab ich mich ja erfolgreich davor gedrückt.
Hm, ich sehe schon( :-) )... die Template Geschichte reicht sich dann
immer weiter hoch.
Aber für die Nibbel Geschichte von K.H. ist ja ein Template optimal.
Warum tut man sich das denn eigentlich an? Warum erdenkt man sich
kompliziert zu lesende C++ Gebilde für etwas was andere Sprachen viel
einfacher und damit im Lernaufwand schneller lösen können? Warum nicht
gleich mit einer speziell zugeschnittenen Sprache wie hier
http://avr.myluna.de/doku.php?id=de:features
sich genau den Teil der Objektorientiertheit herauspicken, den es auch
wirklich braucht?
Wozu muss man die Dinge nur immer unnötig verkomplizieren?
Verkomplizierung bringt neue Fehleranfälligkeit durch
Verständnisprobleme bei unnötig komplizierten Ausdrücken.
Warum also macht man das? Um sich anschließend selbst zu beweihräuchern?
ICH kann C++ SOGAR auf MC und DU dümpelst mit deiner "schlichten Sprache
für JEDERMANN" nur im allgemeinen Fahrwasser? Ich bin "die Elite", DU
hingegen bist "der Depp", der nur das kann, was jeder andere gut lernen
und damit umsetzen kann?
Wo ist der wirkliche MEHRWERT? Ist das Endergebnis wirklich besser?
Lohnt der Aufwand wirklich?
Karl Heinz schrieb:> Templates, denke ich, werden dir da nicht helfen. Denn Timer leben> davon, dass ich Funktionalität in die ISR einbringen kann. Das können ja> auch mehrere Funktionale Einheiten sein. Gleichzeitig in der ISR> entprellen und eine Uhr treiben, wobei Entpreller und Uhr> unterschiedliche Klassen sind - ich seh da keinen anderen Ausweg als> virtuelle Funktionen.
Wie machst du dem Interrupt-Handler eine variable Anzahl von Clienten
bekannt? Hmm... Halt! Variable Anzahl? Die gibt's bestimmt nicht, denn
du hast nur eine konstante Anzahl von Tastern und Uhren an deinem
Controller hängen.
Dann können wir also z.B. einen Array mit Pointern auf die Clienten
anlegen, damit wir die virtuelle Handler-Funktione aufrufen können.
Vielleicht so:
1
staticconstOVF_Handler*OVF_clients[]={
2
&uhr,
3
&taste1,
4
&taste2
5
};
6
ISR(TIMER0_OVF_vect){
7
for(autoclient:OVF_clients)
8
client->OVF_vect();
9
}
Aber das kriegt man auch mit Template-Metaprogramming hin (Stichwort
Parameter Packs). (Wie genau, müsste ich mir erst überlegen -- bin noch
nicht so geübt darin.) Sieht auf der User-Seite dann ungefähr so aus:
clients();// operator(), ruft OVF_vect() von den Clients mittels Meta-Programming-"Schleife" auf
4
}
Und schon sind wir die Pointer los, und OVF_vect() in den Clients
braucht nicht mehr virtuell sein; die Clients müssen nicht einmal eine
gemeinsame Basisklasse haben, solange sie eine Funktion OVF_vect()
implementieren.
Thomas Holmes schrieb:> Also erst einmal zur Entzauberung: C++ und OPP ist mit Sicherheit nicht> schwerer als C oder gar Assembler zu programmieren. Im Gegenteil.
So? Ich denke, schlechter Code ist schnell geschrieben, gerade bei der
Komplexität, die C++ bietet. Gerade durch die Bandbreite an Stilmitteln
wird's doch kompliziert — angefangen von der saumäßig lahmen RTTI, über
die reine Behandlung statisch typisierter Objekte bis hin zur
effizienten, aber gruselig lesbaren, template-Ebene: man sollte schon
wissen, was man tut. Was Karlheinz und Peter hier diskutieren ist die
Verwendung der Sprache für effizientest möglichen Code, der trotzdem die
Grundlage für halbwegs objektorientierte Ansätze sein kann. Gerade auch
durch Metaprogrammierung mit templates sollte einiges drin sein.
Die Implementierung von Karlheinz ist super: sie erzeugt relativ
selbsterklärenden und gut lesbaren Code und ist effizient.
> Die Verwendung von C++ auf kleinen µC ist meiner Meinung nach aber> Nonsens –und ich schreibe Nonsens und nicht Verbrechen- weil es> keinerlei Vorteil bringt.
Für wen bringt es keine Vorteile? So üblen Code produzieren die Compiler
gar nicht - auf kleinen ARMs würde ich überhaupt keinen Asm mehr
anfassen, noch nicht mal für NEON der Cortexe oder so, sondern pauschal
nicht. Und auf AVR — wenn es eine libC++ gibt, die Arduino-like Dinge
kapselt, dann halte ich es mit Don Knuth, der vor vorschnellen
Optimierungen warnt. Man kann auch mit C Grütze schreiben und
softwaretechnischen Unsinn, der einzeln effektiv, aber im Gesamtsystem
uneffizient ist.
> Es sei man glaubt die Mär vom kompakteren und> schnelleren Code. Dies kann aber eigentlich nur ein Grenzdebiler> glauben. Hinzu kommt bei den ausgewiesenen Hochsprachen mit starker> Abstrahierung dass der ganze Misst nicht mehr vernünftig zu debuggen> ist.
Persönlich bin ich ein großer Fan von Abstraktion, eben weil sie
Portabilität bzw. Testbarkeit von Einzelkomponenten ermöglicht und somit
Debugging eigentlich im Vorfeld vermeiden kann. Natürlich werden auch
Fehler versteckt, das liegt in der Natur der Sache. Abstraktion lässt
aber Optionen offen: nämlich Komponenten zu bauen, die z.B. besonders
performant (z.B. Karlheinz Code-Demo) oder auch gut wiederverwendbar
sind. Wenn man halt das volle Programm haben will, sowohl Performanz als
auch Wiederverwendbarkeit, muss man auch entsprechend viel Zeit
investieren. Was aber leider nicht immer vermittelbar ist, weil C++ ja
angeblich so pille-palle ist und bloß durch die reine Verwendung die
Time-To-Market reduziert.
Und das muss jeder der hardwarenah programmiert eben tun und damit
> meine ich nicht den eigenen Prozessor im Focus habend, sondern die immer> wieder neue und unbekannte periphere HW mit der ein µC Entwickler zu tun> hat und der vor der Aufgabe steht anhand eines Datenblattes seinen Code> anzupassen.
Sollte doch genauso möglich sein. Ich persönlich mag C auch (besonders
c99), weil es mit vergleichsweise wenig Sprachumfang sehr effizient ist.
In der einfachsten Denke fasst C++ halt gewisse Sachen einfach durch ein
Schlüsselwort zusammen. In C machste 'ne struct mit Funktionspointern,
in C++ isses 'ne Klasse. Der umgekehrte Weg - Klassen mit C-Strukturen
darstellen - ist imho auch nicht verkehrt, das würde auch eine Art
Programmierung gegen Schnittstellen sein. In C++ ist das halt gleich
durch die Syntax von 'class' mit dabei, was dir eine vtable erzeugen
kann.
> Ich Entwickle seit über 10 Jahren für OSX und iOS und davor für Windows.> Für solche Systeme führt fast kein Weg an C++ bzw. den> Weiterentwicklungen C“#“ und „Objectice C“ vorbei. Hier stellt quasi OOP> mit all seinen Vorteilen und den >>bereits vorhandenen<< mächtigen und> kaum zu überschauenden Klassen und Schnittstellen die einzige> Möglichkeit dar überhaupt noch mit einem so komplexen System zu> kommunizieren und dessen Funktionen zu verwenden. Dafür ist diese> Sprache das einzige Mittel.
Polymorphie hat Vorteile, hat aber auch Nachteile. Für die Modellierung
ist sie schön, wenn sie richtig angewandt wird. Ich hab sie auch selber
schon oft genug selber falsch angewendet - sei es durch unbekannte oder
zu flexible Requirements / Constraints und sicher auch einfach durch
manchmal ungeschickte Platzierung.
> Gäbe es für alle Mikrocontroller und jede aktuelle mögliche periphere> Hardware frei verfügbare und funktionierende Klassen und einem Compiler> der C++ fehlerfrei unterstützt zuzüglich einer funktionierenden> Möglichkeit adäquat zu debuggen dann sähe die Sache für den einen oder> anderen Fall möglicherweise etwas anders aus. Denn dann wäre wie bei> den „Hochbetriebssystemen“, OOP eine Erleichterung und würde seine> eigentliche Stärke ausspielen. Das tut es aber nicht und wird es> vermutlich nie geben. Aus vielen verschiedenen und offensichtlichen> Gründen. Und unter den gegebenen Voraussetzungen ist C++ ein Hemmschuh.
Im Zweifelsfall sollte auch der sportliche Aspekt gelten: einfach mal
gucken, wie gut es im Endeffekt wirklich funktioniert.
Leute, der Thread heißt:
"C++ auf einem MC, wie geht das?"
Könnt ihr das philosophieren nicht auf den andren heute gestarteten
Laber Thread verschieben?
>Ich möchte gerne folgendes erreichen:> LED0 = KEY0.state; // an solange Taste gedrückt> LED1.toggle = KEY1.press // wechsel bei jeder Drückflanke> LED2.off = KEY2.press_short; // kurz drücken - aus> LED2.on = KEY2.press_long; // lang drücken - an
Man könnte ein Konzept verwenden, welches bei Java oder C# übliche ist.
Dort gibt es die sogenannten "Listener" also Zuhörer, die bei einer
Ereigniserzeuger eingetragen werden. In Java würden die KEYs übrigens
"Button" heißen.
Ich wandle das Prinzip aus Java etwas ab, damit es auf einfache Weise
zum Tastenproblem passt.
Es geht hier mittlerweile auch darum möglichst guten Code zu erzeugen
;-)
Im Normalfall weiß man zur Compilezeit wieviele Tasten angeschlossen
sind.
Eine dynamische Erzeugung ist somit überflüssig!
tictactoe schrieb:> Dann können wir also z.B. einen Array mit Pointern auf die Clienten> anlegen, damit wir die virtuelle Handler-Funktione aufrufen können.> Vielleicht so> ...> Aber das kriegt man auch mit Template-Metaprogramming hin
Ich muss hier nochmal nachhaken. Mein Post ist eine Werbung für
Template-Metaprogramming. Man muss hier aber einen Schritt zurück machen
und noch mal die Ausgangssituation betrachten. Gerade weil man auf einem
µC keine dynamischen Listen von (Uhren, Tastern...) hat, könnte man
Template-Metaprogramming verwenden (weil alle teilnehmenden Klassen zur
Compile-Zeit bekannt sind). Aber aus dem selben Grund kann man genau so
gut auch schreiben:
1
ISR(TIMER0_OVF_vect){
2
uhr.OVF_vect();
3
taste1.OVF_vect();
4
taste2.OVF_vect();
5
}
Wozu also das ganze Gedöns um virtuelle Funktionen (braucht man bei
dieser Form nicht) und Template-Metaprogramming? Hab' ich was an den
Anforderungen nicht verstanden?
tictactoe schrieb:> Wozu also das ganze Gedöns um virtuelle Funktionen (braucht man bei> dieser Form nicht) und Template-Metaprogramming? Hab' ich was an den> Anforderungen nicht verstanden?
Das sehe ich auch so. So schön die ganze Template Geschichte auch ist,
man kann kaum richtig einschätzen was der Compiler am Ende wirklich
daraus macht. Wenn man dann aber erst im Assemblerlisting rumkramen muss
um die Qualität der Umsetzung zu beurteilen wir es lästig und fraglich.
Noch dazu bei einem Interrupt. Dann ist die traditionelle Variante:
>> Wozu also das ganze Gedöns um virtuelle Funktionen (braucht man bei>> dieser Form nicht) und Template-Metaprogramming? Hab' ich was an den>> Anforderungen nicht verstanden?
Vermutlich: Damit sich ein Instanziiertes Template selbständig irgendwie
automatisch am Timer registrieren kann.
Also
1
#include<IRPM>
2
3
...
4
5
intmain(){
6
IRMPreceiver<Pin<PORTB,3>>;
7
8
while(1){
9
if(receiver.gotCode()){....}
10
}
11
}
Ohne dass man selber in der ISR ein "receiver.poll()" Aufrufen müsste...
Geht ziemlich in die Richtung von "Flow-Based Programming", oder nicht?
(http://en.wikipedia.org/wiki/Flow-based_programming)
Den Gedanken mag ich sehr, aber mir fiel nie eine vernünftige
Ausdrucksweise in C/C++ ein, dem dem PROGRAMMIERER nützt und noch
irgendwie effizient ist. Da bräuchte man eigentlich eine höhere Sprache,
die das dann beispielsweise in C übersetzt... Mit all den Nachteilen die
man sich da einhandelt.
Ich dachte mir, ich könnte eine C++11 alternative zu <avr/io.h>
schreiben, aber ich schaffe es nicht, dass am Schluss ein sbi dabei
rauskommt. Warum will avr-gcc 4.8.2 hier nicht stärker optimieren?
Was ich erwartet habe:
1
PORTA |= 1;
2
3a: 70 9a sbi 0x0e, 0 ; 14
Was immer herauskommt, egal welche Optimierungseinstellung:
Peter Dannegger schrieb:> Man kann ganz leicht ein Enum in der falschen Statemaschine> verwenden und der Compiler lacht sich ins Fäustchen.
Das umgeht man entweder dadurch, dass man die Sichtbarkeit
einschränkt, indem man die Enumeration (meistens Typdefinition +
Enumeration) nur in dem C-File hinzufügt, in dem auch die Funktion
steckt, die das Enum nutzen soll/darf
oder
mittels Defines nur die notwendigen Enums aus einem Header-File
inkludiert (ifdef ichbin's, dann include nur meinen relevanten Code).
Da wäre z.B. der Tipp sich mal den MID Innovator anzusehen
bezüglich SA/SD/Implementierung.
http://www.mid.de/
Der erledigt dann z.B. die Sichtbarkeit von Handles automatisch.
Peter Danneger schrieb:
>Ich möchte gerne folgendes erreichen:> LED0 = KEY0.state; // an solange Taste gedrückt> LED1.toggle = KEY1.press // wechsel bei jeder Drückflanke> LED2.off = KEY2.press_short; // kurz drücken - aus> LED2.on = KEY2.press_long; // lang drücken - an
Hallo Peter,
da mich das Thema auch ein wenig interessiert, habe ich ein wenig Code
geschrieben. Mir geht es eher um die "Architektur" des Programms,
deshalb habe ich der Einfachheit halber einen Arduino verwendet mit
seinen langsamen Libs verwendet und die Sachen nicht
Geschwindigkeitsoptimiert. Der Arduino liegt hier meistens herum und ich
muss nicht erste ein Steckbrett mit MCU aufbauen.
Da das Prinzip der Listener sich auf den PCs durchgesetzt hat, habe ich
dieses implementiert.
So weit ich weiß, hast Du hier im Forum irgenwo eine sehr gute
Entprellroutine für MCs geschrieben, die sehr viel Anklang findet.
Dein Code ist überlicherweise kurz, klar und gut, deshalb mögen die
langen Funktionsnamen im folgenden Beispiel etwas abschreckend sein,
aber ich versuche selbst erklärenden Code zu produzieren und die
Funktionsnamen für sich sprechen zu lassen.
Dr. Sommer schrieb:> Aber auch in Brainfuck. Warum verwendet niemand Brainfuck? Es ist> supereinfach zu lernen, verwenden und implementieren.
Habe mir das mal angeguckt - ich hätte fast gekotzt.
Daniel A. schrieb:> aber ich schaffe es nicht, dass am Schluss ein sbi dabei> rauskommt.
Dann nimm doch einfach Asm ;-)
Ret schrieb:> Wo ist der wirkliche MEHRWERT? Ist das Endergebnis wirklich besser?> Lohnt der Aufwand wirklich?
Auf diese alberne Frage gibts HIER doch keine Antwort. Da sind andere
Dinge wirklich wichtiger!
>Jetzt kann man da nur LEDs anschließen und das "new" dort ist wohl>unnötig... Für PRESSED etc. besser enum oder enum class...
Hmm, man kann LEDs und Taster anschließen. Ich dachte, das sei die
Aufgabe. Peter hatte ja ganz am Anfang dieses Threads den "Beispielcode"
gepostet, der in C++ umgesetzt werden soll.
Peter Dannegger schrieb:> Hier gibts ja viele Meinungen zu C++, aber wenn man es selber probieren> will, gibt es große Hürden.Peter Dannegger schrieb:> Besonders interessant wäre die Entprellung portweise parallel und nur> das Auswerten der Tasten einzeln.
Peter, ich verstehe dich wirklich nicht. Was soll das bloß?
Also erstens, wozu über C++ nachdenken? Um für einen µC eine Firmware zu
schreiben, nimmt man sinnvollerweise etwas, das dem Teil angemessen ist
und das man kriegen kann und bezahlen kann und womit man selber auch
zurecht kommt. Das läuft zumeist auf simples C hinaus, wobwi allerdings
einige Compiler auch genausogut C++ verstehen.
Wo sind deine Hürden?
Ich sehe das so, daß viele Leute einfach völlig unstrukturiert an ihre
Probleme herangehen und einfach brüllen "Ich brauche jetzt&hier eine
Entprellung meiner 7 Tasten, aber PRONTO!" anstatt sich irgend einen
Gedanken über eine Strukturierung ihrer Firmware zu machen.
Dazu hätte gehört, zwischen quasi einer Anwendung und quasi einem Satz
echter Hardwaretreiber zu unterscheiden, wo das geeignete Abfragen und
Behandeln von solchen Dingen wie Tasten, Drehgebern, Endlagenschaltern
und so weiter sauber und gekapselt und sicher für's System stattfindet.
An solchen Niederungen ist das, was manche unter C++ verstehen wollen,
einfach fehl am Platz.
Peter Dannegger schrieb:> LED1.toggle = KEY1.press
Eben. So ein Gedanke (wenn man das denn so nennen will) ist sowas von
strunzfalsch, daß mir dafür die geeigneten Worte fehlen. Stattdessen
sieht das sinnvollerweise eher so aus:
1. Keytreiber stellt fest, daß KEY1 immer noch ununterbrochen gedrückt
ist, obwohl die Repetierzeit vorbei ist, also ruft er den Eventhandler
auf mit AddEvent(Key1_pressed)
2. Die zentrale Eventverwaltung der Firmware arbeitet die aufgelaufenen
Events ab und wird dann auch "Key1_pressed" an die zuständigen Teile der
Anwendung weiterreichen.
3. Ein Programmteil im Anwendungsbereich der Firmware kriegt diesen
Event ab und tut daraufhin, was er tun soll, z.B. die Heizung
abschalten. Das signalisiert er dann mit der zuständigen Signallampe,
indem er LED1 ausschaltet. Das macht er direkt im zuständigen Port, weil
sowas eine nebeneffektfreie Aktion ist.
Wo siehst du da bloß einen Ansatzpunkt, um Tasten per C++ zu entprellen?
W.S.
>Peter, ich verstehe dich wirklich nicht.
Da liegt wahrscheinlich Dein Problem.
Peter kennt sich mit Treibern zur Tastenentprellung ziemlich gut aus:
http://www.mikrocontroller.net/articles/Entprellung#Timer-Verfahren_.28nach_Peter_Dannegger.29
Ich denke hier geht es eher um die Frage: wie kann die Methoden und
Techniken die C++ und die Objektorientierung bieten auch für eine
Tastenentprellung auf einem MC nutzen.
Daniel A. schrieb:> aber ich schaffe es nicht, dass am Schluss ein sbi dabei> rauskommt. Warum will avr-gcc 4.8.2 hier nicht stärker optimieren?
Ich habe es jetzt herausgefunden, es war mein Fehler: Als ich die
Adressen der Ports aus <avr/iotn48.h> abgeschrieben habe, habe ich
übersehen, dass makro _SFR_IO8 einen offset von 0x20 hinzugefügt hat.
Dadurch hatte gcc die falsche Adresse und konnte kein sbi benutzen.
Im Anhang sind alle geänderten Dateien. Jetzt verwendet gcc immer ein
sbi, und wenn ich das aus dem C++ code generierte hexfile mit dem
hexfile aus folgendem C-Code vergleiche, sind diese Identisch!
Peter Dannegger schrieb:> Sepp schrieb:>> Um die dauer des>> Tastendrucks festzustellen benötigt man auch hier eine ISR>> Das ist klar.> Es ging mir auch haupsächlich darum, wie ich in der Mainloop die> Ereignisse auswerten kann und wie ich die Klassen definieren muß.>> Z.B. ich definiere eine Klasse LED und ein Klasse KEY und darin alle> Aktionen und Initialisierungen dafür. Die Aktionen benutze ich dann in> der Mainloop.> Schön wäre es noch, wenn die Klasse automatisch den passenden> Interrupthandler aktiviert.> Füge ich eine neue Taste der Klasse KEY hinzu, wird geprüft, ob deren> Port bereits entprellt wird und wenn es ein neuer Port ist, wird der> Code dafür hinzugefügt.
Ich finde die Stossrichtung diese Dinge mal objektorientiert zu
betrachten hervorragend und lese neugierig mit.
Mir gehen dazu ein paar Gedanken durch den Kopf, die ich Euch einfach
mal so hinwerfen wollte:
Was Pin, Key und Debounce angeht sind das für mich aus einer
Datenmodell-Perspektive 3 verschiedene Dinge.
Pin ist die "physikalische Schicht", die Hardware.
Key ist ist ein Device, das am Pin hängt.
Debounce ist ein Service, die die beiden verbindet.
Desweiteren ist Debounce nicht 1:1 mit Pin oder Key verbunden, sondern
ist ein "Service", der mehrere Pins abfragt und als Key an die
Applikation liefert, also:
Pin \ / Key
Pin +-- Debounce-Service -- + Key
Pin / \ Key
Wahlweise können Keys dann an Pins hängen, wenn die Hardware-entprellt
sind, also müsste die Key-Klasse mit beidem umgehen können.
Der Debounce-Service wäre also eine Klasse, die im Interrupt hängt,
einen oder mehrere Ports bedient (könnte sogar selbst wissen, wann es
das tun muss) und mehreren Keys als Input dient.
Keys haben also eine Referenz auf ein Bit des Debounce-Objekts, das
einen Port bedient und alternativ eine direkte Referenz auf ein Pin.
Weiters sollte m.E. ein Key nicht mit einer LED verbunden sein,
zumindest nicht 1:1. Denn üblicherweise ist der Zweck eines Keypress
nicht, eine LED zu schalten, sondern die LED ist ein Signal für einen
Zustand des Systems.
Also wäre der primäre Vorgang mit einem Keypress einen Vorgang im System
auszulösen, eine Zustandsänderung. Und die Zustandsänderung ist mit der
LED verknüpft.
Angenommen also, dass ein bestimmter Zustand eine richtige Statemachine
in einen anderen Zustand bringt, dann müsste die LED mit der
Statemachine verbunden sein und bei erreichen eines bestimmten State
geschaltet werden. Und zwar am besten nicht explizit mit "LED.on()",
sondern implizit durch die State Machine, weil die LED mit dem State
verknüpft ist.
Moin !
Ich finde den OOP Gedanken sehr schick.
Aktuell habe ich mal wieder ein Projekt bei dem :
- Ein digitales Signal vermessen wird. (Pulsdauer und Startzeitpunkt)
- Die Daten durch eine simple Berechnung gehen.
- Und am entsprechenden Pin wieder digital zappeln muss.
Das ganze ist vierfach vorhanden.
Die Daten liegen alle in einem Struct.daten1, Struct.daten2 ...
Die "Vermessprozedur" sowie die "Ausgabe" werden mit ihrem
entsprechenden Pointern aufgerufen.
Dabei wird auch ein Pointer auf den entsprechenden Port übergeben.
(nicht sehr schick)
Das schreit doch förmlich nach OOP !?
Die Frage ist jetzt nur wie weit treibe ich den Spuck.
Für meine Problemstellung würde ich folgende Klassen benötigen :
GPIO - Digitale Ein und Ausgabe.
SENSOR - Vermessen des Signal und Trigger für Startzeitpunkt
CALCULATOR - Signal für die Ausgabe berechnen
AKTOR - Gibt bei Trigger von SENSOR die berechnete Pulslänge aus.
Obiges könnte man aber auch zu GPIO und EINSPRITZDING zusammenfassen.
Vielleicht wäre es auch sinnvoll wenn GPIO den PORT erbt.
Bestimmte PORT haben etwas mit GPIO, UART, SPI etc zu tun.
Aber SPI und UART müssten auch auf GPIO zugriff haben.
In echtem guten OOP müsste ich das doch nachbilden ?
Aber klar ist doch das verschiedene GPIO an SENSOR und AKTOR übergeben
werden.
Für mein Problem reicht ein einfaches GPIO.
Macht man es gescheit wirds ne dicke Kuh.
Ich denke ich baue das Programm nochmal in C++ und OOP nach.
Mal schauen was rauskommt...
F. Fo (foldi) schrieb:
Dr. Sommer schrieb:
>> Aber auch in Brainfuck. Warum verwendet niemand Brainfuck? Es ist>> supereinfach zu lernen, verwenden und implementieren.> Habe mir das mal angeguckt - ich hätte fast gekotzt.
Da kann ich dich voll verstehen! Denn mir geht es genau nicht anders.
Wer so einen hingekotzten Zeichensalat (ich hab extra nochmal
nachgeschaut) wie Brainfuck ernsthaft als Programmiersprache empfiehlt,
verarscht entweder seine Mitdiskutanten oder hat schlicht nicht alle
Tassen im Schrank. Langsam nähren sich in mir die Anzeichen, dass ich
solche Leute künftig an anderer Stelle nicht mehr für voll nehmen
sollte. Und wenn mir der gleiche Herr demnächst dann seine C++
Template-Orgien schmackhaft unter die Nase reiben will, denke ich mir
darauf, "hab Nachsicht mit ihm! Das ist einer, der auf Brainfuck steht.
Von dem kann einfach nichts Brauchbares oder Gescheites kommen".
So kann man sich seine Glaubwürdigkeit hier auf einen Schlag verspielen.
Aber vielleicht passt auch alles gut zusammen. Wer auf Brainfuck steht,
der findet manche Brainfuck-Ausdrücke, die in Form von C++ so anfallen,
eben auch "geil" und ergötzt sich daran. Nur verschont bitte die noch
normal gebliebenen Landsleute unter uns damit, die einfach in
vertretbarem Aufwand, LESBAREN UND WARTBAREN CODE programmieren möchten,
ob für den PC oder für µC, ob in C, Assembler, Pascal (Delphi, Lazarus),
LunaAVR, Processing oder einem modernen BASIC-Dialekt.
Esoterische Hirnkrampf-Programmiersprachen sind was für Leute, die zu
viel gelangweilte Freizeit zur Verfügung haben, konsumtechnologisch
total übersättigt sind, fortwährend nach einem neuen Kick suchen und in
ihrem Umfeld schließlich durch zu viel nerdiges Hipster-Verhalten
anderen chronisch auf die Hutschnur gehen. Einfach künftig mal die
Klappe halten und lieber anderen zuhören ist der erste Schritt zur
Heilung. ECHTE Hilfe anbieten ist dann Schritt Nr. 2 usw. Brainfuck
braucht es dazu nicht.
Hier wird jetzt viel geschrieben, a la "geht nicht", "ist nicht anders
vom Aufwand", "ist zu langsam und zu viel overhead". Andere sagen dann
wieder, "geht doch" ...
Mal anders gefragt: Wer von euch programmiert seine µC's mit C++ und was
sind das für Programme?
Wenn jemand wie Peda an sowas denkt und das für eine gute Idee hält, wo
er doch so ein C Spezi ist, dann ist das sicher mal ein berechtigter
Gedanke.
Die einzige wirklich (für mich) brauchbare Aussage habe ich hier
gelesen:
Sepp schrieb:> Was mit persönlich an C++ im Embedded Bereich sehr gefällt sind die> Generics (bei C++ Templates genannt). Damit kann man einen großteil der> statischen DEFINES in einem typeischen C Programm weg bringen und das> ganze dazu noch kapseln.>> z.B. hab ich Filterklassen als Template realisiert denen man bei der> Instantierung nur die Ordnung und die Charakteristik mitgibt. Der vom> Compiler erzeugte Code ist dann exakt genau so schnell wie C code. Der> Vorteil ist das man mit einer Zeile einen neuen Filter hat - ganz ohne> Code Duplication, globale Variablen, Precompiler DEFINES usw... sauber> halt.>> Es geht natürlich auch mit C genau so gut. Nur halt umständlicher und> nicht so sauber, aber für nicht C++ affine Programmier halt dafür viel> verständlicher...
Und genau darum geht es doch.
Conny G. schrieb:> Ich finde die Stossrichtung diese Dinge mal objektorientiert zu> betrachten hervorragendThomas W. schrieb:> Ich finde den OOP Gedanken sehr schick.
Viele (die meisten) Programmierer würden wohl unterschreiben, daß man
die Dinge möglichst einfach halten sollte, verfallen aber dann doch dem
intellektuellen Reiz, das OOP Prinzip Problemstellungen jedweder Art
überzustülpen. Die OOP Sichtweise hat offensichtlich geradezu was
Ideologisches, dem sich manche nicht mehr entziehen können. Wenn
Peter Dannegger schrieb:> Ich möchte gerne folgendes erreichen: LED0 = KEY0.state; //> an solange Taste gedrückt> LED1.toggle = KEY1.press // wechsel bei jeder Drückflanke> LED2.off = KEY2.press_short; // kurz drücken - aus> LED2.on = KEY2.press_long; // lang drücken - an
würde man doch nur
- in einem zyklischen Timerinterrupt vorhandene Keys ein paarmal
abfragen und einen über mehrere Zyklen festgestellten gleichen Zustand
als aktuellen für Key X zuweisen (Entprellung)
- mit dem Wechsel des aktuellen Zustands von Key1 LED1 toggeln
- mit den über im selbigen Timerinterrupt feststellbaren verschieden
langen Key2-ON Statusperioden LED2 aus- und einschalten.
Was zum Teufel muß einen reiten, über die Codierung dieser klar
begrenzte Funktionalität hinaus Gedankenakrobatik mit
unterschiedlichsten OOP Konstrukten zu betreiben?
Moby schrieb:> Was zum Teufel muß einen reiten, über die Codierung dieser klar> begrenzte Funktionalität hinaus Gedankenakrobatik mit> unterschiedlichsten OOP Konstrukten zu betreiben?
Auch wenn ich wenig Hoffnung habe, daß du es irgendwann einsehen willst,
noch ein Versuch: es gibt auch Programme, die über Pinwackeln
hinausgehen.
Ein Programm kann durchaus mehrere Ebenen haben, die man intelektuell zu
erfassen hat (siehe z.B. OSI - 7 Schichten, oder Anwendung - USB-Stack -
HW-Ansteuerung).
Wer in einem Programm ab einer gewissen Komplexität (a) Code, der die
Logik eines Webservers abbildet, mit (b) Code, der IO-Pins setzt,
vermatscht, programmiert ziemlichen Mist.
Das ist Pfusch und keine SW-Entwicklung.
Insofern ist es durchaus sinnvoll, Sachen, die nicht zusammengehören,
auseinander zu ziehen.
Assembler ist jetzt halt geeignet, Prozessorbefehle auszudrücken.
Wenn das Programm aus nicht wesentlich mehr besteht, ist das vollkommen
in Ordnung. Deine "klar begrenzte Funktionalität" ist aber nicht immer
die ganze Welt, sondern hier nur ein Beispiel.
Mit ABS und ESP nur in Assembler geschrieben würde ich mich nicht
anfreunden wollen, wenn jemand irgendwas unstrukturiert hinrotzt.
Für die darüber liegenden Ebenen gibt es einfach bessere Sprachen.
Ob das jetzt C++ ist oder was anderes, steht wieder auf einem anderen
Blatt.
Die ursprüngliche Frage geht jetzt halt nun dahin, wo die Grenze
sinnvollerweise zu ziehen ist und wie man sie gestalten könnte.
Daß du die Frage nicht verstanden hast, weiß inzwischen jeder. Belass es
doch einfach dabei, bevor es noch peinlicher wird.
Mag sein, daß du nur Probleme hast, bei denen deine Arbeitsweise passt.
Mag auch sein daß deine Probleme nicht zu dieser Arbeitsweise passen, du
aber totzdem so arbeitest. Das tut hier aber alles nichts zur Sache.
Hier geht es darum, wie man es auch anders machen könnte.
Also für Leute, die auch mal etwas weiter schauen wollen...
PSOC geht ja so ein bisschen den Weg Hardware von eigentlichem Code zu
trennen. Die Hardware zeichnet man, stellt dort alles ein was so ein Pin
soll und den Rest in Software.
Was ich hier überhaupt nicht verstehe und für mich wieder zeigt wie
viele hier mehr reden als wirklich machen, da doch immer mehr die
Cortexe bevorzugt werden, mit ihren viel höheren Geschwindigkeiten und
mehr Speicher, wieso dann nicht auch gleich die Programmiersprache mit
wechseln?
Auf jeden Fall habe ich schon mal wieder das C++ Buch aus dem Regal
genommen.
Klaus Wachtler schrieb:> sondern hier nur ein Beispiel
Nein nein Klaus- nicht "nur" ein Beispiel.
Wohin man auch schaut wenn man es denn konkret tut: Überall dieselben
"Beispiele"! Und nein, wir reden hier nicht von einem Webserver oder
einem ausladenden SAP-Programm, sondern ganz konkreten
Peter Dannegger schrieb:> MC-spezifische Abläufe ... (IO-Zugriffe, Statemaschine usw.)
Die liegen allermeistens immer noch unter
Klaus Wachtler schrieb:> Programm ab einer gewissen Komplexität
was Du hier offensichtlich meinst.
Klaus Wachtler schrieb:> es gibt auch Programme, die über Pinwackeln> hinausgehen.
Tatsächlich?
Dann weißt Du aber auch, daß sich vielschichtigere Programme beileibe
nicht nur mit OOP-Zauberspuk, sondern ganz klassisch über ein
Interruptsystem realisieren lassen. Das hat nämlich meist den Vorteil,
kürzer, schneller, punktgenauer und übersichtlicher zu sein. Man nutze
nur die Ressourcen seines Controllers, dann klappt es vielleicht auch
mit der Reduktion vermeintlich nötiger Ebenen.
F. Fo schrieb:> wieder das C++ Buch aus dem Regal
Oh da oben stehen bei mir auch noch welche...
Leider haben sie bislang nur Zeit gekostet, fürs E-Hobby aber nix
gebracht ;-(
Bei soviel Kritik an der Idee muss ich doch nochmal was dazu schreiben.
Mit einer mutigen Vision entstehen oft hervorragende Dinge.
Wenn man sich vorab denkt "wer braucht das?", dann wird man sich nie auf
den Weg machen eine neue Kategorie von Lösung zu erforschen.
Schaut "node.js" - da hab ich mir vor ein paar Jahren gedacht - so ein
Quatsch, wer braucht Javascript auf dem Server.
Und inzwischen ist es signifikant populär und ich hab selber node.js für
ein Projekt am Laufen, weil es das nur so gibt. Hätte ich nicht gedacht.
Oder Java vor 20 Jahren - "wer braucht eine hardwareunabhängige
Programmiersprache für Geräte??". Ich arbeite fast nur noch mit Java.
D.h. das Argument "braucht man nicht, wenn ..." (oder "Hammer immer
schon so gemacht") ist kein Innovationstreiber :-)
(V.a. was soll denn das bringen, diejenigen, die diese Idee verfolgen
Niederzuargumentieren und Ihnen den Mut zu nehmen? Innovation bremsen?)
Irgendein Wahnsinniger (oder eine Gruppe von Wahnsinnigen) muss einfach
damit voran machen und die anderen überzeugen.
Wem's nicht gefällt der braucht ja nicht mitmachen.
Es gibt sicher auch einen großen Unterschied zwischen
Hobby-Eletronikern, die moderat viele Dinge umsetzen, deren Komplexität
meist begrenzt bleibt und die auch nur wenig Zeit haben sich mit der
"Schönheit" der Lösung zu beschäftigen, und beruflichen
Mikroprozessor-Lösungs-Entwicklern, die ihre Softwarearbeit skalierbar
gestalten wollen.
Und aus der Ecke scheint PeDas Frage zu kommen:
zuviele Lösungen, die alle in sich die Gefahr tragen mal einen Pin im
Code zu verwechseln etc.
Wenn man das und weitere Komplexität hübsch in OOP einpacken kann,
sodass die Definition der Objekte bereits Fehler verhindert (strenge
Typisierung) und das Ganze einfach übersichtlich macht (Kapselung), dann
ist viel Debug-Zeit erspart und es wird auch pflegbarer und teamfähiger.
Jetzt Schluss mit Infragestellung von PeDas Frage und weiter an der
Lösung!!
Conny G. schrieb:> Erinnerung, dass dies kein Ideologie-Thread ist, sondern die konkrete> Frage von PeDa, ob und wie man seine Fragestellung in C++ lösen kann.
Reichen die bisherigen Beiträge denn WIRKLICH nicht aus, diese Frage
final geklärt zu haben???
Nein?
Dann gebe ich hier mal zwei Antworten unterschiedlicher Art:
1. die Flapsige: Nimm einen C++ Compiler und gib ihm die bisherigen C
Quellen zum Übersetzen. Dann kann man sich rühmen, C++ benutzt zu haben.
2. die Eigentliche: Setze dich hin und versuche zu allererst, deine
Gedanken zu strukturieren. Genau DAS hat nämlich an der eigentlichen
Fragestellung gefehlt. Im Grunde ist es nämich scheissegal, welche
Programmiersprache man zum Erstellen seiner µC Firmware benutzt,
stattdessen ist es nur wichtig, wie man die gewünschte Funktionalität
strukturiert. Ob man da als Geradeausprogrammierer alle Ebenen der
Firmware durcheinanderschmeißt und partout sowas wie Peter anvisiert:
Peter Dannegger schrieb:> Ich möchte gerne folgendes erreichen:> LED0 = KEY0.state; // an solange Taste gedrückt> LED1.toggle = KEY1.press // wechsel bei jeder Drückflanke> LED2.off = KEY2.press_short; // kurz drücken - aus> LED2.on = KEY2.press_long; // lang drücken - an
oder ob man sich Gedanken über sinnvolle Hardwarekapselung - verbunden
mit einer firmwareinternen HAL macht, hängt eben davon ab, ob man
gewillt ist, zu allererst mal seine Gedanken zu strukturieren.
Apropos HAL: Damit meine ich NICHT solche eher blödsinnigen
Möchtegern-Kapselungen wie PortX.SetzeBit(2), sondern was Echtes, also
derart, daß die konkrete Hardware im Interface zum übergeordneten
Programm überhaupt nicht mehr auftaucht. Wer jetzt schreit "Ich brauche
aber PortX und dort Bit 2 in meiner Routine!", hat den Sinn einer HAL
nicht verstanden und seine Firmware schlichtweg falsch oder garnicht
strukturiert.
Fazit: Peters Vorstellung, so etwa wie das oben Zitierte in C++
formulieren zu wollen, ist m.E. inhaltlich nicht sinnvoll. Deswegen
mein Rat, so etwas bleiben zu lassen und stattdessen das gesamte Thema
völlig anders anzugehen:
Tastaturtreiber --> Events --> Eventverteilung im System --> eigentliche
Funktionalroutinen --> Signalisierungsroutinen.
W.S.
W.S. schrieb:> sondern was Echtes, also> derart, daß die konkrete Hardware im Interface zum übergeordneten> Programm überhaupt nicht mehr auftaucht.
Ich glaube das ist ja sein Ziel. So habe ich das verstanden.
Wenn du das schon gemacht hast, dann stelle doch mal hier was vor!
Also ich hab die letzten Tage mal weiter drüber nachgedacht. Es gibts ja
letztlich 2 Ebenen, die man zu betrachten hat.
1) Low-Level. Also das, was man sieht wenn man in die
Peripherie-Treiber-Schicht reinschaut. Hier ist wohl kaum mit
OOP-Techniken irgendetwas zu gewinnen.
2) High-Level. Also das, was man mit dem Low-Level-Teil macht. Hier
hängt es doch sehr stark von der Applikation ab, wie man ihn umsetzt.
Wenn man riesige dynamische Gebilde hat, kommt vielleicht OOP in Frage,
C++ könnte einen hier unterstützen. Vielleicht aber auch nicht.
Ich habe mich dieses Wochenende nun mal zum Spaß auf Part 1
konzentriert, und wie C++ hier vielleicht für übersichtlicheren Code
sorgen kann. Meine Zielhardware ist STM32F4 und was mich immer gestört
hat, waren die Textwände, die die Register initialisieren. Ich habe mir
deshalb den GPIO-Teil herausgesucht, weil der wohl das erste ist, womit
man in Berührung kommt. Herausgekommen ist eine Template-"Bibliothek",
die folgendes zulässt:
Ich bitte die Formatierung zu entschuldigen, für das Forum währen ein
paar Newlines vielleicht praktischer.
Ziele waren mit absteigender Priorität:
1) Der User-Code soll nur noch das beinhalten, was von Belang ist.
Nämlich was die Pins bedeuten, und wie sie eingestellt werden sollen.
2) Vertretbarer Template-Aufwand
3) Qualität des Ausgespuckten Assembler-Codes
Die einzelnen Configure-Aufrufe erzeugen mit -O3 so ziemlich perfekten
Assembler-Code mit meiner Toolchain. Allerdings ist es natürlich so,
dass sie nur innerhalb des Aufrufs Registerzugriffe zusammenlegen
können. Wer das Optimum für seine Plattform und seine Applikation
herausbekommen will, muss in solchen Aspekten aber sowieso von der
Modularisierung Abstand nehmen, weil die einzelnen Teile eben Ressourcen
auf Hardwareebene teilen. Hier wäre eine externe Applikation hilfreich,
der man sagt was man wie verwendet und die dann (meinetwegen auch ASM-)
Code generiert, der die optimale Initialisierung durchführt. Gibt's
sowas schon für AVR/STM32?
Ich weiß nicht, ob ich die entstandene Bibliothek jetzt weiterpflege und
verwende, aber ich hatte auf jeden Fall große Freude, das zu entwickeln
;) Ich finde, der User-Code braucht den Vergleich mit dem ihm
entsprechenden STM-Hal-Code/Direkten Registerzugriffen nicht zu
fürchten.
Ich habe außerdem ein Amulett mit +500 Widesrtand auf Troll, also spart
es euch gleich ;)
Ich denke man sollte erstmal wissen man man möchte.
Wie weit will man abstrahieren ?
Die Pins von unseren Kontrollern sind doch nicht nur GPIOs sondern auch
UART, SPI, I2C, ADC...
Das muss doch von Anfang an bedacht werden.
Für eine Tastenentprellung macht es doch Sinn wenn wenn dem
"Entprellobjekt" ein oder mehrere, vorher initialisiertes, "GPIO
Objekte" übergeben wird.
Über die Läuft der Zugriff.
Jetzt braucht es immer noch einen Timer bei dem eine entsprechende
Methode registriert ist.
Für sowas kenne ich nur SIGNAL und SLOT aus QT.
IMHO werden die durch dem MOC in CallBacks umgewandelt.
Wie würde es sinnvollerweise bei UART, SPI, I2C ADC aussehen ?
Tastenentprellung ist eine reine Softwaresache sowie
Software UART, Software SPI, Software I2C.
Allerdings wird das bei den Hardware Features schon schwieriger.
Immerhin muss sich das entsprechende Objekt die GPIOs "reservieren".
Dazu muss es jemanden geben der die Eigenschaften der Pins und Ports
kennt.
Das weiss das GPIO Objekt nicht, denn dessen Klasse ist ein allgemeines
Objekt.
Bei der Instanziierung muss ihm das erst beigebracht werden.
1
GPIOgpioLED(portA,Pinnummer5,Datenrichtung,...);
2
LEDled(&gpioLED);
3
4
led.on();
5
led.off();
6
led.Blink(500ms);
7
...
Bei meinen Programmen versuche ich stark zwischen der "Logik" und dem
ansprechen der Hardware zu trennen.
Leider kann man das in meinem Beispiel noch nicht so gut sehen.
(sehr heiss gestrickt, eigentlich alles passiert in der ISR)
Bei anderen (fertigen) Projekten würde jetzt hal.h separat inkludiert
und nur auf die bekannten (sozusagen public) Funktionen zugegriffen
werden können.
Somit habe ich eine starke Trennung von der Hardwareansteuerung und der
eigentlichen Programmlogik bzw. hier beim Impulse verlängern.
Aber ich muss für jedes neue Projekt so eine hal.h schreiben.
Das nimmt einen grossteil meiner "Programmierzeit" in Anspruch.
Auch das riesen Struct kostet Zeit.
Für jedes Einspritzventile und jeden Eingang muss ich das "HAL" Struct
händisch erweitern, initialiseren...
Bei einer OOP Umsetzung, würde ich mir erhoffen, nur die GPIOs an die
Injektor und "Sensing" Objekte zu übergeben.
...und noch irgendwie die Sensing mit dem entsprechenden Injektor Objekt
zu "verheiraten" und beim Timer zu registrieren.
Es wäre für mich dann ein bisschen "egaler" wieviele Zylinder ich
befeuern muss.
Auch wird es entsprechend aufgeräumter.
Bei Änderungen muss ich, wenn ich es richtig erdenke, an weniger Stellen
nachbessern.
Stimmt das nicht ?
Fuer das High-Level Design der OOP gibt es UML.
Ich habe versucht mein Programm weiter oben in ein Diagramm zu fassen.
Bitte entschuldigt die etwas duerftige Qualitaet, es ist auf dem Pad
handgezeichnet.
Thomas W. (wagneth) schrieb
>Jetzt braucht es immer noch einen Timer bei dem eine entsprechende Methode
registriert ist.
Den Timer habe ich oben der Einfacheit halber durch eine While-Schleife
realisiert.
1
while(1)
2
{
3
key1.update();
4
key2.update();
5
key3.update();
6
7
delay(100);
8
}
Man koennte natuerlich auch eine richtige Timerklasse bauen, das haette
das Beispielprogramm unnoetig verkompliziert.
Chris,
ja das funktioniert.
Im Prinzip ist das EventDriven.
Ich möchte aber nicht im MainLoop warten.
Zumal die Laufzeiten/Verzögerung abhängig von den Laufzeiten sind.
In meiner ISR ist wenigstens der Start der Verarbeitung zu einem fixen
Zeitpunkt gegeben.
Irgendwie muss ein Automatismus her bei dem man einen Handler
registrieren kann...
>Es muss doch irgendwie ein GPIO Objekt o.ä. an Dein Key Objekt übergeben>werden, oder ?
Ich weiß noch nicht so recht. Der ursprüngliche Vorschlag von Peter war
ja, dass ein Key-Object in die Informationen über den Pin schon hat.
In meiner Umsetzung oben wäre das die Zeile mit
uint8_t keyPin;
1
#define PRESSED 0
2
#define TOOGLE 1
3
#define SHORTLONG 2
4
5
classKey{
6
public:
7
uint8_tkeyPin;
8
Led*oneLed;
9
uint8_taction;
10
uint8_toldKey;
11
uint8_tkeyToogleState;
12
13
Key(){
14
keyToogleState=0;
15
};
16
17
Key(uint8_tp){
18
keyPin=p;
19
pinMode(keyPin,INPUT);
20
21
keyToogleState=0;
22
};
23
24
voidaddKeyPressedListener(Led*led){
25
oneLed=led;
26
action=PRESSED;
27
};
28
voidaddKeyToogleListener(Led*led){
29
oneLed=led;
30
action=TOOGLE;
31
};
32
voidaddShortLongListener(Led*led){
33
oneLed=led;
34
action=SHORTLONG;
35
};
36
37
voidupdate()
38
{
39
uint8_tk=digitalRead(keyPin);
40
if(k!=oldKey)keyToogleState=!keyToogleState;
41
oldKey=k;
42
43
switch(action)
44
{
45
casePRESSED:{
46
oneLed->set(k);
47
};break;
48
49
caseTOOGLE:{
50
oneLed->set(keyToogleState);
51
};break;
52
53
caseSHORTLONG:{
54
55
};break;
56
default:{}break;
57
}
58
59
};
60
};
Wenn ich es richtig verstehe, möchtest Du das Konzept etwas erweitern
d.h. dem Key-Objekt verschiedene Quellen zuordnen können anstatt nur
IO-Pins.
Thomas W. schrieb
>ja das funktioniert.>Im Prinzip ist das EventDriven.>Ich möchte aber nicht im MainLoop warten.>Zumal die Laufzeiten/Verzögerung abhängig von den Laufzeiten sind.>In meiner ISR ist wenigstens der Start der Verarbeitung zu einem fixen>Zeitpunkt gegeben.
Eine While(1)-Loop in Main war auf die Schnelle so am einfachsten zu
realisieren. Man könnte die Key.update() Funktionen auch in die
ISR-hängen.
1
ISR...
2
{
3
key1.update();
4
key2.update();
5
key3.update();
6
}
Ich persönlich bin ein Fan von Interrupt-freien Programmen, weil sich
Interrupts auch gerne mal "verklemmen" wenn man beim Programmieren nicht
aufpasst.
Lange Zeit war die OOP für mich ein Buch mit sieben Siegeln, weil ich
noch aus der Zeit der Homecomputer Ära stamme. Dass ich es nicht
verstanden habe hat mich aber so geärgert, dass ich mich intensiver
damit befasst habe und einige Programme damit geschrieben habe. Um so
länger man es tut, um so klarer wird die Philosophie der OOP.
Zunächst muss ich sagen: wer es wirklich lernen will, sollte nicht mit
C++ sondern eher mit Python anfangen. Damit verschont man sich von den
ganzen Fehlern die C++ als zusätzliches Feature anbietet.
In der OOP gibt es das Prinzip, dass eine Klasse einer anderen eine
Nachricht schicken kann. Ich habe mich lange Zeit gefragt: Wie muss man
das konkret programmieren? Wie kann ein Klasse einer anderen eine
Nachricht schicken.
Oben im Beispielprogramm kann man das sehr gut sehen. In der Klasse Key
ist ein Leerpointer für eine LED eingebaut. Man muss einen Key
initialisieren, in dem man dem Key den Pointer auf eine LED mitgibt.
Danach kennt der Key die LED und kann ihr jetzt "Nachrichten" senden,
indem er einfach eine Funktion in der LED aufruft.
Peter Dannegger schrieb:> Schön wäre es noch, wenn die Klasse automatisch den passenden> Interrupthandler aktiviert.
Auf Plattformen wie AVR und CMSIS sind die Namen der Interrupt-Handler
in C Konvention fest vorgegeben und leiten sich von Devicenamen wie
UART0 ab. Zudem benötigt eine Handler als Teil einer Klasseninstanz den
Pointer auf diese Instanz.
Die Zuordnung eines vom Entwicklungsssytem vorgegebenen Handlers zur
passenden Funktion einer Instanz muss also unweigerlich von Hand
erfolgen, als Wrapper-Funktion um den eigentlichen Handler, mit den
Namen des Handlers. Darin wird dann über den Pointer auf die Instanz der
eigentliche Handler aufgerufen, beispielhaft:
extern "C" {
void UART0_Receive_Buffer_Full(void) // offizieller Name
{
UART0object->Receive_Buffer_Full(); // C++ member funktion
}
}
chris_ schrieb:> Ich persönlich bin ein Fan von Interrupt-freien Programmen
Ein wesentliches Feature von MC einfach mal über Board werfen? Naja.
Wenn man auf den Stromverbrauch achten muss, kommt man um Interrupts
nicht herum. Und auch sonst nützen und vereinfachen die mehr, als das
man sie vermeiden müsste.
A. K. schrieb:> Peter Dannegger schrieb:>> Schön wäre es noch, wenn die Klasse automatisch den passenden>> Interrupthandler aktiviert.>> Auf Plattformen wie AVR und CMSIS sind die Namen der Interrupt-Handler> in C Konvention fest vorgegeben und leiten sich von Devicenamen wie> UART0 ab. Zudem benötigt eine Handler als Teil einer Klasseninstanz den> Pointer auf diese Instanz.>> Die Zuordnung eines vom Entwicklungsssytem vorgegebenen Handlers zur> passenden Funktion einer Instanz muss also unweigerlich von Hand> erfolgen, als Wrapper-Funktion um den eigentlichen Handler, mit den> Namen des Handlers. Darin wird dann über den Pointer auf die Instanz der> eigentliche Handler aufgerufen, beispielhaft:>> extern "C" {> void UART0_Receive_Buffer_Full(void) // offizieller Name> {> UART0object->Receive_Buffer_Full(); // C++ member funktion> }> }
Je nachdem wie spendabel man mit Taktzyklen im Int sein möchte könnte es
über eine Int-Handler Registry erfolgen.
Es gibt ein Objekt "IntDispatcher" das den Keys bekannt ist und sie
registrieren sich dort mit "intDispatcher.register(&intHandler)".
Und der eigentliche Interrupt ruft grundsätzlich
intDispatcher->Timer0OvfInt() auf, der sich dann um seine Kunden
kümmert.
Alternativ gibt es eine IntTimer0OvfHandler-Basisklasse von der Klassen
wie die Key-Klasse erben und automatisch einen Handler für den Int
haben, der dann mit aufgerufen wird.
chris_ schrieb:>>Ein wesentliches Feature von MC einfach mal über Board werfen?>> Guck mal, hier gibt es einen sehr innovativen Controller ganz ohne> Interrupts:> http://www.parallax.com/microcontrollers/propeller
Ja, der löst den Bedarf an Ints alternativ mit 8 Cores, d.h. statt
Interrupts die den laufenden Code unterbrechen kann ein Core gemütlich
auf ein Ereignis warten. Finde den auch sehr cool.
Conny G. schrieb:> Ja, der löst den Bedarf an Ints alternativ mit 8 Cores, d.h. statt> Interrupts die den laufenden Code unterbrechen kann ein Core gemütlich> auf ein Ereignis warten. Finde den auch sehr cool.
XMOS ist ähnlich, aber weit besser konstruiert.
Sowas ist aus meiner Sicht tödlich. Funktionen in einer ISR aufrufen,
ist ja schon in C verpönt. Unter günstigen Umständen macht der Compiler
da ein paar wenige Instruktionen draus, wenn er die Funktion kennt! Aber
bestimmt nicht, wenn die Funktion - irgenwo weit weg - in einer anderen
Datei als Methode einer Klasse deklariert wurde.
'key1.update();' sieht ja wenigstens noch wie ein Funktionsaufruf aus.
Aber bei 'Beleuchtung.Led1 = an' können sich jede Menge Funktionsaufrufe
dahinter verbergen.
Ralf G. schrieb:> Funktionen in einer ISR aufrufen, ist ja schon in C verpönt.
Ach? Warum? Weil das dir vertraute Interrupt-System keine Priorisierung
kennt, somit kein Interrupt länger als 10 Takte dauern darf?
Das:
Conny G. schrieb:> Alternativ gibt es eine IntTimer0OvfHandler-Basisklasse von der Klassen> wie die Key-Klasse erben und automatisch einen Handler für den Int> haben, der dann mit aufgerufen wird.
läuft dann auf sowas:
Conny G. schrieb:> Es gibt ein Objekt "IntDispatcher" das den Keys bekannt ist und sie> registrieren sich dort mit "intDispatcher.register(&intHandler)".> Und der eigentliche Interrupt ruft grundsätzlich> intDispatcher->Timer0OvfInt() auf, der sich dann um seine Kunden> kümmert.
hinaus, da die ISR nicht an eine Klasse gebunden werden kann. Also wenn,
dann nur mit Tricks (Funktionszeiger). (Vielleicht auch nur in der
jetzigen Konstellation?)
Wenn sich jetzt eine Klasse (über Ihre Basisklasse) sozusagen bei der
Timer-ISR anmeldet, um von ihr später mal benachrichtigt zu werden, wenn
'was anliegt', dann muss das ja in einer Liste vermerkt werden. Die
Liste wird in der ISR dann abgearbeitet. Wenn's gut läuft, reicht das
vielleicht, der benachrichtigten Klasse nur einen Wert zu übergeben.
Ohne Funktionsaufruf! Wenn nicht, ...
Billig ist's auf jeden Fall nicht (Speicher/ Rechenzeit).
A. K. schrieb:> Ach? Warum? Weil das dir vertraute Interrupt-System keine Priorisierung> kennt, somit kein Interrupt länger als 10 Takte dauern darf?
Vielleicht. Kann sein.
Nur irgenwann ist trotzdem Schicht im Schacht.
Bei 'C++ auf einem MC, wie geht das?' denke ich nicht an 'Einen' (jeder
sucht sich seinen raus und alle reden aneinander vorbei) sondern an
'Alle' im Sinne von universell.
Ralf G. schrieb:> Billig ist's auf jeden Fall nicht (Speicher/ Rechenzeit).
Deshalb schrieb ich auch, je nachdem wie spendabel man für Komfort sein
will.
Am Ende ist das manuelle Mappen von ISR nach Methode auch akzeptabel.
Wenn man will könnte man es in einen Mechanismus packen.
Dann läuft man aber wiederum Gefahr in die
Arduino-Vollkasko-Bequemlichkeit zu kommen, die dann richtig viel
Resourcen kosten kann.
D.h. der Knackpunkt an einer cleveren OOP-Implementation muss sein:
Strukturierung und Fehlervermeidung, nicht alleine Komfort.
Und da wäre diese Vorgehensweise (Automatismus für ISRs) schon an oder
vielleicht sogar schon über der Grenze des Sinnvollen.
Denn über die genaue ISR-Architektur muss man sich als Entwickler
unbedingt Gedanken machen, das ist gefährlich, wenn ein Framework einem
suggeriert es sei alles easy.
A. K. (prx) schrieb:
Conny G. schrieb:
>> Ja, der löst den Bedarf an Ints alternativ mit 8 Cores, d.h. statt>> Interrupts die den laufenden Code unterbrechen kann ein Core gemütlich>> auf ein Ereignis warten. Finde den auch sehr cool.> XMOS ist ähnlich, aber weit besser konstruiert.
Alle Jahre wieder, siehe auch Abdul's Frage: "Was wurde aus .."
Beitrag "XMOS jemand?"
;)
Finde die Diskussion auch interessant.
Ich würde bis hierher mal festhalten:
OOP mittels C++ auf Mikrocontrollern geht und macht Sinn. Dass es die
Fehlerquellen reduzieren und leserlichen Code produzieren kann bei
gleichzeitig geringerer Einarbeitungszeit sieht man am Beispiel des
Arduino (hier blende ich mal die typischen Anfängerfragen aus, die nicht
dem Arduino-System verschuldet sind sondern schlichtweg aus Unwissen
oder Faulheit des Fragenstellers resultieren).
Die Probleme der Arduino-Libs (Laufzeit, Ressourcen) könnte man lösen,
wenn man sich mal hinsetzt und gründlich Gedanken macht. Das Beispiel
oben mit dem Portsetzen (was in einem sbi resultiert) beweist dies.
Ret schrieb:> A. K. (prx) schrieb:> Conny G. schrieb:>>> Ja, der löst den Bedarf an Ints alternativ mit 8 Cores, d.h. statt>>> Interrupts die den laufenden Code unterbrechen kann ein Core gemütlich>>> auf ein Ereignis warten. Finde den auch sehr cool.>> XMOS ist ähnlich, aber weit besser konstruiert.>> Alle Jahre wieder, siehe auch Abdul's Frage: "Was wurde aus .."> Beitrag "XMOS jemand?"> ;)
Danke für die Inspiration, hab mir grad ein StartKit bestellt. Habe
gerade eine Aufgabenstellung, wo der Propeller eigentlich gut passen
würde, aber genau dort seine Schwäche hat (RAM-Daten-Transfer zwischen
COGS).
Mal gespannt, ob das mit dem Xmos besser ist.
chris_ schrieb:> Guck mal, hier gibt es einen sehr innovativen Controller ganz ohne> Interrupts:> http://www.parallax.com/microcontrollers/propeller
okay, aber das ist nun nicht gerade ein Feld-Wald-und-Wiesen
Controller...
Auf anderen Controllern habe ich überlicherweise nur einen Core, und
entweder ich polle andauernd alle I/Os oder ich benutze IRQs.
Klar, der Propeller hat eine sehr spezielle Architektur. Bei anderen
Prozessoren gibt es viele Anwendungsfälle, in denen Interrupts
unerlässlich sind wie z.B. Treiber mit Buffer für die serielle
Schnittstelle.
Ich meine zu erkennen, dass das Beispiel von SisyAvr oben auch keine
Interrupts benutzt.
Hier sieht man, dass sie in der Basisklasse vordefinierte
Timerfunktionen haben, die sie bei Bedarf überschreiben:
http://www.avr-cpp.de/doku.php?id=timer
@Chris:
...und wo ist in Deinem Bildchen der Controller ?
(damit meine ich seine Pins)
Abstrahiert hat man doch folgendes Szenarion :
- Entprellroutine muss regelmässig aufgerufen werden.
- Entprellroutine muss irgendwo was machen, vielleicht Dein MyObjekt...
- MyObjekt triggert das LED Object. Dieses Entscheidet was am GPIO
passieren soll.
- GPIO Object ist das unterste Bindeglied.
Das Einzige was pollt ist die Entprellroutine.
Weil sie es muss.
Alle anderen Objecte arbeiten event getriggert.
Kürzer geht es nicht.
In Deinem Bild sehen sich auch noch irgendwie
LED und Entprellung / KEY.
Wofür ? Das wäre wieder komplexität.
Ich denke es ist so wie es oben BastiDerBastler beschrieben hat:
>1) Low-Level. Also das, was man sieht wenn man in die>Peripherie-Treiber-Schicht reinschaut. Hier ist wohl kaum mit>OOP-Techniken irgendetwas zu gewinnen.>>2) High-Level. Also das, was man mit dem Low-Level-Teil macht. Hier>hängt es doch sehr stark von der Applikation ab, wie man ihn umsetzt.>Wenn man riesige dynamische Gebilde hat, kommt vielleicht OOP in Frage,>C++ könnte einen hier unterstützen. Vielleicht aber auch nicht.
Ich wollte mich mit Punkt 2 beschäftigen, oben wurden ja schon ziemlich
effiziente Methoden der Hardwareabstraktion gefunden.
Thomas W. schrieb
>...und wo ist in Deinem Bildchen der Controller ?>(damit meine ich seine Pins)
Zugegebenermaßen ist mein Bildchen dahingehend etwas irreführend, weil
ich ja in Wirklichkeit gar keine Klasse habe, die sich Main nennt. Es
ist einfach die Main-Loop des Arduino:
1
voidloop(){
2
3
Ledled1(12);// led on x
4
Ledled2(13);
5
Ledled3(11);
6
7
Keykey1(4);
8
Keykey2(5);
9
Keykey3(6);
10
11
key1.addKeyPressedListener(&led1);
12
key2.addKeyToogleListener(&led2);
13
key3.addShortLongListener(&led3);
14
15
16
while(1)
17
{
18
key1.update();
19
key2.update();
20
key3.update();
21
22
delay(100);
23
Serial.println(key2.keyToogleState);
24
}
25
}
Wie man sieht, werden die LEDs und KEYs in der "loop" angelegt.
Deshalb verwende ich auch das die "Komposition" in meiner Handzeichnung,
weil die LEDs und KEYs mit der Erzeugung der Main-Loop entstehen:
http://wiki.adolf-reichwein-schule.de/images/8/8f/Uml-assoziationen03.png
Für das Bekanntmachen der LEDs bei den Tastern verwende ich die
Aggregation, weil beide unabhängig von einander existieren.
>Abstrahiert hat man doch folgendes Szenarion :>- Entprellroutine muss regelmässig aufgerufen werden.
Das stimmt. In meinem Programm passiert das durch Key.update(), weil die
Entprellroutinen direkt im Key implementiert sind ( bzw. nur ansatzweise
implementiert sind, weil ich zu faul war und dachte das wäre
ersichtlich) . Wenn Du es lieber hast, könnten wir die Entprellroutinen
raus ziehen. Ich bin mir aber noch nicht sicher, ob das wirklich viel
bringt. Ich gehe davon aus, dass wir die Taste entprellen wollen und
nicht die serielle Schnittstelle.
Chris,
genau deswegen würde ich als LowLevel einen GPIO, SPIHardware, SPI oder
sonstwas Objekt an die nächste Instanz übergeben.
Der Trick ist doch das Du mit jedem Verschiedenen BitWackelTeil, z.b.
die LED, jedes mal das Portgewackel neu erfinden musst.
Wiederverwertbarkeit von Code ist doch auch eine Forderung an OOP.
Man kann den Handler ja auch in der ISR manuell eintragen oder
man baut ein Wrapper Objekt.
Savr oder Dein Link von oben machen das doch eigentlich schon ganz net
vor.
Wobei ich bei letzterem nicht so genau weiss wie es mit der Abstraktion
aussieht.
Bei einem LC Display musst Du gleich mehrere Pins an das Objekt
weitergeben,
man müsste das Bitgewackel wieder selbst anfassen.
Wenn es aber auf das Timing ankommt muss die nächst höhere Instanz ran.
Sehe ich das Richtig,
unbenutzte und überladene Mehtoden werden nicht mitgelinkt ?
Es wird nur das mitgelinkt, was referenziert wird. Zumindest in
optimierten Builds. Wenn eine Funktion überall ge-inlined wird, wird
auch sie nicht ins Image übernommen.
>Der Trick ist doch das Du mit jedem Verschiedenen BitWackelTeil, z.b. die LED,
jedes mal das >Portgewackel neu erfinden musst.
>Wiederverwertbarkeit von Code ist doch auch eine Forderung an OOP.
Ok, mache doch mal 4-5 konkrete Beispiele. So in der Art
"wird eine Taste gedrueckt, soll eine Led leuchten" .
Dann koenen wir darueber nachdenken, wie das umzusetzen waere.
Muss denn hinter jeder Aktion gleich eine Reaktion stehen? Die Aktionen
werden ja meist in einer ISR abgerufen und dort ist meist eh keine Zeit
um groß etwas zu rechnen, etc. Also wird in der Regel eine Flag gesetzt
oder Daten in einen Buffer geschrieben Und das ganze dann im
Hauptprogramm abgearbeitet. Ob die LED jetzt nach 1µS oder nach 50ms
leuchtet ist in der Regel egal und wird sowieso nicht wahrgenommen.
Wichtiger als OOP bis auf das letzte Bit des Controllers runter zu
bringen ist doch eine einheitliche Basis, auf der man aufbauen kann. Vom
Programmablauf ähneln sich ja die meisten Programme ja mehr oder
weniger:
- Hardware initialisieren, Systemtimer starten. In den ISR Daten in
einen Buffer schreiben und eine Flag für das Hauptprogramm setzten. Im
Hauptprogramm wird sich wenn nichts mehr zu tun ist, schlafen gelegt.
Für ein Setze_Bit braucht es noch keine Klasse, aber es wird wohl in
sehr vielen Verwendung finden. Deswegen wäre der Erste schritt eine Art
Betriebssystem zu schreiben, das definierte Funktionen zur Verfügung
stellt und die Hardware entsprechend initialisiert. Also Systemtimer,
UART, I/O,... nach defines o.ä. initialisiert. Letztenendes Alles was
direkten Zugriff auf die Hardware hat muss bereitgestellt werden.
Auf diesen Grundfunktionen können dann alle anderen Funktionen aufbauen,
ob eine Softuart Klasse, Tastenentprellen,... alle haben die gleichen
Hardwareschnittstellen und greifen auf die gleichen Funktionen zu.
Dadurch wird auch schon eine gewisse Wiederverwentbarkeit des Codes
ermöglicht. Auf einem Neuen Controller müssen halt nur genau diese
Grundfunktionen bereit gestellt werden. Aber eine Initialisierung der
Hardware ist ja so oder so nötig.
Und mal nebenbei erwähnt, bei euren Beispielen mit dem Hinzufügen zu den
ISR habt ihr einen entscheidenden Punkt vergessen, die häufigkeit des
aufrufens, nicht jede Funktion muss in jeder Timer ISR aufgerufen
werden. Ich würde sogar noch einen Schritt weiter gehen und einen Offset
einplanen damit man z.B. eine funktion nur in geraden und eine andere
Funktion nur in ungeraden Timer ISRs aufrufen kann und somit die Last
verteilung beeinflussen.
>Der Trick ist doch das Du mit jedem Verschiedenen BitWackelTeil, z.b. die LED,>jedes mal das >Portgewackel neu erfinden musst.>Wiederverwertbarkeit von Code ist doch auch eine Forderung an OOP.
Noch besser wird's, wenn zig verschiedene Klassen auf die Klasse
BitWackel aufbauen. Die brauchen dann für eine neue Wackelart nur eine
dafür vorgesehene Implementierung, d.h. statt WackelAVR ein WackelARM,
und schon Funktionieren sie auf der neuen Kiste. Zwar nur theoretisch,
aber das ist mehr als "gar nicht". Und wenn man sich beim Wackelart
Design nicht zu blöd angestellt hat, dann spielt auch die Realität mit.
Ich behaupte wir haben hier nicht nur blinkende LEDs,
in meinem nicht OOP Beispiel wird ein Signal in 100µs Schritten
abgetastet und auch wieder ausgegeben.
In der Interrupt.h wird doch der ISR Handler per Makro und
Funktionszeiger gebaut.
Das müsste doch auch mit einer Methode gehen.
Ahh ein Methodenzeiger !
So könnte man vielleicht den Wrapper los werden.
...und jetzt noch die virtuelle Handler Methode der Basis Timerklasse in
der Implementierung damit überschreiben.
Schon hat man seine eigene Timerklasse in der man in den Handler
eintragen kann was man will.
---
Eine meiner Forderungen wäre das dass Ansprechen der Hardware gekapselt
von der anderen Programmlogik ist.
Dabei würde es doch schon reichen wenn man
den Buchegger aufbläst
Beitrag "Re: C++ auf einem MC, wie geht das?"
Seine pin Klasse kann auch das DDR zum auslesen setzen...
Nach dem Stil noch die Hardwarefeatures (spi,uart...) aufgesetzt und Gut
ists.
Thomas W. schrieb:> In der Interrupt.h wird doch der ISR Handler per Makro und> Funktionszeiger gebaut.> Das müsste doch auch mit einer Methode gehen.> Ahh ein Methodenzeiger !> So könnte man vielleicht den Wrapper los werden.
Ein Memberfunktion-pointer ist aber anders als ein funktionspointer. Der
aufruf erfordert zwingend das dazugehörige object. Man müsste also eine
templateclasse nehmen, den pointer auf eine darin enthaltene statische
metode holen, welche die als templateparameter übergebene funktion auf
das ebenfalls als templateparameter übergebenen objekt aufruft.
Daniel A. schrieb:> Man müsste also eine> templateclasse nehmen, den pointer auf eine darin enthaltene statische> metode holen, welche die als templateparameter übergebene funktion auf> das ebenfalls als templateparameter übergebenen objekt aufruft.
Mit Pointern kommt man nicht weiter, wenn die Sprung/Vektorleiste für
Interrupt-Handler direkt feste Namen entsprechend C Namenskonvention
anspringt. Da müsste man schon CMSIS oder die avr-libc über Bord werfen
und eine ziemlich eigene Methode für die Vektorleiste erfinden.
Krempelt man jedoch nicht alles um, dann müsste man bei diesem Prinzip
der statischen Methode einen auf Linker-Ebene frei wählbaren
Funktionsnamen verpassen können (irgendein GCC Attribut?) und den dem
Template übergeben können. Geht das?
Bei ARM7/9 Prozessoren hat man es leichter, denn da hat man 32-Bit Werte
beliebiger Konvention in Registern des Interrupt-Controllers und könnte
sogar an Stelle einer Funktionsadresse einen Pointer auf einen Pointer
auf eine Memberfunktion dort reinschreiben.
Ich habe mal einige ideen dazu gesammelt:
* Vieleicht kann man da mit dem gcc alias attribut was drehen
* Mit extern c kann das name mangeling deaktiviert werden. Vileicht
kann man gcc ja eien ensprechend benannten funktionszeiger unterjubeln?
* Man macht ein Makro, welches erst die memberfunktion als inline
deklariert, dann in einem extern block das isr makro benutzt, in der isr
die memberfunktion aufruft, und danach die memberfunktion definiert.
Einen konkretes Anwendungsszenario hinzuschreiben, scheint gar nicht so
einfach ....
Seis drumm, im Anhang ein Bild zur Pinklasse. Wahrscheinlich ist es gut
OutPin und Innpin abzuleiten, dann kann man gleich dir
Richtungsinitialisierung einbauen.
chris_ schrieb:> Einen konkretes Anwendungsszenario hinzuschreiben, scheint gar nicht so> einfach ....> Seis drumm, im Anhang ein Bild zur Pinklasse. Wahrscheinlich ist es gut> OutPin und Innpin abzuleiten, dann kann man gleich dir> Richtungsinitialisierung einbauen.
Könnte man so noch weiter untergliedern, in die Funktion des jeweiligen
Pins.
Jetzt wären wir wieder beim Konkreten: wie soll eine Tri-State Klasse
verwendet werden? Ich habe auch schon darüber nachgedacht, mir ist aber
nichts eingefallen.
Man könnte es aber auch fast so belassen, wie es Karl-Heinz oben gemacht
hat:
http://www.mikrocontroller.net/attachment/237464/PinTest.cpp
Dem Pin eine Funktion toOutput mitgeben, mit dem er auf Output gestellt
wird. Meiner Meinung nach fehlt dort aber die Funktion toInput, bei der
das DDR wieder auf Input umgestellt werden kann. Außerdem sollte das Pin
in der Inittialisierung auf "input" gestellt werden.
Die Idee mit dem Methodenaufruf für die ISR geht nicht weil der ISR
Vector vom Linker aufgelöst wird ?
Dachte immer das wäre ein reinrassiger Funktionszeiger.
Die sind mehr als nur ein Jump auf Assemblerebene ?
(Kontext etc...)
---
Denke für Polling wird das so funktionieren.
Beim PinChange Interrupt kehrt sich die Richtung um.
In meinem nicht OOP Beispiel ist (wird wenn der entsprechende Solver*
eingebaut ist) alles nebenläufig sein.
Das könnte sich am ende beissen.
Hier wird immer eine ganze Kette aufgerufen.
Oder verrenne ich mich gerade ?
* : Der Solver wird eine multiplikative und additive "korrektur"
einbringen. Abgetastet wird mit dem ISR Takt, genauso die Ausgabe.
Gerechnet wird im Solver. (MainLoop)
PS: Hängt wohl an "meiner Betriebsart" das ich aus den vorhandenen
Modell immer alle pins Zyklisch setze, auch wenn sich nichts verändert
hat.
Diese ganzen Klassen hören sich erstmal toll an, vor allem wenn man den
Gedanken weiterspinnt und sich UART, LCD und SPI Klassen anlegt. Eine
LCD Klasse erhält noch die benötigten Pins mit übergeben (per Template
oder im Konstruktor).
Aber wie würde man vorgehen, wenn es von einer internen Hardware mehrere
Instanzen gäbe, Beispiel UART?
Ich müsste entweder das komplette Registersetup per Template/Konstruktor
übergeben, oder ich halte für jede Instanz eine eine Liste im RAM vor.
Bei letzterem bin ich mir aber nicht sicher wie effizient der Compiler
dann die Zugriffe umsetzen kann...
Man könnte die Pins und die Hardware (z.B. lcd) auch mit einem kabel
verbinden. Man hat dann irgendwo die classe lcd, und irgendwo die klasse
wire. Die klasse wire wird dann in einem eigenen file mit den pins
(welche eine eigene classe haben) und der Hardware ( classe lcd_xyz)
instanziert. Dadurch kann man dann ganz einfach auf unterschiedlichen
hardwareconfigurationen unterschiedliche verbindungen definieren.
Naja,
ich denke man sollte schon die verschiedenen Schichten stark trennen.
Niedrige schicht: GPIO, SPI, TWI --- Direkte Hardwaremanipulation.
Höhe Schicht : LCD, Schiebergister --- Übersetzt Datenstrom,
Anforderungen in LowLevel Ebene.
Höchste Schicht : Anwendung/Solver die auf die über die unteren
schichten auf die Hardware zugreifen. --- komplette höhere Logik,
Denke darunter versteht man einen Hardware Abstration Layer.
Karl Heinz schrieb:> Ich denke ein wirklich> nicht unwesentlicher Punkt wäre es, für diese Low-Level Sachen ein gut> aufgebautes Framework zu haben, welches diese ganzen Port, Pin, DDR> Sachen sauber kapselt OHNE dabei Codemässig stark aufzutragen.
Ich hab' da mal was probiert... (Siehe Anhang.)
So eine Pin-Klasse trägt überhaupt nicht auf. Das alles reduziert sich
auf ein paar Befehle.
Jetzt habe ich aber ein Problem als Hobby-Programmierer! Wenn ich nun
versuche von 'mcPin' eine Klasse 'mcKey' abzuleiten, dann fängt der
Compiler an, liederlich zu arbeiten ;-) Auf einmal wird nichts mehr
ge-inlined.
Was brauch' ich jetzt für Zaubersprüche, um dem Compiler auf die Sprünge
zu helfen?
Hallo Karl Heinz,
Karl Heinz schrieb:> Das hab ich gerade eben erst erfunden :-)> Und es ist das leidige Problem, zuverlässig aus der Portangabe die DDR> Adresse zu ermitteln.
Warum die Information nicht einfach mitnehmen? Der Optimizier will sich
ja auch nicht langweilen.
Da sind jetzt schon ein paar C++-Features (Klassen, Vererbung, Überladen
von Operatoren) drin, die das (IMHO) am Ende deutlich les- und wartbarer
machen als den entsprechenden C-Code. Und das ist meines Erachtens dann
auch das eigentliche Ziel von C++: den Code besser zu strukturieren und
dessen Wartbarkeit und Wiederverwendbarkeit zu erhöhen.
Nebenbei habe ich den Code mal mit avr-c++ -O3 übersetzt, mit avr-strip
bearbytet, jeweils einmal mit überladenem "="-Operator und einmal ohne
(und dafür [1]) in der main()-Loop. Ergebnis: in beiden Fällen ist das
Kompilat 524 Bytes groß (-mmcu=atmega328).
Um einmal zu schauen, wie groß das Kompilat ist, wenn ich genau dieselbe
Funktionalität in C implementiere, habe ich den Code aus [2] benutzt und
mit denselben Compilereinstellungen mit avr-gcc übersetzt.
Wie soll ich sagen: auf meinem Kubuntu 14.04 LTS mit avr-gcc 4.8.2 ist
auch das C-Kompilat exakt 524 Bytes groß, genau wie mit C++. Übrigens
dasselbe, wenn ich die unnötige Zeile weglasse. Scheint ja wirklich ein
böses Zeug mit einem riesigen Overhead zu sein, dieses C++. Oder so. ;-)
Wie dem auch sei: als jemand, der seine Brötchen als UNIX-Guru verdient
und daher nicht täglich Codezeilen wie "DDRB &= ~(1 << PB0)" sieht,
finde ich die C++-Variante viel les- und wartbarer als die Version in C.
Just my centz,
Karl
[1] Code:
1
if(btnPin.isHigh()){
2
ledPin.setHigh();
3
}else{
4
ledPin.setLow();
5
}
[2] Code:
1
#include<avr/io.h>
2
3
intmain(){
4
5
DDRD|=(1<<PD0);// set OUTPUT
6
DDRB&=~(1<<PB0);// unnecessary: is already an INPUT
Hallo Peter,
Peter Dannegger schrieb:> Heiliger Bimbam schrieb:>> Ist Euch die Programmierung in den gängigen Sprachen noch nicht>> kompliziert genug?>> Karl Heinz hat das ganz richtig erkannt, es geht mir vorrangig darum,> das Programmieren einfacher und sicherer zu machen.
Genau dafür ist C++ ein enorm leistungsfähiges Werkzeug. Leider werfen
die meisten, die hier Beiträge verfassen, die Konzepte wild
durcheinander und scheinen C++ nicht wirklich verstanden zu haben.
> Und C++ ist ja schon im AVR-GCC includiert, man braucht also an der> Programmierumgebung nichts zu ändern. Einfach nur *.c nach *.C> umbenennen.
Besser ".cpp" und ".hpp", dann kommen auch die Windowsleute besser klar.
Liebe Grüße,
Karl
PS: danke für Deine Libraries!
Hi Ticktacktoe,
tictactoe schrieb:> tictactoe schrieb:>> Dann können wir also z.B. einen Array mit Pointern auf die Clienten>> anlegen, damit wir die virtuelle Handler-Funktione aufrufen können.>> Vielleicht so>> ...>> Aber das kriegt man auch mit Template-Metaprogramming hin>> Ich muss hier nochmal nachhaken. Mein Post ist eine Werbung für> Template-Metaprogramming. Man muss hier aber einen Schritt zurück machen> und noch mal die Ausgangssituation betrachten. Gerade weil man auf einem> µC keine dynamischen Listen von (Uhren, Tastern...) hat, könnte man> Template-Metaprogramming verwenden (weil alle teilnehmenden Klassen zur> Compile-Zeit bekannt sind). Aber aus dem selben Grund kann man genau so> gut auch schreiben:>
1
ISR(TIMER0_OVF_vect){
2
>uhr.OVF_vect();
3
>taste1.OVF_vect();
4
>taste2.OVF_vect();
5
>}
6
>
> Wozu also das ganze Gedöns um virtuelle Funktionen (braucht man bei> dieser Form nicht) und Template-Metaprogramming? Hab' ich was an den> Anforderungen nicht verstanden?
Nein, die Anforderungen sind nicht das Problem. Das Problem ist ein ganz
anderes, viel tiefgreifenderes.
Du (und Karl Heinz und viele andere hier im Thread) habt den Sinn von
Klassen, Instanzen und vor allem Templates einfach noch nicht richtig
verstanden. Ein Template-Parameter ist ein Datentyp und wird übergeben,
damit dieselbe Operation mit verschiedenen Datentypen ausgeführt werden
kann.
Liebe Grüße,
Karl
Ret schrieb:> F. Fo (foldi) schrieb:>> Dr. Sommer schrieb:>>> Aber auch in Brainfuck. Warum verwendet niemand Brainfuck? Es ist>>> supereinfach zu lernen, verwenden und implementieren.>>> Habe mir das mal angeguckt - ich hätte fast gekotzt.>> Da kann ich dich voll verstehen! Denn mir geht es genau nicht anders.> Wer so einen hingekotzten Zeichensalat (ich hab extra nochmal> nachgeschaut) wie Brainfuck ernsthaft als Programmiersprache empfiehlt,> verarscht entweder seine Mitdiskutanten oder hat schlicht nicht alle> Tassen im Schrank. Langsam nähren sich in mir die Anzeichen, dass ich> solche Leute künftig an anderer Stelle nicht mehr für voll nehmen> sollte. Und wenn mir der gleiche Herr demnächst dann seine C++> Template-Orgien schmackhaft unter die Nase reiben will, denke ich mir> darauf, "hab Nachsicht mit ihm! Das ist einer, der auf Brainfuck steht.> Von dem kann einfach nichts Brauchbares oder Gescheites kommen".>> So kann man sich seine Glaubwürdigkeit hier auf einen Schlag verspielen.> Aber vielleicht passt auch alles gut zusammen. Wer auf Brainfuck steht,
Man kann das hier abkürzen: wer auf Brainfuck steht, ist brainfucked.
Wer auf Mikrocontrollern C++-Templateprogrammierung einsetzen will,
braucht aber auch gute Gründe. ;-)
Beste Grüße,
Karl
Karl Käfer schrieb:> verstanden. Ein Template-Parameter ist ein Datentyp und wird übergeben,> damit dieselbe Operation mit verschiedenen Datentypen ausgeführt werden> kann.
Das ist praktisch das Konstrukt, das in Java "generics" heißt?
Hallo Conny,
Conny G. schrieb:> Karl Käfer schrieb:>> verstanden. Ein Template-Parameter ist ein Datentyp und wird übergeben,>> damit dieselbe Operation mit verschiedenen Datentypen ausgeführt werden>> kann.>> Das ist praktisch das Konstrukt, das in Java "generics" heißt?
Ja, genau das.
Liebe Grüße,
Karl
Karl Käfer schrieb:> Du (und Karl Heinz und viele andere hier im Thread) habt den Sinn von> Klassen, Instanzen und vor allem Templates einfach noch nicht richtig> verstanden. Ein Template-Parameter ist ein Datentyp und wird übergeben,> damit dieselbe Operation mit verschiedenen Datentypen ausgeführt werden> kann.
Nananana..
Also ich sag's mal so: Wenn die Features einer Programmiersprache so
extrem schwer zu begreifen sind, daß "Du (und Karl Heinz und viele
andere hier im Thread)" es trotz deiner vielen Worte noch immer nicht
begriffen haben, dann taugt diese Programmiersprache nix. Schließlich
soll sie ja dem geneigten Programmierer das Leben erleichtern und ihn
nicht veranlassen, sich das Hirn zermartern zu müssen.
Aber mal konkret: Klassen sind Typen und um damit was anfangen zu
können, muß man Daten so eines Types erschaffen, auch Instanziieren
genannt. Sowas ist ganz generell eine softwareinterne Angelegenheit.
Du kannst keinen Port instanziieren, denn der ist schlichtweg da, ganz
einfach in der zugrundeliegenden Hardware DA. Du kannst mit all dem
Klassengedöns lediglich die Hardware in eine zusätzliche Softwareschicht
einwickeln - und diese Schicht kannst du dann instanziieren nach
Belieben.
Blöd ist nur, daß du je nach Hardware innerhalb der Methoden deiner
Klasse in ganz erheblichen Fallunterscheidungen ersticken wirst.
Versuche z.B. mal die Pins eines gewöhnlichen ARM Controllers
einzurichten. Ei wo ist denn nun der Satz von 2 oder 4 Bits innerhalb
eines der 9 PINSEL Register, der für das gesuchte Pin zuständig ist? Und
hat dieser Eintrag überhaupt was zu sagen, wenn an anderer Stelle ein
Peripheriecore aktiviert ist, der diesen Pin von sch aus belegt?
Mit so einem simplen AVR als Beispiel sieht die Welt noch einfach aus,
aber wollen wir hier über akademische Bleistift-Geraderückerei reden
oder über reale Dinge aus der Praxis? Vielleicht sollte man sowas ganz
am Anfang eines Threads mal klar festlegen.
Aber bei der o.g. Wrapperei geht der tiefere Sinn verloren, denn du hast
in der Hardware niemals mehrere gleichartige Objekte, die aus Sicht des
übergeordneten Programms gleichbehandelt werden können. Immerhin hat
(eigentlich) jedes verdammte Pin am Käfer seine besondere Bedeutung und
seine Funktionalität, die es von allen anderen unterscheidet. Das Pin
für das Zünden der Bombe hat wirklich eine ganz andere Behandlung
verdient als das Pin zum Einschalten der Displaybeleuchtung.
Abgesehen davon halte ich so eine Pseudo-Vereinfachung wie
if(btnPin.isHigh())
ledPin.setHigh();
else
ledPin.setLow();
für unzureichenden Mumpitz. Es wird in jedem Falle ein Unterprogramm
aufgerufen und ehe man irgendwelche allumfassenden Wrapper-Klassen
erfindet, wäre es leichter und klarer, selbiges mit ganz simplen
gewöhnlichen Unterprogrammen zu erledigen.
Das würde dann ENDLICH die eigentlich angstrebte Abstraktion bringen:
if (StartKnopfGedrueckt())
{ BeleuchtungEin();
if (GangEingelegt() || !BremseGedrueckt())
beep(); //
else
StarteMotor();
}
else
{ ZuendungAus();
BeleuchtungAus();
}
Siehste: Hier wird nicht mit popligen Bit High oder Bit Low
herumgefummelt, denn jemand, der draufschaut, weiß nicht, welchem
eigentlichen Systemzustand "btnPin.isHigh()" entspricht. Und was man mit
"ledPin.setLow()" anrichtet, steht auch nicht dabei. Sowas ist also ein
ausgesprochen mickriger Abstraktionslevel, der letztlich herzlich unnütz
ist.
W.S.
@WS:
Ich vermute ich verstehe Dich recht gut.
Das was Du beschreibst setzte ich so um.
Allerdings lasse ich in einer Timer ISR alle Ausgänge immer wieder neu
setzten.
Die eigentlichen Zustände stehen in einem grossen Struct.
Die Setter/Getter Funktionen greifen nur auf das Struct und /oder
bereiten dinge vor.
Dabei kommt sowas heraus wie : setMotor(Motor1,Zeit_in_Timertakte);
Im Logikteil Zustandsautomat Hauptschleife sieht das recht gut aus.
Mein obiges Beispiel ist unfertig und hat noch keine Setter/Getter,
aber der Grundsätzliche aufbau ist sichtbar.
Du kannst Dir sicherlich vorstellen wie sich (prozeduraler Ansatz) die
ISR aufbläht, unübersichtlich wird.
Tiefere Änderungen sind schmerzhaft !
Derzeit habe ich einen Kunden der ständig das Lastenheft ändert.
Ich glaube ich habe die ISR und Setter/Getter schon mindestens 100 mal
angeändert.
Das Projekt verschlingt mittlerweile 6kb flash, ohne Mathmatik.
Was meinst Du wieviel schreiberei sowas in prozedural ersetzen könnte ?
{
GPIOPWMGEN motor1PWM(GPIO::GPIO(PORTB,4));
GPIOPWMGEN motor2PWM(GPIO::GPIO(PORTB,6));
GPIOPWMGEN motor3PWM(GPIO::GPIO(PORTB,9));
motor1PWM.setPWM(PẀM1);
motor2PWM.setPWM(PWM2);
motor3PWM.setPWM(PWM3);
....
}
ISR (TIMER)
{
motor1PWM.handler();
motor2PWM.handler();
motor3PWM.handler();
}
Dafür braucht man aber auch eine GPIO, PWM, SPI... irgendwas Klasse.
Danach baut man die "höhere" Logik ein.
Andere Baustelle, oder ?
Hallo W.S.,
W.S. schrieb:> Also ich sag's mal so: Wenn die Features einer Programmiersprache so> extrem schwer zu begreifen sind, daß "Du (und Karl Heinz und viele> andere hier im Thread)" es trotz deiner vielen Worte noch immer nicht> begriffen haben, dann taugt diese Programmiersprache nix. Schließlich> soll sie ja dem geneigten Programmierer das Leben erleichtern und ihn> nicht veranlassen, sich das Hirn zermartern zu müssen.
Tja, das ist so eine Sache mit dem Verständnis: entweder man hat es,
oder man hat es eben nicht. Daß schon C enorm komplex ist, siehst Du
schon an den vielen Fragen und Fehlern, die hier ständig aufschlagen --
und die bisweilen sogar sehr erfahrenen C-Entwicklern passieren. Was
hattest Du denn erwartet, was herauskommt, wenn man eine komplizierte
Sprache wie C um ein komplexes Paradigma wie die Objektorientierung
erweitert? Diese Komplexität ist der Preis für die Mächtigkeit, und wenn
Du von diesen vielen neuen Features überfordert bist, spricht das nicht
unbedingt gegen die Features. Dies gilt insbesondere, da es sich bei dem
Besagten um eines handelt, das ausschließlich der Bequemlichkeit des
Entwicklers dient und somit zur besseren Wart- und Wiederverwendbarkeit
des Code beiträgt.
Du mußt das Feature ja nicht benutzen und kannst Dir gerne und jederzeit
Deine eigenen Klassen für vector_int, vector_long, vector_float und so
weiter schreiben. Ich hingegen bevorzuge eindeutig das Standard-Template
std::vector<T> und kann damit genau dieselben vector-Klassen automatisch
erzeugen lassen, die Du Dir mühselig und fehlerträchtig von Hand
zusammen basteln müßtest.
Und wenn Du Deine Aussagen einmal zuende denkst, endest Du irgendwann
bei Programmiersprachen, die dem intellektuellen Niveau solcher,
hüstel, "Argumentationen", entsprechen: Niki the robot, BASIC und
Scratch. ;-)
> Aber mal konkret: Klassen sind Typen und um damit was anfangen zu> können, muß man Daten so eines Types erschaffen, auch Instanziieren> genannt. Sowas ist ganz generell eine softwareinterne Angelegenheit.
Gut erkannt.
> Du kannst keinen Port instanziieren, denn der ist schlichtweg da, ganz> einfach in der zugrundeliegenden Hardware DA.
Ja, und nein. Ja, der Port im Sinne der zugrundeliegenden Hardware ist
bereits vorhanden, aber seine softwaretechnische Abstraktion natürlich
nicht. Oder verzichtest Du auf <avr/io.h> und programmierst direkt gegen
die Registeradressen? Auch die #defines sind letztlich Abstraktionen,
welche die Lesbarkeit und die Verständlichkeit erhöhen sollen.
Aber richtig: ein Port ist jedenfalls kein Datentyp, und seine Adresse
ist auch keiner. Genau deswegen sind, wie ich schon sagte, Templates
hier schlicht und ergreifend deplatziert und nicht das Mittel der Wahl.
Daß Du dieses Feature mißverstehst, spricht immer noch nicht gegen das
Feature, sondern nur gegen Dein Verständnis davon. Nicht schlimm: wie
wir gerade erst gesehen haben, befindest Du Dich da in sehr guter
Gesellschaft.
> Blöd ist nur, daß du je nach Hardware innerhalb der Methoden deiner> Klasse in ganz erheblichen Fallunterscheidungen ersticken wirst.> Versuche z.B. mal die Pins eines gewöhnlichen ARM Controllers> einzurichten. Ei wo ist denn nun der Satz von 2 oder 4 Bits innerhalb> eines der 9 PINSEL Register, der für das gesuchte Pin zuständig ist? Und> hat dieser Eintrag überhaupt was zu sagen, wenn an anderer Stelle ein> Peripheriecore aktiviert ist, der diesen Pin von sch aus belegt?
Du benutzt die AVR-Header für ARM-Controller? Erzähl mehr davon, das
klingt zwar wenig zielführend, dafür aber sehr interessant. Nein, im
Ernst: wer sagt, daß man für AVRs, PICs und ARMs dieselben Klassen,
dieselbe Abstraktion verwenden muß? Das ist nur Deine ganz persönliche,
naive, wenn nicht sogar böswillige Unterstellung.
Tatsächlich könnte man jedoch für ARMs, AVRs und andere Controller immer
dieselbe Schnittstelle verwenden. Was die Methode Pin::setHigh() macht,
läßt sich hinter einer simplen Abstraktionsschicht verbergen, ähnlich
wie avr/io.h die richtigen Adressen für DDRB, PINB und PORTB des
betreffenden Controllers einbindet -- je nachdem, was avr-gcc als "mmcu"
bekommt.
> Mit so einem simplen AVR als Beispiel sieht die Welt noch einfach aus,> aber wollen wir hier über akademische Bleistift-Geraderückerei reden> oder über reale Dinge aus der Praxis? Vielleicht sollte man sowas ganz> am Anfang eines Threads mal klar festlegen.
In diesem Fall sind die akademische und die praktische Betrachtung
absolut deckungsgleich: Templates übergeben Datentypen an generische
Klassen und Funktionen, Punkt. Templates sind kein alternativer
Mechanismus zur Übergabe von Variablen, Punkt. Ob Du das praktisch,
theoretisch, oder akademisch betrachtest, ist egal, es kommt immer
dasselbe dabei heraus: Templates sind hier nicht das richtige Feature,
ganz einfach.
> Aber bei der o.g. Wrapperei geht der tiefere Sinn verloren, denn du hast> in der Hardware niemals mehrere gleichartige Objekte, die aus Sicht des> übergeordneten Programms gleichbehandelt werden können. Immerhin hat> (eigentlich) jedes verdammte Pin am Käfer seine besondere Bedeutung und> seine Funktionalität, die es von allen anderen unterscheidet. Das Pin> für das Zünden der Bombe hat wirklich eine ganz andere Behandlung> verdient als das Pin zum Einschalten der Displaybeleuchtung.
Letzten Endes ist ihre Funktionalität aber exakt dieselbe: High heißt
"$Peripherie einschalten", Low bedeutet "$Periperhie ausschalten" -- und
zwar völlig ungeachtet ob $Peripherie nun der Initialzünder der Bombe
oder die Displaybeleuchtung ist. Und wenn ich in meinem Programm die
Instanz des Bomben-Pins "zuender" und die Instanz des Display-Pins
"beleuchtung" nenne, ist das am Ende schon ziemlich deutlich.
> Abgesehen davon halte ich so eine Pseudo-Vereinfachung wie>> if(btnPin.isHigh())> ledPin.setHigh();> else> ledPin.setLow();>> für unzureichenden Mumpitz.
Nein. Der Mumpitz ist nur, was Du daraus zusammenkonstruierst, um krude
Thesen zu belegen. Was ich hier gemacht habe, ist eine Verallgemeinerung
der Hardware, hier die von Input- und Output-Pins. Das sagt natürlich
noch nichts darüber, was an den Input- und Output-Pins hängt, welchen
Zuständen der daran hängenden Peripherie-Hardware isHigh() und isLow()
entsprechen, und was setHigh() und setLow() am ledPin bewirken. Das
gehört aber ganz woanders hin und ist für jedes Projekt unterschiedlich:
in einem Projekt schalte ich eine LED gegen GND, dann schaltet setHigh()
die LED ein, und in einem anderen Projekt schalte ich die LED gegen Vcc,
dann schaltet setHigh() die LED aus. Deswegen kann ich nicht
allgemeingültig eine Klasse LED festlegen, weil ich nicht
allgemeingültig sagen kann, ob on() den Pin jetzt High oder Low setzen
muß.
> Es wird in jedem Falle ein Unterprogramm aufgerufen
Nö, der Optimizer macht genau dasselbe wie beim Direktzugriff auf die
Register. Aber während Du eine sehr konkrete Funktionalität für ein ganz
bestimmtes Projekt mit einer definierten Peripherie-Hardware zeigst,
ging es bei mir um eine allgemeine Abstraktion der uC-Hardware statt um
eine singuläre, projektspezifische Hardwarebeschreibung der Peripherie.
Um also aus meiner Abstraktion des Mikrocontrollers ein richtiges
Projekt mit einer bestimmten Peripherie-Hardware zu machen, braucht es
noch einen weiteren Layer: nämlich eine Abstraktion der
Peripherie-Hardware -- die allerdings prima auf Basis meiner
uC-Abstraktion erfolgen kann.
> und ehe man irgendwelche allumfassenden Wrapper-Klassen> erfindet, wäre es leichter und klarer, selbiges mit ganz simplen> gewöhnlichen Unterprogrammen zu erledigen.> Das würde dann ENDLICH die eigentlich angstrebte Abstraktion bringen:> if (StartKnopfGedrueckt())> { BeleuchtungEin();> if (GangEingelegt() || !BremseGedrueckt())> beep(); //> else> StarteMotor();> }> else> { ZuendungAus();> BeleuchtungAus();> }
Ja, Du kannst Dir natürlich für jeden Kram eine eigene Funktion basteln,
genau wie ich mir meine Klasse. Von der Konsistenz ("StarteMotor()" und
"ZuendungAus()"?) Deines Beispielcode einmal abgesehen: was hindert mich
daran, das mit Klassen zu abstrahieren? Ach ja: nichts.
1
if(startknopf.gedrueckt()){
2
beleuchtung.ein();
3
if(gang.eingelegt()||!bremse.gedrueckt()){
4
beep();//
5
}else{
6
motor.ein();
7
}
8
}else{
9
motor.aus();
10
beleuchtung.aus();
11
}
> Siehste: Hier wird nicht mit popligen Bit High oder Bit Low> herumgefummelt, denn jemand, der draufschaut, weiß nicht, welchem> eigentlichen Systemzustand "btnPin.isHigh()" entspricht.
Vergleiche meinen Code mit Deinem. Merkst Du was? Zum Beispiel für die
Instanzen motor und beleuchtung oder für die Instanzen startknopf und
bremse? Na? Genau:
So läßt sich auf der Basis meiner Klassen eine einfache, lesbare und
trotzdem sehr flexible Abstraktion der Peripherie-Hardware durchführen.
Der Code in der Hauptschleife ist ähnlich wie Deiner, nur konsistenter,
lesbarer -- und ohne den logischen Fehler den Du gemacht hast. Diese
Abstraktionsschicht ist sehr leicht, absolut projektspezifisch und trägt
auch im Maschinencode nicht auf: das optimierte Kompilat ist nach
avr-strip 536 Bytes groß. Und jetzt? Fällt Dir auch mal was Sachliches,
Konstruktives und vielleicht sogar Kluges zum Thema ein?
Liebe Grüße,
Karl
@Karl:
Das finde ich ein gutes Beispiel.
Werden mehrere Motoren verwendet, dann wird der Vorteil von
Objektorientierung besonders deutlich:
EinAus motorLinks = EinAus(&DDRB, &PORTB, &PINB, PB1);
EinAus motorRechts = EinAus(&DDRB, &PORTB, &PINB, PB2);
...
motorLinks.ein();
motorRechts.ein();
Natürlich geht es auch so:
StarteMotorlinks();
StarteMotorRechts();
oder:
StarteMotor(pMotorParameterLinks);
StarteMotor(pMotorParameterRechts);
Aber dafür darf ich mein komplettes Programm ändern - überall, wo bisher
StarteMotor() stand.
Spätestens, wenn die Motorsteuerung komplexer wird, weil z.B. der Choke
in Abhängigkeit von der Temperatur etc. gezogen werden muß: beim
objektorientierten Ansatz ändert sich nur die Klasse Motor, im gesamten
restlichen Programm wird der Code nicht angefasst.
Ich habe lange "normales" C programmiert. Nachdem ich auf C++
umgestiegen bin, habe ich dann gemerkt, daß viele Probleme, die ich
früher mühsam von Hand mit structs etc. gelöst habe, in C++ sehr einfach
und logisch lösbar sind. Und in den allermeisten Fällen ohne negative
Auswirkungen auf Performance und Codegröße.
Viele Grüße,
Stefan
... macht nach Adam Riese maximal schnelle 32 Bytes (+ ein paar für die
Beep-Routine) bei viel weniger Schreibaufwand, mindestens genauso
übersichtlich und ohne sich das Hirn mit komplexen Programmausdrücken
aufreiben zu müssen. Hier fehlt in Asm lediglich, einem Ausdruck wie
portb,0 ein passendes Alias geben zu können. Und jetzt?
Moby schrieb:> Und jetzt?
Und jetzt bitte eine Antwort auf die Frage des TO.
Nur am Rande: er hatte nicht gefragt, ob und wie man ein Bit in
Assembler setzen kann.
Moby: Bei Deinem Quelltext muss ich dem Programmfluss folgen um zu
sehen, wo und wie die Schleifen oder die Fallunterscheidungen liegen.
Daher habe ich länger benötigt um zu verstehen, was geschieht.
Mal abgesehen davon, daß der TO nach C++ gefragt hat, ist es einfach nur
peinlich, Peter Danegger etwas über Assembler erzählen zu wollen.
Gruß, Stefan
Moby schrieb:> Und jetzt?
Der Assemblerbrei ist ja wohl auch keine Schmeicheleinheit für die
Augen. Ob das nun 32Byte oder das 10fache ist spielt keine Rolle. Aus so
was wird nie wartbarer Code.
Moby schrieb im Beitrag #3910701:
> Ich denke, diese wenigen Zeilen werden keinen der doch so komplex denken> könnenden C++ Programmierer überfordern ;-)
Schön provokant, aber in der Sache kein zusätzlicher Gedankeninhalt.
Nächster Versuch!
Das lieber Lässt Du mal besser weg.
Sowohl Peter Danegger, als auch Klaus, und genauso ich haben sicher
schon wesentlich mehr in Assembler gearbeitet und sind mittlerweile ein
paar Softwaregenerationen weiter als Du.
Stefan
Moby schrieb:> Und jetzt?
Und jetzt portierst Du den Assembler-Code auf zwölf verschiedene
Mikroprozessoren mit unterschiedlichen Befehlssätzen. Viel Spaß dabei!
:-)
Ganz unrecht hat moby aber auch nicht. Gut bei seinem code fehlt die
initialisierung. Also lasst es 100 zu 500 byte code sein. Das is ne
Menge für ein und dieselbe Aufgabe.
Lesbarer wäre der Code mit kommentaren, ist also auch nicht so schlimm.
Und offtopic ist es auch nicht, da ihr immer argumentiert, es gibt
keinen overhead.
Also wo kommen die ca400 bytes her? Hier wäre das asm file nicht
schlecht. Evtl fehlende optimierungen?
Vom code sieht das c++ ja nicht schlecht aus, wobei ich dem pin noch nen
startwert mitgeben würde beim initialisieren.
Auch wurde ich dem outputpin gleich an und aus als Funktion geben und
noch nen inveroutoutpin einbauen.
Moby schrieb im Beitrag #3910841:
> Ist das etwa alles?> Ich meine zum eigentlichen Thema?
Jetzt folgst Du vollends dem normalen Trollschema. Du trägst nichts bei
außer einer (möglichst feinfühligen) Provokation und dann drehen wir uns
wieder auf einer Metaebene im Kreis, also lassen wir's einfach bleiben!
Robin E. schrieb:> Ganz unrecht hat moby aber auch nicht. Gut bei seinem code fehlt die> initialisierung. Also lasst es 100 zu 500 byte code sein. Das is ne> Menge für ein und dieselbe Aufgabe.
Was auch immer der Autor dort oben mit seiner Bibliothek gemacht hat, es
war offenbar nicht gut, oder aber der entsprechende Compiler ist extrem
schlecht, wenn es zu solchen Unterschieden kommt. Ja, Startup kostet ein
bissl was, C-Runtime-Init vielleicht auch (aber nicht so viel,
wahrscheinlich hat er keine minimal-Runtime ausgewählt). So sollte aber
nicht dermaßen viel Overhead entstehen. Wenn man im Stande ist, den
Startup-Code zu verändern, entfällt es dann auch gänzlich. Vielleicht
ist der AVR-gcc aber auch einfach schlecht, würde mich nicht
überraschen.
Moby schrieb im Beitrag #3910701:
> Tja Klaus, zur Frage C++ auf MC gehört immer auch die Frage der> Sinnhaftigkeit.
Wie wärs wenn du mal über die Sinnhaftigkeit deiner Beiträge denkst,
anstatt hier ständig ungefragt deine Ansichten zu C und C++ zu
verbreiten. Im Forum müsste es jetzt jeder wissen. Gibt es dir nicht mal
langsam zu denken, wenn kein Anderer deine Meinung teilt und dir schon
etliche Leute (die auch noch beruflich mit µCs zutun haben) dagegen
argumentieren? Dazu kommt dann noch dass du von C++ (und anscheinend C)
keine Ahnung hast, es aber dennoch erbittert bekämpfst. Ganz ehrlich, du
erinnerst mich an irgendwelche fanatischen Missionare, die meinen den
einzig wahren Glauben verbreiten zu müssen.
Moby schrieb:> . macht nach Adam Riese maximal schnelle 32 Bytes (+ ein paar für die> Beep-Routine) bei viel weniger Schreibaufwand, mindestens genauso> übersichtlich und ohne sich das Hirn mit komplexen Programmausdrücken> aufreiben zu müssen. Hier fehlt in Asm lediglich, einem Ausdruck wie> portb,0 ein passendes Alias geben zu können. Und jetzt?
Auf den Punkt gebracht. Zumindest für diese Aufgabe. Natürlich
programmiere auch ich keine LAN Anbindung in Assembler. Wobei man da
schon den klassischen MC verlässt. Der Kluge Entwickler passt sein
Sprachwerkzeug der Anforderung an und micht C u. assembler und da wo er
komplexe nicht zeitkritische Aufgaben hat und den entsprechenden
Speicher zur Verfügung meinetwegen auch C++.
TriHexagon schrieb:> die meinen den> einzig wahren Glauben verbreiten zu müssen
Nun, das Asm-Beispiel bezeugt keinen Glauben, sondern schlicht Fakten.
Auch wenn sie manchem OOP-Gläubigen nicht in den Kram passen ;-)
Paladium schrieb:> Auf den Punkt gebracht. Zumindest für diese Aufgabe.
Für viele weitere genauso ;-)
Moby schrieb im Beitrag #3910701:
> Tja Klaus, zur Frage C++ auf MC gehört immer auch die Frage der> Sinnhaftigkeit
Sicher nicht. Peters Frage war klar - nach Assemblerlösungen wurde nicht
gefragt.
Wenn Du diese Frage stellen willst, mach bitte Deinen eigenen Thread
dazu auf.
Danke.
Hallo Moby,
Moby schrieb:> Und jetzt?
Irgendwie habe ich trotz intensiver Bemühungen noch nicht verstanden,
welche Relevanz das denn für eine Diskussion haben sollte, bei der es um
C++ (und damit: um les-, wart- und wiederverwendbaren Code) auf
Mikrocontrollern geht.
Trotzdem vielen Dank für Deinen "Beitrag".
Liebe Grüße,
Karl
Ich hatte ja schon mal hier die PSoC angeführt. Da wird der Hardwareteil
als Schaltung dargestellt und auch die Funktionen, die ein Pin ausüben
soll wird dort in der graphischen Ansicht eingestellt.
Dahinter muss sich doch im Prinzip solch ein Konzept verbergen.
Eine einmal klar getrennte Hardware, wohl möglich noch für eine ganze
Gruppe von µC's, die dann den eigentlich wichtigen Code wartbarer und
austauschbar macht, das wäre doch wünschenswert.
das war ein spannender und sehr detaillierter Vortrag zum Für und Wider
C++
http://www.ese-kongress.de/paper/presentation/id/309
die für mich wichtigste Botschaft war: C und C++ Lösungen miteinander
vergleichen, zum Beispiel bezüglich Speicherplatz und Laufzeit, läuft
oft auf den Vergleich von Äpfeln und Birnen hinaus. Er hat sehr das
schön an Beispielen nachgewiesen die sich eben erst auf den zweiten
Blick erschließen. So hat er eine C-Lösung für einen Up- und
Down-Counter einmal klassisch und einmal objektorientiert vorgestellt
und auseinandergepflückt. Quintessenz war dabei die geschickt gebaute
Klasse war nur 3% größer aber dafür 20% schneller :-o zudem war die
OO-Lösung noch Typsicher und hat automatisch verhindert dass die Counter
durch Anwendungsentwickler falsch benutzt werden konnten.
Dann hat er die C-Lösung vorgestellt die tatsächlich äquivalente
Eigenschaften hatte und die war um einiges fetter und langsamer als die
C++ Lösung da es eben durch den Programmierer und nicht auf
Compilerebene gelöst wurde.
Vielleicht ist der Vortrag ja mal im Download verfügbar.
Gruß J.
Aber der vergleicht ja nur C mit C++. Für eingeschworene ASM'er beides
das gleiche Teufelszeug. Gefundene Ironie bitte behalten!
Ansonst, die Zusammenfassung bringt's auf den Punkt: mehr Features ohne
extra Kosten.
Bastler schrieb:> die Zusammenfassung bringt's auf den Punkt: mehr Features ohne> extra Kosten.
So habe ich das bisher auch gesehen. Auf einem PC programmiere ich ja
auch in C++ oder C#. Vor allem Templates sind genial.
Aber mit den IDEs, die ich für µCs benutze, hat das nie richtig
funktioniert. Der Compiler für den MSP430 unterstützt keine Templates
und bem Atmel Studio und CooCox funktioniert es bei mir (noch?) nicht,
beim Debuggen mit Breakpoints in die Instanz-Variablen zu schauen.
Welche IDEs benutzt Ihr für C++ und klappt dort das Debugging?
Karl Käfer schrieb:> habe ich trotz intensiver Bemühungen noch nicht verstanden
Das hast Du bestimmt ganz genau verstanden.
Aber was sollst Du bei diesen offensichtlichen Unterschieden nun auch
sagen.
Also verstecke Dich ruhig weiter hinter der völlig irrealistischen
Moderatoren-Richtlinie, stets konstant und punktgenau beim Thema zu
bleiben.
Um es dann (wie könnte es auch anders sein) selber nicht so genau zu
nehmen.
Johannes R. B. schrieb:> Dann hat er die C-Lösung vorgestellt die tatsächlich äquivalente> Eigenschaften hatte und die war um einiges fetter und langsamer als die> C++ Lösung da es eben durch den Programmierer und nicht auf> Compilerebene gelöst wurde.
Solange man den Quelltext solcher grandiosen Beispiele nicht sehen kann,
glaube ich solchen Leuten kein einziges Wort. Das ist ganz genau so wie
bei Vertretern irgendeiner Ware, die sehr schön zeigen können, daß
allein mit IHRER Ware der wahre Fortschritt einhergeht und alle anderen
bloß Stümper sind. Frei nach Churchill.. glaube ich nur der Statistik,
die ich eigenhändig gefälscht habe.
W.S.
@ Moby + W.S.: Hört doch mal mit Eurem kleigeistigen Krieg auf.
Folgende Frage finde ich im Zusammenhang "wie geht das?" viel
spannender.
Torsten C. schrieb:> Welche IDEs benutzt Ihr für C++ und klappt dort das Debugging?
Bei mir geht das eben leider noch nicht so schön, wie in der Theorie.
Oder hat der TO Peter das Problem vielleicht gar nicht? Ich will ja
nicht den Thread kapern.
@Karl Käfer: Kannst Du den Header vielleicht irgendwo hochladen, würde
das gerne mal auf meine Plattform -> sinngemäß <- portieren.
@all: EinAus(&DDRB, &PORTB, &PINB, PB1);
Warum sind beim Atmel 4 Werte vonnöten um einen Pin zu steuern? Was ist
DDRB und was unterscheidet es von PORTB, was bedeutet PINB und warum
beinhaltet dann PB1 nochmal das B? Ich bin da lieder außenstehender :(
BastiDerBastler schrieb:> Warum sind beim Atmel 4 Werte vonnöten um einen Pin zu steuern? Was ist> DDRB und was unterscheidet es von PORTB, was bedeutet PINB und warum> beinhaltet dann PB1 nochmal das B? Ich bin da lieder außenstehender :(
DDRB = Portpins I/O Definition Register
PORTB = Port-Output Register
PINB = Port-Input Register
PB1 = Port B Bit 1
BastiDerBastler schrieb:> Also würde im Prinzip auch PB1 genügen, DDRB, PORTB, PINB ließen sich> daraus ableiten?
So wie ich das verstehe enthält Px0 - Px7 nur die Bitnummer 0-7 für x=
Port A,B,C..., woraus sich natürlich nicht die verschiedenen I/O
Locations der drei zu einem Port x gehörigen Register DDRx,PORTx,PINx
ableiten lassen sondern explizit anzugeben sind.
Ich versuche gerade die Snippets aus dem Artikel (erstes Beispiel) :
http://www.mikrocontroller.net/articles/AVR_Interrupt_Routinen_mit_C%2B%2B
einzubauen.
Ich C++ Neuling, kann da mal jemand drübersehen ?
Die Timer0 Klasse wird in der Main.cpp abgeleitet.
Sollte das so funktionieren ???
Gibt es einen besseren weg ?
BastiDerBastler schrieb:> Also würde im Prinzip auch PB1 genügen, DDRB, PORTB, PINB ließen sich> daraus ableiten?
Nein.
Ein Port besteht hier aus einem Byte, bzw. 8 Pit. Zu einem Port gehören
drei Register, jeweils 1 Byte bzw. 8 Bit groß:
DDRx = Pins als Eingönge oder Ausgänge einstellen
PORTx = Für Ausgang-Pins ihr Wert, also An oder Aus
PINx = Für Eingangs-Pins ihr Wert, also An oder Aus
Und nun musst Du noch die Bitnummer Deines Ports wissen. Die steht in
Pxn.
chris schrieb:> Meiner Meinung nach wäre aber ein ZIP-File sinnvoller
Meiner Meinung ist die "Codeansicht" zumindest bei den .cpp ganz nett,
die ginge bei zip verloren.
@ Thomas W: Welche IDE benutzt Du?
Eclipse CDT mit dem AVR GCC Plugin.
Habe misst gebaut.
Wenn man MyTimer instanziiert passiert das hier :
Timer.cpp:(.text._ZN6Timer0C2Ev+0x2): undefined reference to
`Timer0::timerInterrupt::ownerTimer'
Das muss jetzt aber bis morgen warten.
Im nachhinein finde ich den Anhang auch extrem unübersichtlich.
BastiDerBastler schrieb:> @Karl Käfer: Kannst Du den Header vielleicht irgendwo hochladen, würde> das gerne mal auf meine Plattform -> sinngemäß <- portieren.
Der Header ist der Code für die Klassen Pin, InputPin und OutputPin aus
meinem vorherigen Beispiel.
HTH,
Karl
Soo,
also ich habe den Code hier mal so hingefrickelt in meiner Bibliothek.
Wollte jetzt kein Getriebe von einem Pin ableiten lassen usw.
(Komposition wäre da eindeutig besser).
Vielleicht sollte man die Dinge auch nicht "pin" nennen, sondern
"pin_ref", schließlich referenziert das ja nur einen real existierenden
Pin.
Mit den ".configure" bin ich noch nicht zufrieden, aber input_pin,
output_pin wären falsch, weil der Zustand des realen Pins ja nicht an
die Referenz gebunden ist.
"pb<3>" usw. sind "statische" Pin-Referenzen, die man auch verwenden
könnte, aber da der Beispielcode oben ja Laufzeit-Werte genommen hat,
habe ich das hier ebenso gemacht.
[cpp]
void playground()
{
using namespace gpio;
pin motor = pb<3>();
pin beleuchtung = pb<2>();
pin startknopf = pd<1>();
pin bremse = pd<2>();
pin gang = pd<3>();
motor.configure(inout());
beleuchtung.configure(inout());
startknopf.configure(input);
bremse.configure(input);
gang.configure(input);
while(1) {
if(startknopf.get()) {
beleuchtung.set();
if(gang.get() && !bremse.get()) {
// beep
} else {
motor.set();
}
} else {
motor.reset();
beleuchtung.reset();
}
}
}
[/cpp]
Der Schleifenanteil nimmt genau 32 Byte Code ein. Die Konfiguration
nimmt mehr ein, weil das ja ->Laufzeitabhängig<- in einigen Fällen
maskierte read-modify-write auf mehrere Register bedeutet (in meinem
Fall: STM32F4). Wären die Pins festverdrahtet, würden die Masken von
meinen Templates entsprechend zusammengelegt und ausgerechnet, für
entsprechend weniger Schreiboperationen.
Karl Käfer schrieb:> Tja, das ist so eine Sache mit dem Verständnis: entweder man hat es,> oder man hat es eben nicht. Daß schon C enorm komplex ist, siehst Du> schon an den vielen Fragen und Fehlern, die hier ständig aufschlagen --> und die bisweilen sogar sehr erfahrenen C-Entwicklern passieren. Was> hattest Du denn erwartet, was herauskommt, wenn man eine komplizierte> Sprache wie C um ein komplexes Paradigma wie die Objektorientierung> erweitert?
Der Volksmund sagt "getroffene Hunde bellen". Da hast du in einem
ellenlangen Beitrag versucht, deine seltsamen Ansichten zu untermauern -
und hast doch nur geschrieben, daß du kein Verständnis für echte
Abstraktionen hast. Du hast nicht begriffen, daß das Abstrahieren der
Hardware auf ein bloßes Wrappen a la Port123.SetBit9.high oder so
einfach nur ein viel zu mickriger Denkansatz ist.
Was also sollen solche billigen Dinge wie:
pin motor = pb<3>();
und
motor.configure(inout());
denn zum Verbessern der Situation innerhalb der Firmware beitragen? Es
ist eben ganau DAS, was ich schon schrieb: ein viel zu mickriger
Denkansatz - geschrieben eben nur zu dem Zweck, hier eine krampfhafte OO
Anwendung mittels C++ zu posten. Von einem echten HW-Treiber bzw. einer
echten HAL ist sowas meilenweit entfernt.
Aber mal zum oben zitierten: C ist überhaupt nicht komplex. C ist
lediglich kompliziert, weil es zu einem ganz erheblichen Teil auf
Konstruktionsfehlern beruht, die wiederum per Dekret a la Ordre de Mufti
wieder ausgebügelt wurden. Ich spare mir mal die Details, das hatten wir
schon anderweitig.
Wenn man eine komplexe UND unkomplizierte Sprache (wie Pascal) erweitert
(um "ein komplexes Paradigma wie die Objektorientierung"), dann bleibt
das dank Weitsicht der Leute, die das taten, auch leicht les- und
verstehbar. Es geht also. Und man kann sogar mehrere Konstruktoren
haben und nicht bloß einen einzigen mickrigen Konstruktor wi bei C++.
W.S.
W.S. schrieb:> Und man kann sogar mehrere Konstruktoren> haben und nicht bloß einen einzigen mickrigen Konstruktor wi bei C++.
Und wo hast du die Weisheit her, dass C++ keine überladenen
Konstruktoren unterstützt?
W.S. schrieb:> Aber mal zum oben zitierten: C ist überhaupt nicht komplex. C ist> lediglich kompliziert, weil es zu einem ganz erheblichen Teil auf> Konstruktionsfehlern beruht, die wiederum per Dekret a la Ordre de Mufti> wieder ausgebügelt wurden. Ich spare mir mal die Details, das hatten wir> schon anderweitig.
Wo soll den C kompliziert sein? Natürlich sind da ein paar grobe
Schnitzer, aber welche Sprache ist perfekt?
C ist eine richtige pragmatische Systemprogrammiersprache, die dazu
entwickelt wurde portable Betriebssysteme entwickeln zu können (z.B.
Unix). Was Anfängern immer wieder aus der Bahn wirft, sind solche Dinge
wie z.B., dass ein Stringliteral ein const char* Zeiger zurück gibt und
dass ein String nicht durch ein = Operator kopiert werden kann. Was ist
daran kompliziert? Da stolpert halt ein ungedultiger Anfänger drüber.
Viele Eigenschaften von C erscheinen erstmal merkwürdig und eigensinnig
(wie z.B. die automatische Initialisierung von Variablen), wenn man aber
die Hintergründe erforscht, dann ist es eigentlich immer logisch und
eingängig.