Forum: Mikrocontroller und Digitale Elektronik ATmega8 - Dauer einer ADC-Messung zu lange


von Max K. (achulio)


Angehängte Dateien:

Lesenswert?

Hallo Forum,
ich tüftle seit mehreren Tagen an diesem Problem: Ich will mit einem 
ATMega8 die Amplitude eines Signals im Bereich 1kHz-3kHz loggen. Der 
Atmega läuft mit 3,686411MHz, der ADC läuft im free running mode mit 
prescaler 32. Also:
3686411 / 32 = 115kHz ADC-Takt
Da eine Messung im ADC free running mode 13 ADC-Zyklen benötigt, komme 
ich theoretisch auf eine Geschwindigkeit von:
115kHZ / 13 = 8,8kHz Datentakt im ADC-Interrupt

Ich sende alle 50ms das Maximum der in der letzten Periode gemessenen 
Werte UND die Gesamtzahl der gemessenen Werte in der 50 ms Periode via 
UART. Dabei fiel mir auf, dass:
>> nur 105 ADC-Werte in 50 ms erfasst werden.
Theoretisch sollten das doch:
>> 8,8kHz * 0.05s = 440
also 440 ADC Werte pro 50ms sein. Habe ich einen Denkfehler oder einen 
Fehler im Code? Mein Programmcode ist untenstehend:


#define F_CPU 3686411UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <avr/interrupt.h>



void USART_vInit(void){
  UCSRB |= (1<<TXEN)|(1<<RXEN);
  UCSRC |= (1<<URSEL)|(1 << UCSZ1)|(1 << UCSZ0);   // Asynchron 8N1

  UBRRH = 0x00;
  UBRRL = 0x17;
}


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


void uart_puts (char *s)
{
    while (*s)
    {   /* so lange *s != '\0' also ungleich dem "String-Endezeichen" */
        uart_putc(*s);
        s++;
    }
}



char s[15];
volatile uint16_t max_adc_value = 0;  // Maximal-Wert des ADC innerhalb 
eines send-zyklus
volatile uint8_t ADC_Counter = 0;        // Über wie viele Messungen 
wird das max ermittelt?

int main(void)
{
          // Initialise USART
    USART_vInit();

    // Set up 8bit Timer2 for Data send interval
      TCCR2 |= (1<<CS20)|(1<<CS21)|(1<<CS22);    // prescaler 1024
    TCCR2 |= (1<<WGM21);        // Activate Mode3: CTC on compare match 
with OCR2
    OCR2 = 180;              // 72:20ms; 180:50ms; 360:100ms;
    TIMSK |= (1<<OCIE2);        // enable timer2 compare match interrupt

    // Set up ADC
    ADCSRA |= (1<<ADPS2) | (1<<ADPS0);   // ADC prescaler 32
    ADMUX |= (1<<REFS0);        // reference is 5V VCC
    ADMUX &= ~((1<<MUX0) | (1<<MUX1) | (1<<MUX2) | (1<<MUX3));  // ADC 
Kanal 0
    ADCSRA |= (1<<ADFR);        // ADC free running mode
    ADCSRA |= (1<<ADEN);          // enable ADC
    ADCSRA |= (1<<ADIE);        // enable ADC interrupt
    ADCSRA |= (1<<ADSC);        // start ADC FR measurements

    sei();          // enable global interrupts

  while(1){      // loop forever
  }

return 0;
}


// Interrupt on ADC conversion complete
ISR(ADC_vect)
{
  // Es werden nur max-Werte des ADC gespeichert!
  if (max_adc_value<ADCW) (max_adc_value=ADCW);
  ADC_Counter++;
}


// Interrupt on timer0 overflow - send data
ISR(TIMER2_COMP_vect)                          // every 50ms
{
  //top-value of max_adc_value can only be 1023
  if (max_adc_value>1023) (max_adc_value=1023);

  //uart_puts("$1;1;;"); //$ + Channel No + Status No + No Timestamp
  uart_puts( itoa( ADC_Counter, s, 10 ) );
  uart_puts(";");
  uart_puts( itoa( max_adc_value, s, 10 ) );
  uart_puts(";0\r\n");

  //set counters back to 0;
  max_adc_value = 0;
  ADC_Counter = 0;
}

von Frank L. (franklink)


Lesenswert?

Hallo,
grundsätzlich, nimm als erstes die UART - Ausgabe aus dem 
Timerinterrupt.

Beim AVR werden die Interrupts seriell ausgeführt. D.h. solange der 
TIMER_2_COMP_VECT läuft, wird Dein ADC nicht messen.

Besser ist es, im Timer2 nur eine Variable zu incrementieren und in der 
Hauptschleife in Abhänigkeit davon die UART-Ausgabe zu machen.

Dann sollte es auch mit den Messwerten klapppen.

Gruß
Frank

von Falk B. (falk)


Lesenswert?

Siehe Interrupt

von Max K. (achulio)


Lesenswert?

Hallo ihr beiden -
ihr habt natürlich recht! Die UART-Routine habe ich in die main() 
ausgelagert - trotzdem hat sich an der Anzahl der Messungen nichts 
geändert - es bleiben immer noch 105 in 50ms :(
Hier der aktuelle Code:

#define F_CPU 3686411UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include <stdbool.h>



void USART_vInit(void){
  UCSRB |= (1<<TXEN)|(1<<RXEN);
  UCSRC |= (1<<URSEL)|(1 << UCSZ1)|(1 << UCSZ0);   // Asynchron 8N0

  UBRRH = 0x00;
  UBRRL = 0x17;
}


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


void uart_puts (char *s)
{
    while (*s)
    {   /* so lange *s != '\0' also ungleich dem "String-Endezeichen" */
        uart_putc(*s);
        s++;
    }
}

char s[15];
volatile uint16_t max_adc_value = 0;  // Maximal-Wert des ADC innerhalb 
eines send-zyklus
volatile uint8_t ADC_Counter = 0;    // Über wie viele Messungen wird 
das max ermittelt?
volatile bool Send_UART = false;    // Kann via UART gesendet werden?

int main(void)
{
           // Initialise USART
    USART_vInit();

    // Set up 8bit Timer2 for Data send interval
      TCCR2 |= (1<<CS20)|(1<<CS21)|(1<<CS22);    // prescaler 1024
    TCCR2 |= (1<<WGM21);        // Activate Mode3: CTC on compare match 
with OCR2
    OCR2 = 180;              // 72:20ms; 180:50ms; 360:100ms;
    TIMSK |= (1<<OCIE2);        // enable timer2 compare match interrupt

    // Set up ADC
    ADCSRA |= (1<<ADPS2) | (1<<ADPS0);   // ADC prescaler 32
    ADMUX |= (1<<REFS0);        // reference is 5V VCC
    ADMUX &= ~((1<<MUX0) | (1<<MUX1) | (1<<MUX2) | (1<<MUX3));  // ADC 
Kanal 0
    ADCSRA |= (1<<ADFR);        // ADC free running mode
    ADCSRA |= (1<<ADEN);          // enable ADC
    ADCSRA |= (1<<ADIE);        // enable ADC interrupt
    ADCSRA |= (1<<ADSC);        // start ADC FR measurements

    sei();          // enable global interrupts

  while(1){      // loop forever

  if (Send_UART) {
    //top-value of max_adc_value can only be 1023
    if (max_adc_value>1023) (max_adc_value=1023);

    //uart_puts("$1;1;;"); //$ + Channel No + Status No + No Timestamp
    uart_puts( itoa( ADC_Counter, s, 10 ) );
    uart_puts(";");
    uart_puts( itoa( max_adc_value, s, 10 ) );
    uart_puts(";0\r\n");

    //set counters back to 0;
    max_adc_value = 0;
    ADC_Counter = 0;
    Send_UART = false;
  }

  }

return 0;
}


// Interrupt on ADC conversion complete
ISR(ADC_vect)
{
  // Es werden nur max-Werte des ADC gespeichert!
  if (max_adc_value<ADCW) (max_adc_value=ADCW);
  ADC_Counter++;
}


// Interrupt on timer0 overflow - send data
ISR(TIMER2_COMP_vect)                          // every 50ms
{
  Send_UART = true;
}

von Frank L. (franklink)


Lesenswert?

Hallo,
kann es sein, dass Dein Controller nur mit 1 MHz läuft? Das würd in etwa 
auch das Verhältnis von 105 zu 440 erwarteten erklären.

Prüf mal Deine Fuses und aktiviere den externen Quarz.

Gruß
Frank

von Max K. (achulio)


Lesenswert?

Hi Frank, merci.
Die Fuses sind korrekt auf den externen Quartz gesetzt.
Beim experimentieren fiel mir auf: desto weniger Zeichen ich mit dem 
UART sende, desto höher wird mein Wert ADC_Counter (sprich: desto mehr 
ADC-Interrupts wurden ausgelöst) >> ???
Die UART-Anweisungen stehen doch schon in der main()... und sollten 
daher keinen Einfluss haben.

von Falk B. (falk)


Lesenswert?

@  Max K. (achulio)

>ausgelagert - trotzdem hat sich an der Anzahl der Messungen nichts
>geändert - es bleiben immer noch 105 in 50ms :(

Na dann rechne mal.

Du sendest mit schnarchlangsamen 9600 Baud. Macht 960 Zeichen/s.
Pro Messungen sendest du bis zu 18 Zeichen. Macht max. 53 Messwerte/s, 
die du übertragen kannst.
Mit 115k2 Baud schaffst du auch nur max. 636, zu wenig.
Was du brauchst ist ein FIFO, in welches du die Messwerte erstmal 
reinschreibst. Dann kannst du sie beliebig langsam übertragen.
1
  while(1){      // loop forever
2
3
  if (Send_UART) {
4
    //top-value of max_adc_value can only be 1023
5
    if (max_adc_value>1023) (max_adc_value=1023);
6
7
    //uart_puts("$1;1;;"); //$ + Channel No + Status No + No Timestamp
8
    uart_puts( itoa( ADC_Counter, s, 10 ) );
9
    uart_puts(";");
10
    uart_puts( itoa( max_adc_value, s, 10 ) );
11
    uart_puts(";0\r\n");
12
13
    //set counters back to 0;
14
    max_adc_value = 0;
15
    ADC_Counter = 0;
16
    Send_UART = false;
17
  }

Das Send_UART = false; sollte an den Anfang des IF Blocks. Denn was 
passiert, wenn dein Block recht lange dauert und zwischendurch die 
Variable im Interrupt neu gesetzt wird? Du verschluckst dann Messungen.
Ausserdem sind deine Zugriff auf max_adc_value und ADC_Counter NICHT 
atomar. Das geht schief, siehe Artikel Interrupt.
1
// Interrupt on ADC conversion complete
2
ISR(ADC_vect)
3
{
4
  // Es werden nur max-Werte des ADC gespeichert!
5
  if (max_adc_value<ADCW) (max_adc_value=ADCW);
6
  ADC_Counter++;
7
}
8
9
10
// Interrupt on timer0 overflow - send data
11
ISR(TIMER2_COMP_vect)                          // every 50ms
12
{
13
  Send_UART = true;
14
}

Also du willst nur 1 mal pro 50 ms das Maximum senden? Das hast du oben 
aber anders geschrieben!!!
Dann reichen 9600 Baud.

MfG
Falk

P S Lies mal was über Netiquette. Längere Quelltexte postet man als 
Anhang.

von Max K. (achulio)


Lesenswert?

Habe meinen (DUMMEN) Fehler gefunden: die Variable ADC_Counter muss 
natürlich als uint16_t initialisiert werden, nicht als uint8_t... *in 
die Ecke und Schäm*

dennoch stelle ich nun fest, dass die Anzahl der ADC-Interrupts von der 
Menge der gesendeten UART-Daten abhängt:
bei 20 gesendeten Zeichen 344 ADC-Interrupts
bei 10 gesendeten Zeichen 398 ADC Interrupts

Wie kann man sich das erklären?

von Falk B. (falk)


Lesenswert?

@  Max K. (achulio)

>Habe meinen (DUMMEN) Fehler gefunden: die Variable ADC_Counter muss
>natürlich als uint16_t initialisiert werden, nicht als uint8_t... *in
>die Ecke und Schäm*

Das ist keine Initialisierung, das ist eine Deklaration.

>dennoch stelle ich nun fest, dass die Anzahl der ADC-Interrupts von der
>Menge der gesendeten UART-Daten abhängt:
>bei 20 gesendeten Zeichen 344 ADC-Interrupts
>bei 10 gesendeten Zeichen 398 ADC Interrupts

>Wie kann man sich das erklären?

Siehe mein posting oben.

Das Send_UART = false; sollte an den Anfang des IF Blocks.

MFG
Falk

von Max K. (achulio)


Lesenswert?

Hi Falk,
Danke für deine Hilfe. Wie du sicher erkannt hast, bin ich 
Quereinsteiger und meine AVR und C-Kenntnisse befinden sich noch im 
Aufbau :)

Habe ich wohl mißverständlich geschrieben - in der Tat will ich nur alle 
50ms den Maximalwert übertragen. 960Zeichen/s reichen dafür gerade so 
aus.

Ich werde deine Tips befolgen, muss mich jedoch erst in das Phänomen 
"atomar" einlesen - davon weiss ich bis jetzt noch nichts.

Danke und Gruß

von Jens A. (Gast)


Lesenswert?

Das würde mich auch interessieren - was bedeutet "atomar"?

von Karl H. (kbuchegg)


Lesenswert?

Jens A. schrieb:
> Das würde mich auch interessieren - was bedeutet "atomar"?

'unteilbar'

einen uint8_t kann ein AVR in einem Rutsch zuweisen. Das eine Byte 
landet in der empfangenden Variable genau so, wie es aus dem Sender 
kommt. In diesem Sinne ist ein einzelnes Byte 'unteilbar' und die 
Kopieraktion sicher.

(Wenn zwischen lesen des Bytes und schreiben des Bytes in Interrupt 
reinknallt, spielt das auch keine Rolle, das Byte selbst ist unteilbar)


Einen uint16_t kann ein AVR aber nicht in einem Rutsch schreiben u. 
lesen. Also macht er es als 2 Byte-Operationen. Zuerst wird das eine 
Byte umkopiert und dann das andere. Besonders unangenehm ist es dann 
natürlich, wenn das eine Byte schon umkopiert wurde und dann genau an 
dieser Stelle ein Interrupt zuschlägt, der die Ausgangsvariable 
verändert und erst danach das zweite Byte umkopiert wird. In der 
Empfängervariable ist dann nach Abschluss der Kopieraktion das eine Byte 
vom vorhergehenden Zustand und das andere vom neuen Zustand der 
Sendevariable.

Die Situation ist vergleichbar mit einem mittelalterlichen Mönch, der 
ein Manuskript bcuhstabenweise abmalt. Er malt Buchstabe für Buchstabe 
und in der Mittagspause kommt der Abt und legt ihm ein anderes Buch zum 
abmalen hin. Der Mönch merkt das nicht und malt weiter Buchstabe für 
Buchstabe aus dem neuen Buch ab. Und so hat er dann ein Ergebnis, bei 
dem sich mittendrinn plötzlich der Text geändert hat. Die Kopie ist 
weder eine Kopie der ersten Vorlage, noch ist es eine Kopie der zweiten 
Vorlage sondern eine Mischung aus beidem.

von Stefan B. (stefan) Benutzerseite


Lesenswert?


von Falk B. (falk)


Lesenswert?

@  Jens A. (Gast)

>Das würde mich auch interessieren - was bedeutet "atomar"?

Warum folgst du nicht den Links in meinem Posting?

von Falk B. (falk)


Lesenswert?

@  Karl heinz Buchegger (kbuchegg) (Moderator)

>> Das würde mich auch interessieren - was bedeutet "atomar"?

>'unteilbar'

An dir ist ein Grundschullehrer verloren gegangen ;-)

von Troll B. (blaubeer)


Lesenswert?

Immer noch besser als ein Oberle(h/e)rer.

von Max K. (achulio)


Angehängte Dateien:

Lesenswert?

Hallo alle
(insb. Karl heinz B., klasse Erklärung :) )
vielen Dank für eure Tips, meine main()-Routine beinhaltet nun alle 
tips:
1
if (Send_UART) {
2
    
3
    Send_UART = false;
4
    
5
    //top-value of max_adc_value can only be 1023
6
    if (max_adc_value>1023) (max_adc_value=1023);
7
  
8
    uart_puts("$1;1;;"); //$ + Channel No + Status No + No Timestamp
9
    uart_puts( itoa( ADC_Counter, s, 10 ) );
10
    uart_puts( itoa( max_adc_value, s, 10 ) );
11
    uart_puts(";0\r\n");
12
    
13
    //set counters back to 0;
14
    cli();
15
    max_adc_value = 0;
16
    ADC_Counter = 0;
17
    sei();        
18
  }

Ich "verschlucke" zwar dadurch (ich sende 14 Zeichen via UART) 80 
ADC-Messungen (komme nicht auf 440, sondern 360), aber meine Ergebnisse 
sind OK.

Mich würde nun die Theorie dahinter interessieren. Wie kann ich die 
Dauer des UART-Sendevorgangs abschätzen? Ich sende pro byte 10 bit bei 
9600baud.

von Falk B. (falk)


Lesenswert?

@  Max K. (achulio)

>vielen Dank für eure Tips, meine main()-Routine beinhaltet nun alle
>tips:

Nöö, du greisft immer noch nciht atomar auf die Variabeln zu. Nämlich in 
deinen uart_puts Funktionen . . .
Besser so.
1
int16_t max_adc_value_tmp, ADC_Counter_tmp;
2
3
if (Send_UART) {
4
    
5
    Send_UART = false;
6
7
    //set counters back to 0;
8
    cli();
9
  max_adc_value_tmp = max_adc_value;
10
  ADC_Counter_tmp = ADC_Counter;
11
    max_adc_value = 0;
12
    ADC_Counter = 0;
13
    sei();        
14
15
    //top-value of max_adc_value can only be 1023
16
    if (max_adc_value_tmp>1023) (max_adc_value_tmp=1023);
17
  
18
    uart_puts("$1;1;;"); //$ + Channel No + Status No + No Timestamp
19
    uart_puts( itoa( ADC_Counter_tmp, s, 10 ) );
20
    uart_puts( itoa( max_adc_value_tmp, s, 10 ) );
21
    uart_puts(";0\r\n");
22
    
23
  }

>Ich "verschlucke" zwar dadurch (ich sende 14 Zeichen via UART) 80
>ADC-Messungen (komme nicht auf 440, sondern 360),

Das ist schlecht. Setz doch einfach mal die Baudrate hoch. Wenn du ein 
Oszi hast, kannst du am Anfnag des IF-Blocks ein Pin auf HIGH setzten 
und am Ende auf LOW, da kannst du messen, wie lange der braucht. Kann 
man aber auch einfach im Simulator machen. Richtige Frequenz dort 
einstellen.

> aber meine Ergebnisse sind OK.

Nicht wenn du weißt, dass du Daten verschluckst.

>Mich würde nun die Theorie dahinter interessieren. Wie kann ich die
>Dauer des UART-Sendevorgangs abschätzen? Ich sende pro byte 10 bit bei
>9600baud.

Rechnen? Das ist ja nun weiss Gott trivial. Siehe Rs232.

MfG
Falk

von Max K. (achulio)


Lesenswert?

Hi Falk,
habe mich nun eingelesen - wenn man weiß, dass die Sendelänge eines Bits 
bei RS232 indirekt proportional zur Baudrate ist, ist es tatsächlich 
trivial.
Danke für die Unterstützung!

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.