Help with 3.5 Interval Timer and maybe I2C ?

Status
Not open for further replies.

Jabberwoky

New member
Hi all I would really appreciate some help with my code.

First of all a bit about the hardware: I am using a Teensy 3.5 with an adafruit VL53L0X and adafruit 128x32 OLED LCD (I2C). My objective is to data-log the sensor data onto a microSD card that is built into the Teensy.

I have tested my hardware connections using several different examples from both the Adafruit and pololu libraries and they all seem to indicate my hardware is functional and connected properly.

My code starts ok and makes it through setup but goes on the fritz shortly after starting the main loop. More specifically the serial monitor stops outputting any data and the OLED display starts to fill up with white. My hunch is that this has something to do with the IntervalTimer but I can't explain why it works initially then stops. My other thought is that something is messing up my i2c bus but again I can't explain why that would work initially and be fine with the other sample sketches.

Any help is much appreciated, below is a sample of the serial output and the code:


SERIAL OUT SAMPLE:
Initializing SD card...card initialized.

Logging to: Log_05.csv

Time [ms], Distance [mm], Marker

End Setup!

0, 0, 0

0, 0, 0

3145, 47, 0

3228, 47, 0

3265, 43, 0

3347, 51, 0

3385, 51, 0

3467, 48, 0

3508, 48, 0

3588, 53, 0

3628, 49, 0

3665, 52, 0

3748, 51, 0

3787, 50, 0



Code:
/**************************************************************************
 HARDWARE for this Project:
-> https://www.pjrc.com/store/teensy35.html
-> https://www.adafruit.com/product/3317
-> https://www.adafruit.com/product/931
-> http://www.migamotors.com/index.php?main_page=product_info&products_id=40
 **************************************************************************/

//**********************************************************************************
//*    Libraries Used
//**********************************************************************************
#include <Wire.h>
#include <VL53L0X.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <SD.h>
#include <SPI.h>

//*****************************************************************
//            DECLARATIONS
//*****************************************************************
//----------------For Logging to SD Card -----------------------------
#define ECHO_TO_SERIAL 1 //Echo data to serial port 1 = Turned ON
#define SYNC_INTERVAL 50 // How often to push data to SD after main event. 

//for teensy 3.x data logging, set chipSelect = BUILTIN_SDCARD
const int chipSelect = BUILTIN_SDCARD;

//The logging file
File logfile;

//Logging Millis
volatile unsigned long currentTime = 0;
unsigned long currentTime_copy = 0;

//Create an IntervalTimer Object
IntervalTimer myTimer;


//-----------------For Display----------------------------------------
// Declaration for an SSD1306 connected to I2C (SDA,SCL pins)  
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//----------------For Sensor------------------------------------------
// Declaration for an VL53L0X connected to I2C (SDA, SCL pins)
VL53L0X sensor;

//---------------Global Variables and Constants -------------------
char filename[] = "Log_00.csv";

unsigned int marker = 0;
volatile unsigned int distance = 0;
unsigned int distance_copy = 0;

//For button Interrupts
volatile bool startFlag = false;
const uint8_t STARTPOINT = 2; // Pin 2

volatile bool stopFlag = false;
const uint8_t STOPPOINT = 6; // Pin 6 

// ---------------States for state machine------------------------
enum operatingState {WAITING = 0, ACTIVE};
volatile operatingState opState = WAITING;


//****************************************************************
//             FUNCTION PROTOTYPES
//****************************************************************
void Waiting_State();
void Active_State();
void LogValues(unsigned int marker);
void displayMeasurement(unsigned int measurement, int opState);

void getDistance();
void StopISR();
void StartISR();

//****************************************************************
//              SETUP
//****************************************************************
void setup() {
noInterrupts();
// Start up the serial line//////////////////////
#if ECHO_TO_SERIAL   
  Serial.begin(9600);
  delay(2000);
#endif

//Start up the I2C lines//////////////////////////
Wire.begin();

//Start Up SD card/////////////////////////////
#if ECHO_TO_SERIAL
   Serial.print("Initializing SD card...");
#endif

  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
#if ECHO_TO_SERIAL
    Serial.println("Card failed, or not present");
#endif
    return;
  }
#if ECHO_TO_SERIAL
  Serial.println("card initialized.");
#endif

// Setup Log File
  for (int i =0; i < 100; i++){
    filename[4] = i / 10 + '0';
    filename[5] = i % 10 + '0';
    if (! SD.exists(filename)){
      logfile = SD.open(filename, FILE_WRITE); // Only open a new file if it doesn't exist
      break;
    }
  }
  if (! logfile){
#if ECHO_TO_SERIAL
    Serial.println("could not create file!");
#endif
  }

#if ECHO_TO_SERIAL
  Serial.print("Logging to: ");
  Serial.println(filename);
#endif
  
  // Write the Headers
  logfile.println("Time [ms], Distance [mm], Marker");
#if ECHO_TO_SERIAL
  Serial.println("Time [ms], Distance [mm], Marker");
#endif

// Setup OLED Display /////////////////////////////////////////////////////
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
#if ECHO_TO_SERIAL
    Serial.println(F("SSD1306 allocation failed"));
#endif
    for(;;); // Don't proceed, loop forever
  } 
  
// Show initial display buffer contents on the screen --
  display.display();
  delay(500); // Pause for 1 second

  // Clear the buffer
  display.clearDisplay(); 
  
// Setup Sensor  //////////////////////////////////////////////////////////
  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1) {}
  }
  // reduce timing budget to 20 ms (default is about 33 ms)
  sensor.setMeasurementTimingBudget(20000);
  

//Setup Start Switch, attach to ISR////////////////////////////////////////
  pinMode(STARTPOINT, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(STARTPOINT), StartISR, FALLING);

//Setup Stop Switch, attach to ISR
  pinMode(STOPPOINT, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(STOPPOINT), StopISR, FALLING);

//Setup Interval timer object used to get regular distance measurements from sensor. 
  myTimer.begin(getDistance, 40000);  // timer.begin (ISR name, call interval in microseconds)


 interrupts();
#if ECHO_TO_SERIAL
  Serial.println("End Setup!");
#endif
}


//*****************************************************************
//                BEGIN MAIN LOOP
//*****************************************************************
void loop() {
  display.clearDisplay();

  switch (opState){
    case WAITING:
      Waiting_State();
      break;
    case ACTIVE:
      Active_State();
      break;
  }
   
}// END MAIN

//******************************************************************
//                 STATE FUNCTIONS 
//******************************************************************

// **************************************************
// Waiting State - (Initial State) 
//    press Start Button to switch to Active State 
// **************************************************
void Waiting_State(){
  
  while(!startFlag){
    distance_copy = 0;
    
    LogValues(marker);

    displayMeasurement(distance_copy, WAITING);
  }

  startFlag = false;
    
  opState = ACTIVE;
  
  return;
}

// ********************************************************************
// Active State -  
//    press Stop Button to return to waiting state 
// ********************************************************************
void Active_State(){
    marker = 1; // indicating START point of inflation event. 
    LogValues(marker); // single call with marker set to 1.
    marker = 0; // Restore marker to default state
        
    while(!stopFlag){      

      distance_copy = 0;
            
      LogValues(marker);
            
      displayMeasurement(distance_copy, ACTIVE);
    }

    marker = 2; // indicating Stop point of inflation event. 
    LogValues(marker); // single call with marker set to 2.
    marker = 0;

    stopFlag = false; 

    opState = WAITING;

    return;
}

//******************************************************************
//                 SUPPORT FUNCTIONS 
//******************************************************************
void displayMeasurement(unsigned int measurement,int opState) {
  display.clearDisplay();
  display.setTextSize(2);             // Normal 1:1 pixel scale
  display.setTextColor(WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(filename);
  display.setCursor(0,16);
  display.print("mm: ");
  display.print(measurement);
  if (opState == ACTIVE){
    display.print('*');
  }

  display.display();
  return;
}
//------------------------------------------------------------------------------
void LogValues(unsigned int marker){
  noInterrupts();
  distance_copy = distance;  
  currentTime_copy = currentTime;
  interrupts();

  logfile.print(currentTime_copy);
  logfile.print(", ");
  logfile.print(distance_copy);
  logfile.print(", ");
  logfile.println(marker);
  logfile.flush();

#if ECHO_TO_SERIAL
  Serial.print(currentTime_copy);
  Serial.print(", ");
  Serial.print(distance_copy);
  Serial.print(", ");
  Serial.println(marker);
  Serial.flush();
#endif

  return;
}

//------------------------------------------------------------------------------


// ***********************************************************
//                          ISRs 
// ***********************************************************
void getDistance()
{
  distance = sensor.readRangeSingleMillimeters();
  currentTime = millis();
}


// For Start Switch
//-----------------------------------------------------------
void StartISR() 
{
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 100ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 100) 
  {
   startFlag = true;
  }
  last_interrupt_time = interrupt_time;
}  

// For Stop Switch
//-----------------------------------------------------------
void StopISR() 
{
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 100ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 100) 
  {
   stopFlag = true;
  }
  last_interrupt_time = interrupt_time;
}
 
there are few things that dont make sense (at least to me) in the code, like the for(;;); and display.display() lines..
perhaps you should try evaluating all hardware parts one by one and then combine them together..
sorry i cant help you because im not familiar with the sensor youre using..
 
Last edited by a moderator:
Seems like you're hammering the SD card with writes in the main loop(). Try limiting the writes to a lower frequency.

Code:
elapsedMicros loop_timer;
void loop() 
{
  if (loop_timer < 1000) // 1000 us = 1 ms
  {
    return;
  }
  // do your state machine
  loop_timer = 0;
}
 
Thanks for the comments so far.
I replaced the for(;;); with a while(1) as that is the way would have coded that (I had copied the for(;;); from an example)

Regarding the writes to the SD. How can I reduce this and not miss data that is being read every 40ms?

An additional thought that just occurred to me is that my Interval Timer might be interfering with my display since they are both on the same I2C bus. Basically if the interval timer tries to get a measurement while the display is trying to update that could put noise on the bus. Gonna try to split those to split those out to separate lines.
 
I don't know how fast the SD card can be written to, but I bet it's a lot faster than your 40 ms interval - probably in the order of 0.1 ms. IMO you should write to it just double the rate of your fastest device, so every 20 ms. You probably won't benefit anything from logging at 1000 Hz.

Judging from your serial monitor log though, it seems as if it is already only writing every 40 ms for some reason, maybe I don't understand your code fully.

Regarding the I2C bus, I'd make sure that adequate pull-up resistors are present for both devices. IIRC Teensy 3.5 has weak internal pull-up resistors which are not enough for I2C: https://forum.pjrc.com/threads/21680-New-I2C-library-for-Teensy3
 
@prnthp Thanks again for your help with this. I removed the display from the equation and that revealed the absurdity of my 1kHz logging rate lol.

My plan at this point is to first get the logging timing setup to be more reasonable an then I'll take a shot at adding the display back into the mix.
 
Then it looks like timing isn't the issue, since the display.display() calls were blocking everything else. There are several threads about the SSD1306 and it seems like there is a better way to refresh it with the DMA. I don't have experience with driving displays so I can't comment much on it.
 
Status
Not open for further replies.
Back
Top