Some Serial.print(); not work in this sketch?

Hi all.
The code attached run on ESP32S3 + SSD1306_OLED works well.
Added some Serial.print(); to analysis code, some printed good, some not, why?

the all Serial.print(); inside void displayMeasuredValues(bool no_finger, int32_t beatAvg, int32_t spo2) doesn't printed, however, the OLED shown the MAX30105 readings on screen, that means the code did call and run void displayMeasuredValues(bool no_finger, int32_t beatAvg, int32_t spo2).

Thanks.
Adam

SKETCH:

#include <MAX3010x.h>
#include <Adafruit_SSD1306.h>
#include "filters.h"

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Sensor (adjust to your sensor type)
MAX30105 sensor;
const auto kSamplingRate = sensor.SAMPLING_RATE_400SPS;
const float kSamplingFrequency = 400.0;

// Finger Detection Threshold and Cooldown
const unsigned long kFingerThreshold = 10000;
const unsigned int kFingerCooldownMs = 500;

// Edge Detection Threshold (decrease for MAX30100)
const float kEdgeThreshold = -2000.0;

// Filters
const float kLowPassCutoff = 5.0;
const float kHighPassCutoff = 0.5;

// Averaging
const bool kEnableAveraging = true;
const int kAveragingSamples = 5;
const int kSampleThreshold = 5;

void setup() {

  Serial.begin(115200);

  delay(5000);
  Serial.println("ESP32_MAX30102_OLED_gd_S3_2!");

  Wire.begin(43, 44); //SDA, SCL

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally

  //display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    while (1);
  }

  sensor.begin() && sensor.setSamplingRate(kSamplingRate);

  Serial.print("71 sensor.begin()=");
  Serial.println(sensor.begin());
  Serial.print("73 sensor.setSamplingRate(kSamplingRate)=");
  Serial.println(sensor.setSamplingRate(kSamplingRate));

  /*  //if (sensor.begin() && sensor.setSamplingRate(kSamplingRate)) {
    //  Serial.println("Sensor initialized");
    //}
    else {
      Serial.println("Sensor not found");
      while (1);
    }  */

  display.clearDisplay();
  initDrawScreen();
}

// Filter Instances
LowPassFilter low_pass_filter_red(kLowPassCutoff, kSamplingFrequency);  // used: filters.h
LowPassFilter low_pass_filter_ir(kLowPassCutoff, kSamplingFrequency);
HighPassFilter high_pass_filter(kHighPassCutoff, kSamplingFrequency);
Differentiator differentiator(kSamplingFrequency);
MovingAverageFilter<kAveragingSamples> averager_bpm;
MovingAverageFilter<kAveragingSamples> averager_r;
MovingAverageFilter<kAveragingSamples> averager_spo2;

// Statistic for pulse oximetry
MinMaxAvgStatistic stat_red;
MinMaxAvgStatistic stat_ir;

// R value to SpO2 calibration factors
// See https://www.maximintegrated.com/en/design/technical-documents/app-notes/6/6845.html
float kSpO2_A = 1.5958422;
float kSpO2_B = -34.6596622;
float kSpO2_C = 112.6898759;

// Timestamp of the last heartbeat
long last_heartbeat = 0;

// Timestamp for finger detection
long finger_timestamp = 0;
bool finger_detected = false;

// Last diff to detect zero crossing
float last_diff = NAN;
bool crossed = false;
long crossed_time = 0;

void loop() {
  auto sample = sensor.readSample(1000);
  float current_value_red = sample.red;
  float current_value_ir = sample.ir;

  Serial.print("154! current_value_red=");
  Serial.println(current_value_red);

  // Detect Finger using raw sensor value
  if (sample.red > kFingerThreshold) {  /// kFingerThreshold = 10000;

    Serial.print("160! sample.red=");
    Serial.println(sample.red);

    if (millis() - finger_timestamp > kFingerCooldownMs) {  /// kFingerCooldownMs = 500;
      finger_detected = true;

      Serial.print("166! millis() - finger_timestamp=");
      Serial.println(millis() - finger_timestamp);
    }
  }
  else {
    // Reset values if the finger is removed
    differentiator.reset();
    averager_bpm.reset();
    averager_r.reset();
    averager_spo2.reset();
    low_pass_filter_red.reset();
    low_pass_filter_ir.reset();
    high_pass_filter.reset();
    stat_red.reset();
    stat_ir.reset();

    finger_detected = false;
    finger_timestamp = millis();

    Serial.print("185! finger_timestamp=");
    Serial.println(finger_timestamp);
  }

  if (finger_detected) {
    displayMeasuredValues(false, 0, 0);
    current_value_red = low_pass_filter_red.process(current_value_red);
    current_value_ir = low_pass_filter_ir.process(current_value_ir);


    Serial.print("195! current_value_red =");
    Serial.println(current_value_red);


    // Statistics for pulse oximetry
    stat_red.process(current_value_red);
    stat_ir.process(current_value_ir);

    // Heart beat detection using value for red LED
    float current_value = high_pass_filter.process(current_value_red);
    float current_diff = differentiator.process(current_value);

    // Valid values?
    if (!isnan(current_diff) && !isnan(last_diff)) {

      Serial.print("210! current_diff =");
      Serial.println(current_diff);
      Serial.print("212! last_diff =");
      Serial.println(last_diff);

      // Detect Heartbeat - Zero-Crossing
      if (last_diff > 0 && current_diff < 0) {
        crossed = true;
        crossed_time = millis();
      }

      if (current_diff > 0) {
        crossed = false;
      }

      // Detect Heartbeat - Falling Edge Threshold
      if (crossed && current_diff < kEdgeThreshold) {
        if (last_heartbeat != 0 && crossed_time - last_heartbeat > 300) {
          // Show Results
          int bpm = 60000 / (crossed_time - last_heartbeat);
          float rred = (stat_red.maximum() - stat_red.minimum()) / stat_red.average();
          float rir = (stat_ir.maximum() - stat_ir.minimum()) / stat_ir.average();
          float r = rred / rir;
          float spo2 = kSpO2_A * r * r + kSpO2_B * r + kSpO2_C;

          if (bpm > 50 && bpm < 250) {
            // Average?
            if (kEnableAveraging) {
              int average_bpm = averager_bpm.process(bpm);
              int average_r = averager_r.process(r);
              int average_spo2 = averager_spo2.process(spo2);

              // Show if enough samples have been collected
              if (averager_bpm.count() >= kSampleThreshold) {
                Serial.print("Time (ms): ");
                Serial.println(millis());
                Serial.print("246! Heart Rate (avg, bpm): ");
                Serial.println(average_bpm);
                Serial.print("248! R-Value (avg): ");
                Serial.println(average_r);
                Serial.print("250! SpO2 (avg, %): ");
                Serial.println(average_spo2);
                displayMeasuredValues(false, average_bpm, average_spo2);
              }
            }
            else {
              Serial.print("Time (ms): ");
              Serial.println(millis());
              Serial.print("258! Heart Rate (current, bpm): ");
              Serial.println(bpm);
              Serial.print("R-Value (current): ");
              Serial.println(r);
              Serial.print("262! SpO2 (current, %): ");
              Serial.println(spo2);
              displayMeasuredValues(false, bpm, spo2);
            }
          }

          // Reset statistic
          stat_red.reset();
          stat_ir.reset();
        }

        crossed = false;
        last_heartbeat = crossed_time;
      }
    }

    last_diff = current_diff;
  } else {
    displayMeasuredValues(true, 0, 0);
  }
}

void initDrawScreen(void) {
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(WHITE);        // Draw white text
  display.setCursor(0, 0);            // Start at top-left corner
  display.println(F("    Taste The Code"));
  display.println(F(""));
  display.setCursor(5, display.getCursorY());
  display.setTextSize(2);
  display.println(F("BPM  %SpO2"));
  display.display();
}

bool display_reset = true;

void displayMeasuredValues(bool no_finger, int32_t beatAvg, int32_t spo2) {
  display.setCursor(5, 35);
  display.setTextColor(WHITE, BLACK);
  if (no_finger) {
    display.setTextSize(2);
    display.println(F("NO Finger            "));
    display_reset = true;

    //Serial.print("299 beatAvg =");
    Serial.print("309NO Finger???");  //shown on OLED, and output;

    display.display();
  } else if (beatAvg < 30 && display_reset) {
    display.setTextSize(2);
    display.println(F("Pls.  Wait             "));
    display_reset = false;
    display.display();

    Serial.print("318! beatAvg =");  /// 318 (shown on OLED, and output;) beatAvg < 30???165 current_value_red =869889.00
    Serial.println(beatAvg);

  } else if (beatAvg >= 30) {   /// never happen?

    Serial.print("323! beatAvg =");
    Serial.println(beatAvg);

    display.setTextSize(2);
    display.println(F("             "));

    //.............
    display.setCursor(5, 35);
    display.setTextSize(1);
    display.print("beatAvg=");

    //..................

    display.setCursor(5, 45);  //5,35
    display.setTextSize(2);
    display.print(beatAvg);

    Serial.print("340 beatAvg =");  //shown on OLED, doesn't output, whp?
    Serial.print(beatAvg);

    display.print(F(" "));
    if (spo2 >= 20 && spo2 <= 100) {
      display.print(spo2);

      Serial.print("347! spo2 =");  //shown on OLED, doesn't output, whp?
      Serial.print(spo2);

    } else {
      display.print(F("--"));

      Serial.print("353! spo2 =");  //shown on OLED, doesn't output, whp?
      Serial.print(spo2);
    }
    display.println(F("    "));
    display.display();
  }
}

filters.h:



///filters.h

#ifndef FILTERS_H
#define FILTERS_H

/**
 * @brief Statistic block for min/nax/avg
 */
class MinMaxAvgStatistic {
 float min_;
 float max_;
 float sum_;
 int count_;
public:
 /**
  * @brief Initialize the Statistic block
  */
 MinMaxAvgStatistic() :
  min_(NAN),  /// nan-Not-A-Number-Returns a quiet NaN (Not-A-Number) value of type double.
  max_(NAN),
  sum_(0),
  count_(0){}

 /**
  * @brief Add value to the statistic
  */
 void process(float value) {  
  min_ = isnan(min_) ? value : min(min_, value);  // isnan-Is Not-A-Number-Returns whether x is a NaN (Not-A-Number) value.
  max_ = isnan(max_) ? value : max(max_, value);
  sum_ += value;
  count_++;
 }

 /**
  * @brief Resets the stored values
  */
 void reset() {
  min_ = NAN;
  max_ = NAN;
  sum_ = 0;
  count_ = 0;
 }

 /**
  * @brief Get Minimum
  * @return Minimum Value
  */
 float minimum() const {
  return min_;
 }

 /**
  * @brief Get Maximum
  * @return Maximum Value
  */
 float maximum() const {
  return max_;
 }

 /**
  * @brief Get Average
  * @return Average Value
  */
 float average() const {
  return sum_/count_;
 }
};

/**
 * @brief High Pass Filter 
 */
class HighPassFilter {
 const float kX;
 const float kA0;
 const float kA1;
 const float kB1;
 float last_filter_value_;
 float last_raw_value_;
public:
 /**
  * @brief Initialize the High Pass Filter
  * @param samples Number of samples until decay to 36.8 %
  * @remark Sample number is an RC time-constant equivalent
  */
 HighPassFilter(float samples) :
  kX(exp(-1/samples)),
  kA0((1+kX)/2),
  kA1(-kA0),
  kB1(kX),
  last_filter_value_(NAN),
  last_raw_value_(NAN){}

 /**
  * @brief Initialize the High Pass Filter
  * @param cutoff Cutoff frequency
  * @pram sampling_frequency Sampling frequency
  */
 HighPassFilter(float cutoff, float sampling_frequency) :
  HighPassFilter(sampling_frequency/(cutoff*2*PI)){}

 /**
  * @brief Applies the high pass filter
  */
 float process(float value) { 
  if(isnan(last_filter_value_) || isnan(last_raw_value_)) {
   last_filter_value_ = 0.0;
  }
  else {
   last_filter_value_ = 
    kA0 * value 
    + kA1 * last_raw_value_ 
    + kB1 * last_filter_value_;
  }
   
  last_raw_value_ = value;
  return last_filter_value_;
 }

 /**
  * @brief Resets the stored values
  */
 void reset() {
  last_raw_value_ = NAN;
  last_filter_value_ = NAN;
 }
};

/**
 * @brief Low Pass Filter 
 */
class LowPassFilter {
 const float kX;
 const float kA0;
 const float kB1;
 float last_value_;
public:
 /**
  * @brief Initialize the Low Pass Filter
  * @param samples Number of samples until decay to 36.8 %
  * @remark Sample number is an RC time-constant equivalent
  */
 LowPassFilter(float samples) :
  kX(exp(-1/samples)),
  kA0(1-kX),
  kB1(kX),
  last_value_(NAN){}

 /**
  * @brief Initialize the Low Pass Filter
  * @param cutoff Cutoff frequency
  * @pram sampling_frequency Sampling frequency
  */
 LowPassFilter(float cutoff, float sampling_frequency) :
  LowPassFilter(sampling_frequency/(cutoff*2*PI)){}

 /**
  * @brief Applies the low pass filter
  */
 float process(float value) {  
  if(isnan(last_value_)) {
   last_value_ = value;
  }
  else {  
   last_value_ = kA0 * value + kB1 * last_value_;
  }
  return last_value_;
 }

 /**
  * @brief Resets the stored values
  */
 void reset() {
  last_value_ = NAN;
 }
};

/**
 * @brief Differentiator
 */
class Differentiator {
 const float kSamplingFrequency;
 float last_value_;
public:
 /**
  * @brief Initializes the differentiator
  */
 Differentiator(float sampling_frequency) :
  kSamplingFrequency(sampling_frequency),
  last_value_(NAN){}

 /**
  * @brief Applies the differentiator
  */
 float process(float value) {  
   float diff = (value-last_value_)*kSamplingFrequency;
   last_value_ = value;
   return diff;
 }

 /**
  * @brief Resets the stored values
  */
 void reset() {
  last_value_ = NAN;
 }
};

/**
 * @brief MovingAverageFilter
 * @tparam buffer_size Number of samples to average over
 */
template<int kBufferSize> class MovingAverageFilter {
 int index_;
 int count_;
 float values_[kBufferSize];
public:
 /**
  * @brief Initalize moving average filter
  */
 MovingAverageFilter() :
  index_(0),
  count_(0){}

 /**
  * @brief Applies the moving average filter
  */
 float process(float value) {  
   // Add value
   values_[index_] = value;

   // Increase index and count
   index_ = (index_ + 1) % kBufferSize;
   if(count_ < kBufferSize) {
    count_++;  
   }

   // Calculate sum
   float sum = 0.0;
   for(int i = 0; i < count_; i++) {
     sum += values_[i];
   }

   // Calculate average
   return sum/count_;
 }

 /**
  * @brief Resets the stored values
  */
 void reset() {
  index_ = 0;
  count_ = 0;
 }

 /**
  * @brief Get number of samples
  * @return Number of stored samples
  */
 int count() const {
  return count_;
 }
};

#endif // FILTERS_H

Where is Serial.begin?

1 Like

Some are behind exception code such as "if" etc and unless the condition is valid it will not print. It would get you a better answer if we knew what was not printing.

1 Like

Thanks.
that included in SerialSettingS3(); which I forget to attach, I modified it back now.

Thanks.
all Serial.print() ; inside void displayMeasuredValues(bool no_finger, int32_t beatAvg, int32_t spo2) { from line 300 to line 359; didn't print except: Serial.print("309NO Finger???");.
that are line number followed by "!" like: Serial.print("318! beatAvg ="); / Serial.print("323! beatAvg ="); etc.

I verified that part code called by OLED shown that content.