Generate ESC DSHOT pulses using PDB PulseOut to an output pin.

cfrank

Member
Hello all. I am new to the forum, and all PDB related questions seem to end at the ADC. I am attempting to drive DSHOT ESC on teensy3.2 pin 12 https://blck.mn/2016/11/dshot-the-new-kid-on-the-block/
Using PDB Pulse Output as described on page 3 of this page: http://www.nxp.com/assets/documents/data/en/application-notes/AN4424.pdf
I am able to get the pdb_isr() triggered, but:
1. It seems to fire on every clock pulse. I want an interrupt on the rising edge of PulseOut
2. Pin 11 only gets a short (500nS) pulse, not the expected PulseOut duration PDB_DLY_DSHOT_1
3. I can't seem to find a way to get the configured PulseOut to pin 12.
 
Last edited:
Code:
int count;
bool bitHigh = false;

#define PDB_DLY_DSHOT_0   (0x0011<<16 | 0x0111)   // Configure Pulse-Out DLY1 and DLY2  (PDBx_POyDLY) (K66 manual p 1120)
#define PDB_DLY_DSHOT_1   (0x00110000 | 0x1111)   // Configure Pulse-Out DLY1 and DLY2  (PDBx_POyDLY) (K66 manual p 1120)
#define PDB_CONFIG (PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_PDBIE \
  | PDB0_SC | PDB_SC_PRESCALER(0) | PDB_SC_MULT(0))

void setup() {
  pinMode(13, OUTPUT);
  SIM_SCGC6 |= SIM_SCGC6_PDB;            // Enable the PDB clock
  PDB0_POEN    = 0b00001111;             // ?HELP? Configure 4 Pulse-Out n pins: Enable register   (PDBx_POEN) (K66 manual p 1119) 
  PDB0_PO0DLY  = PDB_DLY_DSHOT_0;
  CORE_PIN11_CONFIG = PORT_PCR_MUX(3);   // Does not seem to like pin 9.  Only seems to show a short. trigger pulse.
  CORE_PIN12_CONFIG = PORT_PCR_IRQC(1);  // ?HELP?  How do I get Pulse Out to pin 12 ?   // PORT_PCR_IRQC(1) | PORT_PCR_MUX(3); ???

  PDB0_MOD = 45;              // Very fast clock.
  PDB0_IDLY= 0;               // Interrupt delay
  PDB0_SC  = PDB_CONFIG;      // PDB status and control
  PDB0_SC |= PDB_SC_SWTRIG;   // Software trigger (reset and restart counter)
  PDB0_SC |= PDB_SC_LDOK;     // Load OK
  NVIC_ENABLE_IRQ(IRQ_PDB);   // Enable interrupt request
}

void pdb_isr() {
  PDB0_SC = PDB_CONFIG | PDB_SC_LDOK; // (also clears interrupt flag)
  count++;
  // ?HELP?  Stop pulses when 16 pulsed (bits) are sent.

  if ( true ) {  // ?HELP?  Do this on the rising edge of the PDB pusle out (pin 12)
    PDB0_PO0DLY = bitHigh ? PDB_DLY_DSHOT_1 : PDB_DLY_DSHOT_0;    // Alternate pulse length on pin 12: 0, 1, 0, 1, ....
    bitHigh = !bitHigh;
  }
}

void loop() {
}
 
Which Teensy? You mention Teensy 3.2, which is K20, your code says K66 (Teensy 3.6).

The K20 / K66 PDB doesn't have output pins. From what I can tell, the only thing you can do with the pulse output is use it as sample window for the comparators. I don't see a way to abuse the comparators as PDB output to set a pin.

Is there a reason you don't want to use an TPM / FTM timer? You could also use a timer (FTM, TPM, PDB) to trigger DMA transfers to the port toggle register (e.g. a sequence of 1 0 1 for a long pulse or 1 1 0 for a short pulse).
 
Thanks for the answer. Using teensy 3.2. Didn't know there was more than one Kxx chip. I plead the newbee :) I was able to get a varying FTM duty cycle on the oscilloscope, but the PDB PulseOut description seemed more applicable. Never used DMA, but the term "port toggle register" sounds exactly like what I want to do! I'll do some more Google on that, but is there a code example of how I would start DMA/port toggle on the K20?
 
Never used DMA, but the term "port toggle register" sounds exactly like what I want to do!
Using the FTM timer is more accurate. There is potential jitter using DMA to toggle the pin via the port registers.

This code writes a 16-bit frame and 2 zero-bits for frame separation continuously:

Code:
#include <DMAChannel.h>
#include <array>

DMAChannel dma;

const uint16_t short_pulse = uint64_t(F_BUS) * 625 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) * 1250 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) * 1670 / 1000000000;

std::array<volatile uint16_t, 18> dma_source = {
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    0, 0
};

void setupDMA() {
    // configure pin 22 for FTM PWM output (FTM0 channel 0)
    CORE_PIN22_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;

    FTM0_SC = 0; // disable FTM0
    
    dma.destination((uint16_t&) FTM0_C0V);
    dma.sourceBuffer(dma_source.data(), sizeof(dma_source));
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM0_CH7);
    dma.enable();
    
    FTM0_CNT = 0;
    FTM0_MOD = bit_length;
    FTM0_CNTIN = 0;
    // edge aligned PWM output for channel 0
    FTM0_C0SC = FTM_CSC_MSB | FTM_CSC_ELSB;
    FTM0_C0V = 0;
    // channel 7 triggers DMA
    FTM0_C7SC = FTM_CSC_CHIE | FTM_CSC_DMA | FTM_CSC_MSA | FTM_CSC_ELSA; // output compare, enable DMA trigger
    FTM0_C7V = 0;
    FTM0_SC = FTM_SC_CLKS(1); // enable timer with busclock
}

void setup() {
    setupDMA();
}

void loop() {}

The FTM timer is used in TPM-mode. That means the compare value update to FTM0_C0V is buffered and the update happens when the counter overflows (end of PWM period). FTM0 channel 0 (pin 22) is used for PWM output, channel 7 is used for triggering DMA (unfortunately, a timer channel has to be sacrificed).
 

Attachments

  • dshot_pwm.PNG
    dshot_pwm.PNG
    3 KB · Views: 213
Last edited:
Nice! Using dma.disableOnCompletion() I can fire off single bursts of 16 width-modulated bits. This is sweet!
Thank you.
 
I modified the code above to generate one DShot frame every 10 ms, for checking with a scope:

Code:
#include <DMAChannel.h>
#include <array>

DMAChannel dma;

const uint16_t short_pulse = uint64_t(F_BUS) * 625 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) * 1250 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) * 1670 / 1000000000;

std::array<volatile uint16_t, 18> dma_source = {
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    0, 0
};

void setupDMA() {
    // configure pin 22 for FTM PWM output (FTM0 channel 0)
    CORE_PIN22_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;

    FTM0_SC = 0; // disable FTM0
    
    dma.destination((uint16_t&) FTM0_C0V);
    dma.sourceBuffer(dma_source.data(), sizeof(dma_source));
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM0_CH7);
//    dma.enable();
    dma.disableOnCompletion();
    
    FTM0_CNT = 0;
    FTM0_MOD = bit_length;
    FTM0_CNTIN = 0;
    // edge aligned PWM output for channel 0
    FTM0_C0SC = FTM_CSC_MSB | FTM_CSC_ELSB;
    FTM0_C0V = 0;
    // channel 7 triggers DMA
    FTM0_C7SC = FTM_CSC_CHIE | FTM_CSC_DMA | FTM_CSC_MSA | FTM_CSC_ELSA; // output compare, enable DMA trigger
    FTM0_C7V = 0;
    FTM0_SC = FTM_SC_CLKS(1); // enable timer with busclock
}

void setup() {
    setupDMA();
}

void loop()
{
  dma.enable();
  delay(10);
}

ds1054z-scope-display_2020-01-04_00-16-46.png

I'll now add code around this to actually control an ESC with a pot connected to the T3.2.
 
Good job @Christoph,

I remember adding more code to this a whole back. In fact I was able to run 4 BetaFlight ESC at DSHOT 3600 (pulse freq > 1MHz :) with ESC telemetry feedback. That's right 3 times the speed of DSHOT-1200 (Thanks to @tni for schooling me on the FTM timer). I was hoping to make a library out of it, but got distracted with machine learning somewhere along the way. I will find and upload the nearly finished code.
 
The following sketch uses the DShot.h class below to Enable, Run, and Retrieve telemetry from 4 BL-Helli DSHOT ESCs. Tested with Betaflight ESC and Teensy 3.2 at up to DSHOT-3600. Please excuse the unfinished nature the code. Enjoy.

======= FTM_DMA_DSHOT sketch uses Classes: DshotMotor, ESCTelem from DShot.h below=======
Code:
#include <DMAChannel.h>
#include <array>
#include <Encoder.h>   // Stripped out encoder stuff
#include "DShot.h"

float count = 4;
int curSpeed = 0;
uint8_t motorList[] = { 20, 21, 22, 23 };   // DShot signal pins for the motors.

DshotMotor M(count, motorList);
ESCTelem   MT;           // Serial1 == pin 0.  TODO Pass to constructor

void setup() {
    pinMode(19, INPUT_PULLUP);
    Serial.begin(19200);
    Serial.println("Setup Done");
    if (!M.armed) {
      M.armMotorESC();
    }
    TelemSerial.begin(115200, SERIAL_8N1);   // https://www.pjrc.com/teensy/td_uart.html
}

void loop() {
    // Vary motor speeds up and down. Using sine wave
    int motorSpeed = 1000 - 400 * cos(PI * count++/9800);
    curSpeed = (curSpeed + motorSpeed) / 2;   // MovingAverage(2)

    M.setRunSpeed(curSpeed);               // All motors!

    if ((int) count % 1050 == 0) {
      M.requestTelemetry = true;
      Serial.println("Tmp Volt, Amp, mAh, kRpm  ");
    }
    
  delayMicroseconds(225);            // delay(50);
  MT.xpoll();
}

I was planning to turn this into a library. For now just add DShot.h along side your sketch file and include it as in the above sketch.
======= DShot.h =======
Code:
// Author: cfrank
// TODO:
// DSHOT reserved functions...
// Program BL-Helli through DSHOT

#define DSHOT_3600 1
#ifdef DSHOT_4800
const uint16_t short_pulse= uint64_t(F_BUS) *   78 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) *  156 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) *  208 / 1000000000;    // 208
#elif DSHOT_3600  // Good on teensy 3.2 + Betaflight ESC
const uint16_t short_pulse= uint64_t(F_BUS) *  104 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) *  208 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) *  278 / 1000000000;    // 278
#elif DSHOT_2400  // Solid on teensy 3.2 + Betaflight ESC
const uint16_t short_pulse= uint64_t(F_BUS) *  156 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) *  312 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) *  416 / 1000000000;    // 416
#elif DSHOT_1200  // Solid on teensy 3.2 + Betaflight ESC
const uint16_t short_pulse= uint64_t(F_BUS) *  312 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) *  625 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) *  833 / 1000000000;    // 833
#elif DSHOT_600  // Solid on teensy 3.2 + Betaflight ESC
const uint16_t short_pulse= uint64_t(F_BUS) *  625 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) * 1250 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) * 1666 / 1000000000;    // 1670
#elif DSHOT_300  // Solid on teensy 3.2 + Betaflight ESC
const uint16_t short_pulse= uint64_t(F_BUS) * 1250 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) * 2500 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) * 3340 / 1000000000;
#else // DSHOT_150
const uint16_t short_pulse= uint64_t(F_BUS) * 2500 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) * 4800 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) * 6690 / 1000000000;
#endif

class DshotMotor
{
  protected:
    DMAChannel dma;
    std::array<volatile uint16_t, 18> dma_source;
    uint8_t pin[8];   // Up to 8 motor pins;
    uint8_t cnt = 0;
    volatile uint32_t dmaDoneAt, xferTimeMicros;

    void dmaSetup() {
        dma.sourceBuffer(dma_source.data(), sizeof(dma_source));
        dma.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM0_CH7);
        dma.disableOnCompletion();
    }
    
    // Highly Teensy 3.2 specific stuff!

    void dshotSend(uint8_t pin) {
        switch (pin) {
          case 22: dma.destination((uint16_t&) FTM0_C0V); CORE_PIN22_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; break;
          case 23: dma.destination((uint16_t&) FTM0_C1V); CORE_PIN23_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; break;
          case  9: dma.destination((uint16_t&) FTM0_C2V); CORE_PIN9_CONFIG  = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; break;
          case 10: dma.destination((uint16_t&) FTM0_C3V); CORE_PIN10_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; break;
          case  6: dma.destination((uint16_t&) FTM0_C4V); CORE_PIN6_CONFIG  = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; break;
          case 20: dma.destination((uint16_t&) FTM0_C5V); CORE_PIN20_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; break;
    #if defined(KINETISK)
          case 21: dma.destination((uint16_t&) FTM0_C6V); CORE_PIN21_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; break;
          case  5: dma.destination((uint16_t&) FTM0_C7V); CORE_PIN5_CONFIG  = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; break;
    #endif
          default:
            return;
        };
    
        FTM0_SC = 0; // disable FTM0
        FTM0_CNT = 0;
        FTM0_CNTIN = 0;
        FTM0_MOD = bit_length;
    // edge aligned PWM output for channel 0
        FTM0_C0SC = FTM_CSC_MSB | FTM_CSC_ELSB;
        FTM0_C0V = 0;
    // channel 7 triggers DMA
        FTM0_C7SC = FTM_CSC_CHIE | FTM_CSC_DMA | FTM_CSC_MSA | FTM_CSC_ELSA; // output compare, enable DMA trigger
        FTM0_C7V = 0;
        FTM0_SC = FTM_SC_CLKS(1); // enable timer with busclock
        dma.enable();
        dmaDoneAt = micros() + xferTimeMicros;
    }

    uint16_t prepareDshotPacket(const uint16_t value, uint16_t telemBit = 0)
    {
        uint16_t packet = (value << 1) | telemBit | requestTelemetry;   // | (motor->requestTelemetry ? 1 : 0);
        requestTelemetry = false;
        int csum = 0, csum_data = packet;
        for (int i = 0; i < 3; i++) {
            csum ^=  csum_data;   // xor data by nibbles
            csum_data >>= 4;
        }
        return (packet << 4) | (csum & 0xf);    // append checksum
    }

    void dshotSend(uint8_t pin, uint16_t dsWord) {
      volatile int32_t toWait = dmaDoneAt - micros();
      if (toWait > 0) {
        delayMicroseconds((uint32_t)toWait < xferTimeMicros ? toWait : xferTimeMicros);  // If clock rolls over we can get a weird number.
      }
      for (int i=0; i<16; i++) {
        dma_source[i] = (dsWord<<i & 0x8000) ? long_pulse : short_pulse;  // MSB first
      }
      dshotSend(pin);
    }

    uint16_t sendDshotCmd(uint8_t pin, int cmd) {
      uint16_t dsWord = prepareDshotPacket(cmd);
      dshotSend(pin, dsWord);
      return dsWord;
    }

  public:
    bool armed = false, requestTelemetry=false;
    DshotMotor(int motorCount, uint8_t M[]) {
      for(int i=0; i<motorCount; i++) {
        switch(M[i]) {
          case 5: case 6: case 9: case 10: case 20: case 21: case 22: case 23: pin[i] = M[i]; break;   // From switch;
          default: Serial.print("Invalid pin for DShot: "); Serial.println(M[i]); return;    // Throw something
        }
      }
      cnt = motorCount;
      int bit_period = bit_length * uint64_t(1000000000) / uint64_t(F_BUS);
      xferTimeMicros = (16 + 2) * bit_period / 1000;
      dmaSetup();
    }
   
    void armMotorESC() {
      for (int i=0; i<=2000; i+=5) { setRunSpeed(i); delay(2);  }   // Throttle up to max
      for (int i=2000; i>=0; i-=5) { setRunSpeed(i); delay(2);  }   // Throttle zown to zero
      armed = true;
    }
    
    void fwdSpin(int n)                  { sendDshotCmd(pin[n], 20); }
    void revSpin(int n)                  { sendDshotCmd(pin[n], 21); }
    void beep   (int n, int freq)        { sendDshotCmd(pin[n], 1 + freq); delay(250); } // Wait length of beep plus 100ms before next command
    void ledOn  (int n, int led)         { sendDshotCmd(pin[n], 22 + led);  delay(100); }
    void ledOff (int n, int led)         { sendDshotCmd(pin[n], 26 + led); }
    void setRunSpeed(int n, int escSpeed) { sendDshotCmd(pin[n], escSpeed + 48); }
    void setRunSpeed(int escSpeed) { for(int i=0; i<4; i++) setRunSpeed(i, escSpeed); }
};


// ==========================================================

#define TelemSerial Serial1   
class ESCTelem {
  uint8_t buf[10];    // https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=4&cad=rja&uact=8&ved=0ahUKEwjE84Xn9InYAhUs9YMKHYyhDVUQFghBMAM&url=https%3A%2F%2Fwww.rcgroups.com%2Fforums%2Fshowatt.php%3Fattachmentid%3D8524039%26d%3D1450424877&usg=AOvVaw1FWow1ljvZue1ImISgzlca
  uint8_t  *temp  = (uint8_t*) (buf + 0);
  uint16_t *mVolt = (uint16_t*) (buf + 1);
  uint16_t *mAmp  = (uint16_t*) (buf + 3);
  uint16_t *mUsage= (uint16_t*) (buf + 5);
  uint16_t *rpm   = (uint16_t*) (buf + 7);
  uint8_t  *crc   = (uint8_t*) (buf + 9);
  uint8_t  *readPtr = buf;

  float getVolt()  { uint16_t raw = buf[1] << 8 | buf[2]; return (float) raw / 100.0; }
  float getAmps()  { uint16_t raw = buf[3] << 8 | buf[4]; return (float) raw / 100.0; }
  float getUse()   { uint16_t raw = buf[5] << 8 | buf[6]; return (float) raw / 100.0; }
  float getKRpm () { uint16_t raw = buf[7] << 8 | buf[8]; return (float) raw / 10.0 / 11; }   // 22 motor pols.  Divide by 11
  void readByte() {
    *readPtr++ = TelemSerial.read();
    if (readPtr == buf + 10*sizeof(uint8_t)) {
      Serial.print(*temp, DEC);
      Serial.print(", ");     Serial.print(getVolt());
      Serial.print(", ");     Serial.print(getAmps());
      Serial.print(", ");     Serial.print(getUse());
      Serial.print(", ");     Serial.println(getKRpm());
      readPtr = buf;
    }
  }
  
public:
  void xpoll() { while(TelemSerial.available()) readByte(); }
};
 
This sketch outputs DShot600 at 50 Hz update rate on Pin 22. Throttle is set with a 10k pot + 1µF cap to ground on pin 14 (A0). The lower and upper 5% of the ADC range are a deadband (zero or max throttle), and the sketch waits for the potentiometer to be turned to 0V before it actually uses the potentiometer value to set the DShot throttle value. Throttle value is sent over Serial:

Code:
#include <DMAChannel.h>
#include <array>
#include <elapsedMillis.h>

DMAChannel dma;

const uint16_t short_pulse = uint64_t(F_BUS) * 625 / 1000000000;
const uint16_t long_pulse = uint64_t(F_BUS) * 1250 / 1000000000;
const uint16_t bit_length = uint64_t(F_BUS) * 1670 / 1000000000;

std::array<volatile uint16_t, 18> dma_source = {
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    short_pulse, long_pulse,
    0, 0
};

void setupDMA() {
    // configure pin 22 for FTM PWM output (FTM0 channel 0)
    CORE_PIN22_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;

    FTM0_SC = 0; // disable FTM0
    
    dma.destination((uint16_t&) FTM0_C0V);
    dma.sourceBuffer(dma_source.data(), sizeof(dma_source));
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM0_CH7);
//    dma.enable();
    dma.disableOnCompletion();
    
    FTM0_CNT = 0;
    FTM0_MOD = bit_length;
    FTM0_CNTIN = 0;
    // edge aligned PWM output for channel 0
    FTM0_C0SC = FTM_CSC_MSB | FTM_CSC_ELSB;
    FTM0_C0V = 0;
    // channel 7 triggers DMA
    FTM0_C7SC = FTM_CSC_CHIE | FTM_CSC_DMA | FTM_CSC_MSA | FTM_CSC_ELSA; // output compare, enable DMA trigger
    FTM0_C7V = 0;
    FTM0_SC = FTM_SC_CLKS(1); // enable timer with busclock
}

void setup() {
  setupDMA();
  analogReadResolution(10);
//  while(!Serial);
}

// prepareDhostPacket copied from https://www.rcgroups.com/forums/showthread.php?3347115-Dshot-ESC-Protocol-on-STM32F4-using-Arduino
uint16_t prepareDshotPacket(uint16_t value, bool requestTelemetry) {

  uint16_t packet = (value << 1) | (requestTelemetry ? 1 : 0);
  requestTelemetry = false;    // reset telemetry request to make sure it's triggered only once in a row

  // compute checksum
  int csum = 0;
  int csum_data = packet;
  for (int i = 0; i < 3; i++) {
    csum ^=  csum_data;   // xor data by nibbles
    csum_data >>= 4;
  }
  csum &= 0xf;
  // append checksum
  packet = (packet << 4) | csum;

  return packet;
}

void sendDshotThrottle(uint16_t throttle)
{
  Serial.println(throttle);
  Serial.flush();

  uint16_t dshot_value = min(1999,throttle)+48;
  uint16_t dshot_packet = prepareDshotPacket(dshot_value, false);
  for(uint8_t i = 0; i < 16; i++)
  {
    if((dshot_packet & 0x8000) > 0)
    {
      dma_source[i] = long_pulse;
    }
    else
    {
      dma_source[i] = short_pulse;
    }
    dshot_packet = dshot_packet << 1;
  }
  dma.enable();
}

uint16_t updatePeriod = 20;
elapsedMillis updateTimer = updatePeriod;
const uint8_t pot_deadBand = 50;

void loop()
{
  static bool waitForLowThrottle = true;
  if(updateTimer >= updatePeriod)
  {
    updateTimer = 0;
    uint16_t potVal = analogRead(A0);
    if(waitForLowThrottle && (potVal < pot_deadBand))
    {
      waitForLowThrottle = false;
    }
    if(waitForLowThrottle)
    {
      sendDshotThrottle(0);
    }
    else
    {
      if(potVal < pot_deadBand)
      {
        sendDshotThrottle(0);
      }
      else if(potVal >= (1023-pot_deadBand))
      {
        sendDshotThrottle(1999);
      }
      else
      {
        uint16_t throttle = (uint32_t(potVal-pot_deadBand)*1999)/(1023-2*pot_deadBand);
        sendDshotThrottle(throttle);
      }
    }
  }
  while(Serial.available()) Serial.read();
}
 
Hi guys,

Having successfully used @cfrank's code to spin motors with DSHOT600 ESCs using a Teensy3.2, I'm looking to upgrade to a Teensy4.0, with the ultimate goal of using the 4.0 as the next platform for my Arduino-compatible multirotor firmware. I managed to find an actively-maintained Teensy3.5/3.6/4.0 DSHOT repository, but have been unable to spin any motors with it. Being a complete newbie with DMA, I'm hoping that someone on this forum can give me some tips for migrating @cfrank's code (specifically, the stuff commented with "// Highly Teensy 3.2 specific stuff!") to the Teensy4.0 DMA protocol.

Thanks,
Simon
 
Back
Top