Forum: Mikrocontroller und Digitale Elektronik Problem mit USART (Tiny2313) + Terminal


von _matze (Gast)


Angehängte Dateien:

Lesenswert?

Hallo!

Ich bin Neuling in der AVR-Programmierung (und in C auch, komme 
eigentlich aus der Clipper- und VB-Welt) und habe folgendes Problem:

Ich habe ein Progrämmchen gebastelt, dass Eingaben über ein Terminal 
(bei mir HTerm) entgegennimmt, diese auf Tastendruck in einem Array und 
schließlich im EEPROM speichert, und (ebenfalls per Taster) wieder auf 
dem Terminal ausgibt. Dabei kann man auch mehrere Eingaben 
hintereinander tätigen und mit Enter bestätigen. erst beim Drücken des 
'SAVE'-Tasters wird der komplette String ins EEPROM geschrieben.

Nun funktioniert das soweit ganz gut, jedoch gibt es manchmal das 
Phänomen, dass nicht alle Zeichen gespeichert werden. Z.B. sende ich 10 
Zeichen "1234567890", was mehrmals hintereinander klappt. Dann jedoch 
kommen nur 2 oder 3 der 10 Zeichen an. Alles im selben 
Programmdurchlauf. Danach kann wieder alles funktionieren. Sehr seltsam.

Die Taktung müsste stimmen (F_CPU=4000000, BAUD_RATE=9600), ich bekomme 
mit dem Funktionsaufruf long_delay_ms(1000) auch eine Verzögerung von 
genau 1 Sekunde. Das ist doch das Indiz schlechthin, dass in der 
Hinsicht alles stimmt, oder?

Jedenfalls arbeite ich schon seit Tagen an dem Programm und bekomme es 
nicht 100%-ig hin. Ich habe mehrere Test-Ausgaben eingebaut. Bbei Druck 
auf SW0-Taster wird 'SAVED' gesendet, bei SW2 wird der Wert von 
iUSART_inp ausgegeben (Anzahl der eingelesenen Zeichen). Dieser Wert ist 
dann beim Auftreten des Fehlers entsprechend klein (z.B. 2 statt 10 
Zeichen).

Ich verwende den Tiny2313 mit dem STK500 und das AVR-Studio 4.13 (Build 
528).

Ich poste hier einfach mal den ganzen Code. Angehängt habe ich einen 
ScreenShot von HTerm, wo sehr gut zu sehen ist, was ich meine. Die 
Reihenfolge bei HTerm war folgendermaßen: Eingabe, SW2 (iUSART_inp 
ausgeben), SW0 (im EEPROM speichern), SW1 (EEPROM auslesen und ans 
Terminal schicken).
1
#ifndef F_CPU
2
  #define F_CPU 4000000
3
#endif
4
#ifndef UART_BAUD_RATE
5
  #define UART_BAUD_RATE 9600
6
#endif
7
//
8
#include <stdlib.h>
9
#include <avr/io.h>
10
#include <avr/eeprom.h>
11
#include <stdio.h>
12
#include <util/delay.h>
13
/*
14
#include <avr/interrupt.h>
15
#include <stdint.h>
16
#include <stdbool.h>
17
*/
18
//
19
//PROTOTYPEN
20
         void myWait(unsigned int iX);
21
 unsigned int myPot2(unsigned int iExp);
22
         void USART_init(unsigned int baud);
23
         void USART_transmit(unsigned char cData);
24
         void USART_transmit_str(char *cStr);
25
         void long_delay_ms( unsigned long ms );
26
27
28
/***************/
29
int main(void) {
30
/***************/
31
  unsigned short int i = 0;
32
                char *cUSART_inp = NULL;
33
  unsigned short int iUSART_inp = 0;
34
      unsigned short iTest=1;
35
                char cZahl[5] = {0};
36
       unsigned char cBoolSaved = 0;
37
  //
38
  DDRB = 0xff;  //Port B Pins als Ausgänge definieren
39
  PORTB = 0xff;
40
  //DDRD = 0x00;  //Port D Pins als Eingänge definieren. Wohl nicht notwendig, da die Bits standardmäßig sowieso 0 sind
41
  //
42
  USART_init(UART_BAUD_RATE);
43
  //
44
  for(;;) {
45
    //
46
/*
47
    if( ++iTest % 500 == 0 ) {
48
      USART_transmit('.');
49
      iTest = 0;
50
    }
51
*/
52
    //
53
    if( UCSRA & (1<<RXC) ) {  //Zeichen werden empfangen und im char-array gespeichert
54
      cUSART_inp = (char *)realloc( cUSART_inp , (++iUSART_inp) * sizeof(char) );
55
      cUSART_inp[iUSART_inp-1] = UDR;
56
      cBoolSaved = 0;
57
    }
58
    //
59
    if( !(PIND & (1<<PIND2)) && iUSART_inp>0) {    //SW0 gedrückt
60
      for(i=0;i<iUSART_inp;i++) {
61
        //eeprom_write_byte((unsigned char*)i,cUSART_inp[i]);  //char-array (cUSART_inp) in EEPROM speichern
62
        eeprom_write_byte((uint8_t*)i,cUSART_inp[i]);  //char-array (cUSART_inp) in EEPROM speichern
63
      }
64
      //
65
      free(cUSART_inp);   //char-array löschen, aber nicht den Index iUSART_inp, da dieser
66
      cUSART_inp = NULL;  //beim Zurücklesen gebraucht wird.
67
      //
68
      USART_transmit_str(" SAVED ");
69
      cBoolSaved = 1;
70
      long_delay_ms(500);  //wartet 1 Sek, nicht 200 MSek... warum auch immer...
71
    }
72
    //
73
    if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved ) {    //SW1 gedrückt
74
      cUSART_inp = (char *)realloc( cUSART_inp , iUSART_inp * sizeof(char) );
75
      //
76
      for(i=0;i<iUSART_inp;i++) {
77
        cUSART_inp[i] = eeprom_read_byte((uint8_t*)i);  //Inhalt des EEPROMs rurück in das char-array schreiben
78
      }
79
      cUSART_inp[i] = '\0';
80
      //
81
      USART_transmit_str(cUSART_inp);
82
      //
83
      free(cUSART_inp);   //string wurde auf Terminal ausgegeben, daher char-array
84
      cUSART_inp = NULL;  //und Index zurücksetzen
85
      iUSART_inp = 0;
86
      cBoolSaved = 0;
87
      long_delay_ms(500);
88
    }
89
    if( !(PIND & (1<<PIND4)) ) {    //SW2 gedrückt: iUSART_inp (Anzahl eingegebener Zeichen) ausgeben
90
      USART_transmit_str(" iUSART_inp: ");
91
      USART_transmit_str(itoa(iUSART_inp,cZahl,10));
92
      USART_transmit(32);
93
      long_delay_ms(500);
94
    }
95
  }
96
  //
97
  return 0;
98
}
99
100
/***************/
101
unsigned int myPot2(unsigned int iExp) {  //potenziert die Basis 2 mit dem Exponenten iExp
102
/***************/
103
  unsigned short int i,iErg;
104
  iErg = 1;
105
  for(i=0;i<iExp;i++) {
106
    iErg = iErg * 2;
107
  }
108
  return iErg;
109
110
}
111
112
/***************/
113
void long_delay_ms( volatile unsigned long ms ) {
114
/***************/
115
  while( ms-- )
116
    _delay_ms( 1 );
117
}
118
119
/***************/
120
void USART_init(unsigned int baud) {    //nochmal genau ansehen und schöner formulieren!
121
/***************/
122
/*
123
UBRRH = (unsigned char)(baud>>8);    //Baudrate setzen
124
UBRRL = (unsigned char)baud;        //
125
//
126
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
127
//
128
UCSRC = (1<<USBS)|(3<<UCSZ0);  //8 Datenbits, 2 Stopbits (?)
129
*/
130
UCSRA=0x00;
131
//UCSRB=0x08;
132
UCSRC=0x86;
133
UBRRH=0x00;
134
UBRRL=0x17;
135
//
136
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
137
}
138
139
/***************/
140
void USART_transmit(unsigned char cData) {
141
/***************/
142
  while( !(UCSRA & (1<<UDRE)) ) {
143
    ;//warten, bis der transmit buffer leer ist, so dass wieder übertragen werden kann
144
  }
145
  UDR = cData;
146
}
147
148
/***************/
149
void USART_transmit_str(char *cStr) {
150
/***************/
151
  while(*cStr) {
152
    USART_transmit(*cStr++);
153
  }
154
}

Ich hoffe, jemand findet hier den Fehler oder kann mir sonst irgendeinen 
Tipp geben. Ich verliere langsam den Mut... ach, wär ich doch beim 
Clipper geblieben...

von _matze (Gast)


Lesenswert?

Ergänzung: Die Funktion myPot2() gehört da nicht mehr rein, einfach 
nicht beachten. Alles andere ist relevant.

von Karl H. (kbuchegg)


Lesenswert?

Was mir nicht gefällt sind die ganzen Wartereien im Programm.
Mit dem realloc bin ich auch nicht wirklich glücklich.

zum Problem:
Ich würde mal versuchen, direkt im Empfangsabschnitt das
empfangene Zeichen zu echoen um zu sehen ob da im
Fehlerfall überhaupt was ankommt, bzw. ob da irgnedwelche
Zeitverzögerungen erkennbar sind.

von _matze (Gast)


Angehängte Dateien:

Lesenswert?

Hi Karl Heinz,

erstmal HERZLICHEN DANK dafür, dass du dir diese Mühe machst!

Zum Thema Delays und realloc():

Ist ja nur ein Testprogramm, dass ich ständig erweitere. realloc() habe 
ich eingebaut, nachdem das Lesen und Schreiben im EEPROM auf dem 
direkten Wege nicht gut funktioniert hat (Zeichen haben gefehlt etc.). 
Daher habe ich diese Zwischenlösung gebaut, die, denke ich, auch nicht 
das Problem ist. Natürlich wäre es im Hinblick auf Codegröße und 
Sicherheit sinnvoller, ein statisches Array zu nehmen, da sowieso nur 
128 Bytes (tiny2313) im EEPROM zur Verfügung stehen. Sobald ich dafür 
Zeit habe...

Die Delays sind meiner Meinung nach notwendig, da sonst die Schleife 
mehrmals durchlaufen wird, während man noch den Taster drückt. 
Vielleicht ist das keine elegante Lösung, aber ich kenne keine bessere. 
Für Vorschläge bin ich offen.

Echo habe ich eingebaut:
1
if( UCSRA & (1<<RXC) ) {  //Zeichen werden empfangen und im char-array gespeichert
2
      USART_transmit(UDR);
3
      cUSART_inp = (char *)realloc( cUSART_inp , (++iUSART_inp) * sizeof(char) );
4
      cUSART_inp[iUSART_inp-1] = UDR;
5
      cBoolSaved = 0;
6
}

es kommen alle Zeichen an und werden unverfälscht wieder ans Terminal 
gesendet. Die eigentliche Funktionalität des Programms wird durch diesen 
Test aber zerstört. Die Strings werden nur noch zerstückelt gespeichert 
(s. Anhang).
Liegt das daran, dass das Datenregister UDR nur dazu gedacht ist, einmal 
verwendet zu werden? Würde beispielsweise Folgendes gehen?
1
sTmp  = UDR;
2
sTmp2 = UDR;
3
sTmp3 = UDR;

Oder muss man daraus dieses machen, damit es funktioniert?
1
sTmp  = UDR;
2
sTmp2 = sTmp;
3
sTmp3 = sTmp;

So, ich sehe also nun, dass die Zeichen korrekt gesendet und wieder 
empfangen werden können. Da die bisherige Funktionalität durch den Test 
ausgehebelt wird (oder mache ich da etwas falsch?!), lässt sich leider 
nicht sagen, ob im Fehlerfall auch alle Zeichen ankommen oder nicht. Wo, 
denkst du, könnte die Fehlerquelle liegen?

Schließlich muss man ja bedenken, dass der Fehler, wie gesagt, ja nur 
manchmal auftritt, mit demselben String, der 5 Sekunden vorher gepasst 
hat. Ist das etwas, was ich per Code in den Griff bekomme?

Gruß Matze

von _matze (Gast)


Angehängte Dateien:

Lesenswert?

Hier noch ein ScreenShot von HTerm. Man sieht, dass die ersten drei 
Versuche problemlos klappen. Der String "GAAANZ LAAANGER VERSUCH" hat 
das Ganze zum Absturz gebracht.

Der 2313 reagiert nicht mehr. Die LEDs 2,3 und 4 (an PORTB) leuchten 
plötzlich (hab ich bislang nicht gesehen)! Irgendeine Idee, was das zu 
bedeuten hat?

Gruß Matze

von _matze (Gast)


Lesenswert?

Der Fehler ist reproduzierbar, allerdings haben die LEDs diesmal nicht 
mehr geleuchtet.

von _matze (Gast)


Angehängte Dateien:

Lesenswert?

So, hier ein ScreenShot, mit dem du vielleicht was anfangen kannst. Es 
wurden zwei Strings gesendet:

"langerTextDerOhneLeerzeichenIst"

und

"zweiterLangerTextOhneBlanks"

Direkt nach Eingabe (vor irgendeiner Verarbeitung) wurden jede Menge 
binäre Nullen an den Rechner gesendet und das Programm blieb hängen.

Vielleicht kann ja jemand was mit diesem Phänomen anfangen...

Gruß Matze

von _matze (Gast)


Lesenswert?

Sorry, ich wollte sagen, direkt nach Eingabe des 2. Strings wurde die 
Nullen gesendet und das Programm blieb hängen. Der erste String wurde 
noch (scheinbar) korrekt verarbeitet.

von PM (Gast)


Lesenswert?

Sieht für mich nach einem Pointer/Speicherfehler aus...
Mach das ganze Programm doch erst mal "sauber". Also mit festem Array 
zum abspeichern.
Weiteres Problem könnten die delays sein, wenn während des delays Daten 
eingehen können diese nicht verarbeitet werden. Das könntest Du in einem 
der USART Register abfragen (komm ich grad nicht drauf), oder noch 
besser, Empfang mit einer ISR realisieren.

Gruß
Philipp

von _matze (Gast)


Lesenswert?

Bin dabei, realloc & Konsorten durch ein statisches Array zu ersetzen. 
Morgen gibt das (hoffentlich positive) Ergebnis.

Gruß Matze

von Karl H. (kbuchegg)


Lesenswert?

Das Schreiben ins EEPROM könnte da ebenfalls noch ein
Problem aufwerfen. Der Vorgang ist relativ langsam.
Ich kann jetzt aber nicht sagen, wie das in Relation
zur Baudrate steht.

Ev. wirst du über ein Handshake auf der UART nachdenken
müssen.

von _matze (Gast)


Lesenswert?

So, es hat sich einiges getan!

Ich habe realloc() rausgeschmissen und durch ein statisches Array 
ersetzt (Code um die Hälfte geschrumpft!).

Desweiteren gibt es nun eine Funktion, die (ausgelöst durch einen 
Taster)
den restlichen verfügbaren Speicher (SRAM) ans Terminal sendet. Daran 
sieht man auch die Problematik mit realloc(). Denn ich habe für das 
Array sowieso nur 30 oder 40 Bytes zur Verfügung, der Rest der 128 Bytes 
(tiny2313) ist belegt. Deshalb haben kleine Strings immer funktioniert, 
große aber nicht.
Das Programm hätte also funktioniert, wenn ich mehr Speicher gehabt 
hätte...

Die größte Neuerung sind zwei Funktionen, die ich für das Speichern im 
EEPROM geschrieben habe:

eeprom_get_next_byte()
eeprom_get_first_written_byte()

So ist es mir möglich, mich quasi im Kreis zu bewegen und das EEPROM 
gleichmäßig zu beschreiben. Ich bin sicher, da gibt es haufenweise 
(wahrscheinlich bessere) Lösungen im Internet, aber diese kleine 
Herausforderung wollte ich mir nicht nehmen lassen. Man kann schließlich 
auch ohne Copy&Paste programmieren (ja, das geht wirklich)!

Einziger Nachteil meiner Lösung: Beim ersten Durchlauf (wirklich nur 
nach Programmstart) wird das Byte 0 im EEPROM ausgelassen. Bei jedem 
weiteren Durchlauf wird es aber beschrieben. Da mir keine bessere Lösung 
einfiel, als mit einer globalen Variable zu arbeiten (kostet ja wieder 
Speicher), habe ich es erstmal so gelassen. Dann lebt das erste Byte 
eben minimal länger als die anderen...

So, nochmal vielen Dank an alle, die mir geholfen haben!

Da ich denke, dass es viele AVR-Anfänger wie mich gibt, poste ich hier 
nochmal den kompletten, aktuellen Code. Ich habe ihn hinreichend 
kommentiert und bin sicher, dass dem einen oder anderen damit vielleicht 
ein Licht aufgeht.

Kritik/Anregungen/Vorschläge sind ausdrücklich erwünscht!

Gruß Matze

1
/*  T E S T P R O G R A M M  (USART+EEPROM)
2
 *  ---------------------------------------
3
 *  
4
 *  Dieses Testprogramm (geschrieben für ATTiny2313) empfängt und sendet Zeichen über die
5
 *  RS232-Schnittstelle (UART/USART) und ermöglicht die folgenden Funktionen:
6
 *
7
 *  SW0 (Taster 0, bei mir an PIND2): empfangene Zeichen im EEPROM speichern
8
 *
9
 *  SW1 (Taster 1, bei mir an PIND3): gespeicherte Zeichen wieder auslesen und ans Terminal senden
10
 *
11
 *  SW2 (Taster 2, bei mir an PIND4): Anzahl aktuell eingelesener Zeichen ans Terminal senden
12
 *
13
 *  SW3 (Taster 3, bei mir an PIND5): freien Speicher (SRAM) ans Terminal senden
14
 *
15
 *  Zum Testen dieses Programm benötigt man ein Terminal-Programm (ich nutze HTerm), mit dem man
16
 *  Daten zum AVR schicken und von ihm empfangen kann.
17
 *
18
 *  Wenn das Terminal-Programm verbunden ist, sieht man, dass kontinuierlich ein Punkt ('.') gesendet
19
 *  wird. Dies ist als Lebenszeichen des AVR zu verstehen. Bricht der Datenstrom plötzlich ab, ist
20
 *  das Programm wahrscheinlich hängengeblieben (z.B. wegen Speicherüberlauf o.ä.).
21
 *
22
 *  Da das SRAM in seiner Größe sehr beschränkt ist (beim ATTiny2313 nur 128 Bytes), habe ich eine
23
 *  maximale Größe für den empfangenen String festgelegt. Sie kann über IUSART_INP_MAX geändert werden.
24
 *
25
 *  Das Beschreiben des EEPROM ist in der Hinsicht kritisch, als dass der Speicher nur
26
 *  ca. 100000 mal beschreibbar ist. Deshalb ist es nicht sinnvoll, jedesmal wieder ab
27
 *  Byte 0 zu schreiben. Deshalb habe ich die Funktionen eeprom_get_last_byte() und
28
 *  eeprom_get_first_written_byte() geschrieben. Mithilfe dieser Funktionen kann man so
29
 *  navigieren, dass das EEPROM kontinuierlich beschrieben wird, so dass alle Bytes
30
 *  gleichmäßig beansprucht werden und die Lebensdauer deutlich erhöht wird. Erreicht man
31
 *  das Ende des EEPROM (beim ATTiny2313 128 Bytes, änderbar über EEPROM_MAX_BYTE), so
32
 *  beginnt man wieder bei Byte 0, wobei ein String auch überlappen kann (z.B. ein
33
 *  5-Zeichen-String in den Bytes 126, 127, 0, 1 und 2).
34
 *
35
 *  Bei meinem Testboard (STK500) habe ich die Taster nicht nach Standardkonfiguration angeschlossen,
36
 *  wie oben an der Funktionsübersicht zu sehen ist (SW0 auf PIND2 statt PIND0 usw.). Wer ganz normal
37
 *  SW0 an PIND0 hat, muss lediglich diese wenigen Stellen im Code abändern.
38
 *
39
 *  Beispiel:
40
 *  ---------
41
 *    
42
 *  if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
43
 *                       -
44
 *  ändern in
45
 *
46
 *  if( !(PIND & (1<<PIND1)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
47
 *                       -
48
 *
49
 *  Tipp: Wer HTerm benutzt, sollte in der oberen Ansicht (vom AVR gesendete Zeichen) nur die ASCII-
50
 *        Ausgabe zulassen (also Häkchen weg bei Hex, Dec und Bin). So behält man den Überblick.
51
 *
52
 *
53
 *  Wer (wie ich gerade) in die AVR-Programmierung einsteigt und an der Kommunikation zwischen AVR
54
 *  und PC sowie am Beschreiben des EEPROM interessiert ist, wird vielleicht (hoffentlich) mithilfe
55
 *  dieses Programms weiterkommen.
56
 *
57
 *
58
 *  Matthias Marschhausen (2007-07-20)
59
 *
60
 */
61
#ifndef F_CPU
62
  #define F_CPU 4000000
63
#endif
64
#ifndef UART_BAUD_RATE
65
  #define UART_BAUD_RATE 9600
66
#endif
67
//
68
#define IUSART_INP_MAX 20   //maximale Anzahl an Zeichen, die vom AVR empfangen werden darf
69
#define EEPROM_MAX_BYTE 127 //letztes Byte des EEPROM (beim ATTiny2313 128 Byte großer Speicher)
70
//
71
extern unsigned char __heap_start;
72
//
73
#include <avr/io.h>
74
#include <stdlib.h>
75
#include <avr/eeprom.h>
76
#include <util/delay.h>
77
/*
78
#include <avr/interrupt.h>
79
#include <stdio.h>
80
#include <stdint.h>
81
*/
82
//
83
//PROTOTYPEN
84
//----------
85
          void USART_init(unsigned int baud);
86
          void USART_transmit(unsigned char cData);
87
          void USART_transmit_str(char *cStr);
88
          void long_delay_ms( unsigned long ms );
89
          void clearArray(char cArray[],int iMax);
90
          void __attribute__ ((naked, section (".init8"))) __init8_mem (void);
91
       uint8_t eeprom_get_next_byte(uint8_t iLastByte);
92
       uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes);
93
94
//GLOBALE VARIABLEN
95
//-----------------
96
uint8_t i = 0;
97
uint8_t iEEPROM_lastByte = 0;
98
99
100
/***************/
101
int main(void) {
102
/***************/
103
                char cUSART_inp[IUSART_INP_MAX];  //Array für das Zwischenspeichern von über USART empfangenen Zeichen
104
      unsigned short iUSART_inp = 0;  //Index für das Array cUSART_inp[], entspricht der Anzahl der empfangenen Zeichen
105
      unsigned short iDot = 0;  //zum ständigen Senden eines Punktes (als Lebenszeichen)
106
                char cTmpZahl[5] = {0};  //Temp-Variable für Konvertierungen mit itoa()
107
       unsigned char cBoolSaved = 0;  //wurden die empfangenen Zeichen im EEPROM gespeichert?
108
  //
109
  DDRB = 0xff;  //Port B Pins als Ausgänge definieren
110
  PORTB = 0xff;
111
  //DDRD = 0x00;  //Port D Pins als Eingänge definieren. Wohl nicht notwendig, da die Bits standardmäßig sowieso 0 sind
112
  //
113
  USART_init(UART_BAUD_RATE);   //USART initialisieren
114
  //
115
  for(;;) {   //Endlosschleife (bei AVR-Programmierung gewollt/nötig)
116
    if( ++iDot % 1000 == 0 ) { //alle 1000 Durchläufe einen Punkt ans Terminal senden
117
      USART_transmit('.');  //Punkt senden
118
      iDot = 0;  //iTest natürlich zurücksetzen, um einen Überlauf zu verhindern
119
    }
120
    if( iUSART_inp > IUSART_INP_MAX ) {  //string wäre zu lang, also alles zurücksetzen
121
      USART_transmit_str(" WARNUNG: >");                     //Warnung/Information ans
122
      USART_transmit_str(itoa(IUSART_INP_MAX,cTmpZahl,10));  //Terminal senden
123
      USART_transmit_str(" Zeichen - RESET ");            //
124
      clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
125
      iUSART_inp = 0;   //Array-Index zurücksetzen
126
      long_delay_ms(500);   //kurze Pause...
127
    }
128
    //
129
    if( UCSRA & (1<<RXC) ) {  //Zeichen werden empfangen und im char-array gespeichert
130
      //USART_transmit(UDR);  //sendet jedes empfangene Zeichen direkt wieder and Terminal
131
                              //(kann zum grundsätzlichen Testen der Kommunikation aktiviert werden)
132
      cUSART_inp[++iUSART_inp-1] = UDR; //empfangenes Zeichen im Array speichern
133
      cBoolSaved = 0;   //"gespeichert"-Status auf 0 (false) setzen
134
    }
135
    //
136
    if( !(PIND & (1<<PIND2)) && iUSART_inp>0) {    //SW0 gedrückt
137
      for(i=0;i<iUSART_inp;i++) {
138
        //char-array (cUSART_inp) in EEPROM speichern
139
        eeprom_write_byte((uint8_t*)(unsigned int)eeprom_get_next_byte(iEEPROM_lastByte),cUSART_inp[i]);
140
      }
141
      //
142
      clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
143
      //
144
      USART_transmit_str(" SAVED ");  //damit man was sieht...
145
      cBoolSaved = 1;   //"gespeichert"-Status auf 1 (true) setzen
146
      long_delay_ms(500);   //kurze Pause...
147
    }
148
    //
149
    if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
150
      //
151
      for(i=0;i<iUSART_inp;i++) {
152
        //Inhalt des EEPROM rurück in das char-array schreiben
153
        cUSART_inp[i] = eeprom_read_byte((uint8_t*)(unsigned int)eeprom_get_first_written_byte(iEEPROM_lastByte,iUSART_inp-1-i));
154
      }
155
      //
156
      USART_transmit_str(cUSART_inp);  //Array ans Terminal senden
157
      //
158
      iUSART_inp = 0; //Array-Index zurücksetzen
159
      cBoolSaved = 0; //"gespeichert"-Status auf 0 (false) setzen
160
      long_delay_ms(500);   //kurze Pause...
161
    }
162
    if( !(PIND & (1<<PIND4)) ) {  //SW2 gedrückt: iUSART_inp (Anzahl eingegebener Zeichen) ausgeben
163
      USART_transmit_str(" Anzahl Zeichen: ");                //ans Terminal
164
      USART_transmit_str(itoa(iUSART_inp,cTmpZahl,10));   //senden
165
      USART_transmit(32);                                 //
166
      long_delay_ms(500);   //kurze Pause...
167
    }
168
    if( !(PIND & (1<<PIND5)) ) {          //SW3 gedrückt: freien Speicher (SRAM) ermitteln
169
      USART_transmit_str(" FREE MEM: ");                //und ans Terminal senden
170
      USART_transmit_str(itoa(SP - (uint16_t) &__heap_start,cTmpZahl,10));
171
      USART_transmit_str(" Bytes ");
172
      long_delay_ms(500);   //kurze Pause...
173
    }
174
  }
175
  //
176
  return(0);  //wird nie erreicht, da Endlosschleife...
177
}
178
179
/***************/
180
void long_delay_ms( volatile unsigned long ms ) {
181
/***************/
182
  while( ms-- )
183
    _delay_ms( 1 );
184
}
185
186
/***************/
187
void USART_init(unsigned int baud) {    //nochmal genau ansehen und schöner formulieren!
188
/***************/
189
/*
190
UBRRH = (unsigned char)(baud>>8);    //Baudrate setzen
191
UBRRL = (unsigned char)baud;        //
192
//
193
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
194
//
195
UCSRC = (1<<USBS)|(3<<UCSZ0);  //8 Datenbits, 2 Stopbits (?)
196
*/
197
UCSRA=0x00;
198
//UCSRB=0x08;
199
UCSRC=0x86;
200
UBRRH=0x00;
201
UBRRL=0x17;
202
//
203
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
204
}
205
206
/***************/
207
void USART_transmit(unsigned char cData) {  //sendet ein Zeichen ans Terminal
208
/***************/
209
  while( !(UCSRA & (1<<UDRE)) ) {
210
    ;//warten, bis der transmit buffer leer ist, so dass wieder übertragen werden kann
211
  }
212
  UDR = cData;
213
}
214
215
/***************/
216
void USART_transmit_str(char *cStr) {   //sendet einen String (char-Array) ans Terminal
217
/***************/
218
  while(*cStr) {
219
    USART_transmit(*cStr++);
220
  }
221
}
222
223
/***************/
224
void clearArray(char cArray[],int iMax) { //ersetzt alle Werte in einem Array durch binäre Nullen ('\0')
225
/***************/
226
  for(i=0;i<iMax;i++) {
227
    cArray[i]='\0';
228
  }
229
}
230
231
/***************/
232
uint8_t eeprom_get_next_byte(uint8_t iLastByte) {
233
/***************/
234
  uint8_t iNextByte = iLastByte + 1;
235
  //
236
  if(iNextByte > EEPROM_MAX_BYTE) {
237
    iNextByte = 0;
238
  }
239
  //
240
  iEEPROM_lastByte = iNextByte;
241
  return(iNextByte);
242
}
243
244
/***************/
245
uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes) {
246
/***************/
247
  uint8_t iFirstByte;
248
  if( iLastByte - iWrittenBytes < 0 ) {
249
    iFirstByte = EEPROM_MAX_BYTE + iLastByte - iWrittenBytes;
250
  } else {
251
    iFirstByte = iLastByte - iWrittenBytes;
252
  }
253
  return(iFirstByte);
254
}

  

von _matze (Gast)


Lesenswert?

Oh, wir haben ja noch Juni... ein Versehen...

von _matze (Gast)


Lesenswert?

So, hier nochmal der aktuelle Code. Die Delay-Funktionen habe ich 
entfernt und durch eine eigene Funktion zum Tastenentprellen ersetzt. 
Außerdem gibt es noch weitere kleine Verbesserungen.

Perfekt ist es noch nicht, aber ich muss mich jetzt meiner produktiven 
Aufgabe widmen und kann leider nicht weiter am Testprogramm werkeln.

Die funktion long_delay_ms() wird nicht mehr benutzt. Ich habe sie aber 
drinnengelassen, da ich finde, dass eine solche Funktion für Anfänger 
zum Testen doch sehr nützlich ist. Bitte nicht schlagen! Ich weiß ja, 
dass delays keinen guten Ruf haben, aber wir reden hier von den ersten 
Gehversuchen in der AVR-Welt...
1
/*  T E S T P R O G R A M M  (USART+EEPROM)
2
 *  ---------------------------------------
3
 *  
4
 *  Dieses Testprogramm (geschrieben für ATTiny2313) empfängt und sendet Zeichen über die
5
 *  RS232-Schnittstelle (UART/USART) und ermöglicht die folgenden Funktionen:
6
 *
7
 *  SW0 (Taster 0, bei mir an PIND2): empfangene Zeichen im EEPROM speichern
8
 *
9
 *  SW1 (Taster 1, bei mir an PIND3): gespeicherte Zeichen wieder auslesen und ans Terminal senden
10
 *
11
 *  SW2 (Taster 2, bei mir an PIND4): Anzahl aktuell eingelesener Zeichen ans Terminal senden
12
 *
13
 *  SW3 (Taster 3, bei mir an PIND5): freien Speicher (SRAM) ans Terminal senden
14
 *
15
 *  Zum Testen dieses Programm benötigt man ein Terminal-Programm (ich nutze HTerm), mit dem man
16
 *  Daten zum AVR schicken und von ihm empfangen kann.
17
 *
18
 *  Wenn das Terminal-Programm verbunden ist, sieht man, dass kontinuierlich ein Punkt ('.') gesendet
19
 *  wird. Dies ist als Lebenszeichen des AVR zu verstehen. Bricht der Datenstrom plötzlich ab, ist
20
 *  das Programm wahrscheinlich hängengeblieben (z.B. wegen Speicherüberlauf o.ä.).
21
 *
22
 *  Da das SRAM in seiner Größe sehr beschränkt ist (beim ATTiny2313 nur 128 Bytes), habe ich eine
23
 *  maximale Größe für den empfangenen String festgelegt. Sie kann über IUSART_INP_MAX geändert werden.
24
 *
25
 *  Das Beschreiben des EEPROM ist in der Hinsicht kritisch, als dass der Speicher nur
26
 *  ca. 100000 mal beschreibbar ist. Deshalb ist es nicht sinnvoll, jedesmal wieder ab
27
 *  Byte 0 zu schreiben. Deshalb habe ich die Funktionen eeprom_get_last_byte() und
28
 *  eeprom_get_first_written_byte() geschrieben. Mithilfe dieser Funktionen kann man so
29
 *  navigieren, dass das EEPROM kontinuierlich beschrieben wird, so dass alle Bytes
30
 *  gleichmäßig beansprucht werden und die Lebensdauer deutlich erhöht wird. Erreicht man
31
 *  das Ende des EEPROM (beim ATTiny2313 128 Bytes, änderbar über EEPROM_MAX_BYTE), so
32
 *  beginnt man wieder bei Byte 0, wobei ein String auch überlappen kann (z.B. ein
33
 *  5-Zeichen-String in den Bytes 126, 127, 0, 1 und 2).
34
 *
35
 *  Bei meinem Testboard (STK500) habe ich die Taster nicht nach Standardkonfiguration angeschlossen,
36
 *  wie oben an der Funktionsübersicht zu sehen ist (SW0 auf PIND2 statt PIND0 usw.). Wer ganz normal
37
 *  SW0 an PIND0 hat, muss lediglich diese wenigen Stellen im Code abändern.
38
 *
39
 *  Beispiel:
40
 *  ---------
41
 *    
42
 *  if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
43
 *                       -
44
 *  ändern in
45
 *
46
 *  if( !(PIND & (1<<PIND1)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
47
 *                       -
48
 *
49
 *  Tipp: Wer HTerm benutzt, sollte in der oberen Ansicht (vom AVR gesendete Zeichen) nur die ASCII-
50
 *        Ausgabe zulassen (also Häkchen weg bei Hex, Dec und Bin). So behält man den Überblick.
51
 *
52
 *
53
 *  Wer (wie ich gerade) in die AVR-Programmierung einsteigt und an der Kommunikation zwischen AVR
54
 *  und PC sowie am Beschreiben des EEPROM interessiert ist, wird vielleicht (hoffentlich) mithilfe
55
 *  dieses Programms weiterkommen.
56
 *
57
 *
58
 *  Matthias Marschhausen (2007-07-20)
59
 *
60
 */
61
#ifndef F_CPU
62
  #define F_CPU 4000000
63
#endif
64
#ifndef UART_BAUD_RATE
65
  #define UART_BAUD_RATE 9600
66
#endif
67
//
68
#define IUSART_INP_MAX 20   //maximale Anzahl an Zeichen, die vom AVR empfangen werden darf
69
#define EEPROM_MAX_BYTE 127 //letztes Byte des EEPROM (beim ATTiny2313 128 Byte großer Speicher)
70
//
71
extern unsigned char __heap_start;
72
//
73
#include <avr/io.h>
74
#include <stdlib.h>
75
#include <avr/eeprom.h>
76
#include <util/delay.h>
77
/*
78
#include <avr/interrupt.h>
79
#include <stdio.h>
80
#include <stdint.h>
81
*/
82
//
83
typedef struct {
84
  unsigned char D2:1;
85
  unsigned char D3:1;
86
  unsigned char D4:1;
87
  unsigned char D5:1;
88
}swBit;
89
//
90
//
91
//FUNKTIONS-PROTOTYPEN
92
//--------------------
93
   static void USART_init(unsigned int baud);
94
          void USART_transmit(unsigned char cData);
95
          void USART_transmit_str(char *cStr);
96
          void USART_clear(void);
97
          void clearArray(char cArray[],int iMax);
98
          void __attribute__ ((naked, section (".init8"))) __init8_mem (void);
99
static uint8_t eeprom_get_next_byte(uint8_t iLastByte);
100
static uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes);
101
          void myDebounce(swBit *bitfeld,uint8_t iPin);
102
          void long_delay_ms( unsigned long ms );
103
//
104
//
105
//GLOBALE VARIABLEN
106
//-----------------
107
uint8_t i = 0;
108
uint8_t iEEPROM_lastByte = 0;
109
swBit swPressed;
110
111
112
/***************/
113
int main(void) {
114
/***************/
115
         static char cUSART_inp[IUSART_INP_MAX];  //Array für das Zwischenspeichern von über USART empfangenen Zeichen
116
      unsigned short iUSART_inp = 0;  //Index für das Array cUSART_inp[], entspricht der Anzahl der empfangenen Zeichen
117
      unsigned short iDot = 0;  //zum ständigen Senden eines Punktes (als Lebenszeichen)
118
         static char cTmpZahl[4] = {0};  //Temp-Variable für Konvertierungen mit itoa()
119
       unsigned char cBoolSaved = 0;  //wurden die empfangenen Zeichen im EEPROM gespeichert?
120
       
121
  //
122
  DDRB = 0xff;  //Port B Pins als Ausgänge definieren
123
  PORTB = 0xff;
124
  //DDRD = 0x00;  //Port D Pins als Eingänge definieren. Wohl nicht notwendig, da die Bits standardmäßig sowieso 0 sind
125
  //
126
  USART_init(UART_BAUD_RATE);   //USART initialisieren
127
  //
128
  clearArray(cUSART_inp,IUSART_INP_MAX);
129
  for(;;) {   //Endlosschleife (bei AVR-Programmierung gewollt/nötig)
130
    if( ++iDot % 1000 == 0 ) { //alle 1000 Durchläufe einen Punkt ans Terminal senden
131
      USART_transmit('.');  //Punkt senden
132
      iDot = 0;  //iTest natürlich zurücksetzen, um einen Überlauf zu verhindern
133
    }
134
    if( iUSART_inp > IUSART_INP_MAX ) {      //string wäre zu lang, also alles zurücksetzen
135
      USART_transmit_str("WARNUNG: >");                       //Warnung ans
136
      USART_transmit_str(itoa(IUSART_INP_MAX,cTmpZahl,10));   //Terminal senden
137
      USART_transmit_str(" Zeichen - RESET ");                //
138
      clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
139
      iUSART_inp = 0;   //Array-Index zurücksetzen
140
      USART_clear();
141
    }
142
    //
143
    if( UCSRA & (1<<RXC) ) {  //Zeichen werden empfangen und im char-array gespeichert
144
      //USART_transmit(UDR);  //sendet jedes empfangene Zeichen direkt wieder and Terminal
145
                              //(kann zum grundsätzlichen Testen der Kommunikation aktiviert werden)
146
      cUSART_inp[++iUSART_inp-1] = UDR; //empfangenes Zeichen im Array speichern
147
      cBoolSaved = 0;   //"gespeichert"-Status auf 0 (false) setzen
148
    }
149
    //
150
    if( !(PIND & (1<<PIND2)) && iUSART_inp>0 && !swPressed.D2 && !cBoolSaved) {    //SW0 gedrückt
151
      for(i=0;i<iUSART_inp;i++) {
152
        //char-array (cUSART_inp) in EEPROM speichern
153
        eeprom_write_byte((uint8_t*)(unsigned int)eeprom_get_next_byte(iEEPROM_lastByte),cUSART_inp[i]);
154
      }
155
      //
156
      clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
157
      //
158
      USART_transmit_str(" SAVED ");  //damit man was sieht...
159
      cBoolSaved = 1;   //"gespeichert"-Status auf 1 (true) setzen
160
    }
161
    //
162
    myDebounce(&swPressed,PIND2);
163
    //
164
    if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && !swPressed.D3 && cBoolSaved) {    //SW1 gedrückt
165
      //
166
      for(i=0;i<iUSART_inp;i++) {
167
        //Inhalt des EEPROM rurück in das char-array schreiben
168
//        cUSART_inp[i] = eeprom_read_byte((uint8_t*)(unsigned int)eeprom_get_first_written_byte(iEEPROM_lastByte,iUSART_inp-1-i));
169
        cUSART_inp[i] = eeprom_read_byte((void*)eeprom_get_first_written_byte(iEEPROM_lastByte,iUSART_inp-1-i));
170
      }
171
      //
172
      USART_transmit_str(cUSART_inp);  //Array ans Terminal senden
173
      //
174
      iUSART_inp = 0; //Array-Index zurücksetzen
175
      cBoolSaved = 0; //"gespeichert"-Status auf 0 (false) setzen
176
    }
177
    //
178
    myDebounce(&swPressed,PIND3);
179
    //
180
    if( !(PIND & (1<<PIND4)) && !swPressed.D4 ) {  //SW2 gedrückt: iUSART_inp (Anzahl eingegebener Zeichen) ausgeben
181
      USART_transmit_str(" Anzahl Zeichen: ");                //ans Terminal
182
      USART_transmit_str(itoa(iUSART_inp,cTmpZahl,10));   //senden
183
      USART_transmit(32);                                 //
184
    }
185
    //
186
    myDebounce(&swPressed,PIND4);
187
    //
188
    if( !(PIND & (1<<PIND5)) && !swPressed.D5 ) {          //SW3 gedrückt: freien Speicher (SRAM) ermitteln
189
      USART_transmit_str(" FREE MEM: ");                //und ans Terminal senden
190
      USART_transmit_str(itoa(SP - (uint16_t) &__heap_start,cTmpZahl,10));
191
      USART_transmit_str(" Bytes ");
192
    }
193
    //
194
    myDebounce(&swPressed,PIND5);
195
    //
196
  }
197
  //
198
  return(0);  //wird nie erreicht, da Endlosschleife...
199
}
200
201
202
/***************/
203
static void USART_init(unsigned int baud) {    //nochmal genau ansehen und schöner formulieren!
204
/***************/
205
/*
206
UBRRH = (unsigned char)(baud>>8);    //Baudrate setzen
207
UBRRL = (unsigned char)baud;        //
208
//
209
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
210
//
211
UCSRC = (1<<USBS)|(3<<UCSZ0);  //8 Datenbits, 2 Stopbits (?)
212
*/
213
UCSRA=0x00;
214
//UCSRB=0x08;
215
UCSRC=0x86;
216
UBRRH=0x00;
217
UBRRL=0x17;
218
//
219
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
220
}
221
222
/***************/
223
void USART_transmit(unsigned char cData) {  //sendet ein Zeichen ans Terminal
224
/***************/
225
  while( !(UCSRA & (1<<UDRE)) ) {
226
    ;//warten, bis der transmit buffer leer ist, so dass wieder übertragen werden kann
227
  }
228
  UDR = cData;
229
}
230
231
/***************/
232
void USART_transmit_str(char *cStr) {   //sendet einen String (char-Array) ans Terminal
233
/***************/
234
  while(*cStr) {
235
    USART_transmit(*cStr++);
236
  }
237
}
238
239
/***************/           //Liest solange empfangene Zeichen ein (und verwirft sie), bis 
240
void USART_clear(void) {    //keine mehr da sind.
241
/***************/
242
  uint8_t cTmp;
243
  while( UCSRA & (1<<RXC) ) {
244
    cTmp = UDR;
245
  }
246
  while( UCSRA & (1<<RXC) ) {
247
    cTmp = UDR;
248
  }
249
  return;
250
}
251
/***************/
252
void clearArray(char cArray[],int iMax) { //ersetzt alle Werte in einem Array durch binäre Nullen ('\0')
253
/***************/
254
  for(i=0;i<iMax;i++) {
255
    cArray[i]='\0';
256
  }
257
}
258
259
/***************/
260
static uint8_t eeprom_get_next_byte(uint8_t iLastByte) {
261
/***************/
262
  uint8_t iNextByte = iLastByte + 1;
263
  //
264
  if(iNextByte > EEPROM_MAX_BYTE) {
265
    iNextByte = 0;
266
  }
267
  //
268
  iEEPROM_lastByte = iNextByte;
269
  return(iNextByte);
270
}
271
272
/***************/
273
static uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes) {
274
/***************/
275
  uint8_t iFirstByte;
276
  if( iLastByte - iWrittenBytes < 0 ) {
277
    iFirstByte = EEPROM_MAX_BYTE + iLastByte - iWrittenBytes;
278
  } else {
279
    iFirstByte = iLastByte - iWrittenBytes;
280
  }
281
  return(iFirstByte);
282
}
283
284
/***************/                                   //damit beim Tastendruck nur einmal der
285
void myDebounce(swBit *bitfeld,uint8_t iPin) {      //entsprechende Programmteil ausgeführt wird.
286
/***************/                                   //Die übergebene Struktur speichert, welche
287
  switch(iPin) {                                    //Tasten gerade gedrückt sind oder nicht
288
    case(2):                                        //(0=nicht gedrückt, 1=gedrückt).
289
      if( !(PIND & (1<<iPin)) && !(bitfeld->D2) ) { //Wird eine Taste gedrückt, wird ein weiteres
290
        bitfeld->D2 = 1;                            //Drücken bzw. Gedrückthalten solange ignoriert,
291
      }else {                                       //bis sie wieder losgelassen wurde.
292
        if( PIND & (1<<iPin) ) {
293
          bitfeld->D2 = 0;
294
        }
295
      }
296
      break;
297
    case(3):
298
      if( !(PIND & (1<<iPin)) && !(bitfeld->D3) ) {
299
        bitfeld->D3 = 1;
300
      }else {
301
        if( PIND & (1<<iPin) ) {
302
          bitfeld->D3 = 0;
303
        }
304
      }
305
      break;
306
    case(4):
307
      if( !(PIND & (1<<iPin)) && !(bitfeld->D4) ) {
308
        bitfeld->D4 = 1;
309
      }else {
310
        if( PIND & (1<<iPin) ) {
311
          bitfeld->D4 = 0;
312
        }
313
      }
314
      break;
315
    case(5):
316
      if( !(PIND & (1<<iPin)) && !(bitfeld->D5) ) {
317
        bitfeld->D5 = 1;
318
      }else {
319
        if( PIND & (1<<iPin) ) {
320
          bitfeld->D5 = 0;
321
        }
322
      }
323
      break;
324
    //
325
  }
326
}
327
328
329
/***************/                         //hält für ms Millisekunden die Programmausführung an
330
void long_delay_ms( unsigned long ms ) {  //(nicht mehr im Programm verwendet)
331
/***************/
332
  while( ms-- )
333
    _delay_ms( 1 );
334
}

von _matze (Gast)


Lesenswert?

Nachschlag zum Thema delays: Im AVR-GCC-Tutorial werden in der Funktion 
zum Tastenentprellen übrigens auch delays verwendet. Nur so am Rande...

von Karl H. (kbuchegg)


Lesenswert?

_matze wrote:
> Nachschlag zum Thema delays: Im AVR-GCC-Tutorial werden in der Funktion
> zum Tastenentprellen übrigens auch delays verwendet. Nur so am Rande...

Und gleich unter dem Code steht auch, wo der Pferdefuss dabei liegt.
So what?

von _matze (Gast)


Lesenswert?

Ja, da hast du Recht. Ich habe, nachdem ich meine Funktion geschrieben 
hatte, mal im Internet nach alternativen Lösungen gesucht und bin (mal 
wieder) auf das AVR-GCC-Tutorial gestoßen. Ich hab mir aber nur den Code 
angesehen und nicht den Text darunter gelesen.

Ich wollte ja auch nur darauf hinweisen, dass delays trotz ihres 
offensichtlich schlechten Rufes eine gewisse Daseinsberechtigung haben, 
und dies wollte ich damit untermauern, dass sogar im genannten Tutorial 
delays vorkommen.

Gruß Matze

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.