Ich will mit einem Atmega48 eine serielle Kommunikation mit einem
RingBuffer programmieren. Objektorientierung erschien mir ganz
praktisch. Leider habe ich nicht besonders viel Ahnung von C++. Bei
meinem Code bringt der Compiler diese Fehlermeldung: "'TxBuffer' was not
declared in this scope". Aber warum? TxBuffer wurde doch in der Klasse
angegeben?
Anonymous U. schrieb:> TxBuffer wurde doch in der Klasse angegeben?
Nein, nicht wirklich; denn cRingBuffer wurde nie definiert, d.h.
TxBuffer hat keinen bekannten Typ. Das ist wie
1
blabla_twert;
ohne vorher je gesagt zu haben, was blabla_t sein soll.
In dem gezeigten Code wird nirgends ein Objekt SerialComm angelegt, auf
das Du in der ISR zugreifst.
Ansonsten poste mal den Originalcode als Anhang und die vollständige
Fehlermeldung inkl. Zeilennummer, in der der Fehler auftritt.
Peter schrieb:> In dem gezeigten Code wird nirgends ein Objekt SerialComm angelegt, auf> das Du in der ISR zugreifst.
Das die ISR noch nicht funktioniert ist mir klar. Kann ich die irgendwie
in die Klasse mit einbinden? Dazu muss diese aber ein Singleton sein?
Wie mache ich das, mit static?
Johann L. schrieb:> Nein, nicht wirklich; denn cRingBuffer wurde nie definiert, d.h.> TxBuffer hat keinen bekannten Typ. Das ist wie
Ah ich kommer der lösung näher. Ich habe nun cRingBuffer per header file
eingebunden. Blame C# for that.
Hier mal mein Code bisher:
1
// DataStructs.cpp
2
3
#include<avr/io.h>
4
5
classcRingBuffer
6
{
7
private:
8
// variables
9
#define BufferMask 0b00011111 // has to correspond with buffer size to avoid overflow
10
charBuffer[32];
11
12
public:
13
// variables
14
uint8_tRdIndex;
15
uint8_tWrIndex;
16
// constructor
17
cRingBuffer()
18
{
19
RdIndex=0;
20
WrIndex=0;
21
}
22
// write string in buffer
23
// returns -1 if buffer overflow
24
// max string length: 255
25
intWriteString(char*data)
26
{
27
// loop for char access in data string
28
uint8_ti=0;
29
while(*(data+i)!='\0')
30
{
31
Buffer[WrIndex]=*(data+i);
32
WrIndex=BufferMask&(++WrIndex);
33
// 1 unused byte in Buffer for speed optimization
34
if(RdIndex==WrIndex)return-1;
35
}
36
return0;
37
}
38
// read char from buffer
39
charReadChar()
40
{
41
// buffer empty?
42
if(RdIndex==WrIndex)return'\0';
43
// read next char
44
charTemp=Buffer[RdIndex];
45
RdIndex=BufferMask&(++RdIndex);
46
returnTemp;
47
}
48
};
49
50
51
// DataStructs.h
52
53
#include<avr/io.h>
54
55
classcRingBuffer
56
{
57
private:
58
// variables
59
#define BufferMask 0b00011111 // has to correspond with buffer size to avoid overflow
Schon länger her, aber ich habe das immer so gemacht:
class Sercom
{
....
OnReceiveMessage(char c);
};
//lokal Objekt der Klasse instanziieren
Sercom DevTtyS1;
//globalen Pointer auf Objekt für ISR
Sercom *gp_DevTtyS1 = DevTtyS1
void ISR(void)
{
if ( gp_DevTtyS1 )
gp_DevTtyS1->OnReceiveMessage(c);
}
Soo. Nun funktioniert mein Kot endlich. Ich denke das ich OOP vernünftig
eingesetzt habe, soweit das kleine µC betrifft. Ich poste den mal hier,
Meinung, Kritik und Verbesserungsvorschläge erwünscht.
Main File:
Anonymous U. schrieb:> Das die ISR noch nicht funktioniert ist mir klar. Kann ich die irgendwie> in die Klasse mit einbinden? Dazu muss diese aber ein Singleton sein?> Wie mache ich das, mit static?
Die letzte Frage betrachte ich mal als rhetorisch.
Folgendes geht:
Noch eine andere Frage: Bei meiner cRingBuffer Klasse verwende ich eine
Bitmaske. Nun will ich die Buffergröße variabel machen. Dazu habe ich
die Template-Variable BufferSize_2expN angelegt.
Dieser Code funktioniert zwar, aber wenn ich anstatt der automatischen
Berechnung einfach #defines einfüge, spare ich 12 bytes. Ich verstehe
aber nicht warum!
>> Anonymous U. schrieb:>>> Ich will mit einem Atmega48 eine serielle Kommunikation mit einem>>> RingBuffer programmieren.>>>> Das ist in Asm zu einem Bruchteil des C++ Textwusts möglich. Ohne>> Studium und abstrakte Verrenkungen.
Mit Assembler kann man das sicher schlanker hinbringen. Aber der Gewinn
an Übersichtlichkeit vom Sourcecode, den höhere Abstraktionsebenen
bieten, ist mir der kleine Overhead wert. Natürlich muss man wissen wie
man sein Werkzeug richtig einsetzt und im Falle C++ versuche ich grade
herauszufinden wie man den Code optimiert, bzw. wann es Sinn macht OO
einzusetzen und wo nicht.
By the way: Bei ganz kleinen µC (Stichwort IoT) macht Assembler
tatsächlich Sinn. Mein aktuelles Projekt wird allerdings etwas größer
und ich bin mit dem Atmega nicht ganz so beschränkt.
Veit D. schrieb:> bist mit der uart Lib von Peter Fleury bestimmt besser dran.
Die eigene Lib ist schnell programmiert, was den UART angeht. Das ist
kein großer Aufwand von der Programmlogik her. Der große Zeitfresser ist
im Moment zu lernen wie man C++ hier richtig einzusetzt. Ob ich dafür
jetzt irgendeine Bibliotek reinklatsche oder meine eigene erstelle macht
in diesem Fall Zeitmäßig keinen Unterschied. Allerdings weiß ich zum
Schluss halt was mein Code genau macht, bzw. ist der für meine Zwecke
dann besser optimiert.
Mal ne kurze Frage bzgl. C++-Klassen und Peripherie.
Wenn ich mir z.B. eine serielle Klasse für den UART implementiere, dann
muss ich ja ein Objekt von der Klasse erzeugen, damit ich dann auf
send(), receive() etc... zugreifen kann. Prinzipiell erst mal kein
Problem. Aber wie funktioniert dass dann Modulübergreifend? Also wie
kann ich von x verschiedenen Modulen dann auf die serielle Klasse
zugreifen? Jedesmal ein neues Objekt erzeugen kann ja nicht die Lösung
sein?
mfg
Felix F. schrieb:> Wenn ich mir z.B. eine serielle Klasse für den UART implementiere, dann> muss ich ja ein Objekt von der Klasse erzeugen, damit ich dann auf> send(), receive() etc... zugreifen kann.> wie> kann ich von x verschiedenen Modulen dann auf die serielle Klasse> zugreifen?
Ich nehme an, du meinst "Übersetzungseinheiten" anstatt "Module" und
"UART-Objekt" (auch "Instanz") statt "serielle Klasse".
Und die Antwort ist die gleiche wie für C auch: eine globale Variable,
in diesem Fall ein globales Objekt. Das mußt du in einer
Übersetzungseinheit definieren und in allen anderen (die es verwenden)
deklarieren, üblicherweise in einem Headerfile das du überall
einbindest.
Anonymous U. schrieb:> Mit Assembler kann man das sicher schlanker hinbringen. Aber der Gewinn> an Übersichtlichkeit vom Sourcecode, den höhere Abstraktionsebenen> bieten, ist mir der kleine Overhead wert.
Also ich sehe da zumindest im konkreten Fall keinerlei Vorteile
bezüglich der Übersichtlichkeit des Codes, sondern eher das genaue
Gegenteil...
Felix F. schrieb:> Mal ne kurze Frage bzgl. C++-Klassen und Peripherie.>> Wenn ich mir z.B. eine serielle Klasse für den UART implementiere, dann> muss ich ja ein Objekt von der Klasse erzeugen, damit ich dann auf> send(), receive() etc... zugreifen kann. Prinzipiell erst mal kein> Problem. Aber wie funktioniert dass dann Modulübergreifend? Also wie> kann ich von x verschiedenen Modulen dann auf die serielle Klasse> zugreifen? Jedesmal ein neues Objekt erzeugen kann ja nicht die Lösung> sein?
Das könnte man als Monostate - Pattern so machen.
Man kann aber auch gar kein Objekt anlegen, sondern alles static machen.
Das macht man dann mit Templates: so kann man zu Compilezeit auch gleich
verhindern, dass zuviele Uarts benutzt werden. Andernfalls müsste man
es zur Laufzeit prüfen. Compilezeitfehler sind besser ;-)
Übersichtlicher wirds im Hauptprogramm, was aber nur bei entsprechend
komplexen Anwendungen vorteilhaft ist und weniger aufm Controller, wo es
im Extremfall gar kein Hauptprogramm gibt.
Wilhelm M. schrieb:> Felix F. schrieb:>> Mal ne kurze Frage bzgl. C++-Klassen und Peripherie.>>>> Wenn ich mir z.B. eine serielle Klasse für den UART implementiere, dann>> muss ich ja ein Objekt von der Klasse erzeugen, damit ich dann auf>> send(), receive() etc... zugreifen kann. Prinzipiell erst mal kein>> Problem. Aber wie funktioniert dass dann Modulübergreifend? Also wie>> kann ich von x verschiedenen Modulen dann auf die serielle Klasse>> zugreifen? Jedesmal ein neues Objekt erzeugen kann ja nicht die Lösung>> sein?>> Das könnte man als Monostate - Pattern so machen.>> Man kann aber auch gar kein Objekt anlegen, sondern alles static machen.> Das macht man dann mit Templates: so kann man zu Compilezeit auch gleich> verhindern, dass zuviele Uarts benutzt werden. Andernfalls müsste man> es zur Laufzeit prüfen. Compilezeitfehler sind besser ;-)
Hättest du dazu auch einen Link etc mit einem Beispiel?
mfg
batman schrieb:> Übersichtlicher wirds im Hauptprogramm
Nicht mal das ist der Fall. Ob ich da hinschreibe
rcall getfifobyte
br(irgenwas) gotfifobyte
gotfifobyte:
;got fifo byte, bereits im Register, fertig zur näheren Inspektion
oder
if getfifobyte(dämlicher char-Zeiger)
{
//got fifobyte, muss aber erst wieder aus dem Speicher geholt werden
//um es genauer ansehen zu können
}
Dürfte von der Übersicht her gleich sein. Vom Tippaufwand her ist die
Assemblerversion sogar kürzer. Und von der Effizienz her auch noch
deutlich überlegen...
Das MAKE_ISR von Johann schaut soweit phantastisch aus.
Aber leider sorgt das static dafür das auch alle anderen Daten samt
Methoden, auf die zugegriffen werden soll, auch static sein müssen.
Oder es muss mit in die ISR Methode rein.
Das macht es schwierig einen Handler einer anderen (fremden)
Klasseninstanz aufzurufen.
Komplizierter macht es Christian M. und baut quasi eine zweite
Sprungtabelle :
[[https://www.mikrocontroller.net/articles/AVR_Interrupt_Routinen_mit_C++]]
Hier hatte ich das zum ersten mal gesehen.
Daraus habe ich das folgende "geklaut" (und soweit verstanden;) :