Forum: Compiler & IDEs Wie einen HAL bauen


von Jürgen S. (jsachs)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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
class Uart    
2
{
3
public:
4
5
  enum eBaud
6
    {
7
      eBaud300,
8
      eBaud600,
9
      eBaud1200,
10
      eBaud2400,
11
      eBaud4800,
12
      eBaud9600,
13
      eBaud19200,
14
      eBaud38400
15
    };
16
17
  Uart( uint8_t device = 0,
18
        eBaud   id_baudrate = eBaud9600
19
        )
20
       : device( device )
21
  {
22
    // Initialisierung...
23
    setBaud( id_baudrate );
24
  }
25
26
  ~Uart()
27
  {
28
    // aufräumen ...
29
  }
30
31
  int setBaud( eBaud id_baudrate )
32
  {
33
    // ...
34
  }
35
36
  void write( char c )
37
  {
38
    // Zeichen ausgeben ...
39
  }
40
41
  void write( const char *s )
42
  {
43
    while( s && *s )
44
    {
45
      write( *std::string );
46
    }
47
  }
48
49
  // restliche Methoden...
50
51
private:
52
  uint8_t  device;
53
54
};
55
56
// auf einem System, das nur eine RS232 hat oder nur eine nutzt:
57
void f1()
58
{
59
  // Öffnen:
60
  Uart  eineSerielle();
61
62
  eineSerielle.write( "Hallo\n" );
63
}
64
65
// 2 serielle werden genutzt:
66
void f2()
67
{
68
  // Öffnen:
69
  Uart  eineSerielle( 0 );
70
  Uart  eineAndereSerielle( 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.

von Klaus F. (kfalser)


Lesenswert?

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.

von Jürgen S. (jsachs)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von klaus (Gast)


Lesenswert?

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:
1
void Uart_Setup(UART_REGS* regs, uint32_t baudrate)
2
{
3
   regs->BAUD_REG = /* ... */ baudrate;
4
}

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.

von Karl H. (kbuchegg)


Lesenswert?

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:
>
>
1
> void Uart_Setup(UART_REGS* regs, uint32_t baudrate)
2
> {
3
>    regs->BAUD_REG = /* ... */ baudrate;
4
> }
5
>

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.

von klaus (Gast)


Lesenswert?

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"

von Karl H. (kbuchegg)


Lesenswert?

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 :-)

von Klaus W. (mfgkw)


Lesenswert?

Auch C ist überflüssig; mehr als in Assembler kann man nicht damit 
machen.
Eher weniger.

von Jürgen S. (jsachs)


Lesenswert?

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?

von Klaus W. (mfgkw)


Lesenswert?

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).

von Klaus W. (mfgkw)


Lesenswert?

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?

von Klaus W. (mfgkw)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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
class UART
2
{
3
public:
4
   UART( int deviceid );
5
   {
6
      switch( deviceid ) ... // alles konstant beim Kompilieren!
7
      {
8
          case 0: tuwas(): // wird wegoptimiert
9
                  break;
10
          case 1: tuwasanderes(): // wird kompiliert
11
                  break;
12
      }
13
   ...
14
};
15
16
...
17
   UART meineUART( 1 ); // kein Funktionsuafruf, da inline
18
   meineUART.write( ... );
nicht viel übrig bleiben.

von Jürgen S. (jsachs)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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
class UART
2
{
3
public:
4
5
  enum eBaud
6
    {
7
      eBaud300,
8
      eBaud600,
9
      eBaud1200,
10
      eBaud2400,
11
      eBaud4800,
12
      eBaud9600,
13
      eBaud19200,
14
      eBaud38400
15
    };
16
17
18
  UART( uint8_t             id_device,
19
        volatile uint8_t   *p_port_RS485sendflag,
20
        uint8_t             pin_RS485sendflag,
21
        eBaud               id_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_t             id_device;
42
  volatile uint8_t   *p_port_RS485sendflag;
43
  uint8_t             pin_RS485sendflag_mask;
44
  uint8_t             id_baudrate;
45
};
46
47
...
48
49
  UART   meineUART( 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?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.