Hallo, mich beschäftigt schon eine Weile ein Problem, bei dem ich ohne Hilfe wohl nie den Fehler finde. Ich habe an einen ATMEGA 16 unter anderem 6 Potis angeschlossen, um mit Hilfe des A/D Wandlers Analogwerte zur weiteren Verarbeitung in einem Programm vorzugeben (Endstellungen für Servos). Die Hardware ist richtig aufgebaut und die Kanäle arbeiten einzeln getestet auch richtig. Das Auslesen zweier Kanäle geht auch, wenn ich die auskommentierte if – else Konstruktion verwende. Für das Auslesen aller 6 Kanäle in der Interruptroutine würde es aber sehr unübersichtlich. Deshalb habe ich eine case Auswahl zur Umschaltung eingesetzt. Aber diese tut einfach nicht. Ich bin mir sicher, dass ich alle anderen Fehlerquellen ausgeschlossen habe und es nur daran liegen kann. Einen Fehler kann ich aber nicht entdecken. Langsam frage ich mich, ob es überhaupt möglich ist, in der Interruptroutine eine case Auswahl einzusetzen. Allerdings habe ich auch nirgends einen Hinweis gefunden, dass es da Einschränkungen geben könnte. Mittlerweile bin ich ratlos. Es wäre nett, wenn Ihr mal einen Blick auf den Code werfen könntet. Vielleicht übersehe ich ja die ganze Zeit etwas.
Als C Datei anhängen oder als Code einbinden. Ansonsten geht das Highlighting nicht ;-)
1 | #include <avr/io.h> |
2 | #include <avr/interrupt.h> |
3 | #include "global.h" |
4 | #include <stdbool.h> // bool einbinden |
5 | |
6 | |
7 | //AD-Wandler
|
8 | |
9 | static unsigned char I=1; //Schleifenzähler |
10 | static unsigned char A=0; //Merker für Kanalumschaltung |
11 | static unsigned int ADCwert=0; //Wert AD-Wandler |
12 | |
13 | |
14 | |
15 | ISR (ADC_vect) //Interruptroutine für AD Wandlung, wird aufgerufen, wenn ein neues Ergebnis vom AD-Wandler vorliegt |
16 | {
|
17 | |
18 | unsigned int schmier=0; //Schmiervariable |
19 | |
20 | //AD-Wandlung
|
21 | |
22 | //schmier = ADCH; //die 8 höchsten Bits des Ergebnisses abholen (0...255)
|
23 | schmier = ADCL; |
24 | schmier += (ADCH<<8); |
25 | |
26 | ADCwert=ADCwert+schmier; //Aufsummieren |
27 | I=I+1; |
28 | |
29 | if (I == 20) |
30 | {
|
31 | ADCwert=ADCwert/I; //Mittelwert berechnen |
32 | I=1; |
33 | |
34 | //Wert übergeben
|
35 | |
36 | uiADC[A] = ADCwert; //Wert an Übergabefeld |
37 | ADCwert=0; |
38 | |
39 | //Kanalwechsel
|
40 | |
41 | switch (A) |
42 | {
|
43 | case 0: |
44 | |
45 | ADMUX |=(1<<MUX0); //MUX0 setzen für Kanal 1 |
46 | A=1; //Kanalmerker auf 1 |
47 | break; |
48 | |
49 | case 1: |
50 | |
51 | ADMUX &=~(1<<MUX0); //MUX0 rücksetzen und |
52 | ADMUX |=(1<<MUX1); //MUX1 setzen für Kanal 2 |
53 | A=2; //Kanalmerker auf 2 setzen |
54 | break; |
55 | |
56 | case 2: |
57 | |
58 | ADMUX |=(1<<MUX0); //MUX0 zusätzlich zu MUX1 setzen für Kanal 2 |
59 | A=3; //Kanalmerker auf 3 |
60 | break; |
61 | |
62 | case 3: |
63 | |
64 | ADMUX &=~(1<<MUX0); ADMUX &=~(MUX1); //MUX0 und MUX1 rücksetzen und |
65 | ADMUX |=(1<<MUX2); //MUX2 setzen für Kanal 4 |
66 | A=4; //Kanalmerker auf 4 |
67 | break; |
68 | |
69 | case 4: |
70 | |
71 | ADMUX |=(1<<MUX0); //MUX0 zusätzlich zu MUX2 setzen für Kanal 5 |
72 | A=5; //Kanalmerker auf 5 |
73 | break; |
74 | |
75 | case 5: |
76 | |
77 | ADMUX &=~(1<<MUX0); ADMUX &=~(MUX2); //MUX0 und MUX2 rücksetzen für Kanal 0 |
78 | A=0; //Kanalmerker auf 0 |
79 | break; |
80 | |
81 | }
|
82 | |
83 | /*
|
84 | if (A==0) //wenn Kanal 0
|
85 | {
|
86 | ADMUX |=(1<<MUX0); //MUX0 setzen für Kanal 1
|
87 | A=1; //Kanalmerker auf 1
|
88 | }
|
89 |
|
90 | else //wenn Kanal 1
|
91 | {
|
92 | ADMUX &=~(1<<MUX0); //MUX0 rücksetzen für Kanal 0
|
93 | A=0; //Kanalmerker auf 0
|
94 | }
|
95 | */
|
96 | }
|
97 | |
98 | //ADC neu starten
|
99 | |
100 | ADCSRA |=(1<<ADSC); |
101 | |
102 | }
|
Danke für den Tipp. Ich wusste nicht, dass das geht. Ich schreibe zu selten.
Jenny Lo. schrieb: > Deshalb habe ich > eine case Auswahl zur Umschaltung eingesetz Kann man machen, würde ich aber nicht. Jenny Lo. schrieb: > Langsam frage ich mich, ob es überhaupt möglich > ist, in der Interruptroutine eine case Auswahl einzusetzen Doch, geht. Wieso auch nicht. Ich kann es nicht ausprobieren hier, aber ich hätte es mal so wie im Anhang probiert.
>uiADC[adcCh] Das stimmt natürlich nicht. >uiADC[adcCh%8] So ist es richtiger.
Wenn man in einer ISR globale Variable verwendet, müssen die mit volatile versehen werden. Wenn du außerhalb auf adcwert zugreifst, muss es mit von cli und sei umgeben werden, sonst ist der Wert manchmal falsch.
:
Bearbeitet durch User
Ich nehme direkt ADMUX im switch-case Konstrukt, mach mir allerdings vorher ein paar #defines, um das ganze übersichlich zu halten:
1 | //in 'setup.h'
|
2 | //! The ADC channel where the analog speed reference is connected.
|
3 | #define ADC_CHANNEL_SPEED_REF 3
|
4 | //! The ADC channel where the motor current measuring amp is connected.
|
5 | #define ADC_CHANNEL_CURRENT 4
|
6 | //! The spare ADC channel ( e.g. heatsink temperatur or battery volts )
|
7 | #define ADC_CHANNEL_TEMP 5
|
8 | //! ADC clock prescaler 2 setting.
|
9 | #define ADC_PRESCALER_2 ((0 << ADPS2) | (0 << ADPS1) | (1 << ADPS0))
|
10 | //! ADC clock prescaler 4 setting.
|
11 | #define ADC_PRESCALER_4 ((0 << ADPS2) | (1 << ADPS1) | (0 << ADPS0))
|
12 | |
13 | //! ADC clock prescaler 8 setting.
|
14 | #define ADC_PRESCALER_8 ((0 << ADPS2) | (1 << ADPS1) | (1 << ADPS0))
|
15 | |
16 | //! ADC clock prescaler 16 setting.
|
17 | #define ADC_PRESCALER_16 ((1 << ADPS2) | (0 << ADPS1) | (0 << ADPS0))
|
18 | //! ADC clock prescaler 64 setting.
|
19 | #define ADC_PRESCALER_64 ((1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0))
|
20 | //! ADC clock prescaler used in this application.
|
21 | #define ADC_PRESCALER ADC_PRESCALER_64
|
22 | |
23 | //! ADC internal voltage reference channel value.
|
24 | #define ADC_REFERENCE_VOLTAGE_INTERNAL ((1 << REFS1) | (1 << REFS0))
|
25 | |
26 | //! ADC VCC voltage reference channel value.
|
27 | #define ADC_REFERENCE_VOLTAGE_VCC ((0 << REFS1) | (1 << REFS0))
|
28 | |
29 | //! ADC AREF voltage reference channel value.
|
30 | #define ADC_REFERENCE_VOLTAGE_AREF ((0 << REFS1) | (0 << REFS0))
|
31 | |
32 | /*! ADC voltage reference used in this application.
|
33 | *
|
34 | * \todo Select ADC voltage reference channel.
|
35 | */
|
36 | #define ADC_REFERENCE_VOLTAGE ADC_REFERENCE_VOLTAGE_VCC
|
37 | |
38 | //! ADMUX settings for analog speed reference measurement.
|
39 | #define ADMUX_SPEED_REF (ADC_REFERENCE_VOLTAGE | (1 << ADLAR) | (ADC_CHANNEL_SPEED_REF << MUX0))
|
40 | |
41 | //! ADMUX settings for motor current measurement.
|
42 | #define ADMUX_CURRENT (ADC_REFERENCE_VOLTAGE | (1 << ADLAR) | (ADC_CHANNEL_CURRENT << MUX0))
|
43 | |
44 | //! ADMUX settings for spare ad channel (heatsink temp).
|
45 | #define ADMUX_TEMP (ADC_REFERENCE_VOLTAGE | (1 << ADLAR) | (ADC_CHANNEL_TEMP << MUX0))
|
46 | |
47 | // in main.c
|
48 | // globals
|
49 | volatile uint8_t current,speedInput,temperature; |
50 | ISR(ADC_vect) |
51 | {
|
52 | switch (ADMUX) |
53 | {
|
54 | case ADMUX_SPEED_REF: |
55 | speedInput = (speedInput + ADCH) >> 1; // do some averaging |
56 | ADMUX = ADMUX_CURRENT; |
57 | break; |
58 | case ADMUX_CURRENT: |
59 | current = ADCH; |
60 | ADMUX = ADMUX_TEMP; break; |
61 | case ADMUX_TEMP: |
62 | temperature = ADCH; |
63 | ADMUX = ADMUX_SPEED_REF; |
64 | if (temperature > MOTOR_MAX_TEMPERATURE) error |= ERR_OVERHEAT; break; |
65 | default:
|
66 | //This is probably an error and should be handled.
|
67 | ADMUX = ADMUX_SPEED_REF; |
68 | break; |
69 | }
|
70 | |
71 | // restart the ADC
|
72 | ADCSRA = (1 << ADEN) | (1 << ADSC) | (0 << ADATE) | (1 << ADIF) | (1 << ADIE) | ADC_PRESCALER; |
73 | |
74 | }
|
:
Bearbeitet durch User
1 | //schmier = ADCH; //die 8 höchsten Bits des Ergebnisses abholen (0...255)
|
2 | schmier = ADCL; |
3 | schmier += (ADCH<<8); |
4 | |
5 | ADCwert=ADCwert+schmier; //Aufsummieren |
6 | I=I+1; |
Das ist, glaube ich, nicht so gewollt. 0. Du rechnest mit ADCH, holst das Ergebnis aber nicht ab. 1. Aus dem 'nicht gezeigten Code-Teil' erahne ich: Du brauchst nur eine 8-Bit-Wandlung. Dieses Ergebnis schiebst du um 8 Bit nach links und addierst es zu 'schmier'. Wenn's schlecht läuft, machst du das nur einmal! (Hinweis: 255 * 256 = 65280) 2. Es reicht, ADCH zu lesen. (Unter der Annahme von Punkt 1.)
1 | //AD-Wandler
|
2 | |
3 | ISR (ADC_vect) |
4 | {
|
5 | static uint8_t I=1; //Schleifenzähler |
6 | static uint8_t A=0; //Merker für Kanalumschaltung |
7 | static uint16_t ADCwert=0; //Wert AD-Wandler |
8 | |
9 | //AD-Wandlung
|
10 | |
11 | ADCwert += ADCH; //Aufsummieren |
12 | I=I+1; |
13 | |
14 | ....
|
3. Falls ich jetzt nur nicht durch deine Berechnungen mit 'schmier' durchgestiegen sein sollte, musst du trotzdem ADCH abholen. In C ganz einfach:
1 | variable = ADC; |
Recht vielen Dank für Eure Antworten. Auch wenn ich als Gelegenheitsprogrammiererin nicht alles verstanden habe, ist mir klar geworden, dass die Kanalumschaltung durch das Umschalten der einzelnen Bits im ADMUX Register Murks ist. Ich habe den Code so verändert, dass immer der passende Hex-Wert in das Register geschrieben wird und das Ganze mit ein paar "defines" lesbarer gemacht. Im Anhang das Ergebnis. Nachmals vielen Dank für die Hilfe.
:
Bearbeitet durch User
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.