Bit auf bestimmten Wert setzen

Gast #4788455
Lesenswert?

Nabend zusammen,

eine ganz kurze Frage, die mir google und die Suche hier nicht so recht 
beantworten konnte.

Follgender Code:
1
//set Slave Select
2
void spi_setSS(uint8_t state)
3
{
4
  if(state == 0)
5
    PORTB &= ~(1<<PB4);
6
  else
7
    PORTB |= (1<<PB4);  
8
}

Ich möchte ein bestimmtest Bit (PB4) auf den Wert setzen, der per state 
übergeben wird (also 1 oder 0)
Aktuell mache ich das so wie oben angegeben...
Macht man das so? Oder Gibts da eine sauberere Lösung?

Gruß Basti
(Firma: Vescomp) #4788468
Lesenswert?

Basti schrieb:
> Macht man das so? Oder Gibts da eine sauberere Lösung?

 Nein, gibt es nicht.
 Irgendjemand von unzähligen möchtegernExperten hier wird bestimmt
 das Gegenteil behaupten, aber der Compiler macht aus deinem C-Code
 den kürzest möglichen Assembler-Code.

 Natürlich geht das mit #define kürzer und schneller, aber als
 function nicht.
Moderator Persönliche Seite #4789084
Lesenswert?

A. K. schrieb:
> Immerhin weiss man üblicherweise, ob man SS setzen oder
> löschen will.

Genau so ist es. Man wird also in den meisten Fällen
entweder

   spi_setSS(0);

oder

   spi_setSS(1);

aufrufen. Eine Variable wird da überhaupt nicht benötigt.

Damit ist das if-Statement aber dann hinfällig und es wäre eleganter, 
entweder zwei Funktionen zu schreiben wie z.B. spi_enable_ss() und 
spi_disable_ss() oder halt zwei simple Makros.
Gast #4789190
Lesenswert?

Achim schrieb:
> So ein Code ist übel und ein Grund für das hacker-image von C. Und nicht
> immer optimal, da bit-funktionen des uC nicht verwendet werden können.

Im Grunde hast du ja Recht, aber das ist eben so, wenn man eine 
Programmiersprache verwenden will, die eben nicht direkt 
hardwarebezogen ist wie Assembler.

Genau DAS ist auch der Grund, weswegen bei ganz kleinen µC (512 
Programmschritte oder so und 2 Stackplätze) auch heutzutage noch immer 
Assembler angesagt ist und das Daherkommen mit C einfach deplaziert 
wirkt.

Ansonsten bin ich für Frank's separate Funktionen, eine zum auf HI 
setzen und die ander zum auf LO setzen. Das liest sich am saubersten und 
ergibt auch den kompaktesten Code, da kein Argument benötigt wird und 
jegliche Fallunterscheidung unnötig ist.

W.S.
Gast #4789228
Lesenswert?

W.S. schrieb:
> Ansonsten bin ich für Frank's separate Funktionen, eine zum auf HI
> setzen und die ander zum auf LO setzen. Das liest sich am saubersten und
> ergibt auch den kompaktesten Code, da kein Argument benötigt wird und
> jegliche Fallunterscheidung unnötig ist.

So mach ich das jetzt auch, Danke euch nochmal
Gast #4789446
Lesenswert?

Erwin D. schrieb:
> Wie müßte er aussehen, damit er nicht "übel" wäre?

Bitshiftverrenkungen gehören gekapselt. Es dauert einfach länger, die 
Aufgabe zu erfassen. Shieben, invertieren, &

> PORTB &= ~(1<<PB4);

Leider gibt es keinen Standard. Wenn ich auf einer fremden Plattform was 
einhacken soll, ist das OK. Wenn ich diese Plattform professionell 
nutze, sorge ich für die Infrastruktur. Die kostet einen Tag harte 
Arbeit, aber dann steht sie.

Hier Alternativen für die Zeile oben, ganz allgemein, ohne 
Spezialisierung auf die SPI-Aufgabe (die ich auch machen würde).
1
PORTB.PB4 = 0; /*Structs/Unions für jedes Prozessorregister*/
2

3
PORTB.bit4 = 0; /*generische typen mit shortcuts für Register */
4

5
BIT_CLR(&PORTB, PB2); /* als Makro ohne &, wenn PORTB nicht adressierbar ist */

Witkatz :. schrieb:
> Es ist die Sache des Compilers, die Funktionen des µC optimal zu
> verwenden und das machen die heutigen Compiler auch sehr gut.

Ich muss sagen, da schwimme ich. Darf der Compiler im original-Code 
Bitbefehle nutzen, wenn PORTB volatile ist?
Gast #4789535
Lesenswert?

Achim S. schrieb:
> Darf der Compiler im original-Code
> Bitbefehle nutzen, wenn PORTB volatile ist?

Nein. Der Originalcode sagt aus, daß die Variable erstmal zu lesen ist, 
das darf bei volatile nicht wegoptimiert werden. Mit einem direkten 
Bitsetzbefehl würde man das aber wegoptimieren.

Volatile könnte ja auch heißen, daß durch das Lesen selber die Variable 
sich ändert, wie das bei Statusregistern oft der Fall ist. Der Compiler 
darf volatile-Lesezugriffe nichtmal dann wegoptimieren, wenn man exakt 
dieselbe Variable zweimal nacheinander liest.

Will man da mit Bitbefehlen ran, dann sollte man sich Makros mit 
Inline-Assembler definieren.
Gast #4789567
Lesenswert?

Zeigt jedenfalls wunderbar, das die Behauptung C sei eine geeignete 
Sprache sei um hardwarenah zu programmieren ein hanebüchener Hirnschiss 
ist! Und dabei sind wir noch nicht mal bei dem Thema von Havard 
Architekturen. Kann C einfach nicht. Egal was man für einen Workaround 
darum bastelt - es kommt nur noch mehr Hirnschiss raus.

Gibts eigentlich eine vernünftige Sprache für hardwarenahe 
Programmierung? Und nein, Assembler ist auch Unsinn. Ein bißchen mehr 
Abstraktion ist schon nötig um vernünftig und effizient programmieren zu 
können. Das geht nicht mit Assembler.
#4789647
Lesenswert?

langhaarrocker schrieb:
> Zeigt jedenfalls wunderbar, das die Behauptung C sei eine geeignete
> Sprache sei um hardwarenah zu programmieren ein hanebüchener Hirnschiss
> ist!

Es zeigt eigentlich nur, dass man nicht gleichzeitig auf unterster Ebene 
hardwarenah programmieren kann, ohne bei entweder Portabilität oder 
Effizienz Abstriche machen zu müssen. Das liegt aber in der Natur der 
Sache, denn was die Hardware nicht hergibt kann die Software nicht 
gleichwertig ersetzen. Egal in welcher Sprache.
#4789649
Lesenswert?

Nop schrieb:
> Achim S. schrieb:
>> Darf der Compiler im original-Code
>> Bitbefehle nutzen, wenn PORTB volatile ist?
>
> Nein. Der Originalcode sagt aus, daß die Variable erstmal zu lesen ist,
> das darf bei volatile nicht wegoptimiert werden. Mit einem direkten
> Bitsetzbefehl würde man das aber wegoptimieren.

So ein Blödsinn. Das sagt volatile natürlich nicht aus. Nuhr!
Folgerichtig übersetzt z.B. der avr-gcc das folgende C-Fragment:

1
ACSR |= _BV(ACD);

zu diesem Assembler-Schnipsel:

1
sbi 0x8,7

Disclaimer: avr-gcc 4.8.1 in Optimierungsstufe -Os für einem ATtiny2313
#4789653
Lesenswert?

Frank M. schrieb:
> A. K. schrieb:
>> Immerhin weiss man üblicherweise, ob man SS setzen oder
>> löschen will.
>
> Genau so ist es.  ... es wäre eleganter,
> entweder zwei Funktionen zu schreiben wie z.B. spi_enable_ss() und
> spi_disable_ss() oder halt zwei simple Makros.

Das gilt allerdings nur spezifisch für diesen Fall, wo man den 
Logikpegel für die Peripherie immer manuell setzt. Das Gegenbeispiel ist 
das Datensignal für eine serielle Kommunikation, wenn man z.B. SPI in 
Software implementiert.

Ich kapsele so etwas typischerweise in Makros (und packe alle diese 
Makros in eine Art mini-HAL in ports.h):

1
#define CS_low()   PORTD &= ~(1<<2); _delay_us(1)
2
#define CS_high()  _delay_us(1); PORTD |= (1<<2);
3
#define DATA(x)    if ((x)==0) PORTD &= ~(1<<3); else PORTD |= (1<<3)
4
#define WR_pulse() PORTD &= ~(1<<4); _delay_us(3); PORTD |= (1<<4); _delay_us(3)

Für das CS Signal habe ich wie von dir (und vorher A. K.) präferiert, 
zwei eigenständige Makros, um es auf H oder L zu setzen. Für die 
Datenleitung ist das Makro DATA() zuständig, und das ist exakt so 
implementiert, wie es der TE vorschlägt. Den Vorteil sieht man, wenn man 
sich den Code anschaut, der mit Hilfe dieser Makros formuliert ist:

1
void lcd_cmd(uint8_t cmd) {
2
    CS_low();
3
    DATA(1); WR_pulse();
4
    DATA(0); WR_pulse();
5
    DATA(0); WR_pulse();
6
    uint8_t mask= 0x80;
7
    while (mask) {
8
        DATA(cmd&mask);
9
        WR_pulse();
10
        mask>>=1;
11
    }
12
    DATA(1); WR_pulse();
13
    CS_high();
14
}

Der Ausdruck "cmd&mask" kann dabei jeweils entweder 0 sein oder genau 
ein gesetztes Bit haben und ich möchte den Daten-Pin entweder auf 0 oder 
1 gesetzt haben. Das DATA() Makro erledigt das auf die direkte und 
i.d.R. auch optimale Art und Weise.
#4789657
Lesenswert?

Axel S. schrieb:
> zu diesem Assembler-Schnipsel:

Tut er - aber die Sache ist ein wenig grenzwertig, da
  port |= 1<<a;
als äquivalent zu
  port = port | 1<<a;
definiert ist, aber
  in  r16, port
  ori r16, 1<<a
  out port, r16
hardwaremässig nicht durchweg vollständig äquivalent zu
  sbi port, a
ist. Auf beispielsweise Interrupt-Flag-Register angewandt verhält sich 
hier der ATmega8 gleich, der ATmega88 verschieden.

Ein ähnliches Problem gibt es bei 8051ern, bei denen Operationen wie
  or port, mask   //(1)
nicht exakt die gleiche Funktion haben wie (frei formuliert)
  load  port      //(2)
  or    mask
  store port
weil (1) die Output-Latches liest, (2) aber den Pinzustand.

Reale Compiler optimieren hier zu Lasten der formalen Korrektheit im 
Sinn der C Definition.

In diesem Sinn geben ich "langhaarrocker" Recht. Die cbi/sbis der AVRs 
und die Porteigenschaft der 51er lassen sich in C nicht formal zu 100% 
korrekt abbilden, ohne auf Intrinsics zurückzugreifen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren