Nanoprecision
Member
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
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"
);
}