I am trying to reproduce the DShot protocol on an Arduino UNO using SPI. I can go into the theory if that is helpful but the upshot is that I'm using 4 SPI bytes to simulate 1 DShot bit.
DShot LOW (0) = 10 SPI bits ON, 17 SPI bits OFF = 0x07FE 0000
DShot HIGH (1) = 20 SPI bits ON, 7 SPI bits OFF = 0x07FF FF80
As shown in the attached image (DShot ON First 16 bits) this works although the timing is a bit off due to the delay between sending bytes.
The issue is that I am getting voltage spikes on the SS SPI pin. See second attached image (Voltage spikes on SS).
As the ATMega328P data sheet says: "When configured as a master, the SPI interface has no automatic control of the SS line. This must be handled by user software before communication can start."
The SS line is active low, so as shown in the code below I set this LOW before transmission and then HIGH at the end of sending the DShot frame. However these voltage spikes are interrupting communication and messing up the bit patterns.
The hardware setup is very simple. I only have an UNO connected to a protocol analyser via the 4 SPI pins (SS, MOSI, MISO and CLOCK).
I know that there is a bit banging library written for DShot but I would like to understand why this isn't working.
The test code is as follows - I am just sending a fixed frame at this stage.
/**********************
@file DShot_SPI.ino
@brief Testing DShot packet transmission using SPI.
@author David Such
Code: David Such
Version: 1.0
Date: 19/05/20
1.0 Original Release 19/05/20
**********************/
#include <SPI.h>
// System defined UNO SPI PINS
// SS: pin 10
// MOSI: pin 11
// MISO: pin 12
// SCK: pin 13
// DShot starting test frame = throttle (11 bits) + telemetry request (1 bit)
uint16_t frame = 0;
uint16_t throttle = 0b10101010101; // 0x555 or 1365 Decimal (68% throttle)
bool telemetry = 0;
void setup() {
Serial.begin(9600);
Serial.println();
Serial.println("DShot SPI Test");
// Define PIN modes & Disable SPI
pinMode(SS, OUTPUT);
digitalWrite (SS, HIGH);
// Start the SPI library
SPI.begin();
SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
// Append telemetry to throttle
if (valid_throttle(throttle)) {
frame = (throttle << 1) | telemetry;
}
Serial.print("Test Frame: ");
Serial.println(frame, BIN);
}
void loop() {
// With a fixed frame we could just do this once in setup
int csum = checksum(frame);
int mask = 0b10000000;
// append checksum to test_frame
frame = (frame << 4) | csum;
// Slave Select - Active LOW
digitalWrite (SS, LOW);
for (byte i = 0; i < 15; i++) {
if (frame & mask) {
dShot_1();
}
else {
dShot_0();
}
mask = mask >> 1;
}
digitalWrite (SS, HIGH);
delay(200);
}
// DShot Functions
// For Arduino UNO (16MHz CPU),
// 0: 10 SPI bits ON, 17 SPI bits OFF = 0x07FE 0000
// 1: 20 SPI bits ON, 7 SPI bits OFF = 0x07FF FF80
// Total 27 SPI bits for every DShot bit
void dShot_0() {
SPI.transfer16(0x07FE);
SPI.transfer16(0x0000);
}
void dShot_1() {
SPI.transfer16(0x07FF);
SPI.transfer16(0xFF80);
}
bool valid_throttle(uint16_t throttle) {
// Note throttle values < 47 are special commands.
// See notes in article.
if (throttle > 0 && throttle < 2048) {
return true;
}
return false;
}
int checksum(uint16_t packet) {
// 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;
Serial.print("Checksum: ");
Serial.println(csum, BIN);
return csum;
}

