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 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; }
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ß
Das würde mich auch interessieren - was bedeutet "atomar"?
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.
http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Variablen_gr.C3.B6.C3.9Fer_1_Byte
@ Jens A. (Gast)
>Das würde mich auch interessieren - was bedeutet "atomar"?
Warum folgst du nicht den Links in meinem Posting?
@ 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.
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.