Pages

Monday, June 30, 2014

Automated ECG Device Input using an Arduino Micro

Medical devices are intended for the diagnosis, prevention, and treatment of diseases and other medical conditions. Therefore, these devices are limited in purpose, scope, and functionality. These limitations, in my experience, translate to numerous pitfalls for consumers of medical devices. For example, a particular ECG machine may output continuous ECG measurements in digital and print format, while another ECG machine outputs in print format only.

I find this irritating. While it is true that major features of the device are consistent (e.g., ECG machines measure ECG signals), general feature sets that expand device usability and functionality are limited. Device manufacturers do offer options to skirt around limitations, but premium functionality comes at a premium price.

I talk about my experience with a GE MAC 1600, or resting ECG device, in this post. I note that this device does exactly what it is suppose to do, and I do not intend on unfairly representing the device. Simply, I have a feature requirement that is not met by the device, and therefore I am forced to build in my own feature with auxiliary hardware.

My background story in short: the GE MAC 1600 can only store 100 ECG signals, each of length 10 seconds. I want to collect for periods longer than 10 seconds. This is impossible using functions provided by the device alone. Therefore, I must address this problem using an alternative method that is not supported by the device.

A crawl through the GE MAC's documentation was my first step toward solving the problem. I wanted to know if there was an option or configuration that might enable the storage of ECG signals longer than 10 seconds. I quickly found that there were no such options. I then began a different search through the documentation: does the device support external inputs such as USB keyboard? I found that it did!

I attached a USB keyboard and then began iterating through button presses to learn the device button mappings. Figure 1 shows that the keyboard mapping to the ECG button is not as intuitive as, "F5 for ECG." I found that upon a few iterations that the F10 key mapped to the ECG button. Great.

Figure 1: GE MAC 1600 Keyboard. The red button is the ECG Button.

Two approaches became clear to me. The first approach was for me to press the ECG button every 10 seconds to capture a continuous window of time greater than 10 seconds. This is not optimal as it introduces human error (i.e., missed button presses and time inaccuracies). The second approach mitigates this error by relying on hardware; specifically, an Arduino Micro.

Arduino is a prototype platform that has gained popularity because of its open-source approach to development, accessibility, and simple programming language based on Wiring. Arduino micro is a microcontroller based on the ATmega32u4, which uses built-in USB communication for serial comm and USB HID device emulation --- keyboards!

I prototyped my ECG button presser via an Arduino Micro, breadboard, two LEDs, jumper wires, and two 3-way rocker switches (i.e., components scrounged from neighboring labs). I developed a breadboard layout for the replication of my design using Fritzing. See Figure 2 for the design and Figure 3 for the design implementation. Side note: I'm still new to electronics and just noticed that I did not put a resistor between the digital pin and the rocker switch. The circuit still functions as intended.

Figure 2: Breadboard Design of ECG Button Presser.

Figure 3: Design Implementation.

I then wrote a sketch, or Arduino program, that uses serial output over the USB connected device class (CDC) driver to verify the correctness of my switch/LED logic. I left these statements in the sketch and you can find them in the code below. The sketch uses the digitalRead() and digitalWrite() functions to read digital pin values for the switches, either HIGH or LOW, or set digital pin values for the LEDs, also HIGH or LOW. And, the sketch uses the mouse and keyboard libraries to send keystrokes to an attached computer.
/* GE MAC 1600 Keyboard Control
 * 
 * Controls the GE MAC 1600 keyboard via USB. 
 *
 * Ref: http://arduino.cc/en/Reference/MouseKeyboard
 */

// Define our setup constants. Adjust for your board.
const int ecgRest_switch =          8; // Resting Switch.
const int ecgRest_LEDpin =          4; // Yellow.
const int ecgWalk_switch =         12; // Walking Switch.
const int ecgWalk_LEDpin =          7; // Red.
const unsigned long t_sample =  10000; // Sample period.
const unsigned long tf_rest  = 900000; // 15 min in ms.
const unsigned long tf_walk1 = 600000; // 10 min.
const unsigned long tf_walk2 = 300000; // 5 min.
boolean start               =    true;
boolean resting             =   false;
boolean walking             =   false;
boolean walking_sit         =   false;
boolean attention           =   false;

void setup() {
  // Online forums recommended adding a delay for USB enumeration.
  delay(500);

  // Virtual serial driver reserved for USB CDC communication.
  // USB speed 12 Mbits/sec.
  //Serial.begin(9600);

  // Init keyboard emulation to connected ECG machine.
  Keyboard.begin();

  // Set our switches as input.
  pinMode(ecgRest_switch, INPUT);
  pinMode(ecgWalk_switch, INPUT);

  // Set our LEDs as output.
  pinMode(ecgRest_LEDpin, OUTPUT);
  pinMode(ecgWalk_LEDpin, OUTPUT);
}

void setLEDs(boolean ecgrest, int ecgrest_val, boolean ecgwalk, int ecgwalk_val) {
  if(ecgrest) {
    if(ecgrest_val < 1) {
      digitalWrite(ecgRest_LEDpin, LOW);
    } 
    else {
      digitalWrite(ecgRest_LEDpin, HIGH);
    }
  }
  if(ecgwalk) {
    if(ecgwalk_val < 1) {
      digitalWrite(ecgWalk_LEDpin, LOW);
    } 
    else {
      digitalWrite(ecgWalk_LEDpin, HIGH);
    }
  }
}

void loop() {  
  // On start check that both switches are LOW.
  if(start == true) {
    delay(1000);
    //Serial.println("Starting...");        
    setLEDs(true, 1, true, 1);
  }

  while(start) {
    // Only when both switchers are LOW can we start.
    if(digitalRead(ecgRest_switch) == LOW &&
      digitalRead(ecgWalk_switch) == LOW) {
      start = false;
      setLEDs(true, 0, true, 0);
    }
  }

  // When the user puts the rest switch at high we
  // will loop until global constant tf is met.  
  if(digitalRead(ecgRest_switch) == HIGH) {
    resting = true;
    unsigned long time_initial = millis();
    setLEDs(true, 1, false, 0);
    //Serial.println("Starting ECG Resting test...");

    // Loop until the maximum time with periodic
    // keyboard input --- F10 for ECG button.
    while(resting) {
      // Emulate keyboard press and release for ECG start.
      Keyboard.write(KEY_F10);

      //Serial.println(millis()-time_initial);       
      delay(t_sample);     
      // Additionally, when the SD Card fills it will start
      // to spit out error messages. Lets ignore those with
      // ESCAPE key press.
      Keyboard.write(KEY_ESC);

      // If current time is greater than or equal 
      // to our maximum time, set false and break.
      if((millis()-time_initial) >= tf_rest) {
        resting = false;
      }
    }

    // Set both LEDs to HIGH until the user sets them
    // both LOW again. We do this by setting start=true.
    start = true;
  }

  // When the user puts the walk switch at high we
  // will loop until global constant tf_walk is met.
  if(digitalRead(ecgWalk_switch) == HIGH) {
    walking = true;
    walking_sit = true;
    unsigned long time_initial = millis();
    setLEDs(false, 0, true, 1);
    //Serial.println("Starting ECG Walking test...");

    // Loop until the maximum time with periodic
    // keyboard input --- F10 for ECG button.
    while(walking) {
      // Emulate keyboard press and release.
      Keyboard.write(KEY_F10);      
 
      //Serial.println(millis()-time_initial);       
      delay(t_sample);
      Keyboard.write(KEY_ESC);     
      
      // Same as above with one exception. Once the
      // time tf_walk1 has been met, we will start 
      // a new loop for tf_walk2 and pause for the
      // to indicate start. 
      if((millis()-time_initial) >= tf_walk1) {
        setLEDs(true, 1, false, 1);
        boolean wait_user = true;
        walking = false;

        while(wait_user) {
          // Tricky with only two LEDs. We ask the user
          // to set the switch LOW to continue.
          if(digitalRead(ecgWalk_switch) == LOW) {
            wait_user = false;
          }
        }
      }
    }

    // After the user sets walk to LOW we will get the
    // current time and start capturing ECG data for
    // walking_sit.
    //Serial.println("Starting ECG sitting test...");
    setLEDs(false, 0, true, 1);

    // Reset this or it'll just run for one iteration. 
    // Day ruined! 
    time_initial = millis();
    while(walking_sit) {
      // Emulate keyboard press and release.
      Keyboard.write(KEY_F10);   
   
      //Serial.println(millis()-time_initial);       
      delay(t_sample);
      Keyboard.write(KEY_ESC);   

      if((millis()-time_initial) >= tf_walk2) {
        setLEDs(true, 0, true, 0);
        walking_sit = false;
      }
    }

    // Set both LEDs to HIGH until the user sets them
    // both LOW again. We do this by setting start=true.
    start = false;
  }
}
Once I finished coding up and testing my sketch, I thought I was ready. I uploaded my sketch to the Arduino micro, and then I connected it to various devices (e.g., Macbook, Linux VM, RPi). The circuit behaved just as expected. I then connected my Arduino Micro to the GE MAC 1600, and...

A constantly lit RX (receive) light. What can it possibly be receiving? On inspection of other device connections the RX light briefly flashed on boot and then went dark. I tried pushing a switch which  resulted in a constantly lit TX (transmit) light. Something was clearly wrong.

My first attempt to alleviate this seemingly random problem was to debug my code. I still had serial statements in the sketch and I thought this might be the problem. I commented those out and tried again. Same problem. I quickly became confused and frustrated. How is it that the only device this doesn't work for is the device I intended to use it on? How am I suppose to learn what serial input it was receiving if I didn't have a serial monitor or external storage? Was it a USB HID problem? If it were, I could spoof a Dell Keyboard configuration by mirroring the device descriptor...

Searching the internet and trail-and-error for a few days brought me to one conclusion: the GE MAC must recognize the Arduino Micro CDC driver first, flooding it with input (e.g., external barcode scanner). I discovered one solution to alleviate this problem: burn a new USB keyboard-only bootloader onto the Micro. I could burn this new bootloader with another Arduino, or with dedicated programmers (I did not have one in the lab), using the in-circuit serial programming (ICSP) header. I ended up not acting on this solution because I stumbled on a simpler solution first. I did, however, go as far as setting up an Arduino Mega 2560 as an ISP in Figure 4. I have provided a Fritzing breadboard diagram to help those wanting to do something similar.

Figure 4: Arduino Mega 2560 ISP for Arduino Micro


Figure 5: Breadboard Diagram

I relent that I tried many solutions before finding the path-of-least-resistance. For example, I tried the Arduino device firmware update (DFU) software. If you're interested, you can program Atmega8u2 chips directly using the DFU USB protocol, but it wasn't clear to me if this was the case for Arduino Micros and their Atmega32u4. If you have a Mac, you can give this a try by installing the appropraite binaries via MacPorts: sudo port install dfu-programmer dfu-util.

The solution that works is, thankfully, much simpler. I noticed, using lsusb and dmesg, that the Arduino Micro was describing itself as USB CDC and USB HID. I thought about how an Arduino sketch would support both CDC and HID. There must be a library. I pulled up the USB and HID code directly: /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/. I found the following:

USBDesc.h has one simple and powerful macro. Can you spot it?
/* Copyright (c) 2011, Peter Barrett  
**  
** Permission to use, copy, modify, and/or distribute this software for  
** any purpose with or without fee is hereby granted, provided that the  
** above copyright notice and this permission notice appear in all copies.  
** 
** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL  
** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED  
** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR  
** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES  
** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,  
** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,  
** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS  
** SOFTWARE.  
*/

#define CDC_ENABLED
#define HID_ENABLED


#ifdef CDC_ENABLED
#define CDC_INTERFACE_COUNT 2
#define CDC_ENPOINT_COUNT 3
#else
#define CDC_INTERFACE_COUNT 0
#define CDC_ENPOINT_COUNT 0
#endif

#ifdef HID_ENABLED
#define HID_INTERFACE_COUNT 1
#define HID_ENPOINT_COUNT 1
#else
#define HID_INTERFACE_COUNT 0
#define HID_ENPOINT_COUNT 0
#endif

All you need to do is set #define CDC_ENABLED to #define CDC_DISABLED (or any text for that matter). Finally. I was able to use my Arduino Micro to automate ECG button presses on the GE MAC 1600! There are some complications with this method. For example, the Arduino IDE uses CDC to upload sketches. You cannot upload sketches with CDC disabled.

I discovered a way what seems like a bricked arduino (I assume you that it is not). First, note that we not burn a new bootloader. When the stock Atmega32u4 bootloader boots up, CDC communication is available until it hands off to the sketch. This allows you to upload a new sketch by giving power to the micro (or resetting it via the onboard button), and immediately uploading a new sketch. You may have to attempt this more than once if you receive an error in the IDE. In fact, I have received errors and still had method this work. Also note that if you want CDC turned back, you have to enable it in the USBDesc header file: #define CDC_ENABLED.

All of this work for one feature. Perhaps this effort speaks volumes about the current state of medical devices or perhaps it only says that, "you get what you pay for"? Either way, as medical devices become more integrated one can only hope that feature sets include more functionality for future applications. Of course we would hope for more security as well... that's a topic for another day.

2 comments:

  1. Thanks for sharing this information about Bluetooth Earpiece can u tell the some features of Micro Cheating Earpiece Device

    ReplyDelete
  2. Thanks for this blog, if you want to buy Alivcor's ecg device in Indiathen you can visit Alivecor India. This is the official website of AliveCor. You can buy 6 lead ecg, ecg device for home.

    ReplyDelete