Sending double data from Matlab to the Teensy 3.6 via the microUSB

Hello Everyone,

I've been hitting against a wall for over a week now. My current project is to use a Teensy 3.6 to send data over an SPI bus to a chip which is effective going to be a signal generator. I've made a GUI in Matlab to control this function generator in 'real-time' by sending over values such as frequency and amplitude. For my initial case, I just want to output a sine wave. to do this I am using the built in math function sin and just getting the amplitude and frequency from Matlab. However, when I send over my data I'm getting nothing on the output, but when I hard program in the values, it works just fine. From this I assume I am not sending the data over correctly OR not converting it correctly upon receiving it.

My order of operations are:

  1. send my variable to the Teensy 3.6 using fwrite in Matlab
 serialID = serial('COM5','BaudRate',115200);                    
 fopen(serialID);                           
val = 2.55;
fwrite(serialID, val, 'double');
  1. receiving the data on the Teensy 3.6 using Serial.read()
byte temp[8];
for (int c = 0; c<8; c++){
temp[c] =Serial.read();
}
  1. converting my byte array to a double using a union
double byteToDouble(byte DoubleInBytes[8]) {                             //Receives 8 bytes values and return them into a double value

  union {
    double   Out;                                                      
    byte   In[8];                                                      
  } U ;

  for (int c = 0; c < 8; c++) {
    U.In[c] = DoubleInBytes[c];
  }

  return U.Out;                                                 // Return the double value Out
}

I'm at a loss as to what I might be doing wrong. I've tried to use the serial monitor to monitor variable states but I can't use that while Matlab is using the COM port.

Any helpful tips would be appreciated.

Thanks,

Ryne

I'm at a loss as to what I might be doing wrong.

You need to post your snippets at http://snippets-r-us.com. Here, we expect to see ALL of your code.

It would be easier to debug the Teensy if you sent the data as ASCII data, instead of binary data.

Maybe the serial input basics tutorial might help.

for (int c = 0; c<8; c++){
temp[c] =Serial.read();
}

Maybe you should see if there is anything in the serial buffer before reading it. And Serial.read() returns only one byte, not the whole message.

ALL of my code is a lot of code for one small section I'm having an issue with. I actually tried to upload it all because I've seen in many places this exact statement about wanting ALL the code, however, this exceeds the allowable 9000 character limit. I'll need to slim it down somehow that'll take a bit.

Sending over the data as ASCII, do you mean that I should send over something like ['2','.','5','5']? I've thought about that but I'm unsure how to translate this now char array into a decimal value on the Teensy side of things.

As for the Serial.read pulling in a byte at a time. That is why I have this in a for loop running 8 times to collect the 8 bytes of a double.

ALL of my code is a lot of code for one small section I'm having an issue with.

The smart way to approach this problem is to write a program that simply tests the transmission of the double value, and debug that.

An additional advantage is that you can post the tiny number of program lines that are required to accomplish that task in line.

Are you using the correct byte order?

Alrighty, this is the most I could condense my code and still send it out to my SPI connected device.

Arduino

#include <SPI.h>  //SPI library for Arduino
#include <math.h>
#include <string.h>

//Teensy 3.6 Constants
uint8_t             chipSelectPin     =               15; 
bool                GO                =            false;

char                startMarker       =              '<';
char                endMarker         =              '>';

const int           ArbMem            =            50e3;
uint16_t            Istim             =               0;

// Intan Constants
uint8_t             stimRegisters[]   =         {32, 33};
uint16_t            MagicNumbers[]    = {0xAAAA, 0x00FF};
uint16_t            nonMagicNumbers[] = {0x0000, 0x0000};
uint16_t            StimNull          =           0x8000;
uint8_t             PolReg            =               44;
uint16_t            PolSet;
uint8_t             OnOffReg          =               42;

uint8_t             negRegisters[]    = {64, 65, 66, 67,
                                         68, 69, 70, 71,
                                         72, 73, 74, 75,
                                         76, 77, 78, 79
                                        };
uint8_t             posRegisters[]    = {96, 97, 98, 99,
                                         100, 101, 102, 103,
                                         104, 105, 106, 107,
                                         108, 109, 110, 111
                                        };

uint8_t             registerValues[]   =        { 0,
                                                  1,
                                                  2,
                                                  3,
                                                  4,
                                                  5,
                                                  6,
                                                  7,
                                                  8,
                                                  10,
                                                  12,
                                                  34,
                                                  35,
                                                  36,
                                                  37,
                                                  42,
                                                  44,
                                                  46,
                                                  48
                                                };

uint16_t initializationValues[] =              { 0x00C7,
                                                 0x051A,
                                                 0x0040,
                                                 0x0080,
                                                 0x1A26,
                                                 0x1F05,
                                                 0x3638,
                                                 0x3638,
                                                 0xFFFF,
                                                 0x0000,
                                                 0xFFFF,
                                                 0x000F, 
                                                 0x00FF,
                                                 0x0080,
                                                 0x4F00,
                                                 0x0000,
                                                 0x0000,
                                                 0x0000,
                                                 0x0000
                                               };

uint16_t LD;
uint32_t  N;
char      SendCode[2];
uint8_t  ArbHeap[ArbMem] = {};
bool     ArbPol[ArbMem]  = {};
uint8_t  pairs;
uint8_t  ElecPairs[2]    = {};
double freq;
double  amp;

void setup() {
  SPI.begin();
  delay(10);
  Serial.begin(115200);
  pinMode (chipSelectPin, OUTPUT);

  SPI.beginTransaction(SPISettings(17e6, MSBFIRST, SPI_MODE0));
  digitalWrite(chipSelectPin, HIGH);
  delay(1000);
  StimOff();

  for ( int c = 0; c < (int)sizeof(registerValues); c++) {
    writeRHS(registerValues[c], initializationValues[c]);
  }

  NullStim();
  delay(10);
}

void loop() {
  if (GO) {
    UpdateStim();
  }    
  if (Serial.available() > 0) {
    UNOSerialEvent();
  } 
}

void UNOSerialEvent() 
  byte            temp[8];
  ElecPairs[1] = 4;
  ElecPairs[0] = 5;

  byte rb = Serial.read();
  if (rb == startMarker) {
    Serial.flush();

    for (int c = 0; c < 8; c++) {
      temp[c] = Serial.read();
    }
    freq = byteToDouble(temp);

    // get amplitude value second
    for (int c = 0; c < 8; c++) {
      temp[c] = Serial.read();
    }
    amp = byteToDouble(temp);
    
    makeSine(freq, amp);
    GO = true;
  }

  rb = Serial.read();
}

void UpdateStim(void) {

  uint16_t                        Amp;
  uint16_t PolState = readRHS(PolReg);
  Amp = ArbHeap[Istim];

  // Set Polarity
  if (ArbPol[Istim]) { 
    PolState =  setBit( PolState, ElecPairs[1], 1);
    PolState =  setBit( PolState, ElecPairs[0], 0);
  } else {
    PolState =  setBit( PolState, ElecPairs[1], 0);
    PolState =  setBit( PolState, ElecPairs[0], 1);
  }

  // Send Stim values over
  if (ArbPol[Istim]) { 
    writeRHS( posRegisters[ElecPairs[1]], Amp);
    writeRHS( negRegisters[ElecPairs[0]], Amp);
  } else { 
    writeRHS( posRegisters[ElecPairs[0]], Amp);
    writeRHS( negRegisters[ElecPairs[1]], Amp);
  }

  writeRHS(PolReg, PolState);
  clearComplianceMonitor();
  delayMicroseconds(LD);

  Istim++;
  if (Istim >= N)  {
    Istim = 0;
  }
}

void StimOff(void) {
  readRHS(255);
  writeRHS(stimRegisters[0], nonMagicNumbers[0]);
  writeRHS(stimRegisters[1], nonMagicNumbers[1]);
  writeRHS(38, 0xFFFF);
  clearRHS();
}

void NullStim(void) {
  for ( int c = 0; c < (int)sizeof(negRegisters); c++) {
    writeRHS(negRegisters[c], StimNull);
    writeRHS(posRegisters[c], StimNull);
  }

  writeRHS(stimRegisters[0], MagicNumbers[0]);
  writeRHS(stimRegisters[1], MagicNumbers[1]);
  clearComplianceMonitor();

  writeRHS(OnOffReg, 0xFFFF);
  clearComplianceMonitor();
}

void makeSine(double Freq, double Amp) {
  double   Stim;
  uint8_t  Trim       =                         0x80;
  double   tld        =                        14.25;
  uint32_t FreqUpdate =          floor(1 / (tld * 1e-6));
  uint16_t FreqB      =                           50;
  N                   = (uint32_t)(FreqUpdate / FreqB);
  LD                  =  floor(tld * ((FreqB / Freq) - 1));

  for ( int c = 0; c < (int)N; c++) {
    Stim = Amp * sin((2.0 * PI * ((double)c)) / (double)N);

    if (Stim >= 0) {
      ArbPol[c]  =  true;
    } else {
      ArbPol[c]  = false;
    }
    ArbHeap[c] =     ADC_RHS(Stim, Trim);
  }
}

uint16_t setBit(uint16_t value, uint8_t bitnum, uint8_t setVal) {

  if (setVal == 1) {
    value = (value | (1 << bitnum));
  } else {
    value = (value & ~(1 << bitnum));
  }
  return value;
}

uint16_t ADC_RHS(double Stim, uint8_t Trim) {
  uint16_t DStim   = (uint16_t)round(abs(Stim)*100);
  return (((uint16_t) Trim) << 8) + DStim;
}

//======================= Converter functions =============================
double byteToDouble(byte doubleNbyte[8]) {

  union {
    double   Out;
    byte   In[8];
  } U ;

  for (int c = 0; c < 8; c++) {
    U.In[c] = doubleNbyte[c];
  }
  return U.Out;
}

void writeRHS(int registerNumber, uint16_t dataOut) {
  uint16_t writeMask = 0b1000000000000000 | registerNumber;
  digitalWrite(chipSelectPin, LOW);
  SPI.transfer16(writeMask);
  SPI.transfer16(dataOut);
  digitalWrite(chipSelectPin, HIGH);
}

uint16_t readRHS(int registerNumber) {
  uint16_t dataIn;
  uint16_t readMask = 0b1100000000000000 | registerNumber;
  digitalWrite(chipSelectPin, LOW);
  SPI.transfer16(readMask);
  dataIn = SPI.transfer16(0);
  digitalWrite(chipSelectPin, HIGH);
  return dataIn;
}

void clearRHS() {
  uint16_t clearCommand = 0b0110101000000000;
  digitalWrite(chipSelectPin, LOW);
  SPI.transfer16(clearCommand);
  SPI.transfer16(0);
  digitalWrite(chipSelectPin, HIGH);
}

void clearComplianceMonitor() {
  uint16_t readMask = 0b1111000000000000;
  digitalWrite(chipSelectPin, LOW);
  SPI.transfer16(readMask);
  SPI.transfer16(0);
  digitalWrite(chipSelectPin, HIGH);
}

Matlab

SerialID = serial('COM5','BaudRate',115200);
fopen(SerialID);
fwrite(SerialID,'<','char');
fwrite(SerialID,10.0);
fwrite(SerialID,2.55);
fwrite(SerialID,'>','char');
fclose(SerialID);

I meant a small program to debug serial transmission of the value from Matlab to the Arduino, because that is most likely where the problem lies.

If you can receive a binary 2.55 from Matlab, and use Serial.print() to display 2.55 on the serial monitor, it should work. Did you check the byte order (big or little endian), as advised earlier?

However, it would be sensible to transmit the values in ASCII, e.g.

fwrite(SerialID,'<','char');
fwrite(SerialID,"10.0,",'char');
fwrite(SerialID,"2.55",'char');
fwrite(SerialID,'>','char');

I would use sprintf() on Matlab and sscanf() on the Teensy (with floating point enabled if necessary), to perform the conversions.

opps, forgot to mention that in the last post. My computer uses little endian and the teensy 3.6 does as well. So that should all be fine and well there.

I'm concerned about sending ASCII because I do not know the number of characters I will be sending over to the teensy. Will I need to code out something to ensure it is, say 5 characters long or is it read over as a complete package like a string (i.e. '02.55' and '10.75')?

Let me have a play around with ASCII. I've modified my code to ping back the value of the sine wave back to matlab and I have it being graphed there.

updated Arduino Code (likely with errors)

#include <SPI.h>  //SPI library for Arduino
#include <math.h>
#include <string.h>

//Teensy 3.6 Constants
uint8_t             chipSelectPin     =               15; 
bool                GO                =            false;

char                startMarker       =              '<';
char                endMarker         =              '>';

const int           ArbMem            =            50e3;
uint16_t            Istim             =               0;

uint16_t LD;
uint32_t  N;
char      SendCode[2];
double  ArbHeap[ArbMem] = {};
double freq;
double  amp;

void setup() {
  SPI.begin();
  delay(10);
  Serial.begin(115200);
  pinMode (chipSelectPin, OUTPUT);

  SPI.beginTransaction(SPISettings(17e6, MSBFIRST, SPI_MODE0));
  digitalWrite(chipSelectPin, HIGH);
  delay(1000);
}

void loop() {
  if (GO) {
    UpdateStim();
  }   
  if (Serial.available() > 0) {
    UNOSerialEvent();
  }
}

void UNOSerialEvent() {
  byte            temp[8];
  ElecPairs[1] = 4;
  ElecPairs[0] = 5;

  byte rb = Serial.read();
  if (rb == startMarker) {
    Serial.flush();

    for (int c = 0; c < 8; c++) {
      temp[c] = Serial.read();
    }
    freq = byteToDouble(temp);

    // get amplitude value second
    for (int c = 0; c < 8; c++) {
      temp[c] = Serial.read();
    }
    amp = byteToDouble(temp);
    
    makeSine(freq, amp);
    GO = true;
  }

  rb = Serial.read();
}


void UpdateStim(void) {

  double Amp = ArbHeap[Istim];

  Serial.write(Amp);
  
  Istim++;
  if (Istim >= N)  {
    Istim = 0;
  }
}

void makeSine(double Freq, double Amp) {
  double   Stim;
  uint8_t  Trim       =                         0x80;
  double   tld        =                        14.25;
  uint32_t FreqUpdate =          floor(1 / (tld * 1e-6));
  uint16_t FreqB      =                           50;
  N                   = (uint32_t)(FreqUpdate / FreqB);
  LD                  =  floor(tld * ((FreqB / Freq) - 1));

  for ( int c = 0; c < (int)N; c++) {
    Stim = Amp*sin((2.0*PI*((double)c))/(double)N);

    ArbHeap[c] = Stim;
  }
}

double byteToDouble(byte doubleNbyte[8]) {

  union {
    double   Out;
    byte   In[8];
  } U ;

  for (int c = 0; c < 8; c++) {
    U.In[c] = doubleNbyte[c];
  }
  return U.Out;
}

Matlab

SerialID = serial('COM5','BaudRate',115200);
fopen(SerialID);
fwrite(SerialID,'<','char');
fwrite(SerialID,10.00,'double');
fwrite(SerialID,1.00,'double');
fwrite(SerialID,'>','char');

figure(1)
c = 0;
while(1)
    c=c+1;
    data(c) = fread(SerialID,1,'double');
    
    plot(c,data(c),'.b')
    if c == 1
        hold on
    end
    drawnow;
end

I'm concerned about sending ASCII because I do not know the number of characters I will be sending over to the teensy.

Why should you care? sprintf and sscanf don't need to know that.

Matlab code:

a=2.55;
str_f = sprintf(">%0.6f<",a);
disp(str_f);

Displays:

2.550000<

You may need to enable floats or doubles in sscanf() on the teensy:

void setup() {
  // this is the magic trick for printf to support float
  asm(".global _printf_float");
  
  // this is the magic trick for scanf to support float
  asm(".global _scanf_float");
}

void loop() {
  float f;
  sscanf("123.456", "%f", &f);
  Serial.println(f);
  delay(1000);
}

Alrighty, I am getting there but there are some quirks that I am not understanding. While debugging I had the teensy spit back the double values I sent it. They came back correctly, so that was great! I then went to clean up my code and removed the double to byte conversion and Serial.write of those bytes (I also removed the Matlab code to read my double values) the sine wave went flat again. Straight zeros. It seems I need this Serial.write to make it work? I have experimented with having a delay(1000) but that did not do the trick either. Anyone have any ideas as to why I need to transmit back to Matlab?

Arduino Code

#include <SPI.h>  //SPI library for Arduino
#include <math.h>
#include <string.h>

//Teensy 3.6 Constants
uint8_t             chipSelectPin     =               15; 
bool                GO                =            false;

char                startMarker       =              '<';
char                endMarker         =              '>';

const int           ArbMem            =            10e3;
uint16_t            Istim             =               0;

uint16_t LD;
uint32_t  N;
double  ArbHeap[ArbMem] = {};
double freq;
double  amp;

//========================== Setup Function ============================
void setup() {
  SPI.begin();
  delay(10);
  Serial.begin(9600);
  pinMode (chipSelectPin, OUTPUT);

  SPI.beginTransaction(SPISettings(17e6, MSBFIRST, SPI_MODE0));
  digitalWrite(chipSelectPin, HIGH);
  delay(1000);
}

void loop() {
  if (GO) {
    UpdateStim();
  }   
  if (Serial.available() > 0) {
    UNOSerialEvent();
  }
}

void UNOSerialEvent() {
  int                NB=4;
  char           temp[NB];
  String incoming = ""; 
  
  char rb = Serial.read();
  if (rb == startMarker) {
    // get frequency value first
    incoming = Serial.readString();
    incoming.toCharArray(temp, sizeof(temp));
    freq = (double)atof(temp);
    byte * b = (byte *) &freq;
    Serial.write(b,8);

    // get amplitude value second
    incoming = Serial.readString();
    incoming.toCharArray(temp, sizeof(temp));
    amp = (double)atof(temp);
    byte * d = (byte *) &amp;
    Serial.write(d,8);
    
    makeSine(freq, amp);
    GO = true;
  }

  rb = Serial.read();
}

void UpdateStim(void) {

  double Amp = ArbHeap[Istim];
  byte * b = (byte *) &Amp;
  Serial.write(b,8);
  
  Istim++;
  if (Istim >= N)  {
    Istim = 0;
  }
}

void makeSine(double Freq, double Amp) {
  double   Stim;
  double   tld        =                        14.25;
  uint32_t FreqUpdate =          floor(1/(tld*1e-6));
  uint16_t FreqB      =                           50;
  N                   = (uint32_t)(FreqUpdate/FreqB);
  LD                  =  floor(tld*((FreqB/Freq)-1));

  for ( int c = 0; c < (int)N; c++) {
    Stim = Amp*sin((2.0*PI*((double)c))/(double)N);

    ArbHeap[c] = Stim;
  }
}

Matlab Code

fclose(SerialID);

SerialID = serial('COM5','BaudRate',9600);
fopen(SerialID);
fwrite(SerialID,'<','char');
temp = sprintf('%2.2f',5);
fwrite(SerialID,temp,'char');
fread(SerialID,1,'double');
temp = sprintf('%2.2f',1);
fwrite(SerialID,temp,'char');
fread(SerialID,1,'double');
fwrite(SerialID,'>','char');

figure(1)
c = 0;
while(1)
    c=c+1;
    data(c) = fread(SerialID,1,'double');
    
    plot(c,data(c),'.b')
    if c == 1
        hold on
    end
    drawnow;
end
    
% fclose(SerialID);
const int           ArbMem            =            10e3;
uint16_t            Istim             =               0;

uint16_t LD;
uint32_t  N;
double  ArbHeap[ArbMem] = {};

Which Arduino has SRAM for 10000 doubles?

A very big one. The Teensy 3.6 has 256kB (Bytes) of SRAM.

Since doubles are equivalent to 8 bytes I am using 80kB of the 256kB for just this waveform. But still only sitting at ~31.25% of the available variable space.

Just to put an answer on this post.

I was able to transmit double into my Arduino using the Matlab command

temp = sprintf('%2.2f',2.55);
fread(SerialID,1,'double');

In the Arduino I needed to use something like this

String    incoming = "";
incoming = Serial.readString();
incoming.toCharArray(temp, sizeof(temp));
td = (unsigned long)atof(temp);

My issue with timing I mentioned above was resolved by adding a delay(100) after each of my other non double Serial.read(). Like this.

char rb = Serial.read();
delay(100);

Seems I was going too fast in my code and was continuing onto my next steps while the serial signal was still sending. UARTs is slow compared to the Arduino (Teensy 3.6) speed so you need to account for this it seems or you will get garbage on the output. Trap for young players (me).

Thanks for holding my hand through this issue!