Teensy 4.x H/W Quadrature Encoder Library

When doing this bridge between pin 0 and pin 2, should I add in any passive components, such as a diode or something to make sure there is no noise or reverse voltage going back to pin 0? or is this overkill?

Someone with more hardware knowledge will have to answer this one.
 
@Rezo

Know this is probably overkill but this might help. Let us know what you come up - I am interested as well
 

Attachments

  • Use QDC-ENC-Quad Timer Peripherals to Calculate the Angle and Speed.pdf
    456.7 KB · Views: 275
Dear all,

I have an issue to get the QuadEncoder up&running in combination with
an existing code which is also using the HW timer functions.

I have bought a PCB using a T4.0 plus (public domain) code to measure
up to four frequencies in the low MHz range. Output is the difference between
two frequencies: a reference and up to three measurements.
Alternatively, I can also use this HW to measure quadEncoder signals
(input signal frequency: some 10kHz) this QuadEncoder library.

Using either one separately works fine.
When combining both, the code compiles but the results are missing/wrong.

Using a software encoder "Encoder" for the QuadEncoder signals, I do get results,
but the performance (speed) of the measurements is significantly degraded up to a
point where the results are unreliable.

First I have to admit that I have *no* clue on how to program the
timers, MUXes or XBARS of the Teensy HW, but I assume that there is a conflict
within the setup/usage of the timers.

Do you have any idea what I need to change to get it up & running with the QuadEncodere lib?
Unfortunately, I cannot change the PIN assignment, as these are a given by the PCB.

Here is the code plus the QuadEncoder debug data.

Thanks so much,
Tom


C++:
#include "QuadEncoder.h"
const int channelNum = 4;
const int phaseAPin = 5;
const int phaseBPin = 7;
const int pullupsRequired = 0;

QuadEncoder GMS_Encoder(channelNum, phaseAPin, phaseBPin, pullupsRequired);  // Encoder on channel 1 of 4 available
                                   // Phase A (pin0), PhaseB(pin1), Pullups Req(0)
// Encoder GMS_Encoder(5, 7);


#define Heterodyne

#define Multiplier 2              // Heterodyne: Integer counts/cycle.  1 for rising edge; 2 for double clocking.  Do not mess with this - used for TMR_CTRL_CM value.
#define Scale_Shift  1           // Set this to logbase2(Multiplier)

#ifdef Heterodyne
  IMXRT_TMR_t * TMR4 = (IMXRT_TMR_t *)&IMXRT_TMR4;
  IMXRT_TMR_t * TMR2 = (IMXRT_TMR_t *)&IMXRT_TMR2;
  IntervalTimer dataAqcuisitionTimer;  // here is where we acquire and oversample data
#endif

IntervalTimer usbTimer;              // send USB data at predefinded rate to make frequency analysis work in the GUI

int64_t counter_REF   =  0;
int64_t counter_MEAS1 =  0;

int64_t counter_REF_save   =  0;
int64_t counter_MEAS1_save =  0;

int32_t REF =  0;
int32_t MEAS1 =  0;

int32_t  GMS_DisplacementRAW     = 0;
int64_t  displacement1           = 0;

uint64_t sequenceNumber          = 0;



#ifdef Heterodyne
  void GrabAndOversampleData()
  {
  // todo : average_values
    asm volatile("ldr    r1 ,=0x401e000a  \n\t" // load address of TMR2_CNTR0 into r1
                "ldr    r3 ,=0x401e800a  \n\t" // load address of TMR4_CNTR0 into r3
                "ldrh   r5 ,[r1],#0      \n\t" // hold TMR2 by reading TMR2_CNTR0
                "ldrh   r7 ,[r3],#0      \n\t" // hold TMR4 by reading TMR4_CNTR0
                :
                :
                : "r1", "r3", "r5", "r7"
                );
    // Load counter values

    counter_REF   =  TMR2->CH[3].HOLD;
    counter_REF   =  counter_REF * 65536    + TMR2->CH[2].HOLD;
    counter_REF   =  counter_REF * 65536    + TMR2->CH[1].HOLD;
    counter_REF   =  counter_REF * 65536    + TMR2->CH[0].HOLD;
    
    counter_MEAS1 =  TMR4->CH[3].HOLD;
    counter_MEAS1 =  counter_MEAS1 * 65536  + TMR4->CH[2].HOLD;
    counter_MEAS1 =  counter_MEAS1 * 65536  + TMR4->CH[1].HOLD;
    counter_MEAS1 =  counter_MEAS1 * 65536  + TMR4->CH[0].HOLD;

    displacement1 = (counter_MEAS1 - counter_REF);
  }

  void xbar_connect2(unsigned int input, unsigned int output)
  {
    // function to make setting the crossbar SEL fields easier; 2 of these SEL fields are fit into a 32 register; there are many of these fields....
    if (input >= 88) return;
    if (output >= 132) return;
    volatile uint16_t *xbar = &XBARA1_SEL0 + (output / 2);
    uint16_t val = *xbar;
    if (!(output & 1)) {
      val = (val & 0xFF00) | input;
    } else {
      val = (val & 0x00FF) | (input << 8);
    }
    *xbar = val;
  }



  void QuadTimer_Setup()
  {
    // QuadTimer setup - DO NOT MESS WITH THIS!!!

    // The IOMUX is also used to configure other pin characteristics, such as voltage level, drive strength, and hysteresis. These may not be set optimally. More experimentation / real world data is necessary

    // set up QuadTimer2: REF on D0
    CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);                 // turn clock on for XBAR1
    IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_03 = 1;                   // IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_03 (pin 0) to ALT1 mux port: XBAR1_INOUT17
    IOMUXC_XBAR1_IN17_SELECT_INPUT = 1 ;                       // XBAR1_INOUT17 has several inputs to choose from. Pick IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_03
    IOMUXC_GPR_GPR6 |= 0b0000000010000;                        // connect XBAR as input for QTIMER2_TIMER0
    xbar_connect2(17, XBARA1_OUT_QTIMER2_TIMER0);               // connect XBAR1_INOUT17 to XBARA1_OUT_QTIMER2_TIMER0
    CCM_CCGR6 |= CCM_CCGR6_QTIMER2(CCM_CCGR_ON);               // enable QTMR2 clock
    IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B0_03 |= 0b1000000000000000; // enable hysteresis in pin D0
    TMR2->CH[0].CTRL = 0;                                      // stop
    TMR2->CH[0].SCTRL = TMR2->CH[0].CSCTRL = 0;
    TMR2->CH[1].CTRL = 0;                                      // stop
    TMR2->CH[2].CTRL = 0;                                      // stop
    TMR2->CH[3].CTRL = 0;                                      // stop
    TMR2->CH[0].CNTR = 0;                                      // set count to 0
    TMR2->CH[1].CNTR = 0;                                      // set count to 0
    TMR2->CH[2].CNTR = 0;                                      // set count to 0
    TMR2->CH[3].CNTR = 0;                                      // set count to 0
    TMR2->CH[0].LOAD = 0;
    TMR2->CH[1].LOAD = 0;
    TMR2->CH[2].LOAD = 0;
    TMR2->CH[3].LOAD = 0;
    TMR2->CH[0].SCTRL = TMR2->CH[0].CSCTRL = 0;
    TMR2->CH[1].SCTRL = TMR2->CH[1].CSCTRL = 0;
    TMR2->CH[2].SCTRL = TMR2->CH[2].CSCTRL = 0;
    TMR2->CH[3].SCTRL = TMR2->CH[3].CSCTRL = 0;
    TMR2->CH[0].COMP1 =  0xffff;                               // send count signal to next counter on overflow at 0xffff
    TMR2->CH[1].COMP1 =  0xffff;                               // send count signal to next counter on overflow at 0xffff
    TMR2->CH[2].COMP1 =  0xffff;                               // send count signal to next counter on overflow at 0xffff
    TMR2->CH[3].COMP1 =  0xffff;                               // send count signal to next counter on overflow at 0xffff
    TMR2->CH[0].CMPLD1 =  0xffff;
    TMR2->CH[1].CMPLD1 =  0xffff;
    TMR2->CH[2].CMPLD1 =  0xffff;
    TMR2->CH[3].CMPLD1 =  0xffff;
    TMR2->CH[3].CTRL  = TMR_CTRL_CM (7);                       // Count Mode:           Cascaded counter mode
    TMR2->CH[3].CTRL |= TMR_CTRL_PCS(6);                       // Primary Count Source: CH[2] output
    TMR2->CH[2].CTRL  = TMR_CTRL_CM (7);                       // Count Mode:           Cascaded counter mode
    TMR2->CH[2].CTRL |= TMR_CTRL_PCS(5);                       // Primary Count Source: CH[1] output
    TMR2->CH[1].CTRL  = TMR_CTRL_CM (7);                       // Count Mode:           Cascaded counter mode
    TMR2->CH[1].CTRL |= TMR_CTRL_PCS(4);                       // Primary Count Source: CH[0] output
    TMR2->CH[0].CTRL  = TMR_CTRL_CM (Multiplier);              // Count Mode: Count rising edges or both edges of primary source
    TMR2->CH[0].CTRL |= TMR_CTRL_PCS(0);                       // Primary Count Source: Counter 0 input pin

    // set up QuadTimer4: MEAS1 on D9
    CCM_CCGR6 |= CCM_CCGR6_QTIMER4(CCM_CCGR_ON);               // enable QTMR4 clock
    IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_11 = 1;                      // QuadTimerT4 Counter 2 on pin D9
    IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_11 |= 0b1000000000000000;    // enable hysteresis in pin D9
    TMR4->CH[0].CTRL = 0;                                      // stop
    TMR4->CH[1].CTRL = 0;                                      // stop
    TMR4->CH[2].CTRL = 0;                                      // stop
    TMR4->CH[3].CTRL = 0;                                      // stop
    TMR4->CH[0].CNTR = 0;                                      // set count to 0
    TMR4->CH[1].CNTR = 0;                                      // set count to 0
    TMR4->CH[2].CNTR = 0;                                      // set count to 0
    TMR4->CH[3].CNTR = 0;                                      // set count to 0
    TMR4->CH[0].LOAD = 0;
    TMR4->CH[1].LOAD = 0;
    TMR4->CH[2].LOAD = 0;
    TMR4->CH[3].LOAD = 0;
    TMR4->CH[0].SCTRL = TMR4->CH[0].CSCTRL = 0;
    TMR4->CH[1].SCTRL = TMR4->CH[1].CSCTRL = 0;
    TMR4->CH[2].SCTRL = TMR4->CH[2].CSCTRL = 0;
    TMR4->CH[3].SCTRL = TMR4->CH[3].CSCTRL = 0;
    TMR4->CH[0].COMP1 =  0xffff;                               // send count signal to next counter on overflow at 0xffff
    TMR4->CH[1].COMP1 =  0xffff;                               // send count signal to next counter on overflow at 0xffff
    TMR4->CH[2].COMP1 =  0xffff;                               // send count signal to next counter on overflow at 0xffff
    TMR4->CH[3].COMP1 =  0xffff;                               // send count signal to next counter on overflow at 0xffff
    TMR4->CH[0].CMPLD1 =  0xffff;
    TMR4->CH[1].CMPLD1 =  0xffff;
    TMR4->CH[2].CMPLD1 =  0xffff;
    TMR4->CH[3].CMPLD1 =  0xffff;
    TMR4->CH[3].CTRL  = TMR_CTRL_CM (7);                       // Count Mode:           Cascaded counter mode
    TMR4->CH[3].CTRL |= TMR_CTRL_PCS(6);                       // Primary Count Source: CH[2] output
    TMR4->CH[2].CTRL  = TMR_CTRL_CM (7);                       // Count Mode:           Cascaded counter mode
    TMR4->CH[2].CTRL |= TMR_CTRL_PCS(5);                       // Primary Count Source: CH[1] output
    TMR4->CH[1].CTRL  = TMR_CTRL_CM (7);                       // Count Mode:           Cascaded counter mode
    TMR4->CH[1].CTRL |= TMR_CTRL_PCS(4);                       // Primary Count Source: CH[0] output
    TMR4->CH[0].CTRL  = TMR_CTRL_CM (Multiplier);              // Count Mode: Count rising edges or both edges of primary source
    TMR4->CH[0].CTRL |= TMR_CTRL_PCS(2);                       // Primary Count Source: Counter 2 input pin
  }
#endif // Heterodyne










void setup()
{
  pinMode(0, INPUT_PULLUP);  // REF input
  pinMode(1, INPUT_PULLUP);  // Hom 1A wired to REF
  pinMode(2, INPUT_PULLUP);  // Hom 1B wired to MEAS1
  pinMode(9, INPUT_PULLUP);  // MEAS1 input

  Serial.begin(2000000);
 
  GMS_Encoder.setInitConfig();
  // GMS_Encoder.EncConfig.IndexTrigger = ENABLE;  //enable to  use index counter
  // GMS_Encoder.EncConfig.INDEXTriggerMode = RISING_EDGE;
  GMS_Encoder.init();


  usbTimer.begin(USBSender, 1000);       // Send USB data every 1000 microseconds
  usbTimer.priority(200);                // Lower numbers are higher priority, with 0 the highest and 255 the lowest. Most other interrupts default to 128
 
  #ifdef Heterodyne
    QuadTimer_Setup();
    dataAqcuisitionTimer.begin(GrabAndOversampleData, 4);   // Average updated every 4 microseconds.
    dataAqcuisitionTimer.priority(100);     
  #endif

}



void USBSender()
{
  sequenceNumber++;

  GMS_DisplacementRAW = GMS_Encoder.read();

  REF   = (counter_REF   - counter_REF_save)   >> Scale_Shift;
  MEAS1 = (counter_MEAS1 - counter_MEAS1_save) >> Scale_Shift;

  counter_REF_save = counter_REF;
  counter_MEAS1_save = counter_MEAS1;

  logToSerial();

}


void loop()
{

}




void logToSerial()
{
    Serial.print(sequenceNumber);            Serial.print(",  ");
    Serial.print(displacement1);             Serial.print(",  ");
    Serial.print(GMS_DisplacementRAW);       Serial.println();
}


Code:
begin: encoder channel-> 4
begin: pinA-> 5, pinB-> 7

xbara_mapping: pin-> 5, idx-> 5
xbara_mapping: hw_count-> 11
xbara_mapping: hardware[_pin_idx].pin-> 5, .select_val-> 3
xbara_mapping: Encoder-> 4
xbarIO-> 17, PhaseA-> 81

xbara_mapping: pin-> 7, idx-> 6
xbara_mapping: hw_count-> 11
xbara_mapping: hardware[_pin_idx].pin-> 7, .select_val-> 1
xbara_mapping: Encoder-> 4
xbarIO-> 15, PhaseB-> 82
 
I have an issue to get the QuadEncoder up&running in combination with an existing code which is also using the HW timer functions.

Do you have any idea what I need to change to get it up & running with the QuadEncodere lib?
Unfortunately, I cannot change the PIN assignment, as these are a given by the PCB.

The Heterodyne code is using input 17 of XBAR1 for the signal coming into pin 0, and QuadEncoder is also using XBAR input 17 for the phase A encoder signal coming into pin 5.

Since your code sets up QuadEncoder before it sets up the Heterodyne inputs, this could explain why QuadEncoder is okay when you don't configure the Heterodyne, but is not okay when you call QuadTimer_Setup(). I don't know enough about XBAR to say whether you can simply choose to use a different input for pin 0, but maybe someone else can provide that information.

Another possibility, even though you can't change the hardware, can you somehow jumper pin 5 of the T4.0 to, say, pin 6? That way you could set your QuadEncoder A,B pins to 6,7 instead of 5,7, and the QuadEncoder library, instead of using XBAR input 17 would use XBAR input 15.
 
Dear Joe (is Joe correct?),

thanks for your tip.
PIN6 is currently unused. So yes, I can bridge PIN5 to PIn6. :)
If I read the Quadencoder.cpp correctly, the lib will automatically set the XBAR input to 15 if I set PINs 5&6 as inputs. Is my understanding correct
or do I need to change something else?

I'll give it a try in the next day(s).

/BR
Thomas
 
Dear Joe (is Joe correct?),

thanks for your tip.
PIN6 is currently unused. So yes, I can bridge PIN5 to PIn6. :)
If I read the Quadencoder.cpp correctly, the lib will automatically set the XBAR input to 15 if I set PINs 5&6 as inputs. Is my understanding correct
or do I need to change something else?

I'll give it a try in the next day(s).

/BR
Thomas
Yes, but you would be using pins 6,7 and not 5,6. The idea is to avoid using pin 5.
 
Hello,

as I read from the documentation, PIN6 is not available for the quadEncoder and I have to use other PINs (e.g. PIN8).
Unfortunately, need to make an adapter board to use other available PINs of my PCB, which takes some time.
 
as I read from the documentation, PIN6 is not available for the quadEncoder and I have to use other PINs (e.g. PIN8).
Unfortunately, need to make an adapter board to use other available PINs of my PCB, which takes some time.

Yes, you're right. Here's the table from QuadEncoder.cpp, and pin 6 is not an option, but you could use 3, 4, 8, 30, 31, 33. Are you saying you can't simply jumper pin 5 to any of those pins, say be soldering on a blue wire?

Code:
#if defined( ARDUINO_TEENSY40)
const  QuadEncoder::ENC_Hardware_t QuadEncoder::hardware[] = {   
    {0, 0, &CORE_XIO_PIN0, 1, 17, 1},    {1, 1, &CORE_XIO_PIN1, 1, 16, 0},
    {2, 2, &CORE_XIO_PIN2, 3, 6, 0},    {3, 3, &CORE_XIO_PIN3, 3, 7, 0},
    {4, 4, &CORE_XIO_PIN4,3, 8, 0},        {5, 5, &CORE_XIO_PIN5, 3, 17, 0},
    {6, 7, &CORE_XIO_PIN7, 1, 15, 1},    {8, 8, &CORE_XIO_PIN8, 1, 14, 1},
    {8, 30, &CORE_XIO_PIN30, 1, 23, 0},    {9, 31, &CORE_XIO_PIN31, 1, 22, 0},
    {10, 33, &CORE_XIO_PIN33, 3, 9, 0}
};
#endif
 
A simple jumper does not work as there is a signal input on PIN 8. There are some funny results if I just jumper P5 to P8.
But I don‘t really need the signal on P8. There will be some use case restrictions if I don‘t use this signal, but as said, I can live with that.
The adapter board will simply cut off the connection from the PCB to PIN8 and shortcut P5 to P8.

Let‘s see.
 
Final feedback:
Yes, you're right. Here's the table from QuadEncoder.cpp, and pin 6 is not an option, but you could use 3, 4, 8, 30, 31, 33. Are you saying you can't simply jumper pin 5 to any of those pins, say be soldering on a blue wire?
I don't want to solder directly on the Teensy PCB and Pogo-Pins don't work.

BUT:
The adapter did the job. It is runnig! YES!!
Case closed.

Thanks so much Joe!
 
Back
Top