├── .gitignore ├── .travis.yml ├── README.md ├── config.h ├── lcventilator.h ├── lcventilator.ino ├── platformio.ini ├── simulate.cpp └── simulate.h /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .clang_complete 3 | .gcc-flags.json 4 | lcventilator 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lcventilator 2 | Low cost ventilator based on Dr. Jeff Ebin's design 3 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H 2 | #define _CONFIG_H 3 | 4 | //#define LCD // enable LCD 5 | #define LCD_I2C // enable I2C LCD 6 | 7 | #define DEBUG // enable serial debugging 8 | #define SERIAL_SPEED 9600 9 | 10 | ///////////////////////////// 11 | //// PINS 12 | ///////////////////////////// 13 | #define CYCLES_PER_MIN_PIN A0 14 | #define ACTUATOR_DIST_PIN A1 15 | #define MOTOR_IN1_PIN 9 16 | #define MOTOR_IN2_PIN 10 17 | #define MOTOR_ENABLE_PIN 13 18 | 19 | // LCD I2C pins (if LCD_I2C is defined) 20 | #define LCD_I2C_ADDRESS 0x27 21 | 22 | // LCD pins (if LCD is defined) 23 | #define LCD_RS 12 24 | #define LCD_EN 11 25 | #define LCD_D4 5 26 | #define LCD_D5 4 27 | #define LCD_D6 3 28 | #define LCD_D7 2 29 | 30 | ///////////////////////////// 31 | //// VALUES 32 | ///////////////////////////// 33 | #define MAX_CYCLES_PER_MIN 40 // max full actuations (fwd+back) per minute 34 | #define MIN_CYCLES_PER_MIN 1 // minimum actuations per minute 35 | #define LCD_COLUMNS 16 36 | #define LCD_ROWS 2 37 | 38 | #define PERCENTAGE_GOING_IN 25 // percentage of time we should go in vs out, not including delays 39 | 40 | // motor control speed (pwm value) 41 | #define MIN_SPEED 0 42 | #define MAX_SPEED 255 43 | 44 | // display values of (relative) distance 45 | #define MIN_DISTANCE 0 46 | #define MAX_DISTANCE 255 47 | 48 | // analogRead range on analog pins 49 | #define MIN_ANALOG_VALUE 0 50 | #define MAX_ANALOG_VALUE 1023 51 | 52 | #define DELAY_PRE_CYCLE_MS 10 // milliseconds to wait before actuating forward 53 | #define DELAY_MID_CYCLE_MS 10 // milliseconds to wait before actuating in reverse 54 | 55 | /////////////////////////////////////////////////////////////////////// 56 | 57 | #ifdef DEBUG 58 | #ifdef SIMULATE 59 | #define SDEBUG // simulate debug 60 | #define d(...) print(__VA_ARGS__) 61 | #define dln(...) { print(__VA_ARGS__); print("\n"); } 62 | #else 63 | #define RDEBUG // real debug 64 | #define d(...) Serial.print(__VA_ARGS__) 65 | #define dln(...) Serial.println(__VA_ARGS__) 66 | #endif 67 | #else 68 | #define d(...) (void)0 69 | #define dln(...) (void)0 70 | #endif 71 | 72 | #endif // _CONFIG_H 73 | -------------------------------------------------------------------------------- /lcventilator.h: -------------------------------------------------------------------------------- 1 | #ifndef _VENTILATOR_H 2 | #define _VENTILATOR_H 3 | 4 | #ifndef SIMULATE 5 | #include 6 | #endif 7 | 8 | void setup(); 9 | void loop(); 10 | #endif -------------------------------------------------------------------------------- /lcventilator.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Low cost ventilator based on Dr. Jeffrey Ebin's design. 4 | 5 | Supports most low cost MCUs (Arduino Uno, Nano, Mega, Teensy, etc) 6 | - optional LCD or I2C LCD 7 | - 2 pots required to control rate + distance 8 | 9 | -samy kamkar 10 | 2020/03/20 11 | 12 | TODO: 13 | - support diff delay between forward and reverse actuating 14 | - support duty cycle of motor 15 | - schematic 16 | - resolve any warnings 17 | 18 | to simulate: 19 | g++ -DSIMULATE *.cpp *.ino -o lcventilator 20 | 21 | */ 22 | 23 | // change settings in config.h 24 | #include "config.h" 25 | #include "lcventilator.h" 26 | #include "simulate.h" 27 | 28 | /////////////////////////////////////////////////////////////////////// 29 | 30 | #if defined(LCD) && defined(LCD_I2C) 31 | #error "define either LCD, LCD_I2C, or neither" 32 | #endif 33 | 34 | #if defined(LCD) 35 | #define _LCD 36 | #include 37 | LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7); 38 | 39 | #elif defined(LCD_I2C) 40 | #define _LCD 41 | #include 42 | #include 43 | #include 44 | LiquidCrystal_I2C lcd(LCD_I2C_ADDRESS, LCD_COLUMNS, LCD_ROWS); 45 | #endif 46 | 47 | #define BUFFER_SIZE (LCD_COLUMNS+1) 48 | char buffer[BUFFER_SIZE]; 49 | 50 | uint16_t cycles_input = 0, cycles_per_min = 0, actuator_input = 0, speed = 0; 51 | uint32_t start_time; 52 | bool last_direction, direction; 53 | 54 | // milliseconds per cycle (not including pre/post delay times) 55 | #define MS_IN_MIN ((uint32_t)60 * 1000) 56 | #define TOTAL_DELAY_MS (DELAY_PRE_CYCLE_MS + DELAY_MID_CYCLE_MS) 57 | #define TOTAL_DELAY_MS_PER_MIN (cycles_per_min * TOTAL_DELAY_MS) 58 | #define CYCLE_MS ((MS_IN_MIN - TOTAL_DELAY_MS_PER_MIN) / cycles_per_min) 59 | #define CURRENT_TIME_IN_CYCLE ((millis() - start_time) % CYCLE_MS) 60 | 61 | // which side of the breath we're in 62 | #define PERCENT_OF(X, PERCENT) (X / (100.0 / PERCENT)) 63 | #define CYCLE_TIME_IN (DELAY_PRE_CYCLE_MS + PERCENT_OF(CYCLE_MS, PERCENTAGE_GOING_IN)) 64 | #define DIRECTION (CURRENT_TIME_IN_CYCLE >= CYCLE_TIME_IN) 65 | #define CURRENT_SPEED (PERCENT_OF(speed, (direction ? PERCENTAGE_GOING_IN : (100 - PERCENTAGE_GOING_IN)))) 66 | #define BREATH_IN 0 67 | #define BREATH_OUT 1 68 | 69 | /////////////////////////////////////////////// 70 | // lcd helper functions 71 | 72 | // clear lcd 73 | void lcdClear() 74 | { 75 | #ifdef _LCD 76 | lcd.clear(); 77 | #endif 78 | } 79 | 80 | // print to column and row 81 | void lcdPrint(uint8_t col, uint8_t row, char *buf) 82 | { 83 | #ifdef _LCD 84 | lcd.setCursor(col, row); 85 | lcd.print(buf); 86 | #endif 87 | } 88 | 89 | // default to first column 90 | void lcdPrint(uint8_t row, char *buf) 91 | { 92 | #ifdef _LCD 93 | lcd.setCursor(0, row); 94 | lcd.print(buf); 95 | #endif 96 | } 97 | 98 | // default to first column and row 99 | void lcdPrint(char *buf) 100 | { 101 | #ifdef _LCD 102 | lcdPrint(0, 0, buf); 103 | #endif 104 | } 105 | 106 | //////////////////////////////////////////////////// 107 | // other helper functions 108 | 109 | // handle analog value mappings 110 | long analogMap(long input, long from, long to) 111 | { 112 | return map(input, MIN_ANALOG_VALUE, MAX_ANALOG_VALUE, from, to); 113 | } 114 | 115 | long analogMap(long input, long to) 116 | { 117 | return analogMap(input, 0, to); 118 | } 119 | 120 | // drive our motor, L293D or similar 121 | void driveMotor(uint8_t m_speed, bool m_direction) 122 | { 123 | d("driving motor speed="); 124 | d(m_speed); 125 | d(" direction="); 126 | dln(direction); 127 | 128 | analogWrite(MOTOR_ENABLE_PIN, m_speed); 129 | digitalWrite(MOTOR_IN1_PIN, !m_direction); 130 | digitalWrite(MOTOR_IN2_PIN, m_direction); 131 | } 132 | 133 | // stop motor 134 | void stopMotor() 135 | { 136 | d("stop motor"); 137 | analogWrite(MOTOR_ENABLE_PIN, 0); 138 | } 139 | 140 | // grab values from pots and scale 141 | void readInputs() 142 | { 143 | start_time = millis(); 144 | actuator_input = analogRead(ACTUATOR_DIST_PIN); 145 | cycles_input = analogRead(CYCLES_PER_MIN_PIN); 146 | cycles_per_min = analogMap(cycles_input, MIN_CYCLES_PER_MIN, MAX_CYCLES_PER_MIN); 147 | speed = analogMap(actuator_input, MIN_SPEED, MAX_SPEED); 148 | 149 | dln("read inputs"); 150 | lcdPrint(0, 1, "C"); 151 | } 152 | 153 | // display to LCD 154 | void display() 155 | { 156 | // read from analog pins because normally we only change values on breath 157 | uint8_t tmp_cpm = analogMap(analogRead(CYCLES_PER_MIN_PIN), MIN_CYCLES_PER_MIN, MAX_CYCLES_PER_MIN); 158 | uint8_t tmp_rate = analogMap(analogRead(ACTUATOR_DIST_PIN), MIN_DISTANCE, MAX_DISTANCE); 159 | 160 | // print to display 161 | snprintf(buffer, BUFFER_SIZE, "%c Breath/min: %2d", direction ? '+' : '-', tmp_cpm); 162 | lcdPrint(buffer); 163 | 164 | snprintf(buffer, BUFFER_SIZE, " Distance: %3d", tmp_rate); 165 | lcdPrint(1, buffer); 166 | 167 | // debug output to serial (if DEBUG enabled) 168 | d(millis()); 169 | d(" sec="); 170 | d(int(millis()/1000)); 171 | d(" cycles="); 172 | d(cycles_input); 173 | d(" actuator="); 174 | d(actuator_input); 175 | d(" CYCLE_MS="); 176 | d(CYCLE_MS); 177 | d(" DIR="); 178 | d(DIRECTION); 179 | d(" dir="); 180 | d(direction); 181 | d(" speed="); 182 | d(speed); 183 | d(" curspeed="); 184 | d(CURRENT_SPEED); 185 | d(" PGI="); 186 | d(PERCENTAGE_GOING_IN); 187 | d(" cpm="); 188 | d(cycles_per_min); 189 | d(" tcpm="); 190 | d(tmp_cpm); 191 | d(" trate="); 192 | dln(tmp_rate); 193 | } 194 | 195 | 196 | ////////////////////////////////////////////////// 197 | // main 198 | void setup() 199 | { 200 | #ifdef RDEBUG 201 | Serial.begin(SERIAL_SPEED); 202 | #endif 203 | 204 | // pot pins 205 | pinMode(CYCLES_PER_MIN_PIN, INPUT); 206 | pinMode(ACTUATOR_DIST_PIN, INPUT); 207 | 208 | // motor pins 209 | pinMode(MOTOR_ENABLE_PIN, OUTPUT); 210 | pinMode(MOTOR_IN1_PIN, OUTPUT); 211 | pinMode(MOTOR_IN2_PIN, OUTPUT); 212 | 213 | #ifdef _LCD 214 | // try to support LiquidCrystal_I2C libs from F Malpartida, Frank de Brabander, Marco Schwartz, ejoyneering, Tony Kambourakis (platformio 136, 576, 1574, 6158) 215 | #if defined(LiquidCrystal_i2c_h) // if using 6158 by ejoyneering - this conflicts with 576 216 | lcd.begin(LCD_COLUMNS, LCD_ROWS); 217 | #elif defined(LiquidCrystal_4bit_h) // 136 218 | lcd.begin(LCD_COLUMNS, LCD_ROWS); 219 | #else 220 | lcd.begin(LCD_COLUMNS, LCD_ROWS, LCD_5x8DOTS); 221 | #endif 222 | 223 | #endif 224 | 225 | lcdClear(); 226 | lcdPrint("Ventilator On"); 227 | delay(2000); 228 | 229 | // get initial values 230 | readInputs(); 231 | } 232 | 233 | // read our values, drive motor, display output 234 | void loop() 235 | { 236 | // cache our direction 237 | direction = DIRECTION; 238 | 239 | // only read new values once we switch directions 240 | if (last_direction != direction) 241 | { 242 | // begin cycle 243 | if (direction == BREATH_IN) 244 | { 245 | readInputs(); 246 | #if DELAY_PRE_CYCLE_MS 247 | stopMotor(); 248 | delay(DELAY_PRE_CYCLE_MS); 249 | #endif 250 | } 251 | 252 | // return from other half cycle 253 | else if (direction == BREATH_OUT) 254 | { 255 | #if DELAY_MID_CYCLE_MS 256 | stopMotor(); 257 | delay(DELAY_MID_CYCLE_MS); 258 | #endif 259 | } 260 | } 261 | last_direction = direction; 262 | 263 | driveMotor(CURRENT_SPEED, direction); 264 | 265 | display(); 266 | } 267 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ;PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:megaatmega2560] 12 | platform = atmelavr 13 | board = megaatmega2560 14 | framework = arduino 15 | 16 | -------------------------------------------------------------------------------- /simulate.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "simulate.h" 3 | #include "lcventilator.h" 4 | 5 | /////////////////////////////////////////////////////////////// 6 | // simulation helpers 7 | #ifdef SIMULATE 8 | 9 | #define SIM_DELAY 100 10 | 11 | #define MAX_ANALOG_PINS 20 12 | #define MAX_DIGITAL_PINS 20 13 | 14 | uint8_t analogReadValues[MAX_ANALOG_PINS]; 15 | uint8_t digitalReadValues[MAX_DIGITAL_PINS]; 16 | uint8_t outputPins[MAX_DIGITAL_PINS]; 17 | 18 | struct timeval _start, _now; // set in main 19 | 20 | 21 | // XXX 22 | void pinMode(uint8_t, uint8_t) 23 | { 24 | delay(SIM_DELAY); 25 | } 26 | 27 | void digitalWrite(uint8_t pin, uint8_t val) 28 | { 29 | outputPins[pin] = val ? 255 : 0; 30 | delay(SIM_DELAY); 31 | } 32 | 33 | void analogWrite(uint8_t pin, int val) 34 | { 35 | outputPins[pin] = val; 36 | delay(SIM_DELAY); 37 | } 38 | 39 | int digitalRead(uint8_t pin) 40 | { 41 | return digitalReadValues[pin]; 42 | } 43 | int analogRead(uint8_t pin) 44 | { 45 | return analogReadValues[pin]; 46 | } 47 | 48 | void delay(unsigned long ms) 49 | { 50 | usleep(ms * 1000); 51 | } 52 | 53 | unsigned long millis() 54 | { 55 | gettimeofday(&_now, NULL); 56 | return ((_now.tv_sec - _start.tv_sec) * 1000 + (_now.tv_usec - _start.tv_usec) / 1000); 57 | } 58 | 59 | long map(long x, long in_min, long in_max, long out_min, long out_max) 60 | { 61 | return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 62 | } 63 | 64 | size_t print(long val) 65 | { 66 | printf("%u", val); 67 | return sizeof(long); 68 | } 69 | size_t print(char *val) 70 | { 71 | printf("%s", val); 72 | return strlen(val); 73 | } 74 | 75 | int main() 76 | { 77 | analogReadValues[ACTUATOR_DIST_PIN] = 200; 78 | analogReadValues[CYCLES_PER_MIN_PIN] = 128; 79 | 80 | gettimeofday(&_start, NULL); 81 | 82 | setup(); 83 | while (1) 84 | loop(); 85 | return 0; 86 | } 87 | #endif 88 | 89 | -------------------------------------------------------------------------------- /simulate.h: -------------------------------------------------------------------------------- 1 | #ifndef _SIMULATE_H 2 | #define _SIMULATE_H 3 | 4 | #ifdef SIMULATE 5 | 6 | #include // usleep 7 | #include // printf, sprintf 8 | #include // types 9 | #include // strlen 10 | #include // timeval, gettimeofday 11 | 12 | // some default vals 13 | #define A0 30 14 | #define A1 31 15 | #define INPUT 1 16 | #define OUTPUT 0 17 | 18 | // disable LCD 19 | #undef LCD 20 | #undef LCD_I2C 21 | 22 | void pinMode(uint8_t, uint8_t); 23 | int digitalRead(uint8_t); 24 | void digitalWrite(uint8_t pin, uint8_t val); 25 | int analogRead(uint8_t); 26 | void analogWrite(uint8_t pin, int val); 27 | void delay(unsigned long ms); 28 | unsigned long millis(); 29 | long map(long x, long in_min, long in_max, long out_min, long out_max); 30 | size_t print(long val); 31 | size_t print(char *val); 32 | 33 | #endif // SIMULATE 34 | 35 | #endif // _SIMULATE_Hstruct timeval _start, _now; // set in main; 36 | --------------------------------------------------------------------------------