Issues with Timers and QuadEncoder

Dear all,

I hope you can help me with my problem.
Well two problems, but one I can live with :)

The setup:
Teensy 4.0 with Arduino IDE 1.8.13 (I'll get to that later).


I got some 3rd-party code which uses an own QuadTimer setup
with own x_bar settings plus some inline assembler code.
Why? I'm running measurements which are very timing critical.
This code is fine and runs as expected.

Prob #1 (the easy one):
The code compiles only with Arduino 1.8.13 IDE, any IDE2.xx throws the error

>>>
'asm' operand has impossible constraints
asm volatile("ldr r0, =0x401dc00a \n\t" // load address of MEAS2 (TMR1_CNTR0) into r0
<<<

If there is an easy solution that the Arduino 2.xx IDE
compiles the code, I'd be grateful. But I can live with it "as is" and use IDE 1.8.xx


Prob #2 (the difficult one, at least for me!)
I need to add a Quadrature Encoder and want to use the
build-in QuadEncoder of the Teensy 4.0, as it is easy and as I
hope there is no impact on the timings of the other code.

The QuadEncoder works fine as long as I use it stand-alone.
And here comes the BUT:
If I add the Quad*Timer* setup code (= uncommenting line 35),
the Quad*Encoder* delivers junk.

As I have absolutely *no* clue about the Teensy internals or how to
set up the timers, maybe there is someone out there who has a solution for me
how to implement this or which code parts need to be changed?


Thanks so much,
Tom


Code:
#define HomodyneMultiplier 4 // Set to homodyne interferometer integer counts/cycle.  4 is Quadpulse and Renishaw Coarse; Fine  is 16.
#define Multiplier 1         // Heterodyne: Integer counts/cycle.  1 for rising edge; 2 for double clocking.  Do not mess with this - used for TMR_CTRL_CM value.
#include "QuadEncoder.h"

IMXRT_TMR_t * TMR4 = (IMXRT_TMR_t *)&IMXRT_TMR4;
IMXRT_TMR_t * TMR2 = (IMXRT_TMR_t *)&IMXRT_TMR2;
#define IMXRT_GPIO6_DIRECT  (*(volatile uint32_t *)0x42004008)


//The Teensy 4.0 Encoders are supported on pins: 0, 1, 2, 3, 4, 5, 7, 30, 31 and 33
QuadEncoder encoder3(3, 5, 7, 0);  // Encoder on channel 3 of 4 available; Phase A (pin5), PhaseB(pin7), Pullups Req(0)

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

void REF_Sync_Assembly_Block() __attribute__((optimize("-O0")));

  uint32_t encoder3Value;
  volatile uint32_t No_REF = 0;
  uint32_t gpioData[1000];

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
  pinMode(6, INPUT);         // REF   interpolation input
  pinMode(8, INPUT);         // MEAS1 interpolation input
  pinMode(15, INPUT_PULLUP); // Input to initiate data transfer to Teensy_GUI

  Serial.begin(2000000);

//////////////////
//////////////////
//  this is the problematic call 
//  QuadTimer_Setup();



  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

   // Reset counters again.  Only should need to do LSBs.
  TMR2->CH[0].CNTR = 0;   // set REF count to 0
  TMR4->CH[0].CNTR = 0;   // set MEAS1 count to 0
  
  while (!Serial && millis() < 1000);

  // Initialize the ENC module.
  encoder3.setInitConfig();
  encoder3.init();
}

void USBSender()
{
  encoder3Value = encoder3.read();
  Serial.print("encoder value:  "); Serial.println(encoder3Value);



  /////////////
  // Heterodyne code here. No influence on issue...
  /////////////
/*


*/
  /////////////
  // end Heterodyne code
  /////////////


}  // end Setup


void loop(){};



void xbar_connect(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_connect(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
}

void REF_Sync_Assembly_Block () {

  asm volatile("ldr    r0, =0x401dc00a \n\t" // load address of MEAS2 (TMR1_CNTR0) into r0
               "ldr    r1, =0x401e000a \n\t" // load address of REF (TMR2_CNTR0) into r1
               "ldr    r2, =0x401e400a \n\t" // load address of MEAS3 (TMR3_CNTR0) into r2
               "ldr    r3, =0x401e800a \n\t" // load address of MEAS1 (TMR4_CNTR0) into r3

               "ldr    r8, =0x42004008 \n\t" // load address of GPIO6_PSR into r8
               "mov    r9, %1          \n\t" // copy address of array into r9, index = 0
               "mov    r10, r9         \n\t" // copy r9 to r10 to start on end-of-loop-condition
               "add    r10, #3996      \n\t" // Uend-of-loop condition. we want Waveform_Length 4 byte values, so the end is at 4000 - 4 = 3996 bytes after the beginning of the array

               "mov    r5, #0          \n\t" // reset error code to 0
               "str    r5, %0          \n\t" //
               "mov    r5, #0x400      \n\t" // load bit to test in r5
               "mov    r4, #100        \n\t" // load loop count in r4
               
#ifndef REF_Sync
               "b REFEdgeFound1         \n\t" // Do NOT sync on REF edges//
#endif

              // Sync with rising edge on REF clock (Timer3, GPIO bit 0x400)

    "REFSyncLoop:                      \n\t" //
               "ldr    r6, [r8]        \n\t" // Load value of GPIO_PSR into r6
               "tst    r6, r5          \n\t" // Check GPIO_PSR REF counter clock bit
               "bne    REFEdgeFound1   \n\t" // End if REF clock high
               "subs   r4, r4, #1      \n\t" //
               "bne    REFSyncLoop     \n\t" // Do it again if loop count not 0

    "NoREFEdgeFound1:                  \n\t" // No high REF clock detected
               "ldrh   r5, [r1], #0    \n\t" // hold TMR2 by reading TMR2_CNTR0 (REF)
               "ldrh   r5, [r3], #0    \n\t" // hold TMR4 by reading TMR4_CNTR0 (MEAS1)
               "movw   r5, #12345      \n\t" // load error code - 12345 for now
               "str    r5, %0          \n\t"
               "b      Abort           \n\t" // Skip gpioData capture if no REFs
                       
    "REFEdgeFound1:                    \n\t" // Breakout
               "ldrh   r5, [r1], #0    \n\t" // hold TMR2 by reading TMR2_CNTR0 (REF)
               "ldrh   r5, [r3], #0    \n\t" // hold TMR4 by reading TMR4_CNTR0 (MEAS1)
               
  // read GPIO directly for interpolation calculation

    "nextdata:                        \n\t"  //
               "ldr    r3, [r8]       \n\t"  // load value of GPIO6_PSR into r3
               "str    r3, [r9], #4   \n\t"  // store value into gpioDataArray and then add 4 bytes to the index
               "cmp    r9, r10        \n\t"  // check loop counter against loop limit
               "ble    nextdata       \n\t"  // loop if limit not reached

    "Abort:                           \n\t"  //  
               "nop                   \n\t"  //

               : "=m" (No_REF)         // output operand list
               : "r" (gpioData)              // input operand list
               : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r8", "r9", "r10"
              );
}
 
a) the author of the assembler part provided a new implementation which can be compiled with Arduino IDE2.xx
b) obviously, a standard encoder lib does the job. Still need to do some timing testing, but looks ok so far.
 
The IDE shouldn't have any relevance on whether assembly code compiles or not; it just displays/edits the code, it doesn't compile it.
 
The IDE shouldn't have any relevance on whether assembly code compiles or not; it just displays/edits the code, it doesn't compile it.
Technically, your are of course right. It is the gcc(?) compiler. But as the IDE comes with a specific gcc compiler version, for me -the simple user- the IDE and the compiler version are one single tool I just use.
 
Technically, your are of course right. It is the gcc(?) compiler. But as the IDE comes with a specific gcc compiler version, for me -the simple user- the IDE and the compiler version are one single tool I just use.
The compiler is included in the version of Teensyduino that you install, it is not bundled with the IDE.
 
Back
Top