Hallo Leute,
habe für einen ATMega folgenden Code erstellt, kann ich da noch was
optimieren?
ich bin leider Anfänger in Sachen C Programmierung und wollte jetzt
etwas Hilfe. Vielleicht lässt sich da noch was verbessern...
oh, ja mein Code ist zum Empfang von einer String Zeile um
die PortPin von PORTB[0-5] und PORTC[0-5] ein- und auszuschalten.
Er soll aber nur dann reagieren, wenn die Zeile mit "#a" anfängt.
Ich würde die Senderoutine auch noch auf Interrupt umstellen um die
blockierende while Schleife loszuwerden. Sonst scheint das zu passen.
Sieht mir ziemlich nach dem Beispiel aus dem Atmel Datenblatt aus.
ja, doch leider soll es eine quasi Master-Slave Geschichte werden. Also
wollte ich den Zeitpunkt des Sendes fest in der Hand haben.
So das die µC nur auf ihren Start hören "#a", "#b", "#c" ....
Das macht bestimmt was es soll (Pin0-PortC einschalten) aber halt falsch
geschrieben, damit du später mal nicht durcheinander kommst ändere dies
noch in PC0, PC1, etc... um.
Achim schrieb:> So ist es wirklich schön.>> Verwirrend könnte sein, dass ein Telegramm mit \r und \n am Ende 2 Main> -durchläufe startet.
Hi, sollte ich da nur aus eines Testen, also nur \r?
Dachte nur weil Windows und/oder Linux..
Moinsen,
ich würde an deiner Stelle den Seiteneffekt aus der Main rauswerfen.
Bernd schrieb:> uart_str_complete = 0;
das sollte aus der main raus. Mach dir eine Funktion int GetLine(char
*line), die 0 zurückgibt, wenn keine Zeile gelesen wurde und nicht null,
wenn eine komplette Zeile da ist. Da kannst du dann prüfen, ob du eine
Zeile im buffer hast.
Wenn du mal dein Programm erweitern willst, tust du gut daran, so was in
Zukunft zu kapseln. Das kann sonst schnell ausarten.
Grüße,
Das Hackergetrickse spart ein paar Zeilen, aber erfordert eine
Dokumentation, die ungefähr so aussehen würde wie der Code vom OP
sowieso schon aussah.
main() ist viel zu lang. Das Auswerten des Strings ist ein
übersichtlicher und in sich abgeschlossener Block, das Setzen eines
Ausgangs abhängig vom Zeichen auch. Deshalb darf man Funktionen daraus
machen:
Nachtrag: Man könnte überlegen, ob man einen einfachen Zustandsautomaten
mitlaufen lässt, nach den Zeichen '#a' vom "Ich lauere"- in den "Ich bin
dran"-Modus geht und ab dann jedes empfangene Zeichen ohne
Zwischenspeicherung direkt auf die Ausgänge wirken lässt. Ein '\n' würde
dann wieder auf "Ich lauere" schalten.
Bernd schrieb:> Hi, sollte ich da nur aus eines Testen, also nur \r?
Ich würde den Code so lassen schadet ja nicht, solange keiner "Fehler"
zählt.. Eher würde ich das # als Startzeichen festlegen, und immer dann
und nur dann neu starten, wenn es kommt.
Aber das gehört nicht zu C sondern zu Protokoll.
Peter D. schrieb:> Man könnte die Regel aufschreiben, nach der die Bits gesetzt> werden:uint8_t setbit( uint8_t val, uint8_t bit, uint8_t level )> {> uint8_t mask = 1<<bit;> val |= mask;> if( level == 0 )> val ^= mask;> return val;> }>> void setiobit( char c )> {> uint8_t level = 0;> switch( c ){> case 'A' ... 'L': level = 1;> c += 'a' - 'A';> }> switch( c ){> case 'a' ... 'f': PORTC = setbit( PORTC, c - 'a', level );> break;> case 'g' ... 'l': PORTB = setbit( PORTB, c - 'g', level );> }> }
DANKE, cool, genau so was hatte ich gesucht, da wäre ich noch/nie darf
gekommen. Das mit dem Auslagern und den "nur" warten auf "#", werde ich
auch noch umsetzten.
Und ja es soll am Schluss eine "Art" Protokoll sein.
Gruß
Bernd
Peter D. schrieb:> void setiobit( char c )> {> uint8_t level = 0;> switch( c ){> case 'A' ... 'L': level = 1;> c += 'a' - 'A';> }> switch( c ){> case 'a' ... 'f': PORTC = setbit( PORTC, c - 'a', level );> break;> case 'g' ... 'l': PORTB = setbit( PORTB, c - 'g', level );> }> }
Peter... kannst du mir das mal aufdrösseln bitte?!
Bei den case ('A'...'L' z.b.) wird da jeder Wert welcher in diesem
Bereich liegt beachtet? Das hab ich noch nie so gesehen, auch in keinem
C Buch. Aber wäre ja klasse wenn das so der Fall ist.
Hi,
habe es so mal in ein Shell C Code mit fprint Ausgabe geschrieben. Und
ja es werden alle Werte einzel verarbeitet.
Der erste switch merke sich nur da "AN" in level.
Im zweiten wird dann nur das entsprechen Bit gesetzt/gelöscht.
echt klasse, nochmals Danke
Gruß
Bernd
PS.:
angenommen c = 'B':
> switch( c ){>> case 'A' ... 'L': level = 1;>> c += 'a' - 'A';
ist c in der Menge von 'A' bis 'L', ja dann 1. level = 1 //merke war
groß Buchstabe. 2. c = c + 97 - 65 -->> c = 66 + 97 - 65 somit ist c =
98 = 'b'
Leo C. schrieb:> Das ist eine GCC Erweiterung.
Ja, das ist schade. Man damit viele Sachen deutlich besser lesbar
hinschreiben. Bzw. man stelle sich mal vor, bis zu 65536 cases einzeln
hinschreiben zu müssen.
Allerdings hat der GCC in der MC-Welt recht große Verbreitung gefunden.
Ich weiß, man kann das auch mit if/else-Ketten machen, aber das ist
lange nicht so übersichtlich. Schon, daß man bei jedem Vergleich
überprüfen muß, ob da auch wirklich die selbe Variable steht, macht es
schwer lesbar. Ein kleiner Vertipper (100* i und einmal j) und man sucht
sich nen Wolf.
Und außerdem ist es fehlerträchtig, da man versehentlich überlappende
Bereiche schreiben kann.
Peter D. schrieb:> Ich weiß, man kann das auch mit if/else-Ketten machen, aber das ist> lange nicht so übersichtlich. Schon, daß man bei jedem Vergleich> überprüfen muß, ob da auch wirklich die selbe Variable steht, macht es> schwer lesbar. Ein kleiner Vertipper (100* i und einmal j) und man sucht> sich nen Wolf.> Und außerdem ist es fehlerträchtig, da man versehentlich überlappende> Bereiche schreiben kann.
Für diesen Fall ist das aber nicht sooooo schlimm. Da tuts ein:
Tom schrieb:> Das Hackergetrickse spart ein paar Zeilen, aber erfordert eine> Dokumentation
Es mag zwar sein, daß die Regel etwas schwerer zu verstehen ist, aber
bei Copy&Paste-Monstern habe ich immer das Problem, daß sich schnell ein
Schreibfehler in eine Kopie einschleicht.
Und ich muß auch immer sämtliche Fälle prüfen. Bei der Regel reicht es,
wenn ich prüfe, daß 'a' und 'L' funktionieren.
Daher bevorzuge ich das Finden einer Regel. Die Regel kann ich dann auch
viel leichter anpassen, z.B. eine Case-Zeile für PORTD hinzufügen oder
andere Bitbereiche je Port.
Und anscheinend wurde die Regel ja auch ohne Dokumentation gut
verstanden.
Für die Berechnung der Baudrate kannst du dich alternativ auf die
Library setbaud.h verlassen
http://www.nongnu.org/avr-libc/user-manual/group__util__setbaud.html.
Die Taktfrequenz (F_CPU) stellt man überlicherweise im Makefile bzw. in
den Projekteinstellungen ein, damit nicht nur dein Hauptprogramm sondern
alle Files die Taktfrequenz kennen. Bei diesem kleinen Programm macht
das sicher keinen Unterschied, bei komplexeren Projekten aber schon.
ich halte den Originalcode für einen Anfänger für sehr gut. Ich möchte
jedoch auf 2 Punkte bei diesem Konstrukten eingehen:
1
>case'A':PORTC|=(1<<PB0);break;
2
>case'a':PORTC&=~(1<<PB0);break;
Punkt 1: Die Bitmanipulation: Zu Beginn völlig OK, doch bald sollte es
dafür generelle Konstrukte geben, um nicht jedesmal "umzurechnen". Egal
ob Funktion oder Makro, egal ob BIT als 0..7 oder als 1,2,4,...128
definiert wird. Wichtig ist mir nur, das die Variable nur einmal in
einer Anweisung steht. Also z.B.:
1
#define BIT0 1
2
...
3
#define BIT7 128
4
5
#define SET(reg, Bit) ((reg) |= (Bit))
6
#define RES(reg, Bit) ((reg) &= ~(Bit))
7
...
8
case'A':SET(PORTC,BIT0);break;
9
case'a':RES(PORTC,BIT0);break;
Also nicht PORTC = SET(PORTC,BIT0);
und auch keine Makros, die den Registernamen zerstückeln, also nicht
1
#define SetPortBit(port,bit) (PORT##port)|=(Bit))
2
...
3
SetPortBit(C,BIT0);
Punkt 2: Im Original sieht man, welcher Pin zu welchem Buchstaben
gehört. Das ist gut. "case 'A'..'L'" sind schwieriger Lesbar und nicht
wartbar. Will man (aufgrund eines Verdrahtungsfehlers) a mit f
vertauschen, zerfällt der Code zu Spaghetti. Im Urpost vertauscht man
2*2 Buchstaben und kommentiert das kurz. Der 'A'..'L'-Code ist auch
nicht effektiver, eher im Gegenteil.
Ich würde eher die copy-paste Anteile in ein Quick-n-dirty-Makro
packen um den Konfigurationsaspekt herauszustellen:
1
#define qd(OnCmd, OffCmd, Port, Bit) \
2
case OnCmd: SET(Port,1<<Bit); break;
3
caseOffCmd:RES(Port,1<<Bit);break;
4
...
5
qd('A','a',PORTB,PB0);
6
qd('B','b',PORTB,PB1);
Diese Zeilen verdeutlichen auch nochmal, wie wichtig eine konsistent
Interpretation von Bits im Projekt ist (BIT7==7 || BIT7==128??). Die
Vor- und Nachteile findet jeder selber im Einsatz schnell heraus.
Achim schrieb:> Ich würde eher die copy-paste Anteile in ein Quick-n-dirty-Makro> packen um den Konfigurationsaspekt herauszustellen:> #define qd(OnCmd, OffCmd, Port, Bit) \> case OnCmd: SET(Port,1<<Bit); break;> case OffCmd: RES(Port,1<<Bit); break;> ...> qd('A','a',PORTB,PB0);> qd('B','b',PORTB,PB1);> Diese Zeilen verdeutlichen auch nochmal, wie wichtig eine konsistent> Interpretation von Bits im Projekt ist (BIT7==7 || BIT7==128??). Die> Vor- und Nachteile findet jeder selber im Einsatz schnell heraus.
Hier bin ich raus, mach da bitte mal ein richtiges Beispiel von, würde
mir das gerne mal anschauen.
Achim schrieb:> #define qd(OnCmd, OffCmd, Port, Bit) \> case OnCmd: SET(Port,1<<Bit); break;> case OffCmd: RES(Port,1<<Bit); break;> ...> qd('A','a',PORTB,PB0);> qd('B','b',PORTB,PB1);
Cases hinter Makros verstecken? Das ist keine gute Idee. In einem halben
Jahr weiß der Programmierer selber nicht mehr, was das soll. Da finde
ich sogar das Konstrukt vom Arduino Fanboy aus dem anderen Thread noch
lesbarer:
Arduino F. schrieb:> state=__LINE__; return; case __LINE__:> while(millis()-timestamp<interval) return;Achim schrieb:> Will man (aufgrund eines Verdrahtungsfehlers) a mit f> vertauschen, zerfällt der Code zu Spaghetti.
Das willst du auch nicht in Software ändern. Nachdem das ein
Master/Slave Konstrukt wird mit mehreren Slaves, willst du keinen
"besonderen" mit einer angepassten Firmware haben. Lieber die Verdratung
korrigieren.
Wenn schon hochflexibel (ob das überhaupt nötigh ist wäre eine wichtige
Frage), dann die Daten in ein Struct Array packen und das auswerten.
Wenn man so was über den kürzesten weg machen möchte wird es immer
unübersichtlich.
Grüße,