Forum: Mikrocontroller und Digitale Elektronik Char Array über USART Empfangen und vergleichen


von Markus P. (sebastianwurst)


Lesenswert?

Hallo,
ich will eine Variablenfolge die z.B so aussieht 99|33|01|01 über die 
serielle Schnittstelle zum Mikrocontroller schicken und im Mikro dann 
das Empfangene auf Gleichheit vergleichen.

Im Moment mache ich das so:
1
char ReceiveSerial[40];
2
char Lr99110101[]  = "99|11|01|01";
3
4
// Zeichen Empfangen wenn was anliegt
5
if ((UCSR0A & (1<<RXC0)))
6
    {   
7
  
8
  uart_gets(ReceiveSerial, sizeof( ReceiveSerial));
9
  
10
  USART_Transmit(); // Kontrolle zum Terminalprogramm
11
12
13
     if (!strcmp(ReceiveSerial,Lr99110101)) 
14
   {  
15
  
16
  stopbit = false;
17
  }
18
19
}


Das ist das uart_gets()
1
/*String empfangen*/
2
void uart_gets( char* Buffer, uint8_t MaxLen )
3
{
4
  uint8_t NextChar;
5
  uint8_t StringLen = 0;
6
 
7
  NextChar = uart_getc();         // Warte auf und empfange das nächste Zeichen
8
 
9
                                  // Sammle solange Zeichen, bis:
10
                                  // * entweder das String Ende  Zeichen '\0'kam
11
                                  // * oder das aufnehmende Array voll ist
12
  while( NextChar != '\r'  && StringLen < MaxLen - 1 ) {
13
    *Buffer++ = NextChar;
14
    StringLen++;
15
    NextChar = uart_getc();
16
  } 
17
                                  // Noch ein '\0' anhängen um einen Standard
18
                                  // C-String daraus zu machen
19
  *Buffer = '\0';
20
}


Funktionieren tut das, doch merke ich das die uart_gets() unglaublich 
viel Zeit beansprucht, da andere Funktionen ins stocken geraten.
Wie könnte ich denn auch eleganter dieses char Array auf Gleichheit 
abfragen ohne eine Funktion zu benutzen die das mit eine Stringfunktion 
macht?

Vielen Dank für die Tips....

von Daniel V. (danvet)


Lesenswert?

1.) UART Receive Interrupt verwenden
ODER
2.) while-schleife abbrechen, wenn z.B. 10ms kein Zeichen gekommen ist. 
Wenn allerdings ein Zeichen kommt, dann alle einlesen, bis '\r'. Sonst 
wartest du bis auf den St.Nimmerleinstag auf deinen String

von Matthias H. (experimentator)


Lesenswert?

Hallo Markus,

Deine Empfangsfunktion bringt wahrscheinlich deshalb den Rest ins 
Stocken, weil es je nach Baudrate, Sendegeschwindigkeit/-frequenz der 
Gegenseite und Länge der Nachricht ein paar Millisekunden oder länger 
dauern kann, bis die Nachricht gelesen wurde.
Die Abhilfe geht in die Richtung, die Daniel geschrieben hat. Falls das 
nicht möglich ist, müßtest Du während des Empfangens hin und wieder eine 
Funktion aufrufen, die die anderen gewünschten Funktionen ausführt, so 
eine Art Miniatur-Scheduler.

Zum Stringvergleich:
In diesem Fall ist der in Ordnung, anders geht es auch nicht schneller. 
Wenn Du mit mehreren anderen Strings vergleichen willst, dann solltest 
Du Dir einen Zustandsautomaten schreiben, der mit jedem Zeichen 
weitergesetzt wird oder Dich mal mit String-Matching-Algorithmen 
beschäftigen 
(http://en.wikipedia.org/wiki/Category:Algorithms_on_strings, 
http://de.wikipedia.org/wiki/String-Matching-Algorithmus).

von Markus P. (sebastianwurst)


Lesenswert?

Danke schonmal.

Kann mir vielleicht einer ein Tipp geben und zwar will ich diese 
Variable:
1
char Codetest2[]  = {'29','34','41','52'};


per RS232 ohne eine String Funktion übertragen. Also einfach nur per 
Zahl. Wie ist da der beste Weg? Ich habe folgende Funktionen zur 
Verfügung (s. Unten), gerne nehme ich dann auch noch Beispiele entgegen.
DAs Problem welches ich einfach habe ist das ein char nur 1 Zeichen sein 
darf, also bei der erste Ziffer mit 29 würde mit der usart_write_char 
Funktion nur eine 9 übertragen.
Wie stelle ich das am besten an das nachher "29344152" auf meinem 
Terminal steht?

Vielen Dank.
1
void usart_write_char(char c)
2
{
3
#if CMD_TELNET
4
  if(usart_status.usart_disable)
5
  {
6
        if(rx_buffer_pointer_in == (rx_buffer_pointer_out - 1))
7
        {
8
            //Datenverlust
9
                    telnetd_send_data ();
10
                    while(telnetd_status.ack_wait)
11
                    {
12
                        eth_get_data();
13
                    }
14
        }
15
16
        *rx_buffer_pointer_in++ = c;
17
18
        if (rx_buffer_pointer_in == &usart_rx_buffer[BUFFER_SIZE-1])
19
        {
20
            rx_buffer_pointer_in = &usart_rx_buffer[0];
21
        }
22
    }
23
    return;
24
#else
25
    #if !USE_CAM
26
        if(!usart_status.usart_disable)
27
        {
28
            //Warten solange bis Zeichen gesendet wurde
29
            while(!(USR & (1<<UDRE)));
30
            //Ausgabe des Zeichens
31
            UDR = c;
32
        }
33
        return;
34
    #endif //USE_CAM
35
#endif
36
}
37
38
//------------------------------------------------------------------------------
39
void usart_write_P (const char *Buffer,...)
40
{
41
  va_list ap;
42
  va_start (ap, Buffer);  
43
  
44
  int format_flag;
45
  char str_buffer[10];
46
  char str_null_buffer[10];
47
  char move = 0;
48
  char Base = 0;
49
  int tmp = 0;
50
  char by;
51
  char *ptr;
52
    
53
  //Ausgabe der Zeichen
54
    for(;;)
55
  {
56
    by = pgm_read_byte(Buffer++);
57
    if(by==0) break; // end of format string
58
            
59
    if (by == '%')
60
    {
61
            by = pgm_read_byte(Buffer++);
62
      if (isdigit(by)>0)
63
        {
64
                                 
65
         str_null_buffer[0] = by;
66
        str_null_buffer[1] = '\0';
67
        move = atoi(str_null_buffer);
68
                by = pgm_read_byte(Buffer++);
69
        }
70
71
      switch (by)
72
        {
73
                case 's':
74
                    ptr = va_arg(ap,char *);
75
                    while(*ptr) { usart_write_char(*ptr++); }
76
                    break;
77
        case 'b':
78
          Base = 2;
79
          goto ConversionLoop;
80
        case 'c':
81
          //Int to char
82
          format_flag = va_arg(ap,int);
83
          usart_write_char (format_flag++);
84
          break;
85
        case 'i':
86
          Base = 10;
87
          goto ConversionLoop;
88
        case 'o':
89
          Base = 8;
90
          goto ConversionLoop;
91
        case 'x':
92
          Base = 16;
93
          //****************************
94
          ConversionLoop:
95
          //****************************
96
          itoa(va_arg(ap,int),str_buffer,Base);
97
          int b=0;
98
          while (str_buffer[b++] != 0){};
99
          b--;
100
          if (b<move)
101
            {
102
            move -=b;
103
            for (tmp = 0;tmp<move;tmp++)
104
              {
105
              str_null_buffer[tmp] = '0';
106
              }
107
            //tmp ++;
108
            str_null_buffer[tmp] = '\0';
109
            strcat(str_null_buffer,str_buffer);
110
            strcpy(str_buffer,str_null_buffer);
111
            }
112
          usart_write_str (str_buffer);
113
          move =0;
114
          break;
115
        }
116
      
117
      }  
118
    else
119
    {
120
      usart_write_char ( by );  
121
    }
122
  }
123
  va_end(ap);
124
}
125
126
//----------------------------------------------------------------------------
127
//Ausgabe eines Strings
128
void usart_write_str(char *str)
129
130
{
131
  while (*str)
132
  {
133
    usart_write_char(*str++);
134
  }  
135
}
136
137
void USART_Transmit( unsigned char data )
138
{
139
/* Wait for empty transmit buffer */
140
while ( !( UCSR0A & (1<<UDRE0)) )
141
;
142
/* Put data into buffer, sends the data */
143
UDR0 = data;
144
}
145
146
/* puts ist unabhaengig vom Controllertyp */
147
void uart_puts (char *s)
148
{
149
    while (*s)
150
    {   /* so lange *s != '\0' also ungleich dem "String-Endezeichen" */
151
        USART_Transmit(*s);
152
        s++;
153
    }
154
}

von Markus P. (sebastianwurst)


Lesenswert?

Matthias H. schrieb:
> Hallo Markus,
>
> Deine Empfangsfunktion bringt wahrscheinlich deshalb den Rest ins
> Stocken, weil es je nach Baudrate, Sendegeschwindigkeit/-frequenz der
> Gegenseite und Länge der Nachricht ein paar Millisekunden oder länger
> dauern kann, bis die Nachricht gelesen wurde.
> Die Abhilfe geht in die Richtung, die Daniel geschrieben hat. Falls das
> nicht möglich ist, müßtest Du während des Empfangens hin und wieder eine
> Funktion aufrufen, die die anderen gewünschten Funktionen ausführt, so
> eine Art Miniatur-Scheduler.
>


Ich merke es auch beim Empfangen, also da passiert auch irgendetwas 
zeitkritisches...

von Matthias H. (experimentator)


Lesenswert?

Hallo Markus,

> Ich merke es auch beim Empfangen, also da passiert auch irgendetwas
> zeitkritisches...

Das kannst Du ausrechnen. Nehmen wir an, Du benutzt eine Baudrate von 
9600 und die Einstellungen 8N1 (8 Bits, keine Parität, 1 Stoppbit). Dann 
werden pro Zeichen 9 Bits plus Startbit übertragen, also max. 960 
Zeichen pro Sekunde. Deine Nachricht ist anscheinend 12 Zeichen lang. 
Also dauert die Übertragung mindestens 1/80 Sekunde, also 12,5 ms. Falls 
Dein Sender langsamer sendet, mit Pausen zwischen den Zeichen, oder 
Deine Empfangsfunktion aus irgendeinem Grund noch viel Rechenleistung 
verbrät, dann dauert es entsprechend länger.

von Karl H. (kbuchegg)


Lesenswert?

Markus P. schrieb:
> Danke schonmal.
>
> Kann mir vielleicht einer ein Tipp geben und zwar will ich diese
> Variable:
>
>
1
> char Codetest2[]  = {'29','34','41','52'};
2
>
>
>
> per RS232 ohne eine String Funktion übertragen. Also einfach nur per
> Zahl. Wie ist da der beste Weg? Ich habe folgende Funktionen zur
> Verfügung (s. Unten), gerne nehme ich dann auch noch Beispiele entgegen.
> DAs Problem welches ich einfach habe ist das ein char nur 1 Zeichen sein
> darf, also bei der erste Ziffer mit 29 würde mit der usart_write_char
> Funktion nur eine 9 übertragen.
> Wie stelle ich das am besten an das nachher "29344152" auf meinem
> Terminal steht?

Indem du damit anfängst erst mal das Array richtig zu definieren und zu 
initialisieren.
1
uint8_t Codetest2[] = { 29, 34, 41, 52 };

Du willst Zahlen speichern. Also ist schon mal char kein geeigneter 
Datentyp dafür. Gewöhn dir an, char ausschliesslich für echte Zeichen 
(also 'A', 'B', 'C'...) bzw. in Arrayform für Strings ("Hallo") zu 
benutzen.


und dann schreibst du dir eine Funktion, die eine Zahl ausgeben kann.
1
void usart_write_uint8( uint8_t nr )
2
{
3
  char buffer[6];
4
5
  itoa( nr, buffer, 10 );
6
  usart_write_str( buffer );
7
}

mit dieser Hilfsfunktion schreibst du dir eine weitere Funktion, die ein 
Array von Zahlen ausgeben kann
1
void usart_write_uint8Array( uint8_t size, uint8_t numbers )
2
{
3
  uint8_t i;
4
5
  for( i = 0; i < size; i++ )
6
    usart_write_uint8( numbers[i] );
7
}

und die benutzt du dann um dein ursprüngliches Array auszugeben
1
#define ARRAY_SIZE(x)  (sizeof(x)/sizeof(*x))
2
3
int main()
4
{
5
  .....
6
7
8
  usart_write_uint8Array( ARRAY_SIZE( Codetest2 ), Codetest2 );
9
}

Das Geheimnis beim Programmieren besteht nicht darin, sich für jeden 
Pfurz immer wieder möglichst komplizierte neue Funktionen einfallen zu 
lassen. Das Geheimnis besteht darin, sich mit Basisfunktionen und darauf 
aufbauenden Hilfsfunktionen letztendlich bis zu der Ebene 
hochzuarbeiten, mit der man sein Problem lösen kann.

von Matthias H. (experimentator)


Lesenswert?

> char Codetest2[]  = {'29','34','41','52'};

Das funktioniert so natürlich nicht, falls es überhaupt durch den 
Compiler geht.

Du hast im wesentlichen folgende Möglichkeiten, falls Du nicht was 
völlig eigenes basteln möchtest:

- Du überträgst zeichenweise (+ Nullbyte o. ä. als Endemarkierung). Da 
braucht man dann nichts umzucodieren.
1
char Codetest2[]  = "29344152";

- Du überträgst zeichenweise binär. Dann mußt Du irgendein Zeichen außer 
der 0 als Endezeichen festlegen. Ist aber ineffizient.

- Du überträgst in BCD-Codierung 
(http://de.wikipedia.org/wiki/BCD-Code) binär. Dabei werden 2 Ziffern in 
ein Byte gepackt, was effizienter ist als zeichenweiser Versand.

BCD geht ungefähr so (mit ungültiger Ziffer als Endemarkierung):
1
char Codetest2[]  = {0x29, 0x34, 0x41, 0x52, 0xf0};

- Du überträgst binär. Dann mußt Du festlegen, welchen Datentyp Du 
verwenden möchtest, z. B. 4-Byte-Integer mit Vorzeichen, und mußt auf 
die Byte Order achten, also in welcher Reihenfolge der Prozessor die 
einzelnen Datenbytes, die die Variable bilden, im Speicher anordnet. Es 
ist Network Byte Order = "Big Endian" empfehlenswert. Siehe Funktionen 
htonl()/ntohl() usw. der Standardbibliothek, soweit vorhanden.

- Wenn Du kompliziertere Datenstrukturen übertragen möchtest, so wäre es 
eine Überlegung wert, sich mal Google Protocol Buffers oder andere 
Standardverfahren anzusehen (wobei Protocol Buffers wahrscheinlich zu 
groß für Deinen Mikrocontroller ist...).

von Markus P. (sebastianwurst)


Lesenswert?

Vielen Dank Karl-Heinz du hast mir sehr weiter geholfen!!!!
Mein Problem wird nur auch noch das sein was der Matthias mir schon sehr 
gut vorgerechnet hat. Ich betreibe auf den gleichen yc auch noch einen 
Phasenanschnitt und da muss ich alle paar ms einen Triac ansteuern, da 
kann ich mir keinen Zeitverlust  gönnen. Jetzt weiß ich garnicht wie ich 
das machen kann ohne den Phasenanschnitt zu beeinflussen ? Hättest du 
(ihr) da auch vielleicht eine Idee ?
Vielen super dank schonmal ...

von Karl H. (kbuchegg)


Lesenswert?

Regelmässige Dinge tun?
ganz klar: ein Fall für einen Timer.

von Matthias H. (experimentator)


Lesenswert?

Hallo Markus,

Markus P. schrieb:
> Mein Problem wird nur auch noch das sein was der Matthias mir schon sehr
> gut vorgerechnet hat. Ich betreibe auf den gleichen yc auch noch einen
> Phasenanschnitt und da muss ich alle paar ms einen Triac ansteuern, da
> kann ich mir keinen Zeitverlust  gönnen. Jetzt weiß ich garnicht wie ich
> das machen kann ohne den Phasenanschnitt zu beeinflussen ? Hättest du
> (ihr) da auch vielleicht eine Idee ?

Du mußt Dir überlegen, in welchem Zeitraster Du den Triac bedienen mußt, 
damit er funktioniert, wie Du willst und wieviel Zeit Dir zum Lesen der 
Nachricht übrig bleibt.

Falls die Funktion uart_getc() blockierend liest, mußt Du eine andere 
Möglichkeit finden.
Daniel V. hat ja dazu schon Anregungen geschrieben.

Interruptbetrieb heißt, daß der Schnittstellenbaustein einen 
Hardware-Interrupt auslöst, sobald ein Zeichen ankommt und dann eine 
Funktion aufgerufen wird, die das Zeichen z. B. in einen Buffer (am 
besten eine globale Variable) liest. Die Interruptfunktion sollte so 
wenig wie möglich machen, das heißt, das Prüfen, ob eine Nachricht 
vollständig gelesen wurde und die Verarbeitung sollten außerhalb des 
Interrupthandlers in der Hauptschleife gemacht werden. Es könnte noch 
weitere Nebenbedingungen geben, z. B. daß Funktionen der 
Standardbibliothek nicht aufgerufen werden dürfen oder so. Ob das von 
Deinem Mikrocontroller unterstützt wird und wie es geht, sollte in der 
Dokumentation stehen. Dazu sind noch ein paar Kleinigkeiten zu beachten, 
z. B. sollten die Interrupts abgeschaltet werden, während der Puffer in 
der Hauptschleife geleert wird, sonst gibt es undefiniertes Verhalten 
und Fehler.

Beim Lesen mit Timeout brauchst Du eine nichtblockierende Lesefunktion 
für die Schnittstelle und liest so lange, bis ein Timer zuende ist, z. 
B. 10 ms, oder die Nachricht komplett ist.

von Markus P. (sebastianwurst)


Lesenswert?

Hab es jetzt mit einem Timer gelöst.

von Nn N. (jaytharevo)


Lesenswert?

Markus P. schrieb:
>
1
 // Sammle solange Zeichen, bis:
2
>                                   // * entweder das String Ende  Zeichen '\0'kam
3
>                                   // * oder das aufnehmende Array voll ist
4
>   while( NextChar != '\r'  && StringLen < MaxLen - 1 )

Hey, dein Kommentar und dein Code passen nicht zusammen.
Das ist kein oder_ sondern ein _und.

von Daniel V. (danvet)


Lesenswert?

Julian Schild schrieb:
> Markus P. schrieb:
>>
1
 // Sammle solange Zeichen, bis:
2
>>                                   // * entweder das String Ende  Zeichen '\0'kam
3
>>                                   // * oder das aufnehmende Array voll ist
4
>>   while( NextChar != '\r'  && StringLen < MaxLen - 1 )
>
> Hey, dein Kommentar und dein Code passen nicht zusammen.
> Das ist kein oder_ sondern ein _und.

Aber es ist eine while-Abfrage. Er soll solange drin bleiben, wie BEIDE 
(also UND) Bedingungen erfüllt sind. Ist die eine ODER die andere 
Bedingung nicht mehr erfüllt, dann wird die Schleife beendet.

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.