Forum: Mikrocontroller und Digitale Elektronik ATtiny13 - 2 Funktionen spielen verrückt


von Squat *. (squat)


Lesenswert?

Hallo,

ich habe ein Problem mit der AD-Wandlung beim ATtiny13 bzw. spielen 
meine zwei Funktionen errückt, wenn ich den ADC mitlaufen lasse.

Ich möchte an PB3 (ADMUX=11) die Batteriespannung messen. Leider bekomme 
ich das nicht mehr hin. Ich hatte eine erste Messung auf einem ATmega8 
hinbekommen und ich bin auch der Meinung, dass es bei dem ATtiny als DIP 
Version auch schon funktionierte.

Jetzt habe ich eine Platine angefertigt und den Quelltext angepasst. 
Jedoch funktioniert die Messung nicht mehr richtig.

Beide Funktionen (Lauflicht und Blicklicht) funktionieren einzelt, wenn 
diese aufgerufen werden (die Andere auskommentieren oder wenn ich für 
result einen Wert einsetze), jedoch wenn diese zusammen über dem ADC 
ausgewählt werden sollen (Größer / Kleiner als 410), dann laufen beide 
Funktionen in einander ab.
1
//ICC-AVR application builder :
2
// Target : T13
3
// Crystal: 9.6000Mhz
4
5
#include <iot13v.h>
6
#include <macros.h>
7
8
unsigned char count10 = 0;
9
unsigned char countV = 0;
10
unsigned char blink_state = 0;
11
unsigned char countLED = 0;
12
unsigned char i = 0;
13
unsigned int result;
14
15
/* PORT-Zuweisung */
16
17
//#define PB5 
18
#define R_LED4 PB4
19
//#define AD_W PB3
20
#define R_LED1 PB2
21
#define R_LED2 PB1
22
#define R_LED3 PB0
23
24
void port_init(void)
25
{
26
 PORTB = (1<<PB5)|(1<<PB3);
27
 DDRB = (1<<DDB4)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0);
28
}
29
30
//TIMER0 initialize - prescale:1024
31
// WGM: Normal
32
// desired value: 10mSec
33
// actual value:  9,920mSec (0,8%)
34
void timer0_init(void)
35
{
36
 TCCR0B = 0x00; //stop
37
 OCR0A = 0x5D;
38
 OCR0B = 0x5D;
39
 TCNT0 = 0xA3; //set count
40
 TCCR0A = 0x00; 
41
 TCCR0B = 0x05; //start timer
42
43
}
44
45
#pragma interrupt_handler timer0_ovf_isr:iv_TIM0_OVF
46
void timer0_ovf_isr(void)
47
{
48
 TCNT0 = 0xA3; //reload counter value
49
 ADW();
50
// result = 411;
51
 if (result <= 410) {Warning();}
52
 if (result > 410) {rul();} 
53
}
54
55
56
//ADC initialize
57
// Conversion time: 104uS
58
void adc_init(void)
59
{
60
 ADCSRA = 0x00;  //disable adc
61
 ADMUX = 0x00; 
62
 ACSR  = 0x80;
63
 ADCSRB = 0x00;
64
 ADCSRA = 0x86;//01
65
}
66
67
void rul(void)
68
{ 
69
}             //
70
71
void Warning(void)
72
{
73
}
74
75
void ADW(void)
76
{
77
//----------------------------------------------------------------------------//
78
       //         AD-Wandlung
79
//----------------------------------------------------------------------------//
80
       // Dummy-Readout
81
       ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1);  // ADC aktivieren, Frequenzvorteiler: setzen auf 64 (8 MHz / 64 = 125 kHz) und ADC aktivieren
82
       ADMUX = 0x11;                      // Kanal waehlen (ADC3)
83
       ADMUX |= (1<<REFS0);                // interne Referenzspannung nutzen 
84
       ADCSRA |= (1<<ADSC);               // eine ADC-Wandlung
85
       while(!(ADCSRA & (1<<ADIF)));          // auf Abschluss der Konvertierung warten (ADIF-bit)
86
       result = ADC;                    // ADC muss einmal gelesen werden,
87
                                        // sonst wird Ergebnis der nächsten Wandlung
88
                                        // nicht übernommen.
89
       
90
       //ADC-Messung mit arithmetischen Mittel aus 4 Messungen
91
       result = 0;
92
       for (i=0;i<4;i++)
93
            {
94
          ADCSRA |= (1<<ADSC);             // Messung ausfuehren
95
        while ( ADCSRA & (1<<ADSC) );        // Konvertierung abwarten
96
        result += ADC;                     // aufaddieren zur Mittelwertbildung (ADC auslesen der zwei Bits
97
        }
98
        ADCSRA &= ~(1<<ADEN);               // ADC ausschalten
99
        
100
        result /= 4;                 // 
101
102
}          
103
            
104
105
106
//call this routine to initialize all peripherals
107
void main(void)
108
{
109
 //stop errant interrupts until set up
110
 CLI(); //disable all interrupts
111
 port_init();
112
 timer0_init();
113
 adc_init();
114
115
 MCUCR = 0x00;
116
 TIMSK0 = 0x02; //timer interrupt sources
117
 GIMSK = 0x00; //interrupt sources
118
 SEI(); //re-enable interrupts
119
 //all peripherals are now initialized
120
121
while(1);
122
}

Auch wenn ich
1
  if (result <= 410) {Warning();}
2
  else{rul();}
statt der zwei if-Abfragen einsetze spielen diese verrückt.

Habe ich bei der AD-Wandlung einen Gedankenfehler (läuft diese so 
schnell ab, dass mehere Ergebnisse Zustande kommen)oder woran könnte das 
liegen?

Vielen Dank für eure Tipps.

von Grrrr (Gast)


Lesenswert?

Im Timerinterrupt Wandlungen starten, mitteln und dann abhängig vom 
Ergebnis noch ein Blink- oder Lauflicht. Alles im selben Interrupt. Und 
das ganze ist schon mal in einem DIP-Gehäuse gelaufen. Hmmm. 
Interessant.

Also, tut mir ja leid, aber das kann leider so nicht funktionieren.

Lies mal die Tutorials hier und fang mal mit einfachen Sachen an.

Dann erfährst Du u.A. folgendes:
Interrupts sind so kurz wie möglich. Volatile. Flags setzen. Aktionen in 
main. AD-Wandlung läuft sowieso mit eigenem Interrupt. Auch 
kontinuierlich.

von Hc Z. (mizch)


Lesenswert?

> läuft diese so
> schnell ab, dass mehere Ergebnisse Zustande kommen

Dein Timer-Interrupt kommt alle 10 ms.  Mehr lässt sich zu diesem Punkt 
nicht sagen, da Du den Source von Warning() und rul() nicht mitlieferst.

Weitere Punkte, ohne Anspruch auf Vollständigkeit:

In der ADC-Routine setzt Du ADIF nicht zurück, also wird es immer 
gesetzt sein und das Ergebnis der Wandlung wird gar nie abgewartet. 
Wieso wartest Du nicht auf das Rücksetzen von ADSC -- da musst Du nichts 
von Hand zurücksetzen?  Falls Du doch bei ADIF bleiben willst: mach Dich 
kundig, wie es rückgesetzt wird.  Nein, nicht indem man 0 reinschreibt.

Du erledigst viel zu viel im Interrupt, das ist keine sinnvolle 
Programmstruktur.  Setze im Interrupt maximal ein Flag, ob das Resultat 
über der Schwelle liegt, und mache die weitere Verarbeitung im 
Hauptprogramm.

von Floh (Gast)


Lesenswert?

Meine Tips zum Programm wären:

Den Timerinterrupt wegschmeißen.
ADC auf freerunning laufen lassen.
in nem ADC-Interrupt die Mittelbildung machen.
In der main einfach nur abfragen, ob der Mittelwert grad kleiner oder 
größergleich 410 ist und dann in der main die funktionen aufrufen.

von Squat *. (squat)


Lesenswert?

Vielen Dank erstmal. Ich habe zwar noch nicht die Lösung gefunden, weiß 
aber schonmal, woran es liegen könnte.

Den Interrupt nutze ich um das Rundumlicht sowie das Blinklicht anzu 
steuern. Diese sollen im 100ms bzw 200ms Takt durchlaufen.

Da die Funktionen rul() und Warning() gehen, hatte ich diese nicht 
eingefügt.

zur Vollständigkeit bringe ich diese hier nochmal an.
1
void rul(void)
2
{
3
  switch (blink_state)
4
 {
5
6
  case 0: // R_LED1
7
  {
8
   PORTB &= ~((1<<R_LED2) | (1<<R_LED3) | (1<<R_LED4));
9
   PORTB |= (1<<R_LED1);
10
   break;  
11
  }
12
  case 1: // R_LED2 ein
13
  {
14
   PORTB &= ~((1<<R_LED1) | (1<<R_LED3) | (1<<R_LED4));
15
   PORTB |= (1<<R_LED2);
16
   break;  
17
  }  
18
  case 2: // R_LED3 ein
19
  {
20
   PORTB &= ~((1<<R_LED1) | (1<<R_LED2) | (1<<R_LED4));
21
   PORTB |= (1<<R_LED3);
22
   break;  
23
  }  
24
  case 3: // R_LED4 ein
25
  {
26
   PORTB &= ~((1<<R_LED1) | (1<<R_LED2) | (1<<R_LED3));
27
   PORTB |= (1<<R_LED4);
28
   break; 
29
  } 
30
 }
31
countLED++;
32
if (countLED>10) 
33
{
34
countLED=0;
35
blink_state++;
36
}             //
37
if (blink_state>3) blink_state=0;     // Lauflicht neustarten
38
}
1
oid Warning(void)
2
{
3
    if (countV>50) // LEDs aus
4
             {
5
        PORTB &= ~((1<<R_LED1) | (1<<R_LED2) | (1<<R_LED3) | (1<<R_LED4));
6
        }
7
    if (countV>99) // LEDs aus
8
             {
9
        PORTB |= ((1<<R_LED1) | (1<<R_LED2) | (1<<R_LED3) | (1<<R_LED4));
10
        countV=0;
11
        }
12
countV++;
13
}

von Karl H. (kbuchegg)


Lesenswert?

Squat *** schrieb:
> Vielen Dank erstmal. Ich habe zwar noch nicht die Lösung gefunden, weiß
> aber schonmal, woran es liegen könnte.

Lass dir doch mal den ADC Wert direkt auf die LED ausgeben (die obersten 
4 Bit werden wohl genügen).
Ich wette mit dir, dass du keinen stabilen Messwert hast, sondern das 
deine LED mehr oder eniger zufällige Zahlen anzeigen werden.

Warum das so ist?
Dafür gibt es mehrere Möglichkeiten. Anfangen würde ich mit der 
Referenzspannung.

von Squat *. (squat)


Lesenswert?

So, ich habe den Fehler gefunden.

Es lag einfach an der Kanalauswahl. Bin ich gerade zufällig drauf 
gestoßen, als ich mir ein anderes Programm angeschaut habe.

Richtig muss es
1
ADMUX = 0x03;
 lauten.

Vielen Dank an alle Beteiligten!!


Gerne würde ich noch mehr aus diesem Threat herausziehen.

Zum einen habe ich jetzt das Problem, dass in der Übergangsphase 
zwischen Rundumlicht und Blicklicht die Spannung einbricht, sobald das 
Blicklicht beginnt. Um dieses Problem zu umgehen ist entweder auf der 
Hardwareseite ein Kondensator zu verbauen. Oder softwaremäßig die 
Messung gezielt zwischen den Blinken anzusetzen.
Ich habe es vorerst mit dem ELko versucht, jedoch half das nicht. 
Daraufhin habe ich mir gedacht, ich rufe die ADC nur solange auf, wie 
die Rundumleuchte an ist. Sobald einmal die Spannung zu niedrig ist, 
wird nur noch das Blicklicht ausgeführt. Das funktioniert soweit auch, 
jedoch nach dem ersten Blicken bricht die Spannung soweit zusammen, dass 
der ATtiny neustartet. Und so beginnt das Spiel wieder von vorne. Wenn 
ich aber die Spannung weiter nach unten schraube, Blicken irgendwann die 
Lämpchen, so wie geplan.

Es geht also um den Übergang.

In der Software hatte ich schonmal probiert die Mittelwertbildung zu 
erweitern. Bis 50 geht dass gut. Bei Hundert klappt es nicht mehr und er 
Blink nur noch (blinkt einmalig und dann startet der Prozessor neu egal 
wie viel Spannung anliegt.

Ich weiß leider nicht, wie ich dieses Problem umgehen kann.



Und noch eine kleine Frage am Rande. Wie kann ich im Hauptprogramm 
erfragen, ob der Timer übergelaufen ist.

Und nochmals besten Dank für die Tipps.

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.