Hi,
I'm having some trouble setting up the PWM on my ATSAMD51 so I can try and use it to send DShot packets. I'd also love some guidance on using DMA to update the duty cycle.
DShot is a protocol for sending commands over a single wire from a multi-copter flight controller to brushless motor speed controller. I'm trying to use my ATSAMD51 to send these commands instead. The protocol is superficially similar to the protocol used for NeoPixels, the difference between a '1' and a '0' is the pulse width in each bit period. For DShot600 a bit period is 1.67 microseconds, a duty cycle of 75% would be a '1' and a duty cycle of 37.5% is a '0'. A single DShot packet contains 16 bits and the line idles low between packets.
I've seen an example of an STM32Fxxx microcontroller sending the packets by using PWM with the PWM duty cycle updated every period to send the 16 duty cycles or bits plus a 17th 0% duty cycle to make the line idle low. Searching though these forums I've found a couple of examples of setting up PWM, but based from these I can't the PWM to work. The ItsyBitsyM4 just shows red on the DotStar LED.
I'm using a custom PCB but it's essential an Adafruit ItsyBitsy M4 in a different form factor. I plan to have 2x Electronic Speed Controllers (ESCs) attached to digital pins 10 & 11 receiving DShot packets. I'm also planning to have quite a lot of processing going on so simply bit-banging the packets would consume too much time/resources.
/* Settings */
const unsigned short dutyHigh = 150;
const unsigned short dutyLow = 75;
/** Pin Map */
#define PIN_ESC_CTRL_L (D10) // PA20/S5.2/S3.2/TC7[0]/TCC1[4]/TCC0[0]
#define PIN_ESC_CTRL_R (D11) // PA21/S5.3/S3.3/TC7[1]/TCC1[5]/TCC0[1]
void setup(void) {
Serial.begin(115200);
Serial.print("Starting PWM testing");
// Set up the generic clock (GCLK7) to clock timer TCC0
GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) | // Divide the 120MHz clock source by divisor 1: 120MHz/1 = 120MHz
GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK7
//GCLK_GENCTRL_SRC_DFLL; // Select 48MHz DFLL clock source
GCLK_GENCTRL_SRC_DPLL0; // Select 120MHz DPLL clock source
//GCLK_GENCTRL_SRC_DPLL1; // Select 100MHz DPLL clock source
while (GCLK->SYNCBUSY.bit.GENCTRL7); // Wait for synchronization
GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN | // Enable the TCC0 perhipheral channel
GCLK_PCHCTRL_GEN_GCLK7; // Connect generic clock 7 to TCC0
// Enable the peripheral multiplexer on PIN_ESC_CTRL_L
PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
// Set the D10 (PORT_PA20) peripheral multiplexer to peripheral (even port number) G(0x6): TCC0, Channel 0
PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);
// Enable the peripheral multiplexer on pin D11
PORT->Group[g_APinDescription[11].ulPort].PINCFG[g_APinDescription[11].ulPin].bit.PMUXEN = 1;
// Set the D11 (PORT_PA21) peripheral multiplexer to peripheral (odd port number) G(0x6): TCC0, Channel 1
PORT->Group[g_APinDescription[11].ulPort].PMUX[g_APinDescription[11].ulPin >> 1].reg |= PORT_PMUX_PMUXO(6);
TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV1 | // Set prescaler to 1, 120MHz/1 = 120MHz
TC_CTRLA_PRESCSYNC_PRESC; // Set the reset/reload to trigger on prescaler clock
TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM; // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
while (TCC0->SYNCBUSY.bit.WAVE) // Wait for synchronization
TCC0->PER.reg = 200; // Set-up the PER (period) register to 600kHz DShot bits
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
TCC0->CC[0].reg = 0; // Set the PWM to idle low
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC0->CC[1].reg = 0; // Set the PWM to idle low
while (TCC0->SYNCBUSY.bit.CC1); // Wait for synchronization
TCC0->CTRLA.bit.ENABLE = 1; // Enable timer TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
Serial.print("Finished setting up PWM");
}
void loop(void) {
TCC1->CCBUF[0].reg = dutyLow; // Set DShot bit to '0'
TCC1->CCBUF[1].reg = dutyHigh; // Set DShot bit to '1'
delay(1000); // Wait for 1 second
TCC1->CCBUF[0].reg = dutyHigh; // Set DShot bit to '1'
TCC1->CCBUF[1].reg = dutyLow; // Set DShot bit to '0'
delay(1000); // Wait for 1 second
}