Overclocking Teensy 4.1?

nAnd

Member
I'm using a Teensy for a data acquisition setup and was wondering if I could overclock it a little to up the polling rate a bit. I know this will potentially introduce some instability and reduce the expected lifespan of the CPU, but this is a situation where burning out a Teensy every now and then would be worth the potential 20-50% performance improvements we'd get from pushing it pretty close to the max. I've poked around online but was only really able to find a thread saying why not to do it instead of how to do it. Can someone point me in the direction of the files I need to modify to change the clock speed/voltages and some documentation for what all the different variables in said file control? Thanks in advance!
 
Before you resort to overclocking, if you're willing to share code for review here, maybe we could help you improve performance. But please understand the degree to which we can help really depends on the amount and quality of the info you share. If you share a complete and relatively small / simple program, and give us details on the required hardware (or if we can run it just on a Teensy without anything else connected and get random noise or just zeros) odds are pretty good we might see ways to improve or optimize it to run faster. If you share a huge complicated program, we have to spend a lot more time just to help, but maybe someone will do it (has happened many times before). But if you don't share a complete program anyone can actually copy into Arduino IDE and run themselves, like only showing small code fragments, your chances for useful help decrease quite a lot. However, sometimes we do manage to help even by blind guesswork, so you might as well try asking one way or another. Just know, the help we can give on improving performance really depends on how much detail you give about the problem, and especially whether or not anyone can easily reproduce the lacking performance.
 
Thanks for your response Paul! I've seen your comments all over this forum and have found them to be some of the most enlightening information on here. I've pasted my entire program below so everyone who sees this has full context since it's pretty short. (Sorry about the somewhat dumb sounding comments, I still have a lot to learn)
Code:
//Note: Should get an external powersupply to get better VCC stability
//sd card stuff

//try to change this to use interupts instead of constantly checking each pin (there's apparently a seperate piece of hardware that generates the interupt signal so using interupts should save clock cycles)
#include "SD.h"
#include "SPI.h"
//time stuff
#include <TimeLib.h>
#include <Adafruit_ADS1X15.h>
#include <Wire.h>
#include <Adafruit_BNO055.h>

#define BAUD 230400

#define serialMonitor Serial

struct {
  unsigned long long int seconds;
  unsigned long int micros;
  int binaryValues[3];
  int analogValues[4];
  float orientation[3];
  float acceleration[3];
  float linearAcceleration[3];
  float quaternionCoords[4];
  int calibration[4];
  float orientation1[3];
  float acceleration1[3];
  float linearAcceleration1[3];
  float quaternionCoords1[4];
  int calibration1[4];
} dataStruct;


File outputFile;

bool isRecording = false;

Adafruit_ADS1115 ads;

//current analog sensor number being polled
int currentAnalogSensor = 0;

//create an interval timer for BNO05
IntervalTimer BNO05Timer;

//creates a new instance of the BNO055 class
Adafruit_BNO055 bno = Adafruit_BNO055(55, 0x29, &Wire);
Adafruit_BNO055 bno1 = Adafruit_BNO055(55, 0x28, &Wire);

//varaibles for data from BNO05
sensors_event_t orientationData, linearAccelData, accelerometerData;
uint8_t BNO05System, gyro, accel, mag = 0;
imu::Quaternion quat;

volatile bool analogValueFlag = false;
volatile bool BNO05flag = false;

//saves the last time data was saved
ulong lastSaveTimeInMillis = 0;

void setup() {
  pinMode(8, OUTPUT); //white LED (powered on)
  Serial.begin(115200);
  //set up time stuff for rtc
  setSyncProvider(getTeensy3Time);
  if (timeStatus()!= timeSet) {
    Serial.println("Unable to sync with the RTC");
  } else {
    Serial.println("RTC has set the system time");
  }
  serialMonitor.begin(BAUD);
  SD.begin(BUILTIN_SDCARD);
  delay(500);
  String time =  String(year()) + "-" + String(month()) + "-" + String(day()) + " " + String(hour()) + "_" + String(minute()) + "_" + String(second())+".bin";
  Serial.println(time.c_str());
  outputFile = SD.open(time.c_str(),  FILE_WRITE);
  //sets top leds to output
  pinMode(9, OUTPUT); //red LED (recording)
  digitalWrite(8, HIGH); //turn on white LED
  pinMode(7, INPUT_PULLDOWN); //pull down input for record stop/start button (NEEDED TO MAKE IT WORK)
  //set the values in the digital value array to their initial values (likely doesn't matter but feels better to do it this way)
  dataStruct.binaryValues[0] = digitalRead(20);
  dataStruct.binaryValues[1] = digitalRead(21);
  dataStruct.binaryValues[2] = digitalRead(22);
  //sets up interupts for binary values
  attachInterrupt(digitalPinToInterrupt(20), updateRearDiff, CHANGE); //rear diff
  attachInterrupt(digitalPinToInterrupt(21), updateFrontLeftHalleffect, CHANGE); // front left halleffect
  attachInterrupt(digitalPinToInterrupt(22), updateFrontRightHalleffect, CHANGE); // front right halleffect
  BNO05Timer.begin(updatBNO05Flag, 5000); //BNO05 polling flag
  //sets up interupt pin for ADS1115 ADC (have to pullup alert pin in accordance with ADS1115 datasheet)
  pinMode(15, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(15), updateAnalogValueFlag, FALLING);
  //set up ADC on seperate bus to hopefully make them play nice
  ads.begin(ADS1X15_ADDRESS, &Wire1);
  //set data rate to max
  ads.setDataRate(RATE_ADS1115_860SPS);
  isRecording = true;
  digitalWrite(9, HIGH); //turn on red LED
  //start the BNO05
  bno.begin();
  bno1.begin();
  //start first ADC reading to begin the cycle
  ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_0, false);
  updateBNO05Readings();
}
//writes data to SD card
void loop() {
  dataStruct.seconds = now();
  dataStruct.micros = micros();
  //size of is apparently computed at compile time
  outputFile.write(&dataStruct, sizeof(dataStruct));
  if(BNO05flag) {
    updateBNO05Readings();
    BNO05flag = false;
  }
  if (analogValueFlag) {
    readAnalogValues();
    analogValueFlag = false;
  }
  if (digitalRead(7) && lastSaveTimeInMillis + 1000 < millis()) {
    changeRecordingState();
  }

}
//method to update BNO05 readings
void updateBNO05Readings() {
  bno.getEvent(&orientationData, Adafruit_BNO055::VECTOR_EULER);
  bno.getEvent(&linearAccelData, Adafruit_BNO055::VECTOR_LINEARACCEL);
  bno.getEvent(&accelerometerData, Adafruit_BNO055::VECTOR_ACCELEROMETER);
  bno.getCalibration(&BNO05System, &gyro, &accel, &mag);
  quat = bno.getQuat();
  dataStruct.orientation[0] = orientationData.orientation.x;
  dataStruct.orientation[1] = orientationData.orientation.y;
  dataStruct.orientation[2] = orientationData.orientation.z;
  dataStruct.linearAcceleration[0] = linearAccelData.acceleration.x;
  dataStruct.linearAcceleration[1] = linearAccelData.acceleration.y;
  dataStruct.linearAcceleration[2] = linearAccelData.acceleration.z;
  dataStruct.acceleration[0] = accelerometerData.acceleration.x;
  dataStruct.acceleration[1] = accelerometerData.acceleration.y;
  dataStruct.acceleration[2] = accelerometerData.acceleration.z;
  dataStruct.calibration[0] = BNO05System;
  dataStruct.calibration[1] = gyro;
  dataStruct.calibration[2] = accel;
  dataStruct.calibration[3] = mag;
  bno1.getEvent(&orientationData, Adafruit_BNO055::VECTOR_EULER);
  bno1.getEvent(&linearAccelData, Adafruit_BNO055::VECTOR_LINEARACCEL);
  bno1.getEvent(&accelerometerData, Adafruit_BNO055::VECTOR_ACCELEROMETER);
  bno1.getCalibration(&BNO05System, &gyro, &accel, &mag);
  quat = bno1.getQuat();
  dataStruct.orientation1[0] = orientationData.orientation.x;
  dataStruct.orientation1[1] = orientationData.orientation.y;
  dataStruct.orientation1[2] = orientationData.orientation.z;
  dataStruct.linearAcceleration1[0] = linearAccelData.acceleration.x;
  dataStruct.linearAcceleration1[1] = linearAccelData.acceleration.y;
  dataStruct.linearAcceleration1[2] = linearAccelData.acceleration.z;
  dataStruct.acceleration1[0] = accelerometerData.acceleration.x;
  dataStruct.acceleration1[1] = accelerometerData.acceleration.y;
  dataStruct.acceleration1[2] = accelerometerData.acceleration.z;
  dataStruct.calibration1[0] = BNO05System;
  dataStruct.calibration1[1] = gyro;
  dataStruct.calibration1[2] = accel;
  dataStruct.calibration1[3] = mag;
}
//method needed to get time
time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}
void updateAnalogValueFlag() {
  analogValueFlag = true;
}
void updatBNO05Flag() {
  BNO05flag = true;
}
void updateRearDiff() {
  dataStruct.binaryValues[0] = !dataStruct.binaryValues[0];
}
void updateFrontLeftHalleffect() {
  dataStruct.binaryValues[1] = !dataStruct.binaryValues[1];
}
void updateFrontRightHalleffect() {
  dataStruct.binaryValues[2] = !dataStruct.binaryValues[2];
}
void changeRecordingState() {
    noInterrupts();
    if(isRecording == true) {
      while(digitalRead(7) == 1) {
        delay(10);
      }
      outputFile.close();
      digitalWrite(9, LOW); //turn off red LED
      Serial.println("Data Recording Stopped");
      isRecording = false;
    }
    else {
      while(digitalRead(7) == 1) {
        delay(10);
      }
      String time =  String(year()) + "-" + String(month()) + "-" + String(day()) + " " + String(hour()) + "_" + String(minute()) + "_" + String(second());
      Serial.println(time.c_str());
      //turn on red LED
      digitalWrite(9, HIGH);
      outputFile = SD.open(time.c_str(),  FILE_WRITE);
      isRecording = true;
    }
    lastSaveTimeInMillis = millis();
    interrupts();   
}
void readAnalogValues() {
  switch (currentAnalogSensor) {
      case 0:
        dataStruct.analogValues[0] =  ads.getLastConversionResults();
        currentAnalogSensor = 1;
        ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_1, false);
        break;
      case 1:
        dataStruct.analogValues[1] =  ads.getLastConversionResults();
        currentAnalogSensor = 2;
        ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_2, false);
        break;
      case 2:
        dataStruct.analogValues[2] =  ads.getLastConversionResults();
        currentAnalogSensor = 3;
        ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_3, false);
        break;
      case 3:
        dataStruct.analogValues[3] =  ads.getLastConversionResults();
        currentAnalogSensor = 0;
        ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_0, false);
        break;
  }
}
 
A short note on what is the slow part - or what part func() needs to run in less time?

The "BNO055 Absolute Orientation Sensor ... to get 9 DoF" transfer speed will be limited by i2c clock.

On T_3.6 we saw 500 updates/sec with a different unit that is now EOL IIRC - but the calc's slowed it down so SPI_TRANSFER was created to offload the data to a second T_3.6 for processing to get that speed.

How many updates are working now? How many are desired? From that BNO055and the ' Adafruit_ADS1115 : ADC '.
 
A couple of thoughts based on a quick scan through the code:

You look to be writing data to the SD card as fast as possible rather than only writing when one of your inputs has changed.
It may be they are changing fast enough that this is always the case but I wouldn't necessarily count on it. Only writing when something has changed would be good.

You are reading from sensors connected over I2C. I2C is slow. Overclocking the processor won't help much if it's spending all it's time for a slow interface bus. All that would happen is the processor would sit there waiting for more clock cycles.
Look at increasing the I2C speed or since you have multiple sensors connecting them on two different I2C busses, using DMA or some other asynchronous method and read both busses at the same time rather than one then the other.

SD cards have notoriously variable write times, some writes will be almost instant, some will take hundreds of ms. Overclocking won't help with this, it's the internals of the SD card that cause this delay.
Currently you have a single loop that is both reading the sensors and writing to the SD card, when the SD card stalls you're going to get a big gap in the data. The best solution to this is to break the link between data collection and data writing. Have a minimum priority interrupt (it could be a timer running at a suitable rate) that checks for new data, if data is available format it into the appropriate data record and add it to a queue. All the background loop does is check if the queue contains data and if so write it to the card.
This breaks the link between the SD card timings and the data collection. It does require a buffer large enough to store ideally at least 200ms of data but fortunately you don't seem to be using much memory so you've got 500k or so of memory you could use for that buffer.

In short there looks to be a lot you could do to speed things up which would be significantly more effective than overclocking.
 
Thanks for your input! The implementation of the i2c devices was thrown together last weekend in preparation for an upcoming testing day and it's abundantly clear that I could make it work much much better than it currently is. I'm currently focusing my efforts on trying to get the teensy to communicate over CAN so we can incorporate data from a wheel force transducer but I'll make sure to incorporate those changes as soon as possible! I'll almost certainly have more questions as I dive into that, but, before I begin, should I implement the queue just by defining an array of dataStructs or do I need to use mallocs? I'm wondering if mallocing nodes of a queue would be the best option since I think file.write() already has a buffer to make sure data's only actually written to the SD card when a 500byte chunk is ready (plus it would make it wayyyyyyy easier to deal with adding data points while some are still being written). Also, I agree that there's a ton of optimizations to be made in terms of when/what data should be saved, but how do I handle the eventual situation (basically as soon as we get some better ADCs installed) where the ADCs legitimately always have data available? Should I be using interval timers for more or less all my data collection and just say "this is how fast we need this data so just poll at this rate"? Also, for some reason the use of an interval timer has the effect of slowing down my code. I think this is due to the millis() clock slipping as interrupts keep on messing with it, but my sampling rate dropped from minimums of about 8000sps to about 5000sps when I changed the updateBNO05 readings method from being called by a millis() function in the main loop to the interrupt. Is this due to inefficiencies in the inervalTimer library or just something with the old version of my code not actually polling at the rate it was supposed to?

Sorry if I rambled too much there, your collective input has been incredibly helpful so far and has me thinking about a lot of different stuff atm.

Edit: I don't have a CAN transceiver so I guess that's not happening :(. Guess I'll get started on the queue
 
Last edited:
I tried to add a queue but I did something wrong and the teensy keeps on hard rebooting. I'm gonna try and see if I can track the free heap/ram and see if that's the problem but figured I'd post the updated code here for now.
Code:
//Note: Should get an external powersupply to get better VCC stability
//sd card stuff

//try to change this to use interupts instead of constantly checking each pin (there's apparently a seperate piece of hardware that geenrates the interupt signal so using interupts should save clock cycles)
#include "SD.h"
#include "SPI.h"
//time stuff
#include <TimeLib.h>
#include <Adafruit_ADS1X15.h>
#include <Wire.h>
#include <Adafruit_BNO055.h>

#define BAUD 230400

#define serialMonitor Serial

//create struct for data packet
struct DataStruct{
  unsigned long long int seconds;
  unsigned long int micros;
  int binaryValues[3];
  int analogValues[4];
  float orientation[3];
  float acceleration[3];
  float linearAcceleration[3];
  float quaternionCoords[4];
  int calibration[4];
};

//create struct for queueNode
struct QueueNode {
  DataStruct *data;
  QueueNode *next;
};

//create struct for queue
struct Queue {
  QueueNode *head;
  QueueNode *tail;
};


//create a new queue (note may need to initialize some of the parameters of this node)
Queue Queue = {NULL, NULL};


File outputFile;

bool isRecording = false;

Adafruit_ADS1115 ads;

//current analog sensor number being polled
int currentAnalogSensor = 0;

//create an interval timer for BNO05
IntervalTimer BNO05Timer;

//create an interval timer for overal Data Collection Method
IntervalTimer dataCollectionTimer;

//creates a new instance of the BNO055 class
Adafruit_BNO055 bno = Adafruit_BNO055(55, 0x29, &Wire);

//lists for the current analog and binary readings
int binaryValues[3];
int analogValues[4];

//variables for data from BNO05
sensors_event_t orientationData, linearAccelData, accelerometerData;
uint8_t BNO05System, gyro, accel, mag = 0;

imu::Quaternion quat;

volatile bool analogValueFlag = false;
volatile bool BNO05flag = false;

//saves the last time data was saved
ulong lastSaveTimeInMillis = 0;

void setup() {
  pinMode(8, OUTPUT); //white LED (powered on)
  Serial.begin(115200);
  //set up time stuff for rtc
  setSyncProvider(getTeensy3Time);
  if (timeStatus()!= timeSet) {
    Serial.println("Unable to sync with the RTC");
  } else {
    Serial.println("RTC has set the system time");
  }
  serialMonitor.begin(BAUD);
  SD.begin(BUILTIN_SDCARD);
  delay(500);
  String time =  String(year()) + "-" + String(month()) + "-" + String(day()) + " " + String(hour()) + "_" + String(minute()) + "_" + String(second())+".bin";
  Serial.println(time.c_str());
  outputFile = SD.open(time.c_str(),  FILE_WRITE);
  //sets top leds to output
  pinMode(9, OUTPUT); //red LED (recording)
  digitalWrite(8, HIGH); //turn on white LED
  pinMode(7, INPUT_PULLDOWN); //pull down input for record stop/start button (NEEDED TO MAKE IT WORK)
  //set the values in the digital value array to their initial values (likely doesn't matter but feels better to do it this way)
  binaryValues[0] = digitalRead(20);
  binaryValues[1] = digitalRead(21);
  binaryValues[2] = digitalRead(22);
  //sets up interupts for binary values
  attachInterrupt(digitalPinToInterrupt(20), updateRearDiff, CHANGE); //rear diff
  attachInterrupt(digitalPinToInterrupt(21), updateFrontLeftHalleffect, CHANGE); // front left halleffect
  attachInterrupt(digitalPinToInterrupt(22), updateFrontRightHalleffect, CHANGE); // front right halleffect
  BNO05Timer.begin(updatBNO05Flag, 5000); //BNO05 polling flag
  dataCollectionTimer.begin(checkIfNewData, 100); //set rate of data checking
  dataCollectionTimer.priority(255); //set priority of interrupt to absolute lowest to make sure it doesn't interfere with data collection (prob have to tune this tbh)
  //sets up interupt pin for ADS1115 ADC (have to pullup alert pin in accordance with ADS1115 datasheet)
  pinMode(15, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(15), updateAnalogValueFlag, FALLING);
  //set up ADC on seperate bus to hopefully make them play nice
  ads.begin(ADS1X15_ADDRESS, &Wire1);
  //set data rate to max
  ads.setDataRate(RATE_ADS1115_860SPS);
  isRecording = true;
  digitalWrite(9, HIGH); //turn on red LED
  //start the BNO05
  bno.begin();
  //start first ADC reading to begin the cycle
  ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_0, false);
  updateBNO05Readings();
}
//writes data to SD card
void loop() {
  //size of is apparently computed at compile time
  if(Queue.head != NULL) {
    outputFile.write(Queue.head -> data, sizeof(DataStruct));
    QueueNode temp = *Queue.head;
    free(Queue.head -> data);
    free(Queue.head);
    Queue.head = temp.next;
  }
}
void checkIfNewData() {
  bool timeToUpdateData = false;
  if(BNO05flag) {
    updateBNO05Readings();
    BNO05flag = false;
    timeToUpdateData = true;
  }
  if (analogValueFlag) {
    readAnalogValues();
    analogValueFlag = false;
    timeToUpdateData = true;
  }
  if (digitalRead(7) && lastSaveTimeInMillis + 1000 < millis()) {
    changeRecordingState();
  }
  if (timeToUpdateData) {
    updateData();
  }
}

void updateData() {
  DataStruct *data = (DataStruct *)malloc(sizeof(DataStruct));
  data -> seconds = now();
  data -> micros = micros();
  data -> binaryValues[0] = binaryValues[0];
  data -> binaryValues[1] = binaryValues[1];
  data -> binaryValues[2] = binaryValues[2];
  data -> analogValues[0] = analogValues[0];
  data -> analogValues[1] = analogValues[1];
  data -> analogValues[2] = analogValues[2];
  data -> analogValues[3] = analogValues[3];
  data -> orientation[0] = orientationData.orientation.x;
  data -> orientation[1] = orientationData.orientation.y;
  data -> orientation[2] = orientationData.orientation.z;
  data -> linearAcceleration[0] = linearAccelData.acceleration.x;
  data -> linearAcceleration[1] = linearAccelData.acceleration.y;
  data -> linearAcceleration[2] = linearAccelData.acceleration.z;
  data -> acceleration[0] = accelerometerData.acceleration.x;
  data -> acceleration[1] = accelerometerData.acceleration.y;
  data -> acceleration[2] = accelerometerData.acceleration.z;
  data -> calibration[0] = BNO05System;
  data -> calibration[1] = gyro;
  data -> calibration[2] = accel;
  data -> calibration[3] = mag;
  QueueNode *queueNode = (QueueNode *)malloc(sizeof(QueueNode));
  queueNode -> data = data;
  if (Queue.head != NULL) {
    Queue.tail -> next = queueNode;
    Queue.tail = queueNode;
  } else {
    Queue.head = queueNode;
    Queue.tail = queueNode;
  }
}

//method to update BNO05 readings
void updateBNO05Readings() {
  bno.getEvent(&orientationData, Adafruit_BNO055::VECTOR_EULER);
  bno.getEvent(&linearAccelData, Adafruit_BNO055::VECTOR_LINEARACCEL);
  bno.getEvent(&accelerometerData, Adafruit_BNO055::VECTOR_ACCELEROMETER);
  bno.getCalibration(&BNO05System, &gyro, &accel, &mag);
  quat = bno.getQuat();
}
//method needed to get time
time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}
void updateAnalogValueFlag() {
  analogValueFlag = true;
}
void updatBNO05Flag() {
  BNO05flag = true;
}
void updateRearDiff() {
  binaryValues[0] = !binaryValues[0];
}
void updateFrontLeftHalleffect() {
  binaryValues[1] = !binaryValues[1];
}
void updateFrontRightHalleffect() {
  binaryValues[2] = !binaryValues[2];
}
void changeRecordingState() {
    noInterrupts();
    if(isRecording == true) {
      while(digitalRead(7) == 1) {
        delay(10);
      }
      while (Queue.head != NULL) {
        outputFile.write(Queue.head -> data, sizeof(DataStruct));
        QueueNode temp = *Queue.head;
        free(Queue.head -> data);
        free(Queue.head);
        Queue.head = temp.next;
      }
      outputFile.close();
      digitalWrite(9, LOW); //turn off red LED
      Serial.println("Data Recording Stopped");
      isRecording = false;
    }
    else {
      while(digitalRead(7) == 1) {
        delay(10);
      }
      String time =  String(year()) + "-" + String(month()) + "-" + String(day()) + " " + String(hour()) + "_" + String(minute()) + "_" + String(second());
      Serial.println(time.c_str());
      //turn on red LED
      digitalWrite(9, HIGH);
      outputFile = SD.open(time.c_str(),  FILE_WRITE);
      isRecording = true;
    }
    lastSaveTimeInMillis = millis();
    interrupts();  
}
void readAnalogValues() {
  switch (currentAnalogSensor) {
      case 0:
        analogValues[0] =  ads.getLastConversionResults();
        currentAnalogSensor = 1;
        ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_1, false);
        break;
      case 1:
        analogValues[1] =  ads.getLastConversionResults();
        currentAnalogSensor = 2;
        ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_2, false);
        break;
      case 2:
        analogValues[2] =  ads.getLastConversionResults();
        currentAnalogSensor = 3;
        ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_3, false);
        break;
      case 3:
        analogValues[3] =  ads.getLastConversionResults();
        currentAnalogSensor = 0;
        ads.startADCReading(ADS1X15_REG_CONFIG_MUX_SINGLE_0, false);
        break;
  }
}

Thanks again for all the help!
 
Last edited:
For data logging to SD, I strongly suggest using the method shown in SdFat example program TeensySdioLogger. It shows how to use SdFat's RingBuf, which is a template class that associates a ring buffer with an SD file, with methods to write data to the buffer and write data from the buffer to the file (and vice versa). Equally important, the example shows how to use SdFat's isBusy() to avoid blocking. In a nutshell, you should always write to SD files in chunks of 512 bytes (1 sector), and always check for isBusy() before writing. The long delays described by AndyA can occur on any write, but they occur AFTER the write function returns. During that delay, isBusy() will return true, so if you always check isBusy(), you can avoid attempting to write during the delays. In my testing with a SanDisk Ultra 32GB SD card, the longest delay I have seen is 40 ms. If you size your RingBuf so that it can hold 100 ms of data, that should be plenty. Your data structure is about 100 bytes, so if you were writing that every 1 ms, your data rate would be about 100*1000 = 100KB/s, and an appropriate buffer size would be about 10KB. The RingBuf class allocates the buffer statically, so it will always be in T4's fast RAM. You really don't have to mess with interrupts, either, you can just do something like this within loop()
  • collect data
  • write to RingBuf
  • if ( (RingBuf >= 512 bytes) && (isBusy()==false) ) write to file
 
I tried to add a queue but I did something wrong and the teensy keeps on hard rebooting. I'm gonna try and see if I can track the free heap/ram and see if that's the problem but figured I'd post the updated code here for now.

Rather than trying to do anything clever just define an array to act as your buffer. Use the DMAMEM keyword to put it in RAM2 so that you have plenty of space for it. As @joepasquariello indicated handle this as a ring buffer, track the head and tail index within that array and cycle around it as needed. You could do this as a raw byte array and advance by sizeof(datastruct) each time or have it as an array of data structures, both work. Personally I'd go with an array of data structures since it ensures that the buffer is an exact multiple of your structure and you don't have to handle wraparound cases.

Since you're writing a binary structure normally I'd say you need to ensure it's defined as packed so that the layout is fixed. However since everything in your structure is a multiple of 32 bits that wouldn't have any effect in this case.

 
Thanks for the advice! I'll try to implement those changes tomorrow.

As for the interrupt bit, I like the idea of using interrupts as I can only record/poll sensors when their data is ready instead of having to poll at their nyquist rate, wasting a lot of clock cycles for unnecessary duplicates of data. Is this advantage worth it or should I just not bother with them and poll data as fast as possible? I find it kind of cool to see 4 gigabyte binary files after a test, but it does seem a little excessive as the highest nyquist rate on the car is less than 5000Hz and we're averaging 20kHz with minimums of about 10kHz as it stands (atleast I think that's what we saw today, I haven't processed all of the 22.7GB of binary data we pulled off the car yet.)
 
This is for a car? Cars simply don't move that fast, 100Hz is fast enough if you are measuring body dynamics for a normal car. Maybe 500Hz if you want complete overkill. Anything over that and all you'll be logging is sensor noise.
 
That's true to a certain extent but we'll be measuring stuff like shock travel with lds sensors, brake pressure with ratiometric brake sensors, and wheel rpm off the drive shaft and transfer case output which have fairly high nyquist rates. It is admittedly somewhat overkill, but that's also part of the fun since this is for a collegiate event and I want something to show future employers. This is the team's website if you're curious (https://gtor.gatech.edu/)
 
Are you confusing (digital) pulse rates with the Nyquist rate of a regularly sampled analog signal? For digital signals we usually talk of maximum frequency or minimum pulse width/time as Fourier Transforms aren't involved.
 
I believe I may be. I must confess I didn't understand signals class all that well. Def more of a CE than an EE. Don't you still have to poll at twice the rpm/60*numteeth tho?
 
For a digital signal you have to poll at greater than 1 / minimum_pulse_width.
So if the minimum pulse width (positive or negative) is 100µs, you must poll faster than 10kHz to see every transition, or simply use interrupt driven input.

A 100Hz digital signal thats 1ms high and 9ms low needs sampling at > 1kHz,
A 100Hz square wave only needs sampling at > 200Hz

With a _regularly_ sampled analog signal (sampled at Fs) there are several considerations:

1) There should be negligible signal energy reaching the ADC _at or above_ Fs/2 (an anti-aliasing filter is often needed to ensure this)
2) You can only recover frequencies _below_ Fs/2. Fs/2 is the Nyquist frequency.
3) In practice with sensible filter designs the limit is more like Fs/3

So for instance sampling a voice signal for standard 8kSPS digital telephone use requires first a steep cutoff filter with a passband to about 3.5kHz and stopband at 4kHz, then 8kHz sampled ADC. Without the filter you get heavy ring-modulation (frequency aliasing).
 
Back
Top