Forum: Mikrocontroller und Digitale Elektronik ATXMega128A1: 32-bit quadrature decoder mit compare match ?


von stefan s. (stef89)


Lesenswert?

Hallo,

da ich gerne auf 3 Achsen die quadrature encoder meiner Schrittmotoren
auslesen wuerde, verwende ich entsprechend der AVR appnote AVR1600
das Event System und einen 16-bit Counter.
Den Zaehlerstand moechte ich als relative Positionsangabe verwenden.

Mein Encoder hat 1000 Zeilen, wodurch ich pro Umdrehung 4000 
Counter-Takte erhalte.
Bei einem 16-bit Counter kommt es somit spaetestens alle
(2^16-1)/4000 ~= 16 Umdrehungen zu einem Overflow.
Um das Problem zu loesen, verwende ich einen zweiten kaskadierten 16-bit 
Counter, sodass ein 32-bit quadrature counter entsteht.

Den zweiten Counter takte ich ebenfalls ueber das Event System, jedoch 
loese ich die UP/DOWN count Events manuell in der Counter Overflow ISR 
aus.
Das funktioniert soweit ganz gut, allerdings kann es leider ab und zu 
etwa durch Jitter vorkommen, dass zwei Overflow Interrupts ausgeloest 
werden, obwohl sich der Encoder nur minimal bewegt hat.

Dieses Problem scheint ebenso bei Anderen aufzutreten:
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=89873


Basierend auf der Idee, dass bei zwei gueltigen Overflows die 
Counter-Mitte zumindest einmal ueberschritten werden muss, wuerde ich 
gerne folgenden Lösungsansatz umsetzen:

Grundsaetzlich werden aufeinanderfolgende gleiche Overflows ignoriert, 
solange nicht zwischen den Overflows auch die Counter-Mitte 
ueberschritten wird.
Dazu setze ich fuer den unteren Counter eines der Compare-Register (z.B. 
TCC0.CCA) auf die Mitte der Counter Periode (TCC0.PER/2).
Im der ISR zum compare-match Interrupt kann ich dann eine globale 
lock-Variable steuern, die angibt, ob bei ein jeweiliger 
Counter-Overflow gezaehlt werden soll oder nicht.
Ich kann domit auf einfache Weise entscheiden, ob ein Counter Overflow 
gueltig ist, oder ob der Overflow etwa durch Jitter aufgetreten ist.


Leider funktioniert die praktische Umsetzung aus mir derzeit nicht 
bekannten Gruenden nicht, da je nach verwendetem Compare-Register 
unterschiedliches "Counter-Fehler" auftreten:

Ohne compare matching funktioniert der up/down Counter. Bei jedem 
under-/overflow wird der Counter korrekt zurueckgesetzt.
Setze ich etwa die Periode auf 4000 (TCC0.PER=4000), so arbeitet der 
Counter korrekt:
Wird hinaufgezaehlt und es kommt zu einem Overflow, springt der Counter 
von 4000-->0.
Wird hinuntergezaehlt und es kommt zu einem Underfow, springt der 
Counter von 0-->4000.

Verwende ich nun das Compare-A Register (TCC0.CCA) und setze es auf 2000 
(dh. die Haelfte der Periode), so stimmen zwar die Compare-Match 
Interrupts, jedoch wird der Counter nur mehr bei einem Underflow
zurueckgesetzt.
Wird hinuntergezaehlt und es kommt zu einem Underfow, springt der 
Counter von 0-->4000.
Wird hinaufgezaehlt, kommt es allerdings nicht mehr bei TCC0.PER (also 
bei 4000) zum Overflow.
Stattdessen zaehlt der Counter einfach weiter hinauf.

Verwende ich die Compare-Register B, C oder D, so bekomme ich auf einmal 
Compare-Matches und Overflows bei wesentlich niedrigeren und scheinbar 
zufaelligen Counter-Staenden (z.B. bei 7, 10, ....).


* Weiss jemand warum dieses eigenartige Verhalten auftritt ?

Ich wuerde darauf tippen, dass bei einem up/down Counter intern evt. 
schon alle 4 Compare-Register
verwendet werden, obwohl ich nichts entsprechendes im Datenblatt finden 
konnte.

Meine derzeitige Implementation sieht wie folgt aus:

lg,
Stef
1
#define ENC_PORT        PORTF           // quadrature encoder port
2
3
...
4
int main()
5
{
6
    ...
7
    ENC_PORT.DIRCLR = PIN0_bm | PIN1_bm;        // encoder0 pins
8
    PORTCFG.MPCMASK = PIN0_bm | PIN1_bm;        // multi-pin config mask
9
    ENC_PORT.PIN0CTRL = (ENC_PORT.PIN0CTRL & ~PORT_OPC_gm) | PORT_OPC_PULLUP_gc;    // enable pull-up on those pins
10
11
    PORTCFG.MPCMASK = PIN0_bm | PIN1_bm;        // multi-pin config mask
12
    ENC_PORT.PIN0CTRL = (ENC_PORT.PIN0CTRL & ~PORT_ISC_gm) | PORT_ISC_BOTHEDGES_gc; // sense both edges
13
14
    // setup event system, use channel0 for encoder0 input sense event
15
    EVSYS.CH0MUX = EVSYS_CHMUX_PORTF_PIN0_gc;   // use events from PF0 on event channel 0
16
    EVSYS.CH0CTRL = EVSYS_QDEN_bm | EVSYS_DIGFILT_2SAMPLES_gc;  // enable quadrature decoder and 2 sample filtering
17
18
    // setup PORTF timer0 to handle the quadrature decoding action on event channel 0 (i.e. encoder0)
19
    TCC0.CTRLD = TC_EVACT_QDEC_gc | TC_EVSEL_CH0_gc;
20
    TCC0.PER = 4000;                                // period
21
    TCC0.CTRLA = TC_CLKSEL_EVCH0_gc;
22
    TCC0.INTCTRLA = TC_OVFINTLVL_LO_gc;             // low level interrupt on over-/underflow
23
24
    // set up compare matching in the middle of the period
25
    TCC0.CCA = TCC0.PER/2;
26
    TCC0.CTRLB |= TC0_CMPA_bm;            // enable compare match on compare channel A
27
    TCC0.INTCTRLB |= TC_CCAINTLVL_LO_gc;      // low level interrupt on compare match
28
29
    // setting up an event based 32-bit up/down counter is tricky, as
30
    // there only is an overflow but no underflow event.
31
    // Our solution is to determine the type of overflow in software and
32
    // send an UP/DOWN counting event manually.
33
34
    // setup event system, don't catch any hardware events on channel1
35
    EVSYS.CH1MUX = EVSYS_CHMUX_OFF_gc;
36
37
    TCC1.CTRLD = TC_EVACT_QDEC_gc | TC_EVSEL_CH1_gc;    // up/down counting based the up/down events on event ch1
38
    TCC1.PER = 0xffff;                              // 16 bit period
39
    TCC1.CTRLA = TC_CLKSEL_EVCH1_gc;                // enable timer with prescaler 1
40
41
    PMIC.CTRL |= PMIC_LOLVLEN_bm;                   // allow low level inetrrupts
42
    sei();                                          // enable interrupts
43
44
    while(1)
45
    {
46
      // loop forever
47
    }
48
}
49
50
ISR(TCC0_CCA_vect, ISR_BLOCK)
51
{
52
    printf_P(PSTR("middle: %i\n"), TCC0.CNT);
53
}
54
55
ISR(TCC0_OVF_vect, ISR_BLOCK)
56
{
57
    // check if we're counting up
58
    if (!(TCC0.CTRLFSET & TC0_DIR_bm))
59
    {
60
        if (TCC0.CNT==0)
61
        {
62
            printf_P(PSTR("up OV: %i\n"), TCC0.CNT);
63
64
            // send an increment event, see Table 6-2
65
            EVSYS.DATA |= (1<<1);
66
            EVSYS.STROBE |= (1<<1);
67
        }
68
    }
69
    // otherwise we're counting down
70
    else
71
    {
72
        if (TCC0.CNT>0)
73
        {
74
            printf_P(PSTR("dn OV: %i\n"), TCC0.CNT);
75
76
            // send a decrement event
77
            EVSYS.DATA &= ~(1<<1);
78
            EVSYS.STROBE |= (1<<1);
79
        }
80
    }
81
}

von stefan s. (stef89)


Lesenswert?

Hallo,

stefan m. schrieb:
>     // set up compare matching in the middle of the period
>     TCC0.CCA = TCC0.PER/2;
>     TCC0.CTRLB |= TC0_CMPA_bm;            // enable compare match on compare 
channel A
>     TCC0.INTCTRLB |= TC_CCAINTLVL_LO_gc;      // low level interrupt on compare 
match

ich habe das Problem gefunden.
Compare matching auf channel A wird nicht mittels TC0_CMPA_bm,
sondern mit TC0_CCAEN_bm aktiviert.
Indem ich die falsche bitmask in das Register geschrieben habe, wurde 
auch die waveform generation mode veraendert was u.A. zu dem 
eigenartigen Verhalten gefuehrt hat.

Nachdem das compare matching jetzt in der Mitte der Periode geht, sollte 
der 32-bit quadrature decoder auch bald funktionieren ;)

lg,
stef

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.