AD8232와 ESP8266을 이용해

ECG 로거를 제작함

초기 버전 완료하여 기록을 남김 ...

 

심전도를 SD카드에 저장 

SPIFFS에 저장하기는 속도도 느리고

용량제한도 있어 사용하지 않음

 

SD카드에 저장하는데 매 ADC 읽기 이벤트 마다

SD카드에 저장하면 전체적인 속도가 매우 느려짐

따라서 해결책을 구글검색에서 조언받음

약512바이트 크기의 버퍼 2개를 교대로 사용해서 저장하고

하나의 버퍼가 다 차면 그때 SD카드에 저장

그동안은 다른 버퍼에 저장...

 

프로토타입 - 케이스에 조립

프로토 타입 - 테스트 중

케이스 : TINKERCAD -- 수정할 곳이 있음 (배터리 홀더, 충전잭, 커버의 LED 구멍위치)

ecg-v07case.stl
0.13MB
ecg-v07top.stl
0.13MB

프로그램 소스

// ============================================================================================
// ESP8266 보드 설치하기
// https://learn.sparkfun.com/tutorials/esp8266-thing-hookup-guide/installing-the-esp8266-arduino-addon
// To begin, we’ll need to update the board manager with a custom URL. Open up Arduino, 
// then go to the Preferences (File > Preferences). Then, towards the bottom of the window, 
// copy this URL into the “Additional Board Manager URLs” text box:
//  ==> http://arduino.esp8266.com/stable/package_esp8266com_index.json
// Hit OK. Then navigate to the Board Manager by going to Tools > Boards > Boards Manager. 
// There should be a couple new entries in addition to the standard Arduino boards. 
// Look for esp8266. Click on that entry, then select Install.
//
// 'LIB' should be copied into under 'C:\Users\XXXXXXXXXXX\Documents\Arduino\libraries'
//
// SSD1306 Lib for ESP8266
// https://randomnerdtutorials.com/esp8266-0-96-inch-oled-display-with-arduino-ide/
// https://github.com/ThingPulse/esp8266-oled-ssd1306   (below is same LIB)
// https://github.com/squix78/esp8266-oled-ssd1306/archive/master.zip
// unzip to : C:\Users\XXXXXXXXXXX\Documents\Arduino\libraries
// other lib not tested
//
// Note : esp8266 board generic unknown error
// https://arduino-esp8266.readthedocs.io/en/latest/faq/a04-board-generic-is-unknown.html#how-to-fix-it
// 아래 폴더 아래에 있는 오래된 것 삭제 후 재시작
// => C:\Users\XXXXXXXXXXX\AppData\Local\Arduino15\packages\esp8266\hardware 
//  The blue LED on the ESP-01 - GPIO1 : BLUE = TxD --> GPIO 1, RxD = GPIO 3
//  The blue LED on the Wemos D1 mini ESP-12F - GPIO2
//  10-bit analog ADC. The ADC range is from 0V to 1.0V (0 ... 1023)
// Timer & Ticker Exam : 
//   https://circuits4you.com/2018/01/02/esp8266-timer-ticker-example/
//   'timer 0' is for WiFi, only 'timer 1' can be used
//   However 'Ticker' is prefered due to 'crash'
// Internal SD LIB is used no need to install
// SD exam only : https://www.instructables.com/id/SD-Card-Module-With-ESP8266/
// https://github.com/G6EJD/ESP8266-SD-Card-Reading-Writing/blob/master/ESP8266_D1_MicroSD_Test.ino
// ============================================================================================
// 2020.01.27 : 최초버전
//              OLED 연결 : 0x3C address of the OLED, SDA=GPIO 0, SCL=GPIO 2
//              SD카드 연결 : SCK,MOSI,MISO,SS 연결 (GPIO15 = SS)
// 2020.02.17 : micro SD 카드 연결 및 시간 + 삼각파 + AD값 저장
// 2020.02.18 : SD카드 저장시 속도 느려 그래프 디스플레이 잘 안됨 (SD 끄면 그래프 정상)
// 2020.02.21 : SPIFFS 사용 - 속도 느림 ....
// 2020.02.21 : SPIFFS 삭제 .. SD 저장가능하고 ECG는 판독할 수준임
// 2020.02.22 : SD 쓰기버퍼 시험
//              크기 500바이트 버퍼 2개 사용 후 교차 저장 - 작동속도 UP! (1sample/2-3ms)
//              10ms 마다 저장하도록 변경, OLED 그래프는 900-300 범위로 설정
// ============================================================================================
=

 

프로그램 변수 및 셋업과 타임 이벤트 핸들러

매 10ms 마다 이벤트 핸들러를 호출함 --> 5ms 마다 저장으로 수정중

clockTick.attach_ms(10, ClockHandler);  //  Use 'attach_ms' for ms

void ClockHandler(){      // Not much task here / no function call or RESET
  
  ms++;
  yaxis = yaxis + ydirection;
  if(yaxis>300) ydirection = -1;          // ECG의 시간기준 톱니파 생성
  if(yaxis<0) ydirection = 1; 
  if(ms>=100) {ms = 0; sec++;}
  if(sec>=60) {sec = 0; m++;}
  if(m>=60) {m = 0; h++;}
  if(h>=24) h = 0;
}
// -------------------------------------------------------------------------------------
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  //pinMode(A0, INPUT_ANALOG); 
  digitalWrite(LED_BUILTIN, LOW);
  Serial.begin(115200);                  // Arduino monitor
  display.init();                         // OLED stuff : Initialise the display.
  //display.flipScreenVertically();       // flipping 
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 0, "ESP Clock");
  display.drawString(70, 0, "Clock Init ...");  
  display.display();                      // write the buffer to the display
  //clockTick.attach(1, ClockHandler);    //  Use 'attach' for sec
  clockTick.attach_ms(10, ClockHandler);  //  Use 'attach_ms' for ms
  
  if (!SD.begin(CS_PIN)) {
    Serial.println("Fail to open SD");
    display.drawString(0, 0, "SD Fail");
    display.display(); 
    return;
  }
  File root;
  root = SD.open("/");
  root.rewindDirectory();
  printDirectory(root, 0);                      // Display the card contents
  root.close(); 
  
  for(int ndx=0; ndx<100; ndx++){               // Log 화일명 설정 Log0 - Log99
    logName = "Log" + String(ndx);
    logFileName = logName + ".txt";             // SD card 화일 조사
    if (!SD.exists(logFileName)) break;         // 지난번에 기록한 로그
    Serial.print(logFileName);
    Serial.println(" is exsist on SD");
  }

  dataFile = SD.open(logFileName, FILE_WRITE);  // 이번에 기록할 화일명 
  if(dataFile){
    Serial.print(logFileName); 
    Serial.println(" is open to save log");   
  } 
  dataFile.close();
}

ADC 포트 읽고 이벤트 처리부분

SD카드 버퍼를 사용하면 데이터 저장을 2-3ms 마다 한번씩 할 수 있음

데이터의 저장 용량이 너무 커져 10ms 마다 한번씩 저장하도록 함

void loop() {
  
  if(secOld != sec){
    //detectBPM();
    display.setColor(BLACK);
    display.fillRect(0, 0, 127, 10);
    display.display();
    display.setColor(WHITE);
    display.drawString(0, 0, logName);                // 현재 로그화일 이름 표시
    display.drawString(35, 0, " BPM ");
    display.drawString(60, 0, BPM + " " + dispTime);
    display.display();                                // write the buffer to the display        
    //Serial.print(curTime);
    //Serial.print(" | ");
    //Serial.print(yaxis);
    //Serial.print(" | ");    
    //Serial.println(adcValue);
    secOld = sec;
  }
  if(msOld != ms){      // ----------------- do every 10 ms ---------------------
    msOld = ms;
    dispTime = String(h) + ":" + String(m) + ":" + String(sec);
    curTime = dispTime + ":" + String(ms*10);  
  
    if ((digitalRead(LOM) == 0) && (digitalRead(LOM) == 0)) {
      adcValue = analogRead(A0);                          // ADC=10 bits,  1V 
      adcLowN = alpha*adcValue + (1-alpha)*adcLowN1;      // Low pass filter
      adcLowN1 = adcLowN;
    }
    y = 60 - ((adcLowN - 300) / 10);  // OLED : 128x64, OLED 그래프는 900-300 범위로 설정
    display.drawLine(lastx, lasty, x, y);                 // ECG 그래프
    display.drawLine(lastx, yaxisOld/5, x, yaxis/5);      // 참고 타임라인
    display.display(); 
  
    // 먼저 버퍼에 기록 (512Byte 블럭) 시간정보 참고 타임라인 ECG 신호
    char cTime[14] = {0};
    char tempF[5] = {0};
    curTime.toCharArray(cTime,13);
    dtostrf(adcLowN, 4,0,tempF);
    snprintf(strBuff,sizeof(strBuff),"%12s,%03d,%4s\n", cTime, yaxis, tempF);
    if(buffIndex < 22)  sdCardBuffA = sdCardBuffA + strBuff;
    else                sdCardBuffB = sdCardBuffB + strBuff;
    buffIndex++;
    if(buffIndex == 22){                                  // SD에 기록
      dataFile = SD.open(logFileName, FILE_WRITE);        // 파일존재하면 - APEEND모드
      if(dataFile){
        dataFile.println(sdCardBuffA);    dataFile.close();
      }
      sdCardBuffA = "";
    }
    if(buffIndex == 44){                                  // SD에 기록
      dataFile = SD.open(logFileName, FILE_WRITE);        // 파일존재하면 - APEEND모드
      if(dataFile){
        dataFile.println(sdCardBuffB);    dataFile.close();
      }
      sdCardBuffB = "";
      buffIndex = 0;
    }  
    Serial.println(adcLowN);
    lasty = y;
    lastx = x; 
    yaxisOld = yaxis;
    x++;
    if(x >= 128){                       // x > 128, then RESET & clear screen
      x = 0;
      lastx = 0;                        // x가 128이면 0초기화 (라인이 끝까지 생성됨방지)
      display.setColor(BLACK);
      display.fillRect(0,10, 127, 63);
      display.display();
      display.setColor(WHITE);
    }
  }     // ----------------- do every 10 ms ---------------------
}

기타 .. 처리

void detectBPM() {        // calc bpm
  
  if (adcValue > UpperThreshold) {
    if (BeatComplete) {
      BPM = ThisTime - LastTime;
      BPM = int(60 / (float(BPM) / 1000));
      BPMTiming = false;
      BeatComplete = false;
    }
    if (BPMTiming == false) {
      LastTime = millis();
      BPMTiming = true;
    }
  }
  if ((adcValue < LowerThreshold) & (BPMTiming))
    BeatComplete = true;
}
// -------------------------------------------------------------------------------------
// 초기에 한번 사용 : SD 카드내의 화일 및 폴더 목록을 보여줌
// -------------------------------------------------------------------------------------
void printDirectory(File dir, int numTabs) {
  int colcnt = 0;
  while(true) {
    File entry =  dir.openNextFile();
    if (! entry) {
      break;  // no more files
     }
     if (numTabs > 0) {
       for (uint8_t i=0; i<=numTabs; i++) {
         Serial.print('\t');
       }
     }
     Serial.print(entry.name());
     if (entry.isDirectory()) {
       Serial.println("/");
       printDirectory(entry, numTabs+1);
     } else {
       // files have sizes, directories do not
       Serial.print("\t");
       Serial.println(entry.size(), DEC);
     }
     entry.close();
  }
}

로그데이터 샘플 (10ms 저장인데 실제론 20ms 정도임)

Log50.txt
0.09MB

ESG 로그 데이터 검토 프로그램

WpfECGGraph_200225A.zip
5.06MB

'IoT_ESP8266' 카테고리의 다른 글

ESP8266 Modbus - qModMaster  (0) 2022.07.02
ESP-12 D1 mini OLED SD card  (0) 2020.01.22
ESp-01 : 두개의 다이얼 제어  (0) 2019.01.16
ESP-01 : 서버와 데이터 주고 받기 정리  (0) 2019.01.14
jQuery 사용하기  (0) 2019.01.13
블로그 이미지

DIYworld

,