Forum: Mikrocontroller und Digitale Elektronik IO-Pins als Bitfeld zusammenfügen und maskieren (AVR)


von Uli S. (badiman)


Lesenswert?

Hallo liebe µC Gemeinde,

in einem Projekt auf einem ATmega644PA bin ich gerade dabei 9 DC Motoren 
auf verschiedenen Ports/Pins anzusteuern. Mit Hilfe von 9 Software-PWMs 
für die Motoren, soll über eine berechnete Bitmaske die Ansteuerung 
erfolgen (zu sehen im Beitrag: Soft-PWM).
Nun gilt es, die 9 IO-Pins zusammen zufassen, so dass die entstandene 
Bitmaske der 9 Soft-PWMs auf die Motor-Pins angewendet werden kann. In 
einem Artikel über "verschiedene Ports nach Bitmaske setzen" 
(http://www.roboternetz.de/community/threads/59212-Ports-nach-Bitmaske-setzen) 
fand ich eine Lösung, die funktioniert:
1
#include <avr/io.h>
2
#define ARRAY_LENGTH 9 //wieviele Pins sollen angesteuert werden
3
4
//welche Ports werden für die Motoren verwendet
5
volatile uint8_t * Port_Mask[ARRAY_LENGTH] = { &PORTC, &PORTC, &PORTC, &PORTC, &PORTA, &PORTA, &PORTA, &PORTA, &PORTB };
6
//mit welchen Pins sind die Motoren verbunden
7
uint8_t  Pin_Mask[ARRAY_LENGTH]  = {PC7, PC6, PC5, PC4, PA7, PA6, PA5, PA4, PB0};
8
//Dezimalzahl der entsprechenden Motoren-Stelle in der späteren Bitmaske 
9
uint16_t Potenz_Zahlen[ARRAY_LENGTH] = {1, 2, 4, 8, 16, 32, 64, 128, 256};
10
11
void setPorts(uint16_t bitMaske){
12
  for(uint8_t i = 0; i< ARRAY_LENGTH; i++){
13
    BIT_BOOL_SET(Port_Mask[i], Pin_Mask[i], bitMaske & Potenz_Zahlen[i]); //anhand der bitMaske wird das i-te Bit geprüft,
14
    // das Ergebnis ist entweder 0 und das Bit muss gelöscht, also der Motor abgeschaltet, werden
15
    // oder das Ergebnis ist > 0 und das Bit wird gesetzt, also Motor an.
16
  }
17
}
18
19
inline void BIT_BOOL_SET(volatile uint8_t *target, uint8_t bit, uint16_t b){
20
  if (b>0){
21
    BIT_SET(target, bit);
22
  }else{
23
    BIT_CLEAR(target, bit);
24
  }
25
}
26
27
inline void BIT_SET(volatile uint8_t *target, uint8_t bit){
28
  *target |= (1<<bit);
29
}
30
31
inline void BIT_CLEAR(volatile uint8_t *target, uint8_t bit){
32
  *target &= ~(1<<bit);
33
}

Nun wurde für das einfachere 'Handling' der verschiedenen Motoren-Pins 
ein 'struct' eingeführt (wie im 
Beitrag "#define in C", 3. letzer Eintrag, zu 
lesen).
1
//auf einen Port-Pin wie auf eine Variable zugreifen:
2
struct bits {
3
  uint8_t b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
4
} __packed; //das gleiche wie: __attribute__ ((__packed__))
5
#define SBIT_(port,pin) (((volatile struct bits *) & port) -> b##pin) //»a->b« Kurzform für »(*a).b«
6
#define SBIT(x,y)       SBIT_(x,y)
7
8
#define MOTOR1    SBIT( PORTC, 7 )
9
#define MOTOR1_DDR  SBIT( DDRC,  7 )
10
#define MOTOR2    SBIT( PORTC, 6 )
11
#define MOTOR2_DDR  SBIT( DDRC,  6 )
12
#define MOTOR3    SBIT( PORTC, 5 )
13
#define MOTOR3_DDR  SBIT( DDRC,  5 )
14
#define MOTOR4    SBIT( PORTC, 4 )
15
#define MOTOR4_DDR  SBIT( DDRC,  4 )
16
#define MOTOR5    SBIT( PORTA, 7 )
17
#define MOTOR5_DDR  SBIT( DDRA,  7 )
18
#define MOTOR6    SBIT( PORTA, 6 )
19
#define MOTOR6_DDR  SBIT( DDRA,  6 )
20
#define MOTOR7    SBIT( PORTA, 5 )
21
#define MOTOR7_DDR  SBIT( DDRA,  5 )
22
#define MOTOR8    SBIT( PORTA, 4 )
23
#define MOTOR8_DDR  SBIT( DDRA,  4 )
24
#define MOTOR9    SBIT( PORTB, 0 )
25
#define MOTOR9_DDR  SBIT( DDRB,  0 )
26
#define OUTPUT    1

Damit kann ich nun die verschiedenen Motoren in der 'main()' 
initalisieren und aufgrund der obigen Methoden mit einer Bitmaske die 
entsprechenden Pins in den Port auf 'on'(1) setzen.
1
int main(void){
2
  MOTOR1_DDR  = OUTPUT;
3
  MOTOR2_DDR  = OUTPUT;
4
  MOTOR3_DDR  = OUTPUT;
5
  MOTOR4_DDR  = OUTPUT;
6
  MOTOR5_DDR  = OUTPUT;
7
  MOTOR6_DDR  = OUTPUT;
8
  MOTOR7_DDR  = OUTPUT;
9
  MOTOR8_DDR  = OUTPUT;
10
  MOTOR9_DDR  = OUTPUT;
11
  
12
  uint16_t neueBitmaske = 0b010001010; //dez:138 - setzt bit 2,7 und 11 also PORTC.5, PORTC.4 und PORTA.4 
13
  setPorts(neueBitmaske);
14
  
15
  while (1) { /*do nothing*/ }
16
}

Vielleicht kann es sich der eine oder andere denken, aber nun sollten 
die beiden Arrays 'Port_Mask' und 'Pin_Mask' ersetzt werden. Dazu wird 
ein Array mit den eigenen definiertes MOTORx Makros benutzt. Glein zu 
Beginn eingebaut, doch schon bei der Initialisierung kam es zu Problemen 
=> "initializer element is not constant" (anscheinend geht das nicht vor 
Ausführung der 'main()'-Methode).
1
uint8_t Motor_Mask[ARRAY_LENGTH] = {
2
  MOTOR1, MOTOR2, MOTOR3, MOTOR4, MOTOR5, MOTOR6, MOTOR7, MOTOR8, MOTOR9
3
};

Also kurzerhand das Motor-Array "leer" initialisiert und in der 'main()' 
über ein lokal erstelltes Array mittels 'memcpy' befüllt.
1
uint8_t Motor_Mask[ARRAY_LENGTH];
2
//...
3
int main(void){
4
  //...
5
  uint8_t tmp_Motor_Mask[ARRAY_LENGTH] = {
6
    MOTOR1, MOTOR2, MOTOR3, MOTOR4, MOTOR5, MOTOR6, MOTOR7, MOTOR8, MOTOR9
7
  };
8
  memcpy(Motor_Mask, tmp_Motor_Mask, ARRAY_LENGTH);
9
  //...
10
}

Nach dem Umschreiben der 'setPort'-Methode auf das neue Motoren-Array 
wurde alles compiliert und über den Simulator gestartet.
1
void setPorts(uint16_t bitMaske){
2
  for(uint8_t i = 0; i< ARRAY_LENGTH; i++){
3
    if( (bitMaske & Potenz_Zahlen[i]) > 0 ){
4
      Motor_Mask[i] = 1;
5
    }else{
6
      Motor_Mask[i] = 0;
7
    }
8
  }
9
}

Und was soll ich sagen: Das Compilieren hat funktioniert, nur es kommt 
überhaupt nichts dabei heraus. Bedeutet: kein einziger Pin der drei 
Ports wird gesetzt. Beim recherchieren bin ich auf Aussagen gestoßen die 
besagen, dass die selbsterstellten Bits nicht in Arrays genutzt werden 
können. Ist das richtig? Vielleicht bin ich mit meinem Vorhaben ja 
komplett auf dem Holzweg und jemand von euch hat gute Vorschläge. Freue 
mich über jede Reaktion. Danke.

Beste Grüße Uli

von bitschubser (Gast)


Lesenswert?

Also ich mach das immer folgendermaßen. Die zu setzenden Bits definiere 
ich mir mit einer sauberen Beschreibung anhand ihres Stellenwertes 
(Umwandlung Binär/Hex), zB, würde das Datenblatt so aussehen:

BIT     Funktion
7       xx
6       yy
..
1       Motor an
1
#define ENGINE_ON 0x01
2
#define 0x02
3
#define 0x04
4
#define 0x08
5
#define 0x10
6
#define YY 0x0020
7
#define XX 0x0040

Wenn ich nun das Bit 7 (xx) setzen will, reicht ein
1
REGISTER |= XX;

bzw. zum löschen:
1
REGISTER &= ~(ENGINE_ON)

Also ähnlich wie bei dir, nur würde ich den ganzen Overhead mit den 
Structs und diesen unleserlichen Makros nicht so machen.

von Stromverdichter (Gast)


Lesenswert?

Hallo Uli,
kannst du deinen Code schrittweise debuggen?
Sind deine Funktionen in einem Header nochmal bekannt gemacht?
BitboolSet wird erst verwendet, und dann beschreibst du, was Sie machen 
soll.

von Uli S. (badiman)


Lesenswert?

Hey bitschupser,

zunächst vielen Dank für deine Antwort. Ja das mit den Structs bzw. 
unleserlichen Makros war eine frühere Idee den Code "leserlicher" zu 
machen. Und was leserlich ist, das definiert ja jeder für sich auf 
andere Weise ;-) wie ich schon häufig hier im Forum lesen konnte.

Der jetzige Ansatz, der ja auch funktioniert, bezieht sich direkt auf 
die PORT-Register (z.B. PORTC) bzw. Pin-Nummern (PC6). Und hinter PC6 
steckt  im Prinzip nichts anderes wie 0x20. Heißt, wenn ich eine 
Bitmaske bekomme, soll diese auf die entsprechenden Pins der 
verschiedenen Port-Register angewendet werden. Also gehe ich diese 
Bitmaske Schritt für Schritt durch (in der setPorts-Methode) und setze 
aufgrund der Reihenfolge in den vordefierten Arrays die Pins. In Deinem 
Vorschlag würde ich das  dann auch so machen. Ich benutze eine 
FOR-Schleife, iteriere über die Bitmaske und rufe entsprechend einer 
vordefinierten Reihenfolge die Funktion setzten oder löschen auf.
D.h. Du würdest das Maskieren mit der eingehenden Bitmaske ebenfalls mit 
einer Schleife lösen!?
-----------------------------------
Hey Stromverdichter,

ebenfalls Danke für deine Antwort. Jepp, hab den Code tatsächlich 
schrittweise debugged. Nur irgendwie macht der Debugger von Atmel Studio 
(v7.0) irgendwie was er will. Im C-Code überspringt er meist 
irgendwelche Zeilen oder zeigt Variablen-Inhalte nicht an. Im 
Disassembler passieren sehr viele Dinge! Allerdings fehlt mir dabei die 
Routine zu wissen, was in Assembler passiert.
Für dieses kleine Test-Projekt habe ich keinen Header angelegt, das 
liegt alles in einer C-Datei. Ist das ein Problem? Du meintest die 
Funktion BIT_BOOL_SET würde erst verwendet und anschließend erst 
beschrieben. Das verstehe ich jetzt nicht ganz.
Die Inline Methoden: BIT_BOOL_SET, BIT_SET, BIT_CLEAR sind alle 
ein Teil der Methode setPorts. Das ist im Prinzip die einzige Methode, 
die ich im Code habe. Die anderen sind ja nur via inline rausgezogen. 
Und dann ist da noch die main -Methode. Darin führe ich die setPorts 
-Methode aus. Wie an 'bitschupser' geschrieben, wird darin über die 
Bitmaske iteriert und dann ein Motor an und aus geschalten. Der soll 
aber (im 2. Schritt, siehe oben) eben nicht über die Register und Pin 
Bezeichnungen angesprochen werden, sondern über das SBIT, welches (aus 
meiner Sicht) Gleiches impliziert. Also:
1
PORTC |= (1<<PC6);
ist das gleiche wie:
1
//...
2
#define MOTOR2    SBIT( PORTC, 6 )
3
//...
4
MOTOR2 = 1;
Nur aus irgendeinem Grund möchte das der Compiler aber nicht so wie ich 
:-) und wäre die Frage, ob es einen Grund dafür gibt, den ich vielleicht 
übersehen habe.

Danke nochmals für eure Hinweise.
Schöne Ostergrüße
Uli

von Nop (Gast)


Lesenswert?

Uli S. schrieb:

> //auf einen Port-Pin wie auf eine Variable zugreifen:
> struct bits {
>   uint8_t

Bitfields sind für Registermapping verführerisch, aber grundfalsch. Der 
C-Standard gibt nicht vor, wie Bitfields zu implementieren sind. Das 
erste deklarierte Bit kann oben sein oder auch unten.

Der Basisdatentyp könnte auch durchaus nach Gusto des Compilers schonmal 
wechseln. Beispielsweise, weil uint8_t letztlich ein char ist, Bitfields 
aber nur auf int-Datentypen definiert sind.

Bitfields sind dafür gedacht, Speicher zu sparen - nicht für IO. Kurzum, 
laß es einfach.

von Uli S. (badiman)


Lesenswert?

Hey Nop,

das ist mal eine Aussage und zudem noch untermauert - vielen Dank dafür. 
Das Hilft mir in diesem und auch für zukünftige Projekte weiter.

Ich hab noch etwas in 'quelloffenen' HowTos nach den Bitfeldern gesucht, 
um mir dann noch ein Bild darüber zu machen, wo der Einsatz Sinn macht. 
Und tatsächlich gibt es einige Dinge zu beachten:
[[http://www.c-howto.de/tutorial-strukturierte-datentypen-bitfelder.html]] 
wie hier zu sehen.
Gleichzeitig habe ich nun meinen Fehler entdeckt und warum der oben 
beschriebene Versuch, ein selbst erstelltes IO-Bitfield zu maskieren, 
nicht gelungen ist. Nicht nur, wie Du Nop schon schriebst, dass es für 
die Bitfields keine C-Standard Vorgabe gibt, sondern wie in diesem 
Beitrag zu lesen:
[[http://openbook.rheinwerk-verlag.de/c_von_a_bis_z/015_c_strukturen_013.htm]]
- Bitfelder belegen keine adressierbare Speicherstelle und können daher 
auch den Adressoperator *&* nicht nutzen.

Vielen Dank nochmal für die Unterstützung. Manchmal braucht man eben nur 
einen Wink mit dem ...
Beste Grüße Uli

: Bearbeitet durch User
von Eric B. (beric)


Lesenswert?

Uli S. schrieb:
> Also:
>
> PORTC |= (1<<PC6);
>
> ist das gleiche wie:
>
> //...
> #define MOTOR2    SBIT( PORTC, 6 )
> //...
> MOTOR2 = 1;

Nöh. Es gibt keinerlei Definition von SBIT die aus der Ausdruck 
"MOTOR2 = 1" irgendwie "PORTC |= (1<<PC6)" zaubern kann. Das kann also 
unmöglich das gleiche sein.

von Uli S. (badiman)


Lesenswert?

1
struct bits {
2
  uint8_t b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1;
3
} __packed; //das gleiche wie: __attribute__ ((__packed__))
4
#define SBIT_(port,pin) (((volatile struct bits *) & port) -> b##pin) //»a->b« Kurzform für »(*a).b«
5
#define SBIT(x,y)       SBIT_(x,y)

Diese Definition stand schon im ersten Eintrag, wollte sie nicht 
wiederholen. Dort hattest Du sie wahrscheinlich übersehen.
Grüße Uli

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.