QuadEncoder Compare Interrupt

moult

Member
Hello,

I am using two quadrature encoders via the hardware peripherals through the tremendous library by mjs513 on Github and I am having trouble figuring out where to start with a compare interrupt. I have pulled through the examples and the code and I can't find something analogous to what I need to do.

I am looking to create a precise pulse every X number of counts in the quadrature register. I know I have to set positionMatchMode = ENABLE and put the value of the target in positionCompareValue, but what after that?

Do I create a ISR() with the interrupt vector in it, clear the flags, and then return? From the chip manual, it looks like there is the ipi_int_comp core interrupt, but I am unclear how to use it. Or do you create a master ISR and test for flags? If so, how do I determine the flag for each encoder channel? And do I enable this with the enableInterrupts in the Quadencoder library?

So many questions!

I suppose it extends to a larger question of how to determine the way to attach interrupts to registers rather than pins.

Thank you in advance.
 
I am using two quadrature encoders via the hardware peripherals through the tremendous library by mjs513 on Github and I am having trouble figuring out where to start with a compare interrupt. I have pulled through the examples and the code and I can't find something analogous to what I need to do.

I am looking to create a precise pulse every X number of counts in the quadrature register. I know I have to set positionMatchMode = ENABLE and put the value of the target in positionCompareValue, but what after that?

Do I create a ISR() with the interrupt vector in it, clear the flags, and then return? From the chip manual, it looks like there is the ipi_int_comp core interrupt, but I am unclear how to use it. Or do you create a master ISR and test for flags? If so, how do I determine the flag for each encoder channel? And do I enable this with the enableInterrupts in the Quadencoder library?

I haven't used that feature, but I'll try to help you get started. Take a look at the source code for the library. There are just two files (QuadEncoder.h/cpp). In the header file, you will see that there are fields in the configuration structure for enabling the position match. Position match is disabled by default. To enable it, you would set the appropriate field in the config structure before calling Init(config). Each of the 4 x QuadEncoder modules objects has just one interrupt, and that interrupt can be triggered by any one of a number of conditions, including position match. To determine the source of an interrupt, the ISR must check the status register to see which of the possible condition flags are set, then process the interrupt and clear the flag(s). The library includes the ISR, so you don't have to write that yourself. It's the last function in the CPP file. Near the bottom, you'll see that when a CMPIRQ interrupt has occurred, the ISR sets compareValueFlag = 1, and then clears the associated status flag. Within your program, you can poll the compareValueFlag, and when it's equal to 1, you'll know that a position match has occurred, and you can then set compareValueFlag = 0. I'll give it a try myself when I can.
 
Hello,

I am using two quadrature encoders via the hardware peripherals through the tremendous library by mjs513 on Github and I am having trouble figuring out where to start with a compare interrupt. I have pulled through the examples and the code and I can't find something analogous to what I need to do.

I am looking to create a precise pulse every X number of counts in the quadrature register. I know I have to set positionMatchMode = ENABLE and put the value of the target in positionCompareValue, but what after that?

Do I create a ISR() with the interrupt vector in it, clear the flags, and then return? From the chip manual, it looks like there is the ipi_int_comp core interrupt, but I am unclear how to use it. Or do you create a master ISR and test for flags? If so, how do I determine the flag for each encoder channel? And do I enable this with the enableInterrupts in the Quadencoder library?

So many questions!

I suppose it extends to a larger question of how to determine the way to attach interrupts to registers rather than pins.

Thank you in advance.

Just to add on to what @joepasquariello posted - which is all good info. If you take a look at the QuadEncoder.ino sketch you will see that what he explained is used with a library call:
Code:
  if(myEnc2.compareValueFlag == 1) {
    //myEnc2.init();
    //resets counter to positionInitialValue so compare 
    //will hit every 200
    myEnc2.write(myEnc2.EncConfig.positionInitialValue);
    Serial.print("Compare Value Hit for Encoder 2:  ");
    Serial.println(myEnc2.compareValueFlag);
    Serial.println();
    myEnc2.compareValueFlag = 0;
  }
 
Thank you for all of the information joepasquariello and mjs513. That all makes sense, but I am still confused as how to execute custom code when the ISR is triggered. I am sure I am being obtuse, and I am a bit rusty at this, but I want to make sure that our timing is as precise as possible.

In the Quadencoder.ino sketch (the code that is posted above) the if statement is in the main polling loop. I would like to have a pseudo code interrupt that does the following:
Code:
if ( myEnc.compareValueFlag == 1 ){
  OutputPinTimer.begin(pulseEnd, HIGH_PULSE_TIME);
  digitalWriteFast(OUTPUT_PIN, HIGH);
  myEnc.compareValueFlag = 0;
  myEnc.EncConfig.positionCompareValue = NEXT_VALUE;
}


I just can't figure out where/how to have a function called by the compare interrupt.

Thanks again for the help.
 
Last edited:
I just can't figure out where/how to have a function called by the compare interrupt.

Looking at the QuadEncoder library, I don't see fields/functions to set up a user function to be called by an interrupt. There are 4 functions named isrEnc1/2/3/4 which are the actual ISRs that get "installed" via the attachInterrupt() function, and that occurs in the Init() function. Those functions each call checkAndProcessInterrupt(), and that function finally calls isr(). @mjs513 may have a better/easier suggestion, but the only thing I can think of doing that does not involve changing the library is (a) write your own ISR to replace isrEncX (where X is 1-4), using the code in the library as an example, and then (b) after calling Init(), call attachInterrupt() a second time, passing it your new ISR to override the ISR installed by the library.

Now, with all of that said, can you tell us more about what you're trying to do? There may be other, more straightforward ways to accomplish what you want rather than using QuadEncoder.
 
I spent some time today testing the QuadEncoder Compare Interrupt, and I think there is a change required to the library. When the encoder count reaches the compare value, the interrupt occurs as expected. The isr() function calls clearStatusFlags(_positionCompareFlag, index). This does clear the CMPIRQ flag, but because the interrupt is still enabled (CMPIE = 1), another interrupt occurs immediately, and the cycle repeats for as long as the encoder count matches the compare value. My fix was to add one line to isr() to disable the compare interrupt (CMPIE = 0), and then re-enable it in loop(), depending on what I'm trying to do, such as move the compare value ahead by some amount. This may not be the right fix for others, depending on what they are trying to do.

Here is my change to QuadEncoder::isr()

Code:
    if (ENC_CTRL_CMPIRQ_MASK == (ENC_CTRL_CMPIRQ_MASK & channel[index].ENC->CTRL))
    {
	compareValueFlag = 1;
	// 12/03/21 JWP add line below to disable compare interrupt
	channel[index].ENC->CTRL &= ~ENC_CTRL_CMPIE_MASK;
	clearStatusFlags(_positionCompareFlag, index);
    }

and here is my test sketch, which used the encoder simulation library EncSim. Note that you should connect pins 0<->3 and 1<->2 to get "positive rotation".

Code:
#include "QuadEncoder.h"
#include "EncSim.h"

EncSim EncSim2(0,1);             // encoder simulation on pins 0/1
QuadEncoder myEnc2(2, 2, 3, 0);  // QuadEncoder (2) on pins 2/3, with no pullups
                                 
void setup()
{
  while(!Serial && millis() < 4000);

  uint32_t ppr = 1200;      // pulses per rev (*4 for quadrature)
  float    rpm = 0.1;       // rpm = (60*frq)/(ppr*4)
  float    frq = (ppr*4)*(fabs(rpm)/60);  // keep this relationship

  EncSim2.begin();        // begin()
  EncSim2         // CONFIGURATION
  .setPhase( 90 )       // normal 90 deg phase shift
  .setTotalBounceDuration( 0 )      // no bouncing
  .setPeriod( ppr*4 )       // marker every ppr A/B pulses
  .setFrequency( frq );       // frequency = f(ppr,rpm)

  EncSim2.setContinuousMode( true );    // don't stop at target
  EncSim2.moveRelAsync( rpm >= 0.0 ? +1 : -1 ); // +/- direction
  
  myEnc2.setInitConfig();  //
  myEnc2.EncConfig.positionInitialValue = 190; 
  myEnc2.EncConfig.positionMatchMode = ENABLE;
  myEnc2.EncConfig.positionCompareValue = 200;
  myEnc2.init();
}

void loop()
{
  static uint32_t mCurPosValue = 0;
  static uint32_t mPrevPosValue = 0;
  
  /* This read operation would capture all the position counter to responding hold registers. */
  mCurPosValue = myEnc2.read();

  if (mCurPosValue != mPrevPosValue) {
    Serial.printf( "%ld\n", mCurPosValue );
    mPrevPosValue = mCurPosValue;
  }

  if (myEnc2.compareValueFlag == 1) {
    Serial.printf( "Compare Match at %1d\n", myEnc2.EncConfig.positionCompareValue );
    myEnc2.compareValueFlag = 0;

    //resets counter to positionInitialValue so compare will hit every 200
    myEnc2.write( myEnc2.EncConfig.positionInitialValue );
    // re-enable the CMPIE
    myEnc2.channel[2].ENC->CTRL |= ENC_CTRL_CMPIE_MASK;
  }
}
 
Another small issue in the QuadEncoder library, there is some confusion between enabling position compare interrupts and configuration of the POSMATCH output signal. The positionMatchMode field of the config struct is being used to enable/disable position compare interrupts (CTRL::CMPIE) and also to set the Output Control field (CTRL2::OUTCTL). Those two things are fully independent, so they require separate configuration fields. Config field positionMatchMode could be renamed to positionCompareMode (DISABLE=0, ENABLE=1), and a new field OutputControlMode could be added to set the Output Control field of CTRL2. That field determines what will trigger the POSMATCH output, which can happen with or without a position compare interrupt. The POSMATCH output can be used to control a timer, which may actually be what the OP is looking for.

@mjs513, I can submit a PR regarding this change, but I'll wait until you reply regarding the previous post about what action to take on position compare interrupts. I think disabling the interrupt on match makes the most sense, and if so, I think it would make sense to also have a function to update positionCompareValue and optionally enable the compare interrupt.
 
Another small issue in the QuadEncoder library, there is some confusion between enabling position compare interrupts and configuration of the POSMATCH output signal. The positionMatchMode field of the config struct is being used to enable/disable position compare interrupts (CTRL::CMPIE) and also to set the Output Control field (CTRL2::OUTCTL). Those two things are fully independent, so they require separate configuration fields. Config field positionMatchMode could be renamed to positionCompareMode (DISABLE=0, ENABLE=1), and a new field OutputControlMode could be added to set the Output Control field of CTRL2. That field determines what will trigger the POSMATCH output, which can happen with or without a position compare interrupt. The POSMATCH output can be used to control a timer, which may actually be what the OP is looking for.

@mjs513, I can submit a PR regarding this change, but I'll wait until you reply regarding the previous post about what action to take on position compare interrupts. I think disabling the interrupt on match makes the most sense, and if so, I think it would make sense to also have a function to update positionCompareValue and optionally enable the compare interrupt.

Going to get set up and give it a try in a bit and will let you know - but just reading your changes it looks correct but let me play with it a bit.
 
@joepasquariello
Got a bit distracted but did test the change in post #6. An interesting thing is that if I run the encsim without the change the sketch crashes but will still work if I use a simple KY-040 encoder when I updated the isr the both methods worked without issue. So yes definitely we need to update the library.

Another small issue in the QuadEncoder library, there is some confusion between enabling position compare interrupts and configuration of the POSMATCH output signal. The positionMatchMode field of the config struct is being used to enable/disable position compare interrupts (CTRL::CMPIE) and also to set the Output Control field (CTRL2::OUTCTL). Those two things are fully independent, so they require separate configuration fields. Config field positionMatchMode could be renamed to positionCompareMode (DISABLE=0, ENABLE=1), and a new field OutputControlMode could be added to set the Output Control field of CTRL2. That field determines what will trigger the POSMATCH output, which can happen with or without a position compare interrupt. The POSMATCH output can be used to control a timer, which may actually be what the OP is looking for.
Yes it makes sense what you say but am concerned about backward compatibility. Library is out a while and making that change would affect any projects that is using the current version of the library. If we could maintain current names and add in new would probably be better.

@mjs513, I can submit a PR regarding this change, but I'll wait until you reply regarding the previous post about what action to take on position compare interrupts. I think disabling the interrupt on match makes the most sense, and if so, I think it would make sense to also have a function to update positionCompareValue and optionally enable the compare interrupt.
Yes makes sense - go ahead and do a PR and we can go from there.
 
Yes it makes sense what you say but am concerned about backward compatibility. Library is out a while and making that change would affect any projects that is using the current version of the library. If we could maintain current names and add in new would probably be better.

Okay. Before I do a PR, I will also repeat my testing with EncSim running on a separate T4.x, rather than looping back 0/1 - 2/3.
 
@mjs513, I've done some more testing. The key change is to set CMPIE=0 to disable compare interrupts, as shown below. I haven't been able to do anything useful with this interrupt without disabling it this way, then re-enabling after changing the position compare value. There are two ways to use position compare. One is to trigger the POSMATCH output, and the other is to generate an interrupt. The manual doesn't really say how to access the POSMATCH output, but rather just says it can be routed to a timer. I'm not sure what that means, but maybe you do? It seems like if anyone was using the compare interrupt, they would have run into the same issue as me, so that may argue for making a breaking change to QuadEncoder and documenting it. Use of the compare interrupt will be very application-specific, so I'm not sure what makes sense as far as supporting it in the library. Perhaps just provide a function to set the position compare value and optionally enable (re-enable) the interrupt?

Code:
	if (ENC_CTRL_CMPIRQ_MASK == (ENC_CTRL_CMPIRQ_MASK & channel[index].ENC->CTRL))
        {
		compareValueFlag = 1;
		// 12/03/21 JWP add line below to disable compare interrupt
		channel[index].ENC->CTRL &= ~ENC_CTRL_CMPIE_MASK;
		clearStatusFlags(_positionCompareFlag, index);
	}
 
@mjs513, I've done some more testing. The key change is to set CMPIE=0 to disable compare interrupts, as shown below. I haven't been able to do anything useful with this interrupt without disabling it this way, then re-enabling after changing the position compare value. There are two ways to use position compare. One is to trigger the POSMATCH output, and the other is to generate an interrupt. The manual doesn't really say how to access the POSMATCH output, but rather just says it can be routed to a timer. I'm not sure what that means, but maybe you do? It seems like if anyone was using the compare interrupt, they would have run into the same issue as me, so that may argue for making a breaking change to QuadEncoder and documenting it. Use of the compare interrupt will be very application-specific, so I'm not sure what makes sense as far as supporting it in the library. Perhaps just provide a function to set the position compare value and optionally enable (re-enable) the interrupt?

Code:
	if (ENC_CTRL_CMPIRQ_MASK == (ENC_CTRL_CMPIRQ_MASK & channel[index].ENC->CTRL))
        {
		compareValueFlag = 1;
		// 12/03/21 JWP add line below to disable compare interrupt
		channel[index].ENC->CTRL &= ~ENC_CTRL_CMPIE_MASK;
		clearStatusFlags(_positionCompareFlag, index);
	}

Sorry was a bit busy today with other stuff so just catching up now =. Will take a look again tomorrow at this.
 
@mjs513, I've done some more testing. The key change is to set CMPIE=0 to disable compare interrupts, as shown below. I haven't been able to do anything useful with this interrupt without disabling it this way, then re-enabling after changing the position compare value. There are two ways to use position compare. One is to trigger the POSMATCH output, and the other is to generate an interrupt. The manual doesn't really say how to access the POSMATCH output, but rather just says it can be routed to a timer. I'm not sure what that means, but maybe you do? It seems like if anyone was using the compare interrupt, they would have run into the same issue as me, so that may argue for making a breaking change to QuadEncoder and documenting it. Use of the compare interrupt will be very application-specific, so I'm not sure what makes sense as far as supporting it in the library. Perhaps just provide a function to set the position compare value and optionally enable (re-enable) the interrupt?

Code:
	if (ENC_CTRL_CMPIRQ_MASK == (ENC_CTRL_CMPIRQ_MASK & channel[index].ENC->CTRL))
        {
		compareValueFlag = 1;
		// 12/03/21 JWP add line below to disable compare interrupt
		channel[index].ENC->CTRL &= ~ENC_CTRL_CMPIE_MASK;
		clearStatusFlags(_positionCompareFlag, index);
	}

Ok spent some time with this today and added a resetCompareInterupt() function that does your last line so all you have to do in your example is to replace it with myEnc2.resetCompareInterupt(); and thats it - seems to work with the example and another test case I did. I will push the changes now.
 
Ok spent some time with this today and added a resetCompareInterupt() function that does your last line so all you have to do in your example is to replace it with myEnc2.resetCompareInterupt(); and thats it - seems to work with the example and another test case I did. I will push the changes now.

Mike, the one other thing I thought should be changed is that you are using the flag positionMatchMode for two things, to enable/disable interrupts, and also to choose what to do on POSMATCH, and I think those should be independent, i.e. two separate flags.
 
Mike, the one other thing I thought should be changed is that you are using the flag positionMatchMode for two things, to enable/disable interrupts, and also to choose what to do on POSMATCH, and I think those should be independent, i.e. two separate flags.

Yep - actually forgot about this one thanks for reminding me. Added a new flag for positionCompare mode so now you have 2 flags, 1 for POSMATCH and the second for Compare:
Code:
		/* Position compare. */
		/* 0 - POSMATCH pulses when a match occurs between the	position counters (POS) and the compare value (COMP). 1 - POSMATCH pulses when any position counter register is read. */
		bool positionMatchMode;
		
[COLOR="#FF0000"]		/* Position Compare Enabled. */
		bool positionCompareMode;[/COLOR]
		/*!< Position compare value. The available value is a 32-bit number.*/
		uint32_t positionCompareValue;

I am attaching a zip of the updated sources. Note; positionCompareMode defaults to disabled and posititionMatch mode defaults to 0.
 

Attachments

  • QuadEncoder.zip
    6.5 KB · Views: 33
Yep - actually forgot about this one thanks for reminding me. Added a new flag for positionCompare mode so now you have 2 flags, 1 for POSMATCH and the second for Compare:
Code:
		/* Position compare. */
		/* 0 - POSMATCH pulses when a match occurs between the	position counters (POS) and the compare value (COMP). 1 - POSMATCH pulses when any position counter register is read. */
		bool positionMatchMode;
		
[COLOR="#FF0000"]		/* Position Compare Enabled. */
		bool positionCompareMode;[/COLOR]
		/*!< Position compare value. The available value is a 32-bit number.*/
		uint32_t positionCompareValue;

I am attaching a zip of the updated sources. Note; positionCompareMode defaults to disabled and posititionMatch mode defaults to 0.

I think the two flag names positionMatchMode and positionCompareMode will create confusion. I know that I will always have to double-check which is which, and for someone new to the library, the names will seem to have the same meaning. Perhaps rename positionMatchMode to outputControlMode to match the terminology in the manual? NXP did not do us any favors by naming the output signal POSMATCH, even though it isn't always triggered by a match. I'm pretty sure that all users so far have been using the one original flag to enable/disable position compare interrupts, and the effect on Output Control was secondary.

Perhaps also update the comment on positionCompareMode to say Position Compare Interrupt Enabled, as opposed to Position Compare Enabled.
 
Not a big deal to change from positionMatchMode to outputControlMode not sure which way would be better. And yes NXP naming convention isn't that great. But think if we use outputControlMode may lead to more confusion that it controls overall output when it really is only applicable to Compare. Maybe compareMatchMode would be better. Most of the time its going to default to 0 anyway. And no - have no idea how to trigger a timer.
Capture.PNG
 
Mike, sorry for the nitpicking, but maybe reconsider the new function name resetCompareInterupt(). I think of "reset" as the same as "clear" to 0, not as "re-set" to 1, so I would find enableCompareInterrupt() to be more clear. If you do keep this name, please add another 'r' for Interrupt.

What my test program does after each interrupt is set initial (current) position back to 0 via write(), then re-enable the interrupt. As long as the current position is modified before the interrupt is enabled, repeat interrupts are avoided. I suspect that another way people will use the compare interrupt is to leave the current position alone and modify the position compare value. Can you add a function to set a new positionCompareValue, similar to what you did with write()?
 
Even that diagram is a bit misleading because the POSMATCH output occurs on position match when OutputControl = 0, but occurs on each/every read of the position registers when OutputControl = 1. That said, I'm okay with it the flag names as is. Your comments do explain the purpose of each flag.
 
Back
Top