C code UART Atmega und Optimierung

Gast #4744409
Lesenswert?

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...
1
 
2
 #define FOSC 16000000 
3
 #define F_CPU FOSC
4
 #define BAUD 9600
5
 #define MYUBRR FOSC/16/BAUD-1
6
 
7
 #include <avr/io.h>
8
 #include <util/delay.h>
9
 #include <avr/interrupt.h>
10
 #include <string.h>
11
 
12
 #define UART_MAXSTRLEN 32
13

14
 volatile uint8_t uart_str_complete = 0;   
15
 volatile uint8_t uart_str_count = 0;
16
 volatile char uart_string[UART_MAXSTRLEN + 1] = "";
17
   
18

19
void USART_Init ( unsigned int ubrr)
20
{
21
  UBRR0H = (unsigned char)(ubrr>>8);
22
  UBRR0L = (unsigned char)ubrr;
23
  UCSR0B = ((1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0));
24
  UCSR0C = (3<<UCSZ00);
25
}
26

27
ISR(USART_RX_vect)
28
{
29
  unsigned char nextChar;
30

31
  nextChar = UDR0;
32
  if( uart_str_complete == 0 ) {  
33

34
    
35
    if( nextChar != '\n' &&
36
    nextChar != '\r' &&
37
    uart_str_count < UART_MAXSTRLEN ) {
38
      uart_string[uart_str_count] = nextChar;
39
      uart_str_count++;
40
    }
41
    else {
42
      uart_string[uart_str_count] = '\0';
43
      uart_str_count = 0;
44
      uart_str_complete = 1;
45
    }
46
  }
47
}
48

49
void USART_Transmit( unsigned char data )
50
{
51
  while ( !( UCSR0A & (1<<UDRE0)) )
52
  ;
53
  
54
  UDR0 = data;  
55
}
56

57
void uart_puts (char *s)
58
{
59
  while (*s)
60
  {   
61
    USART_Transmit(*s);
62
    s++;
63
  }
64
}
65

66
 int main(void)
67
 {
68
    USART_Init (MYUBRR);
69
  sei();
70
  DDRB |= ((1 << PB0) | (1 << PB1) | (1 << PB2) | (1 << PB3) | (1 << PB4) | (1 << PB5));
71
  DDRC |= ((1 << PC0) | (1 << PC1) | (1 << PC2) | (1 << PC3) | (1 << PC4) | (1 << PC5));
72
  
73
  while (1)
74
  {
75
    if (uart_str_complete == 1)
76
    {
77
      if ( (uart_string[0] == '#') && (uart_string[1] == 'a'))
78
       {
79
      for (unsigned int i = 2;i < strlen(uart_string);i++)
80
       {
81
         switch (uart_string[i])
82
          {
83
            case 'A':  PORTC |= ( 1 << PB0 ); break;
84
            case 'a':  PORTC &= ~( 1 << PB0 ); break;
85
            case 'B':  PORTC |= ( 1 << PB1 ); break;
86
            case 'b':  PORTC &= ~( 1 << PB1 ); break;
87
            case 'C':  PORTC |= ( 1 << PB2 ); break;
88
            case 'c':  PORTC &= ~( 1 << PB2 ); break;
89
            case 'D':  PORTC |= ( 1 << PB3 ); break;
90
            case 'd':  PORTC &= ~( 1 << PB3 ); break;
91
            case 'E':  PORTC |= ( 1 << PB4 ); break;
92
            case 'e':  PORTC &= ~( 1 << PB4 ); break;
93
            case 'F':  PORTC |= ( 1 << PB5 ); break;
94
            case 'f':  PORTC &= ~( 1 << PB5 ); break;
95
            
96
            case 'G':  PORTB |= ( 1 << PB0 ); break;
97
            case 'g':  PORTB &= ~( 1 << PB0 ); break;
98
            case 'H':  PORTB |= ( 1 << PB1 ); break;
99
            case 'h':  PORTB &= ~( 1 << PB1 ); break;
100
            case 'I':  PORTB |= ( 1 << PB2 ); break;
101
            case 'i':  PORTB &= ~( 1 << PB2 ); break;
102
            case 'J':  PORTB |= ( 1 << PB3 ); break;
103
            case 'j':  PORTB &= ~( 1 << PB3 ); break;
104
            case 'K':  PORTB |= ( 1 << PB4 ); break;
105
            case 'k':  PORTB &= ~( 1 << PB4 ); break;
106
            case 'L':  PORTB |= ( 1 << PB5 ); break;
107
            case 'l':  PORTB &= ~( 1 << PB5 ); break;  
108
          }
109
       }
110
       }
111
      uart_str_complete = 0;
112
    }
113
  }
114
}

Gruß

  Bernd
Gast #4744430
Lesenswert?

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.
Gast #4744464
Lesenswert?

Bei deiner Portzuweisung passt was nicht:
1
case 'A':  PORTC |= ( 1 << PB0 ); break;
2

3
//...etc

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.
Gast #4744686
Lesenswert?

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,
#4744772
Lesenswert?

Man könnte die Regel aufschreiben, nach der die Bits gesetzt werden:
1
uint8_t setbit( uint8_t val, uint8_t bit, uint8_t level )
2
{
3
  uint8_t mask = 1<<bit;
4
  val |= mask;
5
  if( level == 0 )
6
    val ^= mask;
7
  return val;
8
}
9

10
void setiobit( char c )
11
{
12
  uint8_t level = 0;
13
  switch( c ){
14
    case 'A' ... 'L': level = 1;
15
                      c += 'a' - 'A';
16
  }
17
  switch( c ){
18
    case 'a' ... 'f': PORTC = setbit( PORTC, c - 'a', level );
19
                      break;
20
    case 'g' ... 'l': PORTB = setbit( PORTB, c - 'g', level );
21
  }
22
}
Gast #4744826
Lesenswert?

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:
1
void set_output(char cmd)
2
{
3
    switch (cmd)
4
    {
5
    case 'A': PORTC |= ( 1 << PB0 ); break;
6
    case 'a': PORTC &= ~( 1 << PB0 ); break;
7
// .....
8
    case 'l': PORTB &= ~( 1 << PB5 ); break;
9
    default: break;
10
    }
11
}
12

13

14
void parse_command(const char* s)
15
{
16
    if(*s != '#') return;
17
    ++s;
18
    if(*s != 'a') return;
19
    ++s;
20
    while(*s)
21
    {
22
        set_output(*s);
23
        ++s;
24
    }
25
}
26

27
int main(void)
28
//.....
29
        if (uart_str_complete == 1)
30
        {
31
            parse_command(uart_string);
32
            uart_str_complete = 0;
33
        }
34
//.....
Gast #4744857
Lesenswert?

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.
Gast #4744884
Lesenswert?

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.
Gast #4745685
Lesenswert?

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
Gast #4745757
Lesenswert?

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.
Gast #4745806
Lesenswert?

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
Gast #4745818
Lesenswert?

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'
#4745885
Lesenswert?

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.
Gast #4745901
Lesenswert?

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:
1
if(c >= 'A' && c <= 'L') ...

auch.
#4746152
Lesenswert?

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.
Gast #4746233
Lesenswert?

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.
Gast #4746998
Lesenswert?

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
    case OffCmd: 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.
#4747053
Lesenswert?

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.
Gast #4747214
Lesenswert?

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,

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