avr-chat
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[avr-chat] ATXMega128A1: 32-bit quadrature decoder with compare match ?


From: Stefan Schoenleitner
Subject: [avr-chat] ATXMega128A1: 32-bit quadrature decoder with compare match ?
Date: Fri, 23 Sep 2011 09:26:55 +0200
User-agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.21) Gecko/20110831 Thunderbird/3.1.13

Hi,

I would like to read out the quadrature encoder of my stepping motor to get a 
relative position.
Like described in AVR appnote AVR1600, I'm using the event system and a 16-bit 
counter to do that.
Once it works, I'm planning to extend this to 3 axes.

My encoder has 1000 lines, meaning that per revolution I get 4000 pulses.
However, since the counter is only 16-bit, I could only count up to 
(2^16-1)/4000 ~= 16 revolution
before the counter overflows.
To solve the issue, I would like to cascade the timer with another 16-bit 
timer, so
that I will get a 32-bit quadrature up/down counter.

Unfortunately, this can not be done with the event system alone as there is 
only an overflow event,
that does not provide information on whether there was an over- or an underflow.
For that reason, I configured a second timer to count based on up/down events 
on a separate event channel.
In the overflow interrupt routine of the first timer, I'm then sending up/down 
counting events to that
channel so that the second timer counts up or down.

Theoretically this should work, but practically there can be jitter on the 
encoder signals leading to
multiple overflow interrupts.
So even if the motor has not been moved, I get multiple overflow interrupts 
that would cause
the second counter to be erroneously increased or decreased.

Other people seem to have the same issues as well: 
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=89873

To solve this issue, I came up with the following idea:
If there are two valid subsequent overflows, the counter would need to pass the
counter value in the middle of the period between those overflows.
So, if we assume that the counter period would be 4000 (i.e. TCC0.PER=4000), 
then
the middle would be 2000.
Now to ignore overflows caused by jitter, I would generally ignore subsequent 
overflows if they are
of the same type (whereas with type I mean either underflow or overflow).
However, only if the middle has been passed in between those overflows, I would 
also allow
subsequent overflows of the same type.
To implement that, my idea was to set up compare matching at the middle of the 
period (i.e. TCC0.CCA = TCC0.PER/2).
In the interrupt service routine of the compare match, I would administer a 
locking variable which influences whether subsequent overflows are counted.

When the motor moves, we can get the following timer overflow combinations:

* overflow followed by underflow:
        the motor moved right and then left again, this is a valid sequence

* underflow followed by overflow:
        the motor moved left and then right again, valid sequence

* underflow followed by underflow:
        the motor moves left, two cases are possible.
        Between the underflows the motor has either turned more than 16 
revolutions
        or the subsequent underflows were caused by jitter.
        Due to the compare matching we can distinguish those cases.

* overflow followed by overflow:
        the motor moves right, two cases are possible.
        <see above>



Surprisingly, it turned out that as soon as I enable the compare matching, the 
up/down counter
no longer works like it should.

Thus, without the compare matching, the counter is always in the range 0-4000 
(assuming that TCC0.PER=4000).
If there is an overflow, the counter wraps around from 4000 to 0.
In contrast, if there is an underflow, the counter wraps from 0 to 4000.

However, as soon as I turn on compare matching on channel A (i.e. TCCO0.CCA), 
the counter only
wraps when there is an underflow.
In the other direction, it does not wrap at 4000 like it should, but instead it 
continues to count up further.
With this setup, the compare match is correct (i.e. at 2000).

I also tried different compare match channels (i.e. channels B, C and D).
The result is even worse there: in some cases the counter no longer wraps at 
all and I get incorrect
overflows or compare matches at low counter values (i.e. at 7, 10, ...).

While I assume that for counters configured for quadrature decoding the compare 
registers might already be 
used internally, I found nothing that would confirm this in the datasheet.

Did anyone else have this problem ?
Do you have any hints to solve this issues ?

Here is the relevant code of my current implementation: 

----------------------------------------------------------------------------------------
#define ENC_PORT        PORTF           // quadrature encoder port

...
int main()
{
    ...
    ENC_PORT.DIRCLR = PIN0_bm | PIN1_bm;        // encoder0 pins
    PORTCFG.MPCMASK = PIN0_bm | PIN1_bm;        // multi-pin config mask
    ENC_PORT.PIN0CTRL = (ENC_PORT.PIN0CTRL & ~PORT_OPC_gm) | 
PORT_OPC_PULLUP_gc;    // enable pull-up on those pins

    PORTCFG.MPCMASK = PIN0_bm | PIN1_bm;        // multi-pin config mask
    ENC_PORT.PIN0CTRL = (ENC_PORT.PIN0CTRL & ~PORT_ISC_gm) | 
PORT_ISC_BOTHEDGES_gc; // sense both edges

    // setup event system, use channel0 for encoder0 input sense event
    EVSYS.CH0MUX = EVSYS_CHMUX_PORTF_PIN0_gc;                   // use events 
from PF0 on event channel 0
    EVSYS.CH0CTRL = EVSYS_QDEN_bm | EVSYS_DIGFILT_2SAMPLES_gc;  // enable 
quadrature decoder and 2 sample filtering

    // setup PORTC timer0 to handle the quadrature decoding action on event 
channel 0 (i.e. encoder0)
    TCC0.CTRLD = TC_EVACT_QDEC_gc | TC_EVSEL_CH0_gc;
    TCC0.PER = 4000;                                // period
    TCC0.CTRLA = TC_CLKSEL_EVCH0_gc;
    TCC0.INTCTRLA = TC_OVFINTLVL_LO_gc;             // low level interrupt on 
over-/underflow

    // 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

    // setting up an event based 32-bit up/down counter is tricky, as
    // there only is an overflow, but no underflow event.
    // Our solution is to determine the type of overflow in software and
    // send an UP/DOWN counting event manually.

    // setup event system, don't catch any hardware events on channel1
    EVSYS.CH1MUX = EVSYS_CHMUX_OFF_gc;

    TCC1.CTRLD = TC_EVACT_QDEC_gc | TC_EVSEL_CH1_gc; // up/down counting based 
on the up/down events on event ch1
    TCC1.PER = 0xffff;                               // 16 bit period
    TCC1.CTRLA = TC_CLKSEL_EVCH1_gc;                 // enable timer

    PMIC.CTRL |= PMIC_LOLVLEN_bm;                    // allow low level 
interrupts
    sei();                                           // enable interrupts

    while(1)
    {
      // loop forever
    }
}

ISR(TCC0_CCA_vect, ISR_BLOCK)
{
    printf_P(PSTR("middle: %i\n"), TCC0.CNT);
}

ISR(TCC0_OVF_vect, ISR_BLOCK)
{
    // check if we're counting up
    if (!(TCC0.CTRLFSET & TC0_DIR_bm))
    {
        if (TCC0.CNT==0)
        {
            printf_P(PSTR("up OV: %i\n"), TCC0.CNT);

            // send an increment event, see Table 6-2
            EVSYS.DATA |= (1<<1);
            EVSYS.STROBE |= (1<<1);
        }
    }
    // otherwise we're counting down
    else
    {
        if (TCC0.CNT>0)
        {
            printf_P(PSTR("dn OV: %i\n"), TCC0.CNT);

            // send a decrement event
            EVSYS.DATA &= ~(1<<1);
            EVSYS.STROBE |= (1<<1);
        }
    }
}
----------------------------------------------------------------------------------------


Cheers,
Stefan

P.S.:

I already posted this on the avrfreaks forum, but since I got no response so 
far, I thought the avr-chat mailing list would be a good place for these kinds 
of questions as well.
I hope that's ok.





reply via email to

[Prev in Thread] Current Thread [Next in Thread]