Forum: Mikrocontroller und Digitale Elektronik AVR: UART + ADC, Feinheiten und Fragen


von Michael K. (hummel030)


Angehängte Dateien:

Lesenswert?

Hallo Forum,

nach einigen Anfangsschwierigkeiten mit meiner Hard- und Software läuft 
mein Breadboard und meine AVR-Studio4 + Toolchain erstmal soweit das ich 
damit gut  arbeiten kann.
Ich habe mich jetzt durch diverse Tutorials (hier vor allem das AVR-GCC) 
und Bücher gewurstelt und habe 2 ADC-Kanäle am laufen die über UART an 
meinen PC gesendet werden.
Der Quelltext dazu ist oben als Anhang eingefügt. Da ich diesen Text zu 
Übungszwecken verwende habe ich die auskommentierten Bereiche drinnen 
gelassen und eingerückt.

Zu meinen Fragen:

1. Ist der Code optimierbar bzw. sind grobe Fehler drin die man als 
"erfahrener" C-Programmierer nicht machen sollte?

2.Wenn ich in der uart_init-Funktion (sehr weit oben) das Bit für den 
Free Running Modus (ADFR) im ADCSRA-Register setze funktioniert die 
Ausgabe nicht bzw. auf dem Ausgabeterminal (PUTTY) kommt nichts an. 
Warum?
Ich weiß das ich über die main-Funktion und die adc_read-Funktion immer 
eine einzelne Auswertung bekomme und da while(1) ist, diese immer wieder 
ausgeführt wird.
Wann brauchre ich den Free Running Modus und wie wird er eingesetzt?

3. Ist die Ausgabe beider Kanäle "gut" gelöst oder gibt es eine 
elegantere Methode?

4. Im Terminal-Programm soll in der ersten Zeile ADC0: stehen und 
dahinter ändert sich der gelesene Wert. Genauso soll eine Zeile darunter 
ADC1: stehen und der gelesene Wert ausgegeben werden. Also das 
Terminal-Fenster soll schwarz sein und oben stehen nur diese zwei 
Zeilen. Jetzt "scrollt" es unablässig runter. Gibt es dazu Beispiele?


Die Hummel

von Falk B. (falk)


Lesenswert?

Michael K. schrieb:

> Der Quelltext dazu ist oben als Anhang eingefügt.

Und warum hängst du nicht einfach das Original an? Das hätt u.a. die 
korrekte Endung .c und könnte damit hier live mit Syntaxhighlightig 
dargestellt werden.

> 1. Ist der Code optimierbar bzw.

Sicher, aber das ist in deiner Lage nebensächlich. Erstmal sollte dein 
Code logisch korrekt und gut lesbar sein.

Strukturierte Programmierung auf Mikrocontrollern

https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Prinzipien_der_Optimierung

"Verfrühte Optimierung ist die Wurzel allen Übels!"

> sind grobe Fehler drin die man als
> "erfahrener" C-Programmierer nicht machen sollte?


>#define CLOCK 8000000L        //Makro

Das Ding heißt eher F_CPU, denn das wird auch von anderen Includes 
benutzt.



>void adc_init(void)
>{
>  uint16_t x;

>  ADMUX |= (1<<REFS0);        //AVCC als Referenz

Naja, bei einem Init schreibt man meist das gesamte Register komplett. 
Denn wenn du nur einzelne Bits setzt, ist das Ergebnis vom aktuellen 
Inhalt abhängig.
Wenn man, warum auch immer, das init an mehreren Stellen im Programm 
braucht, gibt das komische Nebeneffekte.

>  ADCSRA |= (1<<ADEN);        // ADC aktivieren
>  ADCSRA |= (1<<ADPS2) | (1<<ADPS0);  // Frequenzvorteiler (auf 250khz da 
8MHz/32= 250KHz)
    //ADMUX |= (0<<MUX3) | (0<<MUX2)| (0<<MUX1)| (1<<MUX0); 
//ADC-Kanal, in main!!!
    //ADCSRA |= (1<<ADFR);        //Free Running Mode ein,
>  ADCSRA |= (1<<ADSC);                // "Dummy-Readout", eine ADC-Wandlung
    //ADMUX = (1<<ADLAR);        //Ergebnis linksbündig, d.h. (8 Bit in 
ADCH)

Dito.


>void uart_send_char()
>{
>    if(UCSRA & (1<<UDRE))    // Senden, wenn UDR frei ist
>    {
>    UDR = 'x';           // schreibt das Zeichen x auf die Schnittstelle
>    }
>}

Und was machst du, wenn UDR nicht frei ist?


>void uart_send_var() //Variable senden, benötigt uart_putc
>{
>   char c;

Da fehlt der Parameter . . .





>void uart_send_int(uint16_t value)
>{
>  char s[7];
>  uint16_t i = value;

Das mit den Funktionsparametern und lokalen Variablen muss du nochmal 
nachlesen. SO ist es Unsinn!


>void uart_send_double()
>{
>   char s[8]; // Pufferspeicher ausreichend groß, evtl. Vorzeichen + width + 
Endezeichen:
 >  float f = -12.345;

Hier das Gleiche.

>uint16_t adc_read( uint16_t channel )
>{
>  uint16_t x;

>  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
>  ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
>  while (ADCSRA & (1<<ADSC) )   // auf Abschluss der Konvertierung warten
>  {
 >   }

>  x = ADCL;
>  x += (ADCH<<8);

Das geht einfacher und besser.

    x = ADC;


> Wann brauchre ich den Free Running Modus und wie wird er eingesetzt?

Nur dann, wenn man relativ schnell und ohne Pause ADC-Daten per 
Interrupt auslesen will.

> 3. Ist die Ausgabe beider Kanäle "gut" gelöst oder gibt es eine
> elegantere Methode?

Für's Erste OK.

> 4. Im Terminal-Programm soll in der ersten Zeile ADC0: stehen und
> dahinter ändert sich der gelesene Wert. Genauso soll eine Zeile darunter
> ADC1: stehen und der gelesene Wert ausgegeben werden. Also das
> Terminal-Fenster soll schwarz sein und oben stehen nur diese zwei
> Zeilen.

Dafür braucht man Steuerbefehle. Such mal nach VT100 Codes. Dann kannst 
du den virtuellen Cursor wieder hoch setzen und neu schreiben, fast wie 
auf einem LCD.

> Jetzt "scrollt" es unablässig runter.

Logisch.

von Michael K. (hummel030)


Angehängte Dateien:

Lesenswert?

Danke für die ausführliche Antwort.


Falk B. schrieb:
> Und warum hängst du nicht einfach das Original an?

s. Anhang (Quelltext aktualisiert)


> "Verfrühte Optimierung ist die Wurzel allen Übels!"

Ist gemerkt.


> Naja, bei einem Init schreibt man meist das gesamte Register komplett.

Ich habe es erstmal so gemacht, damit ich ich einen besseren Überblick 
habe und so sagen kann: Das Bit im RegisterX CHECK, das Bit CHECK usw. 
In Zukunft wenn das besser sitzt, werde ich das gesamte Register mit 
einem Befehl schreiben.


> Das Ding heißt eher F_CPU,...
> Und was machst du, wenn UDR nicht frei ist?...
> Da fehlt der Parameter...
> Hier das Gleiche.
> Das mit den Funktionsparametern und lokalen Variablen muss du nochmal
> nachlesen.
> Das geht einfacher und besser...

Ich habs jetzt nach deinen Anregungen nochmal umgeschrieben. Siehe 
Anhang.


> Nur dann, wenn man relativ schnell und ohne Pause ADC-Daten per
> Interrupt auslesen will.

Da ich mich mit Interupts noch nicht auseinandergesetzt habe merke ich 
mir das auch erstmal vor.


> Dafür braucht man Steuerbefehle. Such mal nach VT100 Codes.

Ebenfalls vorgemerkt.


Jetzt noch zwei allgemeinere Fragen. Ich möchte in ferner Zukunft 
Regelungsaufgaben (PID-Regler) mit dem µC lösen bin aber über das 
weitere Vorgehen ein bisschen ratlos.

1. Sollte ich ersteinmal weiterwursteln und mir neue Sachen wie z.B. die 
Interupts oder Timer aneignen oder jetzt über das Gelernte nachdenken 
und weiter verbessern?

2. Was sind "Must Haves" um Regelungsaufgaben zu lösen?
Zum Beispiel habe ich das Senden vom PC zum µC per UART erstmal nicht 
beachtet, da ich dafür zum jetzigen Zeitpunkt keine Verwendung habe.



Die Hummel

(Edit Rechtschreibung)

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Michael K. schrieb:

>> Und warum hängst du nicht einfach das Original an?
>
> s. Anhang (Quelltext aktualisiert)

Hmm.

Dein void uart_send_char() ist immer noch genau so sinnlos. Wenn der 
UART gerade sendet, landet dein neues Zeichen im Nirvana. Wozu brauchst 
du diese Funktion?

> Ich habs jetzt nach deinen Anregungen nochmal umgeschrieben. Siehe
> Anhang.

Was soll das hier?
1
void uart_send_var() //Variable senden, benötigt uart_putc
2
{
3
   char c;
4
5
   for (uint8_t i=0; i<=9; ++i) 
6
   {
7
      c = i + '0';    
8
      uart_putc( c );  // verkuerzt: uart_putc( i + '0' );
9
   }
10
}

Deine Funktion oben macht immer das Gleiche, nämlich die Zahlen 0-9 als 
ASCII an den USART senden. Ist das deine Absicht? Der Kommentar klingt 
anders.

Der Rest ist erstmal OK.

> 1. Sollte ich ersteinmal weiterwursteln

Nein. Du solltest möglichst systematisch lernen. Das heißt, neue Dinge 
lernen und üben durch Anwenden.

> und mir neue Sachen wie z.B. die
> Interupts oder Timer aneignen

Ja.

> oder jetzt über das Gelernte nachdenken
> und weiter verbessern?

Was hast du denn bisher gelernt? Ein paar Funktionsaufrufe und 
for-Schleifen? Damit kommt man nicht allzu weit, vor allem auf dem 
Mikrocontroller. Man muss schrittweise die Peripherie in Betrieb nehmen. 
Timer und Interrupts sind dafür ganz gut geeignet. Ist auch gar 
nicht so schwer auf dem AVR.

> 2. Was sind "Must Haves" um Regelungsaufgaben zu lösen?

E eingabe, meist per ADC, egal ob intern oder extern
V erarbeitung, das sind die PID-Formeln
A Ausgabe, meist per PWM oder über SPI oder I2C an einen DAC.

Das Ganze im festen Zeitraster per Timer.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Michael K. schrieb:
> Wann brauchre ich den Free Running Modus und wie wird er eingesetzt?

Ich benutze den, um unabhängig vom Hauptprogramm im Hintergrund und per 
ISR Kanäle des ADC zu lesen. Die ISR liest das Resultat, speichert es in 
eine globale Variable, schaltet evtl. auf einen neuen Kanal, startet den 
ADC wieder und beendet dann die ISR. Der ADC wird nur einmal während des 
Inits gestartet und ist dann sozusagen 'Fire and Forget'.

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.