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. _______________________________________________ AVR-chat mailing list [email protected] https://lists.nongnu.org/mailman/listinfo/avr-chat
