Hello all,
I got a Teensy 3.5 a while ago, and was using it to control a couple of servos. The servo library is great, but I think I'm having a problem with interrupts messing with the timing of the PWM signal (I have I2C data incoming and maybe other bits and pieces in the future). I had the idea of controlling the servo completely using DMA, but haven't been able to get anything running successfully.
The basic idea is to have a digital pin set high every 20ms (this is a fixed interval), and then pulled back low after 1-2ms depending on the value set by the user. I played around with two PIT timers and the PTOR register to toggle the digital pin, using 4 DMA channels (not ideal, but I am past doing this because it's a good idea).
Does this look like a logical way of doing things? It seems that there must be a better way of doing it, maybe using the FTM or PDB timers instead?
This doesn't work because PIT1 isn't being re-enabled for some reason? Code is below, at the moment I'm not using a servo, just making the LED on pin 13 flash, so the timers are set to 3s and 1s rather than 20ms and 1-2ms.
I got a Teensy 3.5 a while ago, and was using it to control a couple of servos. The servo library is great, but I think I'm having a problem with interrupts messing with the timing of the PWM signal (I have I2C data incoming and maybe other bits and pieces in the future). I had the idea of controlling the servo completely using DMA, but haven't been able to get anything running successfully.
The basic idea is to have a digital pin set high every 20ms (this is a fixed interval), and then pulled back low after 1-2ms depending on the value set by the user. I played around with two PIT timers and the PTOR register to toggle the digital pin, using 4 DMA channels (not ideal, but I am past doing this because it's a good idea).
- DMA2: triggered on rising edge: disable PIT1 timer
- DMA3: triggered by DMA2: enable PIT1 (restart it)
- DMA1: triggered by DMA3: set pin low (this will only happen when PIT1 hits zero after 1-2ms)
- DMA0: triggered by DMA1: set pin high (this will only happen when PIT0 hits zero after 20ms)
Does this look like a logical way of doing things? It seems that there must be a better way of doing it, maybe using the FTM or PDB timers instead?
This doesn't work because PIT1 isn't being re-enabled for some reason? Code is below, at the moment I'm not using a servo, just making the LED on pin 13 flash, so the timers are set to 3s and 1s rather than 20ms and 1-2ms.
Code:
#include "DMAChannel.h"
DMAChannel dma0;
DMAChannel dma1;
DMAChannel dma2;
DMAChannel dma3;
uint32_t PIN13_MASK = (1 << 5); // CORE_PIN13_BITMASK;
volatile uint32_t& PIN13_ADDR = GPIOC_PTOR;
volatile uint32_t& PIN13_CONFIG = PORTC_PCR5; //CORE_PIN13_CONFIG;
uint32_t PIN13_DMAMUX_SOURCE = 51; //DMAMUX_SOURCE_PORTC;
volatile uint32_t& PIT_VAL = PIT_LDVAL1;
uint32_t FRAME_PERIOD = 3;
uint32_t FRAME_TICKS = F_BUS * FRAME_PERIOD - 1;
uint32_t ON_PERIOD = 1;
uint32_t ON_TICKS = F_BUS * ON_PERIOD - 1;
volatile uint32_t PIT_TCTRL_EN = 1; //enable PIT timer
volatile uint32_t PIT_TCTRL_DIS = 0; //disable PIT timer
void setup() {
Serial.begin(115200);
pinMode(13, OUTPUT);
initPIT();
initDMA();
// toggle pin high to kick things off
PIN13_ADDR = PIN13_MASK;
Serial.println("setup complete.");
}
void loop() {
if (dma0.error ()) {
Serial.println ("Error setting up DMA0");
}
else if (dma1.error ()) {
Serial.println ("Error setting up DMA1");
}
else if (dma2.error ()) {
Serial.println ("Error setting up DMA2");
}
else if (dma3.error ()) {
Serial.println ("Error setting up DMA3");
}
//print remaining time on PIT timers:
float num = (float)PIT_CVAL0;
float den = (float)F_BUS;
Serial.print(num / den);
num = (float)PIT_CVAL1;
Serial.print(" | ");
Serial.println(num / den);
delay(200);
}
void initDMA(void) {
/*
* DMA2: triggered on rising edge: disable PIT1
* DMA3: tiggered by DMA2: enable PIT1
* DMA1: triggered by DMA3: set pin low (when PIT1 hits zero)
* DMA0: triggered by DMA1: set pin high (when PIT0 hits zero)
*/
DMAMUX0_CHCFG0 |= DMAMUX_TRIG; //enable pediodic interrupt from PIT on DMA (bit 6: 64)
dma0.source(PIN13_MASK);
dma0.destination(PIN13_ADDR);
dma0.triggerAtCompletionOf(dma1);
dma0.transferCount(1); //error if not set?
dma0.enable();
DMAMUX0_CHCFG1 |= DMAMUX_TRIG; //enable pediodic interrupt from PIT on DMA (bit 6: 64)
dma1.source(PIN13_MASK);
dma1.destination(PIN13_ADDR);
dma1.triggerAtCompletionOf(dma3);
dma1.transferCount(1);
dma1.enable();
PIN13_CONFIG |= PORT_PCR_IRQC(1); // 1: rising edge, 2: falling edge 3: either edge
dma2.source(PIT_TCTRL_DIS); //source must be global!
dma2.destination(PIT_TCTRL1);
dma2.triggerAtHardwareEvent(PIN13_DMAMUX_SOURCE);
dma2.transferCount(1);
dma2.enable();
dma3.source(PIT_TCTRL_EN); //source must be global!
dma3.destination(PIT_TCTRL1);
dma3.triggerAtCompletionOf(dma2);
dma3.transferCount(1);
dma3.enable();
}
void initPIT(void) {
// use PIT 0 and 1: only work with DMA channels 0 and 1 respectively.
SIM_SCGC6 |= SIM_SCGC6_PIT; // turn on PIT clocks
__asm__ volatile("nop"); // solves timing problem on Teensy 3.5
PIT_MCR = 1; // enable the module
PIT_TFLG0 = PIT_TFLG_TIF; // clear flag
PIT_TCTRL0 |= PIT_TCTRL_TEN; // enable timer 0
PIT_LDVAL0 = FRAME_TICKS; //set to PWM frame length (generally 20ms)
PIT_TFLG1 = PIT_TFLG_TIF; //clear flag
PIT_TCTRL1 |= PIT_TCTRL_TEN; // enable timer 1
PIT_LDVAL1 = ON_TICKS; //set to PWM width of 1-2ms
}