Forum: Compiler & IDEs Port-Informationen mittel Struktur verarbeiten


von Visitior (Gast)


Lesenswert?

Hallo,
ich möchte gerne die Portx, DDRx und PINx Register mittels einer 
Struktur bündeln und via Zeiger an andere Funktionen übergeben, damit in 
den Funktionen mit den Informationen entsprechend richtig auf Port und 
Datenrichtungsregister zugegriffen werden kann. Doch leider fehlt mir 
z.Z. noch das nötige wissen bzgl. Zeiger auf Strukturen bzw. Funktionen.

Evtl. kann mir jemand ein kurzes Beispiel bzgl des Vorgehens posten. 
Hier noch mal mein Vorhaben :

0) In main()
1) Anlegen einer Struktur (ggf. als typedef) für Portzugriffe.
2) Deklaration der Struktur für späteren Zugriff. Ggf. mehr als eine !!!
3) Initialisieren der deklarierten Struktur mit den gewünschten 
Registern (z.B. PORTB , DDRB , PINB)
4) Aufruf einer Funktion miter der Struktur als Übergabeparameter
5) Richtiger Zugriff auf die Ports mittels des Strukturinhaltes in der 
Funktion (also nicht mehr in main() !!!

Was möchte ich damit erreichen :
Hier durch sollen die später eingesetzten Funktionen flexibler sein, da 
ich nur einmal mit der Struktur in main() eine Zuweisung der PORTs 
mache. D.h. ich kann mehrere Zuweisung in main() durchführen und die 
gleiche Funktion mit unterschiedlichen Strukturzeigern aufrufen.

Ich hoffe jemand hat ne Idee / Ansatz.

von Grrrr (Gast)


Lesenswert?

Was für eine Idee fehlt denn?

Du hast doch das "wie" schon skizziert. Schreib's doch einfach.

Was für Informationen über das "Vorgehen" fehlen Dir?

Darf ich mal neugierdehalber fragen, wielange Du schon programmierst?

Ich glaube fast, das Du da so eine abstrakte Idee hast, die keinen 
praktischen Wert hat.

Was ist eigentlich das Problem, das Du lösen willst?

von Grrrr (Gast)


Lesenswert?

"Richtig" greift man auf Ports so zu (mal nen AVR vorausgesetzt)

DDRB = <wert>;
<variable> = PINB;
PORTB = <wert>;

Was willst Du denn da in Funktionen und Strukturen abstrahieren oder 
kapseln?

Verallgemeinerung erreichst Du mit Preprozessor-defines.

Funktional gibt es (fast) nichts zu beachten. Was darüber hinaus geht, 
hat mit der angeschlossenen Peripherie zu tun, sonst nichts. Dann 
abstrahiert man aber die Kontrolle der Peripherie aber nicht die der 
Ports.

von visitor (Gast)


Lesenswert?

Hi,

mein Problem besteht darin , dass ich mir nicht sicher bin, ob ich die 
Struktur wie folgt schreiben muss :

zu 1)
typedef struct
{
 volatile uint8_t *PORT;
 volatile uint8_t *PIN;
 volatile uint8_t *DDR;
}tIOFunctions;

zu 2)
tIOFunctions tSensor1 = (PORTB , PIND , DDRB);

zu 4)
Testfunktion(&tSensor1) bzw. Testfunktion(tSensor1)

Mein Problem liegt in dem Umgang mit den Zeigern, da ich diese auf 
Strukturen bisher nie angewendet habe. Weiterhin verbirgt sich hinter 
PORTB etc. ja eignedlich auch nur eine Adresse (z.B. 0xYY). D.h. heißt 
ich muss eigendlich nur die "richtige" Adresse durchreichen.

Da ich aber nicht mittels Try and Error solange rumtüfteln möchte bis es 
klappt, habe ich die Frage ans Forum gestellt ob jemand eine Lösung hat 
bzw. kurz erklären kann wie man vorgeht. Ich möchte es verstehen.

von Klaus W. (mfgkw)


Lesenswert?

Warum übergibst du nicht einfach direkt die Adressen an deine 
Funktionen?
DDRx, PIx und PORTx sind bei AVR doch in den Speicher eingeblendet.
Wenn du also in der Funktion einen Parameter vom Typ uint8_t*
deklarierst und dann z.B. &PORTA übergibst, ist doch alles ok.

Beispiel:
1
   void meine_fkt( volatile uint8_t *p_port )
2
   {
3
       *p_port = 0x12;
4
   }
5
...
6
   meine_fkt( &PORTA );

was leider nicht geht, ist Port+Bit per Adresse oder Referenz zu
übergeben, da die Bits in C nicht adressierbar sind.

von visitor (Gast)


Lesenswert?

So was in der Art hatte ich auch schon, doch bei einem Konstrukt wie :

void meine_fkt( volatile uint8_t *p_port, volatile uint8_t *p_pin, 
volatile uint8_t *p_ddr )

erhalte ich Fehlermeldung nach Art von function has too many arguments 
!!!
Daher kam ich auf die Idee mit der Sruktur.

von Karl H. (kbuchegg)


Lesenswert?

visitor schrieb:
> So was in der Art hatte ich auch schon, doch bei einem Konstrukt wie :
>
> void meine_fkt( volatile uint8_t *p_port, volatile uint8_t *p_pin,
> volatile uint8_t *p_ddr )
>
> erhalte ich Fehlermeldung nach Art von function has too many arguments
> !!!

Du verwendest gcc?

WEnn ja und die Fehlermeldung ist immer noch da, dann stimmt 
wahrscheinlich der Protoyp oder der Aufruf nicht mit der Funktion 
überein.

> Daher kam ich auf die Idee mit der Sruktur.
Man kan das schon machen.
Besser ist aber, wenn du dem Fehler auf den Grund gehst. Man kann sich 
in C einmal vor einem Fehler verstecken, aber man kann sich nicht immer 
vor dem Fehler verstecken.

von Karl H. (kbuchegg)


Lesenswert?

visitor schrieb:

> Strukturen bisher nie angewendet habe. Weiterhin verbirgt sich hinter
> PORTB etc. ja eignedlich auch nur eine Adresse (z.B. 0xYY).

Nicht ganz.
Das ganze ist so hingetrickst, dass hinter PORTB wieder ein Wert steht. 
Die Adresse vom Port wäre &PORTB

von Andreas F. (aferber)


Lesenswert?

Visitior schrieb:
> ich möchte gerne die Portx, DDRx und PINx Register mittels einer
> Struktur bündeln und via Zeiger an andere Funktionen übergeben, damit in
> den Funktionen mit den Informationen entsprechend richtig auf Port und
> Datenrichtungsregister zugegriffen werden kann.

Wie man das prinzipiell macht haben andere hier ja bereits dargelegt, 
die Frage ist nur, ob das im Ergebnis auch sinnvoll ist. Der Zugriff 
über Pointerindirektion bedeutet bei AVR in vielen Fällen einen 
erheblichen Code- und Zeitoverhead. Wenn die Sache zeit- und 
platzunkritisch ist, kann man das machen, die Konsequenzen müssen einem 
aber immer bewusst sein.

Die Angaben zu den Kompilaten der folgenden Beispiele beziehen sich 
immer auf einen ATmega64 mit "-O2"-Optimierung, bei anderen Controllern 
und Optimierungen kann das leicht unterschiedlich ausfallen, die Tendenz 
bleibt aber.
1
#include <stdint.h>
2
#include <avr/io.h>
3
4
#define MyDDR DDRB
5
#define MyPORT PORTB
6
#define MyPIN PINB
7
8
uint8_t test(void)
9
{
10
        MyDDR |= 0x01;
11
        MyPORT |= 0x01;
12
        return MyPIN & 0x02;
13
}

-> 10 Bytes Code, 10 Takte
1
#include <stdint.h>
2
#include <avr/io.h>
3
4
struct {
5
        volatile uint8_t *ddr;
6
        volatile uint8_t *port;
7
        volatile uint8_t *pin;
8
} my_port = { &DDRB, &PORTB, &PINB };
9
10
uint8_t test(void)
11
{
12
        *my_port.ddr |= 0x01;
13
        *my_port.port |= 0x01;
14
        return *my_port.pin & 0x02;
15
}

-> 42 Bytes Code, 6 Bytes Daten, 29 Takte

Mit Übergabe der struct an die Funktion via Pointer wird es noch 
schlimmer:
1
#include <stdint.h>
2
#include <avr/io.h>
3
4
struct port {
5
        volatile uint8_t *ddr;
6
        volatile uint8_t *port;
7
        volatile uint8_t *pin;
8
};
9
10
uint8_t test(struct port *p)
11
{
12
        *p->ddr |= 0x01;
13
        *p->port |= 0x01;
14
        return *p->pin & 0x02;
15
}

-> auch 42 Bytes Code, aber diesmal 40 Takte

Der Flashbedarf wird also vervierfacht, die Ausführungszeit je nach 
Variante verdreifacht oder vervierfacht.

Ich hoffe ich habe mich bei den Takten nicht verzählt ;-)

Andreas

von Klaus W. (mfgkw)


Lesenswert?

Naja, es wird ja erstens nicht viele Funktionen geben, die sowohl DDR 
als auch PORT/PIN brauchen.
Zweitens könnte man noch geizen, wenn man sich die Adressen
anschaut.
Zumindest soweit ich es gesehen habe, sind bei den AVR die
Abstände zwischen zusammengehörigen DDR/P)IN/PORT immer gleich...
D.h. es reicht eigentlich, eines davon zu übergeben.

von visitor (Gast)


Lesenswert?

@A. Faber
Genau so ein Beispiel habe ich gesucht ! Danke.

Das mit dem mehr an Code/Takten war mir schon klar, doch mir gehts gehts 
vorerst um den Komfort mit dem Funktionsaufruf innerhalb der Funktion 
direkt auf die richtigen Ports zuzugreifen.

Die #defines in deinem ersten Beispiel würden bei Verwendung einer 
Funktion mit Zugriff auf unterschiedlichen Ports zu einem Overhead 
innerhalb der Funktion führen welcher mit Anzahl der Zugriffe auf 
verschiedene Ports ansteigt. Bei der Struktur währe der Overhead aber 
nur einmalig dar.

@K. Wachtler
Die Idee, z.B. PORTB als Offset zu nehmen und dann PINB bzw. DDRB 
mittels Adressinkrement/-dekrement anzusprechen hatte ich auch schon. 
Ich persönlich halte dies aber zu kryptisch (zu sehr Assembler-like ! 
soll jetzt nicht negativ sein !) und da ich dieses Thema mit der 
Struktur und Zeiger noch nie benutzt habe sah ich das für mich eher als 
Herausforderung an.

von Grrrr (Gast)


Lesenswert?

visitor schrieb:
> Die #defines in deinem ersten Beispiel würden bei Verwendung einer
> Funktion mit Zugriff auf unterschiedlichen Ports zu einem Overhead
> innerhalb der Funktion führen welcher mit Anzahl der Zugriffe auf
> verschiedene Ports ansteigt. Bei der Struktur währe der Overhead aber
> nur einmalig dar.

Ich verstehe nicht, was Du da vergleichst. Der Code zeigt doch, das der 
Aufwand an Code und Ausführungszeit mit dem define am geringsten ist.

Nehmen wir mal an, Du verwendest nur einen Port, so steigt mit jeder 
Verwendung der Aufwand linear an. Bei zwei Ports eben um die Anzahl der 
weiteren Zugriffe. Mit der Struktur ist der Aufwand höher, weil noch 
über den Zeigerzugriff gegangen werden muss, steigt aber ebenso linear 
mit der Anzahl der Zugriffe an. Sind wir uns soweit einig? 
(Optimierungen jetzt mal aussen vor gelassen).
Du kannst die Zeiger natürlich noch mal zwischenspeichern. Aber das ist 
wohl nicht Dein Hauptarguement, oder? Da wäre der Aufwand jedenfalls 
auch höher als bei den Defines.

Kannst Du das mal bitte näher erläutern, bitte? Welcher Overhead? Am 
besten mit einem kleinen Beispiel.

Ich bin zwar nach wie vor der Meinung, das Dein Vorhaben keinen Sinn 
macht, aber ich will Dir das garnicht ausreden. Deine Hartnäckigkeit 
bewegt mich aber doch nachzufragen. Mag ja sein, das ich was nicht 
verstehe und an der Stelle was lernen kann.

Du hast ursprünglich auch garnichts vom Overhead geschrieben. Nur das 
man dann "richtig" zugreifen kann. Könntest Du bitte erklären wieso das 
mit defines nicht geht, oder wenn Du meinst, das es geht, was ist dann 
der Vorteil?

von Grrrr (Gast)


Lesenswert?

Wo liegt der Komfort in:
1
   *p->ddr |= 0x01;
2
   *p->port |= 0x01;
3
   return *p->pin & 0x02;

im Vergleich zu:
1
   DDRB |= 0x01;
2
   PORTB |= 0x01;
3
   return PINB & 0x02;

Ich würde gerne Deine Bewertungsmasstäbe und den Gedankengang dabei 
verstehen.

von visitor (Gast)


Lesenswert?

ich hoffe ich kann das Thema mit dem Overhead (bzw. mit dem was ich 
meine) kurz erklären :

- #defines sind Textersatz während der Compilierzeit, d.h. während der 
Laufzeit habe ich keine Möglichkeit eine Änderung vorzunehmen !!!

Hier das obige Beispiel :

#define MyDDR DDRB
#define MyPORT PORTB
#define MyPIN PINB

uint8_t test(void)
{
        MyDDR |= 0x01;
        MyPORT |= 0x01;
        return MyPIN & 0x02;
}

nun möchte ich aber mehrere I/O´s mit dieser Funktion ansprechen (nict 
gleichzeitig sondern durch separate Funktionsaufrufe)

#define MyDDR1 DDRB
#define MyPORT1 PORTB
#define MyPIN1 PINB

#define MyDDR2 DDRA
#define MyPORT2 PORTA
#define MyPIN2 PINA

#define MyDDRx DDRC
#define MyPORTx PORTC
#define MyPINx PINC

uint8_t test(unsigned char WelcherPortDennNu)
{
  if(WelcherPortDennNu == 1)
        MyDDR1 |= 0x01;
        MyPORT1 |= 0x01;
        return MyPIN1 & 0x02;
  else if(WelcherPortDennNu == 2)
        MyDDR2 |= 0x01;
        MyPORT2 |= 0x01;
        return MyPIN2 & 0x02;
  else
        MyDDRx |= 0x01;
        MyPORTx |= 0x01;
        return MyPINx & 0x02;
}

wie man nun sieht ensteht ein Overhead via if-Konstrukt innerhalb der 
Funktion was die Funktion zum anderen auch weniger gut lesbar macht. 
Zumal immer das gleiche gemacht wird, halt nur mit unterschiedlichen 
Ports. Eine Struktur kann ich im Zweifelsfall sogar mit neuen Werten 
(PORTS etc.) füllen und muss innerhalb der Funktion dies nicht via if 
etc. explizit abfragen.

Mit dem Beispiel von A. Ferber konnte ich mein Problem lösen und habe 
auch das Vorgehen verstanden. Da ich aktuell nichts zeitkritisches habe 
ist die Laufzeit nicht soooo relevant mir gehts erstmal um die 
Lesbarkeit und dem Umgang mit Zeigern auf Strukturen am Beíspiel von 
Port-Zugriffen.

von holger (Gast)


Lesenswert?

>Wo liegt der Komfort in:
>
>   *p->ddr |= 0x01;
>   *p->port |= 0x01;
>   return *p->pin & 0x02;
>
>im Vergleich zu:
>
>   DDRB |= 0x01;
>   PORTB |= 0x01;
>   return PINB & 0x02;
>
>Ich würde gerne Deine Bewertungsmasstäbe und den Gedankengang dabei
>verstehen.

Schliess mal drei Text LCDs mit beliebiger Verteilung der
Pins an einen uC an. Die LCD Funktionen bekommen einen Zeiger
auf die Struktur zu dem jeweiligen LCD.
Hab ich mal ausprobiert. Man kann so z.B. drei LCDs an
einem 8 Bit Port betreiben;)

von Peter D. (peda)


Lesenswert?

holger schrieb:
> Schliess mal drei Text LCDs mit beliebiger Verteilung der
> Pins an einen uC an. Die LCD Funktionen bekommen einen Zeiger
> auf die Struktur zu dem jeweiligen LCD.
> Hab ich mal ausprobiert. Man kann so z.B. drei LCDs an
> einem 8 Bit Port betreiben;)

Warum sollte man denn die IO-Pins so unnütz verschwenden?

Die 3 LCDs werden natürlich alle parallel geschaltet, nur der E-Pin 
nicht.
Die 3 E-Pins legt man auf einen Port und dann braucht man nur noch ne 
Maskenvariable, mit der das LCD ausgewählt wird, d.h. wo an der Stelle 
des E-Pins ne 1 steht.
Dann kann man sogar alle 3 LCDs parallel initialisieren und muß es nicht 
nacheinander machen.


Peter

von Peter D. (peda)


Lesenswert?

Ich wüßte auch gern, wo man es braucht, die Pinfunktion erst zur 
Laufzeit bestimmen zu müssen.

Der Codeaufwand ist erheblich, da man für jede Pinvariable 3 Byte 
bräuchte (Pointer + Bitmaske). Will man alle 3 Register übergeben, sind 
das dann 7 Byte. Damit kriegt man nen gewaltigen Overhead rein. 
Zusätzlich werden viele Funktionen nen Stackframe anlegen müssen, da sie 
dann nicht mehr mit den Arbeitsregistern hinkommen.


Wenn ich z.B. bis zu 8 I2C-Busse, 8 SW-UARTs, 8 1-Wire usw. bräuchte, 
würde ich die Auswahl einfach mit ner Maskenvariable (1 Byte) machen.


Peter

von Andreas F. (aferber)


Lesenswert?

visitor schrieb:
> @A. Faber

"Ferber" bitte, da bestehe ich drauf ;-)

> Die #defines in deinem ersten Beispiel würden bei Verwendung einer
> Funktion mit Zugriff auf unterschiedlichen Ports zu einem Overhead
> innerhalb der Funktion führen welcher mit Anzahl der Zugriffe auf
> verschiedene Ports ansteigt. Bei der Struktur währe der Overhead aber
> nur einmalig dar.

Ganz grosses "Jein". In vielen Fällen braucht bei eingeschaltetem 
Optimizer schon der Funktionsaufruf selbst mehr Code als für einen 
(direkt eingebetteten) Portzugriff nötig wäre, mit jedem weiteren Aufruf 
wird der Overhead dann nur immer noch größer.

Eine definitive Aussage kann man natürlich nur treffen, wenn man die 
beiden Varianten im konkreten Code miteinander vergleicht.

Andreas

von Peter D. (peda)


Lesenswert?

holger schrieb:
> Schliess mal drei Text LCDs mit beliebiger Verteilung der
> Pins an einen uC an.

P.S.:
Klang für mich so, als ob jedes LCD komplett andere Pins kriegt.

Aber warscheinlich meinst Du das gleiche, wie ich.
4 * Daten, 1 * RS, 3 * E = 8 Pins


Peter

von Grrrr (Gast)


Lesenswert?

visitor schrieb:
> wie man nun sieht ensteht ein Overhead via if-Konstrukt innerhalb der
> Funktion was die Funktion zum anderen auch weniger gut lesbar macht.
> Zumal immer das gleiche gemacht wird, halt nur mit unterschiedlichen
> Ports.

Vielen Dank erstmal, das Du geantwortet hast. Leider schreibst Du noch 
nicht welches Problem Du damit lösen willst, aber es lässt sich denken, 
dass Du, weil Du die gleiche Funktion auf mehrere verschiedene Ports 
anwenden willst, befürchtest, bei CopynPaste und anschliessendem 
manuellen ändern der Ports, einen zu verpassen. Also, à la:
1
uint8_t test(uint_8 ddr_, uint_8 port_, uint_8 pin_);
2
3
.
4
.
5
.
6
test (DDRA, PORTA, PINA);
7
test (DDRB, PORTB, PINA);     // oops, da habe ich pin vergessen zu ändern
8
.
9
.
10
.

Stimmt meine Vermutung?

Was mir auffällt, (ich hoffe es ist Dir recht, dass ich Deine Denkweise 
untersuche) ist, dass Du aus der Tatsache, das Du die Ports 
parametrisieren (also bzgl. der Ports verallgemeinern) willst, gleich 
auf eine Strukur kommst. Dafür gibt es eigentlich, falls meine Vermutung 
über das Problem, das Du lösen willst stimmt, keinen Grund der den 
Overhead wegen der Struktur rechtfertigt.

Genauso kannst Du via Textersatz dafür sorgen, das Dir das nicht 
passiert:
1
uint8_t test(uint_8 ddr_, uint_8 port_, uint_8 pin_);
2
// bin mir gerade wg. der Datentypen nicht sicher, aber das lässt sich ja noch herausfinden
3
4
#define TESTA (test (DDRA, PORTA, PINA))
5
#define TESTB (test (DDRB, PORTB, PINB))
6
// mit string concatenation ## geht das evtl. noch etwas kürzer

So ist die Funktion also bzgl. der Port parametrisiert, aber Du kannst 
Dich nicht mehr so leicht vertun und Du hast den Overhead durch die 
Struktur nicht.

Vielleicht ist das Problem ja doch ein anderes. Falls ja, schreib doch 
bitte was es ist.

Ich habe vorher geschrieben, das ich Dir Deine Methode nicht ausreden 
will und nun schlage ich Dir doch was vor. Bitte nimm das als 
konstruktiven Vorschlag. Du kannst es natürlich machen wie Du willst.

von Visitor (Gast)


Lesenswert?

Hallo,

zu erst an A. Ferber :

Sorry, war ein Schreibfehler, nicht böse gemeint :-)

nun zu Grrrr :

Wie bereits anfänglich erklärt wollte ich erst mal wissen wie ich 
mittels Struktur auf die Ports zugreifen kann, da all meine Versuche 
zuvor gescheitert sind. Nun weiß ich es, d.h. ich habe etwas 
dazugelernt. Ob ich dieses nun auch so in meinem nächsten Projekt nutzen 
werde steht auf einem anderen Blatt Papier.

Natürlich ist dein Nachhaken und Fragen berechtigt, ich werde mir deine 
Anmerkungen auch mal durch den Kopf gehen lassen und evtl. sogar diesen 
Weg beschreiten. Ich muss halt sehen auf welchen Weg ich besser zum Ziel 
komme.

Was habe ich nun vor :
Ich habe hier ein schönes Projekt bzgl. des DS1820 gefunden (Autor : 
BvB). Dieses ist auf einem 8051er System programmiert. Dieses möchte ich 
für mich zuerst auf einem AVR anpassen aber nicht so wie die Vorlage mit 
fest definierten Portzugriffen in den OneWire Routinen sondern 
universell (daher die Frage bzgl. der Struktur). So dass ich ggf. später 
zum Beispiel mehrere DS1820 Sensoren je nach belieben an einem Bus oder 
aber auch an separaten Pin´s parallel bzw. sogar mehere Busse an 
verschiedenen Pin´s betreiben kann.

JETZT BITTE NICHT NACH DEM SINN FRAGEN !!! ICH MÖCHTE ES EINFACH EINMAL 
REALISIEREN !!!

von Peter D. (peda)


Lesenswert?

Visitor schrieb:
> So dass ich ggf. später
> zum Beispiel mehrere DS1820 Sensoren je nach belieben an einem Bus oder
> aber auch an separaten Pin´s parallel bzw. sogar mehere Busse an
> verschiedenen Pin´s betreiben kann.

Man kann alle Sensoren an einen Bus hängen.

Und falls der Bus zu lang wird, kann man einfach bis zu 8 Busse an einem 
Port benutzen und die Routinen greifen dann per Maskenbyte auf einen Bus 
zu. Man kann aber auch z.B. die Temperaturwandlung auf allen 8 Bussen 
gleichzeitig starten.
Man kann entweder das Maskenbyte als globale Variable anlegen oder den 
Funktionen als Argment übergeben.


Peter

von Grrrr (Gast)


Lesenswert?

@ Visitor

Bedauerlich, das Du jetzt anfängst zu schreien.

Lassen wir das. Du wirst schon noch an anderer Stelle auf Zielhierachien 
stossen. (Ein Loch ist im Eimer...).

Na dann, viel Erfolg.

von holger (Gast)


Lesenswert?

>P.S.:
>Klang für mich so, als ob jedes LCD komplett andere Pins kriegt.

So war das auch gemeint.

>Aber warscheinlich meinst Du das gleiche, wie ich.
>4 * Daten, 1 * RS, 3 * E = 8 Pins

Nein, ich habe es nur in dieser Konstellation getestet.
Die Pins für die drei LCDs dürfen an jedem beliebigen Port
liegen. Auch die Datenleitungen müssen nicht auf einem Port
liegen. Du kannst nehmen was an Pins frei ist.

von Ulrich Lukas (Gast)


Lesenswert?

Hi,


was für ein Zufall - die Frage mit dem Portzugriff über eine Struktur 
hatte sich für mich vor kurzem ganz genauso gestellt.

An sich ist das für einen C-Programmierer ja die naheliegenste Sache, 
eine Struktur für den Pinzugriff zu verwenden. Dass der Overhead beim 
AVR derart erheblich ist, ist schade. IMO könnte der Compiler das etwas 
besser hinbekommen, beim Übergeben einer konstanten Adresse bzw. einer 
konstanten Struktur den gleichen Code zu erzeugen, wie eine Konstante (a 
la "PORTB") im Funktionsrumpf.

Vorteil von Struktur wäre für mich ganz naheliegend.

Die Sichtbarkeitsregeln von C.

Während der Ausbildung habe ich noch gelernt, dass viele 
Präprozessor-Defines um jeden Preis zu vermeiden sind.

Ich kann das nachvollziehen. Denn C ist doch kein Makroassembler..

Um Namenskollisionen zu vermeiden, muss man IMO auch unhandlich lange 
Namen für die Defines verwenden. Alles großgeschrieben, noch dazu, damit 
das sauber aussieht.


Also, wenn man mich fragt, irgendwie ist das äußerst suboptimal gelöst..


Gruß,
Ulrich

von Karl H. (kbuchegg)


Lesenswert?

Ulrich Lukas schrieb:

> An sich ist das für einen C-Programmierer ja die naheliegenste Sache,
> eine Struktur für den Pinzugriff zu verwenden.

Ja.
Das wäre grundsätzlich eine schöne Sache.
Man könnte dann einen schönen HAL erzeugen, mit Geräten derren 
Pin-Konfiguration in einer Struktur liegen.

> Dass der Overhead beim
> AVR derart erheblich ist, ist schade.

lässt sich aber nicht verhindern (ausser vielleicht bei inlinen). Das 
Problem ist ja eigentlich nicht, dass sich beim Zugriff über Pointer auf 
Strukturmember der Code so aufbläht, sondern dass die Alternative, der 
Zugriff über direkte Portzugriffe, deren Adresse über die bekannten 
Makros reinkommt, alles so hingetrickst ist, dass der Compiler zum Teil 
in die Lage versetzt wird, die speziellen AVR Befehle zum Portzugriff zu 
benutzen.

   PORTB |= 0x02;

wird nun mal nicht wörtlich übersetzt, sondern irgendwann in der 
Optimizerkette filtert der Compiler diese Dinge anhand der konstanten 
Werte aus und benutzt die speziellen Port-Bit-setzen/löschen Befehle. 
Und gegen die kommt man nun mal nicht an.
Verschleiere ich dem Compiler den Port bzw. die Konstante nur kräftig 
genug, dann geht das dann nicht mehr und er muss tatsächlich den langen 
Weg über Port lesen - verodern - Port schreiben gehen. Oftmals auch noch 
flankiert vom freischaufeln eines Arbeitsregisters. Und das dauert dann 
eben.

Das 'Problem' besteht also eher darin, dass die optimierte Variante mit 
direktem Zugriff so dermassen viel besser ist, als die kanonische 
Lösung, dass man sich diesen Vorteil nicht durch die Finger laufen 
lassen will. Zumal dann auch noch dazu kommt, dass man die Universalität 
die man sich teuer erkauft, oftmals gar nicht braucht.

3 LCD an einem µC ist zwar ein schönes Beispiel, und ich kann das auch 
grundsätzlich alles nachvollziehen, aber Hand aufs Herz: Wie oft kommt 
diese Situation tatsächlich vor. Lohnt es sich für diesen Fall 
vorzubauen oder ist es nicht besser in diesem seltenen Speziallfall dann 
auch mit einer speziell angepassten Funktion zu operieren und ansonsten 
die gut optimierende Variante zu benutzen.

> IMO könnte der Compiler das etwas
> besser hinbekommen, beim Übergeben einer konstanten Adresse bzw. einer
> konstanten Struktur den gleichen Code zu erzeugen, wie eine Konstante (a
> la "PORTB") im Funktionsrumpf.

Geht aber nur dann, wenn die Funktion geinlined wird. Die Funktion 
selbst kann ja nicht davon ausgehen, dass sie immer mit der gleichen 
Struktur gefüttert wird.
Allerdings müsste dann der Compiler in der Constant Folding Section auch 
Pointer abklappern, ab sie auf Konstante zeigen, die sich nie ändern. 
Und da lauft man dann auch in das Dilemma, das in C const Variablen 
keine Konstanten sind, so wie sie es in C sind. Da gibt es leicht 
unterschiedliche Regelungen, die IMHO in C++ besser für Optimierungen 
geeignet sind.


> Während der Ausbildung habe ich noch gelernt, dass viele
> Präprozessor-Defines um jeden Preis zu vermeiden sind.

Das ist grundsätzlich auch nicht zu bezweifeln.
Allerdings sind diese Daumenregeln auf µC in der Klasse in der wir uns 
hier bewegen etwas zu lockern. Nicht alles, was von Desktopklasse 
aufwärts, sich als gutes Softwaredesign durchgesetzt hat, ist in unserer 
Rechner-Klasse sinnvoll.
Zb. globale Variablen sind auch so ein Fall
Zb. Allokierung von dynamischen Datenstrukturen. Besonders in der
    Stringverarbeitung ein nicht uninteressantes Thema.

> Um Namenskollisionen zu vermeiden, muss man IMO auch unhandlich lange
> Namen für die Defines verwenden. Alles großgeschrieben, noch dazu, damit
> das sauber aussieht.

Na ja. unendlich ist etwas übertrieben.
Mit einigermassen guten Systematiken ist das schon noch beherrschbar in 
den Griff zu kriegen.

> Also, wenn man mich fragt, irgendwie ist das äußerst suboptimal gelöst..

Ist leider so.
Universalität fordert ihren Preis.
Oder wie ein kluger Mann einmal sagte: Es ist leicht ein Programm 
schnell zu machen, wenn es nicht universell zu sein braucht.

von Wolfgang (Gast)


Lesenswert?

Hallo,

ich kenne mich mit dem AVR nicht aus.
Bei meine ARM Controller wurde die Beschreibung der Register einer 
Peripherals auch mit Strukturen gelöst. Und dies ohne Speicheroverhead.
Das Layout wird nur dem Compiler mitgeteilt, im Speicher steht nur ein 
Verweis auf die Anfagsadresse des Speicherbereichs des Peripherals.
Der Nachteil dabei ist, dass die verschiedenen Instanzen der Perpipherie 
das gleiche Speicherlayout haben müssen (gleicher Abstand zwischen den 
Registern).
1
typedef struct
2
{
3
  volatile unsigned int reg_a;
4
  volatile unsigned int reg_b;
5
  volatile unsigned int reserved1 [3];
6
  volatile unsigned int reg_c;
7
  volatile unsigned int reserved2 [5];
8
  volatile unsigned int reg_d;
9
} PERIPH_REGS_T;  
10
  
11
#define PERIPH ((PERIPH_REGS_T *)(0x01234560))
12
13
void myFunc(PERIPH_REGS_T *regs)
14
{
15
  regs->reg_a = 1234;
16
  regs->reg_b = 56;
17
}
18
19
/* Ein Funktionsaufruf kann nun so aussehen*/
20
myFunc(&PERIPH);

Ich kann mir vorstellen dass dem Ersteller etwa so etwas vorschwebt.

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.