Forum: Mikrocontroller und Digitale Elektronik ATTiny13A PCINET0 Problem


von Thomas R. (live85)


Lesenswert?

Hallo, ich habe ein Problem mit meinem C-Code...

Kurze Beschreibung, ich möchte einen Kleinen Schalter Programmieren.
Das Programm soll den Pin PCINT0 (PB0) Überwachen und bei zwei Impulsen 
innerhalb 500 Mikrosekunden den Pin PCINT4 (PB4) auf Hi (LED) Setzen, 
bis wieder zwei Impulse Kurz hintereinander an PCINT0 ankommen, dann 
PCINT4 LOW...

Mein Problem ist das die Impulse nicht erkennt werden und die LED nur 
Blinkt.

PULL-UP ist aktiviert.
Und das Programm soll leicht zu ändern sein damit es auch zum ATmega168P
1
#include <util/delay.h>
2
#include <stdint.h>
3
#include <avr/io.h>
4
#include <avr/wdt.h>
5
#include <avr/interrupt.h>
6
#include <avr/power.h>
7
#include <avr/sleep.h>
8
9
#if defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__)
10
#define F_CPU 1000000UL
11
#define TIM0_COMPA_vect TIMER0_COMPA_vect
12
#define GIMSK PCICR
13
#define PCIE PCIE0
14
#define PCMSK PCMSK0
15
#endif
16
17
// PB0 - Switch (Input)
18
// PB1 - NC     (Output)
19
// PB2 - NC     (Output)
20
// PB3 - NC     (Output)
21
// PB4 - Out    (Output)
22
// PB5 - NC     (Output)
23
24
#define SWITCH  PB0
25
#define SWITCH_ON   ( !(PINB & (1 << SWITCH)) )
26
#define SWITCH_OFF  (   PINB & (1 << SWITCH)  )
27
28
#define OUT    PB4
29
#define OUT_ON    PORTB |=  (1 << OUT)
30
#define OUT_OFF    PORTB &= ~(1 << OUT)
31
#define OUT_TOGGLE  PORTB ^=  (1 << OUT)
32
33
#define MIN_PULSE_LEN  20
34
#define MAX_PULSE_LEN1  200  // 250 debug
35
#define MAX_PULSE_LEN2  250
36
#define MAX_EVENT_LEN  500
37
38
/*
39
               ATtiny13a
40
                  ----
41
    PB5/Reset 1 -|    |- 8 Vcc
42
          PB3 2 -|    |- 7 PB2
43
    Out - PB4 3 -|    |- 6 PB1
44
          GND 4 -|____|- 5 PB0 - Switch
45
46
 
47
               ATmega168P
48
                  ----
49
    PC6/Reset 1 -|    |- 28 PC5
50
      PD0/RxD 2 -|    |- 27 PC4
51
      PD1/TxD 3 -|    |- 26 PC3
52
        PD2 4 -|    |- 25 PC2
53
          PD3 5 -|    |- 24 PC1
54
          PD4 6 -|    |- 23 PC0
55
          Vcc 7 -|    |- 22 GND
56
          GND 8 -|    |- 21 ARef
57
          PB6 9 -|    |- 20 AVcc
58
         PB7 10 -|    |- 19 PB5
59
         PD5 11 -|    |- 18 PB4 - Out
60
         PD6 12 -|    |- 17 PB3
61
         PD7 13 -|    |- 16 PB2
62
Switch - PB0 14 -|____|- 15 PB1
63
 
64
 */
65
66
uint8_t flag0;
67
uint8_t flag1;
68
uint8_t flag2;
69
70
uint16_t timer1;
71
uint16_t timer2;
72
uint16_t timer3;
73
74
//**************************************************************************
75
76
ISR(TIM0_COMPA_vect)
77
{
78
  // Beginn des ersten Klicks
79
  if (SWITCH_ON && !flag1)
80
  {
81
    flag0 = 1;  // Beginn des ersten Klicks erkannt
82
    timer1++;  // Zähler für die Länge des ersten Klicks
83
  }
84
85
  if (flag0)
86
  {
87
    timer3++;  // Zähler für die Gesamtlänge
88
  }
89
90
  // Ende des ersten Klicks
91
  if (SWITCH_OFF && !flag1 && (timer1 > MIN_PULSE_LEN) && (timer1 < MAX_PULSE_LEN1))
92
  {
93
    flag1 = 1;  //Ende des ersten Klicks erkannt
94
  }
95
96
  // Beginn des zweiten Klicks
97
  if (SWITCH_ON && flag1)
98
  {
99
    flag2 = 1;  // Beginn des zweiten Klicks erkannt
100
    timer2++;  // Zähler für die Länge des zweiten Klicks
101
  }
102
103
  // Ende des zweiten Klicks
104
  if (SWITCH_OFF && flag1 && flag2 && (timer2 > MIN_PULSE_LEN) && (timer2 < MAX_PULSE_LEN2) && (timer3 < MAX_EVENT_LEN))
105
  {
106
    OUT_TOGGLE;
107
    
108
    flag0 = flag1 = flag2 = 0;
109
    timer1 = timer2 = timer3 = 0;
110
  }
111
112
  // Überprüfen auf Überlauf
113
  if (SWITCH_OFF && (timer3 >= MAX_EVENT_LEN))
114
  {
115
    flag0 = flag1 = flag2 = 0;
116
    timer1 = timer2 = timer3 = 0;
117
  }
118
}
119
120
//**************************************************************************
121
122
void Timer0_Init(void)
123
{
124
  TCCR0A = (1 << WGM00);
125
  TCCR0B = (0 << CS02) | (1 << CS01) | (1 << CS00);  // prescaler 64
126
  OCR0A  = (uint8_t)(F_CPU / (64.0 * 1e-3));  // 1 ms
127
  TIMSK0 = (1 << OCIE0A);  // Interrupt für OCR aktivieren
128
}
129
130
//**************************************************************************
131
132
int main(void)
133
{   GIMSK |= (1<<PCINT0);
134
  PORTB = 0;
135
  // Ausgänge definieren
136
    DDRB = (1 << DDB5) | (1 << DDB4) | (1 << DDB3) | (1 << DDB2) | (1 << DDB1);
137
138
#if defined (__AVR_ATtiny13A__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__)
139
  power_adc_disable();
140
#endif
141
  
142
  flag0 = 0;
143
  flag1 = 0;
144
  flag2 = 0;
145
  
146
  timer1 = 0;
147
  timer2 = 0;
148
  timer3 = 0;
149
150
  Timer0_Init();
151
152
    sei();    // Interrupts aktivieren
153
154
    for(;;)
155
    {
156
    set_sleep_mode(SLEEP_MODE_IDLE);
157
    sleep_mode();
158
    }
159
160
    return 0;   //nie erreicht
161
}

von Thomas E. (thomase)


Lesenswert?

Thomas R. schrieb:
> {   GIMSK |= (1<<PCINT0);

Was soll denn das werden? Ein Blick ins Datenblatt täte dir gut.

Der betreffende Pin muß im PCMSK-Register aktiviert werden, der 
richtige(!) Interrupt muß freigegeben werden und es muß eine ISR 
vorhanden sein. Ich sehe nichts von alledem.

von Stefan F. (Gast)


Lesenswert?

Das
1
uint8_t flag0;
2
uint8_t flag1;
3
uint8_t flag2;
4
5
uint16_t timer1;
6
uint16_t timer2;
7
uint16_t timer3;
wäre im Beruf ein Kündigungsgrund. Es macht den Code maximal 
unleserlich.

An anderen Stellen hast du #defines erfolgreich genutzt, um die 
Lesbarkeit zu verbessern. Daran sehe ich, dass du es besser kannst.

Schreibe über deine if Blöcke, wozu die gut sind. Was sollen sie 
bewirken?

Ist Switch ein Schalter? Wenn ja, hast du eventuell ms und µs 
durcheinander gebracht?

> OCR0A  = (uint8_t)(F_CPU / (64.0 * 1e-3));

Das ergibt nach meinem Taschenrechner OCR0A = 156250000
Kann nicht sein, oder?

Du könntest deinen ATmega168 mit einer Hand voll LED's bestücken, die 
bei Erreichen diverser Punkte in der ISR eingeschaltet werden. So siehst 
du, welche Bedingungen wann erfüllt sind.

von Stefan F. (Gast)


Lesenswert?

Anscheinend möchstest du Strom sparen, vielleicht wegen Batteriebetrieb?

Dann würde ich den Timer aber nicht ständig laufen lassen. Ich würde 
stattdessen den Timer erst beim ersten Switch-Impuls starten und nach 
Ablauf der 500 Mikrosekunden (wirklich µS?) wieder anhalten.

Deine LED kann weiter leuchten, während der µC schläft.

von Thomas R. (live85)


Lesenswert?

Die ISR ist doch vorhanden oder habe ich ein Totales verständnis Problem 
?
1
ISR(TIM0_COMPA_vect)
2
{
3
  // Beginn des ersten Klicks
4
  if (SWITCH_ON && !flag1)
5
  {
6
    flag0 = 1;  // Beginn des ersten Klicks erkannt
7
    timer1++;  // Zähler für die Länge des ersten Klicks
8
  }
9
10
  if (flag0)
11
  {
12
    timer3++;  // Zähler für die Gesamtlänge
13
  }
14
15
  // Ende des ersten Klicks
16
  if (SWITCH_OFF && !flag1 && (timer1 > MIN_PULSE_LEN) && (timer1 < MAX_PULSE_LEN1))
17
  {
18
    flag1 = 1;  //Ende des ersten Klicks erkannt
19
  }
20
21
  // Beginn des zweiten Klicks
22
  if (SWITCH_ON && flag1)
23
  {
24
    flag2 = 1;  // Beginn des zweiten Klicks erkannt
25
    timer2++;  // Zähler für die Länge des zweiten Klicks
26
  }
27
28
  // Ende des zweiten Klicks
29
  if (SWITCH_OFF && flag1 && flag2 && (timer2 > MIN_PULSE_LEN) && (timer2 < MAX_PULSE_LEN2) && (timer3 < MAX_EVENT_LEN))
30
  {
31
    OUT_TOGGLE;
32
    
33
    flag0 = flag1 = flag2 = 0;
34
    timer1 = timer2 = timer3 = 0;
35
  }
36
37
  // Überprüfen auf Überlauf
38
  if (SWITCH_OFF && (timer3 >= MAX_EVENT_LEN))
39
  {
40
    flag0 = flag1 = flag2 = 0;
41
    timer1 = timer2 = timer3 = 0;
42
  }
43
}


Und die Interrupts sind auch Freigegeben  (" sei(); ")
1
int main(void)
2
{  
3
  PORTB = 0;
4
  PCMSK |= (1<<PINB1);      
5
  GIMSK |= (1<<INT0);
6
  
7
    DDRB = (1 << DDB5) | (1 << DDB4) | (1 << DDB3) | (1 << DDB2) | (1 << DDB1); // Ausgänge definieren
8
9
#if defined (__AVR_ATtiny13A__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__)
10
  power_adc_disable();
11
#endif
12
  
13
  flag0 = 0;
14
  flag1 = 0;
15
  flag2 = 0;
16
  
17
  timer1 = 0;
18
  timer2 = 0;
19
  timer3 = 0;
20
21
  Timer0_Init();
22
23
    sei();    // Interrupts aktivieren
24
25
    for(;;)
26
    {
27
    set_sleep_mode(SLEEP_MODE_IDLE);
28
    sleep_mode();
29
    }
30
31
    return 0;   //nie erreicht
32
}

Sorry ich fange gerade erst an mit dem Programmieren, bis jetzt habe ich 
mir alles selbst erarbeitet, alles aus Büchern die ich schon zig mal 
gelesen habe....

von Thomas R. (live85)


Lesenswert?

Danke erstmal, das hat mir schon weiter geholfen.
Ich werde die Punkte mal in ruhe abarbeiten.

von Thomas E. (thomase)


Lesenswert?

Thomas R. schrieb:
> Die ISR ist doch vorhanden oder habe ich ein Totales verständnis Problem

Aber sowas von.

Das ist eine ISR. Und zwar für den Timer. Der PCINT bekommt seine 
eigene.

Thomas R. schrieb:
> Sorry ich fange gerade erst an mit dem Programmieren, bis jetzt habe ich
> mir alles selbst erarbeitet, alles aus Büchern die ich schon zig mal
> gelesen habe....

Dann mutest dir gerade zuviel zu. Spiel in Ruhe mit dem PCINT rum. Nur 
mit dem PCINT. Bis es läuft und du das verstanden hast.

von Peter D. (peda)


Lesenswert?

Thomas R. schrieb:
> zwei Impulsen
> innerhalb 500 Mikrosekunden

D.h. die Impulse sind nicht von mechanischen Tastern und man braucht 
keine Entprellung.

Thomas R. schrieb:
> uint8_t flag0;
> uint8_t flag1;
> uint8_t flag2;
>
> uint16_t timer1;
> uint16_t timer2;
> uint16_t timer3;

Damit bin ich raus. Nichts sagende Namen verschlechtern die Lesbarkeit 
extrem (auch für Dich).

Stefan U. schrieb:
> Anscheinend möchstest du Strom sparen, vielleicht wegen Batteriebetrieb?

Nach meiner Erfahrung hat es sich bewährt, zuerst nur mal die Funktion 
zu implementieren und als nächsten Schritt dann das Stromsparen.

von Stefan F. (Gast)


Lesenswert?

Ein weitere möglicher Ansatz, den Programmablauf transparenter zu 
gestalten, wäre der Zustandsautomat. Ein Beispiel
1
enum {
2
    WARTE_AUF_ERSTEN_START_IMPULS, WARTE_AUF_ERSTEN_START_IMPULS_ENDE,
3
    WARTE_AUF_ZWEITEN_START_IMPULS, WARTE_AUF_ZWEITEN_START_IMPULS_ENDE,
4
    AUSGANG_AN, 
5
    WARTE_AUF_ERSTEN_STOP_IMPULS, WARTE_AUF_ERSTEN_STOP_IMPULS_ENDE,
6
    WARTE_AUF_ZWEITEN_STOP_IMPULS, WARTE_AUF_ZWEITEN_STOP_IMPULS_ENDE,
7
    AUSGANG_AUS
8
} 
9
status=WARTE_AUF_ERSTEN_START_IMPULS;
10
11
int main(void)
12
{
13
  // TODO: I/O Pins konfigurieren und Pin-Change Interrupt einschalten
14
  set_sleep_mode(SLEEP_MODE_IDLE);
15
16
  while (1)
17
  {
18
    switch (status)
19
    {
20
        case WARTE_AUF_ERSTEN_START_IMPULS:
21
            sleep();
22
            if (SWITCH_ON)
23
            {
24
                starte_den_timer();
25
                status=WARTE_AUF_ERSTEN_START_IMPULS_ENDE;
26
            }
27
            break;
28
29
        case WARTE_AUF_ERSTEN_START_IMPULS_ENDE
30
            if (SWITCH_OFF)
31
            {
32
                status=WARTE_AUF_ZWEITEN_START_IMPULS;
33
            }
34
            break;
35
36
        case WARTE_AUF_ZWEITEN_START_IMPULS:
37
            if (SWITCH_ON)
38
            {                
39
                status=WARTE_AUF_ZWEITEN_START_IMPULS_ENDE;
40
            }
41
            if (TCNT0 > TIMEOUT_VALUE)
42
            {
43
                // Fange nochmal von vorne an, 
44
                // auf 2 Startimpulse zu warten
45
                stoppe_den_timer();
46
                status=WARTE_AUF_ERSTEN_START_IMPULS;
47
            }
48
            break;
49
50
        case WARTE_AUF_ZWEITEN_START_IMPULS_ENDE:
51
            if (SWITCH_OFF)
52
            {
53
                stoppe_den_timer();
54
                status=AUSGANG_AN;                
55
            }
56
            break;
57
        
58
        case AUSGANG_AN:
59
            OUT_ON;
60
            status=WARTE_AUF_ERSTEN_STOP_IMPULS;
61
            break;
62
63
        case WARTE_AUF_ERSTEN_STOP_IMPULS:
64
            sleep();
65
            if (SWITCH_ON)
66
            {
67
                starte_den_timer();
68
                status=WARTE_AUF_ERSTEN_STOP_IMPULS_ENDE;
69
            }
70
            break;
71
72
        case WARTE_AUF_ERSTEN_STOP_IMPULS_ENDE
73
            if (SWITCH_OFF)
74
            {
75
                status=WARTE_AUF_ZWEITEN_STOP_IMPULS;
76
            }
77
            break;
78
79
        case WARTE_AUF_ZWEITEN_STOP_IMPULS:
80
            if (SWITCH_ON)
81
            {
82
                starte_den_timer();
83
                status=WARTE_AUF_ZWEITEN_STOP_IMPULS_ENDE;
84
            }
85
            if (TCNT0 > TIMEOUT_VALUE)
86
            {
87
                // Fange nochmal von vorne an, 
88
                // auf 2 Stopimpulse zu warten
89
                stoppe_den_timer();
90
                status=WARTE_AUF_ERSTEN_STOP_IMPULS;
91
            }
92
            break;
93
94
        case WARTE_AUF_ZWEITEN_STOP_IMPULS_ENDE:
95
            if (SWITCH_OFF)
96
            {
97
                stoppe_den_timer();
98
                status=AUSGANG_AUS;       
99
            }
100
            break;
101
        
102
        case AUSGANG_AUS:
103
            OUT_OFF;
104
            status=WARTE_AUF_ERSTEN_START_IMPULS;
105
            break;
106
    }
107
  }
108
}

Für den Pin-Change Interrupt verwendest du eine leere ISR (sie muss aber 
existieren). Der Eingang wird hier stattdessen durch Polling abgefragt. 
Den Interrupt brauchst du aber, um aufzuwachen.

Den Timer nutzt du ohne Interrupt. Er soll nach dem Start einfach von 0 
an hoch zählen, aber langsam genug daß er nicht vor dem Erreichen des 
TIMEOUT_VALUE (500us) überlauft.

Mache Dir noch gedanken über die Aufwach-Zeit. Da es Dir hier um 
Mikrosekunden geht, könnte diese Zeit eine signifikante Rolle spielen. 
Da musst du mal ins Datenblatt schauen, wie lange das Aufwachen dauert.

von Stefan F. (Gast)


Lesenswert?

Das coole an so einem Konstrukt ist, daß du mehrere solcher Tasks quasi 
parallel abarbeiten kannst. Im Prinzip beliebig viele.

Dazu packst du jeden switch/case Block in eine eigene Prozedur und rufst 
sie dann in der Hauptschleife so auf:
1
int main(void)
2
{
3
  while (1)
4
  {
5
    task_oma();
6
    task_opa();
7
    task_mama();
8
    task_papa();
9
  }
10
}

von Thomas R. (live85)


Lesenswert?

Danke @Stefan Us werde das mal stück für stück durch arbeiten...

von Peter D. (peda)


Lesenswert?

Man kann sich den Ablauf vereinfachen, indem man eine Entprellroutine 
nimmt, die fertige Drückereignisse liefert.
Und die Zeit für die Doppeldruckerkennung kann man mit einem Scheduler 
machen.
Dafür gibt es auch einen Beispielcode:

Beitrag "Wartezeiten effektiv (Scheduler)"

Ist aber für einen Anfänger ne Menge Holz, beides zu verstehen. Man kann 
aber jeden Teil für sich analysieren. Im Prinzip reicht es auch erstmal 
aus, zu verstehen, was die einzelnen Funktionen machen und wie man sie 
aufruft.
Man sieht daran auch schön, wie man ein Problem in einzelne Teilaufgaben 
zerlegen kann.

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.