Created
May 26, 2020 22:38
-
-
Save reefwing/8a08d0fbe063540f313dea5062b79203 to your computer and use it in GitHub Desktop.
DShot implementation for Arduino using bit-banging method. Library modified to use Timer 2 and Port B.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************** | |
@file DShotTimer2.cpp | |
@brief DShot implementation for Arduino using bit-banging method | |
@author Andy Tsui | |
Original version using Timer 1 - https://github.com/gueei/DShot-Arduino | |
Modified to use Timer 2 and Port B on the Arduino UNO | |
Pins D8 - D13 | |
Modifications: David Such | |
Version: 1.0 | |
Date: 26/05/20 | |
**********************/ | |
#include "Arduino.h" | |
#include "DShotTimer2.h" | |
// Each item contains the bit of the port | |
// Pins that are not attached will always be 1 | |
// This is to offload the Off pattern calculation during bit send | |
static uint8_t dShotBits[16]; | |
// Denote which pin is attached to dShot | |
static uint8_t dShotPins = 0; | |
#define NOP "NOP\n" | |
#define NOP2 NOP NOP | |
#define NOP4 NOP2 NOP2 | |
#define NOP8 NOP4 NOP4 | |
/* | |
DSHOT600 implementation | |
For 16MHz CPU, | |
0: 10 cycle ON, 17 cycle OFF | |
1: 20 cycle ON, 7 cycle OFF | |
Total 27 cycle each bit | |
*/ | |
static inline void sendData(){ | |
noInterrupts(); | |
asm( | |
// For i = 0 to 15: | |
"LDI r23, 0 \n" | |
// Set High for every attached pins | |
// DSHOT_PORT |= dShotPins; | |
"IN r25, %0 \n" | |
"_for_loop: \n" | |
"OR r25, %1 \n" | |
"OUT %0, r25 \n" | |
// Wait 10 cycles | |
NOP4 | |
NOP2 | |
// Set Low for low bits only | |
//DSHOT_PORT &= dShotBits[i]; | |
"LD r24, Z+ \n" | |
"AND r25, r24 \n" | |
"OUT %0, r25 \n" | |
// Wait 10 cycles | |
NOP8 | |
// Turn off everything | |
// DSHOT_PORT &= ~dShotPins; | |
"AND r25, %2 \n" | |
"OUT %0, r25 \n" | |
// Add to i (tmp_reg) | |
"INC r23 \n" | |
"CPI r23, 16 \n" | |
"BRLO _for_loop \n" | |
// 7 cycles to next bit (4 to add to i and branch, 2 to turn on), no wait | |
: | |
: "I" (_SFR_IO_ADDR(DSHOT_PORT)), "r" (dShotPins), "r" (~dShotPins), "z" (dShotBits) | |
: "r25", "r24", "r23" | |
); | |
interrupts(); | |
} | |
static boolean timerActive = false; | |
/* | |
Generated by: | |
http://www.8bit-era.cz/arduino-timer-interrupts-calculator.html | |
1000 Hz Update rate | |
*/ | |
/* Timer 1 Version: | |
static void initISR(){ | |
cli(); // stop interrupts | |
TCCR1A = 0; // set entire TCCR1A register to 0 | |
TCCR1B = 0; // same for TCCR1B | |
TCNT1 = 0; // initialize counter value to 0 | |
// set compare match register for 500 Hz increments | |
OCR1A = 31999; // = 16000000 / (1 * 500) - 1 (must be <65536) | |
// turn on CTC mode | |
TCCR1B |= (1 << WGM12); | |
// Set CS12, CS11 and CS10 bits for 1 prescaler | |
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10); | |
// enable timer compare interrupt | |
TIMSK1 |= (1 << OCIE1A); | |
timerActive = true; | |
for (byte i=0; i<16; i++){ | |
dShotBits[i] = 0; | |
dShotPins = 0; | |
} | |
sei(); // allow interrupts | |
} | |
*/ | |
static void initISR(){ | |
// TIMER 2 for interrupt frequency 1000 Hz: | |
cli(); // stop interrupts | |
TCCR2A = 0; // set entire TCCR2A register to 0 | |
TCCR2B = 0; // same for TCCR2B | |
TCNT2 = 0; // initialize counter value to 0 | |
// set compare match register for 1000 Hz increments | |
OCR2A = 249; // = 16000000 / (64 * 1000) - 1 (must be <256) | |
// turn on CTC mode | |
TCCR2B |= (1 << WGM21); | |
// Set CS22, CS21 and CS20 bits for 64 prescaler | |
TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20); | |
// enable timer compare interrupt | |
TIMSK2 |= (1 << OCIE2A); | |
sei(); // allow interrupts | |
} | |
static boolean isTimerActive(){ | |
return timerActive; | |
} | |
ISR(TIMER2_COMPA_vect){ | |
sendData(); | |
} | |
/* | |
Prepare data packet, attach 0 to telemetry bit, and calculate CRC | |
throttle: 11-bit data | |
*/ | |
static inline uint16_t createPacket(uint16_t throttle){ | |
uint8_t csum = 0; | |
throttle <<= 1; | |
// Indicate as command if less than 48 | |
if (throttle <48) | |
throttle |= 1; | |
uint16_t csum_data = throttle; | |
for (byte i=0; i<3; i++){ | |
csum ^= csum_data; | |
csum_data >>= 4; | |
} | |
csum &= 0xf; | |
return (throttle<<4)|csum; | |
} | |
/****************** end of static functions *******************/ | |
DShot::DShot(){ | |
} | |
void DShot::attach(uint8_t pin){ | |
this->_packet = 0; | |
this->_pinMask = digitalPinToBitMask(pin); | |
pinMode(pin, OUTPUT); | |
if (!isTimerActive()){ | |
initISR(); | |
} | |
dShotPins |= this->_pinMask; | |
} | |
/* | |
Set the throttle value and prepare the data packet and store | |
throttle: 11-bit data | |
*/ | |
uint16_t DShot::setThrottle(uint16_t throttle){ | |
this->_throttle = throttle; | |
// TODO: This part can be further optimized when combine with create packet | |
this->_packet = createPacket(throttle); | |
uint16_t mask = 0x8000; | |
for (byte i=0; i<16; i++){ | |
if (this->_packet & mask) | |
dShotBits[i] |= this->_pinMask; | |
else | |
dShotBits[i] &= ~(this->_pinMask); | |
mask >>= 1; | |
} | |
return _packet; | |
} | |
/* | |
Required to allow DShot to be reinitialised once stopped. | |
*/ | |
void DShot::setTimerActive(bool active){ | |
timerActive = active; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment