HP5508A interferometer replacement hardware 2.0! - Heterodyne

After having set the ground work for the homodyne case, lets teach the Teensy to understand a heterodyne interferometer signal. For the homodyne case we could just use the QuadEncoder library and have it set up all the internals, but now we have to do it ourselves. The Teensy can support 4 64-bit hardware counters. This is just enough for a 3 axis interferometer. Lets start with one counter:

Single 64-bit counter:


// required libraries// u8g2 graphics library available though the Library Manager#include <U8x8lib.h>
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/U8X8_PIN_NONE);
IMXRT_TMR_t * TMRx = (IMXRT_TMR_t *)&IMXRT_TMR4;
void setup(){ Serial.begin(9600); // initialize and clear display u8x8.begin(); u8x8.setPowerSave(0); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(0, 0, "Started !"); CCM_CCGR6 |= CCM_CCGR6_QTIMER4(CCM_CCGR_ON); // enable QTMR4 IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_11 = 1; // QT4 Timer2 on pin 9

TMRx->CH[0].CTRL = 0; // stop TMRx->CH[1].CTRL = 0; // stop TMRx->CH[2].CTRL = 0; // stop TMRx->CH[3].CTRL = 0; // stop TMRx->CH[0].CNTR = 0; // set count to 0 TMRx->CH[1].CNTR = 0; // set count to 0 TMRx->CH[2].CNTR = 0; // set count to 0 TMRx->CH[3].CNTR = 0; // set count to 0 TMRx->CH[0].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMRx->CH[1].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMRx->CH[2].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMRx->CH[3].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMRx->CH[0].CMPLD1 = 0xffff; TMRx->CH[1].CMPLD1 = 0xffff; TMRx->CH[2].CMPLD1 = 0xffff; TMRx->CH[3].CMPLD1 = 0xffff; TMRx->CH[3].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMRx->CH[3].CTRL |= TMR_CTRL_PCS(6); // Primary Count Source: CH[2] output TMRx->CH[2].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMRx->CH[2].CTRL |= TMR_CTRL_PCS(5); // Primary Count Source: CH[1] output TMRx->CH[1].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMRx->CH[1].CTRL |= TMR_CTRL_PCS(4); // Primary Count Source: CH[0] output TMRx->CH[0].CTRL = TMR_CTRL_CM (1); // Count Mode: Count rising edges of primary source TMRx->CH[0].CTRL |= TMR_CTRL_PCS(2); // Primary Count Source: Counter 2 input pin // to test counter: analogWriteFrequency(8, 40000000); // max 75mhz at 150 mhz bus speed analogWrite(8, 128); // PWM duty cycle 50%}
char buffer[100];
void loop(){ sprintf(buffer, "%u", TMRx->CH[0].CNTR); // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels; u8x8.drawString(0, 2, " "); u8x8.drawString(0, 2, buffer); Serial.println(buffer); sprintf(buffer, "%u", TMRx->CH[1].HOLD); // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels; u8x8.drawString(0, 3, " "); u8x8.drawString(0, 3, buffer); Serial.println(buffer); sprintf(buffer, "%u", TMRx->CH[2].HOLD); // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels; u8x8.drawString(0, 4, " "); u8x8.drawString(0, 4, buffer); Serial.println(buffer); sprintf(buffer, "%u", TMRx->CH[3].HOLD); // regular read of CH CNTR causes HOLD registers to be set with corresponding values of other channels; u8x8.drawString(0, 5, " "); u8x8.drawString(0, 5, buffer); Serial.println(buffer); static uint64_t previous = 0; uint64_t counter = TMRx->CH[3].CNTR; counter = counter * 65536 + TMRx->CH[2].HOLD; counter = counter * 65536 + TMRx->CH[1].HOLD; counter = counter * 65536 + TMRx->CH[0].HOLD; Serial.printf("%" PRIu64 "\n", counter-previous); previous = counter; Serial.println(); delay(1000);}

This lets us test the hardware counter using the Teensy's own internal PWM generator.


The bus clock is at 150 Mhz, so we should be able to count to at least 75Mhz. I set the clock output to pin 8 because the default input pin for the Timer I am using is pin9. This way I can just put on a jumper. Configuring the input pins for the timers is a bit complicated. Each of the 4 QuadTimer blocks consists itself of 4 timers - hence the name QuadTimer. But these can be cascaded and read clock-synchronously. To determine the pins available to us for these timers on the Teensy, we have to start with the processor manual:




Lets look at QuadTimer4 which is called TMR4 here. The 4 internal timers are fed with pins GPIO_B0_09, GPIO_B0_10, GPIO_B0_11 and GPIO_B1_11. These timers can be cascaded in any order. So, for example, we can have the overflow of TMR_4_TIMER2 go into TMR_4_TIMER0, have the TMR_4_TIMER0 overflow into TMR_4_TIMER1 and have TMR_4_TIMER1 overflow into TMR_4_TIMER3. The code example above cascades timers in the order 0->1->2->3, but any order can be chosen. This is configured by the PCS parameter:

So within a QuadTimer, there are 4 counters. Each of these counters can be cascaded into any of the others. And each counter can be configured to use any of the clock sources. This last part is a little confusing, because the muxing options table suggests that TMR_4_TIMER0 is hardwired to use GPIO_B0_09 as the external input pin (called the Counter 0 input pin above). However, any of the 4 counters can use the any of the 4 Counter0-3 input pins. In short, the 64-bit counter Timer 4 can count input from any of the 4 pins listed in the muxing table - GPIO_B0_09, GPIO_B0_10, GPIO_B0_11 and GPIO_B1_11.

To determine which pins those are on the Teensy 4.0 we have to look at its documentation. There is a handy file here:

Or we can find it here:

So for QuadTimer4:

  • GPIO_B0_09 - Unavailable on Teensy 4.0
  • GPIO_B0_10 - D6
  • GPIO_B0_11 - D9
  • GPIO_B1_11 - Unavailable on Teensy 4.0

The sample code above feeds Counter 0 of QuadTimer4 with the input pin for Counter 2, GPIO_B0_11. This is pin D9 on the Teensy. There is one more column that we have not covered in the mux table above, the ALT mode. The table shows that for QuadTimer4 we have to set ALT to 1 in order to access the pins listed. Let's have a quick look at this functionality:

The sample code above sets IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_11 to 1 in order to pick the pin listed in the mux table. The ALT selection is used to determine where a physical pin is connected to. For this one, for example,

the pin can be connected to the Counter 2 of QuadTimer4, or PWM module, or the LCD module, or some other things.

This is sufficient for QuadTimer1 and QuadTimer4, because only one pin can go to each of the counter0-3 inputs. However, QuadTimer2 and 3 have multiple potential input pins for each counter input (<SOURCE>_SELECT_INPUT in the IOMUC Cell Block Diagram above).



So this requires an extra line in the code.


We need input pins for all 4 QuadTimers for a 3 axis interferometer. Lets see which pins are available for the other 3.

For QuadTimer3:

  • GPIO_AD_B1_00 - D19
  • GPIO_EMC_15 - Unavailable on Teensy 4.0
  • GPIO_B0_06 - Unavailable on Teensy 4.0
  • GPIO_AD_B1_01 - D18
  • GPIO_EMC_16 - Unavailable on Teensy 4.0
  • GPIO_B0_07 - Unavailable on Teensy 4.0
  • GPIO_EMC_17 - Unavailable on Teensy 4.0
  • GPIO_AD_B1_02 - D14
  • GPIO_B0_08 - Unavailable on Teensy 4.0
  • GPIO_B1_10 - Unavailable on Teensy 4.0
  • GPIO_AD_B1_03 - D15
  • GPIO_EMC_18 - Unavailable on Teensy 4.0

For QuadTimer2:

  • GPIO_EMC_19 - Unavailable on Teensy 4.0
  • GPIO_B0_03 - D13
  • GPIO_EMC_20 - Unavailable on Teensy 4.0
  • GPIO_B0_04 - Unavailable on Teensy 4.0
  • GPIO_EMC_21 - Unavailable on Teensy 4.0
  • GPIO_B0_05 - Unavailable on Teensy 4.0
  • GPIO_EMC_22 - Unavailable on Teensy 4.0
  • GPIO_B1_09 - Unavailable on Teensy 4.0

For QuadTimer1:

  • GPIO_B0_00 - D10
  • GPIO_B0_01 - D12
  • GPIO_B0_02 - D11
  • GPIO_B1_08 - Unavailable on Teensy 4.0

So we have the following sets of pins for the various timers:

Quadtimer1 - pins D10, D11, D12

Quadtimer2 - pins D13

Quadtimer3 - pins D14, D15, D18, D19

Quadtimer4 - pins D6, D9

Ok, so we can choose pins compatible with the homodyne pin assignment now:

  • MEAS1: Quadtimer1 - D10
  • MEAS2: Quadtimer2 - D13
  • MEAS3: Quadtimer3 - D14
  • REF: Quadtimer4 - D9

However, D13 has an LED connected to it. But it is also the only pin exposed by QuadTimer2 by the standard routing. To use a different pin, we need to use the crossbar available on this microcontroller.

The XBAR:

Similar to the ordinary input a daisy chain of connections has to be made. The connection of the XBAR to the timers is determined by the GPR6 General Purpose Register (IOMUXC_GPR_GPR6):

Each of these bits determines whether the timer gets fed by the traditional method we have been using, or by the XBAR. For QuadTimer2:

Next, we need to know which XBAR ports connect to which QTIMER2 TMR.

So the 4 XBAR outputs all come from XBAR1. Finally we need to know which pins can be connected to XBAR1. There is not a compiled list, but for each pin the information is in the pin description. Let' see if Arduino pin 0 is available. First, we determine that pin is on AD_B0_03. Next, we look up the pin MUX control documentation:

Good. Pin 0 can connect to XBAR1_INOUT17. So we need to set up the daisy chain:

Pin 0 (GPIO_AD_B0_03) ->XBAR1_INOUT17->QTIMER2_TIMER0

// set up QuadTimer2 to get signal from pin 0 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

The last connection is a bit tricky, which is why I use the helper function xbar_connect:

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;}

which came from Teensy's pwm.c

Note the line turning on the XBAR1 clock! Without that it won't run.

Four 64-bit counters for single channel interferometer:

We can now pull all of this information together to a 3 axis interferometer program.

// uMD2// heterodyne interferometer firmware for Teensy 4.0// Jan Beck 2020
// required libraries// u8g2 graphics library available though the Library Manager#include <U8x8lib.h>
// REF - D10// MEAS1 - D13// MEAS2 - D14// MEAS3 - D9
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/U8X8_PIN_NONE);
IMXRT_TMR_t * TMR1 = (IMXRT_TMR_t *)&IMXRT_TMR1;IMXRT_TMR_t * TMR2 = (IMXRT_TMR_t *)&IMXRT_TMR2;IMXRT_TMR_t * TMR3 = (IMXRT_TMR_t *)&IMXRT_TMR3;IMXRT_TMR_t * TMR4 = (IMXRT_TMR_t *)&IMXRT_TMR4;
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;}
IntervalTimer usbTimer; // send USB data at predefinded rate to make frequency analysis work in the GUI
// XXX The IOMUX is also used to configure other pin characteristics, such asvoltage level, drive strength, and hysteresis.
void setup(){ Serial.begin(115200); // initialize and clear display u8x8.begin(); u8x8.setPowerSave(0); u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.drawString(0, 0, "MEAS3:"); u8x8.drawString(0, 2, "REF:"); u8x8.drawString(0, 4, "DIFF:");
// set up QuadTimer1 CCM_CCGR6 |= CCM_CCGR6_QTIMER1(CCM_CCGR_ON); // enable QTMR1 clock IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = 1; // QuadTimer1 Counter 0 on pin D10 using ALT1 IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_00 |= 0b1000000000000000; // enable hysteresis in pin TMR1->CH[0].CTRL = 0; // stop TMR1->CH[1].CTRL = 0; // stop TMR1->CH[2].CTRL = 0; // stop TMR1->CH[3].CTRL = 0; // stop TMR1->CH[0].CNTR = 0; // set count to 0 TMR1->CH[1].CNTR = 0; // set count to 0 TMR1->CH[2].CNTR = 0; // set count to 0 TMR1->CH[3].CNTR = 0; // set count to 0 TMR1->CH[0].LOAD = 0; TMR1->CH[1].LOAD = 0; TMR1->CH[2].LOAD = 0; TMR1->CH[3].LOAD = 0; TMR1->CH[0].SCTRL = TMR1->CH[0].CSCTRL = 0; TMR1->CH[1].SCTRL = TMR1->CH[1].CSCTRL = 0; TMR1->CH[2].SCTRL = TMR1->CH[2].CSCTRL = 0; TMR1->CH[3].SCTRL = TMR1->CH[3].CSCTRL = 0; TMR1->CH[0].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR1->CH[1].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR1->CH[2].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR1->CH[3].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR1->CH[0].CMPLD1 = 0xffff; TMR1->CH[1].CMPLD1 = 0xffff; TMR1->CH[2].CMPLD1 = 0xffff; TMR1->CH[3].CMPLD1 = 0xffff; TMR1->CH[3].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR1->CH[3].CTRL |= TMR_CTRL_PCS(6); // Primary Count Source: CH[2] output TMR1->CH[2].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR1->CH[2].CTRL |= TMR_CTRL_PCS(5); // Primary Count Source: CH[1] output TMR1->CH[1].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR1->CH[1].CTRL |= TMR_CTRL_PCS(4); // Primary Count Source: CH[0] output TMR1->CH[0].CTRL = TMR_CTRL_CM (1); // Count Mode: Count rising edges of primary source TMR1->CH[0].CTRL |= TMR_CTRL_PCS(0); // Primary Count Source: Counter 0 input pin
// set up QuadTimer2 - D0 CCM_CCGR6 |= CCM_CCGR6_QTIMER2(CCM_CCGR_ON); // enable QTMR2 clock // set up QuadTimer2 to get signal from pin 0 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 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 (1); // Count Mode: Count rising edges of primary source TMR2->CH[0].CTRL |= TMR_CTRL_PCS(0); // Primary Count Source: Counter 0 input pin
// set up QuadTimer3 - D14 CCM_CCGR6 |= CCM_CCGR6_QTIMER3(CCM_CCGR_ON); // enable QTMR3 clock IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 1; // Daisy Chain 1 - QT3 Counter 2 conects to pin D14 ALT1 IOMUXC_QTIMER3_TIMER2_SELECT_INPUT = 1 ; // Daisy Chain 2 - QT3 Counter 2 conects to pin D14 ALT1 IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_02 |= 0b1000000000000000; // enable hysteresis in pin TMR3->CH[0].CTRL = 0; // stop TMR3->CH[2].SCTRL = TMR3->CH[2].CSCTRL = 0; TMR3->CH[1].CTRL = 0; // stop TMR3->CH[2].CTRL = 0; // stop TMR3->CH[3].CTRL = 0; // stop TMR3->CH[0].CNTR = 0; // set count to 0 TMR3->CH[1].CNTR = 0; // set count to 0 TMR3->CH[2].CNTR = 0; // set count to 0 TMR3->CH[3].CNTR = 0; // set count to 0 TMR3->CH[0].LOAD = 0; TMR3->CH[1].LOAD = 0; TMR3->CH[2].LOAD = 0; TMR3->CH[3].LOAD = 0; TMR3->CH[0].SCTRL = TMR3->CH[0].CSCTRL = 0; TMR3->CH[1].SCTRL = TMR3->CH[1].CSCTRL = 0; TMR3->CH[2].SCTRL = TMR3->CH[2].CSCTRL = 0; TMR3->CH[3].SCTRL = TMR3->CH[3].CSCTRL = 0; TMR3->CH[0].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR3->CH[1].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR3->CH[2].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR3->CH[3].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR3->CH[0].CMPLD1 = 0xffff; TMR3->CH[1].CMPLD1 = 0xffff; TMR3->CH[2].CMPLD1 = 0xffff; TMR3->CH[3].CMPLD1 = 0xffff; TMR3->CH[3].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR3->CH[3].CTRL |= TMR_CTRL_PCS(6); // Primary Count Source: CH[2] output TMR3->CH[2].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR3->CH[2].CTRL |= TMR_CTRL_PCS(5); // Primary Count Source: CH[1] output TMR3->CH[1].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR3->CH[1].CTRL |= TMR_CTRL_PCS(4); // Primary Count Source: CH[0] output TMR3->CH[0].CTRL = TMR_CTRL_CM (1); // Count Mode: Count rising edges of primary source TMR3->CH[0].CTRL |= TMR_CTRL_PCS(2); // Primary Count Source: Counter 2 input pin
// set up QuadTimer4 - MEAS3 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 9 IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_11 |= 0b1000000000000000; // enable hysteresis in pin 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 (1); // Count Mode: Count rising edges of primary source TMR4->CH[0].CTRL |= TMR_CTRL_PCS(2); // Primary Count Source: Counter 2 input pin
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
}
char buffer[100];int64_t counter_MEAS1 = 0;int64_t counter_MEAS2 = 0;int64_t counter_MEAS3 = 0;int64_t counter_REF = 0;
void USBSender(){ if (Serial.availableForWrite() > 256) { //constant delay timer read asm volatile("ldr r0 ,=0x401dc00a \n\t" // load address of TMR1_CNTR0 into r0 "ldr r1 ,=0x401e000a \n\t" // load address of TMR2_CNTR0 into r1 "ldr r2 ,=0x401e400a \n\t" // load address of TMR3_CNTR0 into r2 "ldr r3 ,=0x401e800a \n\t" // load address of TMR4_CNTR0 into r3 "ldrh r4 ,[r0],#0 \n\t" // hold TMR1 by reading TMR1_CNTR0 "ldrh r5 ,[r1],#0 \n\t" // hold TMR2 by reading TMR2_CNTR0 "ldrh r6 ,[r2],#0 \n\t" // hold TMR3 by reading TMR3_CNTR0 "ldrh r7 ,[r3],#0 \n\t" // hold TMR4 by reading TMR4_CNTR0 : : : "r0", "r1", "r2", "r3" ); counter_MEAS1 = TMR1->CH[3].HOLD; counter_MEAS1 = counter_MEAS1 * 65536 + TMR1->CH[2].HOLD; counter_MEAS1 = counter_MEAS1 * 65536 + TMR1->CH[1].HOLD; counter_MEAS1 = counter_MEAS1 * 65536 + TMR1->CH[0].HOLD; counter_MEAS2 = TMR2->CH[3].HOLD; counter_MEAS2 = counter_MEAS2 * 65536 + TMR2->CH[2].HOLD; counter_MEAS2 = counter_MEAS2 * 65536 + TMR2->CH[1].HOLD; counter_MEAS2 = counter_MEAS2 * 65536 + TMR2->CH[0].HOLD; counter_MEAS3 = TMR3->CH[3].HOLD; counter_MEAS3 = counter_MEAS3 * 65536 + TMR3->CH[2].HOLD; counter_MEAS3 = counter_MEAS3 * 65536 + TMR3->CH[1].HOLD; counter_MEAS3 = counter_MEAS3 * 65536 + TMR3->CH[0].HOLD; counter_REF = TMR4->CH[3].HOLD; counter_REF = counter_REF * 65536 + TMR4->CH[2].HOLD; counter_REF = counter_REF * 65536 + TMR4->CH[1].HOLD; counter_REF = counter_REF * 65536 + TMR4->CH[0].HOLD;
int64_t diff = counter_MEAS3 - counter_REF; sprintf(buffer, "%lld", diff); Serial.println(buffer); Serial.printf("%" PRId64 "\n", counter_MEAS1); Serial.printf("%" PRId64 "\n", counter_MEAS2); Serial.printf("%" PRId64 "\n", counter_MEAS3); Serial.printf("%" PRId64 "\n", counter_REF); Serial.println(); }}
void loop(){



delay(200);}

Because I don't see any facility to synchronize the read of the 4 QuadTimers, I wrote the inline assembly routine to read them with constant timing.

Sam Goldwasser has made a number of additions and improvements and made the code compatible with the GUI for the uMD1:


//****************************************************************************************//// Micro Measurement Display 2 - uMD2 - Heterodyne interferometer firmware for Teensy 4.0 //// Jan Beck and Sam Goldwasser 2021 ////****************************************************************************************//
// V2.00 Formatted for GUI and OLED.// V2.01 First version that talks to GUI. :)// V2.02 Changed OLED to display counters XOR displacements. Counts are screwy due to noise or crosstalk or....// V2.04 First working heterodyne version. Removed analogwrite stuff, fixed serial formatting.// V2.05 Changed to double clocking, resolution 79 nm with PMI, Still slow drift (~2 um/min) if OLED is enabled.// V2.06 ??// V2.07 moved D13 to D0
#define FirmwareVersion 2.07 // Firmware version used by OLED and GUI About.
#define Homodyne 0 // Leave at 0 for heterodyne.
#define Heterodyne 3 // Set to # of axes, sample rate for heterodyne always 1 kHz.
#define Multiplier 2 // Integer counts/cycle. 2 for double clocking.#define Scale_Shift 1 // Set logbase2(Multiplier)
#define OLED_Displacement // OLED to display Disp1,Disp2,Disp3
// u8g2 graphics library available though the Library Manager
#include <U8x8lib.h>
// MEAS1 TMR1 D10// MEAS2 TMR2 D0// MEAS3 TMR3 D14// REF TMR4 D9
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/U8X8_PIN_NONE);
IMXRT_TMR_t * TMR1 = (IMXRT_TMR_t *)&IMXRT_TMR1;IMXRT_TMR_t * TMR2 = (IMXRT_TMR_t *)&IMXRT_TMR2;IMXRT_TMR_t * TMR3 = (IMXRT_TMR_t *)&IMXRT_TMR3;IMXRT_TMR_t * TMR4 = (IMXRT_TMR_t *)&IMXRT_TMR4;
IntervalTimer usbTimer; // send USB data at predefinded rate to make frequency analysis work in the GUI
// XXX 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
char buffer[100];char oled_buffer[25];uint8_t tiles[8] = {0x0,0x80,0x7c,0x40,0x40,0x40,0x7c,0x0};
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 setup(){ pinMode(1, INPUT_PULLUP); pinMode(2, INPUT_PULLUP); pinMode(3, INPUT_PULLUP); pinMode(4, INPUT_PULLUP); pinMode(5, INPUT_PULLUP); pinMode(7, INPUT_PULLUP); pinMode(9, INPUT_PULLUP); pinMode(10, INPUT_PULLUP); pinMode(13, INPUT_PULLUP); pinMode(14, INPUT_PULLUP);
Serial.begin(2000000); // initialize and clear display u8x8.begin(); u8x8.setPowerSave(0); u8x8.setFont(u8x8_font_chroma48medium8_r);
// Banner and sequence number display u8x8.setFont(u8x8_font_chroma48medium8_r); // Default font (thin) //u8x8.setFont(u8x8_font_amstrad_cpc_extended_f); // Font with micro symbol (fatter u8x8.drawString(0, 0, " - MD2 V -"); u8x8.drawTile(3, 0, 1, tiles); // Needed for tail of micro symbol
sprintf(buffer, "%.2f", FirmwareVersion); u8x8.drawString(9, 0, buffer);
#ifdef OLED_Displacement u8x8.drawString(0, 2, "Seq#: "); u8x8.drawString(0, 4, "Disp1:");
if (Heterodyne > 1) { u8x8.drawString(0, 5, "Disp2:"); u8x8.drawString(0, 6, "Disp3:"); }#endif
// set up QuadTimer1: MEAS1 on D10 CCM_CCGR6 |= CCM_CCGR6_QTIMER1(CCM_CCGR_ON); // enable QTMR1 clock IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_00 = 1; // QuadTimer1 Counter 0 on pin D10 using ALT1 IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_00 |= 0b1000000000000000; // enable hysteresis in pin D10 TMR1->CH[0].CTRL = 0; // stop TMR1->CH[1].CTRL = 0; // stop TMR1->CH[2].CTRL = 0; // stop TMR1->CH[3].CTRL = 0; // stop TMR1->CH[0].CNTR = 0; // set count to 0 TMR1->CH[1].CNTR = 0; // set count to 0 TMR1->CH[2].CNTR = 0; // set count to 0 TMR1->CH[3].CNTR = 0; // set count to 0 TMR1->CH[0].LOAD = 0; TMR1->CH[1].LOAD = 0; TMR1->CH[2].LOAD = 0; TMR1->CH[3].LOAD = 0; TMR1->CH[0].SCTRL = TMR1->CH[0].CSCTRL = 0; TMR1->CH[1].SCTRL = TMR1->CH[1].CSCTRL = 0; TMR1->CH[2].SCTRL = TMR1->CH[2].CSCTRL = 0; TMR1->CH[3].SCTRL = TMR1->CH[3].CSCTRL = 0; TMR1->CH[0].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR1->CH[1].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR1->CH[2].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR1->CH[3].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR1->CH[0].CMPLD1 = 0xffff; TMR1->CH[1].CMPLD1 = 0xffff; TMR1->CH[2].CMPLD1 = 0xffff; TMR1->CH[3].CMPLD1 = 0xffff; TMR1->CH[3].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR1->CH[3].CTRL |= TMR_CTRL_PCS(6); // Primary Count Source: CH[2] output TMR1->CH[2].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR1->CH[2].CTRL |= TMR_CTRL_PCS(5); // Primary Count Source: CH[1] output TMR1->CH[1].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR1->CH[1].CTRL |= TMR_CTRL_PCS(4); // Primary Count Source: CH[0] output TMR1->CH[0].CTRL = TMR_CTRL_CM (2); // Count Mode: Count rising edges of primary source TMR1->CH[0].CTRL |= TMR_CTRL_PCS(0); // Primary Count Source: Counter 0 input pin // set up QuadTimer2: MEAS2 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 (2); // Count Mode: Count rising edges of primary source TMR2->CH[0].CTRL |= TMR_CTRL_PCS(0); // Primary Count Source: Counter 0 input pin
// set up QuadTimer3: MEAS3 on D14 CCM_CCGR6 |= CCM_CCGR6_QTIMER3(CCM_CCGR_ON); // enable QTMR3 clock IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 1; // Daisy Chain 1 - QT3 Counter 2 conects to pin D14 ALT1 IOMUXC_QTIMER3_TIMER2_SELECT_INPUT = 1 ; // Daisy Chain 2 - QT3 Counter 2 conects to pin D14 ALT1 IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_02 |= 0b1000000000000000; // enable hysteresis in pin D14 TMR3->CH[0].CTRL = 0; // stop TMR3->CH[2].SCTRL = TMR3->CH[2].CSCTRL = 0; TMR3->CH[1].CTRL = 0; // stop TMR3->CH[2].CTRL = 0; // stop TMR3->CH[3].CTRL = 0; // stop TMR3->CH[0].CNTR = 0; // set count to 0 TMR3->CH[1].CNTR = 0; // set count to 0 TMR3->CH[2].CNTR = 0; // set count to 0 TMR3->CH[3].CNTR = 0; // set count to 0 TMR3->CH[0].LOAD = 0; TMR3->CH[1].LOAD = 0; TMR3->CH[2].LOAD = 0; TMR3->CH[3].LOAD = 0; TMR3->CH[0].SCTRL = TMR3->CH[0].CSCTRL = 0; TMR3->CH[1].SCTRL = TMR3->CH[1].CSCTRL = 0; TMR3->CH[2].SCTRL = TMR3->CH[2].CSCTRL = 0; TMR3->CH[3].SCTRL = TMR3->CH[3].CSCTRL = 0; TMR3->CH[0].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR3->CH[1].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR3->CH[2].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR3->CH[3].COMP1 = 0xffff; // send count signal to next counter on overflow at 0xffff TMR3->CH[0].CMPLD1 = 0xffff; TMR3->CH[1].CMPLD1 = 0xffff; TMR3->CH[2].CMPLD1 = 0xffff; TMR3->CH[3].CMPLD1 = 0xffff; TMR3->CH[3].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR3->CH[3].CTRL |= TMR_CTRL_PCS(6); // Primary Count Source: CH[2] output TMR3->CH[2].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR3->CH[2].CTRL |= TMR_CTRL_PCS(5); // Primary Count Source: CH[1] output TMR3->CH[1].CTRL = TMR_CTRL_CM (7); // Count Mode: Cascaded counter mode TMR3->CH[1].CTRL |= TMR_CTRL_PCS(4); // Primary Count Source: CH[0] output TMR3->CH[0].CTRL = TMR_CTRL_CM (2); // Count Mode: Count rising edges of primary source TMR3->CH[0].CTRL |= TMR_CTRL_PCS(2); // Primary Count Source: Counter 2 input pin
// set up QuadTimer4: REF 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 (2); // Count Mode: Count rising edges of primary source TMR4->CH[0].CTRL |= TMR_CTRL_PCS(2); // Primary Count Source: Counter 2 input pin
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
}
int64_t counter_REF = 0;int64_t counter_MEAS1 = 0;int64_t counter_MEAS2 = 0;int64_t counter_MEAS3 = 0;
int64_t counter_REF_save = 0;int64_t counter_MEAS1_save = 0;int64_t counter_MEAS2_save = 0;int64_t counter_MEAS3_save = 0;
int32_t OLED_REF = 0;int32_t OLED_MEAS1 = 0;int32_t OLED_MEAS2 = 0;int32_t OLED_MEAS3 = 0;
int32_t REF = 0;int32_t MEAS1 = 0;int32_t MEAS2 = 0;int32_t MEAS3 = 0;
int32_t OLED_REF_save = 0;int32_t OLED_MEAS1_save = 0;int32_t OLED_MEAS2_save = 0;int32_t OLED_MEAS3_save = 0;
int32_t OLED_DISP1 = 0;int32_t OLED_DISP2 = 0;int32_t OLED_DISP3 = 0;
int32_t OLED_DISP1_save = 0;int32_t OLED_DISP2_save = 0;int32_t OLED_DISP3_save = 0;
int32_t encoder1Value = 0;int32_t encoder2Value = 0;int32_t encoder3Value = 0;
int32_t encoder1ValueSave = 0;int32_t encoder2ValueSave = 0;int32_t encoder3ValueSave = 0;
int32_t compareValue = 0;
int32_t displacement1 = 0;int32_t displacement2 = 0;int32_t displacement3 = 0;
int32_t previous_displacement1 = 0;int32_t previous_displacement2 = 0;int32_t previous_displacement3 = 0;
int32_t velocity1 = 0;int32_t velocity2 = 0;int32_t velocity3 = 0;
uint64_t sequenceNumber = 0;uint32_t sn = 0;
int32_t LowSpeedCode = 0;int32_t LowSpeedData = 0;int32_t LowSpeedCodeSelect = 0;int32_t FirmwareInt = FirmwareVersion * 100;
void USBSender(){ //constant delay timer read - update values. asm volatile("ldr r0 ,=0x401dc00a \n\t" // load address of TMR1_CNTR0 into r0 "ldr r1 ,=0x401e000a \n\t" // load address of TMR2_CNTR0 into r1 "ldr r2 ,=0x401e400a \n\t" // load address of TMR3_CNTR0 into r2 "ldr r3 ,=0x401e800a \n\t" // load address of TMR4_CNTR0 into r3 "ldrh r4 ,[r0],#0 \n\t" // hold TMR1 by reading TMR1_CNTR0 "ldrh r5 ,[r1],#0 \n\t" // hold TMR2 by reading TMR2_CNTR0 "ldrh r6 ,[r2],#0 \n\t" // hold TMR3 by reading TMR3_CNTR0 "ldrh r7 ,[r3],#0 \n\t" // hold TMR4 by reading TMR4_CNTR0 : : : "r0","r1","r2","r3","r4","r5","r6","r7" );
REF = (counter_REF - counter_REF_save) >> Scale_Shift; MEAS1 = (counter_MEAS1 - counter_MEAS1_save) >> Scale_Shift; MEAS2 = (counter_MEAS2 - counter_MEAS2_save) >> Scale_Shift; MEAS3 = (counter_MEAS3 - counter_MEAS3_save) >> Scale_Shift; counter_REF_save = counter_REF; counter_MEAS1_save = counter_MEAS1; counter_MEAS2_save = counter_MEAS2; counter_MEAS3_save = counter_MEAS3;
counter_REF = TMR4->CH[3].HOLD; counter_REF = counter_REF * 65536 + TMR4->CH[2].HOLD; counter_REF = counter_REF * 65536 + TMR4->CH[1].HOLD; counter_REF = counter_REF * 65536 + TMR4->CH[0].HOLD;
counter_MEAS1 = TMR1->CH[3].HOLD; counter_MEAS1 = counter_MEAS1 * 65536 + TMR1->CH[2].HOLD; counter_MEAS1 = counter_MEAS1 * 65536 + TMR1->CH[1].HOLD; counter_MEAS1 = counter_MEAS1 * 65536 + TMR1->CH[0].HOLD; counter_MEAS2 = TMR2->CH[3].HOLD; counter_MEAS2 = counter_MEAS2 * 65536 + TMR2->CH[2].HOLD; counter_MEAS2 = counter_MEAS2 * 65536 + TMR2->CH[1].HOLD; counter_MEAS2 = counter_MEAS2 * 65536 + TMR2->CH[0].HOLD; counter_MEAS3 = TMR3->CH[3].HOLD; counter_MEAS3 = counter_MEAS3 * 65536 + TMR3->CH[2].HOLD; counter_MEAS3 = counter_MEAS3 * 65536 + TMR3->CH[1].HOLD; counter_MEAS3 = counter_MEAS3 * 65536 + TMR3->CH[0].HOLD;
previous_displacement1 = displacement1; previous_displacement2 = displacement2; previous_displacement3 = displacement3; displacement1 = (counter_MEAS1 - counter_REF); displacement2 = (counter_MEAS2 - counter_REF); displacement3 = (counter_MEAS3 - counter_REF);
velocity1 = displacement1 - previous_displacement1; velocity2 = displacement2 - previous_displacement2; velocity3 = displacement3 - previous_displacement3; sequenceNumber++; sn = sequenceNumber;
// Set up appropriate low speed data LowSpeedCode = 0; // Default to no low speed data LowSpeedData = 0; LowSpeedCodeSelect = sequenceNumber & 0x1f;
if (LowSpeedCodeSelect == 1) // Send firmware version { LowSpeedCode = 10; LowSpeedData = FirmwareInt; } else if (LowSpeedCodeSelect == 2) // Sammple frequency x 100 { LowSpeedCode = 8; LowSpeedData = 100000; } else if (LowSpeedCodeSelect == 13) // Tell GUI this is a homodyne system if true { LowSpeedCode = 20; LowSpeedData = (Multiplier * 256) + Homodyne; // # counts per cycle << 8 + # homodyne axes }
else if (LowSpeedCodeSelect == 11) // # CPU clocks spent in capture and analysis { LowSpeedCode = 121; LowSpeedData = 1; }
else if (LowSpeedCodeSelect == 12) // # CPU clocks spent in communications { LowSpeedCode = 122; LowSpeedData = 1; // Timer1USBCounts not implemented yet }
if (Serial.availableForWrite() > 256) { Serial.printf("%li ", REF); // 1: REF Serial.printf("%li ", MEAS1); // 2: MEAS1 Serial.printf("%li ", displacement1); // 3: Displacement 1 Serial.printf("%li ", velocity1); // 4 Velocity Count 1 Serial.print("0 "); // 5 Phase 1 Serial.printf("%llu ", sequenceNumber); // 6 Sequence Number Serial.printf("%li ", LowSpeedCode); // 7 LowSpeedCode Serial.printf("%li", LowSpeedData); // 8 LowSpeedData
if (Heterodyne > 1) { Serial.printf(" %li ", MEAS2); // 9: MEAS2 Serial.printf("%li ", displacement2); // 10: Displacement 2 Serial.printf("%li ", velocity2); // 11: Velocity Count 2 Serial.print("0 "); // 12: Phase 2 Serial.printf("%li ", MEAS3); // 13: MEAS3 Serial.printf("%li ", displacement3); // 14: Displacement 3 Serial.printf("%li ", velocity3); // 15: Velocity Count 3 Serial.print("0"); // 16: Phase 3 } Serial.println(); }}
void loop(){ #ifdef OLED_Displacement
u8x8.drawString(6, 2, " "); sprintf(oled_buffer, "%li", sn); u8x8.drawString(6, 2, oled_buffer);
OLED_DISP1 = displacement1; if (OLED_DISP1 != OLED_DISP1_save) { OLED_DISP1_save = OLED_DISP1; u8x8.drawString(7, 4, " "); sprintf(oled_buffer, "%li", OLED_DISP1); u8x8.drawString(7, 4, oled_buffer); }
if (Heterodyne > 1) { OLED_DISP2 = displacement2; if (OLED_DISP2 != OLED_DISP2_save) { OLED_DISP2_save = OLED_DISP2; u8x8.drawString(7, 5, " "); sprintf(oled_buffer, "%li", OLED_DISP2); u8x8.drawString(7, 5, oled_buffer); }
OLED_DISP3 = displacement3; if (OLED_DISP3 != OLED_DISP3_save) { OLED_DISP3_save = OLED_DISP3; u8x8.drawString(7, 6, " "); sprintf(oled_buffer, "%li", OLED_DISP3); u8x8.drawString(7, 6, oled_buffer); } }
#endif delay(100);}

Sam Goldwasser has taken this ball and run with it. You can find the latest version of this firmware (with many improvements) here:

References: