Forum: Mikrocontroller und Digitale Elektronik Konflikt? WDT + INT0 + SLEEP


von Hansi C. (honsey)


Angehängte Dateien:

Lesenswert?

Hello,

ich habe mir schon an anderer Stelle hier im Forum Hilfe bei der 
Hardware eingeholt. Ich arbeite im Moment an einer Schaltung, die ich in 
kleinerer Stückzahl herstellen möchte, bzw. hergestellt habe (10 Stück).

Es ist handelt sich um kleine Roboter-Avatare. Wenn sich zwei Kollegen 
"erkennen" (soll per IR gelöst werden) fangen sie an sich zu 
"unterhalten".

Das hier ist mein aktueller Code, auf ATTINY84: IR-LED an PA6, TSOP an 
PB2/INT0. Die RGB LEDs belegen PA0-PA5. Es gibt noch einen Piezo auf 
PA7, der ist aber erst mal zweitrangig.
1
#include <avr/power.h>
2
#include <avr/wdt.h>
3
#include <avr/sleep.h>
4
#include <avr/interrupt.h>
5
#include <stdlib.h>
6
#include <util/delay.h>
7
8
#define B1_MASK   (1<<PA0)
9
#define G1_MASK   (1<<PA1)
10
#define R1_MASK   (1<<PA2)
11
#define R2_MASK   (1<<PA3)
12
#define G2_MASK   (1<<PA4)
13
#define B2_MASK   (1<<PA5)
14
15
#define IR_MASK    (1<<PA6)
16
#define PIEZO_MASK (1<<PA7)
17
18
#define IR_CARRIER_T 28     // for 25kHz
19
#define IR_INIT_PERIODS 200 // 200 mal 28us MARK senden
20
#define TOLERANZ 20         // 20% toleranz für gemessene signallänge
21
22
#define TSOP_MASK  (1<<PB2)
23
24
volatile uint8_t scan = 0;
25
26
void pulseIR(long periods){
27
28
    // so viele pulse wie nmöglich abarbeiten
29
    while(periods>0){
30
        PORTA |= IR_MASK;
31
        _delay_us( IR_CARRIER_T/2 );
32
        PORTA &= ~IR_MASK;
33
        _delay_us( IR_CARRIER_T/2 );      // 28us periode macht ziemlich genau 25khz carrier
34
35
        periods -= 1;
36
    }
37
}
38
39
void sendIRsignal() {
40
    pulseIR(IR_INIT_PERIODS); // 200*28 = rechnerisch ca. 5.600us
41
}
42
43
void wdt_init() {
44
    cli();
45
    wdt_reset();
46
    WDTCSR = (1<<WDE) | (1<<WDCE); // enable watchdog
47
    WDTCSR = (1<<WDIE) | WDTO_1S; // enable watchdog interrupt
48
    sei();
49
}
50
51
void sleep_now() {
52
    power_all_disable();
53
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
54
    sleep_enable();
55
    sleep_mode();
56
    sleep_disable();
57
    wdt_init();
58
}
59
60
// alle n sekunden feuert der WDT diesen interrupt
61
ISR(WDT_vect){
62
    sendIRsignal(); // ir signalfolge schicken
63
    _delay_ms(1);   // kleines delay, sonst erkennt INT0 sich selbst
64
}
65
66
// wenn INT0 gegen GND gezogen wird, feuert die ISR
67
ISR(INT0_vect){    
68
    scan = 1; // hier nur die flagge setzen
69
}
70
71
void statusBlink(int mask){
72
  PORTA |= mask;
73
  _delay_ms(100);
74
  PORTA &=~mask;
75
}
76
77
unsigned long checkIRsignal(){
78
79
  unsigned long periods = 0;
80
  
81
  while( !(PORTB & TSOP_MASK) ){      // TSOP ist immer noch auf GND = signal kommt an
82
    
83
    if( periods > IR_INIT_PERIODS ){ // ist das signal schon länger, als das vom gegenpart gesendete?
84
85
      // blinken um zu wissen, wo man ist
86
      statusBlink(B2_MASK);
87
88
      // aus der funktion springen
89
      return 0;
90
    }
91
92
    periods++;                        // ansonsten: weiter hochzählen..
93
    _delay_us( IR_CARRIER_T );        // ..und eine periode warten
94
95
  }
96
97
  // jetzt die gemessene signallänge mit dem erwarteten wert (200*t) abgleichen
98
  if( abs(periods - 200) <= (periods * TOLERANZ / 100) ){
99
    return 1;
100
  }
101
  
102
  // signal zu kurz
103
  statusBlink(G1_MASK); // kurzer status
104
  
105
  return 0;
106
}
107
108
109
int main(void) {
110
111
    // rgb leds, ir transmitter und piezo auf output
112
    DDRA = 0xFF;
113
114
    // tsop ir sensor auf INT0
115
    MCUCR &= ~(1<<ISC01);
116
    MCUCR &= ~(1<<ISC00); // interrupt bei fallender flanke gen GND
117
118
    GIFR |= (1<<INTF0);   // interrupt flag löschen durch logic 1
119
120
    GIMSK |= (1<<INT0);   // intterupt anschalten
121
122
    // watchdog einrichten
123
    wdt_init();
124
125
    // alle interrupts aktivieren
126
    sei();
127
128
    // endlos loop
129
    while( 1 ) {
130
        
131
        // wenn INT0 gegen GND gezogen wird
132
        if(scan){
133
            cli();        // interrupts pausieren
134
            wdt_reset();  // wdt reset - savety first
135
136
            long detectKollega = checkIRsignal();
137
138
            if(detectKollega){
139
              int i;
140
              for(i=0; i<5; i++){
141
                PORTA |= R2_MASK;
142
                _delay_ms(100);
143
                PORTA &=~R2_MASK;
144
                _delay_ms(200);
145
              }
146
            }
147
            
148
            scan=0;       // flag löschen
149
            sei();        // interrupts wieder scharf stellen
150
        }
151
152
        sleep_now();  // cpu wieder schlafen legen
153
        
154
    }
155
156
    return 0;
157
}

Meine bisherigen Probleme waren u.a. dass ein Avatar sich immer selbst 
erkannt hat, wenn der WDT-Interrupt das IR-Signal gesendet hat. Ich habe 
das jetzt mit einem kurzen Delay lösen könnten, aber es fühlt sich ein 
bisschen "schmutzig an". Ich habe eigentlich versucht an der stelle mit 
cli() alle weiteren Interrupts zu pausieren, das hat leider nicht 
geholfen.

Ausserdem hat der TSOP (Ist ein 25kHz-Modell) auch gerne spontan durch 
andere IR-Quellen ausgelöst. Ich habe deshalb die IR-Sende-Funktion so 
angelegt, dass sie über 5ms ein ON/1/Mark sendet. In der 
Empfangsfunktion möchte ich abfragen, ob das ankommende Signal eben 
diese 5ms durchgehend 1/ON/Mark ist, um andere IR-Quellen 
(Fernbedienungen, Lampen, etc.) auszuschliessen.

Genau an dieser Stelle arbeitet der Code aber nicht wie erwartet (wieder 
einmal...). Wenn ich meine Schaltung mit einer normalen Fernbedienung 
befeuere, leuchtet ständig die Blaue LED. Die sollte aber nur leuchten, 
wenn die hochgezählten/durchlaufenen Perioden größer ca. 5ms sind. Was 
bei meiner Fernbedienung nicht sein kann...

Ich bin Anfänger, bastle sonst nur mit Arduino/Raspberry rum und möchte 
aber ein bisschen tiefer in die "kleine" Welt von ATTINYs etc. 
einsteigen, GCC lernen.

Wo liegt mein Fehler? — Hoffentlich kann mich einer von euch in die 
richtige Richtung schubsen.

Danke & Schönen Abend,
Hans

PS: Im Anhang der Schaltplan — Vielleicht ist auch dort der Fehler?

von Peter D. (peda)


Lesenswert?

Ich würde das Geschwurbel mit Watchdog und Sleep erstmal ganz außen vor 
lassen und die Timings und Sendefrequenz mit den richtigen Timern/PWM 
machen.

von Thomas E. (thomase)


Lesenswert?

Peter Dannegger schrieb:
> Ich würde das Geschwurbel mit Watchdog und Sleep erstmal ganz außen vor
> lassen und die Timings und Sendefrequenz mit den richtigen Timern/PWM
> machen.

Das ist das eine.
Und ich würde ihn kein periodisches oder ständiges Signal senden lassen. 
Sondern entweder richtigen IR-Code oder zumindest PWM mit verschiedenen 
Puls/Pausen-Zeiten.

mfg.

: Bearbeitet durch User
von Hansi C. (honsey)


Lesenswert?

Danke schon mal. Die Timer brauche ich später (glaube ich zumindest) um 
PWM für die RGB-LEDs + Sounds durch den Piezo erzeugen zu lassen.

Das triviale 5ms-Dauerfeuer als "Echolot" sollte eigentlich auch mit den 
Delays hinhauen, oder? Ich habe es ja mit dem Oszi gemessen und die 
Delay-Werte entsprechend angepasst, um ca. auf die 25kHz zu kommen. Es 
müssen keine Daten ausgetauscht werden (zumindest für diese erste 
Version). Nur eine simple Erkennung.

Ich teste mit zwei Platinen: Eine sendet nur sturr, alle 1s den 
IR-Initialisierung.

Bei der Anderen, habe ich Sleep, WDT etc. entfernt. Dort gibt es nur den 
INT0 Interrupt, der eine Flagge setzt um die ankommenden Signale im 
Main-Loop auszuwerten.

Trotzdem bekomme ich durch meine LEDs nur die Rückmeldung: Interrupt hat 
gefeuert + Signal ist zu kurz.

Ich habe ein bisschen die Befürchtung, dass der INT0-Interrupt zu oft 
ausgelöst wird. Wie kann ich den denn temporär ausschalten? cli() und 
sei() haben bei mir wie gesagt scheinbar keine Änderung bewirkt.

von Hansi C. (honsey)


Lesenswert?

Herr, lass Hirn regnen! In meinem Wahnsinn hab ich ein paar fiese Fehler 
eingebaut:

1) Zur weiteren Anfrage von PB2/INT0 muss der Pin natürlich als Input 
geschaltet werden

2) Die Abfrage des PB2 in der Schleife war falsch: Da muss natürlich das 
Bit in PINB und nicht in PORTB abgeglichen werden.

Soweit läuft es jetzt also und der Vollständigkeit halber:
1
#include <avr/power.h>
2
#include <avr/wdt.h>
3
#include <avr/sleep.h>
4
#include <avr/interrupt.h>
5
#include <stdlib.h>
6
#include <util/delay.h>
7
8
#define R1_MASK   (1<<PA3)
9
#define G1_MASK   (1<<PA4)
10
#define B1_MASK   (1<<PA5)
11
#define B2_MASK   (1<<PA0)
12
#define G2_MASK   (1<<PA1)
13
#define R2_MASK   (1<<PA2)
14
15
#define IR_MASK    (1<<PA6)
16
#define PIEZO_MASK (1<<PA7)
17
18
#define IR_CARRIER_T 28     // for 25kHz
19
#define IR_INIT_PERIODS 200 // 200 mal 28us MARK senden
20
#define TOLERANZ 20         // 20% toleranz für gemessene signallänge
21
22
#define TSOP_MASK  (1<<PB2)
23
24
volatile uint8_t scan = 0;
25
26
void pulseIR(long periods){
27
28
    // so viele pulse wie nmöglich abarbeiten
29
    while(periods>0){
30
        PORTA |= IR_MASK;
31
        _delay_us( IR_CARRIER_T/2 );
32
        PORTA &= ~IR_MASK;
33
        _delay_us( IR_CARRIER_T/2 );      // 28us periode macht ziemlich genau 25khz carrier
34
35
        periods -= 1;
36
    }
37
}
38
39
void sendIRsignal() {
40
    pulseIR(IR_INIT_PERIODS); // 200*28 = rechnerisch ca. 5.600us
41
}
42
43
void wdt_init() {
44
    cli();
45
    wdt_reset();
46
    WDTCSR = (1<<WDE) | (1<<WDCE); // enable watchdog
47
    WDTCSR = (1<<WDIE) | WDTO_1S; // enable watchdog interrupt
48
    sei();
49
}
50
51
void sleep_now() {
52
    power_all_disable();
53
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
54
    sleep_enable();
55
    sleep_mode();
56
    sleep_disable();
57
    wdt_init();
58
}
59
60
// alle n sekunden feuert der WDT diesen interrupt
61
ISR(WDT_vect){
62
    sendIRsignal(); // ir signalfolge schicken
63
    _delay_ms(1);   // kleines delay, sonst erkennt INT0 sich selbst
64
}
65
66
// wenn INT0 gegen GND gezogen wird, feuert die ISR
67
ISR(INT0_vect){    
68
    scan = 1; // hier nur die flagge setzen
69
}
70
71
void statusBlink(int mask){
72
  PORTA |= mask;
73
  _delay_ms(100);
74
  PORTA &=~mask;
75
}
76
77
unsigned long checkIRsignal(){
78
79
  unsigned long periods = 0;
80
  
81
  while( !(PINB & TSOP_MASK) ){      // TSOP ist immer noch auf GND = signal kommt an
82
    
83
    if( periods > IR_INIT_PERIODS ){ // ist das signal schon länger, als das vom gegenpart gesendete?
84
85
      // blinken um zu wissen, wo man ist
86
      statusBlink(B2_MASK);
87
88
      // aus der funktion springen
89
      return 0;
90
    }
91
92
    periods++;                        // ansonsten: weiter hochzählen..
93
    _delay_us( IR_CARRIER_T );        // ..und eine periode warten
94
95
  }
96
97
  // jetzt die gemessene signallänge mit dem erwarteten wert (200*t) abgleichen
98
  if( abs(periods - 200) <= (periods * TOLERANZ / 100) ){
99
    return 1;
100
  }
101
  
102
  // signal zu kurz
103
  statusBlink(R2_MASK); // kurzer status
104
  
105
  return 0;
106
}
107
108
109
int main(void) {
110
111
    // rgb leds, ir transmitter und piezo auf output
112
    DDRA = 0xFF;
113
114
    // tsop als input definieren
115
    DDRB &=~TSOP_MASK;
116
    PORTB |= TSOP_MASK; // pullup an
117
118
    // tsop ir sensor auf INT0
119
    MCUCR &= ~(1<<ISC01);
120
    MCUCR &= ~(1<<ISC00); // interrupt bei fallender flanke gen GND
121
122
    GIFR |= (1<<INTF0);   // interrupt flag löschen durch logic 1
123
124
    GIMSK |= (1<<INT0);   // intterupt anschalten
125
126
    // watchdog einrichten
127
    wdt_init();
128
129
    // alle interrupts aktivieren
130
    sei();
131
132
    // endlos loop
133
    while( 1 ) {
134
        
135
        // wenn INT0 gegen GND gezogen wird
136
        if(scan){
137
            cli();        // interrupts pausieren
138
            //wdt_reset();  // wdt reset - savety first
139
140
            long detectKollega = checkIRsignal();
141
142
            if(detectKollega){
143
              int i;
144
              for(i=0; i<5; i++){
145
                PORTA |= (1<<i);
146
                _delay_ms(100);
147
                PORTA &=~(1<<i);
148
                _delay_ms(200);
149
              }
150
            }
151
            
152
            scan=0;       // flag löschen
153
            sei();        // interrupts wieder scharf stellen
154
        }
155
156
        sleep_now();  // cpu wieder schlafen legen
157
        
158
    }
159
160
    return 0;
161
}

Jetzt schläft's sich schon ein bisschen besser... :)

von Hansi C. (honsey)


Lesenswert?

...an Schlaf ist nicht zu denken! :-)

Ich versuche gerade meine Teil-Stücke Soft-PWM und die Soundwiedergabe 
über den Piezo wieder ins Programm zu bekommen.

So sieht/sah das bisher aus. cli() und sei() haben doch die zu 
erwartende Wirkung – ohne das funktioniert es nicht, weil wohl der INT0 
oder der WDT dazwischengrätschen.
1
if(scan){
2
            cli(); //interrupts deaktivieren, damit die auswertung nicht gestört wird
3
4
            long detected_friend = checkIRsignal();
5
6
            if(detected_friend){
7
              int i;
8
              for(i=0; i<5; i++){
9
                PORTA |= (1<<i);
10
                _delay_ms(100);
11
                PORTA &=~(1<<i);
12
                _delay_ms(200);
13
              }
14
15
              playSound(); // braucht TIMER0
16
            }
17
            
18
            scan=0;       // flag löschen
19
            sei(); // interrupts wieder aktivieren
20
        }

cli() deaktiviert aber auch die Timer, die ich benötige. Deshalb dachte 
ich, dass ich den INT0-Interrupt und den Watchdog einzeln deaktivieren 
könnte.
1
//cli();
2
GIMSK &= ~(1<<INT0); // INT0 OFF
3
wdt_disable();

Komischerweise läuft mein Programm dann gar nicht mehr durch. Weder die 
LEDs werden angefahren, noch kommt irgend ein Ton aus dem Piezo. Was 
mache ich falsch?

von Hansi C. (honsey)


Lesenswert?

Ich bin ein Stück weiter, aber sobald ich sleep_now() wieder reinholen, 
am ende der Schleife, funktioniert mein TIMER0 nicht mehr. (Der Piezo 
knackt nur noch.)
1
while( 1 ) {
2
        
3
        // wenn INT0 gegen GND gezogen wird
4
        if(scan){
5
            
6
            // intterupts abschalten, aber nicht TIMER0 und TIMER1
7
            cli();
8
            GIMSK &= ~(1<<INT0); // INT0 OFF
9
            MCUSR = 0;           // disable WDT
10
            sei();
11
12
            long detected_friend = 1; //checkIRsignal();
13
14
            if(detected_friend){
15
              
16
              int i;
17
              for(i=0; i<3; i++){
18
                PORTA |= (1<<i);
19
                _delay_ms(100);
20
                PORTA &=~(1<<i);
21
              }
22
23
              _delay_ms(100);
24
25
              playSound(); // this needs TIMER0
26
            }
27
            
28
            scan=0;       // flag löschen
29
            
30
            GIMSK |= (1<<INT0); // INT0 interrupt active again
31
            wdt_init();         // WDT wieder anschmeissen
32
        }
33
34
        //sleep_now();  // cpu wieder schlafen legen
35
        
36
    }

Zur Referenz:
1
void sleep_now() {
2
    power_all_disable();
3
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
4
    sleep_enable();
5
    sleep_mode();
6
    sleep_disable();
7
    wdt_init();
8
}
9
10
void wdt_init() {
11
    cli();
12
    wdt_reset();
13
    WDTCSR = (1<<WDE) | (1<<WDCE); // enable watchdog
14
    WDTCSR = (1<<WDIE) | WDTO_1S; // enable watchdog interrupt
15
    sei();
16
}

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.