Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 12 of 12

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

  1. #1
    Junior Member
    Join Date
    Jun 2017
    Posts
    6

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

    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/...tes/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 by cfrank; 06-27-2017 at 12:41 AM. Reason: Typo

  2. #2
    Junior Member
    Join Date
    Jun 2017
    Posts
    6
    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() {
    }

  3. #3
    Senior Member
    Join Date
    Jan 2013
    Posts
    843
    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).

  4. #4
    Junior Member
    Join Date
    Jun 2017
    Posts
    6
    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?

  5. #5
    Senior Member
    Join Date
    Jan 2013
    Posts
    843
    Quote Originally Posted by cfrank View Post
    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).
    Attached Thumbnails Attached Thumbnails Click image for larger version. 

Name:	dshot_pwm.PNG 
Views:	130 
Size:	3.0 KB 
ID:	10892  
    Last edited by tni; 06-27-2017 at 07:53 PM.

  6. #6
    Junior Member
    Join Date
    Jun 2017
    Posts
    6
    Nice! Using dma.disableOnCompletion() I can fire off single bursts of 16 width-modulated bits. This is sweet!
    Thank you.

  7. #7
    Senior Member
    Join Date
    Sep 2013
    Location
    Hamburg, Germany
    Posts
    894
    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);
    }
    Click image for larger version. 

Name:	ds1054z-scope-display_2020-01-04_00-16-46.png 
Views:	3 
Size:	25.3 KB 
ID:	18630

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

  8. #8
    Junior Member
    Join Date
    Jun 2017
    Posts
    6
    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.

  9. #9
    Junior Member
    Join Date
    Jun 2017
    Posts
    6
    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(); }
    };

  10. #10
    Senior Member
    Join Date
    Sep 2013
    Location
    Hamburg, Germany
    Posts
    894
    I'll check this out, but only at DShot600. I'm building a BLHeli_S-based ESC known to support at least DShot600 and I don't want to bring in another unknown. See here for ESC hardware and progress: https://www.rcgroups.com/forums/show...a-BLHeli_S-ESC

    Might be cool to add thrust, current and voltage sensing to the teensy...

  11. #11
    Senior Member
    Join Date
    Sep 2013
    Location
    Hamburg, Germany
    Posts
    894
    This sketch outputs DShot600 at 50 Hz update rate on Pin 22. Throttle is set with a 10k pot + 1F 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();
    }

  12. #12
    Member
    Join Date
    Nov 2015
    Location
    Lexington, Virginia
    Posts
    28
    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

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •