ATtiny1624, Timer B läuft zu schnell ab

#7875517
Lesenswert?

Moin

Bisweilen etwas frustrierend, woran man(n) scheitert...

Ich möchte Timer/Interruptgesteuert eine LED im Sekundentakt blinken lassen. Also folgendes zu Papier gebracht:

1
#include <xc.h>
2
#include <avr/interrupt.h>
3

4
volatile uint32_t milli_ticks;
5

6
void led1on()... 
7
void led1off()...
8
 
9
ISR(TCB0_INT_vect) {
10
  milli_ticks++;
11
}
12

13
void main(void) {
14

15
  //Timer auf 20000 (bei 20MHz Takt -> 1 ms)
16
  TCB0.CCMPL = 0b00100000;
17
  TCB0.CCMPH = 0b01001110;
18
  //Enable Timer
19
  TCB0.CTRLA |= 1;
20
  //Enable Capture
21
  //DS: Bit 0 – CAPT Capture Interrupt Enable
22
  //    Writing this bit to ‘1’ enables interrupt on capture.
23
  TCB0.INTCTRL |= 1;
24

25
  sei();
26

27

28
  while (1) {
29
    //0-1000 an, 1001-2000 aus
30
    if (milli_ticks > 100000) {
31
      milli_ticks = 0;
32
    } else if (milli_ticks > 50000) {
33
      led1off();
34
    } else {
35
      led1on();
36
    }
37

38
  }
39
}

Jetzt war meine Idee, dass ich jeweils 1.000 1-ms-Ticks abwarte, und dann ist die Sekunde um. Tatsächlich muss ich etwa 50.000 Ticks warten.

Denk- oder Programmfehler?

Gruß Holger

Auszug aus dem DS:

By default, the TCB is in Periodic Interrupt mode. Follow these steps to start using it:

  • 1.Write a TOP value to the Compare/Capture (TCBn.CCMP) register.
  • 2.Optional: Write the Compare/Capture Output Enable (CCMPEN) bit in the Control B (TCBn.CTRLB) register to ‘1’. This will make the waveform output available on the corresponding pin, overriding the value in the corresponding PORT output register. The corresponding pin direction must be configured as an output in the PORT peripheral.
  • 3.Enable the counter by writing a ‘1’ to the ENABLE bit in the Control A (TCBn.CTRLA) register. The counter will start counting clock ticks according to the prescaler setting in the Clock Select (CLKSEL) bit field in the Control A (TCBn.CTRLA) register.
  • 4.The counter value can be read from the Count (TCBn.CNT) register. The peripheral will generate a CAPT interrupt and event when the CNT value reaches TOP.
  • 4.1. If the Compare/Capture register is modified to a value lower than the current CNT, the peripheral will count to MAX and wrap around.
  • 4.2.At MAX, an OVF interrupt and event will be generated.
#7875521
Lesenswert?

Ein Programmierfehler ist zumindest der nicht-atomare Zugriff auf "millis".

Besser:

1
uint32_t get_millis() {
2
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
3
     return milli_ticks;
4
  }
5
}

und das konsequent zum Zugriff auf die millis-Variable verwenden.

Dann: die Variable durchlaufen lassen. Deine Timer dann so umformulieren:

1
static uint32_t last_action_millis;
2
if (get_millis() - last_action_millis > 5000) {
3
   do_something();
4
   last_action_millis = get_millis();
5
}

(Achtung, nicht "if (last_action_millis+5000 < get_millis()) {...". Ließt sich ähnlich, ist was anderes)

#7875537
Lesenswert?

"CAPT Capture Interrupt Flag ... This bit is cleared by writing a ‘1’ to it or ..."

Mööp. Das scheint mir die 90%-Antwort zu sein. Die restlichen 10% gehen an den atomic-Zugriff. Vielen Dank für die Hinweise.

Da hätte ich mir eine Zeile mehr der Datenblatt-Beschreibung gewünscht, wenn da schon explizit aufgeführt wird, wie man den periodischen Interrupt umsetzt.

Jetzt ist eine Sekunde ca. 200 Ticks lang. Müssten doch eigentlich so um die 1000 sein, +/- X Prozent. Andere Größenordnung, immmer noch unerwartet.

Ich hänge den Code nachmal an. Vielleicht können wir das auch noch aufklären.

Angehängte Dateien:
#7875549
Lesenswert?

S. L. schrieb:

"CAPT Capture Interrupt Flag ... This bit is cleared by writing a ‘1’ to it or ..."

Fehlt - die ISR wird also wohl permanent aufgerufen.

Im Unterschied zu den klassik Tiny/Mega muß bei den neuen Typen jedes Interruptflag der Timer händisch gelöscht werden. Automatisches Löschen bei Eintritt in die ISR gibt es nicht mehr. ISR_NOBLOCK ist somit verboten.

#7875685
Lesenswert?

Der Code sollte nicht länger sein als nötig. Hier ist ein Beispiel:

1
#include <avr/io.h>
2

3
uint8_t cnt;                               // SW counter
4

5
int main(void)
6
{
7
  _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm);  // 10 MHz Main Clock
8

9
  EVSYS.CHANNEL0 = EVSYS_CHANNEL0_TCB0_CAPT_gc;       // Event generator: TCB0 CAPT
10
  EVSYS.USERTCB1COUNT = EVSYS_USER_CHANNEL0_gc;       // Event user: TCB1 COUNT
11

12
  TCB0.CCMP = 0x1387;                                 // 1 kHz
13
  TCB0.CTRLA = TCB_CLKSEL_DIV2_gc | TCB_ENABLE_bm;    // CLK_PER/2, enable TCB0
14

15
  TCB1.CCMP = 0x0031;                                 // 20 Hz
16
  TCB1.CTRLA = TCB_CLKSEL_EVENT_gc | TCB_ENABLE_bm;   // select EVENT as clock, enable TCB1
17

18
  PORTB.DIRSET = PIN0_bm;                             // PB0 output (LED)
19

20
  while(1) 
21
  {
22
    if(TCB1.INTFLAGS)
23
    {
24
      TCB1.INTFLAGS = TCB_CAPT_bm;                    // clear TCB1 interrupt flag
25
      cnt++;
26
      if(cnt == 19)
27
      {
28
        PORTB.OUTSET = PIN0_bm;                       // LED ON        
29
      }
30
      else if(cnt == 20)
31
      {
32
        PORTB.OUTCLR = PIN0_bm;                       // LED OFF
33
        cnt = 0;   
34
      }
35
    }
36
  }
37
}

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren