Forum: Mikrocontroller und Digitale Elektronik Frage zur Bitmanipulation


von _sbi_ (Gast)


Lesenswert?

Guten Abend
Ich habe eine Frage bezüglich der Bitmanipulation meines ATMEGA 8.
Ich verwende das Atmel Studio 6.

Ich habe hier ein einfaches Programm um 2 LEDs blinken zu lassen.
1
#define F_CPU 20000000UL
2
#include <util/delay.h>
3
#include <avr/io.h>
4
5
6
7
int main(void)
8
{
9
    DDRD=0b01100000;
10
  while(1)
11
    {
12
        
13
    PORTD |= ((1 << 5) | (1 << 6));
14
    _delay_ms(10);
15
    PORTD &= ~((1 << 5) | (1 << 6));
16
    _delay_ms(10);
17
    
18
    
19
  }
20
}


Meine frage ist ob man die Zuweisung der Ausgänge noch einfacher 
schreiben kann.
Ich bin in diesem Beitrag darauf gestoßen:
Beitrag "8051 sbit?"

PORTx.n = 0;
//oder
PORTx.n = 1;

Dort wurde über ähnliche Syntaxe geredet. Nun ist der Beitrag aber äter 
als 6 Monate und es wurde mir geraten neu hier nachzufragen.

Ich kenne bis jetzt nur diese Schreibweise aus dem Tutorial.

Einen schönen Abend wünsche ich noch.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@_sbi_ (Gast)

>Meine frage ist ob man die Zuweisung der Ausgänge noch einfacher
>schreiben kann.

Nein, nicht bei avr gcc.

>PORTx.n = 0;
>//oder
>PORTx.n = 1;

Geht nicht. Der AVR hat keinen bitaddressierbaren Speicher und avr gcc 
bietet auch keine Makros dafür.

von _sbi_ (Gast)


Lesenswert?

Danke.:-)
Das ging schnell.

Ddas habe ich das wohl falsch interpretiert im anderen Beitrag.
Dann ist der atmega 8 von der Hardware aus schon nicht fähig dafür.

Eine andere Entwicklungsumgebung würde ja auch nichts andern.

Danke dir:-)

von Dennis H. (c-logic) Benutzerseite


Lesenswert?

Falk Brunner schrieb:
> @_sbi_ (Gast)
>
>>Meine frage ist ob man die Zuweisung der Ausgänge noch einfacher
>>schreiben kann.
>
> Nein, nicht bei avr gcc.
>
>>PORTx.n = 0;
>>//oder
>>PORTx.n = 1;
>
> Geht nicht. Der AVR hat keinen bitaddressierbaren Speicher und avr gcc
> bietet auch keine Makros dafür.

Aber zumindest Bit-Adressierbaren IO-Bereich hat der AVR durch die 
Opcodes:

SBI + CBI

Intern macht der AVR-GCC aus "PORTD |= ((1 << 5)" ein "SBI PORTD,5" wenn 
es im unteren 32-IO-Register-Segment liegt.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Etwas Ähnliches wie den Bit Level Access in CodeVision-AVR-Compiler
kannst du in "normalem" C folgendermaßen nachbilden:
1
#include <stdint.h>
2
#include <avr/io.h>
3
4
struct bits {
5
  uint8_t b0:1;
6
  uint8_t b1:1;
7
  uint8_t b2:1;
8
  uint8_t b3:1;
9
  uint8_t b4:1;
10
  uint8_t b5:1;
11
  uint8_t b6:1;
12
  uint8_t b7:1;
13
};
14
15
#define BITS(p) (*(volatile struct bits *)&(p))
16
17
#define BPORTA BITS(PORTA)
18
#define BPORTB BITS(PORTB)
19
#define BPORTC BITS(PORTC)
20
#define BPORTD BITS(PORTD)
21
22
#define BPINA BITS(PINA)
23
#define BPINB BITS(PINB)
24
#define BPINC BITS(PINC)
25
#define BPIND BITS(PIND)
26
27
int main(void) {
28
  BPORTD.b4 = 1;
29
  if(BPINC.b6)
30
    BPORTD.b1 = 0;
31
  return 0;
32
}

Der GCC erzeugt aus dem Beispielcode in main effiziente
Bit-Instruktionen, in diesem Fall SBI, SBIC und CBI).

Beachte aber, dass in deinem Beispiel
1
    PORTD |= ((1 << 5) | (1 << 6));

zwei Bits gleichzeitig gesetzt werden. Mit
1
    BBORTD.b5 = 1;
2
    BBORTD.b6 = 1;

hingegen werden sie nacheinander gesetzt.

Wenn du möchtest, kannst du den einzelnen Bits auch Namen geben:
1
#define LED1   PORTD.b5
2
#define LED2   PORTD.b6
3
#define TASTER PINC.b0
4
// usw.

um dann bspw. schreiben zu können:
1
  if(TASTER)
2
    LED1 = 1;

: Bearbeitet durch Moderator
von Wolfgang (Gast)


Lesenswert?

_sbi_ schrieb:
> Dann ist der atmega 8 von der Hardware aus schon nicht fähig dafür.

Natürlich kannst du dem ATmega8 beibringen, ein einzelnes Bit zu ändern. 
Wie man soetwas in Assembler umsetzt, ist klar.

Ob und wie ein Compiler das umsetzt, ist ein anderes Thema. Aber der 
Prozessor kann am wenigsten dafür, wenn der von dir verwendete Compiler 
das nicht gebacken kriegt.

von _sbi_ (Gast)


Lesenswert?

Guten Morgen Leute :)

Danke für die Hilfe und die Aufklärung.

Ich werde es mal mit

Yalu X. schrieb:
> Etwas Ähnliches wie den Bit Level Access in
> CodeVision-AVR-Compiler
> kannst du in "normalem" C folgendermaßen nachbilden:
> #include <stdint.h>
> #include <avr/io.h>
>
> struct bits {
>   uint8_t b0:1;
>   uint8_t b1:1;
>   uint8_t b2:1;
>   uint8_t b3:1;
>   uint8_t b4:1;
>   uint8_t b5:1;
>   uint8_t b6:1;
>   uint8_t b7:1;
> };
>
> #define BITS(p) (*(volatile struct bits *)&(p))
>
> #define BPORTA BITS(PORTA)
> #define BPORTB BITS(PORTB)
> #define BPORTC BITS(PORTC)
> #define BPORTD BITS(PORTD)
>
> #define BPINA BITS(PINA)
> #define BPINB BITS(PINB)
> #define BPINC BITS(PINC)
> #define BPIND BITS(PIND)
>
> int main(void) {
>   BPORTD.b4 = 1;
>   if(BPINC.b6)
>     BPORTD.b1 = 0;
>   return 0;
> }
>
> Der GCC erzeugt aus dem Beispielcode in main effiziente
> Bit-Instruktionen, in diesem Fall SBI, SBIC und CBI).
>
> Beachte aber, dass in deinem Beispiel
>     PORTD |= ((1 << 5) | (1 << 6));
>
> zwei Bits gleichzeitig gesetzt werden. Mit
>     BBORTD.b5 = 1;
>     BBORTD.b6 = 1;
>
> hingegen werden sie nacheinander gesetzt.
>
> Wenn du möchtest, kannst du den einzelnen Bits auch Namen geben:
> #define LED1   PORTD.b5
> #define LED2   PORTD.b6
> #define TASTER PINC.b0
> // usw.
>
> um dann bspw. schreiben zu können:
>   if(TASTER)
>     LED1 = 1;

diesem Code probieren. Auch wenn ich ihn noch nicht komplett verstanden 
habe.

Danke an dieser Stelle an Yalu X.





Man kann ja so weit ich das in den Beiträgen verstanden habe einen 8051 
z.B. Bitadressieren. Also bei dem wäre das einfacher in der Syntax.
Das würde dann am Compiler liegen der für den 8051 vorgesehen ist. Ist 
das so korrekt?

von Forist (Gast)


Lesenswert?

_sbi_ schrieb:
> diesem Code probieren.

Hier haben wohl die meisten Forenteilnehmer einen Browser, der anhand 
eines Links durchaus in der Lage ist, ein paar Postings zurück zu 
springen. Du brauchst nicht "seitenweise" Quellcode zu zittieren ;-(

von define (Gast)


Lesenswert?

> PORTD |= ((1 << 5) | (1 << 6));
Das ist ab dem dritten Bit nicht mehr übersichtlich. Man schreibt daher 
einmal
#define BIT0   0x01
#define BIT1   0x02
#define BIT2   0x04
...

und kann dann immer

PORTD |= BIT5 | BIT6;

schreiben.

von Falk B. (falk)


Lesenswert?

@ define (Gast)

>und kann dann immer

>PORTD |= BIT5 | BIT6;

>schreiben.

Kann man, ist aber beim AVR nicht unbedingt sinnvoll, weil dort nun mal 
aus historischen Gründen SÄMTLICHE Bits als Bitnummer definiert sind.
Beim ATXmega ist es wieder besser, dort gibt es beide Definitionen mit 
verschiedenen Endungen, sowohl für Einzelbits als auch Bitgruppen

- Bitnummer _bp bit position, _gp group position
- Bitmuster _bm bit mask, _gm group mask

1
/* PORTCFG.EBIOUT  bit masks and bit positions */
2
#define PORTCFG_EBICSOUT_gm  0x03  /* EBI Chip Select Output group mask. */
3
#define PORTCFG_EBICSOUT_gp  0  /* EBI Chip Select Output group position. */
4
#define PORTCFG_EBICSOUT0_bm  (1<<0)  /* EBI Chip Select Output bit 0 mask. */
5
#define PORTCFG_EBICSOUT0_bp  0  /* EBI Chip Select Output bit 0 position. */
6
#define PORTCFG_EBICSOUT1_bm  (1<<1)  /* EBI Chip Select Output bit 1 mask. */
7
#define PORTCFG_EBICSOUT1_bp  1  /* EBI Chip Select Output bit 1 position. */

von define (Gast)


Lesenswert?

> Kann man, ist aber beim AVR nicht unbedingt
> sinnvoll, weil dort nun mal aus historischen
> Gründen SÄMTLICHE Bits als Bitnummer definiert sind.

Das macht es nicht besser. :'(

Nur weil eine Atmelstudenthilfskraft den Blödsinn verzapft hat, muss man 
das nicht leben. :-P

von Falk B. (falk)


Lesenswert?

@ define (Gast)

>Nur weil eine Atmelstudenthilfskraft den Blödsinn verzapft hat, muss man
>das nicht leben. :-P

Mag sein, aber ich hab keinen Bock, SÄMTLICHE Registerbits mit eigenen 
#defines neu zu definieren!

Dann müsste man sinnvollerweise mittels Script aus den alten includes 
ein neues machen, wo überall nur Bitmuster drin sind. In C braucht man 
die Bitnummer eher selten.

von define (Gast)


Lesenswert?

Falk Brunner schrieb:
> In C braucht man
> die Bitnummer eher selten.

Klar, aufm PC. Beim uC brauchst du sie permanent. Und da nervt ( 1 << 
...

von _sbi_ (Gast)


Lesenswert?

Danke euch wie immer für die schnelle Hilfe.

Ich habe es jetzt so gelöst und bin recht zufrieden damit.



#include <avr/io.h>
#include <stdint.h>

char bit[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

int main(void)
{
    DDRD=0b01100000;
  while (1)
  {
PORTD |= bit[5] | bit[6];
  }
}

von Wolfgang (Gast)


Lesenswert?

_sbi_ schrieb:
> Ich habe es jetzt so gelöst und bin recht zufrieden damit.
>
> char bit[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
>
> PORTD |= bit[5] | bit[6];

Dann guck dir mal an, was dein Compiler da für einen Assembler Code 
draus strickt.

Zumindest könntest du ihm verraten, dass du versprichst, nie die Werte 
von dem bit[]-Array zu verändern und das Ding als const deklarieren.

von Falk B. (falk)


Lesenswert?

@ define (Gast)

>> In C braucht man
>> die Bitnummer eher selten.

>Klar, aufm PC. Beim uC brauchst du sie permanent. Und da nervt ( 1 <<

Eben DARUM braucht man die Bitnummer SELTEN, denn man macht ja fast 
immer aus 1 << Bitnummer die Bitmaske. Das ist der MSP430, bzw. dessen 
Compiler besser, die haben von vorn herein nur Bitmasken.

von define (Gast)


Lesenswert?

@Falk
Was schreibst du für wirres Zeug?
Wenn ich für einen uC programmiere brauche andauernd die Bitmasken. 
Und die schiebe ich nicht umständlich zusammen, oder schreibe 5 unnötige 
Zeichen pro Bit dazu.

Jede andere Entwicklungsumgebung (hat nichts mit dem uC zu tun) hat 
vernünftige header!

Und von der Atmelsch... angestiftet wird ein totaler Müll generiert.
> PORTD |= bit[5] | bit[6];

von Hans (Gast)


Lesenswert?

_sbi_ schrieb:
> Danke euch wie immer für die schnelle Hilfe.
>
> Ich habe es jetzt so gelöst und bin recht zufrieden damit.
>
> #include <avr/io.h>
> #include <stdint.h>
>
> char bit[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
>
> int main(void)
> {
>     DDRD=0b01100000;
>   while (1)
>   {
> PORTD |= bit[5] | bit[6];
>   }
> }

Autsch. Das kostet 8 Byte RAM und ist wesentlich langsamer.

Dann doch lieber das _BV-Makro aus avr/io.h:
1
PORTD |= _BV(5) | _BV(6);

Oder Du definierst Dir selber eins:
1
#define BIT(x) (1<<(x))
2
3
PORTD |= BIT(5) | BIT(6);

von Yalu X. (yalu) (Moderator)


Lesenswert?

define schrieb:
> @Falk
> Was schreibst du für wirres Zeug?

Das ist doch nicht wirr.

Falk Brunner schrieb:
> Eben DARUM braucht man die Bitnummer SELTEN

define schrieb:
> Wenn ich für einen uC programmiere brauche andauernd die Bitmasken.

Ich sehe da keinen Widerspruch. Ich glaube vielmehr, ihr beide seid auch
völlig einig und wisst es nur nicht ;-)

Zur Historie der Bitnummern für AVRs:

Am Anfang war der Assembler.

Da im AVR-Befehlssatz die Einzelbitinstruktionen CBI, SBI, SBIC und SBIS
Bitnummern als Argumente erwarten und man mit den Rechenoperationen, die
ein Assembler üblicherweise beherrscht, leichter eine Bitnummer in eine
Bitmaske umrechnen kann als umgekehrt, war es naheliegend, die einzelnen
Bits in den I/O-Registeren als Bitnummern statt als Bitmasken zu
definieren.

Dann kam der C-Compiler in Form des GCC 3.0.

Anfang machte der noch keine CBI/SBI/SBIC/SBIS-Optimierung. Diese
Instruktionen waren nur per Inline-Assembler verfügbar, deswegen hat man
auch hier die Bitnummern gebraucht, da auch der C-Compiler nicht ohne
weiteres in der Lage ist, Bitmasken in Bitnummern umzurechnen.

Dann kam der GCC 3.3 mit CBI/SBI/SBIC/SBIS-Optimierung.

Nun brauchte man keine Bitnummern mehr. Da sich aber schon alle an den
Umgang mit den Bitnummern gewöhnt hatten und keiner Lust hatte,
bestehenden Code von Bitnummern auf Bitmasken umzustellen, bleib einfach
alles so, wie es war ;-)

Die Verwendung von Bitnummern in C hat auch noch den Vorteil, dass man
für GCC und GNU-Assembler dieselben Header-Files (avr/io*.h aus der
AVR-Libc) verwenden kann.

Ansonsten findet wahrscheinlich jeder (einschließlich Falk) in C die
Bitmasken praktischer. ABer jetzt ist es halt einmal so wie es ist :)


Hans schrieb:
> Autsch. Das kostet 8 Byte RAM und ist wesentlich langsamer.

Wie Wolfgang oben schon geschrieben hat, wird der Code optimiert, wenn
man das Array mit const deklariert. Am besten schreibt man auch gleich
noch ein static mit dazu, um zu verhindern, dass das Array
unnötigerweise Speicherplatz belegt. Ich würde es aber trotzdem nicht so
machen.

: Bearbeitet durch Moderator
von define (Gast)


Lesenswert?

@Yalu
Danke für die Historie der Atmel header.

Ja, an die Einbindung von C in Atmels Toolsammlung kann ich mich nur 
noch schwammig erinnern. Das zeigt wieder den Ehrgeiz und die Weitsicht, 
mit der Atmel vorgeht. :-(

Leider muss ich demnächst ein Projekt weiter führen und mit dem Studio 
umsetzen. Mir graut es schon vor der Syntax und dem Framework. Normal 
habe ich Zugang zu IAR und ARM.

von Karl Käfer (Gast)


Lesenswert?

Hallo Hans,

Hans schrieb:
> _sbi_ schrieb:
>> char bit[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
>>
>> int main(void)
>> {
>>     DDRD=0b01100000;
>>   while (1)
>>   {
>> PORTD |= bit[5] | bit[6];
>>   }
>> }
>
> Autsch. Das kostet 8 Byte RAM und ist wesentlich langsamer.
>
> Dann doch lieber das _BV-Makro aus avr/io.h:
>
1
PORTD |= _BV(5) | _BV(6);

Wenn schon, denn schon:
1
PORTD |= _BV(PD5) | _BV(PD6);

Liebe Grüße,
Karl

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Karl Käfer schrieb:
> Wenn schon, denn schon:
> PORTD |= _BV(PD5) | _BV(PD6);
Wobei das irgenwie redundant und das selbe ist wie:
PORTD |= _BV(PA5) | _BV(PC6);

von Karl Käfer (Gast)


Lesenswert?

Hallo Lothar,

Lothar Miller schrieb:
> Karl Käfer schrieb:
>> Wenn schon, denn schon:
>> PORTD |= _BV(PD5) | _BV(PD6);
> Wobei das irgenwie redundant und das selbe ist wie:
> PORTD |= _BV(PA5) | _BV(PC6);

Jaein. Ja, weil PAn == PDn bisher in den AVRs so implementiert ist. 
Nein, weil es in zukünftigen AVRs vielleicht nicht mehr so sein wird -- 
und daß das ziemlich bescheuert wäre, steht hier nicht zur Debatte. 
Zukunftsfähig und portabel, sowie im Sinne der aktuellen 
Implementierung, und daher die einzig richtige Lösung, ist also:
1
PORTD |= _BV(PD5) | _BV(PD6);

Wie gesagt: wenn schon, denn schon. Wenn schon Korinthenkacken, dann 
bitte richtig. Sonst macht's ja keinen Spaß. ;-)

Liebe Grüße,
Karl

von Karl Käfer (Gast)


Lesenswert?

Ach ja,

Karl Käfer schrieb:
> Wie gesagt: wenn schon, denn schon. Wenn schon Korinthenkacken, dann
> bitte richtig. Sonst macht's ja keinen Spaß. ;-)

Wo wir gerade dabei sind, was ist damit:
1
#include <avr/io.h>
2
3
enum bits_d {
4
    d0 = (1 << PD0),
5
    d1 = (1 << PD1),
6
    d2 = (1 << PD2), 
7
    d3 = (1 << PD3), 
8
    d4 = (1 << PD4), 
9
    d5 = (1 << PD5), 
10
    d6 = (1 << PD6), 
11
    d7 = (1 << PD7)
12
};
13
14
int main(void) {
15
    DDRD |= d5 | d6;
16
    while(1) {
17
        PORTD |= d5 | d6;
18
    }
19
    return 0;
20
}

Liebe Grüße,
Karl

von Karl H. (kbuchegg)


Lesenswert?

Karl Käfer schrieb:

Der springende Punkt ist, dass das hier

>
1
> int main(void) {
2
>     DDRD |= d5 | d6;
3
>     while(1) {
4
>         PORTD |= d5 | d6;
5
>     }
6
>     return 0;
7
> }
8
>

so zwar für kurze Einführungsprogramme taugt, aber nicht für ein reales 
Projekt.

In einem realen Projekt habe ich es mit verschiedenen LED zu tun, die 
eine Bedeutung haben. Ich habe es mit Relais zu tun, die etwas schalten. 
Usw, usw.
Und mit genau diesen Begriffen will auch auch im Programm handhaben und 
nicht mit irgendwelchen Bitnummern.

Ich will schreiben (ob jetzt mit dieser Syntax oder einer anderen sei 
jetzt mal dahingestellt)
1
int main()
2
{
3
  ...
4
5
  while( 1 ) {
6
7
    ON( ErrorLED );
8
    OFF( ReadyLED );
9
10
    ON( TuerMotorLinks );
11
    OFF( TuerMotorRechts );
12
13
    ....
14
  }
15
}

womit sich die Frage, wie ich im eigentlichen Code die Bits schreibe so 
gar nicht mehr stellt. Und für die paar #define, die ich dazu im Vorfeld 
benötige spielt es praktisch keine Rolle, ob ich dort mit Bitnummern 
oder mit Bitmasken operiere.

: Bearbeitet durch User
von Karl Käfer (Gast)


Lesenswert?

Hallo Karl Heinz,

Karl Heinz schrieb:
> Karl Käfer schrieb:
>
> Der springende Punkt ist, dass das hier
>
>>
1
>> int main(void) {
2
>>     DDRD |= d5 | d6;
3
>>     while(1) {
4
>>         PORTD |= d5 | d6;
5
>>     }
6
>>     return 0;
7
>> }
8
>>
>
> so zwar für kurze Einführungsprogramme taugt, aber nicht für ein reales
> Projekt.

Pardon, aber das war nicht mein Beispiel.

> In einem realen Projekt habe ich es mit verschiedenen LED zu tun, die
> eine Bedeutung haben. Ich habe es mit Relais zu tun, die etwas schalten.

Was Du suchst, ist ein einfacher Hardware Abstraction Layer. Kein 
Problem, das verstehe ich gut, aber so etwas war hier leider nicht 
gefragt.

> Und mit genau diesen Begriffen will auch auch im Programm handhaben und
> nicht mit irgendwelchen Bitnummern.
>
> [...]
>
> womit sich die Frage, wie ich im eigentlichen Code die Bits schreibe so
> gar nicht mehr stellt. Und für die paar #define, die ich dazu im Vorfeld
> benötige spielt es praktisch keine Rolle, ob ich dort mit Bitnummern
> oder mit Bitmasken operiere.

Das kannst Du auf unterschiedliche Weisen realisieren. Zum Beispiel 
zwingt Dich niemand, Deine Bitnummern "d[0-7]" zu nennen; die kannst Du 
in dem enum auch "klausdieter", "karl" oder 
"rote_led_am_dritten_pin_von_links" nennen, ganz wie Du lustig bist. Und 
wenn Du schon mit Makros hantierst, mach doch einfach sowas:
1
#define ON(PORT,PIN)   ((PORT) |=  (1 << (PIN)))
2
#define OFF(PORT,PIN)  ((PORT) &=~ (1 << (PIN)))
3
#define ErrorLED PORTD,PD5
4
#define ReadyLED PORTD,PD6

Dann expandiert bei "ON( ErrorLED )" erst "ErrorLED" zu "PORTD,PD5" und 
dann "ON(...)" zu "((PORTD) |= (1 << (PD5)))". Was willst Du mehr?

Mit dem Enum sähe das etwa so aus:
1
#define ON(PORT,PIN)   ((PORT) |=  (PIN))
2
#define OFF(PORT,PIN)  ((PORT) &=~ (PIN))
3
#define ErrorLED  PORTD,d5
4
#define ReadyLED  PORTD,d6

Das Ergebnis wäre dann genau dasselbe wie zuvor.

Es gibt also sehr viele Möglichkeiten, solche Sachen hübscher, les- und 
wartbarer zu gestalten und die fiese Bitshifterei wegzukapseln. Aber die 
Basis ist und bleibt eben trotzdem immer noch die fiese Bitshifterei, da 
beißt die Maus keinen Faden ab.

Liebe Grüße,
Karl

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.