// Sample code that demonstrates arbitrary pulse chain generation using FlexPWM.
// Each pulse triggers DMA to write new data into the FlexPWM registers which takes effect on the next pulse.
// This allows independent control of the frequency and duty cycle for each pulse without using interrupts.
#include <Arduino.h>
#include <DMAChannel.h>
/* The following pins with FlexPWM capability can be used:
Teensy 4.0: Pins 0-9, 22-25, 28-29, 33-39
Teensy 4.1: Pins 0-9, 22-25, 28-29, 33, 36-37, 42-47, 51, 54
Teensy Micromod: Pins 0-9, 22-25, 28-29, 33-39 */
const int pin = 0; // Teensy pin number
// Pulse data
const int numPulses = 4;
float frequencies[numPulses] = { 1500000, 750000, 375000, 187500 }; // Hz
float dutyCycles[numPulses] = { 0.5, 0.5, 0.5, 0.5 };
const int prescale = 0; // increase timer prescale for very low frequencies (minimum frequency is F_BUS/65536)
const bool invertPolarity = false;
// FlexPWM pin/channel structure (from cores/pwm.c)
struct pwm_pin_info_struct {
uint8_t type;
uint8_t module;
uint8_t channel;
uint8_t muxval;
};
extern "C" const struct pwm_pin_info_struct pwm_pin_info[];
DMAChannel timerUpdateDMA;
volatile DMAMEM int16_t timerVals[numPulses][2]; // allocate buffer in RAM2 to hold data for two timer registers per pulse
void setup() {
// Calculate timer period and ontime (in ticks) for each pulse
for (int i = 0; i < numPulses; i++) {
int16_t periodTicks = (int16_t)((F_BUS_ACTUAL >> prescale) / frequencies[i]);
int16_t onTimeTicks = (int16_t)((F_BUS_ACTUAL >> prescale) * dutyCycles[i] / frequencies[i]);
timerVals[i][0] = periodTicks - 1;
timerVals[i][1] = onTimeTicks - 1;
}
arm_dcache_flush((void *)&timerVals[0][0], sizeof(timerVals)); // need to flush cache so that DMA can read data from timerVals if it is declared DMAMEM
// configure pin with high speed and drive strength configuration
pinMode(pin, OUTPUT);
*(portControlRegister(pin)) = 0xFF;
// Get hardware struct associated with the selected pin
const pwm_pin_info_struct pinInfo = pwm_pin_info[pin];
// Get the register structure associated with this PWM module
IMXRT_FLEXPWM_t *flexpwm;
uint8_t submodule = pinInfo.module & 0x03;
uint16_t bitmask = 1 << submodule;
uint8_t pwmDmaTrigger;
switch ((pinInfo.module >> 4) & 3) {
case 0:
flexpwm = &IMXRT_FLEXPWM1;
pwmDmaTrigger = DMAMUX_SOURCE_FLEXPWM1_WRITE0 + submodule;
break;
case 1:
flexpwm = &IMXRT_FLEXPWM2;
pwmDmaTrigger = DMAMUX_SOURCE_FLEXPWM2_WRITE0 + submodule;
break;
case 2:
flexpwm = &IMXRT_FLEXPWM3;
pwmDmaTrigger = DMAMUX_SOURCE_FLEXPWM3_WRITE0 + submodule;
break;
default:
flexpwm = &IMXRT_FLEXPWM4;
pwmDmaTrigger = DMAMUX_SOURCE_FLEXPWM4_WRITE0 + submodule;
}
// stop timer until after setup is complete
flexpwm->MCTRL &= ~FLEXPWM_MCTRL_RUN(bitmask);
// set the pin mux
*(portConfigRegister(pin)) = pinInfo.muxval;
// get timer registers that control this pin
volatile uint16_t *timerRegisterPeriod, *timerRegisterOnTime;
uint16_t polarityBit, enableBit;
switch (pinInfo.channel) {
case 0: // Channel X
timerRegisterPeriod = &(flexpwm->SM[submodule].VAL1);
timerRegisterOnTime = &(flexpwm->SM[submodule].VAL0);
polarityBit = invertPolarity ? 0 : FLEXPWM_SMOCTRL_POLX;
enableBit = FLEXPWM_OUTEN_PWMX_EN(bitmask);
break;
case 1: // Channel A
timerRegisterPeriod = &(flexpwm->SM[submodule].VAL1);
timerRegisterOnTime = &(flexpwm->SM[submodule].VAL3);
polarityBit = invertPolarity ? FLEXPWM_SMOCTRL_POLA : 0;
enableBit = FLEXPWM_OUTEN_PWMA_EN(bitmask);
break;
default: // Channel B
timerRegisterPeriod = &(flexpwm->SM[submodule].VAL1);
timerRegisterOnTime = &(flexpwm->SM[submodule].VAL5);
polarityBit = invertPolarity ? FLEXPWM_SMOCTRL_POLB : 0;
enableBit = FLEXPWM_OUTEN_PWMB_EN(bitmask);
}
// set up PWM with initial settings
flexpwm->MCTRL |= FLEXPWM_MCTRL_CLDOK(bitmask);
flexpwm->SM[submodule].INIT = 0;
flexpwm->SM[submodule].VAL2 = 0;
flexpwm->SM[submodule].VAL4 = 0;
flexpwm->SM[submodule].CTRL = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_PRSC(prescale);
flexpwm->SM[submodule].OCTRL = polarityBit;
flexpwm->OUTEN |= enableBit;
flexpwm->MCTRL |= FLEXPWM_MCTRL_LDOK(bitmask);
// Generate a DMA trigger at the beginning of each cycle (when the signal goes high)
// This is used to trigger DMA transfer to reload a new period and duty cycle into the registers, which take effect on the next cycle.
flexpwm->SM[submodule].DMAEN |= FLEXPWM_SMDMAEN_VALDE; // enable DMA trigger at cycle start
// set up DMA transfer
timerUpdateDMA.begin(false);
unsigned int minorLoopBytes, minorLoopIterations, majorLoopBytes, majorLoopIterations;
int destinationAddressOffset, destinationAddressLastOffset, sourceAddressOffset, sourceAddressLastOffset, minorLoopOffset;
volatile void *destinationAddress1, *destinationAddress2, *sourceAddress;
// configure DMA transfer
// timerUpdateDMA updates two FlexPWM registers from the timerVals array.
// The source address reads from timerVals[x][0], then timerVals[x][1], then moves to the next index.
// The destination address writes to timerRegisterPeriod, then timerRegisterOnTime.
// A minor loop offset is used to reset the destination address after each transaction.
// When all iterations are complete (all pulses have been output), the source address is reset to the beginning and the process repeats.
minorLoopIterations = 2; // number of registers to update
minorLoopBytes = minorLoopIterations * sizeof(uint16_t);
majorLoopIterations = numPulses;
majorLoopBytes = majorLoopIterations * minorLoopBytes;
sourceAddress = &timerVals[0][0];
sourceAddressOffset = sizeof(uint16_t); // address offset from timerVals[x][0] to timerVals[x][1]
sourceAddressLastOffset = -majorLoopBytes;
destinationAddress1 = timerRegisterPeriod;
destinationAddress2 = timerRegisterOnTime;
destinationAddressOffset = (int)destinationAddress2 - (int)destinationAddress1;
minorLoopOffset = -minorLoopIterations * destinationAddressOffset;
destinationAddressLastOffset = minorLoopOffset;
DMA_CR |= DMA_CR_EMLM; // Enable minor loop mapping so that we can have a minor loop offset on the destination address
timerUpdateDMA.disable();
timerUpdateDMA.TCD->SADDR = sourceAddress;
timerUpdateDMA.TCD->SOFF = sourceAddressOffset;
timerUpdateDMA.TCD->SLAST = sourceAddressLastOffset;
timerUpdateDMA.TCD->ATTR_SRC = DMA_TCD_ATTR_SIZE_16BIT;
timerUpdateDMA.TCD->DADDR = destinationAddress1;
timerUpdateDMA.TCD->DOFF = destinationAddressOffset;
timerUpdateDMA.TCD->DLASTSGA = destinationAddressLastOffset;
timerUpdateDMA.TCD->ATTR_DST = DMA_TCD_ATTR_SIZE_16BIT;
timerUpdateDMA.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_DMLOE | DMA_TCD_NBYTES_MLOFFYES_MLOFF(minorLoopOffset) | DMA_TCD_NBYTES_MLOFFYES_NBYTES(minorLoopBytes);
timerUpdateDMA.TCD->BITER = majorLoopIterations;
timerUpdateDMA.TCD->CITER = majorLoopIterations;
timerUpdateDMA.triggerAtHardwareEvent(pwmDmaTrigger);
timerUpdateDMA.enable();
// After everything is set up: enable FlexPWM timer to start process
flexpwm->MCTRL |= FLEXPWM_MCTRL_RUN(1 << submodule);
}
void loop() {
}