Forum: Mikrocontroller und Digitale Elektronik Bit auf bestimmten Wert setzen


von Basti (Gast)


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

von Thomas E. (thomase)


Lesenswert?

Basti schrieb:
> Macht man das so?

Genau so.

von Marc V. (Firma: Vescomp) (logarithmus)


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.

von Basti (Gast)


Lesenswert?

Super danke!
Damit ist mir eigentlich schon geholfen...

von Rolf M. (rmagnus)


Lesenswert?

Ich würde es aber als Inline-Funktion (oder ggf. Makro) schreiben, da 
das optimierungstechnische Vorteile bietet.

von (prx) A. K. (prx)


Lesenswert?

Es ist recht selten, dass man für SPI eine Funktion in dieser Form 
benötigt. Immerhin weiss man üblicherweise, ob man SS setzen oder 
löschen will. Es ist also alternativ ebenso sinnvoll, daraus 2 
Funktionen zu machen. Eine aktiviert SS, eine deaktiviert SS.

von Achim (Gast)


Lesenswert?

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.

Zum lernen ok, wenig professionell.

von Thomas E. (thomase)


Lesenswert?

Achim schrieb:
> da bit-funktionen des uC nicht verwendet werden können.

Nein? Na dann guck mal, was der Compiler daraus macht.

von Erwin D. (Gast)


Lesenswert?

Achim schrieb:
> So ein Code ist übel
Wie müßte er aussehen, damit er nicht "übel" wäre?

von Witkatz :. (wit)


Lesenswert?

Achim schrieb:
> nicht
> immer optimal, da bit-funktionen des uC nicht verwendet werden können.

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

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Man könnte das Register auch in eine variable schreiben, das 
entsprechende Bit 0 setzen und state in dieses Bit schreiben. So umgehst 
du zumindest mal die abfrage.

Ob das sinnvoll(er) ist weiß ich nicht. Aber vllt erbarmt sich ja ein 
Profi :)

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


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.

: Bearbeitet durch Moderator
von W.S. (Gast)


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.

von Basti (Gast)


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

von A. S. (Gast)


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?

von Nop (Gast)


Lesenswert?

Frank M. schrieb:
> Damit ist das if-Statement aber dann hinfällig

Das ist es auch, wenn man das nicht als Funktion, sondern als Makro 
macht und immer mit 0/1 aufruft. Der Compiler eliminiert dann das 
if-statement. Oder man macht das gleich mit dem ?-Operator im Makro.

von (prx) A. K. (prx)


Lesenswert?

Nop schrieb:
> Das ist es auch, wenn man das nicht als Funktion, sondern als Makro
> macht und immer mit 0/1 aufruft.

Oder man definiert die Funktion(en) als "inline". Ist eleganter als 
Makros und es kommt hier das gleiche Ergebnis raus.

von Nop (Gast)


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.

von Achim (Gast)


Lesenswert?

Nop schrieb:
> Will man da mit Bitbefehlen ran, dann sollte man sich Makros mit
> Inline-Assembler definieren.

Aber genau das braucht man ja nicht, wenn man bitstrukturen verwendet. 
Sie sind lesbarer und werden auch bei volatile optimal umgesetzt.

von langhaarrocker (Gast)


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.

von Achim (Gast)


Lesenswert?

langhaarrocker schrieb:
> Kann C einfach nicht.

Doch. C kann hier ja genau die bitmanipulation perfekt. Und wenn es die 
nicht könnte, würde man die eine assemblerzeile einfügen. Und hi-tech 
zeigt, wie gut Harvard klappt.

von (prx) A. K. (prx)


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.

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


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

von Axel S. (a-za-z0-9)


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.

von (prx) A. K. (prx)


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.

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