Forum: Mikrocontroller und Digitale Elektronik A/D-Wandler Kanalumschaltung beim ATMEGA 16 mit „case“ Auswahl


von Jenny L. (jenny_lo)


Angehängte Dateien:

Lesenswert?

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.

von frank (Gast)


Lesenswert?

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
}

von Jenny L. (jenny_lo)


Lesenswert?

Danke für den Tipp. Ich wusste nicht, dass das geht. Ich schreibe zu 
selten.

von Frank (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Frank (Gast)


Lesenswert?

>uiADC[adcCh]
Das stimmt natürlich nicht.
>uiADC[adcCh%8]
So ist es richtiger.

von Route_66 H. (route_66)


Lesenswert?

Frank schrieb:
> So ist es richtiger.

...und wie ist es dann "am richtigsten"?

von Fritz G. (fritzg)


Lesenswert?

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
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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
von Ralf G. (ralg)


Lesenswert?

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;

von Jenny L. (jenny_lo)


Angehängte Dateien:

Lesenswert?

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
Noch kein Account? Hier anmelden.