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;
}
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
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
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.
@ 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.
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?
@ 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
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ß
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.
@ Karl heinz Buchegger (kbuchegg) (Moderator)
>> Das würde mich auch interessieren - was bedeutet "atomar"?>'unteilbar'
An dir ist ein Grundschullehrer verloren gegangen ;-)
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.
@ 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.
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
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!