Creating DShot150 with SPI but voltage spikes on the SS Output

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

DShot ON First 16 bits.png

Voltage Spikes on SS.png