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
voidspi_setSS(uint8_tstate)
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
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.
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.
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.
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.
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 :)
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.
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.
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
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?
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.
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.
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.
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.
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.
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.
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.
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
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):
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
voidlcd_cmd(uint8_tcmd){
2
CS_low();
3
DATA(1);WR_pulse();
4
DATA(0);WR_pulse();
5
DATA(0);WR_pulse();
6
uint8_tmask=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.
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.