// proof of concept of using FlexIO1, FlexIO2, and FlexIO3 at the same time
#include <Arduino.h>
#include "FlexIO_t4.h"
#define SHIFTNUM 4 // number of shifters used (must be 1, 2, 4, or 8)
#define BYTES_PER_BEAT (sizeof(uint8_t))
#define BEATS_PER_SHIFTER (sizeof(uint32_t)/BYTES_PER_BEAT)
#define BYTES_PER_BURST (sizeof(uint32_t)*SHIFTNUM)
#define SHIFTER_IRQ (SHIFTNUM-1)
#define TIMER_IRQ 0
#define FLEXIO_BASE_CLOCK 120000000UL
#define SHIFT_CLOCK_DIVIDER 10 // shift clock is 120 MHz divided by 10 = 12 MHz
#define FLEXIO_SHIFT_CLOCK (FLEXIO_BASE_CLOCK/SHIFT_CLOCK_DIVIDER)
#define FLEXIO_ISR_PRIORITY 64 // interrupt is timing sensitive, so use relatively high priority (supersedes USB)
FlexIOHandler *pFlex1, *pFlex2, *pFlex3;
IMXRT_FLEXIO_t *p1, *p2, *p3;
uint8_t databuf[32] = {0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x40, 0x80, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x40, 0x80, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x40, 0x80, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x40, 0x80};
/* variables used by ISR */
volatile unsigned int bytes_remaining;
volatile unsigned int bursts_to_complete;
volatile uint32_t *readPtr1, *readPtr2, *readPtr3;
uint32_t finalBurstBuffer[SHIFTNUM];
volatile bool pendingTransfer;
/*
* Output FlexIO Teensy Pin
* D0 2:0 10
* D1 2:1 12
* D2 2:2 11
* D3 1:4 2
* D4 1:5 3
* D5 1:6 4
* D6 3:16 8
* D7 3:17 7
* CLK 3:2 14
*
* FlexIO1 outputs 8 bits starting on FlexIO1:1 with no clock output.
* FlexIO2 outputs 8 bits starting on FlexIO2:0 with no clock output.
* FlexIO3 outputs 8 bits starting on FlexIO3:10 with clock output on FlexIO3:2.
*/
void setup() {
Serial.begin(115200);
Serial.print(CrashReport);
Serial.println("Start setup");
pinMode(15, OUTPUT);
digitalWriteFast(15, LOW);
FlexIO_Init();
}
void loop() {
transmitAsync(databuf, 32);
delay(1000);
}
void transmitAsync(void *src, uint32_t bytes) {
while (pendingTransfer) {
yield(); // wait until previous transfer is complete
}
pendingTransfer = true;
bursts_to_complete = bytes / BYTES_PER_BURST;
int remainder = bytes % BYTES_PER_BURST;
if (remainder != 0) {
memset(finalBurstBuffer, 0, sizeof(finalBurstBuffer));
memcpy(finalBurstBuffer, (uint8_t*)src + bytes - remainder, remainder);
bursts_to_complete++;
}
bytes_remaining = bytes;
/* Synchronize FlexIO timers by using precise timing.
* FlexIO1, FlexIO2, and FlexIO3 are triggered in sequence with precise delay time equal to the burst time.
* The first two bursts (FlexIO1 and FlexIO2 only) are "dummy" bursts of all zeros with no clock signal.
* Then all three FlexIOs output data simultaneously starting with the third burst.
* Triggering occurs when data is written to the last shifter SHIFTBUF[SHIFTNUM-1].
*/
uint8_t beats = SHIFTNUM * BEATS_PER_SHIFTER;
p1->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
p2->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
p3->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
uint32_t burst_cycles = (F_CPU_ACTUAL) * ((SHIFTNUM * BEATS_PER_SHIFTER) / ((float) FLEXIO_SHIFT_CLOCK));
uint32_t cycleStart = ARM_DWT_CYCCNT;
uint32_t cycleDelay;
// load zeros for dummy burst without triggering yet
for (int i = 0; i < SHIFTNUM - 1; i++) {
p1->SHIFTBUF[i] = 0;
}
// trigger dummy burst
cycleDelay = burst_cycles;
__disable_irq();
while (ARM_DWT_CYCCNT - cycleStart < cycleDelay) ;
p1->SHIFTBUF[SHIFTNUM - 1] = 0;
__enable_irq();
while (0 == (p1->SHIFTSTAT & (1 << (SHIFTNUM - 1)))) ; // wait for dummy burst to start
// load zeros for dummy burst without triggering yet
for (int i = 0; i < SHIFTNUM - 1; i++) {
p1->SHIFTBUF[i] = 0;
p2->SHIFTBUF[i] = 0;
}
// trigger dummy burst
cycleDelay += burst_cycles;
__disable_irq();
p1->SHIFTBUF[SHIFTNUM - 1] = 0;
while (ARM_DWT_CYCCNT - cycleStart < cycleDelay) ;
p2->SHIFTBUF[SHIFTNUM - 1] = 0;
__enable_irq();
while (0 == (p2->SHIFTSTAT & (1 << (SHIFTNUM - 1)))) ; // wait for dummy burst to start
// load data for first data burst without triggering yet
if (bytes_remaining < BYTES_PER_BURST) {
beats = bytes_remaining / BYTES_PER_BEAT;
p1->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
p2->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
p3->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
readPtr1 = finalBurstBuffer;
readPtr2 = finalBurstBuffer;
readPtr3 = finalBurstBuffer;
bytes_remaining = 0;
} else {
readPtr1 = (uint32_t*)src;
readPtr2 = (uint32_t*)src;
readPtr3 = (uint32_t*)src;
bytes_remaining -= BYTES_PER_BURST;
}
for (int i = 0; i < SHIFTNUM - 1; i++) {
p1->SHIFTBUF[i] = *readPtr1++;
p2->SHIFTBUF[i] = *readPtr2++;
p3->SHIFTBUF[i] = *readPtr3++;
}
// trigger first data burst
cycleDelay += burst_cycles;
__disable_irq();
p1->SHIFTBUF[SHIFTNUM - 1] = *readPtr1++;
p2->SHIFTBUF[SHIFTNUM - 1] = *readPtr2++;
while (ARM_DWT_CYCCNT - cycleStart < cycleDelay) ;
p3->SHIFTBUF[SHIFTNUM - 1] = *readPtr3++;
__enable_irq();
asm("dsb");
// now that clocks are synchronized, use an interrupt to trigger following bursts
p3->TIMSTAT = (1 << TIMER_IRQ); // clear timer interrupt signal
p3->TIMIEN |= (1 << TIMER_IRQ);
p3->SHIFTSIEN |= (1 << SHIFTER_IRQ);
}
FASTRUN void isr() {
if (p3->TIMSTAT & (1 << TIMER_IRQ)) { // interrupt from end of burst
p3->TIMSTAT = (1 << TIMER_IRQ); // clear timer interrupt signal
bursts_to_complete--;
if (bursts_to_complete == 0) {
p3->TIMIEN &= ~(1 << TIMER_IRQ); // disable timer interrupt
pendingTransfer = false;
asm("dsb");
transferCompleteCallback();
return;
}
}
if (p3->SHIFTSTAT & (1 << SHIFTER_IRQ)) { // interrupt from empty shifter buffer
if (bytes_remaining == 0) { // just started final burst, no data to load
p3->SHIFTSIEN &= ~(1 << SHIFTER_IRQ); // disable shifter interrupt
} else if (bytes_remaining < BYTES_PER_BURST) { // just started second-to-last burst, load data for final burst
uint8_t beats = bytes_remaining / BYTES_PER_BEAT;
p1->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
p2->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
p3->TIMCMP[0] = ((beats * 2U - 1) << 8) | (SHIFT_CLOCK_DIVIDER / 2U - 1U);
readPtr1 = finalBurstBuffer;
readPtr2 = finalBurstBuffer;
readPtr3 = finalBurstBuffer;
bytes_remaining = 0;
for (int i = 0; i < SHIFTNUM; i++) {
p1->SHIFTBUF[i] = *readPtr1++;
p2->SHIFTBUF[i] = *readPtr2++;
p3->SHIFTBUF[i] = *readPtr3++;
}
} else {
bytes_remaining -= BYTES_PER_BURST;
for (int i = 0; i < SHIFTNUM; i++) {
p1->SHIFTBUF[i] = *readPtr1++;
p2->SHIFTBUF[i] = *readPtr2++;
p3->SHIFTBUF[i] = *readPtr3++;
}
}
}
asm("dsb");
}
void transferCompleteCallback() {
digitalWriteFast(15, HIGH);
delayNanoseconds(80);
digitalWriteFast(15, LOW);
// Serial.println("d");
}
void FlexIO_Init() {
/* Get FlexIO channels */
pFlex1 = FlexIOHandler::flexIOHandler_list[0]; // use FlexIO1
pFlex2 = FlexIOHandler::flexIOHandler_list[1]; // use FlexIO2
pFlex3 = FlexIOHandler::flexIOHandler_list[2]; // use FlexIO3
/* Pointer to the port structures in the FlexIO channels */
p1 = &pFlex1->port();
p2 = &pFlex2->port();
p3 = &pFlex3->port();
/* Pointer to the hardware structures in the FlexIO channels */
const FlexIOHandler::FLEXIO_Hardware_t *hw1, *hw2, *hw3;
hw1 = &pFlex1->hardware();
hw2 = &pFlex2->hardware();
hw3 = &pFlex3->hardware();
/* Basic pin setup */
pinMode(10, OUTPUT); // FlexIO2:0
pinMode(12, OUTPUT); // FlexIO2:1
pinMode(11, OUTPUT); // FlexIO2:2
pinMode(2, OUTPUT); // FlexIO1:4
pinMode(3, OUTPUT); // FlexIO1:5
pinMode(4, OUTPUT); // FlexIO1:6
pinMode(8, OUTPUT); // FlexIO3:16
pinMode(7, OUTPUT); // FlexIO3:17
pinMode(14, OUTPUT); // FlexIO3:2
/* High speed and drive strength configuration */
*(portControlRegister(10)) = 0xFF;
*(portControlRegister(12)) = 0xFF;
*(portControlRegister(11)) = 0xFF;
*(portControlRegister(2)) = 0xFF;
*(portControlRegister(3)) = 0xFF;
*(portControlRegister(4)) = 0xFF;
*(portControlRegister(8)) = 0xFF;
*(portControlRegister(7)) = 0xFF;
*(portControlRegister(14)) = 0xFF;
/* Set clock */
switch (FLEXIO_BASE_CLOCK) {
case 480000000UL:
pFlex1->setClockSettings(3, 0, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=1, CLK_PODF=1)
pFlex2->setClockSettings(3, 0, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=1, CLK_PODF=1)
pFlex3->setClockSettings(3, 0, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=1, CLK_PODF=1)
break;
case 240000000UL:
pFlex1->setClockSettings(3, 1, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=1)
pFlex2->setClockSettings(3, 1, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=1)
pFlex3->setClockSettings(3, 1, 0); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=1)
break;
case 120000000UL:
default:
pFlex1->setClockSettings(3, 1, 1); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=2)
pFlex2->setClockSettings(3, 1, 1); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=2)
pFlex3->setClockSettings(3, 1, 1); // (480 MHz PLL3_SW_CLK clock, CLK_PRED=2, CLK_PODF=2)
break;
}
// Remark: 120 MHz is the maximum FlexIO frequency shown in the reference manual, but testing shows that 240 MHz works.
// 480 MHz has also been shown to work but some bugs may occur if CPU speed is less than 600 MHz...
/* Set up pin mux */
pFlex2->setIOPinToFlexMode(10);
pFlex2->setIOPinToFlexMode(12);
pFlex2->setIOPinToFlexMode(11);
pFlex1->setIOPinToFlexMode(2);
pFlex1->setIOPinToFlexMode(3);
pFlex1->setIOPinToFlexMode(4);
pFlex3->setIOPinToFlexMode(8);
pFlex3->setIOPinToFlexMode(7);
pFlex3->setIOPinToFlexMode(14);
/* Enable the clocks */
hw1->clock_gate_register |= hw1->clock_gate_mask ;
hw2->clock_gate_register |= hw2->clock_gate_mask ;
hw3->clock_gate_register |= hw3->clock_gate_mask ;
/* configure FlexIO registers */
FlexIO_Config(p1, 1, 0xFF);
FlexIO_Config(p2, 0, 0xFF);
FlexIO_Config(p3, 10, 2);
/* configure interrupts on FlexIO3 */
attachInterruptVector(hw3->flex_irq, isr);
NVIC_ENABLE_IRQ(hw3->flex_irq);
NVIC_SET_PRIORITY(hw3->flex_irq, FLEXIO_ISR_PRIORITY);
// disable interrupts until later
p3->SHIFTSIEN &= ~(1 << SHIFTER_IRQ);
p3->TIMIEN &= ~(1 << TIMER_IRQ);
}
static void FlexIO_Config(IMXRT_FLEXIO_t *p, uint8_t shifterPin, uint8_t timerPin) {
// shifterPin and timerPin are FlexIO pin numbers, not Teensy pin numbers
uint8_t beats = SHIFTNUM * BEATS_PER_SHIFTER;
Serial.printf("Multi Beat Quantity: %d \n", beats);
const uint8_t shiftWidth = 8; // 8 bits
const uint8_t timerIndex = TIMER_IRQ;
const uint8_t triggerShifterIndex = SHIFTER_IRQ;
/* Disable and reset FlexIO */
p->CTRL &= ~FLEXIO_CTRL_FLEXEN;
/* Configure the shifters */
for (int i = 0; i <= SHIFTNUM - 1; i++)
{
p->SHIFTCFG[i] =
FLEXIO_SHIFTCFG_INSRC * (1U) /* Shifter input from next shifter's output */
| FLEXIO_SHIFTCFG_SSTOP(0U) /* Shifter stop bit disabled */
| FLEXIO_SHIFTCFG_SSTART(0U) /* Shifter start bit disabled and loading data on enabled */
| FLEXIO_SHIFTCFG_PWIDTH(shiftWidth - 1U); /* Shifter shift width */
}
p->SHIFTCTL[0] =
FLEXIO_SHIFTCTL_TIMSEL(timerIndex) /* Shifter's assigned timer index */
| FLEXIO_SHIFTCTL_TIMPOL * (0U) /* Shift on posedge of shift clock */
| FLEXIO_SHIFTCTL_PINCFG(3U) /* Shifter's pin configured as output */
| FLEXIO_SHIFTCTL_PINSEL(shifterPin) /* Shifter's pin start index */
| FLEXIO_SHIFTCTL_PINPOL * (0U) /* Shifter's pin active high */
| FLEXIO_SHIFTCTL_SMOD(2U); /* shifter mode transmit */
for (int i = 1; i <= SHIFTNUM - 1; i++)
{
p->SHIFTCTL[i] =
FLEXIO_SHIFTCTL_TIMSEL(timerIndex) /* Shifter's assigned timer index */
| FLEXIO_SHIFTCTL_TIMPOL * (0U) /* Shift on posedge of shift clock */
| FLEXIO_SHIFTCTL_PINCFG(0U) /* Shifter's pin configured as output disabled */
| FLEXIO_SHIFTCTL_SMOD(2U); /* shifter mode transmit */
}
/* Configure the timer for shift clock */
p->TIMCMP[timerIndex] =
((beats * 2U - 1) << 8) /* TIMCMP[15:8] = number of beats x 2 – 1 */
| (SHIFT_CLOCK_DIVIDER / 2U - 1U); /* TIMCMP[7:0] = shift clock divide ratio / 2 - 1 */
p->TIMCFG[timerIndex] = FLEXIO_TIMCFG_TIMOUT(0U) /* Timer output logic one when enabled and not affected by reset */
| FLEXIO_TIMCFG_TIMDEC(0U) /* Timer decrement on FlexIO clock, shift clock equals timer output */
| FLEXIO_TIMCFG_TIMRST(0U) /* Timer never reset */
| FLEXIO_TIMCFG_TIMDIS(2U) /* Timer disabled on timer compare */
| FLEXIO_TIMCFG_TIMENA(2U) /* Timer enabled on trigger high */
| FLEXIO_TIMCFG_TSTOP(0U) /* Timer stop bit disabled */
| FLEXIO_TIMCFG_TSTART * (0U); /* Timer start bit disabled */
if (timerPin != 0xFF) { // output clock signal
p->TIMCTL[timerIndex] =
FLEXIO_TIMCTL_TRGSEL((triggerShifterIndex << 2) | 1U) /* Timer trigger selected as highest shifter's status flag */
| FLEXIO_TIMCTL_TRGPOL * (1U) /* Timer trigger polarity as active low */
| FLEXIO_TIMCTL_TRGSRC * (1U) /* Timer trigger source as internal */
| FLEXIO_TIMCTL_PINCFG(3U) /* Timer' pin configured as output */
| FLEXIO_TIMCTL_PINSEL(timerPin) /* Timer' pin index */
| FLEXIO_TIMCTL_PINPOL * (1U) /* Timer' pin active low */
| FLEXIO_TIMCTL_TIMOD(1U); /* Timer mode 8-bit baud counter */
} else { // no clock output
p->TIMCTL[timerIndex] =
FLEXIO_TIMCTL_TRGSEL((triggerShifterIndex << 2) | 1U) /* Timer trigger selected as highest shifter's status flag */
| FLEXIO_TIMCTL_TRGPOL * (1U) /* Timer trigger polarity as active low */
| FLEXIO_TIMCTL_TRGSRC * (1U) /* Timer trigger source as internal */
| FLEXIO_TIMCTL_PINCFG(0U) /* Timer' pin output disabled */
| FLEXIO_TIMCTL_TIMOD(1U); /* Timer mode 8-bit baud counter */
}
/* Enable FlexIO with fast access */
p->CTRL |= FLEXIO_CTRL_FLEXEN | FLEXIO_CTRL_FASTACC;
}