Feasability Inquiry: I2s to Serial through DMA with bidirectional serial traffic

Status
Not open for further replies.

josechow

Well-known member
Solved: I2s to Serial through DMA with bidirectional serial traffic

Untitled Diagram.png

Above is my top level physical layer. In essence, I want to DMA 2 bytes of separate memory data + 31 samples of 16bit audio from the SGTL5000, push it to the serial bus at 1M Baud through differential transceiver from one teensy to the other, and then stop to wait for a response. Concurrently as the data shows up, the second teensy pulls off the 2 bytes of separate data to update a variable and pushes the 31 samples to I2S for the SGTL5000 to output as audio. Upon completion of the 64 bytes, it will send 4 bytes of data back. The first teensy then gets the go ahead from a short routine that it received back a value and starts streaming the 2 bytes and 31 samples to repeat the cycle. Below is what I hope the traffic to look like on the RS-485.

blah.png

So my question is, is this feasible with Teensy 3.2 DMA controllers interfacing with I2S and serial? The two bytes and 4 bytes of info are values that get updated somewhere in loop(). I know this requires alot of special setup, which I am willing to learn, but I would like to know with reasonable confidence if its feasible before I start digging.

What I think I know:
1. I can increase the size of the serial buffer
2. I dont think I need flow control as I am sending a discrete amount of data and waiting for a psuedo handshake operation to complete, so my buffer should in theory never overrun if I set the size correctly.
3. DMA is a pain in the ass to setup, especially if I don't know what I am doing.
4. If I didn't use DMA, I would get 1uS serial interrupt delay and based upon baud rate and size of audio data that would eat up 6% of CPU plus any extra code inside of the serial interrupt. Approximately 10% as an estimate. Precious CPU time wasted!
5. Based on rudimentary knowledge of DMA, i'll need to use 3 or 4 controllers to do the start and stop handling for each teensy
 
Last edited:
Thank you for your reply, however my main reason for going to digital transmission is because the power and ground connection between the two teensy's is very very noisy causing cross talk on the analog line; adding that I can use a differential audio signal to remove the noise but I cannot provide digital communication between the two teensy's on top of the balanced analog audio. I am restricted in that I have 4 wires between the two teensy devices, one for power, one for ground, and two for differential digital transmission.

What I would like to know is, can I create a DMA architecture which talks between I2S, memory, and Serial to minimize overhead to the CPU?
 
I will try that then and avoid the DMA stuff for now. I have had success with lower frequency bidirectional (around 4800 baud), but I haven't tried pushing the speed. I really do need to send some information back down the line to the transmitter, so I suppose I could extrapolate the lost samples during this time period.

I do understand that the complexity of a bi-directional protocol would require me to "figure out" what happened if a bit got flipped or something like that to cause the back and forth traffic to halt.
 
It’s not clear to me how you will be synchronizing the I2S interfaces (i.e. MCLK) of the two Teensy / SGTL5000 pairs on each side of your serial link. It looks like the Audio Library code normally sets up the I2S subsystems to derive MCLK from the processor’s clock. The two Teensy processor clocks will not be frequency synchronous to each other.
 
It’s not clear to me how you will be synchronizing the I2S interfaces (i.e. MCLK) of the two Teensy / SGTL5000 pairs on each side of your serial link. It looks like the Audio Library code normally sets up the I2S subsystems to derive MCLK from the processor’s clock. The two Teensy processor clocks will not be frequency synchronous to each other.

Typical solution for this is to maintain a short buffer on the receiver, and then slowly servo the consumption from the buffer (either reusing a sample, or dropping a sample) to keep the buffer half filled. The clock sync on the 2 Teensy will be close enough such that sample dropping/duplication should be rare (assuming the middleware doesn't have variable latency between serial peripheral and the buffer).
 
Typical solution for this is to maintain a short buffer on the receiver, and then slowly servo the consumption from the buffer (either reusing a sample, or dropping a sample) to keep the buffer half filled. The clock sync on the 2 Teensy will be close enough such that sample dropping/duplication should be rare (assuming the middleware doesn't have variable latency between serial peripheral and the buffer).

I can confirm, this is true. Pumped in a sinuisoid on one teensy, serial over, and the output on the other teensy didn't show any drops or duplications or phase cut-ins when I looked on my scope. I would have expected some phase cut-in differences because I am sampling for the queue faster than the queue should be filled... but it works and I will not question black magic.

I will post up pretty pictures and code for reference later today.
 
IMG_20170506_070229038.jpg

Picture Description
1. Audio Source
2. Recording Teensy - Push queue samples over Serial1
3. Playback Teensy - Receive queue samples over Serial1
4. Audio Output

Important Notes:
- Serial1 buffer size increased to 512
- Guitar Tuner block size decreased from 24 to 6
- Serial1 connections kept short (transmitting at 4Mbit)
- Bidirectional is not implemented here, because its not physically implemented with the proposed RS-485 chip as previously mentioned in the original posting. Read interesting note below.

Interesting Notes:

- Using a IntervalTimer to catch recorded queue samples at 2909us, when it is the only code works correctly as this is the amount of time between 128 samples being filled. Once you add some other audio library stuff like Yin Algorithm in guitar tuner, it drops samples because it gets interrupted? Inserted highest interrupt priority for IntervalTimer, and this is still the case... So, set IntervalTimer at 2100us, all is well, no skipping.

- One would think that over sampling the queue samples would cause phase jumps in the receiving buffer... it does not, as verified with the scope. I am not going to question this (black magic). Must be something related to how queue points to the samples (head vs. tail).

- There is a lot of time to send back a receiving message ontop of the serial data. The recording teensy can tack on data at the end of the audio packet. The playback teensy can transmit a few bytes back immediately after data is received. The only forseeable issue is that the playback teensy doesn't send its data fast enough before the next packet arrives and cause a collision.


Code for Recording Teensy


Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s2;           //xy=223,245
AudioMixer4              mixer1;         //xy=352,258
AudioAnalyzePeak         peak1;          //xy=534,215
AudioRecordQueue         queue1;         //xy=537,297
AudioAnalyzeNoteFrequency notefreq1;      //xy=543,258
AudioConnection          patchCord1(i2s2, 0, mixer1, 0);
AudioConnection          patchCord2(i2s2, 1, mixer1, 1);
AudioConnection          patchCord3(mixer1, queue1);
AudioConnection          patchCord4(mixer1, notefreq1);
AudioConnection          patchCord5(mixer1, peak1);
AudioControlSGTL5000     sgtl5000_1;     //xy=351,318
// GUItool: end automatically generated code


byte sample_buffer[256];

// Create an IntervalTimer object 
IntervalTimer myTimer;



/////////////////////////////////////////////// SETUP
void setup() {

  //Start up computer communication
  Serial.begin(250000);
  delay(500);

  //Start up hardware communication to external
  Serial1.begin(4000000);
  delay(500);

  //Mixer Setup
  //  Combine audio signals
  mixer1.gain(0,0.5);
  mixer1.gain(1,0.5);
  mixer1.gain(2,0.0);
  mixer1.gain(3,0.0);

  //Setup Memory
  // Inventory of Objects using memory
  //   Yin Algorithm - 6 blocks
  //   Input Audio -   2 blocks
  //   Output Audio -  2 blocks
  //   Safe Keeping -  2 blocks
  AudioMemory(12);

  //Yin Algorithm
  // Determination of input frequency
  // Library is modified to detect lowest frequency of 116.56Hz by
  // using 6 blocks. The default is 24 blocks with a lowest detection
  // of 29.14hz. It can reliabily detect sinuisoids up to 5khz, and
  // seemingly 3khz for complex waves like plucked violin. This
  // should remain at 6 blocks, as for future implementation with five
  // string violins, the low C string is ~130Hz.
  notefreq1.begin(0.1);

  //SGTL
  // Turn on the device and set to appropriate volume level.
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.8);

  //QUEUE
  //  Input Storage of samples for transmission over serial to target
  //  Teensy at the receiving end.
  queue1.begin();

  //start interval timer
  // Interval timer is set to retrieve a data packet from audio library.
  // The audio library samples in 128 sample packets at 44000hz, which 
  // corresponds to 2909.09us delay time between 128 sample frames.
  // Collection of the samples any longer will cause frame overrun and
  // clipping at the output; any faster and you are just wasting sample
  // time and impeding unnecessary transmission. However... other
  // interrupts cause the delay of the ISR because it is of a lower 
  // priority. 2100 is default interrupt time in microseconds.
  myTimer.begin(print_samples, 2100);
}

void print_samples(void){
  
  //the queue will always be ready, but this is to prevent errornous reads  
  if(queue1.available()>=1){
    
    //copy the available data and reset the buffer, prevent overflow
    memcpy(sample_buffer, queue1.readBuffer(), 256);
    queue1.clear();

    //send the data over serial to the target teensy
    Serial1.write(sample_buffer,256);
    
  }
}


/////////////////////////////////////////////// Loop some other stuff
void loop() {
  // read back fundamental frequency
  delay(100);
  if (notefreq1.available()) {
      float note = notefreq1.read();
      float prob = notefreq1.probability();
      float amp = peak1.read();
      if (note > 50 && amp > 0.015)
        Serial.printf("Note: %3.2f | Probability: %.2f | Amp: %.2f\n", note, prob, amp);
  }
}

Code for Playback Teensy

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioPlayQueue           queue2;         //xy=307,242
AudioOutputI2S           i2s1;           //xy=436,173
AudioConnection          patchCord1(queue2, 0, i2s1, 0);
AudioConnection          patchCord2(queue2, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1; //xy=387,310
// GUItool: end automatically generated code

unsigned int sample_buffer_pointer=0;
boolean message_ready = false;
unsigned int packet = 1;

// Create an IntervalTimer object 
IntervalTimer myTimer;

void setup() {
  
  //Start up communication with computer
  Serial.begin(250000);
  delay(500);

  //Start up communication with external teensy
  Serial1.begin(4000000);
  delay(500);

  //Setup Audio Memory
  //  Inventory of Objects using memory
  //    Output Audio - 2 blocks
  AudioMemory(2);

  //SGTL
  //  Turn on the device and set to appropriate volume level.
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.8);
  delay(500);

  myTimer.begin(serial_reader, 1000); //1000 is default
}

void loop() {

}



void serial_reader(){
  
  if(Serial1.available()){

    //wait for the packet to fill if exactly on time
    delayMicroseconds(500);
    byte sample_buffer[256];
    //fill up the buffer
    for(int i = 0; i<256; i ++){
      sample_buffer[i] = Serial1.read();
    }

    //pass it out to the SGTL Audio Chip
    int16_t *audioBuffer = queue2.getBuffer();
    if (audioBuffer != NULL){  
      // Copy the raw PCM audio data into the audio buffer.
      memcpy(audioBuffer, sample_buffer, 256);
      // Transmit the buffer to the audio shield for playback.
      queue2.playBuffer();
    } 
  }
  
}
 
Status
Not open for further replies.
Back
Top