Hallo,
ich habe ein existierendes Projekt in avr-gcc. Es ist eine Busanbindung
die als "linkerlib" implementiert ist. Ich bezeichne das jetzt auch mal
als Kernel.
Diese habe mich für ein paar Atmels (8515, m8, m16, m32) übersetzt und
kann so meine existierenden und neuen Projekte einfach umsetzen.
Bisher konnte ich mit Hilfe von ein paar #ifdefined die Unterschiede bei
den AVRs, z.B. den Interrupt Namen, ausgleichen.
Nun kommt ein m162 hinzu und der hat ja 2 serielle. Ich will meinen Code
nicht mit "100erten" #ifdefined zerpflücken und denke daher darüber nach
einen einfachen HAL zu machen.
Aber wie geht man das am besten an ?
Einfach ein paar Makros wie "SET_BAUD(9600)", die dann wiederum per
#ifdefined zerlegt werden an den benötigten Stellen ?
Oder Makros anlegen und für jede CPU in eine ".h" verbannen und über
#ifdefined einbinden, so wie die "avr io.h" ? Den Code der nicht auf
Register zugreift, also CPU unabhängig ist, Synchron zu halten über alle
CPUs wäre ja extrem hoch.
Kann mir da jemand einem Tipp geben ?
Danke im voraus.
Wenn ich das richtig verstehe, was du willst geht das in C nur bedingt
elegant.
In C++ (und nicht über den Präprozessor) könnte man das wesentlich
schicker ausdrücken:
1
classUart
2
{
3
public:
4
5
enumeBaud
6
{
7
eBaud300,
8
eBaud600,
9
eBaud1200,
10
eBaud2400,
11
eBaud4800,
12
eBaud9600,
13
eBaud19200,
14
eBaud38400
15
};
16
17
Uart(uint8_tdevice=0,
18
eBaudid_baudrate=eBaud9600
19
)
20
:device(device)
21
{
22
// Initialisierung...
23
setBaud(id_baudrate);
24
}
25
26
~Uart()
27
{
28
// aufräumen ...
29
}
30
31
intsetBaud(eBaudid_baudrate)
32
{
33
// ...
34
}
35
36
voidwrite(charc)
37
{
38
// Zeichen ausgeben ...
39
}
40
41
voidwrite(constchar*s)
42
{
43
while(s&&*s)
44
{
45
write(*std::string);
46
}
47
}
48
49
// restliche Methoden...
50
51
private:
52
uint8_tdevice;
53
54
};
55
56
// auf einem System, das nur eine RS232 hat oder nur eine nutzt:
57
voidf1()
58
{
59
// Öffnen:
60
UarteineSerielle();
61
62
eineSerielle.write("Hallo\n");
63
}
64
65
// 2 serielle werden genutzt:
66
voidf2()
67
{
68
// Öffnen:
69
UarteineSerielle(0);
70
UarteineAndereSerielle(1);
71
72
eineSerielle.write("Hallo\n");
73
eineAndereSerielle.write("Hallo\n");
74
}
Mit anderen Worten: Erweiterungen auf die zweite serielle
finden nur innerhalb der Klasse statt und ggf. beim
Initialisieren.
Alle anderen Sachen beim Aufrufer bleiben unverändert.
Jürgen Sachs schrieb:> Bisher konnte ich mit Hilfe von ein paar #ifdefined die Unterschiede bei> den AVRs, z.B. den Interrupt Namen, ausgleichen.> Nun kommt ein m162 hinzu und der hat ja 2 serielle. Ich will meinen Code> nicht mit "100erten" #ifdefined zerpflücken und denke daher darüber nach> einen einfachen HAL zu machen.
Ein HAL ist ein großes Wort, aber im kleinen macht sowas doch (fast)
jeder.
Die Quellen teilt man in hardware unabhängige Funktionalität und
hardware abhängige, wie z.B. eine "sio.c" und "sio.h", welche alle
Funtionen zum UART enthalten. Die ganzen Prozessorabhängigen #defines
konkentrieren sich nur dort, und das ganze bleibt wartbar.
> Aber wie geht man das am besten an ?> Einfach ein paar Makros wie "SET_BAUD(9600)", die dann wiederum per> #ifdefined zerlegt werden an den benötigten Stellen ?
Warum ein Makro? Schreib eine Funktion setbaud(), die im hardware
unabhängigen Teil aufgerufen wird.
Und bitte komm nicht mit dem Argument, dass Makros schneller sind als
Funktionsaufrufe.
Für jeden Prozessor schaut dann setbaud() vielleicht ein bischen anders
aus. Das kannst Du erreichen indem Du für jeden Prozessor eine getrennte
Bibliothek anlegts, die immer setbaud() enthält.
Einfacher ist aber eine allgemeine sio.c/sio.h Kombination und mittels
#defines wird zwischen Varianten hin- und hergeschalten.
> Den Code der nicht auf> Register zugreift, also CPU unabhängig ist, Synchron zu halten über alle> CPUs wäre ja extrem hoch.
Was meinst Du damit? Wohl eher im Gegenteil.
Klaus Falser schrieb:> Ein HAL ist ein großes Wort, aber im kleinen macht sowas doch (fast)> jeder.
Ja, schon richtig.
> Die Quellen teilt man in hardware unabhängige Funktionalität und> hardware abhängige, wie z.B. eine "sio.c" und "sio.h", welche alle> Funtionen zum UART enthalten. Die ganzen Prozessorabhängigen #defines> konkentrieren sich nur dort, und das ganze bleibt wartbar.
Die Frage ist wie weit man den HAL Abstrakt macht. Also ob man JEDEN
Zugriff auf eine Hardware in einen HALL packt, oder nur die Punkte
"Kritischer" sind. Z.B. setzen der Baudrate.
>> Aber wie geht man das am besten an ?>> Einfach ein paar Makros wie "SET_BAUD(9600)", die dann wiederum per>> #ifdefined zerlegt werden an den benötigten Stellen ?>> Warum ein Makro? Schreib eine Funktion setbaud(), die im hardware> unabhängigen Teil aufgerufen wird.> Und bitte komm nicht mit dem Argument, dass Makros schneller sind als> Funktionsaufrufe.
Nicht schneller, aber brauchen mehr Platz im Flash. Je nachdem wie gut
der Compiler das Optimiert.
So etwas "Komplexes" wie das setzen der Baudrate würde (habe) ich auch
eine Funktion, aber zum setzen eines Registers oder zum lesen desselben,
also EINZEILERN, würde ich ein Makro vorziehen.
Der Mega8515 ist schon recht voll und ich will die nicht austauschen, da
das ein Redesign der Existierenden Hardware zur Folge hätte.
> Für jeden Prozessor schaut dann setbaud() vielleicht ein bischen anders> aus. Das kannst Du erreichen indem Du für jeden Prozessor eine getrennte> Bibliothek anlegts, die immer setbaud() enthält.> Einfacher ist aber eine allgemeine sio.c/sio.h Kombination und mittels> #defines wird zwischen Varianten hin- und hergeschalten.>
Ja, so habe ich das im Prinzip auch jetzt. Nur waren bisher die
Registernamen und die Namen der BITs im Register gleich.
Mit dem neuen Prozessor leider nicht mehr.
>> Den Code der nicht auf>> Register zugreift, also CPU unabhängig ist, Synchron zu halten über alle>> CPUs wäre ja extrem hoch.>> Was meinst Du damit? Wohl eher im Gegenteil.
Macht man für jede CPU eine eigene Datei, gibt es immer Code, der auch
CPU unabhängig ist. Das Bedeutet das man diesen dann in vielen Dateien
pflegen muss. Ich hätte ja jetzt schon 4 CPU Typen = 4 Dateien.
Packt man alles in eine Datei wird es schnell unübersichtlich durch die
ganzen #ifdefines, z.B:
#ifdefined _CPU1_
do this
#elseifdefined _CPU2_
do that
#else
do error
#endif
Da ich hier nicht in die Falsche Richtung gehen will, wollte ich mal
nach Erfahrungen fragen.
Jürgen Sachs schrieb:>> konkentrieren sich nur dort, und das ganze bleibt wartbar.> Die Frage ist wie weit man den HAL Abstrakt macht. Also ob man JEDEN> Zugriff auf eine Hardware in einen HALL packt, oder nur die Punkte> "Kritischer" sind. Z.B. setzen der Baudrate.
Wenn du den Begriff HAL ernst nimmst, dann jeden.
HAL = Hardware Abstraction Layer
und das bedeutet nun mal, dass sich der Verwender des HAL nicht mehr mit
Details wie "welches Bit ist in welchem Register" herumschlagen muss
bzw. herumschlagen soll.
Der Verwender fordert von der HAL eine Serielle Schnittstelle an, mit
den Eigenschaftem: 8Bit, no parity, 1 Stopbit.
Welche Bits dazu in welchem Register zu setzen sind, hat ihn nicht mehr
zu interessieren.
Aber der Begriff HAL geht noch weiter. Da steckt 'Abstraction', also
Abstrahieren, drinnen. Das bedeutet, dass ich mich als Verwender auch
nicht mehr dafür zu interessieren habe, wie eine bestimmte
Funktionalität erreicht wird. Wenn ich haben will, dass eine Funktion
foo() alle 25.2 Millisekunden aufgerufen werden soll, dann teile ich das
unter Umständen einem Timer Modul mit. Wie dieses Modul es jetzt
einrichtet, dass ich auf meine 25.2 Millisekunden komme ist sein Bier.
Das hat mich nicht zu interessieren.
Die Aufgabe eines HAL besteht nicht so sehr darin, mir einen
standardisierten Zugang zur Hardware zu geben, sondern mir
standardisierte Funktionalität zur Verfügung zu stellen und der HAL
kümmert sich darum, wie diese Funktionalität auf einem bestimmten
Prozessor realisiert wird. Das ist mit HAL gemeint. Eine Grenze, hinter
der die konkreten Eigenschaften einer bestimmten Hardware verborgen
bleiben.
>> Was meinst Du damit? Wohl eher im Gegenteil.> Macht man für jede CPU eine eigene Datei, gibt es immer Code, der auch> CPU unabhängig ist.
Dann extrahiert man diesen Code in eine einzige Datei und verwendet ihn
von den spezifischen Codeteilen heraus.
Im HAL mag es eine Funktion geben, welche die Ausgabe eines Zeichens
bewerkstelligt. Darauf aufbauend kann man Funktionen schreiben, welche
Strings, Integer, Double ausgeben können. Die trennt man unter Umständen
von den hardwarespezifischen Codeteilen ab. Ob man die dann noch als
Bestandteil eines HAL ansieht ist Auslegungssache, es spricht nichts
dagegen es zu tun. Die Grenzen zur klassischen Funktions-Library sind
hier eher fliessend.
> Das Bedeutet das man diesen dann in vielen Dateien> pflegen muss.
Genau das bedeutet es nicht. Unabhängige Teile kommen in eine eigene
Datei und werden daher nur an einer Stzelle zentral gepflegt.
Um einen HAL zu designen fängt man nicht damit an, allen Registern neue
Namen zu geben. Das bringt nicht viel. Wenn man den Begriff HAL ernst
nimmt, dann fängt man damit an, sich zu überlegen welche Funktionalität
ich von einem Modul erwarte. Und welche Teile dieser Funktionalität
hardwareabhängig den kleinsten gemeinsamen Nenner bilden.
Funktionalität, nicht Register. Register interessieren erst wenn es
darum geht, den HAL zu implementieren. Aber die Schnittstellen eines HAL
stellen Funktionalität bereit.
Klaus Wachtler schrieb:> In C++ (und nicht über den Präprozessor) könnte man das wesentlich> schicker ausdrücken:
Sehe da jetzt nichts geschicktes oder was in C nicht auch möglich wäre:
Es verbleiben afaik die switches auf "device" innerhalb der Methoden der
Klasse. Und um defines kommst du in diesem Fall auch kaum herum (wenn
man die selbe Klasse für alle uCs benutzen will), da bei einem uC der
nur einen UART hat die Register für den zweiten UART in keinem Header
File definiert sind. Deshalb wird sich die Klasse nicht übersetzen
lassen. Und ohne define müßtest du die diese Klasse für jeden uC erneut
schreiben.
Was ich aber in diesem Zusammenhang mal gesehen habe war eine gute
Möglichkeit den gleichen Code zu verwenden falls sich nur die Anzahl der
Schnittstellen ändert (oder deren Adressmapping), nicht aber ihr
Registersatz oder die Ansteuerung. Man erstellt eine Struktur welche die
Registerbank des Uart abbildet, etwas so:
1
struct UART_REGS_ST
2
{
3
uint32_t BAUD_REG;
4
uint32_t TX_REG;
5
uint32_t RX_REG;
6
// ...
7
};
8
typedef struct UART_REGS_ST UART_REGS;
Alle UART Funktionen arbeiten dann man dieser Struktur:
Diese Funktionen können für alle uCs gleich bleiben, die sich nur in der
Schnittstellenanzahl unterscheiden. In der Header-File werden dann
jeweils die vorhandenen Registerbänke definiert:
1
#define UART_0 ((volatile UART_REGS*)0x1000uL)
2
#define UART_1 ((volatile UART_REGS*)0x1200uL)
Im Code verwendet man das ganze dann so:
1
// ...
2
3
Uart_Setup(UART_0, 9600);
4
Uart_Setup(UART_1, 115200);
Für uCs die grundlegend andere Register besitzen wird dann die Datei mit
den Uart_* Funktionen neu erstellt. Es wird davon ausgegangen das sich
das Register-Gewurstel völlig unterscheided, was i.d.R. auch so ist.
klaus schrieb:> Sehe da jetzt nichts geschicktes oder was in C nicht auch möglich wäre:>
Das ist ein gern gemachtes Misverständnis.
C++ kann auch nicht mehr als jede andere Programmiersprache.
Schliesslich sind all diese Programmiersprachen Turing-Complete und
damit exakt gleich in ihren prinzipiellen Möglichkeiten.
C++ bietet mit Klassen, Vererbung und vor allen Dingen Polymorphie
Möglichkeiten, wie man seine Strukturen 'out of the box' gut modellieren
kann. Das ist im Wesentlichen alles. Machen kann man all das auch in C,
keine Frage. In C++ hat man einfach nur Sprachmittel dafür.
>
1
> struct UART_REGS_ST
2
> {
3
> uint32_t BAUD_REG;
4
> uint32_t TX_REG;
5
> uint32_t RX_REG;
6
> // ...
7
> };
8
> typedef struct UART_REGS_ST UART_REGS;
9
>
>> Alle UART Funktionen arbeiten dann man dieser Struktur:>>
Wirf UART_REGS_ST und die Funktion Uart_Setup in eine gemeinsame
Struktur zusammen und du bist bei ... einer Uart Klasse
>
1
> // ...
2
>
3
> Uart_Setup(UART_0, 9600);
4
> Uart_Setup(UART_1, 115200);
5
>
Gratuliere. Du hast soeben den this Pointer entdeckt :-)
(Und das ist keineswegs sarkastisch gemeint. Ich kenne C++ Programmierer
die haben Monate gebraucht um das zu durchschauen)
>> Für uCs die grundlegend andere Register besitzen wird dann die Datei mit> den Uart_* Funktionen neu erstellt. Es wird davon ausgegangen das sich> das Register-Gewurstel völlig unterscheided, was i.d.R. auch so ist.
Und da kommt dann eben bei C++ die Vererbung bzw. Polymorphie ins Spiel.
Sie gestattet dir, all die verschiedenen Implementierungen als Ableitung
einer gemeinsamen Basisklasse zu sehen. Die Init-Funktion heißt immer
gleich, lediglich das Objekt für welches es aufgerufen wird, ist ein
anderes. Anderes Objekt - andere Implementierung. Als Benutzer
interessiert mich das aber nicht weiter. Ich rufe einfach nur immer die
Funktion mit gleichem Namen auf. Das Objekt weiß dann selber, welche
Implementierung dafür zuständig ist.
Karl heinz Buchegger schrieb:> Wirf UART_REGS_ST und die Funktion Uart_Setup in eine gemeinsame> Struktur zusammen und du bist bei ... einer Uart Klasse
... nur nicht bei der vorher gezeigten, da diese die Nummer des UART
speichert
1
private:
2
uint8_t device;
3
4
};
Für Register-Zugriffe benötigt sie aber letzten Endes die Adresse (oder
jemand der die Adresse kennt), sodass dies hier eher das OO Equivalent
wäre:
class Uart
{
public:
Uart(uint32_t addr) : regs((volatile UART_REGS*)addr)
{
}
void setBaud(uint32_t baud)
{
regs->BAUD_REG = /* ... */ baud;
}
// ...
private:
volatile UART_REGS* regs;
};
> Und da kommt dann eben bei C++ die Vererbung bzw. Polymorphie ins Spiel.
Polymorphie habe ich auch mal in C modelliert :-) Sozusagen die VMT als
Struktur aus Funktionszeigern und entsprechenden Create() Funktionen
(als Konstruktoren), die bei überschriebenen Funktionen den Pointer
ersetzt haben und vorher die Create() Funktion des "Basisklassenmoduls"
aufrufen und und und. Habe dann aber ganz schnell beschlossen, dass das
händisch alles ziemlich dämlich ist und viel zu kompliziert :-) Wenn ich
in C programmieren muss lasse ich sowas lieber. Das ist etwas wo ich
sagen würde da ist C++ wirklich "geschickter"
klaus schrieb:> ... nur nicht bei der vorher gezeigten, da diese die Nummer des UART> speichert
:-)
Das ist das schöne an Klassen.
Das Konzept besteht darin, dass man (wenn man gut designed hat) die
Internals einer Klasse austauschen kann, ohne dass es aussen wer merkt
:-)
> aufrufen und und und. Habe dann aber ganz schnell beschlossen, dass das> händisch alles ziemlich dämlich ist und viel zu kompliziert :-)
Und vor allen Dingen fehleranfällig.
> Wenn ich> in C programmieren muss lasse ich sowas lieber. Das ist etwas wo ich> sagen würde da ist C++ wirklich "geschickter"
Yep.
genau darum geht es. Natürlich kann man das alles auch in C machen. Aber
es ist einfach besser, wenn es jemanden gibt (den Compiler) der auf die
Einhaltung der Regeln achtet :-)
Das bisherige Projekt ist bis jetzt so oder so schon in C++.
Ich finde es einfach übersichtlicher und man erkennt einfach welche
Funktion wohin gehört.
Gut, zum Ursprung der Frage :-)
Ich hatte in der Tat Ursprünglich gedacht, das ein HAL nur aus Defines
bestehen kann, der einfach die Registernamen usw. wieder "Umbenennt".
Ich werde mir dann mal Gedanken machen wie ich den HAL machen kann für
eine RS485.
Da brauche ich ja :
- RS232 TX
- RS232 RX
- Einen "Direction" Pin
- Baudrate
Was ja dann ungefähr so aussehen könnte (Pseudocode):
halRS485
{
halRS485(uartNr, directionPort, directionPin);
send(uint8_t *data, uint8_t len);
virtual received(uint8_t *data, uint8_t len); // wird vom Interrupt
aufgerufen, und müsste dann vom HAL Nutzer geschrieben werden.
}
Allerdings soll das ganze ja als "Linker lib" umgesetzt werden, ich habe
meine Zweifel ob ich da einfach eine "Virtuelle Funktion" machen kann,
die nachher vom Anwender implementiert wird. Außerdem müsste ich ja pro
erzeugtes "RS485 Objekt" eine andere received Funktion aufrufen. Wie
macht man das am besten? Einen Zeiger auf die Funktion beim erzeugen
(Konstruktor) mit übergeben? Brrrr
Habt Ihr mir da nen Tip?
Das Objekt halRS485 weiß an der uartNr welche Serielle genutzt werden
soll und muss Intern dadurch die Register festlegen.
Bräuchte ich für den Konstruktor dann nicht auch noch das DDR Register ?
Wie wird das denn bei nicht Atmel CPUs festgelegt?
Gibt es da auch PORTx PINx DDRx ?
Im Moment will ich nur Atmel AVR (Mega) nutzen, möchte mir hier jedoch
nicht den Weg verbauen.
Kann ich die PORTx PINx DDRx beim Atmel aus einem "Port" ableiten, damit
ich nicht 3 Parameter übergeben muss? Ich könnte natürlich für die
meisten Kombinationen das im HAL hinterlegen:
-> PORTB = PINB, PORTB, DDRB
-> PORTC = PINC, PORTC, DDRC
usw...
So richtig schön finde ich das allerdings ned, sollte sich aber in
Grenzen halten. Nur sind das dann wieder 3 "Variablen" mehr, die ich
auch im Objekt speichern muss. Im HAL würde man dann anhand des
Parameters directionPort, die Variablen PIN, PORT, DDR setzen und diese
fortan nutzen.
Aber ich denke so muss das dann sein. Also nochmal 3 Variablen für die
Adresse der Ports und 1 Variable für den Pin (oder besser gleich die
Bitmaske). Schnell gedacht, 7 Byte wieder weg (3x uint8_t* und 1x
uint8_t). Oder gibt es da einen besseren Weg? Eine Unterscheidung zur
Laufzeit welches Register genutzt werden muss, dürfte zu lange dauern.
Denke ich da grob richtig?
Jürgen Sachs schrieb:> Allerdings soll das ganze ja als "Linker lib" umgesetzt werden, ich habe> meine Zweifel ob ich da einfach eine "Virtuelle Funktion" machen kann,> die nachher vom Anwender implementiert wird. Außerdem müsste ich ja pro> erzeugtes "RS485 Objekt" eine andere received Funktion aufrufen. Wie> macht man das am besten? Einen Zeiger auf die Funktion beim erzeugen> (Konstruktor) mit übergeben? Brrrr> Habt Ihr mir da nen Tip?
Ob das so Sinn macht, und ob du dir auf einem MC virtuelle Methoden
antun willst, weiß ich nicht.
Aber falls ja: du kannst eine Basisklasse in eine .h und ggf.
einer.cpp schreiben (falls nötig).
Falls eine .cpp: ja, die Objektdatei kann man in eine Lib packen
und der Anwender kann trotzdem eine Klasse davon ableiten, darin die
virtuelle Methode überschreiben und von der abgeleiteten Klasse
ein Objekt bilden.
Wenn er dann von dem Objekt der abgeleiteten Klasse die Methode
aufruft, wird tatsächlich die überschriebene Methode benutzt,
falls:
- er es direkt mit abgeleitet.methode() macht, oder
- eine Referenz oder ein Zeiger vom Typ der Basis auf
abgeleitet verweist und die Methode virtual ist
Genau das ist ja die magische Wirkung von virtual.
Dagegen wird die Methode der Basisklasse aufgerufen, wenn an
ein Basis-Objekt (nicht Referenz) das abgeleitete Objekt zugewiesen
wird (oder als Argument für einen Basis-Parameter einer Funktion
übergeben wird).
Jürgen Sachs schrieb:> Gibt es da auch PORTx PINx DDRx ?> Im Moment will ich nur Atmel AVR (Mega) nutzen, möchte mir hier jedoch> nicht den Weg verbauen.
Das sollte ohnehin nur innerhalb der Klasse relevant sein.
Da muß man natürlich für andere Archtitekturen mit #ifdef etc. arbeiten.
Vom Aufrufer aus gesehen ist das aber hoffentlich nicht zu sehen.
Jürgen Sachs schrieb:> So richtig schön finde ich das allerdings ned, sollte sich aber in> Grenzen halten. Nur sind das dann wieder 3 "Variablen" mehr, die ich> auch im Objekt speichern muss. Im HAL würde man dann anhand des> Parameters directionPort, die Variablen PIN, PORT, DDR setzen und diese> fortan nutzen.
Das verstehe ich auch nicht, wieso du das bei UART wissen willst.
Aber wenn, dann reicht eines der drei, weil bei AVR die jeweiligen
DDR, PIN und PORT immer im gleichen Raster im Speicher eingeblendet
sind. Aus der Adresse des einen kann man die anderen finden.
Setzst du für UART die DDR des jeweiligen Pins? Ich glaube, das
ist überflüssig. Wenn nicht, dann hatte ich bisher viel Glück.
Jürgen Sachs schrieb:> Oder gibt es da einen besseren Weg? Eine Unterscheidung zur> Laufzeit welches Register genutzt werden muss, dürfte zu lange dauern.
Wenn du die ganze Klasse in einer Headerdatei packst und gar
keine .cpp erzeugst, und z.B. die Nummer des UART beim Aufrufer
eine Konstate ist (wird ja meistens der Fall sein, wenn es nicht
zur Laufzeit irgendwie umkonfiguriert wird), dann kannst du auf den
Optimierer im gcc hoffen, der wird das alles eh rauswerfen.
Im Quelltext ist es schön lesbar und portabel, nach dem Compilieren
wird aus so einer Konstruktion:
1
classUART
2
{
3
public:
4
UART(intdeviceid);
5
{
6
switch(deviceid)...// alles konstant beim Kompilieren!
7
{
8
case0:tuwas():// wird wegoptimiert
9
break;
10
case1:tuwasanderes():// wird kompiliert
11
break;
12
}
13
...
14
};
15
16
...
17
UARTmeineUART(1);// kein Funktionsuafruf, da inline
Klaus Wachtler schrieb:> Jürgen Sachs schrieb:>> Bräuchte ich für den Konstruktor dann nicht auch noch das DDR Register ?>> Wozu brauchst du für UART das DDR?
Nicht für den UART.
Bei RS485 braucht man noch einen Pin um die RS485 Treiber auf senden zu
schalten. Dazu braucht man einen Pin. Der muss auf Ausgang geschaltet
werden.
ok.
Der ist dann aber wahrscheinlich sowieso nicht automatisch
festgelegt, indem man die Nummer der UART kennt, sondern kann
ja nach Lust und Laune des Bastlers^H^H^H^H^H^HEntwicklers
irgendwo liegen.
Das wäre dann etwas, was man wahrscheinlich beim
Initialisieren als Parameter übergibt? Dann muß man sich
intern in der Klasse Port und Pin merken (oder Port und
Maske).
Dann gilt aber immer noch: PINx, DDRx und PORTx ligen bei AVR
hintereinander (in dieser Reihenfolge). Es reicht also, sich
eines davon zu merken.
Das stelle ich mir etwa so vor:
1
classUART
2
{
3
public:
4
5
enumeBaud
6
{
7
eBaud300,
8
eBaud600,
9
eBaud1200,
10
eBaud2400,
11
eBaud4800,
12
eBaud9600,
13
eBaud19200,
14
eBaud38400
15
};
16
17
18
UART(uint8_tid_device,
19
volatileuint8_t*p_port_RS485sendflag,
20
uint8_tpin_RS485sendflag,
21
eBaudid_baudrate=eBaud9600
22
)
23
:id_device(id_device),
24
p_port_RS485sendflag(p_port_RS485sendflag),
25
pin_RS485sendflag_mask(1<<pin_RS485sendflag),
26
id_baudrate(id_baudrate)
27
{
28
// Falls p_port_RS485sendflag ungleich NULL, DDR auf Ausgang
29
// setzen:
30
if(p_port_RS485sendflag)
31
{
32
p_port_RS485sendflag[-1]|=pin_RS485sendflag_mask;
33
34
// Analog wäre PORTx als p_port_RS485sendflag[0] zu schreiben
35
// und PINx als p_port_RS485sendflag[-2].
36
}
37
}
38
39
private:
40
41
uint8_tid_device;
42
volatileuint8_t*p_port_RS485sendflag;
43
uint8_tpin_RS485sendflag_mask;
44
uint8_tid_baudrate;
45
};
46
47
...
48
49
UARTmeineUART(0,
50
&PORTA,4,// Adresse PORTx (ggf. NULL) und PIN
51
// für RS485 sende-Signal
52
UART::eBaud9600
53
);
Ansonsten bin ich nicht sicher, ob eine UART-Klasse zwangsweise
auch RS485 können muß.
Vielleicht wäre eine UART-Klasse ohne RS485 sinnvoller, weil es
ja auch RS232 gibt, und das weniger wissen muß.
RS485 könnte man davon ableiten und um die RS485-Geschichten
ergänzen?