Hallo,
ich bin schon länger am überlegen, wie ich die Ausgänge eines
Mikrocontrollers variable initialisieren kann. So möchte ich am Anfang
des Codes z.B.
1
#define Port PORTB
2
#define Pin 2
im Code möchte ich dann eine Funktion aufrufen
1
setDataPin(Port, Pin)
in der Funktion soll dann der Port auf auf Ausgang und auf Low gesetzt
werden.
1
void setDataPin( uint8_t Port, uint8_t Pin ){
2
DDRB |= _BV( Pin ); // setze als ausgang
3
Port |= _BV( Pin ); // ausgang ist low
4
}
als low setzen funktioniert (Port ist ja auch schon richtig beschrieben
(PORTB)). Aber das mit dem Ausgang setzen bekomme ich einfach nicht hin.
So funktioniert es zwar, aber es ist DDRB vorgegeben (Das möchte ich
variable machen)
Hat von euch jemand eine Idee, wie man das umsetzen könnte?
Lars
Lars schrieb:> Hat von euch jemand eine Idee, wie man das umsetzen könnte?
Klar!
Im Anhang ein Arduino Beispiel.
Das Prinzip ist allerdings auf alle AVR/Ide übertragbar
ui schrieb:> gehts um den AVR?
ahso, habe ich vergessen zu sagen. Ja, es ist ein Atmega.
ui schrieb:> warum übergibst du der Funktion nicht einfach noch> zusäztlich DDRB? bzw. DDRA? oder DDRE?> was spricht denn da dagegen?
Wäre natürlich auch eine möglichkeit
Arduino F. schrieb:> Im Anhang ein Arduino Beispiel.
danke dir, gucke es mir gleich mal an
Arduino F. schrieb:> Das Prinzip ist allerdings auf alle AVR/Ide übertragbar
Grandioses Beispiel.
Wobei der TO dann bereits daran scheitert dass er in allen
anderen IDEs die Datei Pin.h nicht bekommt und dasteht wie
am Anfang.
Die Formulierung des Problems bzw des Anliegens vom TO ist
leider nicht klar genug dass man darauf eine gute Antwort
liefern könnte:
Lars schrieb:> in der Funktion soll dann der Port auf auf Ausgang und auf Low gesetzt> werden.
Was soll es Sinn machen bei jedem Aufruf den Port neu zu
initialisieren ... und für eine statische Initialisierung
brauch man keine derartige Funktion.
Lars schrieb:> ich bin schon länger am überlegen, wie ich die Ausgänge eines> Mikrocontrollers variable initialisieren kann.> [...]> Hat von euch jemand eine Idee, wie man das umsetzen könnte?
Scharfanalyse schrieb:> Wobei der TO dann bereits daran scheitert dass er in allen> anderen IDEs die Datei Pin.h nicht bekommt und dasteht wie> am Anfang.
Das verstehe ich nicht.
Scharfanalyse schrieb:> Die Formulierung des Problems bzw des Anliegens vom TO ist> leider nicht klar genug ...
Das sehe ich auch so.
Neige aber in solchen Fällen dazu eine Antwort zu geben, die passen
könnte. Mit dem Risiko, dass sie "falsch" ist.
Scharfanalyse schrieb:> Soviel Bescheidenheit muss sein.
Danke, ich war schon etwas verwirrt...
> Bescheidenheit
? Wozu ?
Das öffentliche Anerkennen, einen Bock geschossen zu haben, verdient
Respekt!
Denn, längst nicht alle sind dazu bereit.
Lars schrieb:> ich bin schon länger am überlegen, wie ich die Ausgänge eines> Mikrocontrollers variable initialisieren kann. So möchte ich am Anfang> des Codes z.B.#define Port PORTB> #define Pin 2>> im Code möchte ich dann eine Funktion aufrufen setDataPin(Port, Pin)
Mein Rat: Laß so etwas lieber bleiben - und zwar grundsätzlich!
Man sieht hier im Forum massenweise Beiträge, wo Leute genau sowas tun
und regelmäßig dabei sich selbst vehemente Probleme bescheren.
Spaghetticode in C.
Grund:
Es wird nichts wirklich abstrahiert und so gelangen Dinge, die
eigentlich in einen Lowlevel-Treiber gehören bis hinein nach main.c.
Mach es anders: kapsele deine Funktionalität von und nach außen in
passable Lowlevel-Treiber, damit du in den höheren Programmschichten
dich nicht mit deren Details herumschlagen mußt.
Also anstelle deines SetPin(Port,Nummer) sollte dein Treiber etwa sowas
liefern:
Ventil_Auf();
Ventil_Zu();
und so weiter. In deinen Programmteilen, die die eigentlichen Abläufe
machen, sollte es egal sein, über welchen Pin das Ventil nun auf- oder
zugemacht wird.
Wenn du meinem Rat folgst, dann kannst du einen Großteil deines Codes
auf ziemlich beliebiger Hardware laufen lassen, denn er ist
hardwareunabhängig. Nur die paar LowLevel-Treiber müßten dabei angepaßt
werden.
Prinzip verstanden?
W.S.
Scharfanalyse schrieb:> Wobei der TO dann bereits daran scheitert dass er in allen> anderen IDEs die Datei Pin.h nicht bekommt und dasteht wie> am Anfang.
Es ist doch immer wieder deprimierend zu lesen, dass nur wenn man nicht
alles weiss, oder es zufriedenstellend beschrieben hat man für völlig
bescheuert gehalten wird.
Lars schrieb:> Es ist doch immer wieder deprimierend zu lesen, dass nur wenn man nicht> alles weiss, oder es zufriedenstellend beschrieben hat man für völlig> bescheuert gehalten wird.
Dann behebe doch deine Deppression damit dass du endlich genau
beschreibst was du tun willst.
Einen gutgemeinten Hinweis in die andere Richtung hat dir
W.S. gerade gegeben.
W.S. schrieb:> Mein Rat: Laß so etwas lieber bleiben - und zwar grundsätzlich!
Lars schrieb:> void setDataPin( uint8_t Port, uint8_t Pin ){> DDRB |= _BV( Pin ); // setze als ausgang> Port |= _BV( Pin ); // ausgang ist low> }
Das kann grundsätzlich nicht funktionieren, weil es im Atmega keinen
Maschinenbefehl zum setzen eines Ports gibt, der den Port als variablen
Parameter hat. Der Port ist nämlich TEIL des Maschinenbefehls und somit
knallhart fest verdrahtet. Es gibt also einen Maschinenbefehl um DDRB zu
setzen, einen anderen um DDRA zu setzen, genauso sind die
Maschinenbefehle zum setzen von PortA, PortB, ... alle unterschiedlich.
Wollte man zur Laufzeit den Port ersetzen, dann müsste man diesen Befehl
neu flashen.
Nach dem gleichen Prinzip gibt es zwar Maschinenbefehle, die ein
einzelnes Bit setzen oder löschen. Aber auch hier gibt es für jedes
einzelne Bit einen einzelnen Maschinenbefehl. Man kann das Bit da nicht
dynamisch als Parameter übergeben.
Der C Compiler kann daher diesen C Code nicht übersetzen.
Man muss dann sowas schreiben wie:
Kurt schrieb:> Der Port ist nämlich TEIL des Maschinenbefehls und somit> knallhart fest verdrahtet.Kurt schrieb:> Es gibt also einen Maschinenbefehl um DDRB zu> setzen, einen anderen um DDRA zu setzen
Dann zeig doch mal den fest verdrahteten Maschinenbefehl der
(nur) für Port B zuständig ist.
Knapp daneben, Kurt ....
Scharfanalyse schrieb:> Dann zeig doch mal den fest verdrahteten Maschinenbefehl der> (nur) für Port B zuständig ist.
In Assembler wären das OUT und IN. Der Port (z.B. PORB) zu diesen
Befehlen wird ist hart kodiert, lediglich der Wert wird per Register
übergeben. Beispiel:
1
volatileuint8_tvalue;
2
PORTB|=value;
kann werden zu
1
out 0x1b, r24;
Der PORTB (ebenso wie Register r24) ist dabei mit 0x1b hart verdrahtet.
Als Maschinencode steht dann irgendwo im Flash die Bytefolge
0x8bbb
Für PORTA käme statt dessen die Bytefolge 0x88bb raus. Der Port steht
also als Teil des Befehls hart im Flash und kann nicht zur Laufzeit
verändert werden. Lediglich der Inhalt von r24 - also der Wert aus value
ist variabel.
Bei SBI, CBI sind Port UND Bit Teil des Befehls und somit zur Laufzeit
nicht änderbar. Aus
1
PORTB|=_BV(1);
2
PORTB|=_BV(2);
wird dann
1
sbi 0x18, 1;
2
sbi 0x18, 2;
oder die Bytefolge
0xc19a
0xc29a
als Maschinenbefehle im Flash. Da spielt gar kein Register mehr mit und
somit ist nicht einmal mehr der Wert variabel, sondern Bestandteil des
Maschinenbefehls.
Wie soll nun ein C Compiler es hinkriegen C Code, der Port und Wert aus
Variablen nehmen soll, in Maschinenbefehle zu übersetzen, bei denen
diese Parameter aber Konstanten sind? Geht nicht. Selbst wenn man den
Compiler dazu prügeln würde ein ähnliches Switch-Konstrukt wie ich oben
schon erwähnte automatisch zu generieren, wäre das nicht erwünscht.
Schließlich hat das ja immense Auswirkungen auf die Laufzeit. Aber die
möchte man ja tunlichst selbst im Griff haben.
Daher halte ich diesen Ratschlag für goldrichtig:
W.S. schrieb:> Also anstelle deines SetPin(Port,Nummer) sollte dein Treiber etwa sowas> liefern:> Ventil_Auf();> Ventil_Zu();
Kurt schrieb:> Wie soll nun ein C Compiler es hinkriegen C Code, der Port und Wert aus> Variablen nehmen soll, in Maschinenbefehle zu übersetzen, bei denen> diese Parameter aber Konstanten sind? Geht nicht.
Ja, nochmal, knapp daneben.
Nachdem die Register der Ports auch Teil des normalen Adressraums
sind kann man diese auch so ansprechen. Z.B. über Pointer, nix
festverdrahtet.
sbi und cbi sind zwar schnell, aber nicht die einzige Möglichkeit
Ports zu schreiben und zu lesen.
Lars schrieb:> Aber das mit dem Ausgang setzen bekomme ich einfach nicht hin.> So funktioniert es zwar, aber es ist DDRB vorgegeben (Das möchte ich> variable machen)
Es ist zwar nicht garantiert, dass das bei allen AVRs so ist. Aber
normalerweise ist der Offset zwischen DDR und PORT konstant. So ist z.B.
beim ATMega16
DDRA == PORTA -1
DDRB == PORTB -1
usw
Das nützt einem aber natürlich nur solange man diese Berechnungen vom
Präprozessor machen lassen kann. Zur Laufzeit hilft das wenig, weil es -
wie ich schon schrob - keine Maschinenbefehle gibt, die die Portangabe
zur Laufzeit als Parameter auswerten könnten.
Kurt schrieb:> Das kann grundsätzlich nicht funktionieren, weil es im Atmega keinen> Maschinenbefehl zum setzen eines Ports gibt, der den Port als variablen> Parameter hat. Der Port ist nämlich TEIL des Maschinenbefehls und somit> knallhart fest verdrahtet. Es gibt also einen Maschinenbefehl um DDRB zu> setzen, einen anderen um DDRA zu setzen, genauso sind die> Maschinenbefehle zum setzen von PortA, PortB, ... alle unterschiedlich.> Wollte man zur Laufzeit den Port ersetzen, dann müsste man diesen Befehl> neu flashen.
DAS ist ziemlicher Unsinn. Daran stimmt nur, dass es solche
"hartverdrahteten" Befehle tatsächlich gibt (und sie nebenbei die bei
weitem effizienteste Zugriffsmöglichkeit auf IO-Register darstellen).
Aber: Es gibt auch andere Befehle, mit denen man auf die Register
zugreifen kann. In Assembler ist das auch absolut kein Problem. Statt
z.B. zu schreiben:
ldi Rxx,WasAuchImmer
out DDRA,Rxx
kann man z.B. auch schreiben:
;->Rxx: auszugebender Wert
; Z: Registeraddresse, auf die er ausgegeben werden soll
; (dabei ist schon egal, ob tatsächlich echtes IO-Register oder
; MMIO-Register, das fummelt sich die Routine selbst hin.
Letztlich
; wird auf das Register immer in MMIO-Manier zugegriffen.
set_ddr:
subi ZL,Low($40)
sbci ZH,high($40)
brcs set_ddr_io
subi ZL,Low(-$40)
sbci ZH,high(-$40)
st Z,Rxx
ret
set_ddr_io:
subi ZL,Low(-$60)
sbci ZH,high(-$60)
st Z,Rxx
ret
Aufrufen tut man den Salat dann so:
ldi Rxx,WasAuchImmer
ldi ZL,Low(DDRA)
ldi ZH,High(DDRA)
rcall set_ddr
sind dann natürlich etliche Takte mehr Rechenzeitverbrauch und auch
etliche Befehlsworte mehr im Flashspace, aber am Ende hat man exakt das
gleiche Ergebnis. Bloß eben mit variablem Ausgaberegister, denn man muß
Z nicht mit immediate-Werten laden, sondern kann es auch aus anderen
Registern laden oder aus dem RAM. Der enorme Mehrverbrauch an Ressourcen
in jeglicher Hinsicht weist aber sehr deutlich darauf hin, dass so eine
Routine für die Regelanwendung totaler Unsinn ist.
Und auch die Tatsache, dass der Arduino-Scheißdreck im Prinzip letztlich
in etwa so funktioniert und u.a. auch deshalb so Scheisse-Lahm ist.
Software von Idioten für Idioten.
> Der C Compiler kann daher diesen C Code nicht übersetzen.
Doch kann er. Man muss den Code nur geeignet formulieren, dann kann man
auch mit einem C-Compiler das Äquivalent zu oben gezeigten Assemblercode
abbilden. Du hast also nicht nur keine Ahnung von der Hardware, sondern
auch keine Ahnung von C.
Du solltest mal auf Nuhr hören...
Kurt schrieb:> wie ich schon schrob - keine Maschinenbefehle gibt, die die Portangabe> zur Laufzeit als Parameter auswerten könnten.
... und das ist - nochmal und deutlicher geschroben - :
Käse.
Scharfanalyse schrieb:> Nachdem die Register der Ports auch Teil des normalen Adressraums> sind kann man diese auch so ansprechen. Z.B. über Pointer, nix> festverdrahtet.
Es gibt keine "Register der Ports". Man kann den Port nicht über ein
Register angeben. Lediglich den Wert kann man per Register übergeben.
Welcher Port und welches Register (für den Wert! nicht den Port) z.B.
bei einem OUT Befehl verwendet werden, wird zur Compilezeit festgelegt.
Man kann nicht beim ersten Operanden des OUT Befehls ein Register
angeben. Die ATMegas haben keinen Befehl im Befehlssatz, bei dem der
Port zur Laufzeit aus einem Register spezifiziert werden kann.
Kurt schrieb:> Es gibt keine "Register der Ports". Man kann den Port nicht über ein> Register angeben.
Mach weiter so.
Ansonsten lies c-hater vielleicht nochmal. Und nochmal, und nochmal...
Sheeva P. schrieb:> Lars schrieb:> ich bin schon länger am überlegen, wie ich die Ausgänge eines> Mikrocontrollers variable initialisieren kann.> [...]> Hat von euch jemand eine Idee, wie man das umsetzen könnte?>> typedef struct {> uint8_t* port;> uint8_t* pin;> uint8_t* ddr;> uint8_t num;> } PortPin_t;>> void setDataPin(PortPin_t pin) {> *pin.ddr |= _BV(pin.num);> *pin.port |= _BV(pin.num);> }>> PortPin_t dataPin = {&PORTB, &PINB, &DDRB, 3);> setDataPin(dataPin);
Das ist die korrekte Lösung. Punkt.
Man könnte evtl noch Konfigurieren des DDR und Setzen des Ausgangs in
separate Funktionen packen. Aber der Ansatz ist richtig.
Der Compiler wird das auch ordentlich optimieren und den Funktionsaufruf
durch ein einziges sbi oder cbi ersetzen, der Constant Propagation sei
Dank.