Forum: Mikrocontroller und Digitale Elektronik ADC UART Ausgabe nicht richtig


von Blue B. (bluebrain)


Angehängte Dateien:

Lesenswert?

Hallo Forummitglieder,

Ich bin kein µC Profi und arbeite mich gerade durch Beispielprogramme 
sowie das Tutorial.
Zur Zeit experimentiere ich gerade mit einem atmega 8 und versuche 
erfolglos Analog-Werte per uart auszugeben.

Zu der Software:
Ich habe aus ADC-Beispielen eine eigene Code zusammengebastelt. Mein 
Programm tastet alle Eingänge ab und gibt alle 100 ms die Werte per uart 
aus. Die Übertragung auf den Hyperterminal läuft mit 9600 Baudrate und 
8N1.

Code:

//überprüfen ob baudrate mit CPU Frequenz zusammenpasst
#define UBRR_VAL ((F_CPU)/(BAUD*16)-1)      //clever runde
#define ADC_Referenz 5UL // 5 ist die Referenzspannung
#define mV_pro_Volt 1000 // von V auf mV


//globale Variablen definieren
uint8_t ADIteiler=0; uint8_t Channel=0;
uint8_t result=0;char s[20];


//Initialisierung
void init()
{
  //Initialisierung UART-Schnittstelle
  UBRRH = (uint8_t) (UBRR_VAL >> 8);
  UBRRL = (uint8_t) (UBRR_VAL);
  UCSRB = (1<<RXEN)|(1<<TXEN);          //UART TX einschalten
  UCSRC = (1<<URSEL) | ((1<<UCSZ1) | (1<<UCSZ0)) | ((0<<UPM1) | 
(0<<UPM0)) | (0<<USBS);

  //interrupt
  TCNT0 = 6;            //Zählregister setzen
  TIMSK |= (1<<TOIE0);  //damit interrupt auch ausläst wenn überlauf 
//erreicht wird
  TCCR0 |= (1<<CS00);
  TCCR0 &= ~(1<<CS01);
  TCCR0 &= ~(1<<CS02);  //CPU Takt wird verwendet --> 8MHz
  TIFR |= (1<<TOV0);    //startet interrupt bei jedem überlauf
}

uint16_t ADC_Reading(uint8_t Mux_channel)
{
  uint8_t i;
  uint16_t result = 0;

  //Initialisierung ADC
  ADCSRA = (1<<ADEN);       //Enables the ADC
  ADCSRA |= (1<<ADPS2) | (1<<ADPS1);   //Determine the division factor 
64
  _delay_us(40);
  ADMUX |= (1<<REFS0) | (0<<REFS1 | Mux_channel);//AVcc REF Voltage 
Reference Selection //Select pin ADC0 using MUX
  ADMUX |= (0<<ADLAR);    //The result is right adjusted (rechtsbündig)
  ADCSRA |= (1<<ADSC);      //Start conversion
  while(ADCSRA & (1<<ADSC));  //wait until converstion completed
  result = ADCW;    //ADCW must be read once
  result = 0;       //Zwischenpeicher leeren
  ADCSRA &= ~(1<<ADEN); // ADC reanables

//Now reads 4 times the similar tension and selected channel channel

  for(i=0; i<4; i++)
  {
    ADCSRA |= (1<<ADSC);  //A single conversion
    while(ADCSRA & (1<<ADSC));  //Waiting on conversion closing
    result += ADCW;
  ADCSRA &=~(1<<ADEN);  // ADC reanables
  }
  result /= 4;  // Summe durch die Anzahl der Messwerte teilen = arthm.
  return result;
}

//Unterprogramme UART
int uart_putc(char c)
{
    while (!(UCSRA & (1<<UDRE))){}    //warten bis Senden moeglich
    UDR = c;
  return(0);                         //sende Zeichen
}

void uart_puts (char *s)
{
    while (*s)
    {   // so lange *s != '\0' also ungleich dem "String-Endezeichen"
        uart_putc(*s);   //schreibt die einzelne Ziffer auf die UART
        s++;//geht zur (falls vorhandenen) zweiten Ziffer des HEX-Wertes
    }
}


void rs232(uint16_t *wert)
{
  static int16_t alter_wert = -1;
  char puffer[20];

    if (alter_wert != *wert)
    {
        alter_wert = *wert;
  uart_puts(". ");
        uart_puts("Channel = ");
        itoa((int) *wert, puffer, 10);
        uart_puts(puffer);
  uart_puts(" mV");
        uart_puts(" 0x");
        itoa((int) *wert, puffer, 16);
        uart_puts(puffer);
        uart_puts(" 0b");
        itoa((int) *wert, puffer, 2);
        uart_puts(puffer);
        uart_puts("\r\n");
    }
}


//Interrupt Service Routine für AD Wandlung
ISR(TIMER0_OVF_vect)
{
  ADIteiler++;   //zählvariable um den interrupt auf jede ms zu takten
  if(ADIteiler >= 100)    //Zählschleife für alle ms 100
  {
    ADIteiler=0;    //Teiler wieder auf 0 setzen
  Channel++;
  if (Channel >= 6)
  {
  Channel=0;    //Channel wieder auf 0 setzen
  }
  PORTD ^=( 1 << PD3 );
  itoa( (int) Channel, s, 10 ); //HEX-Zahlen in Ascii umwandeln
  uart_puts( s );
  uint16_t result=ADC_Reading(Channel)*ADC_Referenz*mV_pro_Volt/1024; 
//Reading the Analog voltage
  rs232(&result);
  }
}
int main(void)
{
  init();
  sei();                //Globale Interrupts freischalten
  while (1)
{;;}
}


Zu der Hardware:
Alle Analog-Eingänge habe ich an der Masse verbunden (0 V) und die 
Referenzspannung is gleich Vcc (5V). Atmega 8 ist auf interne Takt 8 MHz 
programmiert. Auf dem Hyperterminal sehe ich allerdings, dass die Werte 
von 0 abweichend (s. bitte Anhang). Es sieht als ob dass die Ausgänge 
nicht 10 bits ist sondern 12 bits.   Ist eventuell ein Fehler in meinen 
Code? Kann jemand helfen?

Danke im Voraus.

von Gast (Gast)


Lesenswert?

>   ADMUX |= (1<<REFS0) | (0<<REFS1 | Mux_channel);//AVcc REF Voltage

Welcher Kanal wird da nach den ersten paar Durchläufen wohl gemessen?
ADC7 vielleicht?
Hast du diesen auch beschaltet?

von P. S. (Gast)


Lesenswert?

Mal generell: Das ist schon ein ordentlicher Brocken, den du da debuggen 
willst. Du solltest das schrittweise ausprobieren: Also sowieso erstmal 
ohne Interrupt, nur einen Kanal und vor Allem erst mal die Ausgabe 
testen, dann die Umrechnung testen, dann die Rohwerte pruefen, und so 
weiter. Auf keinen Fall alles auf Einmal...

von Karl H. (kbuchegg)


Lesenswert?

1
  ADCSRA &= ~(1<<ADEN); // ADC reanables

Äh. Nein
Das schaltet den ADC ab und nicht ein.

Innerhalb der nächsten for Schleife: lass den ADC in Ruhe arbeiten und 
fummle nicht am ADEN Bit rum. Vor allen dann nicht, wenn du den ADC 
damit ständig abschaltest.

> Es sieht als ob dass die Ausgänge nicht 10 bits ist sondern 12 bits.

Woran machst du das fest?
Du gibst die gemessenen mV einmal dezimal, dann hex und dann binär aus. 
Ändert aber nichts daran, dass du nicht den Wert vom ADC ausgibst, 
sondern das was du daraus errechnet hast.
Ehe du da rumrechnest, lass dir doch das ADC Ergebnis direkt ausgeben, 
ohne irgendwelche Rumrechnerei.

Bei der Fehlersuche stufenweise vorgehen, wie Peter es schon angedeutet 
hat!

von Maik F. (sabuty) Benutzerseite


Lesenswert?

Ein paar Punkte ohne Anspruch auf Vollständigkeit:

- Ich würde mir erstmal Gedanken machen, warum im Terminal Kanal-Nummern 
größer 6 auftauchen!
- Die ganze Messung und RS232-Ausgabe in einem Timer-Interrupt zu 
machen, ist hier eher ungeschickt.
- volatile für Variablen, die im Interrupt verwendet werden
- Quelltext lesbar posten.

von Karl H. (kbuchegg)


Lesenswert?

Maik F. schrieb:
> Ein paar Punkte ohne Anspruch auf Vollständigkeit:
>
> - Ich würde mir erstmal Gedanken machen, warum im Terminal Kanal-Nummern
> größer 6 auftauchen!

Richtig.
Allerdings ein kleiner Hinweis: Das tun sie in Wirklichkeit gar nicht. 
Die Kanalnummern laufen brav von 0 bis 5. Nur durch die ungeschickte Art 
der Ausgabe entsteht dann ein missverständlicher Output. Die 
eigentlichen Kanalnummern sind schon in Ordnung.

von Blue B. (bluebrain)


Lesenswert?

herzlichen Dank für Eure schnelle Antwort!

Peter Stegemann schrieb:
>Du solltest das schrittweise ausprobieren

Der Code für die AD-Umwandlung und die Umrechnung sollte eigentlich 
einwandfrei funktionieren, da ich bereits für ein anderes AD-Experiment 
verwendet habe. Der Unterschied  zu jetztigem Experiment liegt es daran, 
dass ich damals die Ausgabe der Analog-Werte direkt an den gemultiplexen 
4x7 Segment LED-Anzeigen schickte und ohne Interrupt und ohne 
UART-Ausgabe.
Ich werde allerdings alles nochmals durchchecken.


Karl heinz Buchegger schrieb:
>Richtig.
>Allerdings ein kleiner Hinweis: Das tun sie in Wirklichkeit gar nicht.
>Die Kanalnummern laufen brav von 0 bis 5. Nur durch die ungeschickte Art
>der Ausgabe entsteht dann ein missverständlicher Output. Die
>eigentlichen Kanalnummern sind schon in Ordnung.

Wie Karl heinz Buchegger richtig erkannt hat, zeigt die Ausgabe ein 
missverständlicher Output? Durch die Befehle Channel++ und if (Channel 
>= 6) müssen doch die Kanalnummern richtig in der Reihenfolgen abgefragt 
und ausgegeben werden. Momentan weiss ich nicht genau wo das Problem 
liegt? Ich vermute, dass das Problem an dem UART-Schreibtiming liegt. 
Wie kann man die Ausgabe richtig darstellen?

> Es sieht als ob dass die Ausgänge nicht 10 bits ist sondern 12 bits.
>Woran machst du das fest?
Sorry ich meine 11 bits. Die erkenne ich von der Binär-Ausgabe (z.B. 
1000 0000 0000).

von Karl H. (kbuchegg)


Lesenswert?

Brain Aroon schrieb:
> Wie Karl heinz Buchegger richtig erkannt hat, zeigt die Ausgabe ein
> missverständlicher Output? Durch die Befehle Channel++ und if (Channel
>>= 6) müssen doch die Kanalnummern richtig in der Reihenfolgen abgefragt
> und ausgegeben werden. Momentan weiss ich nicht genau wo das Problem
> liegt? Ich vermute, dass das Problem an dem UART-Schreibtiming liegt.
> Wie kann man die Ausgabe richtig darstellen?

Nein, das meine ich nicht.
Du schreibst die Kanalnummer auf jeden Fall hin, den Wert dazu aber nur 
dann wenn er sich von deinem zuletzt gemerkten Wert in der 
Ausgaberoutine unterscheidet (der dann noch dazu von einem anderen Kanal 
stammt). Daher kann es vorkommen, dass du 2 oder mehr Kanalnummern 
direkt hintereinander ausgibst.

Typischer Fall von: Du hast dich selbst ausgetrickst, indem du deine 
Ausgaben möglichst so verstreut hast, dass du dich selbst nicht mehr 
auskennst.
Dein inkonsistentes und schirches Einrückschema tut ein Übriges dazu, 
möglichst schnell die Übersicht im Code zu verlieren :-)

Und wozu du den Wert in die rs232 Funktion per Pointer übergibst, weißt 
du wahrscheinlich selber nicht.

> Sorry ich meine 11 bits. Die erkenne ich von der Binär-Ausgabe (z.B.
> 1000 0000 0000).

Die gibst du aber gar nicht aus :-)
Du gibst den ADC Wert aus, nachdem du ihn umgerechnet hast. Das was du 
an deiner Anzeige siehst ist in allen 3 Fällen (dezimal, hex, binär) 
schon dein Millivolt Wert. Und ~2400 milliVolt sind nun mal binär nicht 
mit 10 Bits machbar.

Und schon wieder: Du hast dich selbst ausgetrickst.

von Blue B. (bluebrain)


Lesenswert?

Karl heinz Buchegger schrieb
>Du schreibst die Kanalnummer auf jeden Fall hin, den Wert dazu aber nur
>dann wenn er sich von deinem zuletzt gemerkten Wert in der
>Ausgaberoutine unterscheidet (der dann noch dazu von einem anderen Kanal
>stammt).
Meist Du etwa die Stelle im Abschnitt //Interrupt Service Routine für AD 
Wandlung)?
Kannst bitte noch genau die Stelle zeigen, wo du meinst, dass der Wert 
nicht sofort aktualisiert wird? Wenn ich das verstehe, arbeitet das 
Program von oben nach unten. Im Falle vom Abschnitt (//Interrupt Service 
Routine für AD Wandlung) sollten folgende Schritte durchgeführt und zwar 
jedesmal wenn die Interruptbedingung (100 ms) erfüllt ist :
1. Kanalnummer erhöhen beim jeden Aufruf (max. 5)
2. Kanalnummer ausgeben (uart)
3. Umrechnen und Ausgeben des Analogwertes vom gewählten Kanal (uart)
Habe ich ein Denkfehler im Code?

>Und wozu du den Wert in die rs232 Funktion per Pointer übergibst, weißt
du wahrscheinlich selber nicht.
Der Grund ist, dass die Ausgabe (buffer) von der itoa funktion ein 
Pointer ist.

Die Ausgabe von ~2400 milliVolt ist zu groß für einen kurzgeschlossenen 
Eingang. Ich habe alle Dataformat gecheckt. Ich konnte momentan 
diesbezüglich keinen Fehler im meinen Code  entdecken. 
Huhhhhhhhhhhhhhh....:-( Ich brauche ein Denkanstoß!

von Karl H. (kbuchegg)


Lesenswert?

Brain Aroon schrieb:

> Kannst bitte noch genau die Stelle zeigen, wo du meinst, dass der Wert
> nicht sofort aktualisiert wird? Wenn ich das verstehe,

Wenn du das was du da geschrieben hast nicht verstehst, heißt das nur, 
dass du dich mächtig übernommen hast oder aber den Code geklaut hast 
ohne ihn zu verstehen oder auch nur ansatzweise zu analysieren.

> 3. Umrechnen und Ausgeben des Analogwertes vom gewählten Kanal (uart)
> Habe ich ein Denkfehler im Code?

Und was passiert in der Ausgaberoutine?
1
  static int16_t alter_wert = -1;
2
  char puffer[20];
3
4
    if (alter_wert != *wert)
5
    {
6
        alter_wert = *wert;
7
8
        ....


Aha. Die Ausgabe des Wertes erfolgt also nur dann, wenn sich *wert und 
alter_wert unterscheiden. Ich weiß nicht, wie deutlicher ich das jetzt 
noch sagen kann.

>
>>Und wozu du den Wert in die rs232 Funktion per Pointer übergibst, weißt
> du wahrscheinlich selber nicht.
> Der Grund ist, dass die Ausgabe (buffer) von der itoa funktion ein
> Pointer ist.

LOL.
Und deswegen übergibst du wert per Pointer. Was hat denn *wert und 
buffer miteinander zu tun? Richtig: gar nichts.

> Die Ausgabe von ~2400 milliVolt ist zu groß für einen kurzgeschlossenen
> Eingang.

Auch das hab ich dir schon gesagt:
Du hast deinen ADC innerhalb der Messfunktion nicht enabled, sondern 
ausgeschaltet. Die Werte die deine Messfunktion feststellt sind reine 
Phantasieprodukte.

 Ich habe alle Dataformat gecheckt. Ich konnte momentan
> diesbezüglich keinen Fehler im meinen Code  entdecken.
> Huhhhhhhhhhhhhhh....:-( Ich brauche ein Denkanstoß!

Du brauchst
* ein C-Buch
* schmeiss den Code weg, lerne davon und bau ihn neu auf.
  Aber diesmal strukturiert und überlegt.
  Entscheide welche Funktion wofür zuständig ist und dann schreib sie
  Eine rs232 Funktion ist zb dafür zuständig einen Wert auszugeben. Sie
  ist aber ganz sicher nicht dafür zuständig irgendwelche gut gemeinte
  Optimierungen zu machen und die Ausgabe zu unterdrücken.

von Blue B. (bluebrain)


Lesenswert?

>Wenn du das was du da geschrieben hast nicht verstehst, heißt das nur,
>dass du dich mächtig übernommen hast oder aber den Code geklaut hast
>ohne ihn zu verstehen oder auch nur ansatzweise zu analysieren.
Ich habe gesagt, dass ich den Code aus Beispielen zusammengebastelt 
habe. Die meisten Codes (µC Atmel) sind open source, also von Code 
Klauen ist nicht hier der Rede.

Und was passiert in der Ausgaberoutine?

  static int16_t alter_wert = -1;
  char puffer[20];

    if (alter_wert != *wert)
    {
        alter_wert = *wert;

        ....

Da der *wert niemals -1 wird, wird die if anweisung immer gültig sein 
und der Wert immer aktualisiert. Und zwar jedesmals wenn die routine 
rs232 aufgerufen ist.

>Und deswegen übergibst du wert per Pointer. Was hat denn *wert und
buffer miteinander zu tun? Richtig: gar nichts.
Ich wollte ein wenig mit Pointeraddressierung arbeiten. Die funktioniert 
auch  sehr gut. ;-)

von Karl H. (kbuchegg)


Lesenswert?

Brain Aroon schrieb:

> Und was passiert in der Ausgaberoutine?
>
>   static int16_t alter_wert = -1;
>   char puffer[20];
>
>     if (alter_wert != *wert)
>     {
>         alter_wert = *wert;
>
>         ....
>
> Da der *wert niemals -1 wird, wird die if anweisung immer gültig sein
> und der Wert immer aktualisiert. Und zwar jedesmals wenn die routine
> rs232 aufgerufen ist.

Ja. Und was passiert wohl, wenn die Funktion bei 2 
hintereinanderfolgenden Aufrufen mit jeweils demselben Wert gefüttert 
wird?

int16_t  adc = 15;

   rs232( &adc );
   rs232( &adc );

beim ersten mal wird eine Ausgabe erzeugt, weil *wert ja nicht gleich -1 
ist und der Wert 15 innerhalb der Funktion in alter_wert gespeichert. 
Beim 2.ten Aufruf ist aber der wert identisch zu alter_wert und es 
erfolgt keine Ausgabe mehr.

Blöd nur, dass der Aufrufer dass nicht weiß und trotzdem die Kanalnummer 
hinschreibt.

int16_t  adc = 15;

   uart_puts( "1" );
   rs232( &adc );
   uart_puts( "2" );
   rs232( &adc );
   uart_puts( "3" );

Die Ausgabe wird sein

1. Channel = 15 mV ....
23

weil nach der Ausgabe der "2" sich die rs232 nicht bemüssigt sieht die 
15 noch einmal auszugeben. Und daher hängt sich die Ausgabe der "3" 
einfach an die "2" drann.

Studier doch mal deine 'seltsamen' Kanalnummern

0
1
234
50
1
23
4
5
....

die Ziffern sind von links nach rechts wunderbar aufsteigend und nach 
einer 5 kommt eine 0

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.