www.mikrocontroller.net

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


Autor: _matze (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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).
#ifndef F_CPU
  #define F_CPU 4000000
#endif
#ifndef UART_BAUD_RATE
  #define UART_BAUD_RATE 9600
#endif
//
#include <stdlib.h>
#include <avr/io.h>
#include <avr/eeprom.h>
#include <stdio.h>
#include <util/delay.h>
/*
#include <avr/interrupt.h>
#include <stdint.h>
#include <stdbool.h>
*/
//
//PROTOTYPEN
         void myWait(unsigned int iX);
 unsigned int myPot2(unsigned int iExp);
         void USART_init(unsigned int baud);
         void USART_transmit(unsigned char cData);
         void USART_transmit_str(char *cStr);
         void long_delay_ms( unsigned long ms );


/***************/
int main(void) {
/***************/
  unsigned short int i = 0;
                char *cUSART_inp = NULL;
  unsigned short int iUSART_inp = 0;
      unsigned short iTest=1;
                char cZahl[5] = {0};
       unsigned char cBoolSaved = 0;
  //
  DDRB = 0xff;  //Port B Pins als Ausgänge definieren
  PORTB = 0xff;
  //DDRD = 0x00;  //Port D Pins als Eingänge definieren. Wohl nicht notwendig, da die Bits standardmäßig sowieso 0 sind
  //
  USART_init(UART_BAUD_RATE);
  //
  for(;;) {
    //
/*
    if( ++iTest % 500 == 0 ) {
      USART_transmit('.');
      iTest = 0;
    }
*/
    //
    if( UCSRA & (1<<RXC) ) {  //Zeichen werden empfangen und im char-array gespeichert
      cUSART_inp = (char *)realloc( cUSART_inp , (++iUSART_inp) * sizeof(char) );
      cUSART_inp[iUSART_inp-1] = UDR;
      cBoolSaved = 0;
    }
    //
    if( !(PIND & (1<<PIND2)) && iUSART_inp>0) {    //SW0 gedrückt
      for(i=0;i<iUSART_inp;i++) {
        //eeprom_write_byte((unsigned char*)i,cUSART_inp[i]);  //char-array (cUSART_inp) in EEPROM speichern
        eeprom_write_byte((uint8_t*)i,cUSART_inp[i]);  //char-array (cUSART_inp) in EEPROM speichern
      }
      //
      free(cUSART_inp);   //char-array löschen, aber nicht den Index iUSART_inp, da dieser
      cUSART_inp = NULL;  //beim Zurücklesen gebraucht wird.
      //
      USART_transmit_str(" SAVED ");
      cBoolSaved = 1;
      long_delay_ms(500);  //wartet 1 Sek, nicht 200 MSek... warum auch immer...
    }
    //
    if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved ) {    //SW1 gedrückt
      cUSART_inp = (char *)realloc( cUSART_inp , iUSART_inp * sizeof(char) );
      //
      for(i=0;i<iUSART_inp;i++) {
        cUSART_inp[i] = eeprom_read_byte((uint8_t*)i);  //Inhalt des EEPROMs rurück in das char-array schreiben
      }
      cUSART_inp[i] = '\0';
      //
      USART_transmit_str(cUSART_inp);
      //
      free(cUSART_inp);   //string wurde auf Terminal ausgegeben, daher char-array
      cUSART_inp = NULL;  //und Index zurücksetzen
      iUSART_inp = 0;
      cBoolSaved = 0;
      long_delay_ms(500);
    }
    if( !(PIND & (1<<PIND4)) ) {    //SW2 gedrückt: iUSART_inp (Anzahl eingegebener Zeichen) ausgeben
      USART_transmit_str(" iUSART_inp: ");
      USART_transmit_str(itoa(iUSART_inp,cZahl,10));
      USART_transmit(32);
      long_delay_ms(500);
    }
  }
  //
  return 0;
}

/***************/
unsigned int myPot2(unsigned int iExp) {  //potenziert die Basis 2 mit dem Exponenten iExp
/***************/
  unsigned short int i,iErg;
  iErg = 1;
  for(i=0;i<iExp;i++) {
    iErg = iErg * 2;
  }
  return iErg;

}

/***************/
void long_delay_ms( volatile unsigned long ms ) {
/***************/
  while( ms-- )
    _delay_ms( 1 );
}

/***************/
void USART_init(unsigned int baud) {    //nochmal genau ansehen und schöner formulieren!
/***************/
/*
UBRRH = (unsigned char)(baud>>8);    //Baudrate setzen
UBRRL = (unsigned char)baud;        //
//
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
//
UCSRC = (1<<USBS)|(3<<UCSZ0);  //8 Datenbits, 2 Stopbits (?)
*/
UCSRA=0x00;
//UCSRB=0x08;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x17;
//
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
}

/***************/
void USART_transmit(unsigned char cData) {
/***************/
  while( !(UCSRA & (1<<UDRE)) ) {
    ;//warten, bis der transmit buffer leer ist, so dass wieder übertragen werden kann
  }
  UDR = cData;
}

/***************/
void USART_transmit_str(char *cStr) {
/***************/
  while(*cStr) {
    USART_transmit(*cStr++);
  }
}

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...

Autor: _matze (Gast)
Datum:

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: _matze (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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:
if( UCSRA & (1<<RXC) ) {  //Zeichen werden empfangen und im char-array gespeichert
      USART_transmit(UDR);
      cUSART_inp = (char *)realloc( cUSART_inp , (++iUSART_inp) * sizeof(char) );
      cUSART_inp[iUSART_inp-1] = UDR;
      cBoolSaved = 0;
}

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?
sTmp  = UDR;
sTmp2 = UDR;
sTmp3 = UDR;

Oder muss man daraus dieses machen, damit es funktioniert?
sTmp  = UDR;
sTmp2 = sTmp;
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

Autor: _matze (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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

Autor: _matze (Gast)
Datum:

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

Autor: _matze (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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

Autor: _matze (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: PM (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: _matze (Gast)
Datum:

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

Gruß Matze

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: _matze (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

/*  T E S T P R O G R A M M  (USART+EEPROM)
 *  ---------------------------------------
 *  
 *  Dieses Testprogramm (geschrieben für ATTiny2313) empfängt und sendet Zeichen über die
 *  RS232-Schnittstelle (UART/USART) und ermöglicht die folgenden Funktionen:
 *
 *  SW0 (Taster 0, bei mir an PIND2): empfangene Zeichen im EEPROM speichern
 *
 *  SW1 (Taster 1, bei mir an PIND3): gespeicherte Zeichen wieder auslesen und ans Terminal senden
 *
 *  SW2 (Taster 2, bei mir an PIND4): Anzahl aktuell eingelesener Zeichen ans Terminal senden
 *
 *  SW3 (Taster 3, bei mir an PIND5): freien Speicher (SRAM) ans Terminal senden
 *
 *  Zum Testen dieses Programm benötigt man ein Terminal-Programm (ich nutze HTerm), mit dem man
 *  Daten zum AVR schicken und von ihm empfangen kann.
 *
 *  Wenn das Terminal-Programm verbunden ist, sieht man, dass kontinuierlich ein Punkt ('.') gesendet
 *  wird. Dies ist als Lebenszeichen des AVR zu verstehen. Bricht der Datenstrom plötzlich ab, ist
 *  das Programm wahrscheinlich hängengeblieben (z.B. wegen Speicherüberlauf o.ä.).
 *
 *  Da das SRAM in seiner Größe sehr beschränkt ist (beim ATTiny2313 nur 128 Bytes), habe ich eine
 *  maximale Größe für den empfangenen String festgelegt. Sie kann über IUSART_INP_MAX geändert werden.
 *
 *  Das Beschreiben des EEPROM ist in der Hinsicht kritisch, als dass der Speicher nur
 *  ca. 100000 mal beschreibbar ist. Deshalb ist es nicht sinnvoll, jedesmal wieder ab
 *  Byte 0 zu schreiben. Deshalb habe ich die Funktionen eeprom_get_last_byte() und
 *  eeprom_get_first_written_byte() geschrieben. Mithilfe dieser Funktionen kann man so
 *  navigieren, dass das EEPROM kontinuierlich beschrieben wird, so dass alle Bytes
 *  gleichmäßig beansprucht werden und die Lebensdauer deutlich erhöht wird. Erreicht man
 *  das Ende des EEPROM (beim ATTiny2313 128 Bytes, änderbar über EEPROM_MAX_BYTE), so
 *  beginnt man wieder bei Byte 0, wobei ein String auch überlappen kann (z.B. ein
 *  5-Zeichen-String in den Bytes 126, 127, 0, 1 und 2).
 *
 *  Bei meinem Testboard (STK500) habe ich die Taster nicht nach Standardkonfiguration angeschlossen,
 *  wie oben an der Funktionsübersicht zu sehen ist (SW0 auf PIND2 statt PIND0 usw.). Wer ganz normal
 *  SW0 an PIND0 hat, muss lediglich diese wenigen Stellen im Code abändern.
 *
 *  Beispiel:
 *  ---------
 *    
 *  if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
 *                       -
 *  ändern in
 *
 *  if( !(PIND & (1<<PIND1)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
 *                       -
 *
 *  Tipp: Wer HTerm benutzt, sollte in der oberen Ansicht (vom AVR gesendete Zeichen) nur die ASCII-
 *        Ausgabe zulassen (also Häkchen weg bei Hex, Dec und Bin). So behält man den Überblick.
 *
 *
 *  Wer (wie ich gerade) in die AVR-Programmierung einsteigt und an der Kommunikation zwischen AVR
 *  und PC sowie am Beschreiben des EEPROM interessiert ist, wird vielleicht (hoffentlich) mithilfe
 *  dieses Programms weiterkommen.
 *
 *
 *  Matthias Marschhausen (2007-07-20)
 *
 */
#ifndef F_CPU
  #define F_CPU 4000000
#endif
#ifndef UART_BAUD_RATE
  #define UART_BAUD_RATE 9600
#endif
//
#define IUSART_INP_MAX 20   //maximale Anzahl an Zeichen, die vom AVR empfangen werden darf
#define EEPROM_MAX_BYTE 127 //letztes Byte des EEPROM (beim ATTiny2313 128 Byte großer Speicher)
//
extern unsigned char __heap_start;
//
#include <avr/io.h>
#include <stdlib.h>
#include <avr/eeprom.h>
#include <util/delay.h>
/*
#include <avr/interrupt.h>
#include <stdio.h>
#include <stdint.h>
*/
//
//PROTOTYPEN
//----------
          void USART_init(unsigned int baud);
          void USART_transmit(unsigned char cData);
          void USART_transmit_str(char *cStr);
          void long_delay_ms( unsigned long ms );
          void clearArray(char cArray[],int iMax);
          void __attribute__ ((naked, section (".init8"))) __init8_mem (void);
       uint8_t eeprom_get_next_byte(uint8_t iLastByte);
       uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes);

//GLOBALE VARIABLEN
//-----------------
uint8_t i = 0;
uint8_t iEEPROM_lastByte = 0;


/***************/
int main(void) {
/***************/
                char cUSART_inp[IUSART_INP_MAX];  //Array für das Zwischenspeichern von über USART empfangenen Zeichen
      unsigned short iUSART_inp = 0;  //Index für das Array cUSART_inp[], entspricht der Anzahl der empfangenen Zeichen
      unsigned short iDot = 0;  //zum ständigen Senden eines Punktes (als Lebenszeichen)
                char cTmpZahl[5] = {0};  //Temp-Variable für Konvertierungen mit itoa()
       unsigned char cBoolSaved = 0;  //wurden die empfangenen Zeichen im EEPROM gespeichert?
  //
  DDRB = 0xff;  //Port B Pins als Ausgänge definieren
  PORTB = 0xff;
  //DDRD = 0x00;  //Port D Pins als Eingänge definieren. Wohl nicht notwendig, da die Bits standardmäßig sowieso 0 sind
  //
  USART_init(UART_BAUD_RATE);   //USART initialisieren
  //
  for(;;) {   //Endlosschleife (bei AVR-Programmierung gewollt/nötig)
    if( ++iDot % 1000 == 0 ) { //alle 1000 Durchläufe einen Punkt ans Terminal senden
      USART_transmit('.');  //Punkt senden
      iDot = 0;  //iTest natürlich zurücksetzen, um einen Überlauf zu verhindern
    }
    if( iUSART_inp > IUSART_INP_MAX ) {  //string wäre zu lang, also alles zurücksetzen
      USART_transmit_str(" WARNUNG: >");                     //Warnung/Information ans
      USART_transmit_str(itoa(IUSART_INP_MAX,cTmpZahl,10));  //Terminal senden
      USART_transmit_str(" Zeichen - RESET ");            //
      clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
      iUSART_inp = 0;   //Array-Index zurücksetzen
      long_delay_ms(500);   //kurze Pause...
    }
    //
    if( UCSRA & (1<<RXC) ) {  //Zeichen werden empfangen und im char-array gespeichert
      //USART_transmit(UDR);  //sendet jedes empfangene Zeichen direkt wieder and Terminal
                              //(kann zum grundsätzlichen Testen der Kommunikation aktiviert werden)
      cUSART_inp[++iUSART_inp-1] = UDR; //empfangenes Zeichen im Array speichern
      cBoolSaved = 0;   //"gespeichert"-Status auf 0 (false) setzen
    }
    //
    if( !(PIND & (1<<PIND2)) && iUSART_inp>0) {    //SW0 gedrückt
      for(i=0;i<iUSART_inp;i++) {
        //char-array (cUSART_inp) in EEPROM speichern
        eeprom_write_byte((uint8_t*)(unsigned int)eeprom_get_next_byte(iEEPROM_lastByte),cUSART_inp[i]);
      }
      //
      clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
      //
      USART_transmit_str(" SAVED ");  //damit man was sieht...
      cBoolSaved = 1;   //"gespeichert"-Status auf 1 (true) setzen
      long_delay_ms(500);   //kurze Pause...
    }
    //
    if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
      //
      for(i=0;i<iUSART_inp;i++) {
        //Inhalt des EEPROM rurück in das char-array schreiben
        cUSART_inp[i] = eeprom_read_byte((uint8_t*)(unsigned int)eeprom_get_first_written_byte(iEEPROM_lastByte,iUSART_inp-1-i));
      }
      //
      USART_transmit_str(cUSART_inp);  //Array ans Terminal senden
      //
      iUSART_inp = 0; //Array-Index zurücksetzen
      cBoolSaved = 0; //"gespeichert"-Status auf 0 (false) setzen
      long_delay_ms(500);   //kurze Pause...
    }
    if( !(PIND & (1<<PIND4)) ) {  //SW2 gedrückt: iUSART_inp (Anzahl eingegebener Zeichen) ausgeben
      USART_transmit_str(" Anzahl Zeichen: ");                //ans Terminal
      USART_transmit_str(itoa(iUSART_inp,cTmpZahl,10));   //senden
      USART_transmit(32);                                 //
      long_delay_ms(500);   //kurze Pause...
    }
    if( !(PIND & (1<<PIND5)) ) {          //SW3 gedrückt: freien Speicher (SRAM) ermitteln
      USART_transmit_str(" FREE MEM: ");                //und ans Terminal senden
      USART_transmit_str(itoa(SP - (uint16_t) &__heap_start,cTmpZahl,10));
      USART_transmit_str(" Bytes ");
      long_delay_ms(500);   //kurze Pause...
    }
  }
  //
  return(0);  //wird nie erreicht, da Endlosschleife...
}

/***************/
void long_delay_ms( volatile unsigned long ms ) {
/***************/
  while( ms-- )
    _delay_ms( 1 );
}

/***************/
void USART_init(unsigned int baud) {    //nochmal genau ansehen und schöner formulieren!
/***************/
/*
UBRRH = (unsigned char)(baud>>8);    //Baudrate setzen
UBRRL = (unsigned char)baud;        //
//
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
//
UCSRC = (1<<USBS)|(3<<UCSZ0);  //8 Datenbits, 2 Stopbits (?)
*/
UCSRA=0x00;
//UCSRB=0x08;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x17;
//
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
}

/***************/
void USART_transmit(unsigned char cData) {  //sendet ein Zeichen ans Terminal
/***************/
  while( !(UCSRA & (1<<UDRE)) ) {
    ;//warten, bis der transmit buffer leer ist, so dass wieder übertragen werden kann
  }
  UDR = cData;
}

/***************/
void USART_transmit_str(char *cStr) {   //sendet einen String (char-Array) ans Terminal
/***************/
  while(*cStr) {
    USART_transmit(*cStr++);
  }
}

/***************/
void clearArray(char cArray[],int iMax) { //ersetzt alle Werte in einem Array durch binäre Nullen ('\0')
/***************/
  for(i=0;i<iMax;i++) {
    cArray[i]='\0';
  }
}

/***************/
uint8_t eeprom_get_next_byte(uint8_t iLastByte) {
/***************/
  uint8_t iNextByte = iLastByte + 1;
  //
  if(iNextByte > EEPROM_MAX_BYTE) {
    iNextByte = 0;
  }
  //
  iEEPROM_lastByte = iNextByte;
  return(iNextByte);
}

/***************/
uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes) {
/***************/
  uint8_t iFirstByte;
  if( iLastByte - iWrittenBytes < 0 ) {
    iFirstByte = EEPROM_MAX_BYTE + iLastByte - iWrittenBytes;
  } else {
    iFirstByte = iLastByte - iWrittenBytes;
  }
  return(iFirstByte);
}

  

Autor: _matze (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oh, wir haben ja noch Juni... ein Versehen...

Autor: _matze (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...
/*  T E S T P R O G R A M M  (USART+EEPROM)
 *  ---------------------------------------
 *  
 *  Dieses Testprogramm (geschrieben für ATTiny2313) empfängt und sendet Zeichen über die
 *  RS232-Schnittstelle (UART/USART) und ermöglicht die folgenden Funktionen:
 *
 *  SW0 (Taster 0, bei mir an PIND2): empfangene Zeichen im EEPROM speichern
 *
 *  SW1 (Taster 1, bei mir an PIND3): gespeicherte Zeichen wieder auslesen und ans Terminal senden
 *
 *  SW2 (Taster 2, bei mir an PIND4): Anzahl aktuell eingelesener Zeichen ans Terminal senden
 *
 *  SW3 (Taster 3, bei mir an PIND5): freien Speicher (SRAM) ans Terminal senden
 *
 *  Zum Testen dieses Programm benötigt man ein Terminal-Programm (ich nutze HTerm), mit dem man
 *  Daten zum AVR schicken und von ihm empfangen kann.
 *
 *  Wenn das Terminal-Programm verbunden ist, sieht man, dass kontinuierlich ein Punkt ('.') gesendet
 *  wird. Dies ist als Lebenszeichen des AVR zu verstehen. Bricht der Datenstrom plötzlich ab, ist
 *  das Programm wahrscheinlich hängengeblieben (z.B. wegen Speicherüberlauf o.ä.).
 *
 *  Da das SRAM in seiner Größe sehr beschränkt ist (beim ATTiny2313 nur 128 Bytes), habe ich eine
 *  maximale Größe für den empfangenen String festgelegt. Sie kann über IUSART_INP_MAX geändert werden.
 *
 *  Das Beschreiben des EEPROM ist in der Hinsicht kritisch, als dass der Speicher nur
 *  ca. 100000 mal beschreibbar ist. Deshalb ist es nicht sinnvoll, jedesmal wieder ab
 *  Byte 0 zu schreiben. Deshalb habe ich die Funktionen eeprom_get_last_byte() und
 *  eeprom_get_first_written_byte() geschrieben. Mithilfe dieser Funktionen kann man so
 *  navigieren, dass das EEPROM kontinuierlich beschrieben wird, so dass alle Bytes
 *  gleichmäßig beansprucht werden und die Lebensdauer deutlich erhöht wird. Erreicht man
 *  das Ende des EEPROM (beim ATTiny2313 128 Bytes, änderbar über EEPROM_MAX_BYTE), so
 *  beginnt man wieder bei Byte 0, wobei ein String auch überlappen kann (z.B. ein
 *  5-Zeichen-String in den Bytes 126, 127, 0, 1 und 2).
 *
 *  Bei meinem Testboard (STK500) habe ich die Taster nicht nach Standardkonfiguration angeschlossen,
 *  wie oben an der Funktionsübersicht zu sehen ist (SW0 auf PIND2 statt PIND0 usw.). Wer ganz normal
 *  SW0 an PIND0 hat, muss lediglich diese wenigen Stellen im Code abändern.
 *
 *  Beispiel:
 *  ---------
 *    
 *  if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
 *                       -
 *  ändern in
 *
 *  if( !(PIND & (1<<PIND1)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
 *                       -
 *
 *  Tipp: Wer HTerm benutzt, sollte in der oberen Ansicht (vom AVR gesendete Zeichen) nur die ASCII-
 *        Ausgabe zulassen (also Häkchen weg bei Hex, Dec und Bin). So behält man den Überblick.
 *
 *
 *  Wer (wie ich gerade) in die AVR-Programmierung einsteigt und an der Kommunikation zwischen AVR
 *  und PC sowie am Beschreiben des EEPROM interessiert ist, wird vielleicht (hoffentlich) mithilfe
 *  dieses Programms weiterkommen.
 *
 *
 *  Matthias Marschhausen (2007-07-20)
 *
 */
#ifndef F_CPU
  #define F_CPU 4000000
#endif
#ifndef UART_BAUD_RATE
  #define UART_BAUD_RATE 9600
#endif
//
#define IUSART_INP_MAX 20   //maximale Anzahl an Zeichen, die vom AVR empfangen werden darf
#define EEPROM_MAX_BYTE 127 //letztes Byte des EEPROM (beim ATTiny2313 128 Byte großer Speicher)
//
extern unsigned char __heap_start;
//
#include <avr/io.h>
#include <stdlib.h>
#include <avr/eeprom.h>
#include <util/delay.h>
/*
#include <avr/interrupt.h>
#include <stdio.h>
#include <stdint.h>
*/
//
typedef struct {
  unsigned char D2:1;
  unsigned char D3:1;
  unsigned char D4:1;
  unsigned char D5:1;
}swBit;
//
//
//FUNKTIONS-PROTOTYPEN
//--------------------
   static void USART_init(unsigned int baud);
          void USART_transmit(unsigned char cData);
          void USART_transmit_str(char *cStr);
          void USART_clear(void);
          void clearArray(char cArray[],int iMax);
          void __attribute__ ((naked, section (".init8"))) __init8_mem (void);
static uint8_t eeprom_get_next_byte(uint8_t iLastByte);
static uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes);
          void myDebounce(swBit *bitfeld,uint8_t iPin);
          void long_delay_ms( unsigned long ms );
//
//
//GLOBALE VARIABLEN
//-----------------
uint8_t i = 0;
uint8_t iEEPROM_lastByte = 0;
swBit swPressed;


/***************/
int main(void) {
/***************/
         static char cUSART_inp[IUSART_INP_MAX];  //Array für das Zwischenspeichern von über USART empfangenen Zeichen
      unsigned short iUSART_inp = 0;  //Index für das Array cUSART_inp[], entspricht der Anzahl der empfangenen Zeichen
      unsigned short iDot = 0;  //zum ständigen Senden eines Punktes (als Lebenszeichen)
         static char cTmpZahl[4] = {0};  //Temp-Variable für Konvertierungen mit itoa()
       unsigned char cBoolSaved = 0;  //wurden die empfangenen Zeichen im EEPROM gespeichert?
       
  //
  DDRB = 0xff;  //Port B Pins als Ausgänge definieren
  PORTB = 0xff;
  //DDRD = 0x00;  //Port D Pins als Eingänge definieren. Wohl nicht notwendig, da die Bits standardmäßig sowieso 0 sind
  //
  USART_init(UART_BAUD_RATE);   //USART initialisieren
  //
  clearArray(cUSART_inp,IUSART_INP_MAX);
  for(;;) {   //Endlosschleife (bei AVR-Programmierung gewollt/nötig)
    if( ++iDot % 1000 == 0 ) { //alle 1000 Durchläufe einen Punkt ans Terminal senden
      USART_transmit('.');  //Punkt senden
      iDot = 0;  //iTest natürlich zurücksetzen, um einen Überlauf zu verhindern
    }
    if( iUSART_inp > IUSART_INP_MAX ) {      //string wäre zu lang, also alles zurücksetzen
      USART_transmit_str("WARNUNG: >");                       //Warnung ans
      USART_transmit_str(itoa(IUSART_INP_MAX,cTmpZahl,10));   //Terminal senden
      USART_transmit_str(" Zeichen - RESET ");                //
      clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
      iUSART_inp = 0;   //Array-Index zurücksetzen
      USART_clear();
    }
    //
    if( UCSRA & (1<<RXC) ) {  //Zeichen werden empfangen und im char-array gespeichert
      //USART_transmit(UDR);  //sendet jedes empfangene Zeichen direkt wieder and Terminal
                              //(kann zum grundsätzlichen Testen der Kommunikation aktiviert werden)
      cUSART_inp[++iUSART_inp-1] = UDR; //empfangenes Zeichen im Array speichern
      cBoolSaved = 0;   //"gespeichert"-Status auf 0 (false) setzen
    }
    //
    if( !(PIND & (1<<PIND2)) && iUSART_inp>0 && !swPressed.D2 && !cBoolSaved) {    //SW0 gedrückt
      for(i=0;i<iUSART_inp;i++) {
        //char-array (cUSART_inp) in EEPROM speichern
        eeprom_write_byte((uint8_t*)(unsigned int)eeprom_get_next_byte(iEEPROM_lastByte),cUSART_inp[i]);
      }
      //
      clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
      //
      USART_transmit_str(" SAVED ");  //damit man was sieht...
      cBoolSaved = 1;   //"gespeichert"-Status auf 1 (true) setzen
    }
    //
    myDebounce(&swPressed,PIND2);
    //
    if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && !swPressed.D3 && cBoolSaved) {    //SW1 gedrückt
      //
      for(i=0;i<iUSART_inp;i++) {
        //Inhalt des EEPROM rurück in das char-array schreiben
//        cUSART_inp[i] = eeprom_read_byte((uint8_t*)(unsigned int)eeprom_get_first_written_byte(iEEPROM_lastByte,iUSART_inp-1-i));
        cUSART_inp[i] = eeprom_read_byte((void*)eeprom_get_first_written_byte(iEEPROM_lastByte,iUSART_inp-1-i));
      }
      //
      USART_transmit_str(cUSART_inp);  //Array ans Terminal senden
      //
      iUSART_inp = 0; //Array-Index zurücksetzen
      cBoolSaved = 0; //"gespeichert"-Status auf 0 (false) setzen
    }
    //
    myDebounce(&swPressed,PIND3);
    //
    if( !(PIND & (1<<PIND4)) && !swPressed.D4 ) {  //SW2 gedrückt: iUSART_inp (Anzahl eingegebener Zeichen) ausgeben
      USART_transmit_str(" Anzahl Zeichen: ");                //ans Terminal
      USART_transmit_str(itoa(iUSART_inp,cTmpZahl,10));   //senden
      USART_transmit(32);                                 //
    }
    //
    myDebounce(&swPressed,PIND4);
    //
    if( !(PIND & (1<<PIND5)) && !swPressed.D5 ) {          //SW3 gedrückt: freien Speicher (SRAM) ermitteln
      USART_transmit_str(" FREE MEM: ");                //und ans Terminal senden
      USART_transmit_str(itoa(SP - (uint16_t) &__heap_start,cTmpZahl,10));
      USART_transmit_str(" Bytes ");
    }
    //
    myDebounce(&swPressed,PIND5);
    //
  }
  //
  return(0);  //wird nie erreicht, da Endlosschleife...
}


/***************/
static void USART_init(unsigned int baud) {    //nochmal genau ansehen und schöner formulieren!
/***************/
/*
UBRRH = (unsigned char)(baud>>8);    //Baudrate setzen
UBRRL = (unsigned char)baud;        //
//
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
//
UCSRC = (1<<USBS)|(3<<UCSZ0);  //8 Datenbits, 2 Stopbits (?)
*/
UCSRA=0x00;
//UCSRB=0x08;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x17;
//
UCSRB = (1<<RXEN)|(1<<TXEN);    //Receiver und Transmitter aktivieren
}

/***************/
void USART_transmit(unsigned char cData) {  //sendet ein Zeichen ans Terminal
/***************/
  while( !(UCSRA & (1<<UDRE)) ) {
    ;//warten, bis der transmit buffer leer ist, so dass wieder übertragen werden kann
  }
  UDR = cData;
}

/***************/
void USART_transmit_str(char *cStr) {   //sendet einen String (char-Array) ans Terminal
/***************/
  while(*cStr) {
    USART_transmit(*cStr++);
  }
}

/***************/           //Liest solange empfangene Zeichen ein (und verwirft sie), bis 
void USART_clear(void) {    //keine mehr da sind.
/***************/
  uint8_t cTmp;
  while( UCSRA & (1<<RXC) ) {
    cTmp = UDR;
  }
  while( UCSRA & (1<<RXC) ) {
    cTmp = UDR;
  }
  return;
}
/***************/
void clearArray(char cArray[],int iMax) { //ersetzt alle Werte in einem Array durch binäre Nullen ('\0')
/***************/
  for(i=0;i<iMax;i++) {
    cArray[i]='\0';
  }
}

/***************/
static uint8_t eeprom_get_next_byte(uint8_t iLastByte) {
/***************/
  uint8_t iNextByte = iLastByte + 1;
  //
  if(iNextByte > EEPROM_MAX_BYTE) {
    iNextByte = 0;
  }
  //
  iEEPROM_lastByte = iNextByte;
  return(iNextByte);
}

/***************/
static uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes) {
/***************/
  uint8_t iFirstByte;
  if( iLastByte - iWrittenBytes < 0 ) {
    iFirstByte = EEPROM_MAX_BYTE + iLastByte - iWrittenBytes;
  } else {
    iFirstByte = iLastByte - iWrittenBytes;
  }
  return(iFirstByte);
}

/***************/                                   //damit beim Tastendruck nur einmal der
void myDebounce(swBit *bitfeld,uint8_t iPin) {      //entsprechende Programmteil ausgeführt wird.
/***************/                                   //Die übergebene Struktur speichert, welche
  switch(iPin) {                                    //Tasten gerade gedrückt sind oder nicht
    case(2):                                        //(0=nicht gedrückt, 1=gedrückt).
      if( !(PIND & (1<<iPin)) && !(bitfeld->D2) ) { //Wird eine Taste gedrückt, wird ein weiteres
        bitfeld->D2 = 1;                            //Drücken bzw. Gedrückthalten solange ignoriert,
      }else {                                       //bis sie wieder losgelassen wurde.
        if( PIND & (1<<iPin) ) {
          bitfeld->D2 = 0;
        }
      }
      break;
    case(3):
      if( !(PIND & (1<<iPin)) && !(bitfeld->D3) ) {
        bitfeld->D3 = 1;
      }else {
        if( PIND & (1<<iPin) ) {
          bitfeld->D3 = 0;
        }
      }
      break;
    case(4):
      if( !(PIND & (1<<iPin)) && !(bitfeld->D4) ) {
        bitfeld->D4 = 1;
      }else {
        if( PIND & (1<<iPin) ) {
          bitfeld->D4 = 0;
        }
      }
      break;
    case(5):
      if( !(PIND & (1<<iPin)) && !(bitfeld->D5) ) {
        bitfeld->D5 = 1;
      }else {
        if( PIND & (1<<iPin) ) {
          bitfeld->D5 = 0;
        }
      }
      break;
    //
  }
}


/***************/                         //hält für ms Millisekunden die Programmausführung an
void long_delay_ms( unsigned long ms ) {  //(nicht mehr im Programm verwendet)
/***************/
  while( ms-- )
    _delay_ms( 1 );
}

Autor: _matze (Gast)
Datum:

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: _matze (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.