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(); }
};