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.0GPIO_B0_10
- D6GPIO_B0_11
- D9GPIO_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
- D19GPIO_EMC_15
- Unavailable on Teensy 4.0GPIO_B0_06
- Unavailable on Teensy 4.0GPIO_AD_B1_01
- D18GPIO_EMC_16
- Unavailable on Teensy 4.0GPIO_B0_07
- Unavailable on Teensy 4.0GPIO_EMC_17
- Unavailable on Teensy 4.0GPIO_AD_B1_02
- D14GPIO_B0_08
- Unavailable on Teensy 4.0GPIO_B1_10
- Unavailable on Teensy 4.0GPIO_AD_B1_03
- D15GPIO_EMC_18
- Unavailable on Teensy 4.0
For QuadTimer2:
GPIO_EMC_19
- Unavailable on Teensy 4.0GPIO_B0_03
- D13GPIO_EMC_20
- Unavailable on Teensy 4.0GPIO_B0_04
- Unavailable on Teensy 4.0GPIO_EMC_21
- Unavailable on Teensy 4.0GPIO_B0_05
- Unavailable on Teensy 4.0GPIO_EMC_22
- Unavailable on Teensy 4.0GPIO_B1_09
- Unavailable on Teensy 4.0
For QuadTimer1:
GPIO_B0_00
- D10GPIO_B0_01
- D12GPIO_B0_02
- D11GPIO_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:
- ARM GCC Inline Assembler Cookbook - https://www.ic.unicamp.br/~celio/mc404-s2-2015/docs/ARM-GCC-Inline-Assembler-Cookbook.pdf
- i.MX RT1060 Processor ReferenceManual - https://www.pjrc.com/teensy/IMXRT1060RM_rev2.pdf
- Micro Measurement Display 2 (µMD2) - http://repairfaq.cis.upenn.edu/Misc/uMD2/