Forum: Mikrocontroller und Digitale Elektronik Attiny13a deep sleep - external interrupt


von Nils L. (luene)


Lesenswert?

Hallo,

ich habe hier einen Attiny 13A. Er läuft auf einem Steckbrett.
Es sind nur ein Taster und ein 433Mhz Sender angeschlossen, das ganze 
ist an einer Powerbank angeschlossen.

Ich möchte dass wenn der Taster betätigt wird ein 433Mhz-Signal gesendet 
(nennen wird es EIN) wird und wenn der Taster losgelassen wird ein 
anderes 433Mhz Signal (nennen wir es AUS) gesendet wird. In der 
Zwischenzeit soll der Controller in den deep sleep gehen.

Die Funkverbindung klappt.
Wenn ich den Taster betätige wird auch EIN gesendet.
Wenn ich den Taster loslasse wird MEISTENS auch AUS gesendet.
Sporadisch passiert es jedoch, dass beim loslassen erst nochmal EIN 
gesendet wird, und erst danach das für AUS (dazwischen ist 1 Sekunde 
Pause). Der Attiny geht dazwischen nicht in den deep-sleep.

Hat jemand eine Idee woran das liegen könnte?
Ich habe noch nie mit dem Deep sleep gearbeitet, denke dass ich hier 
irgendwas verbockt habe.
Danke schonmal!


Hier ist noch der Code dazu:
1
/*
2
 * Velux_Sensor_V1.c
3
 *
4
 * Created: 27.01.2021 10:26:57
5
 * Author : Nils
6
 */ 
7
8
// DEFINES
9
#define F_CPU 600000    //600 khz
10
11
#define DATA_TIME_ON  980
12
#define DATA_TIME_OFF 350 //ON+OFF == ~1300µs
13
//Pins
14
#define DATA_ON   PORTB |=  (1<<PB4)    // PIN B4
15
#define DATA_OFF PORTB &= ~(1<<PB4)    // PIN B4
16
17
// INCLUDES
18
#include <avr/io.h>
19
#include <util/delay.h>
20
#include <avr/sleep.h>
21
#include <avr/interrupt.h>
22
23
// FUNCTION DECLARATION
24
void send_bit0();
25
void send_bit1();
26
void send_command(uint32_t code);
27
void doSleep();
28
29
// FUNCTIONS
30
int main(void)
31
{
32
  DDRB |=  (1 << DDB4);  //B4: 433Mhz Sender
33
  DDRB &= ~(1 << DDB3);  //B3: Taster
34
  PORTB |= (1 << PB3);  //B3: Pullup
35
36
  while(1){
37
    if ((!(PINB&(1<<PINB3)))){  //EIN
38
      send_command(0x555);
39
    }
40
    else{            //AUS
41
      send_command(0x552);
42
    }
43
    _delay_ms(1000);  //DELAY (DEBUG)
44
    doSleep();
45
  }
46
}
47
48
void doSleep(){
49
  GIMSK |= (1<<PCIE);                     // Enable Pin Change Interrupts
50
  PCMSK |= (1<<PCINT3);                   // Use PB3 as interrupt pin
51
  ADCSRA &= ~(1<<ADEN);                   // ADC off
52
53
54
  WDTCR |= (1<<WDP3 )|(0<<WDP2 )|(0<<WDP1)|(1<<WDP0); // 8s
55
  //WDTCR |= (1<<WDTIE);
56
  sei();  //INTERRUPT EIN
57
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // replaces above statement
58
  sleep_mode();
59
  cli();  //INTERRUPT AUS
60
  return;
61
}
62
63
void send_command(uint32_t code){        //sendet Code
64
  for(int cycle = 0; cycle < 8;cycle++){    //8 mal senden
65
    for (int pos = 24; pos > 0; pos--){    //sendet 24Bit
66
      if (code & (1 << (pos-1)))        //prüft ob Bit an dieser Stelle 1 oder 0
67
        send_bit1();  //Sendet 1
68
      else
69
        send_bit0();  //Sendet 0
70
    }
71
    send_bit0();  //Sendet End-Bit
72
    _delay_ms(10);  //Pause vor dem nächsten Signal
73
  }
74
}
75
76
void send_bit1(){
77
  DATA_ON;
78
  _delay_us(DATA_TIME_ON);
79
  DATA_OFF;
80
  _delay_us(DATA_TIME_OFF);
81
}
82
83
void send_bit0(){
84
  DATA_ON;
85
  _delay_us(DATA_TIME_OFF);
86
  DATA_OFF;
87
  _delay_us(DATA_TIME_ON);
88
}
89
90
ISR(WDT_vect) {
91
  return; //LEER
92
}

: Bearbeitet durch User
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Nils L. schrieb:
> GIMSK |= (1<<PCIE);                     // Enable Pin Change
> Interrupts
>   PCMSK |= (1<<PCINT3);                   // Use PB3 as interrupt pin

Es lohnt sich, hier noch etwaige 'Pending' Interrupts zu löschen, bevor 
der MC schläft.

von Peter D. (peda)


Lesenswert?

Nils L. schrieb:
> Hat jemand eine Idee woran das liegen könnte?

Der Taster prellt.

von S. Landolt (Gast)


Lesenswert?

Und wo ist die Taster-, d.h. die PCINT-ISR?

von Lutz (Gast)


Lesenswert?

Welche ISR? Er liest einfach jede Sekunde den Pin_Status ein...

von Lutz (Gast)


Lesenswert?

Ich ziehe die Bemerkung wieder zurück!

Beitrag #6571923 wurde vom Autor gelöscht.
von m.n. (Gast)


Lesenswert?

Sehr stromsparend auch mit gedrücktem Taster: 
Beitrag "Re: EIN-AUS mit Taster per Interrupt, ATtiny25 o.ä."
Drücken und Loslassen mußt Du entsprechend anpassen.
Verwende einen Taster mit vergoldeten Kontakten oder Reedrelais. Dann 
ist den "Kontaktfreibrennern" ihr Argument genommen ;-)

von Peter D. (peda)


Lesenswert?

S. Landolt schrieb:
> Und wo ist die Taster-, d.h. die PCINT-ISR?

Ohne ISR erfolgt das Aufwachen durch Sprung zum Resetvector (0x0000).
Der Interrupt ist ja freigegeben.

Hier ein Beispiel für Sleep mit Statemaschine:
Beitrag "Re: AVR Sleep Mode / Knight Rider"

von kannAllesBesser! (Gast)


Lesenswert?

Nils L. schrieb:
> ISR(WDT_vect) {
>   return; //LEER
> }

... das ist auch keine leere ISR und das return ist völlig sinnfrei!
Hier wird fleißig push/pop der Register durchgeführt.

besser
#include interrupt.h
EMPTY_INTERRUPT (WDT_vect)

von Nils L. (luene)


Lesenswert?

Peter D. schrieb:
> Der Taster prellt.

Daran habe ich auch schon gedacht. Ich weiß nur nicht, wie ich meinen 
Code anpassen muss, damit der µC so reagiert wie er soll. Der Taster 
soll später durch einen Neigungsschalter ersetzt werden. Also wird es 
nicht zu verhindern sein, dass es prellt.

Matthias S. schrieb:
> Es lohnt sich, hier noch etwaige 'Pending' Interrupts zu löschen, bevor
> der MC schläft.

Wie kann ich diese löschen?
Im Datenblatt habe ich
1
 GIFR |= (1<<PCIF);
gefunden um Interrupts zurückzusetzen. Kann ich das so in meinen Code 
einfügen, vor dem sleep_mode()?

Oder gehört das an eine andere Stelle?

Peter D. schrieb:
> Ohne ISR erfolgt das Aufwachen durch Sprung zum Resetvector (0x0000).
> Der Interrupt ist ja freigegeben.

Ich habe ja eine ISR. Oder ist das für den externen Interrupt die 
falsche? Löse ich mit meinem Interrupt ungewollt einen Reset aus?

von kannAllesBesser! (Gast)


Lesenswert?

Nils L. schrieb:
> Ich habe ja eine ISR.

Lese erstmal das Datenblatt zum Thema Interrupts, nur mit raten kommst 
du nicht weit!

...
EMPTY_INTERRUPT (INT0_vect) //External Interrupt 0
EMPTY_INTERRUPT (PCINT0_vect) //Pin change Interrupt Request0

von S. Landolt (Gast)


Lesenswert?

Nils L. schrieb:
> Ich habe ja eine ISR.
Für den Watchdog (der gar nicht aktiviert wird).

> Löse ich mit meinem Interrupt ungewollt einen Reset aus?
Genau. Damit wird dieses Einsekundendelay nicht erreicht, und das 
Tasterprellen tobt sich voll aus.

von Carl D. (jcw2)


Lesenswert?

Nils L. schrieb:
> Peter D. schrieb:
>> Der Taster prellt.
>
> Daran habe ich auch schon gedacht. Ich weiß nur nicht, wie ich meinen
> Code anpassen muss, damit der µC so reagiert wie er soll. Der Taster
> soll später durch einen Neigungsschalter ersetzt werden. Also wird es
> nicht zu verhindern sein, dass es prellt.

Dein Code wird beim ersten Zappeln aufwachen, einen zufälligen Wert 
lesen, vermutlich oft den richtigen, aber eben nicht immer. Dann 
verbringt er knapp 900ms mit "Code senden", wobei das auch als 
Entprellung angesehen werden kann. In der Zeit konnte aber ein weiteres 
Zappeln am Pin einen weiteren PCInt0 anfordern, d.h. das zugehörige GIFR 
Bit setzen.

> Matthias S. schrieb:
>> Es lohnt sich, hier noch etwaige 'Pending' Interrupts zu löschen, bevor
>> der MC schläft.

Wenn man den Wunsch hat, daß der wegen Prellen während der Sendezeit 
anstehende Interrupt sich nicht auswirkt, dann sollte man das tun.

> Wie kann ich diese löschen?
> Im Datenblatt habe ich
>
1
 GIFR |= (1<<PCIF);
> gefunden um Interrupts zurückzusetzen. Kann ich das so in meinen Code
> einfügen, vor dem sleep_mode()?
Ja
> Oder gehört das an eine andere Stelle?
>
> Peter D. schrieb:
>> Ohne ISR erfolgt das Aufwachen durch Sprung zum Resetvector (0x0000).
>> Der Interrupt ist ja freigegeben.
>
> Ich habe ja eine ISR. Oder ist das für den externen Interrupt die
> falsche? Löse ich mit meinem Interrupt ungewollt einen Reset aus?

PCInt0_vect müßte der Richtige sein (oder nur PCInt_vect? das sagt mir 
in der Regel die IDE). Der ist aber eigentlich nur dazu da, den μC 
aufzuwecken und das PCIF Bit in GIFR zu löschen. Letzteres macht die HW 
selbstständig.


Trotz genereller Abneigung dagegen (und Hang zur PeDa-Entprellung) wäre 
hier ein RC-Tiefpaß die simpelste Lösung. Wartezeit zwischen den 
Schaltvorgängen hat man mehr als genug (fast 1s) und das einzige was 
erreicht werden muß, ist daß der Pegel am Pin nach dem ersten Interrupt 
für einige μs konstant bleibt. Und natürlich vor dem erneuten "warten 
auf Schaltvorgang" das PCIF löschen, um zwischenzeitliche Flanken zu 
"überlesen".

Oder eventuell doch mal PeDa-Entprellung ausprobieren und neue 
Erkenntnisse gewinnen.

von Nils L. (luene)


Lesenswert?

ok, danke schonmal für euer Feedback!

Ich habe nun einiges angepasst:
- "ISR(WDT_vect)" habe ich durch "EMPTY_INTERRUPT(PCINT0_vect);" ersetzt
- nach dem Aufwachen habe ich ein 10ms Delay eingebaut, das als 
Entprellung gut funktioniert.
- mit "GIFR |= (1<<PCIF);" werden pending Interrupts zurückgesetzt (wenn 
es richtig verstanden habe, ist dies aber überhaupt nicht nötig, da 
"sei()" dieses zurücksetzt?)
- außerdem wird nach dem Senden eines Codes nochmal der Status des 
Eingangspin abgefragt. Wenn sich während des Sendens der Status am Pin 
ändern sollte,
wird nach einem 100ms-Delay der andere Code gesendet. Erst dann geht der 
µc schlafen.

Vielen Dank für eure Tipps!

Hier ist nochmal der überarbeitete Code:
1
// DEFINES
2
#define F_CPU 600000    //600 khz
3
4
#define DATA_TIME_ON  980
5
#define DATA_TIME_OFF 350 //ON+OFF == ~1300µs
6
//Pins
7
#define DATA_ON   PORTB |=  (1<<PB4)    // PIN B4
8
#define DATA_OFF PORTB &= ~(1<<PB4)    // PIN B4
9
10
// INCLUDES
11
#include <avr/io.h>
12
#include <util/delay.h>
13
#include <avr/sleep.h>
14
#include <avr/interrupt.h>
15
16
// FUNCTION DECLARATION
17
void send_bit0();
18
void send_bit1();
19
void send_command(uint32_t code);
20
void doSleep();
21
22
// FUNCTIONS
23
int main(void)
24
{
25
  DDRB |=  (1 << DDB4);  //B4: 433Mhz Sender
26
  DDRB &= ~(1 << DDB3);  //B3: Taster
27
  PORTB |= (1 << PB3);  //B3: Pullup
28
  uint8_t state = 2;
29
30
  while(1){
31
    if (!(PINB&(1<<PINB3))){
32
      send_command(0x555);  //EIN
33
      state = 1;
34
    }
35
    else{
36
      send_command(0x552);  //AUS
37
      state = 0;
38
    }
39
    if ((!(PINB&(1<<PINB3))) == state){ //Status unverändert?
40
      doSleep();
41
      _delay_ms(10); //delay zum entprellen
42
    }
43
    else{
44
      _delay_ms(100); //Delay zwischen zwei Codes die gesendet werden
45
    }
46
  }
47
}
48
49
void doSleep(){
50
  GIMSK |= (1<<PCIE);                     // Enable Pin Change Interrupts
51
  PCMSK |= (1<<PCINT3);                   // Use PB3 as interrupt pin
52
  ADCSRA &= ~(1<<ADEN);                   // ADC off
53
  GIFR |= (1<<PCIF);  //Interrupts zurücksetzen
54
55
  sei();  //INTERRUPT EIN
56
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
57
  sleep_mode();
58
  cli();  //INTERRUPT AUS
59
  return;
60
}
61
62
void send_command(uint32_t code){        //sendet Code
63
  for(int cycle = 0; cycle < 8;cycle++){    //8 mal senden
64
    for (int pos = 24; pos > 0; pos--){    //sendet 24Bit
65
      if (code & (1 << (pos-1)))        //prüft ob Bit an dieser Stelle 1 oder 0
66
        send_bit1();  //Sendet 1
67
      else
68
        send_bit0();  //Sendet 0
69
    }
70
    send_bit0();  //Sendet End-Bit
71
    _delay_ms(10);  //Pause vor dem nächsten Signal
72
  }
73
}
74
75
76
void send_bit1(){
77
  DATA_ON;
78
  _delay_us(DATA_TIME_ON);
79
  DATA_OFF;
80
  _delay_us(DATA_TIME_OFF);
81
}
82
83
void send_bit0(){
84
  DATA_ON;
85
  _delay_us(DATA_TIME_OFF);
86
  DATA_OFF;
87
  _delay_us(DATA_TIME_ON);
88
}
89
90
EMPTY_INTERRUPT(PCINT0_vect);

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Nils L. schrieb:
> Im Datenblatt habe ich GIFR |= (1<<PCIF);
> gefunden um Interrupts zurückzusetzen. Kann ich das so in meinen Code
> einfügen, vor dem sleep_mode()?

Ja, das kannst du.

Nils L. schrieb:
> ist dies aber überhaupt nicht nötig, da
> "sei()" dieses zurücksetzt?

Das wäre mir neu. Wenn schon ein IRQ Flag gesetzt ist, und sei() 
ausgeführt wird, wird sofort dieser Interrupt bearbeitet. sei() setzt 
nichts zurück.

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.