Forum: Mikrocontroller und Digitale Elektronik Pin vom Mikrocontroller in Funktion mit variablen initialisieren


von Lars (Gast)


Lesenswert?

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

von ui (Gast)


Lesenswert?

gehts um den AVR? warum übergibst du der Funktion nicht einfach noch 
zusäztlich DDRB? bzw. DDRA? oder DDRE?
was spricht denn da dagegen?

von Einer K. (Gast)


Angehängte Dateien:

Lesenswert?

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

von Lars (Gast)


Lesenswert?

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

von Scharfanalyse (Gast)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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?
1
typedef struct {
2
  uint8_t* port;
3
  uint8_t* pin;
4
  uint8_t* ddr;
5
  uint8_t num;
6
} PortPin_t;
7
8
void setDataPin(PortPin_t pin) {
9
  *pin.ddr |= _BV(pin.num);
10
  *pin.port |= _BV(pin.num);
11
}
12
13
PortPin_t dataPin = {&PORTB, &PINB, &DDRB, 3);
14
setDataPin(dataPin);

von Einer K. (Gast)


Lesenswert?

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.

von Scharfanalyse (Gast)


Lesenswert?

Arduino F. schrieb:
> Das verstehe ich nicht.

Das war unüberlegt und ich nehme es zurück.
Soviel Bescheidenheit muss sein.

von Frank (Gast)


Lesenswert?

1
#define Ddr  DDRB
1
void setDataPin( uint8_t Port, uint8_t Pin ){
2
  Ddr  |=  _BV( Pin ); // setze als ausgang
3
  Port &= ~_BV( Pin ); // ausgang ist low
4
}
So habe ich es verstanden.

am besten erklärst du nochmals was du erreichen möchtest.

von Einer K. (Gast)


Lesenswert?

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.

von Scharfanalyse (Gast)


Lesenswert?

Arduino F. schrieb:
> Das öffentliche Anerkennen, einen Bock geschossen zu haben, verdient
> Respekt!

Ja Danke, ich will mir ja selbst treu bleiben.

von W.S. (Gast)


Lesenswert?

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.

von Lars (Gast)


Lesenswert?

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.

von Scharfanalyse (Gast)


Lesenswert?

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!

von Kurt (Gast)


Lesenswert?

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:
1
void setDataPin( uint8_t Port, uint8_t Pin ){
2
  switch(Port){
3
  case PORTA:
4
    switch (Pin){
5
    case 0:
6
      DDRA |=  _BV( 0); // setze als ausgang
7
      PORTA |= _BV( 0 ); // ausgang ist low
8
      break;
9
    case 1:
10
      DDRA |=  _BV( 1); // setze als ausgang
11
      PORTA |= _BV( 1 ); // ausgang ist low
12
      break;
13
14
    <snip>
15
    }
16
    break;
17
  case PORTB:
18
    switch(Pin){
19
       case 0:
20
         DDRB |=  _BV( 0); // setze als ausgang
21
         PORTB |= _BV( 0 ); // ausgang ist low
22
         break; 
23
    }
24
    <snip>
25
  }
26
}

von Scharfanalyse (Gast)


Lesenswert?

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

von Kurt (Gast)


Lesenswert?

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
volatile uint8_t value;
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();

von Scharfanalyse (Gast)


Lesenswert?

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.

von Kurt (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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

von Scharfanalyse (Gast)


Lesenswert?

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.

von Kurt (Gast)


Lesenswert?

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.

von Scharfanalyse (Gast)


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

Wer ist dieser Kurt? Das bereitet ja schon fast körperliche Schmerzen so 
eine peinliche Selbstdemontage mit ansehen zu müssen :-(

von Bernd K. (prof7bit)


Lesenswert?

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.

: Bearbeitet durch User
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.