EKG monitor
In the beginning, I just wanted to make a simple device to monitor ma heart rate. As it often happens, I couldn’t resist developing the project, because the possibilities of the AD8232 were so promising… The final prototype can display, record and replay EKG signals. 4 slots are available to record EKG measures, up to a bit more than 4 minutes each. The recorded measures can be transferred to a computer, where they can be visualized and printed out. The format of the resulting page(s) is very minimalistic, but similar enough to the usual professional EKG strips to be understood by a specialist. Of course, my contraption is not a medical device. Nevertheless, it could be of some use…
Components and supplies
EKG electrodes (succion)
4.7 kOhm resistor
Adafruit RTC DS3231 Real time clock
LED 5mm Yellow
Through Hole Resistor, 180 ohm
Microswitch, Miniature
screw terminal 2P 2.54mm
100nF Capacitor
Resistor 22K ohm
27 ohm resistor
Trimmer Potentiometer, 20 kohm
Ad8232
EEPROM AT 24C256
Arduino Uno Rev3
9V Battery Clip Connector
CR2032 battery
On –off switch
Dot Pcb
9v Battery
Piezo Buzzer
2 inch 240x320 LCD module (Waveshare)
Tools and machines
drill
Sandpaper
Multimeter
Solder (Generic)
Caliper
JigSaw
File (various)
Screwdrivers
Apps and platforms
Inkscape
Processing 4
FreeCAD
Arduino IDE 2.3.2
Project description
Code
EKG ino
cpp
Code for Arduino device
1/*EKG monitoring system 2Blaise Muller 2024-2025 3Displays, records and replays EKG signals 4Allows transfer of the recorded files to a computer via serial port. 5Computer uses a multi-platform JAVA app. Tested on Linux (Ubuntu laptop) and Windows (Windows 8) 6Sketch uses 29492 bytes (91%) of program storage space. 7*/ 8 9#include <Adafruit_GFX.h> // Core graphics library 10#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789 11#include <Wire.h> 12#include "RTClib.h" 13#include <TimerOne.h> 14 15#define MYCOLOR_DARKGRAY 0x2104 16#define MYCOLOR_DARKGREEN 0x0180 17#define MYCOLOR_DARKBLUE 0x0009 18#define LED 6 19#define TWEETER 17 20#define BATTERY A2 21#define EKG A0 22//this version doesn't use the 2 control pins from the EKG module 23//but they are wiredto 2 arduino inputs, in case someone implements a corresponding function 24//#define LOM 10 25//#define LOP 12 26const int potOf2[4] = { 1, 2, 4, 8 }; 27 28//connections to display 29#define TFT_CS 9 30#define TFT_RST -1 // connect RST to Arduino RESET pin 31#define TFT_DC 7 // Default values were kept : DIN to D11 -CLK to D13 32Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); 33 34//connections to RTC 35RTC_DS1307 rtc; 36#define DS1307_ADDRESS 0x68 37 38//time 39int tmMin; 40int tmHour; 41int tmDay; 42int tmMonth; 43int tmYear; 44const char* txtMonth[12] = { "Jan", "Feb", "Mar", "Apr", 45 "May", "Jun", "Jul", "Aug", 46 "Sep", "Oct", "Nov", "Dec" }; 47const char* txtDofW[7] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; 48const char* timeComment[3] = { " ", " AM", " PM" }; 49int const maxMD[12] = { 31, 10, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 50int timeSetPointer = 0; 51boolean timeSetOK = false; 52 53//EEprom related 54//EEprom on RTC module has address 0x50 55//24MC256 have addresses 0x51 and 0x52 56#define memoMax 16384 57int currentBank = 0; 58int activeEE = 0x51; 59unsigned int memoAddress = 0; 60unsigned int memoEnd = 16383; 61 62//local variables 63byte optTIME = 1; 64byte optDATE = 2; 65byte optFIX = 1; 66 67//flags and presets 68int currentMenu = 0; 69int lastMenu = -1; 70int nextMenu = 0; 71boolean optionsOK = false; 72uint32_t nextHeader = millis() + 60000; 73int lastKB = 6; 74 75//Led and tweeter 76boolean signalsOK = false; 77byte signalLED = 5; 78int ledVol = 125; 79byte signalTWEETER = 5; 80boolean toggleLT = false; 81uint32_t ledOffTime = millis(); 82uint32_t tweeterOffTime = millis(); 83boolean tweeterON = false; 84boolean tweeterMute = false; 85boolean ledON = false; 86int shortMemo; 87 88//for EKG 89int readerCounter = -10; 90boolean storeEKG = false; 91byte EKGwrite = 26; 92byte EKGmemo = 26; 93boolean EKGHalt = false; 94int screenPos; 95int zoomFactor = 1; 96float pulseVal; 97int EKGread; 98uint32_t currPulse; 99uint32_t lastPulse; 100boolean EKGrunning = false; 101boolean fromEeprom = false; 102String filler; 103 104void setup() { 105 //init inputs/outputs 106 pinMode(2, INPUT_PULLUP); //key 0 107 pinMode(3, INPUT_PULLUP); //key 1 108 pinMode(4, INPUT_PULLUP); //key 2 109 pinMode(5, INPUT_PULLUP); //key 3 110 pinMode(TWEETER, OUTPUT); 111 pinMode(LED, OUTPUT); 112 //pinMode(LOM, INPUT);//uncomment if needed 113 //pinMode(LOP, INPUT);//uncomment if needed 114 digitalWrite(TWEETER, HIGH); 115 analogWrite(LED, 128); 116 //create special string to clear pulse rate display 117 filler = char(218); 118 filler.concat(char(218)); 119 filler.concat(char(218)); 120 //Serial.begin(9600);//uncomment this if debugging needed... 121 //init TFT 122 tft.init(240, 320); 123 tft.setRotation(3); 124 tft.fillScreen(ST77XX_BLACK); 125 tft.setTextSize(3); 126 //start RTC functions 127 rtc.begin(); 128 //read stored options from eeprom on RTC module 129 optTIME = eeprom_readByte(0x50, 32767); 130 delay(10); 131 if (optTIME > 1) { 132 optTIME = 1; //default value 133 eeprom_writeByte(0x50, 32767, 3); 134 delay(10); 135 } 136 // optTIME values 137 // 0: hh:mm - am/pm 138 // 1: hh:mm - 24h (default) 139 optDATE = eeprom_readByte(0x50, 32766); 140 delay(10); 141 if (optDATE > 3) { 142 optDATE = 2; //default value 143 eeprom_writeByte(0x50, 32766, 2); 144 delay(10); 145 } 146 /* optDATE values 147 0: YY:MM:DD:DoW 148 1: YY:MM:DD 149 2: DD:MM:YY:DoW (default) 150 3: DD:MM:YY 151 */ 152 optFIX = eeprom_readByte(0x50, 32765); 153 delay(10); 154 if (optFIX > 2) { 155 optFIX = 1; 156 eeprom_writeByte(0x50, 32765, 1); 157 delay(10); 158 } 159 /* optFIX values 160 0: date not visible 161 1: white on black (default) 162 2: black on white 163 */ 164 optionsOK = false; //to allow changes 165 //display start page 166 tft.fillRect(0, 0, 320, 52, ST77XX_BLUE); 167 tft.setTextColor(ST77XX_WHITE); 168 tft.setCursor(3, 3); 169 tft.print(" BM'S EKG 2024 (v.02.02)"); 170 signalLED = eeprom_readByte(0x50, 32764) & 7; 171 delay(10); 172 ledVol = signalLED * signalLED * 5; 173 signalTWEETER = eeprom_readByte(0x50, 32763) & 7; 174 delay(10); 175 //display time 176 readRTC(); 177 tft.setTextColor(ST77XX_WHITE); 178 tft.setCursor(1, 70); 179 showDate(tmYear, tmMonth, tmDay); 180 tft.setCursor(1, 100); 181 showTime(tmHour, tmMin); 182 showBattery(); 183 tft.fillRect(0, 210, 320, 30, ST77XX_WHITE); 184 tft.setTextColor(ST77XX_BLACK); 185 tft.setCursor(1, 211); 186 tft.print("Press any key"); 187 tft.setTextColor(ST77XX_WHITE); 188 Wire.begin(); 189 //wait for key pressed 190 while (readKB() > 3) { ; }; 191 analogWrite(LED, 0); 192 lastMenu = -1; 193 currentMenu = 0; 194 Timer1.initialize(30000); 195 Timer1.attachInterrupt(incReaderCounter); 196} //end of setup() 197 198void incReaderCounter() { 199 readerCounter += 1; 200 //to synchronize EKG readings 201} 202 203void loop() { 204 if (currentMenu != lastMenu) { 205 displayPage(currentMenu); 206 lastMenu = currentMenu; 207 } 208 byte keyPressed = readKB(); 209 if (currentMenu == 0) { //home 210 //SHOW HEADER every minute 211 if (millis() > nextHeader) { 212 nextHeader = millis() + 60000; //+= 60000; 213 showHeader(); 214 } 215 switch (keyPressed) { 216 case 0: //ask for EKG (menu 1) 217 EKGHalt = false; 218 nextMenu = 1; 219 break; 220 case 1: //ask for EXT (menu 2) 221 Serial.end(); 222 Serial.begin(38400); 223 nextMenu = 2; 224 break; 225 case 2: //ask for TIM (menu 3) 226 nextMenu = 3; 227 timeSetPointer = 0; 228 timeSetOK = false; 229 break; 230 case 3: //ask for OPT (menu 4) 231 nextMenu = 4; 232 break; 233 } 234 } 235 if (currentMenu == 1) { //EKG 236 switch (keyPressed) { 237 case 0: //start scope 238 storeEKG = false; 239 fromEeprom = false; 240 readerCounter = -5; 241 showEKG(); 242 break; 243 case 1: //record scope 244 nextMenu = 8; 245 break; 246 case 2: //replay scope 247 nextMenu = 9; 248 break; 249 case 3: //back home 250 nextMenu = 0; 251 break; 252 } 253 } 254 if (currentMenu == 2) { //EXT 255 //Serial is open on Arduino side, wait for pc to send "A" 256 boolean pcOK = false; 257 boolean pcDone = false; 258 char pcSignal = "n"; 259 if (!pcOK) { 260 while (!pcOK) { 261 Serial.println("P"); 262 if (!digitalRead(5)) { 263 Serial.end(); 264 nextMenu = 0; 265 pcOK = true; 266 } 267 delay(100); 268 if (Serial.available()) { 269 pcSignal = Serial.read(); 270 if ((pcSignal >= 'A') && (pcSignal <= 'P')) { 271 int signalMask = pcSignal - 'A'; 272 pcOK = true; 273 //send selected report(s) to pc 274 //chars A to P are decoded to signalMask, value 0 to 15. 275 //each bit of signalMask stands for a bank 276 for (byte CB = 0; CB < 4; CB++) { 277 if ((potOf2[CB] & signalMask) != 0) sendFullBank(CB); 278 } 279 Serial.println(3333); 280 pcDone = true; 281 } 282 if (pcSignal == 'Q') { 283 //send infos of the 4 banks : date, time, length 284 pcOK = true; 285 delay(100); 286 Serial.println(4000); //means start 287 for (int ii = 0; ii < 32; ii++) { 288 Serial.println(eeprom_readByte(0x50, ii)); //send 32 bytes (8 for each bank) 289 } 290 Serial.println(4001); //means done 291 } 292 } 293 } 294 } 295 if (pcDone) { nextMenu = 0; }; 296 if (keyPressed == 3) nextMenu = 0; 297 } 298 if (currentMenu == 3) { //TIM 299 showTimeSet(); 300 switch (keyPressed) { 301 case 0: //next item 302 timeSetPointer += 1; 303 if (timeSetPointer > 4) { 304 timeSetPointer = 0; 305 }; 306 timeSetOK = false; 307 break; 308 case 1: //minus 309 if (timeSetPointer == 0) { //Year- 310 tmYear -= 1; 311 if (tmYear < 1950) tmYear = 1950; 312 } 313 if (timeSetPointer == 1) { //Month- 314 tmMonth -= 1; 315 if (tmMonth < 1) tmMonth = 12; 316 } 317 if (timeSetPointer == 2) { //Day- 318 tmDay -= 1; 319 if (tmDay < 1) tmDay = maxDaysInMonth(tmYear, tmMonth); 320 } 321 if (timeSetPointer == 3) { //Hour- 322 tmHour -= 1; 323 if (tmHour < 0) tmHour = 23; 324 } 325 if (timeSetPointer == 4) { //Minute- 326 tmMin -= 1; 327 if (tmMin < 0) tmMin = 59; 328 } 329 timeSetOK = false; 330 break; 331 case 2: //plus 332 if (timeSetPointer == 0) { //Year+ 333 tmYear += 1; 334 if (tmYear > 2250) tmYear = 2250; 335 } 336 if (timeSetPointer == 1) { //Month+ 337 tmMonth += 1; 338 if (tmMonth > 12) tmMonth = 1; 339 } 340 if (timeSetPointer == 2) { //Day+ 341 tmDay += 1; 342 if (tmDay > maxDaysInMonth(tmYear, tmMonth)) tmDay = 1; 343 } 344 if (timeSetPointer == 3) { //Hour+ 345 tmHour += 1; 346 if (tmHour > 23) tmHour = 0; 347 } 348 if (timeSetPointer == 4) { //Minute+ 349 tmMin += 1; 350 if (tmMin > 59) tmMin = 0; 351 } 352 timeSetOK = false; 353 break; 354 case 3: //back home 355 //Store values 356 setRTC(0, tmMin, tmHour, tmDay, tmMonth, tmYear); 357 nextMenu = 0; 358 break; 359 } 360 } 361 if (currentMenu == 4) { //OPT 362 switch (keyPressed) { 363 case 0: //date and time 364 optionsOK = false; 365 nextMenu = 5; 366 break; 367 case 1: //signals 368 nextMenu = 6; 369 break; 370 case 2: //battery 371 nextMenu = 11; 372 break; 373 case 3: //back home 374 nextMenu = 0; 375 break; 376 } 377 } 378 if (currentMenu == 5) { //sub of OPT - D+T 379 showOptions(); 380 switch (keyPressed) { 381 case 0: //toggle date format 382 optDATE += 1; 383 if (optDATE > 3) optDATE = 0; 384 optionsOK = false; 385 break; 386 case 1: //toggle time format 387 optTIME += 1; 388 if (optTIME > 1) optTIME = 0; 389 optionsOK = false; 390 break; 391 case 2: //toggle time display 392 optFIX += 1; 393 if (optFIX > 2) optFIX = 0; 394 optionsOK = false; 395 break; 396 case 3: //back to OPT 397 //save values 398 eeprom_writeByte(0x50, 32765, optFIX); 399 delay(10); 400 eeprom_writeByte(0x50, 32766, optDATE); 401 delay(10); 402 eeprom_writeByte(0x50, 32767, optTIME); 403 delay(10); 404 nextMenu = 4; 405 break; 406 } 407 } 408 if (currentMenu == 6) { //sub of OPT - SIG 409 signalsOK = false; 410 tweeterOff(); 411 switch (keyPressed) { 412 case 0: //toggle led / tweeter 413 toggleLT = !toggleLT; 414 displayPage(6); 415 break; 416 case 1: //decrease value Led/Tweeter 417 if (toggleLT) { //modify Led value 418 if (signalLED > 0) { 419 signalLED -= 1; 420 displayPage(6); 421 }; 422 } else { //modify Tweeter value 423 if (signalTWEETER > 0) { 424 signalTWEETER -= 1; 425 displayPage(6); 426 }; 427 } 428 break; 429 case 2: //increase value Led/Tweeter 430 if (toggleLT) { //modify Led value 431 if (signalLED < 7) { 432 signalLED += 1; 433 displayPage(6); 434 }; 435 } else { //modify Tweeter value 436 if (signalTWEETER < 7) { 437 signalTWEETER += 1; 438 displayPage(6); 439 }; 440 } 441 break; 442 case 3: //save new values and back to OPT 443 eeprom_writeByte(0x50, 32764, signalLED); 444 delay(10); 445 ledVol = 5 * signalLED * signalLED; 446 eeprom_writeByte(0x50, 32763, signalTWEETER); 447 delay(10); 448 digitalWrite(LED, LOW); 449 digitalWrite(TWEETER, HIGH); 450 nextMenu = 4; 451 break; 452 } 453 } 454 if (currentMenu == 8) { //select bank and start recording EKG 455 switch (keyPressed) { 456 case 0: //decrease value of currentBank 457 currentBank = (currentBank + 3) & 3; 458 displayPage(8); 459 break; 460 case 1: //increase value of currentBank 461 currentBank = (currentBank + 1) & 3; 462 displayPage(8); 463 break; 464 case 2: //bank is ok - run EKG 465 activeEE = 0x51 + int(currentBank / 2); 466 memoAddress = memoMax * (currentBank & 1); 467 memoEnd = memoAddress + memoMax - 1; 468 storeEKG = true; 469 fromEeprom = false; 470 //store datum to EEprom 471 readRTC(); 472 eeprom_writeByte(0x50, 8 * currentBank, tmMin); 473 delay(5); 474 eeprom_writeByte(0x50, 8 * currentBank + 1, tmHour); 475 delay(5); 476 eeprom_writeByte(0x50, 8 * currentBank + 2, tmDay); 477 delay(5); 478 eeprom_writeByte(0x50, 8 * currentBank + 3, tmMonth); 479 delay(5); 480 eeprom_writeByte(0x50, 8 * currentBank + 4, (tmYear & 255)); 481 delay(5); 482 eeprom_writeByte(0x50, 8 * currentBank + 5, int(tmYear / 256)); 483 delay(5); 484 //pos +6 and +7 will be set at the end of the record as length of record 485 readerCounter = -5; 486 showEKG(); 487 nextMenu = 1; 488 break; 489 case 3: //abort and back to EKG menu 490 nextMenu = 1; 491 break; 492 } 493 }; 494 if (currentMenu == 9) { //select bank and start replay of EKG 495 switch (keyPressed) { 496 case 0: //decrease value of currentBank 497 currentBank = (currentBank + 3) & 3; 498 displayPage(9); 499 break; 500 case 1: //increase value of currentBank 501 currentBank = (currentBank + 1) & 3; 502 displayPage(9); 503 break; 504 case 2: //bank is ok - run EKG 505 activeEE = 0x51 + int(currentBank / 2); 506 memoAddress = memoMax * (currentBank & 1); 507 memoEnd = memoAddress + memoMax - 1; 508 storeEKG = false; 509 fromEeprom = true; 510 readerCounter = -5; 511 showEKG(); 512 nextMenu = 1; 513 break; 514 case 3: //abort and back to EKG menu 515 nextMenu = 1; 516 break; 517 } 518 }; 519 if (currentMenu == 10) { //exit from scope 520 //wait for keyboard command 521 if (keyPressed == 3) nextMenu = 4; 522 } 523 if (currentMenu == 11) { //adjust battery measure 524 showBattery(); 525 delay(200); 526 //wait for keyboard command 527 if (keyPressed == 3) nextMenu = 4; 528 } 529 currentMenu = nextMenu; 530} //end of loop() 531 532void ledStart() { 533 ledOffTime = millis() + 100000; 534 ledON = true; 535 analogWrite(LED, ledVol); 536 if (!tweeterMute) { 537 tweeterOffTime = millis() + signalTWEETER * 60; 538 digitalWrite(TWEETER, LOW); 539 tweeterON = true; 540 } 541} 542 543void tweeterStart() { 544 tweeterOffTime = millis() + signalTWEETER * 60; 545 digitalWrite(TWEETER, LOW); 546 tweeterON = true; 547} 548 549void tweeterOff() { 550 digitalWrite(TWEETER, HIGH); 551 tweeterON = false; 552} 553 554void ledOff() { 555 digitalWrite(LED, LOW); 556 ledON = false; 557} 558 559void showOptions() { 560 if (!optionsOK) { 561 tft.fillRect(0, 0, 320, 210, MYCOLOR_DARKGREEN); 562 tft.setCursor(1, 60); 563 showDate(tmYear, tmMonth, tmDay); 564 tft.setCursor(1, 120); 565 showTime(tmHour, tmMin); 566 showHeader(); 567 optionsOK = true; 568 } 569} 570 571void showTimeSet() { 572 //highlights values according to timeSetPointer 573 if (!(timeSetOK)) { 574 displayPage(3); 575 if (tmDay > maxDaysInMonth(tmYear, tmMonth)) { 576 tmDay = maxDaysInMonth(tmYear, tmMonth); 577 }; 578 tft.setCursor(1, 32); 579 if (timeSetPointer != 0) tft.setTextSize(2); 580 tft.print(" YEAR: "); 581 tft.print(tmYear); 582 tft.setTextSize(3); 583 tft.setCursor(1, 64); 584 if (timeSetPointer != 1) { 585 tft.setTextSize(2); 586 }; 587 tft.print(" MONTH: "); 588 print2dec(tmMonth); 589 tft.setTextSize(3); 590 tft.setCursor(1, 96); 591 if (timeSetPointer != 2) tft.setTextSize(2); 592 tft.print(" DAY: "); 593 print2dec(tmDay); 594 tft.setTextSize(3); 595 tft.setCursor(1, 128); 596 if (timeSetPointer != 3) tft.setTextSize(2); 597 tft.print(" HOUR: "); 598 print2dec(tmHour); 599 tft.setTextSize(3); 600 tft.setCursor(1, 160); 601 if (timeSetPointer != 4) tft.setTextSize(2); 602 tft.print("MINUTE: "); 603 print2dec(tmMin); 604 tft.setTextSize(3); 605 } 606 timeSetOK = true; 607} 608 609int maxDaysInMonth(int theYear, int theMonth) { 610 if (theMonth != 2) { 611 return maxMD[theMonth - 1]; 612 }; 613 if ((theYear % 4000) == 0) return 29; 614 if ((theYear % 1000) == 0) return 28; 615 if ((theYear % 4) == 0) return 29; 616 return 28; 617} 618 619void showTime(int H, int M) { 620 //according to selected format 621 readRTC(); 622 int locHour = H; 623 int locComm = 0; 624 if (!(optTIME == 1)) { 625 locComm = 1; 626 if (H > 11) { 627 locHour = H - 12; 628 locComm = 2; 629 }; 630 } 631 print2dec(locHour); 632 tft.print(":"); 633 print2dec(M); 634 tft.print(timeComment[locComm]); 635} 636 637void showBattery() { 638 //the measure is irrelevant if device powered on USB 639 float Vjauge = analogRead(BATTERY) / 2.5; 640 tft.fillRect(10, 160, 170, 32, ST77XX_RED); 641 tft.fillRect(180, 160, 30, 32, ST77XX_YELLOW); 642 tft.fillRect(210, 160, 100, 32, ST77XX_GREEN); 643 tft.fillRect(Vjauge + 10, 160, 5, 32, ST77XX_BLACK); 644 tft.setCursor(15, 167); 645 tft.print(Vjauge / 25, 1); 646 tft.print(" V"); 647} 648 649void print2dec(int theVal) { 650 //force leading 0 if theVal<10 651 if (theVal < 10) tft.print("0"); 652 tft.print(theVal); 653} 654 655byte weekday(int year, byte month, byte day) { 656 /* Calculate day of week in proleptic Gregorian calendar. Sunday == 0. */ 657 int adjustment, mm, yy; 658 if (year < 2000) year += 2000; 659 adjustment = (14 - month) / 12; 660 mm = month + 12 * adjustment - 2; 661 yy = year - adjustment; 662 return (day + (13 * mm - 1) / 5 + yy + yy / 4 - yy / 100 + yy / 400) % 7; 663} 664 665void showDate(int Y, int M, int D) { 666 //according to selected format 667 if (optDATE & 1) { 668 tft.print(Y); 669 tft.print(" "); 670 print2dec(M); 671 tft.print("/"); 672 print2dec(D); 673 } else { 674 print2dec(D); 675 tft.print("/"); 676 print2dec(M); 677 tft.print(" "); 678 tft.print(Y); 679 } 680 if (optDATE & 2) { 681 tft.print(" "); 682 tft.print(txtDofW[weekday(Y, M, D)]); 683 } 684} 685 686byte readKB() { 687 //debounced reading. Returns values 0(left button) to 3(right button) 688 //Returns value 4 if no button pressed 689 byte kbReading = 4; 690 if (!digitalRead(2)) kbReading = 0; 691 if (!digitalRead(3)) kbReading = 1; 692 if (!digitalRead(4)) kbReading = 2; 693 if (!digitalRead(5)) kbReading = 3; 694 delay(20); 695 if (lastKB == kbReading) return 5; 696 lastKB = kbReading; 697 return kbReading; 698} 699 700void keyBoardText(int buttonNumber, char* buttonText) { 701 //displays 3 chars on blue background 702 tft.fillRect(2 + 80 * buttonNumber, 210, 76, 30, ST77XX_BLUE); 703 tft.setCursor(14 + 80 * buttonNumber, 213); 704 tft.print(buttonText); 705} 706 707void keyBoardTextRED(int buttonNumber, char* buttonText) { 708 //displays 3 chars on red background 709 tft.fillRect(2 + 80 * buttonNumber, 210, 76, 30, ST77XX_RED); 710 tft.setCursor(14 + 80 * buttonNumber, 213); 711 tft.print(buttonText); 712} 713 714void keyBoardText4(int buttonNumber, char* buttonText) { 715 //displays 4 chars on blue background 716 tft.fillRect(2 + 80 * buttonNumber, 210, 76, 30, ST77XX_BLUE); 717 tft.setCursor(6 + 80 * buttonNumber, 213); 718 tft.print(buttonText); 719} 720 721void keyBoardText4RED(int buttonNumber, char* buttonText) { 722 //displays 4 chars on red background 723 tft.fillRect(2 + 80 * buttonNumber, 210, 76, 30, ST77XX_RED); 724 tft.setCursor(6 725 + 80 * buttonNumber, 726 213); 727 tft.print(buttonText); 728} 729 730void showHeader() { 731 //happens once every second 732 tft.setTextSize(2); 733 if (optFIX == 1) { 734 tft.fillRect(0, 0, 320, 18, ST77XX_BLACK); 735 tft.setTextColor(ST77XX_WHITE); 736 } 737 if (optFIX == 2) { 738 tft.fillRect(0, 0, 320, 18, ST77XX_WHITE); 739 tft.setTextColor(ST77XX_BLACK); 740 } 741 tft.setCursor(1, 1); 742 showDate(tmYear, tmMonth, tmDay); 743 tft.setCursor(220, 1); 744 showTime(tmHour, tmMin); 745 tft.setTextSize(3); 746 tft.setTextColor(ST77XX_WHITE); 747} 748 749void displayPage(int pageCode) { 750 //this routine only displays infirmations. 751 //No decisions are made, no instructions are read 752 switch (pageCode) { 753 case 0: //main menu (start) 754 tft.fillScreen(MYCOLOR_DARKBLUE); 755 showHeader(); 756 tft.setCursor(1, 45); 757 tft.print(" EKG: access EKG"); 758 tft.setCursor(1, 80); 759 tft.print(" EXT: link to PC"); 760 tft.setCursor(1, 115); 761 tft.print("TIME: date + time"); 762 tft.setCursor(1, 150); 763 tft.print(" OPT: options"); 764 keyBoardText(0, "EKG"); 765 keyBoardText(1, "EXT"); 766 keyBoardText4(2, "TIME"); 767 keyBoardText(3, "OPT"); 768 break; 769 case 1: //EKG menu 770 tft.fillScreen(MYCOLOR_DARKGRAY); 771 tft.setCursor(90, 120); 772 tft.print("R L"); 773 tft.drawLine(40, 100, 120, 100, ST77XX_WHITE); 774 tft.drawLine(120, 170, 120, 100, ST77XX_WHITE); 775 tft.drawLine(120, 170, 200, 170, ST77XX_WHITE); 776 tft.drawLine(200, 170, 200, 100, ST77XX_WHITE); 777 tft.drawLine(200, 100, 280, 100, ST77XX_WHITE); 778 tft.drawCircle(160, 70, 30, ST77XX_WHITE); 779 tft.fillCircle(190, 130, 10, ST77XX_GREEN); 780 tft.fillCircle(130, 110, 10, ST77XX_RED); 781 tft.fillCircle(135, 160, 10, ST77XX_YELLOW); 782 keyBoardText(0, "RUN"); 783 keyBoardText(1, "REC"); 784 keyBoardText4(2, "MEMO"); 785 keyBoardText(3, "|||"); 786 break; 787 case 2: //EXT menu 788 tft.fillScreen(MYCOLOR_DARKGRAY); 789 tft.writeFillRect(0, 40, 320, 80, ST77XX_RED); 790 tft.setCursor(1, 50); 791 tft.print(" Waiting for PC "); 792 tft.setCursor(80, 213); 793 tft.print("cancel ->"); 794 keyBoardText(3, "|||"); 795 break; 796 case 3: //TIM menu 797 tft.fillScreen(MYCOLOR_DARKGREEN); 798 keyBoardText4(0, "NEXT"); 799 keyBoardText(1, " - "); 800 keyBoardText(2, " + "); 801 keyBoardText(3, "|||"); 802 break; 803 case 4: //OPT menu 804 tft.fillScreen(MYCOLOR_DARKGREEN); 805 tft.setTextColor(ST77XX_YELLOW); 806 tft.setCursor(1, 30); 807 tft.print("OPTIONS FOR:"); 808 tft.setTextColor(ST77XX_WHITE); 809 tft.setCursor(1, 75); 810 tft.print(" D+T: date+time"); 811 tft.setCursor(1, 110); 812 tft.print(" SIG: signals"); 813 tft.setCursor(1, 145); 814 tft.print("BATT: battery"); 815 keyBoardText(0, "D+T"); 816 keyBoardText(1, "SIG"); 817 keyBoardText4(2, "BATT"); 818 keyBoardText(3, "|||"); 819 break; 820 case 5: //Sub of D+T menu 821 showHeader(); 822 keyBoardText4(0, "DATE"); 823 keyBoardText4(1, "TIME"); 824 keyBoardText(2, "BAR"); 825 keyBoardText(3, "|||"); 826 break; 827 case 6: //Sub of SND menu 828 tft.fillScreen(MYCOLOR_DARKGREEN); 829 tft.setCursor(10, 20); 830 tft.print("LED AND TWEETER"); 831 if (!toggleLT) { //Changes are for Tweeter 832 tft.setTextColor(ST77XX_GREEN); 833 tft.setTextSize(2); 834 analogWrite(LED, 0); 835 tweeterStart(); 836 while (millis() < tweeterOffTime) { 837 ; 838 }; 839 tweeterOff(); 840 }; 841 tft.setCursor(30, 75); 842 tft.print("Led: "); 843 tft.print(signalLED); 844 tft.setTextColor(ST77XX_WHITE); 845 tft.setTextSize(3); 846 if (toggleLT) { //chamges are for LED 847 tft.setTextColor(ST77XX_GREEN); 848 tft.setTextSize(2); 849 analogWrite(LED, 5 * signalLED * signalLED); 850 }; 851 tft.setCursor(30, 110); 852 tft.print("Tweeter: "); 853 tft.print(signalTWEETER); 854 tft.setTextColor(ST77XX_WHITE); 855 tft.setTextSize(3); 856 tft.setCursor(10, 162); 857 tft.print("L/T : toggle"); 858 keyBoardText(0, "L/T"); 859 keyBoardText(1, " - "); 860 keyBoardText(2, " + "); 861 keyBoardText(3, "|||"); 862 break; 863 case 7: //EKG running 864 tft.fillScreen(ST77XX_BLACK); 865 if (tweeterON) { 866 keyBoardText4(0, "MUTE"); 867 } else { 868 keyBoardText4RED(0, "MUTE"); 869 } 870 if (zoomFactor == 1) { 871 keyBoardText4(1, "ZOOM"); 872 } else { 873 keyBoardText4RED(1, "ZOOM"); 874 } 875 if (EKGHalt) { 876 keyBoardText4RED(2, "HALT"); 877 } else { 878 keyBoardText4(2, "HALT"); 879 } 880 keyBoardText(3, "|||"); 881 break; 882 case 8: //select bank for replaying scope 883 case 9: //select bank for recording EKG 884 tft.fillScreen(ST77XX_BLACK); 885 tft.drawFastHLine(10, 80, 300, ST77XX_WHITE); 886 tft.drawFastHLine(10, 160, 300, ST77XX_WHITE); 887 tft.drawFastVLine(10, 80, 80, ST77XX_WHITE); 888 tft.drawFastVLine(85, 80, 80, ST77XX_WHITE); 889 tft.drawFastVLine(160, 80, 80, ST77XX_WHITE); 890 tft.drawFastVLine(235, 80, 80, ST77XX_WHITE); 891 tft.drawFastVLine(310, 80, 80, ST77XX_WHITE); 892 tft.fillRect(12 + 75 * currentBank, 82, 71, 76, ST77XX_BLUE); 893 keyBoardText(0, " < "); 894 keyBoardText(1, " > "); 895 keyBoardText(2, " OK "); 896 keyBoardText(3, "|||"); 897 tft.setTextSize(4); 898 tft.setTextColor(ST77XX_WHITE); 899 tft.setCursor(35, 100); 900 tft.print("1"); 901 tft.setCursor(110, 100); 902 tft.print("2"); 903 tft.setCursor(185, 100); 904 tft.print("3"); 905 tft.setCursor(260, 100); 906 tft.print("4"); 907 if (pageCode == 9) { 908 tft.setCursor(10, 20); 909 tft.setTextSize(2); 910 int locY = eeprom_readByte(0x50, 8 * currentBank + 5) * 256; 911 delay(5); 912 locY = locY + eeprom_readByte(0x50, 8 * currentBank + 4); //locY is recorded year 913 delay(5); 914 int locM = eeprom_readByte(0x50, 8 * currentBank + 3); 915 delay(5); 916 int locD = eeprom_readByte(0x50, 8 * currentBank + 2); //stored day 917 showDate(locY, locM, locD); 918 locD = eeprom_readByte(0x50, 8 * currentBank + 1); //stored hour 919 delay(5); 920 locM = eeprom_readByte(0x50, 8 * currentBank); //stored minutes 921 tft.print(" / "); 922 showTime(locD, locM); 923 }; 924 if (pageCode == 8) { 925 tft.setCursor(20, 20); 926 tft.setTextSize(3); 927 tft.print("select bank"); 928 } 929 tft.setTextSize(3); 930 break; 931 case 10: //exit from Replay scope 932 keyBoardText(0, ""); 933 keyBoardText(1, ""); 934 keyBoardText(2, ""); 935 keyBoardText(3, "|||"); 936 break; 937 case 11: //Adjust battery measure 938 tft.fillScreen(MYCOLOR_DARKGREEN); 939 tft.setTextColor(ST77XX_RED); 940 tft.setCursor(33, 2); 941 tft.print("^"); 942 tft.setTextSize(2); 943 tft.setTextColor(ST77XX_WHITE); 944 tft.setCursor(1, 35); 945 tft.print("Voltmeter to"); 946 tft.setCursor(1, 62); 947 tft.print("measure pin and"); 948 tft.setCursor(1, 89); 949 tft.print("trim "); 950 tft.setTextColor(ST77XX_RED); 951 tft.print("^ "); 952 tft.setTextColor(ST77XX_WHITE); 953 tft.print("to adjust value"); 954 tft.setTextSize(3); 955 keyBoardText(3, "|||"); 956 break; 957 } 958} 959 960void showEKG() { 961 //acts according to settings : Record or not, from EKG module or from EEprom 962 ledON = true; 963 digitalWrite(LED, LOW); 964 tweeterON = true; 965 digitalWrite(TWEETER, HIGH); 966 displayPage(7); 967 tft.fillRect(0, 0, 320, 25, ST77XX_BLUE); 968 EKGrunning = true; 969 while (EKGrunning) { 970 while (readerCounter < 1) { delay(1); }; 971 readerCounter -= 1; 972 if (ledON) { 973 if (ledOffTime > millis()) { 974 ledON = false; 975 digitalWrite(LED, LOW); 976 } 977 } 978 if (tweeterON) { 979 if (tweeterOffTime > millis()) { 980 tweeterON = false; 981 digitalWrite(TWEETER, HIGH); 982 } 983 } 984 if (fromEeprom) { 985 EKGwrite = eeprom_readByte(activeEE, memoAddress); 986 if (EKGwrite == 0) { 987 EKGrunning = false; 988 }; 989 if ((!fromEeprom) || (!EKGHalt)) memoAddress += 1; 990 if (memoAddress > memoEnd) EKGrunning = false; 991 } else { 992 EKGread = analogRead(A0); //read from EKG module 993 EKGwrite = 209 - (EKGread - 150) / 3; 994 if (EKGwrite > 209) EKGwrite = 209; 995 if (EKGwrite < 26) EKGwrite = 26; 996 if (storeEKG) { 997 eeprom_writeByte(activeEE, memoAddress, EKGwrite); 998 memoAddress += 1; 999 if (memoAddress > memoEnd) { 1000 EKGrunning = false; 1001 storeEKG = false; 1002 eeprom_writeByte(activeEE, memoAddress - 1, 0); 1003 storeRecTime(); 1004 } 1005 } 1006 } 1007 //detect pulse 1008 if ((EKGwrite - EKGmemo) > 30) { //pulse detected 1009 currPulse = millis(); 1010 pulseVal = 60000 / (currPulse - lastPulse); 1011 lastPulse = currPulse; 1012 if ((pulseVal > 30) && (pulseVal < 200)) { 1013 analogWrite(LED, ledVol); 1014 ledStart(); 1015 tft.setCursor(10, 1); 1016 tft.setTextColor(ST77XX_BLUE); 1017 tft.print(filler); 1018 tft.setTextColor(ST77XX_WHITE); 1019 tft.setCursor(10, 1); 1020 tft.print(int(pulseVal + 0.5)); 1021 } 1022 } 1023 if (!EKGHalt) { 1024 tft.drawLine(screenPos, 26, screenPos, 209, MYCOLOR_DARKGRAY); 1025 tft.drawLine(screenPos + zoomFactor, 26, screenPos + zoomFactor, 209, MYCOLOR_DARKGRAY); 1026 tft.drawLine(screenPos + zoomFactor, 26, screenPos + zoomFactor, 209, ST77XX_RED); 1027 if (zoomFactor == 2) tft.drawLine(screenPos - 1, 26, screenPos - 1, 209, ST77XX_BLUE); 1028 tft.drawLine(screenPos - zoomFactor, EKGmemo, screenPos, EKGwrite, ST77XX_WHITE); 1029 EKGmemo = EKGwrite; 1030 screenPos += zoomFactor; 1031 if (screenPos > 318) { 1032 tft.drawLine(screenPos - zoomFactor, 30, screenPos - zoomFactor, 209, MYCOLOR_DARKGRAY); 1033 tft.drawLine(0, 26, 0, 209, MYCOLOR_DARKGRAY); 1034 screenPos = zoomFactor; 1035 }; 1036 } 1037 //read keyboard 1038 int keyDown = readKB(); 1039 switch (keyDown) { 1040 case 0: //toggle sound 1041 tweeterMute = !tweeterMute; 1042 if (!tweeterMute) { 1043 keyBoardText4(0, "MUTE"); 1044 } else { 1045 keyBoardText4RED(0, "MUTE"); 1046 }; 1047 break; 1048 case 1: //zoom 1049 shortMemo = zoomFactor; 1050 if (shortMemo == 1) { 1051 zoomFactor = 2; 1052 keyBoardText4RED(1, "ZOOM"); 1053 } else { 1054 zoomFactor = 1; 1055 keyBoardText4(1, "ZOOM"); 1056 }; 1057 break; 1058 case 2: //halt 1059 EKGHalt = !EKGHalt; 1060 if (!EKGHalt) { 1061 keyBoardText4(2, "HALT"); 1062 } else { 1063 keyBoardText4RED(2, "HALT"); 1064 }; 1065 break; 1066 case 3: //stop 1067 EKGrunning = false; 1068 nextMenu = 1; 1069 displayPage(1); 1070 EKGHalt = false; 1071 zoomFactor = 1; 1072 //if recording data, store elpsed time 1073 if (storeEKG) storeRecTime(); 1074 break; 1075 } 1076 } 1077 tft.setTextColor(ST77XX_WHITE); 1078 tweeterOff(); 1079 digitalWrite(8, LOW); 1080 digitalWrite(LED, LOW); 1081 if (storeEKG) { 1082 eeprom_writeByte(activeEE, memoAddress - 1, 0); 1083 eeprom_writeByte(activeEE, memoAddress, 0); 1084 storeRecTime(); 1085 } 1086 nextMenu = 1; 1087 if (fromEeprom) { 1088 keyBoardText(0, ""); 1089 keyBoardText(1, ""); 1090 keyBoardText(2, ""); 1091 tft.setTextColor(ST77XX_WHITE); 1092 nextMenu = 10; 1093 } 1094} 1095 1096void setRTC(int locSec, int locMin, int locHour, int locDay, int locMonth, int locYear) { 1097 rtc.adjust(DateTime(locYear, locMonth, locDay, locHour, locMin, locSec)); 1098} 1099 1100void readRTC() { 1101 DateTime now = rtc.now(); 1102 tmMin = now.minute(); 1103 tmHour = now.hour(); 1104 tmDay = now.day(); 1105 tmMonth = now.month(); 1106 tmYear = now.year(); 1107} 1108 1109void sendFullBank(byte locBank) { 1110 //send date and time: Y - M - D - H - M 1111 Serial.println(1000 + locBank); 1112 Serial.println(eeprom_readByte(0x50, 8 * locBank + 5)); //Year LSB 1113 Serial.println(eeprom_readByte(0x50, 8 * locBank + 4)); //Year MSB 1114 Serial.println(eeprom_readByte(0x50, 8 * locBank + 3)); //Month 1115 Serial.println(eeprom_readByte(0x50, 8 * locBank + 2)); //Day 1116 Serial.println(eeprom_readByte(0x50, 8 * locBank + 1)); //Hour 1117 Serial.println(eeprom_readByte(0x50, 8 * locBank)); //Minutes 1118 tft.fillScreen(MYCOLOR_DARKGREEN); 1119 tft.setCursor(10, 30); 1120 tft.setTextSize(2); 1121 tft.print("Transferring bank "); 1122 tft.setTextSize(4); 1123 tft.print(locBank + 1); 1124 int locAnim = 20; 1125 tft.drawCircle(locAnim, 100, 20, ST77XX_WHITE); 1126 //Send data from EEprom and run animation 1127 unsigned int startMemo = (locBank & 1) * memoMax; 1128 int locEE = 0x51 + int(locBank / 2); 1129 byte locByte = eeprom_readByte(locEE, startMemo); 1130 Serial.println(locByte); 1131 for (unsigned int i = 1; i < memoMax; i++) { 1132 tft.drawCircle(locAnim, 100, 20, MYCOLOR_DARKGREEN); 1133 locAnim += 2; 1134 if (locAnim > 300) { locAnim = 20; }; 1135 tft.drawCircle(locAnim, 100, 20, ST77XX_WHITE); 1136 locByte = eeprom_readByteFast(locEE); 1137 Serial.println(locByte); 1138 if (locByte == 0) break; 1139 } 1140 Serial.println(2000 + locBank); 1141} 1142 1143void eeprom_writeByte(int deviceaddress, unsigned int eeaddress, byte data) { 1144 //write single byte from EEprom. 1145 int rdata = data; 1146 Wire.beginTransmission(deviceaddress); 1147 Wire.write((byte)(eeaddress >> 8)); // MSB 1148 Wire.write((byte)(eeaddress & 0xFF)); // LSB 1149 Wire.write(rdata); 1150 Wire.endTransmission(); 1151} 1152 1153byte eeprom_readByte(int deviceaddress, unsigned int eeaddress) { 1154 //read single byte from EEprom. If several bytres are to be read, 1155 //this routine opens the reading and gets the first byte. 1156 //the following bytes will be read by the "eeprom_readByteFast" routine 1157 byte rdata = 0xFF; 1158 Wire.beginTransmission(deviceaddress); 1159 Wire.write((byte)(eeaddress >> 8)); // MSB 1160 Wire.write((byte)(eeaddress & 0xFF)); // LSB 1161 Wire.endTransmission(); 1162 Wire.requestFrom(deviceaddress, 1); 1163 if (Wire.available()) rdata = Wire.read(); 1164 return rdata; 1165} 1166 1167byte eeprom_readByteFast(int deviceaddress) { 1168 byte rdata = 0xFF; 1169 Wire.requestFrom(deviceaddress, 1); 1170 if (Wire.available()) rdata = Wire.read(); 1171 return rdata; 1172} 1173 1174void storeRecTime() { 1175 //add length of recorded EKG measures to infos about this record 1176 int locLength = memoAddress & 16383; 1177 int locMSB = locLength >> 8; 1178 int locLSB = locLength & 255; 1179 eeprom_writeByte(0x50, 8 * currentBank + 6, locMSB); 1180 delay(5); 1181 eeprom_writeByte(0x50, 8 * currentBank + 7, locLSB); 1182 delay(5); 1183}
EKG monitor base
java
Code for PC app
1import processing.serial.*; 2import processing.pdf.*; 3String[]list0; 4String[]list1; 5int currentPage=0; 6myButton b0_offline=new myButton(color(128, 255, 128), 0, 255, 10, 40, 280, 40, "Work offline", false); 7myButton b0_arduino=new myButton(color(255, 172, 0), 0, 255, 10, 85, 280, 40, "Link to EKG module", false); 8myButton[] b2select; 9myButton b4List[]; 10myButton b4scrollUP; 11myButton b4scrollDOWN; 12myButton b4toolBar[]; 13myButton b4topBar[]; 14myButton b2OK; 15myButton deleteYES=new myButton(color(0, 255, 0), 0, 255, 93, 150, 100, 40, "YES", false); 16myButton deleteNO=new myButton(color(255, 0, 0), 0, 255, 227, 150, 100, 40, "NO", false); 17String fullPathToCurrentRec=""; 18int[][] ArduinoIn; 19String[]toSave=new String[16385]; 20boolean OFFLINE=false; 21String arduinoPort="no"; 22int nOfPorts; 23int appStatus; 24Serial port; 25boolean portFound; 26String input; 27boolean commIsOn=false; 28int inputVal; 29boolean transmitRaw=false; 30int rawPointer=0; 31boolean rawIn=false; 32int[] bankMemo=new int[32]; 33boolean[] banksToRead={false, false, false, false}; 34String[] bankName={"--", "--", "--", "--"}; 35int downloadCode=0; 36String[]transferCode={"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"}; 37int memoPointer=0; 38int[] verifSize={0, 0, 0, 0}; 39int currBank=0; 40int animPos; 41int inputCommand; 42boolean portIsOpen=false; 43boolean drawBogus=false; 44int transferStatus=0; 45int fileTransferStatus=0;//0-not started 1-started 2-done 46//transferStatus: 47// 0 : init session 48// 1 : port open 49// 2 : raw infos read, 50// 3: ready to select bank(s) 51// 4 : banks selected, transfer ECG data 52// 5 : all loaded, continue offline 53boolean mouseClick=false; 54String pathToFiles; 55int[]potOf2={1, 2, 4, 8}; 56int numberOfRecords; 57int selectOffset=0; 58String fileSelected=""; 59String[]ECGfile; 60boolean autoPlay=false; 61float zoomPlay=1; 62int playOffset=0; 63int playPos=0; 64int lastPulse=0; 65int nowPulse; 66int showGrid=0; 67//0: no grid 68//1: only seconds 69//2:seconds and tenths 70int askForDeletion=0; 71//0: nothing asked 72//1: option box visible 73//2: deletion confirmed 74int currentRecLength=0; 75int mouseScroll=0; 76int keyPress=0; 77boolean offlineSelected=true; 78int fileSelPos=-1; 79boolean pdfDone=false; 80int backTime; 81float barScale; 82 83void settings() { 84 float locW=displayWidth*0.8; 85 float locH=displayHeight*0.8; 86 size(int(locW), int(locH), P2D); 87} 88 89void setup() { 90 background(64); 91 surface.setTitle("BM's EKG interface (2024)"); 92 // surface.setResizable(true); 93 stroke(255); 94 textSize(30); 95 list0=new String[10]; 96 list1=new String[10]; 97 for (int ii=0; ii<10; ii++) { 98 list0[ii]=" "; 99 list1[ii]=" "; 100 } 101 ArduinoIn=new int[4][16385]; 102 b2select=new myButton[4]; 103 for (int i=0; i<4; i++) { 104 b2select[i]=new myButton(color(0, 255, 0), 0, color(255, 0, 0), 350, 38+30*i, 80, 28, " ", false); 105 } 106 b2OK=new myButton(32, 255, 32, 450, 70, 60, 60, "OK", false); 107 b4List=new myButton[10]; 108 for (int i=0; i<10; i++) { 109 b4List[i]=new myButton(color(0, 255, 0), 0, color(255, 0, 0), 10, 90+i*30, 200, 28, " ", false); 110 } 111 b4scrollUP=new myButton(color(0, 255, 0), 0, color(255, 0, 0), 10, 60, 200, 28, " ", false); 112 b4scrollDOWN=new myButton(color(0, 255, 0), 0, color(255, 0, 0), 10, 390, 200, 28, " ", false); 113 transferStatus=0; 114 currentPage=0; 115 pathToFiles=dataPath("")+"/"; 116 b4toolBar=new myButton[6]; 117 float sixth=width/6; 118 float topButtonWidth=sixth-10; 119 for (int i=0; i<6; i++) { 120 b4toolBar[i]=new myButton(color(0, 255, 0), 0, color(255, 0, 0), 5+sixth*i, 30, topButtonWidth, 40, " ", false); 121 } 122 b4topBar=new myButton[5]; 123 for (int i=0; i<5; i++) { 124 b4topBar[i]=new myButton(color(0, 255, 0), 0, color(255, 0, 0), 5+sixth*(i+2), 30, topButtonWidth, 40, " ", false); 125 } 126 println("PROGRAM STARTS HERE"); 127} 128void draw() { 129 switch (currentPage) { 130 case 0://home. fork: 1)connect to Arduino 2)work offline 131 background(64); 132 b0_offline.bShow(); 133 b0_arduino.bShow(); 134 if (keyPress==38) { 135 offlineSelected=true; 136 } 137 if (keyPress==40) { 138 offlineSelected=false; 139 } 140 if ((b0_offline.isInside())&& mousePressed) {//asks for offline work 141 fileSelected=""; 142 mouseClick=false; 143 currentPage=4; 144 } 145 if ((b0_arduino.isInside())&& mousePressed) {//asks for link to Arduino 146 //connect to ECG module and load bank(s) 147 background(96); 148 text("disconnect EKG module, wait a few seconds and reconnect it...", 10, 40); 149 text("if not done within 20 seconds, back to home page...", 10, 70); 150 backTime=(second()+20) % 60; 151 currentPage=1; 152 rawPointer=0; 153 mouseClick=false; 154 transferStatus=0; 155 drawBogus=false; 156 } 157 break; 158 case 1 : //connect to Arduino and load raw bank values 159 background(64); 160 if (second()>backTime) { 161 currentPage=0; 162 } 163 if (!portIsOpen) { 164 arduinoPort=arduinoFound(); 165 //println(arduinoPort); 166 try { 167 port = new Serial(this, arduinoPort, 38400); 168 portIsOpen=true; 169 port.clear(); 170 while (port.available()>0) { 171 port.read(); 172 } 173 transferStatus=1; 174 } 175 catch(Exception e) { 176 println( "error opening port"); 177 currentPage=0; 178 } 179 } 180 if (transferStatus==1) { 181 //println("line 176"); 182 text("Start link by pressing EXT on EKG module", 10, 30); 183 //when link ok, Serial read routine sets transferStatus to 2 184 if (!commIsOn) {//establish contact-wait for "P" from Arduino 185 if (port.available()>0) { 186 input=port.readStringUntil(10); 187 if (input!=null) { 188 if (input.substring(0, 1).equals("P")==true) { 189 port.clear(); 190 port.write("Q"); 191 delay(100); 192 commIsOn=true; 193 } 194 } 195 } 196 } 197 if (commIsOn) {//comm is on, load raw values 198 if (port.available()>0) { 199 input=port.readStringUntil(10); 200 if (input!=null) { 201 try { 202 inputVal=Integer.parseInt(input.trim()); 203 } 204 catch(NumberFormatException e) { 205 inputVal=999; 206 } 207 if (inputVal<300) { 208 bankMemo[rawPointer]=inputVal; 209 rawPointer+=1; 210 print("RV "); 211 print(rawPointer); 212 print("--"); 213 println(inputVal); 214 } 215 if (inputVal==4001) { 216 transferStatus=2; 217 println("raw is in"); 218 currentPage=2; 219 } 220 } 221 } 222 } 223 } 224 break; 225 case 2: 226 background(32, 128, 32); 227 //format banks to display 228 String locName=""; 229 int locY; 230 int locM; 231 int locD; 232 int locH; 233 int locN; 234 int locT; 235 int locDM; 236 int locDS; 237 fill(255); 238 for (int i=0; i<4; i++) { 239 locY=bankMemo[i*8+5]*256+bankMemo[i*8+4]; 240 locM=bankMemo[i*8+3]; 241 locD=bankMemo[i*8+2]; 242 locH=bankMemo[i*8+1]; 243 locN=bankMemo[i*8]; 244 locT=bankMemo[i*8+6]*256+bankMemo[i*8+7]; 245 locT=int(0.5+locT/33); 246 locDM=int(locT/60); 247 locDS=locT-60*locDM; 248 locName=str(locY)+" "+toString2(locM)+"/"+toString2(locD) 249 +" "+toString2(locH)+":"+toString2(locN)+" "+toString2(locDM)+"."+toString2(locDS); 250 bankName[i]=locName; 251 } 252 if (transferStatus<3) { 253 //clear select buttons 254 for (int i=0; i<4; i++) { 255 b2select[i].bp=false; 256 mouseClick=false; 257 } 258 transferStatus=3;//raw info about banks are in. Proceed... 259 } 260 if (transferStatus==3) {//display banks 261 fill(255); 262 for (int i=0; i<4; i++) { 263 text(bankName[i], 10, 60+30*i); 264 if (b2select[i].bp) { 265 b2select[i].bc=color(0, 255, 0); 266 b2select[i].bS="YES"; 267 banksToRead[i]=true; 268 } else { 269 b2select[i].bc=color(255, 0, 0); 270 b2select[i].bS="NO"; 271 banksToRead[i]=false; 272 } 273 if ((b2select[i].isInside())&&(mouseClick)) { 274 b2select[i].bp=!b2select[i].bp; 275 mouseClick=false; 276 } 277 b2select[i].bShow(); 278 } 279 text("Date time minutes", 10, 20); 280 text("Select the bank(s) to download", 10, 190); 281 b2OK.bShow(); 282 fill(255); 283 if ((b2OK.isInside())&&(mouseClick)) { 284 //transfer banks 285 //construct code for files to download 286 downloadCode=(banksToRead[0]?1:0) 287 +(banksToRead[1]?2:0) 288 +(banksToRead[2]?4:0) 289 +(banksToRead[3]?8:0); 290 print("download code "); 291 port.write(transferCode[downloadCode]); 292 //transfer banks should begin, according to sent code "A" to "P"... 293 transferStatus=4; 294 } 295 mouseClick=false; 296 } 297 if ((transferStatus==4)&&(fileTransferStatus==0)) { 298 thread("loadBanks"); 299 fileTransferStatus=1; 300 } 301 if (transferStatus==4) { 302 text("Transferring bank "+(currBank+1), 10, 50); 303 //text("O", verifSize[currBank]%width, 50); 304 barScale=(width-20)*100000/(bankMemo[currBank*8+6]*256+bankMemo[currBank*8+7]); 305 noStroke(); 306 fill(0, 0, 255); 307 rect(width-113, 20, 103, 30); 308 rect(10, 80, width-20, 16); 309 fill(0); 310 rect(10, 80, barScale*verifSize[currBank]/100000, 16); 311 rect(width-113+((millis()/20)%100), 20, 3, 30); 312 313 //line( 314 } 315 break; 316 case 3 : //save Banks 317 background(32, 32, 0); 318 fill(255); 319 //save banks 320 for (int i=0; i<4; i++) { 321 if ((downloadCode & potOf2[i])!=0) { 322 println("save bank # "+(1+i)); 323 locY=bankMemo[i*8+5]*256+bankMemo[i*8+4]; 324 locM=bankMemo[i*8+3]; 325 locD=bankMemo[i*8+2]; 326 locH=bankMemo[i*8+1]; 327 locN=bankMemo[i*8]; 328 locT=bankMemo[i*8+6]*256+bankMemo[i*8+7]; 329 String locBankName=str(locY)+toString2(locM)+toString2(locD) 330 +toString2(locH)+toString2(locN); 331 for (int ii=0; ii<16384; ii++) { 332 toSave[ii]=str(ArduinoIn[i][ii]); 333 } 334 toSave[16384]=str(locT); 335 saveStrings(pathToFiles+locBankName+".txt", toSave); 336 } 337 } 338 selectOffset=0; 339 fileSelected=""; 340 currentPage=4; 341 break; 342 case 4: 343 //work offline 344 String locS; 345 background(160, 160, 255); 346 if (fileSelected.equals("")) { 347 numberOfRecords=0; 348 File file = new File(dataPath("")); 349 File[] files = file.listFiles(); 350 numberOfRecords=files.length; 351 String[]fileName=new String[files.length]; 352 for (int ii=0; ii<numberOfRecords; ii++) { 353 fileName[ii]=files[ii].getName(); 354 } 355 fileName=sort(fileName); 356 boolean scroll=(numberOfRecords>10); 357 int boxHeight=10; 358 if (numberOfRecords<10) { 359 boxHeight=numberOfRecords; 360 } else { 361 b4scrollUP.bShow(); 362 b4scrollDOWN.bShow(); 363 fill(0); 364 triangle(b4scrollUP.bx+b4scrollUP.bw/2, b4scrollUP.by+5, 365 b4scrollUP.bx+b4scrollUP.bw/2-15, b4scrollUP.by+b4scrollUP.bh-5, 366 b4scrollUP.bx+b4scrollUP.bw/2+15, b4scrollUP.by+b4scrollUP.bh-5); 367 triangle(b4scrollDOWN.bx+b4scrollDOWN.bw/2-15, b4scrollDOWN.by+5, 368 b4scrollDOWN.bx+b4scrollDOWN.bw/2+15, b4scrollDOWN.by+5, 369 b4scrollDOWN.bx+b4scrollDOWN.bw/2, b4scrollDOWN.by+b4scrollDOWN.bh-5); 370 } 371 fill(255); 372 for (int i=0; i<boxHeight; i++) { 373 b4List[i].bShow(); 374 try { 375 locS=fileName[i+selectOffset]; 376 } 377 catch(Exception e) { 378 locS="0000000000000000"; 379 } 380 b4List[i].bS=locS.substring(0, locS.length()-4); 381 if (b4List[i].isInside()) { 382 b4List[i].bc=color(255); 383 } else { 384 b4List[i].bc=color(125); 385 } 386 //detect mouse over 387 if (b4List[i].isInside()) { 388 text(formatFileName(b4List[i].bS), 240, b4List[i].by+24); 389 //text(bankName[i], 240, b4List[i].by+24); 390 if (mousePressed) { 391 fileSelected=b4List[i].bS; 392 } 393 } 394 if (mouseScroll>0) { 395 if (selectOffset<(numberOfRecords-10)) { 396 selectOffset+=1; 397 } 398 mouseScroll=0; 399 } 400 if (b4scrollDOWN.isInside()) { 401 b4scrollDOWN.bc=color(180, 255, 180); 402 } else { 403 b4scrollDOWN.bc=color(0, 255, 0); 404 } 405 if (b4scrollUP.isInside()) { 406 b4scrollUP.bc=color(180, 255, 180); 407 } else { 408 b4scrollUP.bc=color(0, 255, 0); 409 } 410 if ((scroll)&&(b4scrollDOWN.isInside())&&(mouseClick)) { 411 if (selectOffset<(numberOfRecords-10)) { 412 selectOffset+=1; 413 } 414 mouseClick=false; 415 } 416 if (mouseScroll<0) { 417 if (selectOffset>0) { 418 selectOffset-=1; 419 } 420 mouseScroll=0; 421 } 422 if ((scroll)&&(b4scrollUP.isInside())&&(mouseClick)) { 423 if (selectOffset>0) { 424 selectOffset-=1; 425 } 426 mouseClick=false; 427 } 428 } 429 text("Select a file", 30, 35); 430 if ((keyPress==38)&&(keyPressed)) { 431 fileSelPos-=1; 432 if (fileSelPos<(-1)) { 433 fileSelPos=-1; 434 } 435 if ((fileSelPos<0)&&(numberOfRecords<11)) { 436 fileSelPos=0; 437 } 438 keyPress=0; 439 } 440 if ((keyPress==40)&&(keyPressed)) { 441 fileSelPos+=1; 442 if ((numberOfRecords<11)&&(fileSelPos>(numberOfRecords-1))) { 443 fileSelPos=numberOfRecords-1; 444 } 445 if ((numberOfRecords>10)&&(fileSelPos>10)) { 446 fileSelPos=10; 447 } 448 keyPress=0; 449 } 450 } 451 if ((!fileSelected.equals(""))&&(!fileSelected.equals("OK"))) { 452 text("file: "+fileSelected, 20, 40); 453 fullPathToCurrentRec=dataPath("")+"/"+fileSelected+".txt"; 454 ECGfile=loadStrings(fullPathToCurrentRec); 455 //println(ECGfile); 456 457 fileSelected="OK"; 458 } 459 if (fileSelected.equals("OK")) { 460 461 // if (askForPDF){ 462 // fill(200, 100, 100); 463 // rect(0, 0, width, 60); 464 // fill(255); 465 // text("CREATING PDF FILE....................",30,50); 466 // draw(); 467 //} 468 469 470 //Show ECG.... 471 fill(255); 472 rect(20, 60, width-40, height-120); 473 float sixth=width/6; 474 float topButtonWidth=sixth-10; 475 b4toolBar[0].bS="RST";//autoPlay ? "AUTO":"MAN"; 476 b4toolBar[1].bS="ZOOM+"; 477 b4toolBar[2].bS="ZOOM-"; 478 b4toolBar[3].bS="<"; 479 b4toolBar[4].bS=">"; 480 b4toolBar[5].bS="GRID"; 481 for (int i=0; i<6; i++) { 482 b4toolBar[i].by=height-50; 483 b4toolBar[i].bw=topButtonWidth; 484 b4toolBar[i].bx=5+sixth*i; 485 b4toolBar[i].bShow(); 486 } 487 b4topBar[0].bS=pdfDone? "PDF DONE":"PDF"; 488 b4topBar[1].bS="DEL"; 489 b4topBar[2].bS="LOAD"; 490 b4topBar[3].bS="HOME"; 491 b4topBar[4].bS="EXIT"; 492 for (int i=0; i<5; i++) { 493 b4topBar[i].by=8; 494 b4topBar[i].bw=topButtonWidth; 495 b4topBar[i].bx=5+sixth*i; 496 if (i>1) { 497 b4topBar[i].bx+=sixth; 498 } 499 if (i!=1) { 500 b4topBar[i].bShow(); 501 } else { 502 if (askForDeletion==0) { 503 b4topBar[i].bShow(); 504 } 505 } 506 } 507 stroke(0); 508 float picLeft=20; 509 float picWidth=width-40; 510 float picTop=60; 511 float picHeight=height-120; 512 float picScale=(picHeight)/256; 513 fill(0); 514 if (showGrid==2) { 515 float secondGap=zoomPlay*1.333; 516 float locVPos=secondGap; 517 stroke(255, 200, 200); 518 //draw vertical red line every 50 millisecond 519 while (locVPos<picWidth) { 520 line(locVPos, picTop, locVPos, picTop+picHeight); 521 locVPos+=secondGap; 522 } 523 stroke(0); 524 } 525 if (showGrid!=0) { 526 stroke(255, 128, 128); 527 if (showGrid==2) { 528 stroke(255, 0, 0); 529 } 530 float secondGap=zoomPlay*6.667; 531 float locVPos=secondGap; 532 //draw vertical red line every 1/4 second 533 while (locVPos<picWidth) { 534 line(locVPos, picTop, locVPos, picTop+picHeight); 535 locVPos+=secondGap; 536 } 537 stroke(0); 538 } 539 //draw ECG signal 540 for (int j=0; j<picWidth/zoomPlay; j++) { 541 line(picLeft+j*zoomPlay, (picTop+picScale*float(ECGfile[j+playOffset])), 542 picLeft+(j+1)*zoomPlay, (picTop+picScale*float(ECGfile[j+1+playOffset]))); 543 //detect pulse 544 if ((int(ECGfile[j+1+playOffset])-int(ECGfile[j+playOffset]))>30) { 545 //detect previous pulse 546 for (int i=j+playOffset-2; i>1; i--) { 547 //if ((int(ECGfile[i])-int(ECGfile[i-1]))>30) { 548 if ((int(ECGfile[i+1])-int(ECGfile[i]))>30) { 549 nowPulse=int(2000/(j+playOffset-i)+0.5); 550 i=0; 551 } 552 } 553 554 //pulse detected 555 textSize(15); 556 text(str(nowPulse), int(picLeft+(j+1)*zoomPlay)-textWidth(str(nowPulse))/2-zoomPlay, picTop+25); 557 } 558 } 559 textSize(30); 560 if ((b4topBar[1].isInside())&&(mouseClick)&&(askForDeletion==0)) {//delete current record 561 mouseClick=false; 562 askForDeletion=1; 563 } 564 if ((b4topBar[2].isInside())&&(mousePressed)) {//back to record select 565 fileSelected=""; 566 mouseClick=false; 567 mousePressed=false; 568 pdfDone=false; 569 } 570 if ((b4topBar[3].isInside())&&(mousePressed)) {//back home 571 fileSelected=""; 572 mouseClick=false; 573 currentPage=0; 574 } 575 if ((b4topBar[4].isInside())&&(mousePressed)) {//exit app 576 exit(); 577 } 578 if ((b4toolBar[5].isInside())&&(mouseClick)) {//toggle grid 579 showGrid+=1; 580 if (showGrid>2) { 581 showGrid=0; 582 } 583 mouseClick=false; 584 } 585 if ((b4toolBar[4].isInside())&&(mousePressed)) {//move > 586 if (mouseButton==LEFT) { 587 playOffset+=1;//delay(100); 588 } 589 if (mouseButton==RIGHT) { 590 playOffset+=5; 591 } 592 if (float(playOffset)>(16383-picWidth)) { 593 playOffset=int(16383-picWidth); 594 } 595 } 596 if ((b4toolBar[3].isInside())&&(mousePressed)) {//move < 597 if (mouseButton==LEFT) { 598 playOffset-=1; 599 } 600 if (mouseButton==RIGHT) { 601 playOffset-=5; 602 } 603 if (float(playOffset)<0) { 604 playOffset=0; 605 } 606 } 607 if ((b4toolBar[2].isInside())&&(mousePressed)) {// Zoom - 608 if (mouseButton==LEFT) { 609 zoomPlay-=0.015; 610 } 611 if (mouseButton==RIGHT) 612 { 613 zoomPlay-=0.075; 614 } 615 if (zoomPlay<0.5) { 616 zoomPlay=0.5; 617 } 618 } 619 if ((b4toolBar[1].isInside())&&(mousePressed)) {//Zoom + 620 if (mouseButton==LEFT) { 621 zoomPlay+=0.015; 622 } 623 if (mouseButton==RIGHT) 624 { 625 zoomPlay+=0.075; 626 } 627 if (zoomPlay>10) { 628 zoomPlay=10; 629 } 630 } 631 if ((b4toolBar[0].isInside())&&(mousePressed)) {//Reset picture 632 zoomPlay=1; 633 playOffset=0; 634 } 635 //mouseClick=false; 636 } 637 if (askForDeletion==1) { 638 rect(60, 80, 300, 150); 639 fill(255); 640 text("Delete current file ?", 60+(300-textWidth("Delete current file ?"))/2, 120); 641 deleteYES.bShow(); 642 deleteNO.bShow(); 643 if ((deleteNO.isInside())&&(mousePressed)) { 644 askForDeletion=0;//means cancel 645 } 646 if ((deleteYES.isInside())&&(mousePressed)) { 647 askForDeletion=2;//means delete the file.... 648 //So, delete it 649 File f = new File(fullPathToCurrentRec); 650 print("delete: "); 651 println(f); 652 if (f.exists()) { 653 f.delete(); 654 askForDeletion=0; 655 currentPage=3; 656 } 657 } 658 } 659 660 if ((b4topBar[0].isInside())&&(mouseClick)&&(!pdfDone)) {//export a PDF version 661 exportPDF(); 662 } 663 mouseClick=false; 664 break; 665 }//end switch 666} 667 668String formatFileName(String IN) { 669 String locS=IN.substring(0, 4)+" "+IN.substring(4, 6)+"/"+IN.substring(6, 8) 670 +" "+IN.substring(8, 10)+":"+IN.substring(10, 12);//+":"+IN.substring(12,14); 671 return locS; 672} 673 674void mouseClicked() { 675 mouseClick=true; 676} 677 678void keyPressed() { 679 keyPress=int(keyCode); 680} 681 682String arduinoFound() { 683 int locTime=millis(); 684 String retVal="";//default for return value 685 boolean arduinoIsHere=false; 686 int NofP; 687 while (!arduinoIsHere) { 688 //clear first list 689 for (int ii=0; ii<10; ii++) { 690 list0[ii]=" "; 691 } 692 //fill first list 693 if (Serial.list().length>0) { 694 try { 695 for (int i=0; i<Serial.list().length; i++) { 696 697 list0[i]=Serial.list()[i]; 698 } 699 } 700 catch(Error e) { 701 list0[0]=""; 702 } 703 } 704 NofP=Serial.list().length; 705 delay(50); 706 //test if Serial.list got a new member 707 if (Serial.list().length>NofP) { 708 //clear second list 709 for (int ii=0; ii<10; ii++) { 710 list1[ii]=" "; 711 } 712 //fill second list 713 for (int i=0; i<Serial.list().length; i++) { 714 list1[i]=Serial.list()[i]; 715 } 716 delay(50); 717 //now, list0 ashould be shorter than list1... 718 for (int i1=0; i1<list1.length; i1++) { 719 arduinoIsHere=true; 720 for (int i0=0; i0<list0.length; i0++) { 721 if (list1[i1].equals(list0[i0])) { 722 arduinoIsHere=false; 723 //break; 724 } 725 }//end of i0 loop 726 if (arduinoIsHere) { 727 retVal=list1[i1]; 728 currentPage=1; 729 break; 730 } 731 }//end of if loop 732 }//end of if loop 733 if (millis()>(locTime+15000)) { 734 arduinoIsHere=true; 735 currentPage=0; 736 return "no"; 737 } 738 }//end while loop 739 return retVal; 740} 741 742String toString2(int theVal) { 743 return ((theVal<10)? "0":"")+str(theVal); 744} 745 746String toString10(int theVal) { 747 String locS=" "+str(theVal); 748 locS=locS.substring(0, 10); 749 return locS; 750} 751 752void exportPDF() { 753 String pdfName=fullPathToCurrentRec; 754 pdfName=pdfName.substring(1, pdfName.length()-4); 755 pdfName=pdfName.substring(pdfName.length()-12); 756 String pdfFullPath=pdfName+".pdf"; 757 println("PDF: "+pdfFullPath); 758 PGraphicsPDF pdf =(PGraphicsPDF) createGraphics(841, 595, PDF, pdfFullPath); 759 String titleComment="Record ref. "; 760 titleComment=titleComment+pdfName.substring(0, 4)+" "+ 761 pdfName.substring(4, 6)+"/"+ 762 pdfName.substring(6, 8)+" "+ 763 pdfName.substring(8, 10)+":"+ 764 pdfName.substring(10, 12); 765 //page has 4 rows. Each row 800 points 766 //determine record length 767 for (int i=1; i<16384; i++) { 768 if (ECGfile[i].equals("0")) { 769 currentRecLength=i-1; 770 break; 771 } 772 } 773 int numberOfLines=1+int(0.5+(currentRecLength-1)/375); 774 int numberOfPages=int((numberOfLines-1)/4)+1; 775 pdf.beginDraw(); 776 int pageOffset=0; 777 int locNofLines=4; 778 pdf.strokeWeight(0.2); 779 for (int thePage=0; thePage<numberOfPages; thePage+=1) { 780 // print a PDF page 781 pageOffset=thePage*1500; 782 locNofLines=numberOfLines-4*thePage; 783 if (locNofLines>4)locNofLines=4; 784 pdf.background(255); 785 pdf.fill(0); 786 pdf.stroke(0); 787 pdf.text(titleComment+" page "+str(thePage+1)+"/"+str(numberOfPages), 30, 30); 788 pdf.text("BM'S EKG © 2024", 680, 30); 789 pdf.text("BM'S EKG © 2024", 681, 30); 790 //paint second marks 791 pdf.stroke(255, 172, 172);//pale red 792 pdf.strokeWeight(0.2); 793 for (int rowNum=0; rowNum<locNofLines; rowNum++) { 794 for (float Xpos=20; Xpos<770; Xpos+=2.667) { 795 pdf.line(Xpos, rowNum*126+78, Xpos, rowNum*126+172); 796 } 797 } 798 pdf.stroke(255, 80, 80);//brighter red 799 for (int rowNum=0; rowNum<locNofLines; rowNum++) { 800 // pdf.line(20, rowNum*126+160, 770, rowNum*126+160); 801 for (float Xpos=20; Xpos<770; Xpos+=13.333) { 802 pdf.line(Xpos, rowNum*126+78, Xpos, rowNum*126+172); 803 } 804 pdf.line(20, rowNum*126+128, 756, rowNum*126+128); 805 } 806 //paint ecg signal 807 pdf.strokeWeight(0.4); 808 //paint ecg signal 809 pdf.stroke(0); 810 for (int rowNum=0; rowNum<locNofLines; rowNum++) { 811 for (int Xpos=0; Xpos<375; Xpos++) { 812 if ((int(ECGfile[Xpos+rowNum*375+pageOffset])>25)&&(int(ECGfile[Xpos+1+rowNum*375+pageOffset])>25)) { 813 pdf.line(Xpos*2+20, rowNum*126+80+0.5*(int(ECGfile[Xpos+rowNum*375+pageOffset])-26), 814 (Xpos+1)*2+20, rowNum*126+80+0.5*(int(ECGfile[Xpos+1+rowNum*375+pageOffset])-26)); 815 } 816 } 817 } 818 if (thePage<(numberOfPages-1))pdf.nextPage(); 819 } 820 pdf.dispose(); 821 pdf.endDraw(); 822 pdfDone=true; 823} 824 825void mouseWheel(MouseEvent event) { 826 mouseScroll = event.getCount(); 827} 828 829void loadBanks() { 830 int pAv; 831 int locDelay=12; 832 while (fileTransferStatus<2) { 833 pAv=port.available(); 834 if ((transferStatus==4)&&(pAv>0)) {//Arduino is sending required banks 835 // println(pAv); 836 locDelay=13; 837 if (pAv<40)locDelay=50; 838 if (pAv>80)locDelay=11; 839 try { 840 delay(locDelay); 841 input = port.readStringUntil(10); 842 try { 843 inputVal=Integer.parseInt(input.trim()); 844 } 845 catch(NumberFormatException e) { 846 inputVal=0; 847 } 848 if (inputVal==3333) {//all data transferred 849 port.stop(); 850 currentPage=3; 851 fileTransferStatus=2; 852 for (int i=0; i<4; i++) { 853 println(verifSize[i]+"="+(bankMemo[i*8+6]*256+bankMemo[i*8+7])); 854 } 855 println("all in"); 856 } 857 if ((inputVal<1050)&&(inputVal>999)) {//bank header.Set pointer 858 currBank=inputVal-1000; 859 memoPointer=0; 860 verifSize[currBank]=-1; 861 } 862 if (inputVal<300) { 863 ArduinoIn[currBank][memoPointer]=inputVal; 864 memoPointer+=1; 865 verifSize[currBank]+=1; 866 } 867 } 868 catch(Error e) { 869 println("ERROR"); 870 input=null; 871 } 872 } 873 } 874} 875 876int bankTime(int theBank) { 877 return bankMemo[theBank*8+6]*256+bankMemo[theBank*8+7]; 878} 879 880 881class myButton { 882 color bc; 883 color bd; 884 color bi; 885 float bx; 886 float by; 887 float bw; 888 float bh; 889 String bS; 890 boolean bp; 891 892 myButton(color locC, color locD, color locI, 893 float locX, float locY, float locW, float locH, 894 String locS, boolean locP) { 895 bc=locC;//normal color of button background 896 bd=locD;//nomal color of button text 897 bi=locI;//contrast color of button background 898 bx=locX;// x pos of left upper corner 899 by=locY;//y pos of left upper corner 900 bw=locW;//width 901 bh=locH;//height 902 bS=locS;//text 903 bp=locP;//boolean pressed/not pressed 904 } 905 906 void bShow() { 907 int locMemo=g.fillColor; 908 stroke(0); 909 fill(bc); 910 rect(bx, by, bw, bh); 911 fill(bd); 912 text(bS, bx+(bw-textWidth(bS))/2, by+bh/2+((textAscent()-textDescent())/2)); 913 fill(locMemo); 914 } 915 916 boolean isInside() { 917 boolean locTest=false; 918 if ((mouseX>bx)&&(mouseX<(bx+bw))&&(mouseY>by)&&(mouseY<(by+bh))) { 919 locTest=true; 920 } 921 return locTest; 922 } 923}//end class myButton
Downloadable files
The handbook
How the device works
https://www.dropbox.com/scl/fi/z4hwhqscki1brxkj9na3a/EKG_monitor_handbook_10-5.odt?rlkey=ofd2vl0xyjx5ygev1w3hoh85a&st=f5kv3qxr&raw=1
stl files for 3D printed parts
Button: 2 versions, for different micro-switches
https://www.dropbox.com/scl/fo/4mghdric0qvm5fw9gjnq3/ABMjDoC_oBR8n1pDReBbA8U?rlkey=8k0vi2ftwf4yl251wake7of6n&st=4wsxxxzu&raw=1
Video Demo
How it works...
https://www.dropbox.com/scl/fi/7avp3hpm6m3eibf1gju3t/EKG_bm.avi?rlkey=jqzskef8mt7jbp1h86m2nih6o&st=hgmmrp57&raw=1
Schematics
NB: The 2inch display from Waveshare runs on 5V
EKGdiagram.png

Inside...
The case is 3D printed.
openSpread2.jpg

Front side of the pcb
Connectors are for the Arduino and the display
pcb_front_connections.jpg

Back side of the pcb
The empty room on the left is for the battery
pcb_back_full.jpg

Comments
Only logged in users can leave comments