Gord Payne
Published © CC BY-NC-SA

Village-Vent - Emergency Ventilator

Build a robust emergency ventilator, for under $50US, in a couple of days, with easily obtainable parts! Get out the Arduino!

IntermediateFull instructions provided2,315
Village-Vent - Emergency Ventilator

Things used in this project

Hardware components

Windshield Wiper Motor - 12V
×1
Arduino Nano R3
Arduino Nano R3
×1
Surplus Computer AT Power Supply 12V/5V
×1
Adult size bag-valve-mask (BVM) (design can be modified for child and pediatric use)
×1
Alphanumeric LCD, 20 x 4
Alphanumeric LCD, 20 x 4
optional - all display can be provided in the Arduino Serial Monitor on laptop
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
any inexpensive SPST momentary contact pusbutton will work
×7
Resistor 10k ohm
Resistor 10k ohm
×8
Power MOSFET N-Channel
Power MOSFET N-Channel
Rated for motor voltage and current of your wiper motor
×1
1N4007 – High Voltage, High Current Rated Diode
1N4007 – High Voltage, High Current Rated Diode
Rated for motor voltage and current of your wiper motor (4 to 6 amps)
×1
Door hinge
Whatever you can find
×1
Foam toy ball, like a Nerf. 10cm to 12cm diameter
For the plunger
×1
Bag Valve Mask (BVM) - Adult size
you'll build your frame to it's dimensions. Like Ambu-bag(TM) Can modify design for child and pediatric size
×1
Plywood for frame - 5/8 inch
Whatever material you have
×1
solid core wire (22 gauge)
×1
16 gauge wire
For the motor and power supply connections
×1
Assorted hardware and glue
Breadboard or solder board, solder, assorted steel or aluminum bar stock, wood glue, silicone or construction adhesive and fasteners(wood screws, nuts, bolts etc)
×1
Aluminum or Steel Bar Stock
1/8" x 3/4" bar stock. For plunger arm. Whatever you have available.
×1
L-brackets - small
To mount plunger arm to motor
×2
Copper pipe, half-inch about 12cm
centre of plunger ball
×1
Steel rod, 16cm length by 6.4mm diameter
spindle for the ball
×1
Light Dependent Resistor (LDR)
for motor pausing (breath pause)
×2
LED (generic)
LED (generic)
white colour is good
×2
Resistor 220 ohm
Resistor 220 ohm
×2
2 conductor speaker wire, 2 metres( 6 feet
For connecting LDRs and LEDs to the control panel.
×1
Corrugated Cardboard
Mounting panels for the LDRs and LEDs
×1
Male and Female header Pins
For connecting the LEDs and LDRs to the control panel circuit board.
×1
Prototype Board 15cm x 9cm approximately
For the control panel
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Table saw
Drill / Driver, Cordless
Drill / Driver, Cordless
Router with roundover bit
to remove sharp edges from compressor supports and other parts that may have rough edges against the BVM bag.
Jigsaw
For cutting the BVM cradle (the 'ribs')

Story

Read more

Custom parts and enclosures

Ventilator Parts

These parts are particular to my motor and ventilator bag. They are provided as a guideline for you. Your own particular parts will guide your actual design.

Parts Vcarve files

These are general starter version of Vcarve CAD files. These parts are particular to my motor and ventilator bag. They are provided as a guideline for you. Your own particular parts will guide your actual design. You will need to refine them and specify tool paths etc if you wish to mill the parts.

Schematics

Control Panel Schematic

Schematic for Village Vent.

Code

VillageVent Arduino Sketch

Arduino
Code to run the motor for the ventilator. See settings at top of code so you can adjust the code for your specific motor performance and machine settings.
//   Village-Vent       Arduino-based Emergency Ventilator
// 2020 - Gordon Payne - IP Commons - Not for commercial use. Charity/Humanitarian Use ONLY
// Entry for United Nations Detect and Protect Technology Challenge on Hackster.io
// This code and design are provided AS IS.
// The designer makes no claims to the SAFETY, EFFICACY, or appropriateness for your use
// *** NOT INTENDED FOR HUMAN USE ***
// **** USE AT OWN RISK! *****
// Based on 12V windshield wiper motor
// and manual Ambu-bag
// Input Voltage: 12V recommended power supply current 1 to 10 amps
// This version allows setting inhale/exhale ratios(seconds) and inhale power, exhale power (% of max motor power)
// and breath volume adjustment.
//-----MACHINE PREFERENCES----
// set these values for your particular motor and preferred minimum and maximum breaths per minute
const int iPwrMax = 100; // power maximum and minimum values duty cycles
const int iPwrMin = 40;
const int ePwrMax = 100;
const int ePwrMin = 40;
const int PwrIncrement = 10;// 10% power change per increment (percent change in duty cycle)
const float iDurMin = 1; //inhale minimum and maximum durations in seconds
const float iDurMax = 5;
const float eDurMin = 1; //inhale minimum and maximum durations in seconds
const float eDurMax = 5;
const float durIncrement = 0.5;// duration increments in 0.5 seconds
const int strokesPerMinuteFullOn = 48; // the number of cycles per minute of the motor on full power (test your particular motor)

const boolean displayAttached = true; // set to false if no LCD display attached. Output will be through Arduino Serial Monitor
const int eLDRmin = 80; // lowest light level before change exhale or inhale // modify for your circuits
const int eLDRmax = 680; // highest light level to change exhale or inhale
const int iLDRmin = 80; // lowest light level before change exhale or inhale // modify for your circuits
const int iLDRmax = 600; // highest light level to change exhale or inhale

//-------------------
/* Wiring
  bStop    2
  bInhale seconds   3    7
  Motor   9 PWM
  bBacklight 6
  bexHale seconds     7    3
  bRun    8
  bInPower A2;// a2
  bExPower A3;// a3
  2 LEDs wired to 5V and Gnd through 220R resistors (for LDRs)
  Inhale LDR A0
  Exhale LDR A1
  Display SCL and SDA, A4 and A5
*/

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F, 20, 4);// address may be 0x23 dependent upon display
int bThresh = 250; //Button detection threshold in milliseconds
int bStop = 2;// pin definitions
int bInPwr = A3;//3
int motor = 9;
int bBacklight = 6;
int bExPwr = A2;//7
int bRun = 8;
int binRatio = 3;//a2
int bexRatio = 7;// a3
int ldrExhale = A0;
int ldrInhale = A1;
boolean lightOn = true;
String inString, exString;

int bSval, bPIval, bBval, bPEval, bRval, binDval, bexDval; // high or low from button press
int ldrEval, ldrIval;
/*int ldrEvMax = 0;
  int ldrEvMin = 1023;
  int ldrIvMax = 0;
  int ldrIvMin = 1023;*/
boolean inhale = false;
boolean exhale = false;
long lastBs = millis();// last button press times
long lastBPi = millis();
long lastBb = millis();
long lastBPe = millis();
long lastBr = millis();
long lastbIn = millis();
long lastbEx = millis();
long inStart, exStart; //time marker for inhale and exhale
long curTime, diff;
int curIpwr = 80;//iPwrMin; // current inhale power setting
int curEpwr = 80;//ePwrMin; // current exhale power setting
int newIpwr = curIpwr;// adjusted non-active power levels
int newEpwr = curEpwr;
int maxLDR = 0;
int minLDR = 1023;
boolean runStatus = false;
float curIdur = iDurMin;// current inhale duration in seconds

float curEdur = eDurMin;// current exhale duration in seconds
float newIdur = curIdur;
float newEdur = curEdur;
long inSeconds, exSeconds;

//exString = String(curEdur);
//exString = exhString.substring(0, 3);
int dutyCycle = 0; // pwm dutyCycle based on curBpm



void setup() {
  // adjust internal timer for PWM on pins 9 and 10 to reduce motor whine.
  TCCR1B = TCCR1B & B11111000 | B00000001;    // set timer 1 divisor to     1 for PWM frequency of 31372.55 Hz BEST
  // TCCR1B = TCCR1B & B11111000 | B00000010;    // set timer 1 divisor to     8 for PWM frequency of  3921.16 Hz
  pinMode(bStop, INPUT);
  pinMode(bInPwr, INPUT);
  pinMode(bBacklight, INPUT);
  pinMode(bExPwr, INPUT);
  pinMode(bRun, INPUT);
  pinMode(binRatio, INPUT);
  pinMode(bexRatio, INPUT);
  pinMode(motor, OUTPUT);
  pinMode(ldrExhale, INPUT);
  pinMode(ldrInhale, INPUT);
  analogWrite(motor, 0); // motor starts at OFF
  Serial.begin(9600);
  lcd.begin();
  exString = String(curEdur);
  exString = exString.substring(0, 3);
  inString = String(curIdur);
  inString = inString.substring(0, 3);
  // display initial text and settings
  lcd.backlight();// Turn on the blacklight
  lcd.setCursor(5, 2);
  lcd.print("** STOP **");
  inSeconds = int(curIdur * 1000);
  exSeconds = int(curEdur * 1000);
  updateSettings();
}

void loop() {
  // scan all the buttons for a press
  bSval = digitalRead(bStop);
  bPEval = digitalRead(bInPwr);
  bBval = digitalRead(bBacklight);
  bPIval = digitalRead(bExPwr);
  bRval = digitalRead(bRun);
  binDval = digitalRead(binRatio);
  bexDval = digitalRead(bexRatio);
  ldrEval = analogRead(ldrExhale);// scan the LDRs for the position of the compressor
  ldrIval = analogRead(ldrInhale);
  // you may want to use these diagnostics to tune your LDRs
  //  if (ldrEval > maxLDR) maxLDR = ldrEval;
  // if (ldrEval < minLDR) minLDR = ldrEval;
  /* Serial.print(ldrEval);
    Serial.print("\t");
    Serial.println(ldrIval);*/
  //Serial.print("\t");
  //Serial.println(minLDR);


  ///////////// LDRs
  /* if ((ldrEval > LDRmin) && (ldrEval < 450)) {
     Serial.println("    exhale");
    }
    if ((ldrIval > LDRmin) && (ldrEval < 450)) {
     Serial.println("inhale");
    }*/
  if (runStatus) { // if motor running
    if ((ldrIval > iLDRmin) && (ldrIval < iLDRmax) && inhale == true) { // detect motor at max inhale position
      if (millis() - inStart < inSeconds) {
        //  Serial.println("in inhale");
        analogWrite(motor, 0); // pause motor until inhale time reached
      } else { // start an exhale cycle
        inhale = false;
        exhale = true;
        dutyCycle = calcDutyCycle(curEpwr);
        analogWrite(motor, dutyCycle); // start motor at exhale speed
        exStart = millis();// set exhale cycle time marker
      }
    }

    if ((ldrEval > eLDRmin) && (ldrEval < eLDRmax) && exhale == true) { // detect motor at max exhale position
      if (millis() - exStart < exSeconds) {
        // Serial.println("              in exhale");
        analogWrite(motor, 0); // pause motor until exhale time reached
      } else { // start an inhale cycle
        exhale = false;
        inhale = true;
        dutyCycle = calcDutyCycle(curIpwr);
        analogWrite(motor, dutyCycle); // start motor at inhale speed
        inStart = millis();// set inhale start time
      }
    }
  }// end of run status
  //////// BUTTONS ////////////////////////
  if (millis() - lastBs > bThresh && bSval == HIGH) {// Stop pressed
    //Serial.println("STOP");
    if (lightOn == false) {
      lightOn = true;
      setBacklight();
    } else {
      runStatus = false;
      retract();// fully open compressor
      //digitalWrite(motor,0); //remove this when ldrs back
      updateRunDisplay();
      if (displayAttached == false) displaySerMon();
    }
    lastBs = millis();
  }
  if (millis() - lastBPe > bThresh && bPEval == HIGH) {// Breath Exhale Power
    if (lightOn == false) {
      lightOn = true;
      setBacklight();
    } else if (newEpwr < ePwrMax ) {
      //Serial.println("breath ex power");
      newEpwr = newEpwr + PwrIncrement;
    } else {
      newEpwr = ePwrMin; // reset to minimum power
    }
    //updateEpwr();
    updateSettings();
    lastBPe = millis();
  }



  if (millis() - lastBb > bThresh && bBval == HIGH) {// Backlight pressed
    // Serial.println("backlight");
    lightOn = !lightOn;
    setBacklight();
    lastBb = millis();
  }

  if (millis() - lastBPi > bThresh && bPIval == HIGH) {// Breath Inhale Power
    if (lightOn == false) {
      lightOn = true;
      setBacklight();
    } else if (newIpwr < iPwrMax ) {
      // Serial.println("breath in power");
      newIpwr = newIpwr + PwrIncrement;
    } else {
      newIpwr = iPwrMin; // reset to minimum power
    }
    updateSettings();

    lastBPe = millis();
  }

  if (millis() - lastBr > bThresh && bRval == HIGH) {// Run pressed
    // Serial.println("RUN");
    if (lightOn == false) {
      lightOn = true;
      setBacklight();
    } else {
      runStatus = true;
      retract();
      curIpwr = newIpwr;// update power setting to values on display
      curEpwr = newEpwr;
      curIdur = newIdur;
      curEdur = newEdur;
      dutyCycle = calcDutyCycle(curIpwr);
      inhale = true;
      inSeconds = int(newIdur * 1000);
      exSeconds = int(newEdur * 1000);

      updateRunDisplay();
      if (displayAttached == false) displaySerMon();
      analogWrite(motor, dutyCycle);
      inStart = millis();// start inhale timing
    }
    lastBr = millis();

  }
  if (millis() - lastbIn > bThresh && binDval == HIGH) {// inhale duration pressed
    //Serial.println("inhale duration");
    if (lightOn == false) {
      lightOn = true;
      setBacklight();
    } else if (newIdur < iDurMax) {
      newIdur = newIdur + durIncrement;
    }  else {
      newIdur = iDurMin; // reset to minimum power
    }

    updateSettings();
    if (displayAttached == false) displaySerMon();
    lastbIn = millis();
  }

  if (millis() - lastbEx > bThresh && bexDval == HIGH) {// exhale duration pressed
    // Serial.println("exhale duration");
    if (lightOn == false) {
      lightOn = true;
      setBacklight();
    } else if (newEdur < eDurMax) {
      newEdur = newEdur + durIncrement;
    }  else {
      newEdur = eDurMin; // reset to minimum power
    }
    updateSettings();
    if (displayAttached == false) displaySerMon();
    lastbEx = millis();
  }
  // end of buttons



  delay(80);// was 50
}

void updateRunDisplay() {
  inString = String(curIpwr);
  lcd.setCursor(13, 3);
  lcd.print(inString);
  if (curIpwr < 100) {
    lcd.setCursor(15, 3);
    lcd.print(" ");
  }
  lcd.setCursor(16, 3);
  lcd.print("/");
  exString = String(curEpwr);
  lcd.setCursor(17, 3);
  lcd.print(exString);
  if (curEpwr < 100) {
    lcd.setCursor(19, 3);
    lcd.print(" ");
  }
  inString = String(curIdur);
  inString = inString.substring(0, 3);
  exString = String(curEdur);
  exString = exString.substring(0, 3);
  lcd.setCursor(0, 3);
  lcd.print(inString);
  lcd.setCursor(3, 3);
  lcd.print("/");
  lcd.setCursor(4, 3);
  lcd.print(exString);
  lcd.setCursor(5, 2);
  if (runStatus) {
    lcd.print("** RUN ** ");
    lcd.setCursor(9, 3);
    lcd.print("OPR");
  } else {
    lcd.print("** STOP **");
    lcd.setCursor(9, 3);
    lcd.print("   ");
  }
}

int calcDutyCycle(int pwrIn) {
  int dc = (pwrIn * 255) / 100; // scaling for duty cycle
  return dc;
}

void retract() { // open compressor and turn off power
  dutyCycle = calcDutyCycle(curEpwr);
  ldrEval = analogRead(ldrExhale);
  analogWrite(motor, dutyCycle);
  while (!(ldrEval > eLDRmin) && (ldrEval < eLDRmax)) { // as long as arm NOT in full retract position
    ldrEval = analogRead(ldrExhale);
    delay(50);
  }
  analogWrite(motor, 0); // turn off
}

void setBacklight() { // toggle the backlight on/off based on button press
  if (lightOn == true) {
    lcd.backlight();
  } else {
    lcd.noBacklight();
  }
}

void displaySerMon() { // if no LCD display attached, display machine status in Serial Monitor
  /* Add your own code here if you'll be using the Serial Monitor as your control panel
   *  Serial.print("STOP");
    Serial.print(stopSt);
    Serial.print('\t');
    Serial.print("SET:");
    Serial.print(curBpm);
    Serial.print('\t');
    Serial.print("PAUSE:");
    Serial.print(pauseString);
    Serial.print('\t');
    Serial.print("RUN");
    Serial.println(runSt);*/
}



void updateSettings() {
  inString = String(newIdur);
  inString = inString.substring(0, 3);
  exString = String(newEdur);
  exString = exString.substring(0, 3);
  lcd.setCursor(0, 0);
  lcd.print("InS/ExS SET  InP/ExP");
  lcd.setCursor(0, 1);
  lcd.print(inString);
  lcd.setCursor(3, 1);
  lcd.print("/");
  lcd.setCursor(4, 1);
  lcd.print(exString);
  lcd.setCursor(13, 1);
  lcd.print(newIpwr);
  if (newIpwr < 100) {
    lcd.setCursor(15, 1);
    lcd.print(" ");
  }
  lcd.setCursor(16, 1);
  lcd.print("/");
  if (newEpwr < 100) {
    lcd.setCursor(19, 1);
    lcd.print(" ");
  }
  lcd.setCursor(17, 1);
  lcd.print(newEpwr);
  if (displayAttached == false) displaySerMon();
}

Credits

Gord Payne

Gord Payne

1 project • 2 followers
High school Computer Science teacher. Life long maker. Arduino fan.

Comments