Great feedback, thanks for all suggestions.
I have it measuring pulses from both left and right encoders as small as 15 usec, with leading edges down to 33 usec apart. Interrupt service delay is 15-17 usec. However for these tiny pulses so close together, if both encoders interrupt near simultaneously, the second pulse is sometimes missed. In reality though with the larger 25 usec pulses, spaced MUCH farther apart (milliseconds) I don't expect much of an issue with missed pulses.
This is WITHOUT the extra sections saved in the #pragma and with zero print or servo glitches.
Code:
/*******************************************************************************
*
* FUNCTION: Left_Encoder_Int_Handler()
* PURPOSE: If enabled, the interrupt 1 handler is called when the
* interrupt 1 pin changes logic level. The edge that the
* interrupt 1 pin reacts to is programmable (see comments
* in the Initialize_Encoders() function, above)
* CALLED FROM: user_routines_fast.c/InterruptHandlerLow()
* PARAMETERS: None
* RETURNS: Nothing
* COMMENTS:
*
*******************************************************************************/
//#define MEASURE_PERIOD_FREQUENCY
//#define MEASURE_ISR
#define DBGOUT_LPULSESTART rc_dig_out16 = 1
#define DBGOUT_LPULSEEND rc_dig_out16 = 0
#define DBGOUT_RPULSESTART rc_dig_out15 = 1
#define DBGOUT_RPULSEEND rc_dig_out15 = 0
void Left_Encoder_Int_Handler(void)
{
#ifdef PULSE_WIDTH_METHOD
#ifdef MEASURE_ISR
unsigned int time_interruptend;
#endif
// values to be preserved between calls to this handler
#ifdef MEASURE_PERIOD_FREQUENCY
static unsigned long _PrevPulseStartLong = 0;
static unsigned long _PulseStartLong = 0;
#endif
unsigned int time_interruptstart;
unsigned int time_pulselength; // unsigned int for faster comparison
static unsigned int _PulseStart = 0;
// minimize function() calls to allow this routine to run as fast as possible
// on a 40 MHz PIC, this code takes approx 13 microseconds
// ************************************************************************************************
// DEBUG BY PUTTING LOGIC ANALYZER ON DIGOUT16 (must be configured for OUTPUT in user_routines.c) *
// WHICH MIMICS THE STATE OF THE OBSERVED PULSE *
// ************************************************************************************************
time_interruptstart = Timer_3_ReadTicks();
if (INTCON2bits.INTEDG2 == LEFT_ENCODER_PULSE_EDGE)
{
// pulse start
DBGOUT_LPULSESTART;
_PulseStart = time_interruptstart; // for pulse duration measurement
#ifdef MEASURE_PERIOD_FREQUENCY
_PulseStartLong = Timer_3_ReadTicksLong(); // for pulse-to-pulse measurement
#endif
}
else
{
// pulse end
DBGOUT_LPULSEEND;
time_pulselength = (time_interruptstart - _PulseStart); // units=tenths of microseconds
encoder_pulselen = time_pulselength; // for debugging
// adjust encoder count, based on pulse lengths. Manifest Constant is used because encoders
// on left and right sides of robot probably rotate in opposite directions.
if (time_pulselength > PULSE_LENGTH_FORWARD)
{
// reversing
_Left_Direction = -LEFT_ENCODER_FORWARD_DELTA;
_Left_NetPulses -= LEFT_ENCODER_FORWARD_DELTA;
}
else
{
// moving forward
_Left_Direction = LEFT_ENCODER_FORWARD_DELTA;
_Left_NetPulses += LEFT_ENCODER_FORWARD_DELTA;
}
_Left_PulseLength = time_pulselength;
#ifdef MEASURE_PERIOD_FREQUENCY
_Left_PulsePeriod = (_PulseStartLong - _PrevPulseStartLong); // units=tenths of microseconds
_Left_PulseFreq = (_Left_PulsePeriod == 0) ? 0 : (10000000#/_Left_PulsePeriod); // calculate Hertz (VERY SLOW CALCULATION!)
// set up for next time
_PrevPulseStartLong = _PulseStartLong;
#endif
}
// set to interrupt on opposite edge of pulse
INTCON2bits.INTEDG2 = INTCON2bits.INTEDG2 ^ 1; // flip the bit with Exclusive OR (XOR)
#ifdef MEASURE_ISR
time_interruptend = Timer_3_ReadTicks();
encoder_isrtime = (time_interruptend - time_interruptstart);
#endif
#else
// The encoder's phase-A signal just transitioned
// from zero to one, causing this interrupt service
// routine to be called. We know that the encoder just
// rotated one count or "tick" so now check the logical
// state of the phase-B signal and increment or decrement
// the _Encoder_Count variable.
if (LEFT_ENCODER_PHASE_B_PIN == LEFT_PHASE_B_FORWARD)
{
Left_Encoder_Count += LEFT_ENCODER_FORWARD_DELTA;
}
else
{
Left_Encoder_Count -= LEFT_ENCODER_FORWARD_DELTA;
}
#endif
}
Code:
#pragma code
#pragma interruptlow InterruptHandlerLow save=PROD /* You may want to save additional symbols. */
void InterruptHandlerLow ()
{
unsigned char int_byte;
if (PIR1bits.TMR1IF && PIE1bits.TMR1IE) // wallclock.c timer interrupt?
{
PIR1bits.TMR1IF = 0; // clear the interrupt flag
Timer_1_Int_Handler();
}
else if (PIR2bits.TMR3IF && PIE2bits.TMR3IE) // measure.c timer interrupt?
{
PIR2bits.TMR3IF = 0; // clear the interrupt flag
Timer_3_Int_Handler(); // counts the number of overflows
}
else if (INTCON3bits.INT2IF && INTCON3bits.INT2IE) // left encoder interrupt?
{
INTCON3bits.INT2IF = 0; // clear the interrupt flag [91]
Left_Encoder_Int_Handler(); // call the left encoder interrupt handler (in encoder.c)
}
else if (INTCON3bits.INT3IF && INTCON3bits.INT3IE) // right encoder interrupt?
{
INTCON3bits.INT3IF = 0; // clear the interrupt flag [91]
Right_Encoder_Int_Handler(); // call right encoder interrupt handler (in encoder.c)
}
Quote:
|
The glitches you're seeing are almost certainly due to data sections getting clobbered in your interrupt handler, as you suspect.
|
Indeed, there were two - but not in the encoder code.
I found and removed a % (MOD) operator in my wallclock code (it interrupts every millisecond). AND I took at look at the printf_lib.c code we had been using (modified to print LONG variables) before I installed Kevin's serial_port.c interrupt-driven code, and found it uses / 10 (divide by 10) to extract the lower-order digit.
So without the MOD and DIVIDE operators I was able to remove the both the section (".tmpdata") and section("MATH_DATA") context saves from the #pragma, and successfully measure the small pulses

And still print without glitches, and no servo glitches - perfect!
Quote:
|
What if the timer overflows during this? I don't know if you are accounting for that in your code, but that would definitely cause glitches.Maybe if you reset the timer every edge, reading before the reset, that might work better.
|
I am using Timer3 free-running at 1:1 clock rate (so 1/10 microsecond resolution) which interrupts on overflow to update an overflow counter. I don't want to reset the timer, because I want to measure pulses from multiple sources (two wheels, at least!) and don't want to dedicate a timer to each, even if there were enough of them.
Quote:
|
I assume you're using the PIC 8722 based 2006 controller. There are a number of silicon flaws in that chip that some of us never got our interrupt and timer routines to work properly with in spite of the published "fixes".
|
I am debugging on the 8520 already. I will find out tonight whether the code transfers successfully to the 8722 controller.
Quote:
|
use a custom circuit using a dedicated PIC to monitor the output from each gear tooth sensor.
|
The two-phase encoders with A and B pulses are dead simple to code (see Kevin Watson's encoder code) ... so I would think a custom electronic circuit should provide the same kind of output. I don't think there is an absolute need to oversample (report every 20 teeth, as suggested). You should be able to handle a thousand or two pulses per second - far more than a typical encoder on a wheel will output.
There are lots of places to use encoders on a robot, and a limited number of places to plug them in on the controller. If you are going to the trouble of writing a PIC preprocessor, why not have it handle multiple detectors?
Quote:
|
The port b interrupts may be adequate. They fire on both transitions.
|
If I can do this it will make it possible to reduce the number of missed pulses, and speed up the ISR code due to not having to reprogram the edge detection.