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.
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?
"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.
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.
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
voidmeine_fkt(volatileuint8_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.
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.
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.
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
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_ttest(void)
9
{
10
MyDDR|=0x01;
11
MyPORT|=0x01;
12
returnMyPIN&0x02;
13
}
-> 10 Bytes Code, 10 Takte
1
#include<stdint.h>
2
#include<avr/io.h>
3
4
struct{
5
volatileuint8_t*ddr;
6
volatileuint8_t*port;
7
volatileuint8_t*pin;
8
}my_port={&DDRB,&PORTB,&PINB};
9
10
uint8_ttest(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
structport{
5
volatileuint8_t*ddr;
6
volatileuint8_t*port;
7
volatileuint8_t*pin;
8
};
9
10
uint8_ttest(structport*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
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.
@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.
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?
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.
>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;)
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
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
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
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
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_ttest(uint_8ddr_,uint_8port_,uint_8pin_);
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_ttest(uint_8ddr_,uint_8port_,uint_8pin_);
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.
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 !!!
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
@ 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.
>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.
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
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.
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
typedefstruct
2
{
3
volatileunsignedintreg_a;
4
volatileunsignedintreg_b;
5
volatileunsignedintreserved1[3];
6
volatileunsignedintreg_c;
7
volatileunsignedintreserved2[5];
8
volatileunsignedintreg_d;
9
}PERIPH_REGS_T;
10
11
#define PERIPH ((PERIPH_REGS_T *)(0x01234560))
12
13
voidmyFunc(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.