A project log for Open Source Spirometer using ESP32

An open source standalone spirometer (measures tidal volume) made using Olimex ESP32-POE-ISO and Sensirion SFM3300 Flow Sensor.

PatrickPatrick 04/04/2020 at 00:590 Comments

Starting with an Olimex ESP32-POE-ISO on hand for a different project, I started prototyping around that. I already had a test board, which broke out the i2c for OLED, CANBUS and RS232 for 2nd serial port. 

Since I had the pins available, used dual i2c, one for OLED (using SSD1306 library) and one for sensor.

The SFM3300 had some reference code that I borrowed from here. The CRC-8 function did not work, but I managed to fix that. 

#define POLYNOMIAL 0x131 //P(x)=x^8+x^5+x^4+1 = 100110001

uint8_t crc8(uint8_t data[], uint8_t nbrOfBytes)  {
  uint8_t crc = 0;
  uint8_t byteCtr;
  //calculates 8-Bit checksum with given polynomial
  for (byteCtr = 0; byteCtr < nbrOfBytes; ++byteCtr) { 
    crc ^= (data[byteCtr]);
    for (uint8_t bit = 8; bit > 0; --bit) { 
      if (crc & 0x80) {
        crc = (crc << 1) ^ POLYNOMIAL;
      else {
        crc = (crc << 1);
  return crc;

This sensor outputs in liters per minute, and has a max range of ±250 l/min.

The above is me breathing into the sensor, inhales are positive, exhales are negative. In discussions with doctors, this device might be placed at the patient, where it will see bidirectional flow like this. Or it could be on the inspiratory or expiratory branch of the breathing circuit, in which case, flow will only be in one direction. 

Inside the loop function, I'm looking for positive flow. When I see 2 readings average above .125l/min, I start the measurement. Basically I'm using the timers in microseconds (µs) to determine elapsed time. Then I average the current measurement with the last and multiply it by the elapsed time. Which is about 1ms. Probably way overkill, but it's my starting point. Every 50ms, I'm shooting the value out the serial port on USB so I can plot it. 

      tidalVolume = tidalVolume / 60000; // volume in mL (factor out microsec & minutes)
      if (tidalVolume > 10.0) {
        lastTidalVolume = tidalVolume;
        lastMilliSec = millis();
        hwSerial.print("Duration = ");
        hwSerial.println((double)(lastUs - startUs)/1000000);
        hwSerial.print("TidalVolume = ");
        sTidalVolume = String(tidalVolume,0);
        updateDisplay(double(millis() - lastMilliSec)/1000.0);

When the flow drops down to zero, calculate the tidal volume in mL, by factoring in liters per minute and my microseconds. Code is a bit sloppy here, I know I should be using constants.

I check it to see if it's a real value, a setpoint of 10 is my current minimum breath value. This filters out the noise if the device is just sitting there with no breath.  I also update the display with the tidal volume. And start keeping track of how long it's been since last breath. 

The rest of the code is pretty self-explanatory. I borrowed some stuff for the WiFi from

Besides a bit of code cleanup, code is functionally RTG.