Precise Serial Data Output Clock Speed?

Status
Not open for further replies.

Guilt

Member
For a project I'm working on I'll need to output data at almost exactly 4.3MHz and 8.6MHz from one digital pin. Right now I'm using DMA transfers to the SPI peripheral, but I've noticed that no matter what SPISettings I pass in for SPI.beginTransaction, my oscilloscope measures that the SCK pin only ever outputs multiples of 1MHz. This might cut it for the 4.3Mb/s speed, but definitely not for 8.6Mb/s.

I should note that although I'm using the SPI peripheral right now, I'm only actually using the MOSI pin and leaving SCK unconnected since the target device has its own clock recovery mechanism.

(Okay, that's not totally honest. I noticed that MOSI goes high between SCK pulses after each byte for some reason, so in reality I'm using a CMOS AND gate to filter that out and I'm actually trying to use SCK&MOSI as my output. that's not totally relevant though, I think.)

I'm using the Teensy 3.6 right now (for the clock speed and convenient SD card access) but I wouldn't object to using a different micro controller as long as I can still use DMA for the data output. I figured I could probably try replacing the crystal with something slightly off in order to make the difference, but I feel like that's abusing the hardware. What alternatives are there?

I doubt the source code would help much, but here's what I've got right now:
Code:
#include <Arduino.h>
#include "DMAChannel.h"
#include <spi.h>
#include <SdFs.h>

//SD CARD CARGO CODE=========================================================
#define SD_FAT_TYPE 3

const uint8_t SD_CS_PIN = SDCARD_SS_PIN;
#define SD_CONFIG SdioConfig(FIFO_SDIO)
SdFs sd;
FsFile file;
//===========================================================================

DMAChannel dmachannel;
uint32_t data[256];

void dmaFinishedTest() {  
  digitalWrite(33,HIGH);
}

void dmaSpiTest() {
  for (uint16_t i = 0; i < 256; i++) {
    data[i] = 0b10101010;
  }

  // Setup the SPI clocks and pin configurations
  SPI.begin();
  SPI.beginTransaction(SPISettings(4300000, MSBFIRST, SPI_MODE0)); //this gives me a real output speed of 5Mb/s for some reason

  // Setup SPI for DMA transfer
  SPI0_SR = 0xFF0F0000;
  SPI0_RSER = 0x00;
  SPI0_RSER = SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS; // Make sure SPI triggers a DMA transfer after each transmit
  
  dmachannel.sourceBuffer(data, 256); // The data for which we wish to transmit and its length
  dmachannel.destination((volatile uint8_t&)SPI0_PUSHR); // Move data into the SPI FIFO register
  dmachannel.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX); // Only transfer data once the previous byte has been transmited (This is to ensure all bytes are sent)
  dmachannel.disableOnCompletion(); // Stop after transmitting all 256 bytes
  // dmachannel.interruptAtCompletion();
  dmachannel.attachInterrupt(dmaFinishedTest);
  
  pinMode(33,OUTPUT);
  dmachannel.enable(); // Begin transmit
}

/** Wait for and consume a keypress over USB **/
void waitForKeyPress()
{
  Serial.println("\nPress a key to continue\n");
  while(!Serial.available());
  while(Serial.available())
  {
    Serial.read();
  }
}

void sdCardTest() {
  if (!sd.begin(SD_CONFIG)) {
    Serial.println(F("sd card?"));
    while(true);
  }

  if (!file.open("test", FILE_READ)) {
    Serial.println(F("file?"));
    while(true);
  }
  
  #define BUF_SIZE 512
  uint8_t buf[BUF_SIZE/8];

  Serial.println(F("Reading..."));

  for (int i = 0; i < 5; i++) {
    file.read(buf,8);
  }

  uint64_t filesize = file.size();
  for (uint64_t i = 0; i < filesize; i++) {
    Serial.print("value="); Serial.println(buf[i],HEX);
  }
}

void setup() {
  Serial.begin(9600);
  waitForKeyPress();

  sdCardTest();

  dmaSpiTest();
}

void loop() {
}
 
The divisors for SPI do not allow exact speeds.
Maybe you can try slave mode and use tone() (EDIT: Better use analogWriteFrequency() ) to generate a clk on an other pin and just connect the pins. However, i've not tested if tone() is exact enough.

Theyn, you can always use an external oscillator (with SPI-slave mode).
 
Last edited:
Thank you for the suggestions. It looks like the fastest that tone() will get me is close to 40kHz, so that's right out. analogWriteFrequency() has got me to 8.62MHz which is close enough for rock and roll (and good thing too, because it's strangely difficult to find crystals at multiples of 4.3MHz).
Then the new problem; it looks like it's not a simple task to get the Teensy functioning in SPI Slave mode. I found the library on github at /tonton81/TSPISlave, but it has virtually no documentation (even in the example there is no comments). I'm kind of nervous because I need to have the Teensy working on other things while this SPI data is being clocked out. The end goal here is to use DMA and double-buffering to decompress and stream data out of an SD card at this 8.6Mb/s speed. DMAChannel is another thing I have a hard time finding documentation for...
I'll see if I can use the slave mode for now.
 
I use the function shiftout() for writing to a Led display. This is much slower, but i think shiftout is using a timer for the clock. So perhaps you can change the timer setting......
 
No, shiftout just shifts out as fast as it can with code.
On Teensy 4, there is a builtin "brake" to make it not too fast for slower chips.
 
I'm not exactly sure what you mean. To be clear, this thread and the thread you linked are both not about SD card reading. After you showed me the trick to use analogWriteFrequency() and get the SPI interface outputting data in slave mode I wanted to look further into the interactions between DMA and SPI output, which was sort of difficult but I've made my way through it now. Reading from the SD card is an issue that I'm just now tackling, and I'm beginning to work with Greiman's SD library at your suggestion.
EDIT: I think I see where I was unclear. The system I'm developing would be reading data from an SD card, then decompressing it very quickly, then placing this data into one of two buffers. During this process, DMA will be streaming the contents of the other buffer out of the SPI interface at that 8.6Mb/s frequency. I'm using DmaChannel.h's replaceSettingsOnCompletion() and an interrupt at completion to switch buffers and keep the ping-pong dma constant. DMA is not involved in reading from the SD card (I hadn't even considered that) it's just for getting the data out of the serial interface.
 
Status
Not open for further replies.
Back
Top