Teensy 4.1 and 2 x MAX31855 thermocouples SPI problem

Status
Not open for further replies.

guzu

Well-known member
Hello everyone.
I have a Teensy 4.1 connected to an 4.2" oled and a few sensors. Among the sensors I want to connect to the SPI bus two MAX31855 thermocouples for measuring temperature.
I have designed a PCB on which all the parts fit. When I solder just one of the MAX31855 ICs everything works great. The temperature is read and displayed on the oled without any problems.
But when I solder both the MAX31855 ICs on the PCB, the SPI bus (I *think* it is the SPI) is having some weird issues. The oled begins to display glitches (random lines or dots on the screen, the data displayed begins to move around) and even sometimes the SPI bus stops working (the oled is all black and the SCK pin 13 on the Teensy stops blinking). I think the SPI bus is somehow affected because I also have a NRF24L01+ module connected to SPI and when the glitches appear the module stops working.

Here is what I tested so far:
-The glitches appear only when both the MAX31855 ICs are soldered on the PCB
-It is not a PCB issue. (Both the ICs work just fine when soldered just one at a time in either of the PCB footprints.)
-Both the CS pins of the MAX31855 ICs have separate pull-up resistors.
-Software is not the issue because the glitches appear even when the Teensy is coded just to turn on the oled and display something, without any code/library for the MAX31855 IC. And the temperature is shown correctly when only one of the ICs is soldered.

If you need to see the layout of the PCB you can find a copy of my project here.

Could anyone tell me why this happens? What could I do in order to be able to use both the MAX31855 ICs?

Thank you very much for your input.
 
Sounds indeed like a collision on the SPI bus.
Are you sure you drive both CS-t1 and CS-t2 individually in software?
If you have a 2-channel oscilloscope you could check both these CS's for overlap.

Paul
 
Sounds indeed like a collision on the SPI bus.
Are you sure you drive both CS-t1 and CS-t2 individually in software?
If you have a 2-channel oscilloscope you could check both these CS's for overlap.

Paul

I am pretty sure the CS-t1 and CS-t2 are handled properly. I say this because the glitches appear even if the software for the second MAX31855 IC is all commented out (the second IC is just physically soldered to the PCB without being used in any way)
I am sorry but I do not have an oscilloscope.
I am using the Adafruit MAX31855 library if this is of any help. But again, the problem appears even if no software is written for the second MAX31855 IC (just the CS pin digitalWritten HIGH).

What else could it be? I am running out of ideas on how to fix this. :)

Thank you.
 
Can you verify with a DMM (preferably an oscilloscope) that the CS lines stay high? This certainly sounds like bus collision!
The only other hardware issue I can think of, is that the MAX31855 isn't tri-stating the SPI lines (ie, it's pulling the lines high or low). With only the one IC, you get away with it, but with two, the problem is bad enough to cause other issues.
Do you have access to an oscilloscope?

Can you please provide the code that replicates the issue?
 
There maybe an issue with the Adafruit MAX31855 library, see this page.
Maxim's MAX13855 FAQ clearly states the MAX31855 uses SPI Mode 1, while the library defaults to using SPI Mode 0.
The Adafruit MAX31855 library relies on the Adafruit Bus IO Library.
In Adafruit_SPIDevice.cpp you can see how to set SPI_MODE1. Line 12 states that it defaults to SPI Mode 0.

Since I don't know your code you have to fiddle in this SPI_Mode1 parameter somewhere yourself.

Hope this helps because I'm running out of options as well.

Paul
 
Thank you all very much for your replies.
After spending much of today trying to debug this, I noticed something.
First of all, here is my code: (I know it is quite long)

Code:
/*
  31.07.2020
*/

#include <Audio.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <tvg.h>       // an image in hex format
#include <EEPROM.h>
#include <NMEAGPS.h> // for the NMEA GPS sentences pharsing
#include <ResponsiveAnalogRead.h> // the library for smooth analog reads
#include <Adafruit_MAX31855.h> // for temparature read

/*
  Make a ResponsiveAnalogRead object, pass in the pin, and either true or false depending on if you want sleep enabled
  Enabling sleep will cause values to take less time to stop changing and potentially stop changing more abruptly,
  where as disabling sleep will cause values to ease into their correct position smoothly and with slightly greater accuracy
*/
ResponsiveAnalogRead BRAKE(A0, true);
ResponsiveAnalogRead BATTERYRELAY(A1, true);
ResponsiveAnalogRead BATTERY(A8, true);
ResponsiveAnalogRead VOLUME(A12, true);
ResponsiveAnalogRead STEERING(A16, true);
ResponsiveAnalogRead ACCELERATION(A17, true);

/* ========= */
/* NRF24L01+ */
/* ========= */
RF24 radio(24, 25);       // CE and CSN pins for the NRF24L01+
const uint64_t pipeaddress = 0xFA51AA55AFLL; // NRF pipe address must be the same for transmitter and receiver
bool wireless = true;   // to know if the data should be sent to a wireless device HIGH=Yes, LOW=No
int NRFfail = 0;       // number of failed NRF ack receives. Used to know when to display the WiFi sign
byte NRFfailRate = 10;// how many failed ack receives untill the WiFi sign stops showing
byte ackMsg;

/* ====== */
/* AUDIO  */
/* ====== */
#define audiobuffersize 128 // one block in audio library has 128 values of 2 bytes
// GUItool: begin automatically generated code
AudioInputI2S            i2s1;
AudioPlayQueue           queue2;
AudioMixer4              mixer2;
AudioMixer4              mixer1;
AudioOutputI2S           i2s2;
AudioRecordQueue         queue1;
AudioConnection          patchCord1(i2s1, 0, mixer1, 0);
AudioConnection          patchCord2(i2s1, 1, mixer1, 1);
AudioConnection          patchCord3(queue2, 0, mixer2, 0);
AudioConnection          patchCord4(mixer2, 0, i2s2, 0);
AudioConnection          patchCord5(mixer2, 0, i2s2, 1);
AudioConnection          patchCord6(mixer1, queue1);
AudioControlSGTL5000     sgtl5000_1;
// GUItool: end automatically generated code

uint8_t bufr[audiobuffersize * 2]; // buffer to hold 256 bytes of audio data
uint8_t bufcount = 0;             // received nrf24l01+ packet counter
bool setupR = true, setupT = true, setupN = true; // used to run the setup code only once
byte error = 0;                 // used to calculate the number of errors received when transmitting audio
byte maxErrors = 2;            // the number of errors that show that the audio receiving has ended
unsigned long lastError = 0;  // last time 50 ms have passed since the last Audio Receive

/* ========= */
/* VARIABLES */
/* ========= */
unsigned int accValue = 0, brkValue = 0, zeroAccValue = 0, zeroBrkValue = 0, fullAccValue = 0, fullBrkValue = 0, fullLeftValue = 0, fullRightValue = 0;
byte leds = 0;                    // Variable to hold the pattern of which LEDs are currently turn ed on or off for RPM
byte rpmCount = 0, spdCount = 0; // Variable used in the average of the interval between pulses for RPM and RPMW calculation
unsigned long interval = 0, intervalCount = 0, intervalSpd = 0, intervalCountSpd = 0, lastPulseTime = 0, lastPulseTimeSpd = 0, intervalAvg = 0, intervalSpdAvg = 0; // used for the RPM and SPD calculation
float nr = 0.0526657824;       // for 11" wheel (rear) // number for the rpmW to speed calculation. Formula: wheelDiameter(m)/2*0.10472*18/5 for km/h
// float nr = 0.047877984;    // for 10" wheel (front)
float rpmW = 0;              // used in speed calculations

/* ===== */
/* GEARS */
/* ===== */
bool currentstateGP, currentstateGM;                 // The current state of the gear+, gear-, and  sensors
bool prevstateGP = HIGH, prevstateGM = HIGH;        // The state of gear+, gear-, and lap sensors in previous scan
unsigned long lastGearPlus = 0, lastGearMinus = 0; // for used for activating the relays for changing gears
byte gearImpuls = 50;        // how long the gear change relay stays on (the time neded to actualy change the gear)
byte gearImpulsReturn = 10; // TESTING - for stopping the gear shift motor from turning too much when shifting gears. 
bool geaP = false, geaM = false;

/* ======= */
/* LAPTIME */
/* ======= */
unsigned long lastLap = 0, bestLap = 599990; // Variables for the last and best lap
unsigned long lapMillis = 0;                // How long has it been since the last time the lap has started
int bestLapReset = 0, bestLapLastReset = 0;// used to reset the stored best lap
byte sectors = 1;                         // the number of split/start/finish lines
byte line = 0;                           // the number of times you passed over the split/start/finish lines
bool bestLapFlag = false;               // used to know when one lap is finished to calculate the bestLap
bool startLap = false;                 // used to know when a lap was started
bool firstLap = true;                 // used to not record the seconds from starting the device untill the first magentic line is corossed - first las is started

/* ======= */
/* MPU6050 */
/* ======= */
byte x = 0;                          // used as a counter for the average of MPU readings
float rawX = 0, rawY = 0;           // the float where all the MPU readings are stored then divided by the number of entries (x);
int16_t acc_x, acc_y, acc_z;

/* =================== */
/* SERIAL TRANSMISSION */
/* =================== */
unsigned long lastTransmit = 0; // the last time the $RC2 string was transmitted via serial
byte transmitRate = 50;        // transmit the $RC2 string every 50 milliseconds
String data;                  // this is the string that is going to be sent to the phone

/* === */
/* GPS */
/* === */
NMEAGPS gps;
gps_fix fix;  // the structure that holds all the parsed pieces
/* see https://github.com/SlashDevin/NeoGPS/blob/master/extras/doc/Data%20Model.md for all fix members that can be accesed */
String GpsNmea1, GpsNmea2, GpsNmea1Buf, GpsNmea2Buf, GPScounter; // strings to hold the NMEA buffer and sentences
bool nmea = true;                                               // flag to know when to write to which NMEA sentence string

/* ==== */
/* OLED */
/* ==== */
U8G2_SSD1306_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R2, /* ss=*/ 4, /* dc=*/ 3, /* reset=*/ 2);
unsigned long lastUpdateOled = 0;
byte updateRateOled = 100; // update the display once every 100 milliseconds

/* === */
/* BPM */
/* === */
int UpperThreshold = 518, LowerThreshold = 490, reading = 0;
bool IgnoreReading = false, FirstPulseDetected = false;
unsigned long FirstPulseTime = 0, SecondPulseTime = 0, PulseInterval = 0;

/* ================= */
/* dataNRF STRUCTURE */
/* ================= */
struct transmission {
  float avgXx, avgYy, speed, BPM, lapTime;    // 4 bytes
  byte acceleration, brake, gear, SETUP = 0; // 1 byte
  short int rpm, str;                       // 2 bytes
}; transmission dataNRF;                   // 28 bytes structure

/* ============= */
/* BUTTON STATES */
/* ============= */
bool currentstateBLUE;
bool currentstateGREEN;
bool currentstateTALK;
bool prevstateBLUE = LOW;
bool prevstateGREEN = LOW;
bool prevstateTALK = LOW;

/* ============= */
/* BATTERY LEVEL */
/* ============= */
int lastMillisSec = 0;    // last time the battery level was taken
boolean startBat = true; // update the battery level only once when the Teensy starts. Every 5 seconds after that. 

/* =========== */
/* TEMPERATURE */
/* =========== */
Adafruit_MAX31855 thermocouple1(16);                 // initialize the MAX31855 Thermocouple 1
Adafruit_MAX31855 thermocouple2(17);                // initialize the MAX31855 Thermocouple 2
unsigned long lastTemp1Read = 0, lastTemp2Read = 0;// to know when the last temperature read was made for both thermocouples
double temp1, temp2;                              // variables to hold the temperatures
/* thermocouple1.readCelsius() // thermocouple2.readCelsius() */

/* ======= */
/* TESTING */
/* ======= */
bool go = true, goS = false, goG = false;
unsigned long lastGear = 0;
unsigned long startLoop, endLoop, showTime, startRPM, endRPM, startSPD, endSPD; // for calculating the time it takes for 1 loop of the program
/*==================================================================================================*/
/*===                                                                                            ===*/
/*==================================================================================================*/


void setup() {
  pinMode(2, OUTPUT);                              // Reset pin for the 2.42 inch Oled
  pinMode(3, OUTPUT);                             // DC pin Oled
  pinMode(4, OUTPUT);                            // CS Oled
  pinMode(5, OUTPUT);                           // DS shift register (Steering wheel Oled) DATA
  pinMode(6, OUTPUT);                          // ST-CP shift register (Steering wheel Oled) LATCH
  pinMode(9, INPUT);                          // Lap
  pinMode(10, OUTPUT);                       // SH-CP shift register (Steering wheel Oled) CLOCK
  pinMode(16, OUTPUT);                      // CS for Temperature sensor 1
  digitalWrite(16, HIGH);                  // do not read temperature now 
  pinMode(17, OUTPUT);                    // CS for Temperature sensor 2
  digitalWrite(17, HIGH);                // do not read temperature now
  pinMode(27, INPUT);                   // Button GREEN - Right (Steering wheel Oled)
  pinMode(28, INPUT);                  // Button Talk (Steering wheel Oled)
  pinMode(29, INPUT);                 // Button BLUE - Left (Steering wheel Oled)
  pinMode(30, OUTPUT);               // Gear relay -
  pinMode(31, OUTPUT);              // Transistor for Relay Ground
  pinMode(32, OUTPUT);             // Gear relay +
  pinMode(33, INPUT);             // RPM
  pinMode(36, INPUT);            // SPD
  pinMode(37, INPUT);           // Gear - button
  pinMode(38, INPUT);          // Gear + button
  //pinMode(A0, INPUT);       // Brk
  //pinMode(A1, INPUT);      // Relay Battery level
  //pinMode(A8, INPUT);     // Battery level
  //pinMode(A12, INPUT);   // Vol
  //pinMode(A15, INPUT);  // HeartRate
  //pinMode(A16, INPUT); // Steering angle
  //pinMode(A17, INPUT);// Acc

  Serial.begin(115200);              // initialize the USB Serial port
  Serial1.begin(115200);            // initialize the Serial port to transmit the Bluetooth data
  Serial8.begin(115200);           // initialize the Serial port to receive GPS data

  delay(50);
  SPI.begin();                   // initialize the SPI port

  u8g2.begin();                // initialize the Oled
  u8g2.setFlipMode(true);     // flip the image on the Oled

  Wire.begin();             // initialize the i2c port
  Wire.setClock(400000);   // set the i2c to 400 KHz

  leds = B00000000;      // turn  off all the leds on the steering wheel
  updateShiftRegister();// update the LEDs on the steering wheel. (turn  them off)

  Wire.beginTransmission(0x68);      // Start communicating with the MPU
  Wire.write(0x6B);                 // Start writing to this register (PWR_MGMT_1)
  Wire.write(0x00);                // Set register 0x6B to zero (wakes up the MPU)
  Wire.endTransmission();         // Terminate the connection
  delay(50);
  Wire.beginTransmission(0x68); // Start communicating with the MPU
  Wire.write(0x1C);            // Start writing to this register (ACCEL_CONFIG)
  Wire.write(0x00);           // Set register 0x1C to 0 (sets the sensitivity of the accelerometer to 2g)
  //Wire.write(0x01);        // Set register 0x1C to 1 (sets the sensitivity of the accelerometer to 4g)
  //Wire.write(0x10);       // Set register 0x1C to 2 (sets the sensitivity of the accelerometer to 8g)
  //Wire.write(0x11);      // Set register 0x1C to 3 (sets the sensitivity of the accelerometer to 16g)
  Wire.endTransmission(); // Terminate the connection

  AudioMemory(64);      // Give the Audio Library some memory to work with
  sgtl5000_1.enable(); // Enable the SGTL5000
  
  radio.begin();     // Initialize and configure the NRF24L01+ module
  delay(50);

  /* Attach interrupts for the RPM, SPEED and LAP sensors */
  attachInterrupt(digitalPinToInterrupt(33), &ignitionIsr, FALLING);
  attachInterrupt(digitalPinToInterrupt(36), &wheelRpm, FALLING);
  attachInterrupt(digitalPinToInterrupt(9), &lap, FALLING);
  

  //EEPROM.get(24, bestLap);// read the best lap from EEPROM to show on the Oled

  u8g2.clearBuffer();                // Clear the oled buffer
  u8g2.drawXBM(0, 0, 128, 64, tvg); // The TvG logo
  u8g2.sendBuffer();               // Send the TvG image to the screen
  delay(2000);
  u8g2.clearBuffer();            // Clear the Oled buffer so that the next data can be written in
  
  if (digitalReadFast(27)) {               // if the GREEN button is pressed while powering on
    radio.begin();                        // initialize the NRF module
    delay(50);
    radio.setPALevel(RF24_PA_MAX);      // set the NRF module in the maximum transmission/receiving power
    radio.setDataRate(RF24_250KBPS);   // set the lowest data rate (best for max distance)
    radio.setAutoAck(true);           // enable auto acknowledgement
    radio.setRetries(3, 5);          // set just 3 retries so time is not wasted on resends
    radio.enableAckPayload();       // enable the ack payload
    radio.stopListening();         // start transmitting data
    delay(50);
    radio.openWritingPipe(pipeaddress);      //open the pipe for transmitting data

    dataNRF.gear = 11;                     // tell the wireless device that the main boart is in SETUP mode
    sendData();                           // send the dataNRF.gear value
    sendData();                          // send the dataNRF.gear a second time just in case the first time failed
    u8g2.setFont(u8g2_font_ncenB18_tf); // 18 pixels high font
    u8g2.setCursor(4, 40);
    u8g2.print(F(">SETUP<"));
    u8g2.sendBuffer();
    u8g2.clearBuffer();
    delay(2000);

    dataNRF.gear = 12;// used to know what to display on the wireless device
    sendData();      // send the dataNRF.gear value
    sendData();     // send the dataNRF.gear a second time just in case the first time failed
    u8g2.sendBuffer();
    u8g2.clearBuffer();
    delay(3000);

    //ACCELERATION.update();                   // update the Acc analog value in the ResponsiveAnalogRead library
    //BRAKE.update();                         // update the Brk analog value in the ResponsiveAnalogRead library
    //EEPROM.put(0, ACCELERATION.getValue());// put the analog value in the EEPROM for released ACC
    //EEPROM.put(4, BRAKE.getValue());      // put the analog value in the EEPROM for released BRK
    delay(50);

    dataNRF.gear = 13;// used to know what to display on the wireless device
    sendData();      // send the dataNRF.gear value
    sendData();     // send the dataNRF.gear a second time just in case the first time failed
    u8g2.drawBox(0, 0, 130, 20);
    u8g2.setDrawColor(0);                   // Black font on white background
    u8g2.setFont(u8g2_font_ncenB18_tf);    // 18 pixels high font
    u8g2.setCursor(15, 19);
    u8g2.print(F(">Press<"));
    u8g2.setDrawColor(1);               // White font on black background
    u8g2.setCursor(12, 39);
    u8g2.setFont(u8g2_font_lubB14_tf);// 15 pixels high font
    u8g2.print(F("Acc & Brk"));
    u8g2.setCursor(30, 59);
    u8g2.print(F("pedals"));
    u8g2.sendBuffer();
    u8g2.clearBuffer();
    delay(3000);

    //ACCELERATION.update();                   // update the Acc analog value in the ResponsiveAnalogRead library
    //BRAKE.update();                         // update the Brk analog value in the ResponsiveAnalogRead library
    //EEPROM.put(8, ACCELERATION.getValue());// put the analog value in the EEPROM for pressed ACC
    //EEPROM.put(12, BRAKE.getValue());     // put the analog value in the EEPROM for pressed BRK
    delay(50);

    dataNRF.gear = 14;// used to know what to display on the wireless device
    sendData();      // send the dataNRF.gear value
    sendData();     // send the dataNRF.gear a second time just in case the first time failed
    u8g2.setFont(u8g2_font_ncenB18_tf); // 18 pixels high font
    u8g2.setCursor(15, 30);
    u8g2.print(F("Release"));
    u8g2.setCursor(25, 50);
    u8g2.print(F("pedals"));
    u8g2.sendBuffer();
    u8g2.clearBuffer();
    delay(2000);

    dataNRF.gear = 15;// used to know what to display on the wireless device
    sendData();      // send the dataNRF.gear value
    sendData();     // send the dataNRF.gear a second time just in case the first time failed
    u8g2.setFont(u8g2_font_ncenB18_tf); // 18 pixels high font
    u8g2.setCursor(10, 25);
    u8g2.print(F("Steering"));
    u8g2.setCursor(10, 50);
    u8g2.print(F("MAX LE"));
    u8g2.sendBuffer();
    u8g2.clearBuffer();
    delay(3000);

    //STEERING.update();                   // update the STR analog value in the ResponsiveAnalogRead library
    //EEPROM.put(16, STEERING.getValue());// put the analog value in the EEPROM for max left STR
    delay(50);

    dataNRF.gear = 16;// used to know what to display on the wireless device
    sendData();      // send the dataNRF.gear value
    sendData();     // send the dataNRF.gear a second time just in case the first time failed
    u8g2.setFont(u8g2_font_ncenB18_tf); // 18 pixels high font
    u8g2.setCursor(10, 25);
    u8g2.print(F("Steering"));
    u8g2.setCursor(11, 50);
    u8g2.print(F("MAX RI"));
    u8g2.sendBuffer();
    u8g2.clearBuffer();
    delay(3000);

    //STEERING.update();                   // update the STR analog value in the ResponsiveAnalogRead library
    //EEPROM.put(20, STEERING.getValue());// put the analog value in the EEPROM for max right STR
    delay(50);

    dataNRF.gear = 17;// used to know what to display on the wireless device
    sendData();      // send the dataNRF.gear value
    sendData();     // send the dataNRF.gear a second time just in case the first time failed
    u8g2.setCursor(8, 40);
    u8g2.print(F(">DONE<"));
    u8g2.sendBuffer();
    u8g2.clearBuffer();
    delay(2000);
    dataNRF.gear = 0; // tell the wireless device to enter normal mode
    sendData();      // send the dataNRF.gear value
    sendData();     // send the dataNRF.gear a second time just in case the first time failed

  } else { /* end of TvG KDL setup */
    /* read the stored values for the position of the pedals */
    /*EEPROM.get(0, zeroAccValue);
      EEPROM.get(4, zeroBrkValue);
      EEPROM.get(8, fullAccValue);
      EEPROM.get(12, fullBrkValue);
      EEPROM.get(16, fullLeftValue);
      EEPROM.get(20, fullRightValue);
    */
  }
  zeroAccValue = 517;   //TESTING
  fullAccValue = 785;  //TESTING
  zeroBrkValue = 527; //TESTING
  fullBrkValue = 785;//TESTING
} /* end of Setup function */

void loop() {
  startLoop = micros(); //TESTING
  currentstateTALK = digitalRead(28); // see if the Talk Button is pressed
  /* ========================================== */
  /* CODE for AUDIO TRANSMITTING and  RECEIVING */
  /* ========================================== */
  if (ackMsg == 1) {                                // if the Receiver transmits Audio Data
    if (setupR) {                                  // setup the NRF to receive (just once)
      detachInterrupt(digitalPinToInterrupt(33)); // disable the interupt for RPM
      detachInterrupt(digitalPinToInterrupt(36));// disable the interupt for SPD
      leds = B00000000;                         // turn off the LEDs on the steering wheel
      updateShiftRegister();                   // turn off the LEDs on the steering wheel
      u8g2.clearBuffer();
      u8g2.setFont(u8g2_font_crox4hb_tf);    // 14 pixels high font
      u8g2.setCursor(60, 40);
      u8g2.print("R");                     // display R to know the device is receiving Audio
      u8g2.sendBuffer();                  // send the buffer to the display
      u8g2.clearBuffer();                // clear the buffer
      /* code for setting up the NRF for audio receiving */
      radio.begin();                   // reinitialize the NRF module so that the settings reset to default
      delay(50);
      radio.setPayloadSize(32);      // this is the maximum payload size for a packet transfer of nrf24
      radio.setAutoAck(false);      // no Ack
      radio.disableCRC();          // no CRC
      radio.setPALevel(RF24_PA_MAX);          // set the NRF module in the maximum transmission/receiving power
      radio.setDataRate(RF24_2MBPS);         // set the maximum data rate
      radio.openReadingPipe(0, pipeaddress);// open the pipe for receiving Audio
      delay(50);
      radio.startListening();
      /* done setting up the NRF for audio receiving */
      /* audio settings */
      VOLUME.update(); // update the reading fot the volume analog read pin
      sgtl5000_1.volume(VOLUME.getValue() / 1023.0/*0.4*/); // set the headphone volume from 0.0 to 1.0
      setupR = false;        // so that the setup is only done once
      setupT = true;        // perform the transmit setup next time you press the TRANSMIT the button
      setupN = true;       // perform the NORMAL setup after transmitting/receiving
    }
    /* code for the actual audio receiving */
    if (radio.available()) {
      error = 0;                                // reset the error count so the program knows when too many errors are present
      if ((bufcount >= 0) && (bufcount < 8)) { // get 8 packets a 32 byte audio data
        radio.read(&bufr[bufcount * 32], 32); // get 32 byte audio packet data
        if (bufcount == 7) {                 // test if all 8 packets received
          bufcount = 0;                     // reset the packets received count
          int16_t *p = queue2.getBuffer();
          memcpy(p, &bufr[0], (audiobuffersize * 2));
          queue2.playBuffer();
        } else {
          bufcount++;
        }
      }
    }/* end of code for the actual audio receiving */
     /* code for exiting the Audio Receiver mode */
    if (!radio.available()) {           // if the radio signal is not available
      if (millis() - lastError > 50) { // every 50 milliseconds
        error += 1;                   // increment the number of errors received
        lastError = millis();        // reset the last time an error was logged
      }
      if (error > maxErrors) {     // if the number of errors is bigger than maxErrors it means that probably the receiving of audio stopped
        ackMsg = 0;               // exit receiving audio mode
      }/* end of code for exiting the Audio Receiver mode */
    }
    /* end of code for Receiving Audio */
  } else if (prevstateTALK != currentstateTALK && currentstateTALK == HIGH) { // if the Talk button is pressed and data should be sent to a wireless device
    if (setupT) {                  // setup the NRF to transmit - just once
      detachInterrupt(digitalPinToInterrupt(33)); // disable the interupt for RPM
      detachInterrupt(digitalPinToInterrupt(36));// disable the interupt for SPD
      leds = B00000000;         // turn off the LEDs on the steering wheel
      updateShiftRegister();   // turn off the LEDs on the steering wheel
      u8g2.clearBuffer();     // clear the buffer
      u8g2.setFont(u8g2_font_crox4hb_tf); // 14 pixel high font
      u8g2.setCursor(60, 40);
      u8g2.print("T");     // display T to know the device is receiving Audio
      u8g2.sendBuffer();  // send the buffer to the display
      dataNRF.SETUP = 1; // tell the Receiver that it is time to receive audio data
      sendData();       // send the data containing the SETUP instruction to the receiver using the current NRF settings
      /* setup the NRF for audio transmission */
      radio.begin();  // reinitialize the NRF module so that the settings reset to default
      delay(50);
      radio.setPayloadSize(32); //this is the maximum payload size for a packet transfer of nrf24
      radio.setAutoAck(false); // no Ack
      radio.disableCRC();     // no CRC
      radio.setPALevel(RF24_PA_MAX);       // set the NRF module in the maximum transmission/receiving power
      radio.setDataRate(RF24_2MBPS);      // set the maximum data rate
      radio.openWritingPipe(pipeaddress);// open the pipe for transmitting Audio
      delay(50);
      radio.stopListening();           // start transmitting Audio data
      /* done setting up the NRF for audio receiving */
      /* audio settings */
      sgtl5000_1.inputSelect(AUDIO_INPUT_MIC); //which input to use (MICROPHONE)
      sgtl5000_1.adcHighPassFilterDisable();
      queue1.begin();  // initialize the queue in which to put the Audio data
      setupT = false; // perform the transmit setup only once
      setupR = true; // perform the receive setup next time the conditions are met
      setupN = true;// perform the normal setup next time the conditions are met
    }
    /* code for the actual audio transmitting */
    Serial.println("BUTON");
    if (queue1.available()) {
      memcpy(bufr, queue1.readBuffer(), audiobuffersize * 2); // copy 256 byte audio data from audio library buffer to bufr
      queue1.freeBuffer();
      for (int i = 0; i < 8; i++) { // send audio data in 8 packages a 32 byte to the FIFO buffers
        radio.writeFast(&bufr[i * 32], 32);
      }
    }
    /* end of code for the actual audio transmitting */
  } else {
    if (setupN) {                              // if the TALK button is not pressed and the Receiver is not transmitting Audio
      attachInterrupt(digitalPinToInterrupt(33), &ignitionIsr, FALLING);// enalbe the interrupt for RPM
      attachInterrupt(digitalPinToInterrupt(36), &wheelRpm, FALLING);  // enalbe the interrupt for SPD
      dataNRF.SETUP = 0;                    // tell the Receiver that no more audio data will be transmitted
      error = 0;                           // reset the number of Audio receive errors
      radio.begin();                      // reinitialize the NRF module so that the settings reset to default
      delay(50);
      radio.setPALevel(RF24_PA_MAX);    // set the NRF module in the maximum transmission/receiving power
      radio.setDataRate(RF24_250KBPS); // set the lowest data rate (best for max distance)
      radio.setAutoAck(true);         // enable auto acknowledgement
      radio.setRetries(3, 5);        // set just 3 retries so time is not wasted on resends
      radio.enableAckPayload();     // enable the ack payload
      radio.stopListening();       // start transmitting data
      delay(50);
      radio.openWritingPipe(pipeaddress); //open the pipe for transmitting data
      setupN = false;           // perform the normal operation setup only once
      setupT = true;           // perform the transmit setup next time the conditions are met
      setupR = true;          // perform the receive setup next time the conditions are met
    }
    prevstateTALK = currentstateTALK;

    /* ==================================================================================== */
    /* ==================================================================================== */
    /*                             Beginning of the normal LOOP                             */
    /* ==================================================================================== */
    /* ==================================================================================== */


    /* ======================== */
    /* CALCULATE the HEART RATE */
    /* ======================== */
    bpm();  
    
    
    /* ====================== */
    /* CALCULATE the G-forces */
    /* ====================== */
    gforce(); 


    /* ========================= */
    /*   PHARSE THE GPS DATA     */
    /* ========================= */
    gps_data();


    /* ================================ */
    /* CALCULATE the ACC and BRK values */
    /* ================================ */
    accBrk();
    
        
    /* ======================= */
    /* READ the STEERING ANGLE */
    /* ======================= */
    steering();

    
    /* ===================== */
    /* READ the TEMPERATURES */
    /* ===================== */
      
    if (millis() - lastTemp1Read > 2000){ // read the temperature on thermocouple 1 every 2 seconds
      temp1 = thermocouple1.readCelsius();
      lastTemp1Read = millis();
    }
    
    if (millis() - lastTemp2Read > 3000){ // read the temperature on thermocouple 2 every 3 seconds
      temp2 = thermocouple2.readCelsius();
      lastTemp2Read = millis();
    }
    
    
    /* ====================== */
    /* CALCULATE THE BEST LAP */
    /* ====================== */
    
    if (bestLapFlag) {                          // if a lap was finished and the best lap has not been calculated yet
      if (lastLap > 1 && lastLap <= bestLap) { // if the last lap wan not 1 second and was equal or faster than the best lap
        bestLap = lastLap;                    // store the last lap as beeing the best  lap
        //EEPROM.put(24, bestLap);           // store the best lap in EEPROM for later use
      }
      bestLapFlag = false;                 // reset the flag that was placed when a lap was finished so we can calculate the next best lap
    }


    /* ========================= */
    /* CODE FOR THE BLUE BUTTON  */
    /* ========================= */
    /*currentstateBLUE = digitalReadFast(29);// read the current button state (pressed or not / HIGH or LOW)
    if (prevstateBLUE != currentstateBLUE && currentstateBLUE == HIGH ) {// if the GREEN button is pressed
      if (startLap = false){
        
        startLap = true;
      } else if (startLap = true){
        bestLapFlag = true;
      }
    }
    prevstateBLUE = currentstateBLUE;
    */


    /* ========================= */
    /* CODE FOR THE GREEN BUTTON */
    /* ========================= */
    currentstateGREEN = digitalReadFast(27);// read the current button state (pressed or not / HIGH or LOW)
    if (prevstateGREEN != currentstateGREEN && currentstateGREEN == HIGH ) {// if the GREEN button is pressed
      if (wireless) {
        wireless = LOW;                // there is no wireless device to transmit/receive to/from
      } else {
        wireless = HIGH;             // there is a wireless device to transmit/receive to/from
      }
    }
    prevstateGREEN = currentstateGREEN;
    
    if (digitalReadFast(27)){
      bestLapReset = millis() - bestLapLastReset; // increment the bestLapReset value while the GREEN button is pressed
      if (bestLapReset > 5000){   // if the GREEN button is pressed for more than 5 seconds
        bestLap = 599990;        // reset the best lap to 9:59.99
      //EEPROM.put(24, bestLap);// reset the best Lap stored in EEPROM
      }
    } else {
      bestLapReset = 0;            // reset the bestLapReset value when the GREEN button is not pressed
      bestLapLastReset = millis();// reset the time the button was not pressed
    }


    /* ==================== */
    /* CALCULATIONS FOR RPM */
    /* ==================== */
    /* calculate the average interval between sparks every 10 sparks to calculate the rpm */
    if (rpmCount >= 10) {
      intervalAvg = intervalCount / rpmCount; /* =====!!!!! WARNING !!!!!===== probable error in calculation because the time is calculated every 10 impulses. It should be 1.*/
      dataNRF.rpm = 60000000 / (intervalAvg * 1); // 1 = number of impulses per rotation
      intervalCount = 0;
      rpmCount = 0;
    }
    /*
    if ((micros() - lastPulseTime) > 500000) {        // if more than 0.5 sec has passed since the last engine revolution the engine is probably stopped so rpm=0
      dataNRF.rpm = 0;
    }*/


    /* ====================== */
    /* CALCULATIONS FOR SPEED */
    /* ====================== */
    /* calculate the average interval between wheel rotations every 5 wheel rotations to calculate the speed */
    if (spdCount >= 5) {
      intervalSpdAvg = intervalCountSpd / spdCount;
      rpmW = 60000000 / intervalSpdAvg;
      dataNRF.speed = nr * rpmW;
      intervalCountSpd = 0;
      spdCount = 0;
    }
    /*
    if ((micros() - lastPulseTimeSpd) > 1500000) { // if more than 1.5 sec has passed since the last wheel revolution reset the speed to 0
      dataNRF.speed = 0;
    rpmW = 0;
    }*/


    /* ======================== */
    /* CALCULATIONS FOR LAPTIME */
    /* ======================== */
    if (startLap){ // calculate the current laptime only if you started a lap - passed over at least one magnetic strip
      dataNRF.lapTime = millis() - lapMillis; // update every loop how many milliseconds have passed in this lap - for live LapTime display
    }


    /* ======================= */
    /*          GEAR +         */
    /* ======================= */
    currentstateGP = digitalReadFast(38); // Read hall sensor state (Gear +)
    if (prevstateGP != currentstateGP) { // If there is change in input
      if (currentstateGP == LOW ) {     // If input changes from HIGH to LOW
        dataNRF.gear += 1;             // Increment the Gear number
        if (dataNRF.gear > 6) {       // if you are in 6th gear and trigger the gear+ sensor the display remains 6
          dataNRF.gear = 6;
        }
        digitalWriteFast(32, LOW); // activate the relay to change the gear up
        lastGearPlus = millis();
        geaP = true;
      }
      prevstateGP = currentstateGP;
    }
    if (millis() - lastGearPlus >= gearImpuls) {
      digitalWriteFast(32, HIGH); // after <gearImpuls> milliseconds turn the Gear+ relay off
      delay(10);
      if (geaP){
        digitalWriteFast(30, LOW);
        delay(20);
        digitalWriteFast(30, HIGH);
        geaP = false;
      }
    }



    /* ======================= */
    /*          GEAR -         */
    /* ======================= */
    currentstateGM = digitalReadFast(37);  // Read hall sensor state (Gear -)
    if (prevstateGM != currentstateGM) {  // If there is change in input
      if (currentstateGM == LOW ) {      // If input only changes from HIGH to LOW
        dataNRF.gear -= 1;              // Decrement the Gear number
        if (dataNRF.gear > 10 || dataNRF.gear == 0) {  // if you are in 1st gear and trigger the gear- sensor the display remains 1
          dataNRF.gear = 1;
        }
        digitalWriteFast(30, LOW); // activate the relay to change the gear down
        lastGearMinus = millis();
        geaM = true;
      }
      prevstateGM = currentstateGM;
    }
    if (millis() - lastGearMinus >= gearImpuls) {
      digitalWriteFast(30, HIGH); // after <gearImpuls> milliseconds turn the Gear- relay off
      delay(10);
      if (geaM){
      digitalWriteFast(32, LOW);
      delay(20);
      digitalWriteFast(32, HIGH);
      geaM = false;
      }
    }
    /*
    if (dataNRF.rpm == 0 && dataNRF.speed == 0) { // if the engine is stopped and you are not moving, probably the kart is stopped so gear = 0
      dataNRF.gear = 0;
    }
    */


    /* ================================= */
    /*    UPDATE THE DATA ON THE OLED    */
    /* ================================= */
    if (millis() - lastUpdateOled >= updateRateOled) {
      oled();
      lastUpdateOled = millis();
    }


    /* ================================== */
    /* DATA TRANSMIT TO BLUETOOTH AND NRF */
    /* ================================== */
    if (millis() - lastTransmit >= transmitRate) {
      avgGforce();                      // calculate the average of the g-forces
      constructString();               // construct the string needed for RaceChrono
      if (wireless) {                 // if data should be sent to a wireless device
        sendData();                  // transmit the data to the wireless device using the NRF module
      }
      Serial1.println(data);       // print the data to serial (to the Bluetooth device)
      Serial1.print(GpsNmea1);    // print the first NMEA sentence to serial
      Serial1.print(GpsNmea2);   // print the second NMEA sentence to serial
      //testData();
      rpmLights();             // calculate how many leds to turn on the steering wheel based on the RPM
      lastTransmit = millis();// reset the time when the last $RC2 string was transmitted
      /*
      if (count == 65535) {  // Needed only when not using the GPS 
        count = 0;          // reset the counter variable when it reaches 65535 (maximum an int can hold)
      }
      else {
        count += 1;      // increment the count variable
      }*/
    }
  }
}

/* ==================================================================================== */
/* ==================================================================================== */
/*                             End of the normal LOOP                                   */
/* ==================================================================================== */
/* ==================================================================================== */

void testData() { // TESTING
  if (dataNRF.rpm <= 1000) {go = true;}
  if (dataNRF.rpm >= 12500) {go = false;}
  if (go) {dataNRF.rpm += 900;}
  if (!go) {dataNRF.rpm -= 1000;}
  
  if (dataNRF.speed <= 3.2) {goS = true;}
  if (dataNRF.speed >= 141.5) {goS = false;}
  if (goS) {dataNRF.speed += 3.2;}
  if (!goS) {dataNRF.speed -= 3.2;}

  if (millis() - lastGear >= 1000) {
    if (dataNRF.gear <= 1) {goG = true;}
    if (dataNRF.gear >= 6) {goG = false;}
    if (goG) {dataNRF.gear += 1;}
    if (!goG) {dataNRF.gear -= 1;}
    lastGear = millis();
  }

  dataNRF.BPM += 1;
  if (dataNRF.BPM >= 180) {
    dataNRF.BPM = 1;
  }
}



/* ========================================================================================= */
/* SEND DATA WITH NRF24L01+ */
/* ======================== */
void sendData() {
  if (radio.write(&dataNRF, sizeof(dataNRF))) {
    if (radio.isAckPayloadAvailable()) {   // if the Receiver transmitted the ackMsg
      radio.read(&ackMsg, sizeof(ackMsg));// read the ackMsg
      NRFfail = 0;
    } else {
      NRFfail += 1;
    }
    /* display the WiFi sign on the top right corner */
    if (NRFfail < NRFfailRate) { // if there are no more that <NRFfailRate> failed ack receives, display the WiFi sign
      u8g2.drawLine(119, 4, 121, 2);
      u8g2.drawLine(121, 2, 125, 2);
      u8g2.drawLine(125, 2, 127, 4);
      u8g2.drawLine(121, 5, 122, 4);
      u8g2.drawLine(122, 4, 124, 4);
      u8g2.drawLine(124, 4, 125, 5);
      u8g2.drawPixel(123, 6); // the pixel at the bottom of the signal indicator
    }
  }
}



/* ========================================================================================= */
/* OLED DISPLAY */
/* ============ */
void oled() {

  /* Values for the analogRead(A1) depending on voltage
     5V - 240
    6.6V - 320 minimum for operating the gear shift relays
     9V - 450
    12V - 620
  */
  /* update the battery level once when the Teensy starts */
  if (startBat == true) {
    BATTERY.update();      // update the value read from pin A10
    BATTERYRELAY.update();// update the analog read value for relay battery
    startBat = false;    // reset the flag so the battery level is updated only once every 5 seconds (below)
  }
  
  if (millis() - lastMillisSec > 5000) { // update the battery level indicator every 5 seconds
    BATTERY.update();       // update the analog read value for battery
    BATTERYRELAY.update(); // update the analog read value for relay battery
    lastMillisSec = millis();
  } 
  
  /* battery full/empty frames */
  if (BATTERY.getValue() > 100){ // only show battery level if battery plugged in
    u8g2.drawBox(/*x*/(map (BATTERY.getValue(),      240, 620, 22,  3)), /*y*/ 2, /*length*/ (/*remaining pixels outside the battery frame*/ 22 - (map (BATTERY.getValue(),      240, 620, 22,  3))), /*height*/ 4); // used to draw the rectangle that fills the battery :: battery full in 20 minutes @ USB
  }
  if (BATTERYRELAY.getValue() > 100){ // only show battery level if battery plugged in
    u8g2.drawBox(/*x*/(map (BATTERYRELAY.getValue(), 240, 620, 56, 37)), /*y*/ 2, /*length*/ (/*remaining pixels outside the battery frame*/ 56 - (map (BATTERYRELAY.getValue(), 240, 620, 56, 37))), /*height*/ 4); // used to draw the rectangle that fills the battery for Relay board
  }
  /*MAIN BATTERY LEVEL INDICATOR*/
  u8g2.drawFrame(2, 1, 21, 6);                  // empty battery frame
  u8g2.drawVLine(1, 2, 4);                     // the tip of the battery frame
  if (BATTERYRELAY.getValue() < 250) {
    u8g2.setFont (u8g2_font_courR08_tf);     // 6 pixels high font
    u8g2.drawStr(58, 6, "X");               // display text indicating that gears will not change (voltage under 5V at the gear change relays)
  } else {
    u8g2.setCursor(58, 6);                // FOR TESTING
    u8g2.print(BATTERYRELAY.getValue());// FOR TESTING
  }/*MAIN BATTERY LEVEL INDICATOR*/

  /*RELAY BATTERY LEVEL INDICATOR*/
  u8g2.drawFrame(36, 1, 21, 6);               // empty relay battery frame
  u8g2.drawVLine(36, 2, 4);                  // the tip of the relay battery frame
  if (BATTERY.getValue() < 250) {
    u8g2.setFont (u8g2_font_courR08_tf);   // 6 pixels high font
    u8g2.drawStr(25, 7, "X");             // display text indicating low main PCB battery
  } else {
    u8g2.setFont (u8g2_font_courR08_tf);// FOR TESTING 6 pixels high font
    u8g2.setCursor(25, 7);             // FOR TESTING
    BATTERY.update();                 // update the analog read value for battery
    u8g2.print(BATTERY.getValue());  // FOR TENTING
  }/*RELAY BATTERY LEVEL INDICATOR*/

  /* if there is no wireless device to transmit/receive to/from, display an X instead of the Wifi signal */
  if (!wireless) {
    u8g2.setFont (u8g2_font_courR08_tf);// 6 pixels high font
    u8g2.drawStr(122, 6, "X");         // display the X that indicates there is no wireless device
  }

  /* ----- */
  /* FRAME */
  /* ----- */
  /* draw the horizontal and vertical lines on the display */
  u8g2.drawHLine(0, 47, 127);
  u8g2.drawVLine(93, 0,  42);

  /* ------- */
  /* LAPTIME */
  /* ------- */
  /*CURRENT LAP*/
  u8g2.setFont(u8g2_font_crox4hb_tf);            // 14 pixels high font
  u8g2.setCursor(0, 25);
  u8g2.print("C ");                            // display the C as in CURRENT lap
  u8g2.print((int)dataNRF.lapTime / 60000);       // display the minutes
  u8g2.setCursor(32, 25);
  u8g2.print(":");                         // display the ":" between the minutes and seconds
  if (((int) dataNRF.lapTime % 60000) < 10000) {// if less than 10 seconds should be displayed
    u8g2.setCursor(39, 25);
    u8g2.print("0");                    //add a "0" in front of the seconds so it shows "X:0X.XX"
    u8g2.setCursor(50, 25);
    u8g2.print((int)dataNRF.lapTime % 60000 / 1000.0);   // display the seconds and milliseconds. (Milliseconds recorded divided by 1000 milliseconds/second)
  } 
  if (((int)dataNRF.lapTime % 60000) >= 10000){  // if there are more than 10 seconds to be displayed
    u8g2.setCursor(39, 25);
    u8g2.print((int)dataNRF.lapTime % 60000 / 1000.0);// display the seconds and milliseconds. (Milliseconds recorded divided by 1000 milliseconds/second)

  }
  /*LAST LAP*/
  u8g2.setCursor(0, 43);
  u8g2.print("L ");                    // display the L as in LAST lap
  u8g2.setCursor(20, 43);
  u8g2.print(lastLap / 60000);       // display the full minutes
  u8g2.setCursor(32, 43);
  u8g2.print(":");                 // diaplay the ":" between the minutes and seconds
  if ((lastLap % 60000) < 10000) {// if less than 10 seconds should be displayed
    u8g2.setCursor(39, 43);
    u8g2.print("0");            //add a "0" in front of the seconds so it shows "0x"
    u8g2.setCursor(50, 43);
    u8g2.print(lastLap % 60000 / 1000.0);  // display the seconds and milliseconds. (Milliseconds recorded divided by 1000 milliseconds/second)
  } else {
    u8g2.setCursor(39, 43);
    u8g2.print(lastLap % 60000 / 1000.0);// display the seconds and milliseconds. (Milliseconds recorded divided by 1000 milliseconds/second)
  }
  /*BEST LAP*/
  u8g2.setCursor(0, 64);
  u8g2.print("B ");                      // display the B as in BEST lap
  u8g2.setCursor(20, 64);
  u8g2.print(bestLap / 60000);         // display the full minutes
  u8g2.setCursor(32, 64);
  u8g2.print(":");                   // diaplay the ":" between the minutes and seconds
  if ((bestLap % 60000) < 10000.0) {// if less than 10 seconds should be displayed
    u8g2.setCursor(39, 64);
    u8g2.print("0");              //add a "0" in front of the seconds so it shows "0x"
    u8g2.setCursor(50, 64);
    u8g2.print(bestLap % 60000 / 1000.0);  // display the seconds and milliseconds. (Milliseconds recorded divided by 1000 milliseconds/second)
  } else {
    u8g2.setCursor(39, 64);
    u8g2.print(bestLap % 60000 / 1000.0);// display the seconds and milliseconds. (Milliseconds recorded divided by 1000 milliseconds/second)
  }

  
  /*TEMPERATURE*/
  u8g2.setFont (u8g2_font_courR08_tf);// 6 pixels high font
  u8g2.setCursor(95, 51);
  u8g2.print(temp1, 1);
  u8g2.setCursor(95, 64);
  u8g2.print(temp2, 1);



  /* ----- */
  /* SPEED */
  /* ----- */
  //u8g2.setFont(u8g2_font_crox4hb_tf);// 14 pixels high font
  /*if (dataNRF.speed < 10.0) {
    u8g2.setCursor(37, 60);        //move the start point of the text based on the number of digits displayed so that it gives the impression that the text is right aligned
    u8g2.print(dataNRF.speed, 1);
    }
    if (dataNRF.speed >= 10.0 && dataNRF.speed <= 99.9) {
    u8g2.setCursor(26, 60);    //move the start point of the text based on the number of digits displayed so that it gives the impression that the text is right aligned
    u8g2.print(dataNRF.speed, 1);
    }
    if (dataNRF.speed > 99.9) {
    u8g2.setCursor(15, 60); //move the start point of the text based on the number of digits displayed so that it gives the impression that the text is right aligned
    u8g2.print(dataNRF.speed, 1);
    }

    u8g2.setCursor(80, 60);
    u8g2.print(F("Km/h"));
  */

  /* ---- */
  /* GEAR */
  /* ---- */
  u8g2.setFont(u8g2_font_fub35_tn);// 35 pixels high font
  u8g2.setCursor(95, 43);
  u8g2.print(dataNRF.gear);      // display the current gear
  u8g2.sendBuffer();            // send the buffer to the display
  u8g2.clearBuffer();          // clear the buffer
}



/* ========================================================================================= */
/* CALCULATE NMEA CHECKSUM */
/* ======================= */
char checkSum(String chars) {
  char check = 0;
  for (int c = 0; c < chars.length(); c++) {
    check = char(check ^ chars.charAt(c));
  }
  return check;
}



/* ========================================================================================= */
/* CALCULATE LAPTIME */
/* ================= */
void lap() {
  if (sectors == 1) {                     // if the track has only 1 magnetic strip/sector
    if (firstLap){                       // if this is the begining of the first lap
      lapMillis = millis();             // reset the time recorded from when the device was started
      firstLap = false;                // do this only once when the first lap is started
    } else {
      lastLap = millis() - lapMillis;// store the last lap
      lapMillis = millis();         // reset the last time the sensor was triggered
    }
    startLap = true;              // tell the microcontroller that a lap was started. Start recording time.
    bestLapFlag = true;          // used to know when a lap is finished to calculate the best lap and not waste time in the interrupt routine
  }
  if (sectors > 1) {                        // if the track has more than 1 magnetic strips/sectors 
    line += 1;                             // A split line was crossed
    if (firstLap){                        // if this is the begining of the first lap
      lapMillis = millis();              // reset the time recorded from when the device was started
      firstLap = false;                 // do this only once when the first lap is started
    } 
    if (line == (sectors + 1)) {      // If enough split lines have been crossed
      lastLap = millis() - lapMillis;// store the last lap
      lapMillis = millis();         // Reset the last time that the start/finish line was crossed
      line = 0;                    // Reset the split lines
      startLap = true;            // tell the microcontroller that a lap was started. Start recording time.
      bestLapFlag = true;        // used to know when a lap is finished to calculate the best lap and not waste time in the interrupt routine
    }
  }
}



/* ========================================================================================= */
/* CALCULATE RPM */
/* ============= */
void ignitionIsr() {
  startRPM = micros(); //TESTING
  interval = micros() - lastPulseTime;
  lastPulseTime = micros();
  rpmCount += 1;
  intervalCount += interval;
  endRPM = micros(); //TESTING
}



/* ========================================================================================= */
/* CALCULATE SPEED */
/* =============== */
void wheelRpm() {
  startSPD = micros(); //TESTING
  intervalSpd = micros() - lastPulseTimeSpd;
  lastPulseTimeSpd = micros();
  spdCount += 1;
  intervalCountSpd += intervalSpd;
  endSPD = micros(); //TESTING
}



/* ========================================================================================= */
/* CONSTRUCT the $RC2 STRING */
/* ========================= */
void constructString() {
  String buffer = "RC2," + GPScounter + ",," + String (dataNRF.avgXx, 1) + ',' + String (dataNRF.avgYy, 1) + ",," + dataNRF.rpm + ',' + dataNRF.gear + ',' +  dataNRF.brake + ',' + dataNRF.acceleration + ',' + String(lastLap / 1000.0) + ',' + String(dataNRF.speed, 1) + ',' + String(dataNRF.BPM) + ',' + dataNRF.str + ",,"; // construct the $RC2 string using the variables and taking into account the format RaceChrono asks for
//String buffer = "RC2,," + String(count) + ',' + String (dataNRF.avgXx, 1) + ',' + String (dataNRF.avgYy, 1) + ",," + dataNRF.rpm + ',' + dataNRF.gear + ',' +  dataNRF.brake + ',' + dataNRF.acceleration + ',' + String(lastLap / 1000.0) + ',' + String(dataNRF.speed, 1) + ',' + String(dataNRF.BPM) + ',' + dataNRF.str + ",,"; // construct the $RC2 string using the variables and taking into account the format RaceChrono asks for
  String checksum = String (checkSum(buffer), HEX);// calculate the checksum of the RC2 string
  char crc = checkSum (buffer);
  data = "$" + buffer + "*";
  if (crc < 16) {                               // if the HEX value of the checksum is only one character, add "0" in front
    data = data + "0" + checksum;
  } else {
    data = data + checksum;
  }
}



/* ========================================================================================= */
/* RPM LEDs */
/* ======== */
void rpmLights() {
  if (dataNRF.rpm < 5000                        ) {leds = B00000000;}
  if (dataNRF.rpm > 5000  && dataNRF.rpm < 6000 ) {leds = B10000000;}
  if (dataNRF.rpm > 6000  && dataNRF.rpm < 7000 ) {leds = B11000000;}
  if (dataNRF.rpm > 7000  && dataNRF.rpm < 8000 ) {leds = B11100000;}
  if (dataNRF.rpm > 8000  && dataNRF.rpm < 9000 ) {leds = B01110000;}
  if (dataNRF.rpm > 9000  && dataNRF.rpm < 10000) {leds = B00111000;}
  if (dataNRF.rpm > 10000 && dataNRF.rpm < 11000) {leds = B00011100;}
  if (dataNRF.rpm > 11000 && dataNRF.rpm < 12000) {leds = B00001110;}
  if (dataNRF.rpm > 12000                       ) {leds = B00000111;}
  updateShiftRegister(); //update the shift register to light up the corresponding LEDs
}



/* ========================================================================================= */
/* UPDATE SHIFT REGISTER */
/* ===================== */
void updateShiftRegister() {
  digitalWriteFast(6, LOW);        // (latchPin(ST), LOW);
  shiftOut(5, 10, LSBFIRST, leds);// (dataPin(DS), clockPin(SH), LSBFIRST, leds)
  digitalWriteFast(6, HIGH);     // (latchPin(ST), HIGH);
}



/* ========================================================================================= */
/* CALCULATE THE BPM */
/* ================= */
void bpm() {
  reading = analogRead(A15);
  if (reading > UpperThreshold && IgnoreReading == false) { // Heart beat leading edge detected
    if (FirstPulseDetected == false) {
      FirstPulseTime = millis();
      FirstPulseDetected = true;
    } else {
      SecondPulseTime = millis();
      PulseInterval = SecondPulseTime - FirstPulseTime;
      FirstPulseTime = SecondPulseTime;
    }
    IgnoreReading = true;
  }
  if (reading < LowerThreshold && IgnoreReading == true) { // Heart beat trailing edge detected
    IgnoreReading = false;
  }
  dataNRF.BPM = round((1 / PulseInterval) * 60 * 1000);
}


/* ========================================================================================= */
/* PHARSE THE GPS DATA */
/* =================== */
void gps_data(){
  while (Serial8.available()) {// if receiving data from GPS
    char c = Serial8.read();
    if (c == '\n') {         // if the received character is the endline (\n or \r)
      if (nmea) {           // if the characters received have been appended to nmea string
        nmea = false;      // turn off the nmea flag so that the next characters are stored in the nmea2 string
      }
      if (!nmea) {       // if the characters received have been appended to nmea2 string
        nmea = true;    // turn off the nmea2 flag so that the next characters are stored in the nmea string
      }
    }

    if (nmea)  {
      GpsNmea1Buf += c; // add characters received in Serial7 to the NMEA 1 buffer
    }
    if (!nmea) {
      GpsNmea2Buf += c; // add characters received in Serial7 to the NMEA 2 buffer
    }

    gps.handle(c);  // parse the GPS data in the NeoGPS library

    // Did 'handle' finish making a new fix structure?
    if (gps.available() && (c == 0x0A)) { // wait for last LF char
      fix = gps.read();          // yes, a fix structure is ready (10Hz)
      GpsNmea1 = GpsNmea1Buf;   // put the contents of the nmea buffer1 in the GPSNmea1 string that will be printed to serial
      GpsNmea1Buf = "";        // empty the NMEA 1 buffer
      GpsNmea2 = GpsNmea2Buf; // put the contents of the nmea buffer2 in the GPSNmea2 string that will be printed to serial
      GpsNmea2Buf = "";      // empty the NMEA 2 buffer
      GPScounter = fix.dateTime.hours + fix.dateTime.minutes + fix.dateTime.seconds + '.' + fix.dateTime_cs;
    }
  }
}

   
/* ========================================================================================= */
/* CALCULATE ACC and BRK VALUES */
/* ============================ */
void accBrk(){
  ACCELERATION.update();                // update the ACC analog value to be smoothed out
  BRAKE.update();                      // update the BRK analog value to be smoothed out
  accValue = ACCELERATION.getValue(); // used instead of the analogRead() function. returns much more stable readings.
  brkValue = BRAKE.getValue();       // used instead of the analogRead() function. returns much more stable readings.
  dataNRF.acceleration = ((accValue - zeroAccValue) * 100L) / (fullAccValue - zeroAccValue); //calculates the percentage the acc pedal is pressed
  dataNRF.brake = ((brkValue - zeroBrkValue) * 100L) / (fullBrkValue - zeroBrkValue);       //calculates the percentage the brk pedal is pressed
}


/* ========================================================================================= */
/* READ the STEERING ANGLE */
/* ======================= */
void steering(){
  STEERING.update();
  dataNRF.str = STEERING.getValue();
}
/* ========================================================================================= */
/* READ RAW MPU6050 DATA */
/* ===================== */
void gforce() {
  Wire.beginTransmission(0x68); // start communicating with the MPU
  Wire.write(0x3B);            // set byte 0x3B to indicate start register
  Wire.endTransmission();     // terminate the connection
  Wire.requestFrom(0x68, 6); // request 6 bytes from MPU
  acc_x = Wire.read() << 8 | Wire.read();    // Shift high byte left and add low and high byte to acc_x
  acc_y = Wire.read() << 8 | Wire.read();   // Shift high byte left and add low and high byte to acc_y
  acc_z = Wire.read() << 8 | Wire.read();  // Shift high byte left and add low and high byte to acc_z
  rawX += acc_x;
  rawY += acc_y;
  x += 1;
  
  /*
  acc_x = (int16_t)(Wire.read() << 8 | Wire.read()) / 16384.0;
  acc_y = (int16_t)(Wire.read() << 8 | Wire.read()) / 16384.0;
  acc_z = (int16_t)(Wire.read() << 8 | Wire.read()) / 16384.0;
  */
}



/* ========================================================================================= */
/* AVERAGE THE MPU6050 DATA */
/* ======================== */
void avgGforce() {
  /* 2g sensitivity */
  dataNRF.avgXx = (rawX / x) * 0.061 / 1000;
  dataNRF.avgYy = (rawY / x) * 0.061 / 1000;

  /* 4g sensitivity */
  //dataNRF.avgXx = (rawX/x) * 0.122 / 1000;
  //dataNRF.avgYy = (rawY/x) * 0.122 / 1000;
  
  /* 8g sensitivity */
  //dataNRF.avgXx = (rawX / x) * 0.244 / 1000;
  //dataNRF.avgYy = (rawY / x) * 0.244 / 1000;

  /* 16g sensitivity */
  //dataNRF.avgXx = (rawX/x) * 0.732 / 1000;
  //dataNRF.avgYy = (rawY/x) * 0.732 / 1000;
  x = 0; // reset the counter for the average of MPU
  rawX = 0;
  rawY = 0;
}

If I comment out the following two lines of code from the Audio portion, the SPI bus works well.

Code:
AudioInputI2S            i2s1;
Code:
AudioOutputI2S           i2s2;

I will check the SPI mode mentioned by PaulS and also if I use any of the i2s pins for the MAX31855 ICs.

What could the problem be since commenting out the two lines above eliminates the problem? Obviously I need the i2s functionality in the code so I have to find a way to make it work without commenting out the two lines of code from the Audio library.

Thank you very much.
 
There maybe an issue with the Adafruit MAX31855 library, see this page.
Maxim's MAX13855 FAQ clearly states the MAX31855 uses SPI Mode 1, while the library defaults to using SPI Mode 0.

The problem is a lot of chips out there are not really SPI compliant, they look a bit like SPI. This one clocks out the
first bit on CS falling edge, for instance, so you always have to be careful to check the datasheet timing diagrams
against the code you use looking for problems. This chip for instance doesn't always use a multiple of 8 clocks...
 
If I comment out the following two lines of code from the Audio portion, the SPI bus works well.
Just checking: do you mean that SPI is now working well with 2 populated MAX13855's?

Paul
 
Just checking: do you mean that SPI is now working well with 2 populated MAX31855's?

Paul

Yes. If I do not comment out the two lines of code in post #6, the oled and NRF24 modules (both connected to the SPI bus) do not work as expected. A lot of noise is displayed on the oled, the NRF24 module works intermittently, and sometimes the SPI bus just stops working (the display turns black, the led on pin 13 of the Teensy (SCK) stops blinking, and data is no longer transmitted with the NRF module)

If I solder just one MAX31855 IC on the PCB everything works fine.
If I solder both MAX31855 ICs on the PCB and comment out the two lines of code in post #6 everything works fine.

Could it be something related to the audio and MAX31855 libraries?
 
Could it be something related to the audio and MAX31855 libraries?
Yep, that's my guess as well. Not sure why though. I may look into the Adafruit MAX31855 library and underlying libraries later today.

Paul
 
Looked into the Adafruit MAX31855 library and underlying libraries but did not spot a problem.
So I decided to hook up a logic analyzer and check for collisions on the SPI bus:

logicanalyzer0.jpg

Took the long code from your message #6, downloaded the required libraries, commented out the TVG logo stuff, added a while(!serial) to have time to arm the logic analyzer, and added intialization for pin 25 [CS for nRF24L01]. Left the AudioInputI2S and AudioOutputI2S in. Modified code is attached below.
Then ran the logic analyzer:

logicanalyzer1.png

I have zoomed in on every CS [legend on the left of the image]: no collisions!
Here is a zoomed-in snap at around 2.2secs where you can see all four CS's active after eachother.:

logicanalyzer2.png

MISO is obviously not showing activity since I did not connect any peripherals.

Not sure what to do next...
I will leave the logic analyzer setup on my desk, so if you want me do more measurements let me know.
[you may want to purchase this logic analyzer, it helped me a lot in debugging]

Paul
 

Attachments

  • sketch_aug10a.ino
    57.6 KB · Views: 66
Just found out that if you download and install the DSview software, you can then open the attached .DSL file [zipped] below and look & zoom into the captured waveforms yourself!

Paul
 

Attachments

  • DSLogic-la-200810-120723.zip
    130.5 KB · Views: 69
Thank you very much PaulS for taking the time to test my code.
It is very weird indeed because I tried just soldering the two MAX31855 ICs and powering the PCB without any code to initialize or use the ICs. The problem is still present.
So I think that it has something to do with the Audio library maybe?

Could anyone tell me what the two lines of code in post #6 do exactly? Is there any change done to the way Teensy uses it's pins when these lines of code are compiled?

I triple checked my PCB (the connections, footprints and soldering) and I could not find anything wrong with it. In fact, the Teensy can read the temperature from the MAX31855 ICs, display it and transmit it using the NRF module (until the SPI bus stops working).

I have no idea on what to check next. But I am now inclining to think that this is a physical problem either with one of the MAX31855 ICs or with something else, since the problem is dependent on both the ICs being soldered on the PCB.
If anyone has any other ideas on what I could do, please tell me. :)

Just found out that if you download and install the DSview software, you can then open the attached .DSL file [zipped] below and look & zoom into the captured waveforms yourself!

Paul
Thank you very much. I will try it.
 
As far as I understand those 2 lines instantiate 2 audio objects that you can use for audio input and output. It will probably incorporate a whole bunch of software under the hood. But I'm not familiar with the inner works of the audio library - I just use it...
Those 2 objects should not interfere with the SPI bus though.

By the way: if I only comment out those 2 lines, compilation will throw and error: 'i2s1' was not declared in this scope.

Paul
 
As far as I understand those 2 lines instantiate 2 audio objects that you can use for audio input and output. It will probably incorporate a whole bunch of software under the hood. But I'm not familiar with the inner works of the audio library - I just use it...
Those 2 objects should not interfere with the SPI bus though.

By the way: if I only comment out those 2 lines, compilation will throw and error: 'i2s1' was not declared in this scope.

Paul

You get that error while compiling because the i2s1 and i2s2 objects are used a little down the road in the code.
Commenting these other 4 lines will allow you to compile (but the Audio portion of the code will not work).

Code:
// GUItool: begin automatically generated code
AudioInputI2S            i2s1;
AudioPlayQueue           queue2;
AudioMixer4              mixer2;
AudioMixer4              mixer1;
AudioOutputI2S           i2s2;
AudioRecordQueue         queue1;
AudioConnection          patchCord1([B][COLOR="#FF0000"]i2s1[/COLOR][/B], 0, mixer1, 0);
AudioConnection          patchCord2([B][COLOR="#FF0000"]i2s1[/COLOR][/B], 1, mixer1, 1);
AudioConnection          patchCord3(queue2, 0, mixer2, 0);
AudioConnection          patchCord4(mixer2, 0, [B][COLOR="#FF0000"]i2s2[/COLOR][/B], 0);
AudioConnection          patchCord5(mixer2, 0, [B][COLOR="#FF0000"]i2s2[/COLOR][/B], 1);
AudioConnection          patchCord6(mixer1, queue1);
AudioControlSGTL5000     sgtl5000_1;
// GUItool: end automatically generated code
 
Just checking that we are on the same page... I also assumed that those 4 lines had to be commented out as well.
Anyway, no (obvious) changes to the waveforms after compiling, uploading and running with the 6 lines commented out.

Paul
 
Not sure what to do next...
I will leave the logic analyzer setup on my desk, so if you want me do more measurements let me know.

Can you put a 'scope on the SPI bus lines? A logic analyzer won't show things like bad voltages due to colliding outputs,
or bad crosstalk / ringing...
 
Can you put a 'scope on the SPI bus lines? A logic analyzer won't show things like bad voltages due to colliding outputs,
or bad crosstalk / ringing...

Are you asking me? I don't have the peripherals that output data on the MISO line, just the bare Teensy 4.1.
The OP indicated that he does not have a scope at his disposal.

Paul
 
@ Guzu: did you (re)check all the wiring from connector U6 to the off-board components? I see the SSOLED, SCK and MOSI signals going off-board. If one of those signals short to a neighboring signal or crosstalk is happening, than it could corrupt the SPI bus.
Another idea is to only connect the OLED display [so no other external parts] to connector U6 and check for SPI functionality.

Paul
 
@ Guzu: did you (re)check all the wiring from connector U6 to the off-board components? I see the SSOLED, SCK and MOSI signals going off-board. If one of those signals short to a neighboring signal or crosstalk is happening, than it could corrupt the SPI bus.
Another idea is to only connect the OLED display [so no other external parts] to connector U6 and check for SPI functionality.

Paul

I will disconnect everything else but the oled tomorrow and see if this changes anything.
The SSOLED, SCK and MOSI going out go to the oled. At the oled side there are two RJ45 connectors so I think there is not a problem there with crosstalk.
 
I finally got the time to do some more testing.
This is what I know so far (on top of what I already mentioned in the last posts):

  • If I disconnect everything from the U6 connector (including the Oled) the problem is still present. (I can see this by the way the SCK led blinks erratically)
  • If I leave everything connected (including the oled) and disconnect just the NRF24 module, everything works

What could the problem be?
 
[*]If I leave everything connected (including the oled) and disconnect just the NRF24 module, everything works
Just checking again: "everything" means [1] both MAX13855's work and [2] audio input/output works?

I guess we now have to dig into the nRF24 module and related firmware.

Paul
 
Just checking again: "everything" means [1] both MAX13855's work and [2] audio input/output works?

I guess we now have to dig into the nRF24 module and related firmware.

Paul

So after a few more tests I noticed that even if I unplug the NRF24 module from the PCB the problem is still present. A lot less noise on the screen and a lot less frequent, but I can see that it is not gone. When I `hot plug` the NRF24 module on the PCB while the Teensy is running, a lot of noise appears on the oled. When I remove it, a lot less noise on the oled.
I tried removing all the code for the NRF24 module and it did not solve the problem.

As far as I can see, the problem is with the physical presence of the NRF24 module and both the MAX31855 ICs on the PCB. :confused: I am inclined to think that this is not a software issue but then again, removing the 2 lines of code in post #6 solves the problem. Only removing both lines of code in post #6 solves the problem. Removing just one of them does nothing. So I don't know where to go next :confused:

Could it be that I need some pull down resistors for the SCK, MOSI and MISO lines?
 
I guess it's time now to borrow an oscilloscope somewhere and check the SPI lines and DC voltages on the different parts for analog phenomena.
Looking at the schematic again: what is the powersupply for all 3V3 parts? Is it Q1? Or is it the Teensy?
I would focus on the power supplies now. Are the AMS1117's running hot?

Paul
 
Looking at the schematic again: what is the powersupply for all 3V3 parts? Is it Q1? Or is it the Teensy?
I would focus on the power supplies now. Are the AMS1117's running hot?

Paul

The power supply for the 3V3 parts is Q1. It is a LDO capable of supplying up to 1A of power. This LDO in not getting hot at all. It is just warm.
The Q2 5V LDO is getting hot only when I connect a battery with higher voltage (over 11V) which I think is normal given that the 5V LDO is capable of handling max 16V on the Vin.
I ran some checks with the power. I don't think this is the problem since I powered the Teensy with just a couple of modules connected (way less power that the LDOs can provide) and I still got jitters on the SPI devices.

PaulS considering that this is taking a bit too much of your (and my) limited time and I do not have an oscilloscope to test further, whenever you get bored of fed up with troubleshooting feel free to tell me to just desolder one of the MAX31855 ICs and read just one temperature. ;)
 
Status
Not open for further replies.
Back
Top