Pandemic Ventilator

1.6K82

Intro: Pandemic Ventilator

I am not an engineer, a roboticist, a mathematician, a medical person, an electrician, or a computer scientist.

I am a Maker.

There are many things about this project that still require refinement, and I will continue to develop it, however, this is the minimum viable product, suggestions on how to make improvements are welcome- especially to the code.

This is after many iterations of development, and many of my ideas have been attempted and discarded already.

I started with an idea. Then I drafted some potential courses of action, did a lot of research, and discovered that one of my courses of action is a near-perfect match to the MIT grad student project from 2010. The iteration documented here resembles the MIT project strongly, but in all of my research, I did not come across one single piece of documentation that included a build list or code.

There are a lot of forums out there with a lot of posters making suggestions and posting ideas, but not one piece of buildable documentation.

This is mine.

STEP 1: PVC Chassis

I selected PVC as my construction material. It is easy to work with, relatively low cost, and I already have all of the tools to work it.

I cut the PVC with a table saw and a miter saw. I used a 2-1/2 inch hole saw and a 3-1/2 inch hole saw for the round holes. I used a basic sander to round off the end of the actuator arm.

I assembled the chassis with construction screws and PVC cement.

The base and back are 14-inch x 8-inch.

The bag supports are 5-inch squares with 3-1/2 inch and 2-1/2 inch holes drilled center.

I cut off a single corner of each bag support at a 45° angle to support the circuit board backer.

The circuit board backer is 2-1/4 inch by 5-3/4 inch.

The third support is 1-1/2 inch wide by 5inch at its longest, cut at a 45° angle.

The motor controller supports (not shown) are 1/2 inch square by 1-3/4 inch.

The actuator arm is 6-1/4 inch cut from the 1-1/2inchx3/4 inch board. It has been cut to 1-inch width.

STEP 2: Electronics

After breadboarding, I moved the plan to a perfboard, marking the board with permanent markers. The circuit paths and the headers are shown in the picture, the NODEMCU header rows are not shown in this picture.

I soldered header rows to the perfboard to mount the NODEMCU microcontroller.

I soldered another header row to attach the OLED and the motor-controller pins

The OLED display SCL is attached to Pin D2, OLED SDA is attached to D3, VCC to 3v, and GND to ground.

The Potentiometer center pin is attached to AO, set as analog read, the potentiometer two outer pins are attached to 3v and GND,

The pressure sensor SCL pin is attached to pins D2, SDA pin is attached to D3, Vcc to 3v and GND to ground.

The motor controller is connected to pins D5 and D6, set as digital out, 3v and GND. These pins can also be used as PWM, but with the motor I am using I could only get proper performance at PWM 1000, which is the same as digital HIGH.

You may note that I've not included physical controls, due to my limitations and the pin limitations of the NODEMCU I opted for an APP-based control, using Blynk to monitor and send/ receive variables. Suggestions are welcome.

( I had initially built a 4 button control, but that uses 4 pins, 2 of which I needed for the motor-controller, plus power) One refinement I intend to work is using a rotary encoder with push-button to drive the menu, I assess that it will only require 2 pins that are available plus power).

You may also notice in the photo the 12v-5v buck converter is not present- I had a bad buck converter, and at the time of the photo I was using a 5v wall adapter for the microcontroller and the 12v power directly attached to the motor-controller.

STEP 3: Mechanics

I am using a single drive shaft coupled to the motor output shaft at one end and the potentiometer at the other end. The actuator arm is coupled to the shaft with two wheel adapters.

In this iteration, I am using two pins through the wheel adapters and the actuator arm to allow the wheel adapters to turn the actuator arm. This works as a poor-mans clutch allowing me to de-couple the arm from the shaft as needed. Once all code calibrations are finalized these pins can be replaced with bolts.

Using a rotary tool I machined flat surfaces on the shaft to allow the set screws to engage and prevent shaft slippage.

You may note the lack of any bearing surface for the shaft, by good fortune the wheel adapters sit on the base and provide shaft support. I could have added additional bearings, but this seemed like a less complex solution.

A note about aligning the shaft from motor to potentiometer. I've made the potentiometer mount using aluminum angle stock. Ensure the mount is the same width and the center of the hole is positioned to match the output shaft of the motor. This will make aligning the motor, shaft, and potentiometer much easier.

I connected the CPAP hose to the bag and the mask. The Adafruit pressure sensor is mounted by using a drill and 5/132nd drill bit to make a hole through the silicone piece at the end of the hose. The sensor air port is pushed into the hole and I've secured it tightly (but gently) with a zip tie around the hose.

STEP 4: App

To simplify the controls and preserve available pins on the micro-controller I choose to use an APP for monitoring and setting variables.

I've used the Blynk app to accomplish this. I realize this may not be the best austere-environment solution, but for this stage of my build it reduced some complexity and otherwise achieves my design goals.

Virtual Pins described Left to right, top to bottom

Label, Widget type, Virtual Pin, Code variable

Version- Text input- V7 - version Control

Start/Stop- Button slider- V1- runStop

Pressure- SuperChart- V10- pressure

Pressure- Value Display- V10- pressure

BPM- Numeric Input - V2 - BPMRate

TidalVolume - Numeric INput - V3- TidalVolume

PEEP- Numeric Input- V4- PEEP

LowReleased- Numeric Input- V9- lowReleased

HighCompressed- Numeric Input- V8- highCompressed

STEP 5: The Code- I Know That I Do Not Know What I Do Not Know-

*This is my code so far.  

Im sure the professionals will take a look and determine what I already know:

 That I dont know what I am doing,

This code is not complete, I am still working through some details, 

and I welcome any expert advice that helps me learn and make a better end result.

Thanks for looking,   If You use my code I would really like to hear from you, 

I'd like to see your end  result.  Stay safe, stay well.  -Drew

#define BLYNK_PRINT Serial 
#define analogPin A0;
#define PCP1 14        // NodeMCU  pin D3-GPIO0
#define PCP2 12        // NodeMCU  pin D4-GPIO2
#include <blynksimpleesp8266.h>
//#include <onewire.h>
//#include <spi.h>
#include <wire.h>//adafruit I2C
#include "Adafruit_MPRLS.h"//presuure sensor
#include <adafruit_gfx.h>  /display
#include <adafruit_ssd1306.h> //display
#define RESET_PIN  -1  
#define EOC_PIN    -1  
Adafruit_MPRLS mpr = Adafruit_MPRLS(RESET_PIN, EOC_PIN);
BlynkTimer timer;
Adafruit_SSD1306 display(-1);
char auth[] = "you need a blynk project authentication code";
char ssid[] = "wirelessSSID";
char pass[] = "wifi password";
float BPMCalc;
int BPMMotor;
float BPMRate;
int PEEP;
int runStop;
int TidalVolume;
float pressure;
int highCompressed;
int lowReleased;
int strokeAvg;
int mPosition;
int motorReverse;
float pressure_hPa;
float interval;
// motorPosition
int mPos;
int waitingCount;
unsigned long previousMillis;
unsigned long currentMillis;
String motorDirection = "Stopped";
// end motorPosition
String versionControl = "08f";
BLYNK_WRITE(V1) {  
  runStop = param.asInt();
  startSwitch(); 
}
BLYNK_WRITE(V2) {  
  BPMRate = param.asInt();
  startSwitch();
}
BLYNK_WRITE(V3) {  
  TidalVolume = param.asInt();
  startSwitch();   
}
BLYNK_WRITE(V4) {  
  PEEP = param.asInt();
  startSwitch();   
}
BLYNK_WRITE(V9) {  
  lowReleased = param.asInt();
}
BLYNK_WRITE(V8) {  
  highCompressed = param.asInt();
}
BLYNK_WRITE(V10) {  
  pressure = param.asFloat();
}
void setup() {
	Serial.begin(115200);
	Blynk.begin(auth, ssid, pass);
	while (Blynk.connect() == false) {}       // Wait until connected    
	display.begin(SSD1306_SWITCHCAPVCC, 0x3C);    // initialize with the I2C addr 0x3C
	display.clearDisplay();  
	display.display();                // clears the lcd
	timer.setInterval(1000, pressureCheck);
	if (! mpr.begin()) {
	Serial.println("check pressure sensor");
	while (1) {
	  delay(10);
	}
	}
	Serial.println("starting");
	// sets the arm rotation limits
	highCompressed = 410;
	Blynk.virtualWrite(8, highCompressed);  
	lowReleased = 273;
	Blynk.virtualWrite(8, lowReleased); 
	strokeAvg = (highCompressed + lowReleased) / 2;
	Blynk.virtualWrite(7, versionControl);
	// sets the breath per minute
	BPMCalc = (30000.000/ BPMRate);
	TidalVolume = 500;
	PEEP = 100;
	splashScreen();
	timer.setInterval(3300, startSwitch);
	timer.setInterval(100, motorPosition);
	pinMode (PCP1, OUTPUT);
	pinMode (PCP2, OUTPUT);
}
void startSwitch() {
	if (runStop == 1) {
	menuMain();
	Blynk.virtualWrite(1, runStop);            
	} else if (runStop != 1) {
	runStop = 0;
	Blynk.virtualWrite(1, runStop);
	display.clearDisplay(); 
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(1,10);
	display.print(versionControl);
	display.println(": OFF");
	display.display(); 
  }
}
void splashScreen() {
	Serial.println(versionControl);
	display.clearDisplay(); 
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(0,10);
	display.println(versionControl);
	display.display(); 
}
void menuMain() {
	display.clearDisplay();   
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(0,24);
	display.println("BPM");
	display.setCursor(50,24);
	display.println("TV");
	display.setCursor(98,24);
	display.println("PE");
	display.setTextSize(2);
	display.setCursor(0,1);
	display.println(BPMRate);
	//  Serial.println("BPM is:");
	//  Serial.println(BPMRate);  
	display.setCursor(40,1);
	display.println(TidalVolume);
	//  Serial.println("TV is:");
	//  Serial.println(TidalVolume);  
	display.setCursor(90,1);
	display.println(PEEP);
	//  Serial.println("PEEP is:");
	//  Serial.println(PEEP);
	display.display();
}
void pressureCheck() {
    pressure_hPa = mpr.readPressure();
    pressure = pressure_hPa / 68.947572932;
	Serial.println ("BPMRate");Serial.println (BPMRate);
    Serial.println ("pressure");Serial.println (pressure);
        Blynk.virtualWrite(10, pressure);
}
void motorPosition() {
	BPMCalc = 30/ BPMRate;
	mPos = analogRead(A0);
    Blynk.virtualWrite(1, runStop);
	if (runStop != 1) {
    motorControl(4);
    waitingCount = 0 ;
    Serial.print("Stopped from button at: ");
    Serial.println(mPos);
	} else {
    if (motorDirection == "Stopped"){ //Deal with restarting while in the middle of a stroke by releasing
      if (mPos > strokeAvg) {
        motorControl(1); //release
        Serial.print("Startup release. Position: ");
        Serial.println(mPos);
      } else if (mPos < strokeAvg) {
        motorControl(0); //set compress
        Serial.print("Startup compress. Position: ");
        Serial.println(mPos);
      }
    } else if ((motorDirection == "Compressing") && (mPos >= highCompressed)) { // if motor is compressing and has hit end of range, switch direction
      motorControl(1); //release
      Serial.print("Compress limit hit, releasing with no pause at: ");
      Serial.println(mPos);
	} else if ((motorDirection == "Releasing") && (mPos <= lowReleased)) { // if motor is releasing and hit top of range, stop and set wait
      motorControl(3); //set wait
      Serial.print("Release limit hit, resting for ");
      Serial.print ("BPMCalc"); Serial.println (BPMCalc);
      Serial.println(" seconds");
      previousMillis = millis();
    } else if (motorDirection == "Waiting") {
              BPMCalc = (30000.000/ BPMRate);
              Serial.println("Rest done, starting to compress");
              previousMillis = millis();
              motorControl(0);
              } 
            }
        }
    }
int motorControl(int action) {
  if (action == 0) {
    digitalWrite(PCP2,HIGH);
    digitalWrite(PCP1, 0 );
    motorDirection = "Compressing";
  } else if (action == 1) {
    digitalWrite(PCP2, 0 );
    digitalWrite(PCP1, HIGH);//HIGH
    motorDirection = "Releasing";
  } else if (action == 3) {
    digitalWrite(PCP2, 0 );
    digitalWrite(PCP1, 0);
    motorDirection = "Waiting";
  } else {
    digitalWrite(PCP2, 0 );
    digitalWrite(PCP1, 0 );
    motorDirection = "Stopped";
  }
}
void loop() {
  Blynk.run();
  timer.run();
currentMillis = millis();
startSwitch();
}</p>

Comments

I'm 74 . I'm hoping there will be a ventilator available for me or the Missus if we catch it . However it looks like only 50% come out of those that have to be hospitalised . The older are more susceptible especially if they have other problems to boot.
So I reckon if we go to hospital we have only 1 chance in 3 of coming out alive so I don't think a respirator will alter that much or I would build one.. It seems to be a breakdown of the lung linings to allow fluids to be drawn from the blood and the bad blood coming in is not cleaned and oxygenated as it should be by the lungs. You drown in your own fluids and the carbon dioxide builds up to kill you. Thats probably why they are discussing now who to try to save and who does not have a chance.
I think the instructable is excellent though and well thought out .Thank You.
You have one for your kids should the need arise and they are the ones to save.