CNI widgets: Difference between revisions

From CNI Wiki
Jump to navigation Jump to search
imported>Bobd
No edit summary
imported>Bobd
mNo edit summary
Line 1: Line 1:
The firmware code for the devices is stored in our [[Git|git repository]]. This repository is currently private for internal CNI use. But if you are interested in seeing the source code, just ask.
The firmware code for the devices is stored in our [[Git|git repository]]. This repository is currently restricted for internal CNI use only. But if you are interested in seeing the source code, just ask.


= Serial port scan trigger device =
= Serial port scan trigger device =

Revision as of 22:03, 6 June 2011

The firmware code for the devices is stored in our git repository. This repository is currently restricted for internal CNI use only. But if you are interested in seeing the source code, just ask.

Serial port scan trigger device

CNI trigger box that contains the serial port trigger device described here.
CNI serial port trigger device circuit diagram.

We are using a [Teensy board] programmed using a simple [teensyduino] sketch. The device generates TTl pulses when told to do so through a serial port command ("[t]"). This device will also listen for input pulses and send out a serial port character ("p") whenever one is detected. It uses an external interrupt, so it can reliably detect very brief pulses. We use this to detect the GE 3.3v scope trigger pulses (~4 usec pulse duration).

/*
* CNI_trigger sketch for Arduino.
* 
* 
* A simple sketch to generate TTl pulses when told to do so through a serial
* port command.  
*
* When running this sketch on a Teensy, be sure to set the "USB Type" to
* "Serial" (under the Arduino Tools menu). 
* 
* This sketch will also listen for input pulses and send out a serial port 
* character whenever one is detected. It uses an external interrupt, so it
* can reliably detect very brief pulses. We use this to detect the GE 3.3v 
* scope trigger pulses (~4 usec pulse duration). 
* 
*/

#include <Flash.h>
#include <Messenger.h>

#define BAUD 57600

#define DEFAULT_OUT_PULSE_MSEC 5
#define DEFAULT_IN_PULSE_STATE 0

// The approximate duration of the output pulse, in milliseconds.
// Note that we don't account for delays in the code, so it will 
// always be a bit longer than this.
unsigned int g_outPulseDuration = DEFAULT_OUT_PULSE_MSEC;

// The input pin should be the pin mapped to INT0, unless you change
// the interrupt number below. The output pin can be any digital pin.
// Teensy2.0 has LED on pin 11 and INT0 on pin 5 (D0)
#define INTERRUPT 0
const byte g_outPin = 11;
const byte g_inPin = 5;

byte g_inPulseEdge = FALLING;

volatile byte g_outPinOn = false;
volatile unsigned long g_outPulseStart;

// Instantiate Messenger object used for serial port communication.
Messenger g_message = Messenger(',','[',']');

// Create the Message callback function. This function is called whenever a complete 
// message is received on the serial port.
void messageReady() {
 int val[10];
 byte i = 0;
 if(g_message.available()) {
   // get the command byte
   char command = g_message.readChar();
   switch(command) {
   
   case '?': // display help text
     Serial << F("CNI Trigger Device\n");
     Serial << F("Sends TTL pulses out on pin ") << (int)g_outPin << F(".\n");
     Serial << F("Listens for pulses in on pin ") << (int)g_inPin << F(".\n");
     Serial << F("\nCommands:\n");
     Serial << F("[t]   will send a trigger pulse. This also disables the input pulse\n");
     Serial << F("      detection. Send a [p] command to re-enable it.\n\n");
     Serial << F("[o,N] set the output pulse duration to N milliseconds. Send with\n");
     Serial << F("      no argument to show the current pulse duration.\n\n");
     Serial << F("[p]   enable input pulse detection. Send a [t] to disable.\n\n");
     Serial << F("[r]   reset default state.\n\n");
     break;
     
   case 'o': // Set out-pulse duration (msec)
     while(g_message.available()) val[i++] = g_message.readInt();
     if(i>1){
       Serial << F("ERROR: Set output pulse duration requires no more than one param.\n");
     }else if(i==1){
         g_outPulseDuration = val[0];
     }
     Serial << F("Output pulse duration is set to ") << g_outPulseDuration << F(" msec.\n");
     break;
 
   case 't': // force output trigger
     // First force the pin low, in case it was already on. This will ensure that
     // we get a change on the pin no matter what state we were in.
     if(g_outPinOn) digitalWrite(g_outPin, LOW);
     triggerOut();
     // Automatically disable input pulse detection
     setInPulseState(0);
     break;

   case 'p': // turn on input pulse detection
     //Serial << F("Enabling input pulses\n");
     setInPulseState(1);
     break;

   case 'r': // reset
     setInPulseState(DEFAULT_IN_PULSE_STATE);
     g_outPulseDuration = DEFAULT_OUT_PULSE_MSEC;
     digitalWrite(g_outPin, LOW);
     break;

   default:
     Serial << F("[") << command << F("]\n");
     Serial << F("ERROR: Unknown command.\n\n");
   } // end switch
 } // end while
}

void setup(){
 Serial.begin(BAUD);
 Serial << F("*********************************************************\n");
 Serial << F("* CNI Trigger firmware version ") << VERSION << F("\n");
 Serial << F("* Copyright 2011 Bob Dougherty <bobd@stanford.edu>\n");
 Serial << F("* http://cniweb.stanford.edu/wiki/CNI_widgets\n");
 Serial << F("*********************************************************\n\n");

 pinMode(g_inPin, INPUT);
 pinMode(g_outPin, OUTPUT);
 digitalWrite(g_outPin, LOW);
 setInPulseState(DEFAULT_IN_PULSE_STATE);

 // Attach the callback function to the Messenger
 g_message.attach(messageReady);

 Serial << F("CNI Trigger Ready. Send the ? command ([?]) for help.\n\n");
}

void loop(){
 // Reset the output, if needed:
 if(g_outPinOn){
   // Turn off the output pin after the requested duration.
   // Detect and correct counter wrap-around:
   if(millis()<g_outPulseStart) g_outPulseStart += 4294967295UL;
   if(millis()-g_outPulseStart > g_outPulseDuration)
     digitalWrite(g_outPin, LOW);
 }
 // Handle Messenger's callback:
 if(Serial.available())  g_message.process(Serial.read());
}

void setInPulseState(byte state){
 // Turn on the internal pull-up resistor if we want to detect falling edges.
 if(g_inPulseEdge==FALLING)
   digitalWrite(g_inPin, HIGH);
 else
   digitalWrite(g_inPin, LOW);
 // Attach or detach the interrupt
 if(state)
   attachInterrupt(INTERRUPT, triggerIn, g_inPulseEdge);
 else
   detachInterrupt(INTERRUPT);
}

// The following is an interrupt routine that is run each time the 
// a pulse is detected on the trigger input pin.
void triggerIn(){
 Serial << F("p");
}

void triggerOut(){
   digitalWrite(g_outPin, HIGH);
   g_outPinOn = true;
   g_outPulseStart = millis();
}

GE physiological data monitor

CNI PPG pulse detector device.

We also built a device to read serial data from the GE MR750 physiological monitoring port in the PGR cabinet. We need to detect the pulses in real-time for EEG balistocardiogram artifact removal. Unfortunately, the pulses are not available in the GE system, but they do provide the raw PPG data (along with respiration belt and ECG data).

The data come in 12-byte (96-bit) packets delivered at 115200 bps. A packet comes every 5ms, with silence between packets. This produces data bursts of 96bits / 115.2bits/ms = .8333ms with 5 - .8333 = 4.1667ms of silence. (This timing pattern was confirmed with a scope.) So we can detect the start of a data packet by waiting for the silent period before the data arrives. Here we only care about the PPG value. But the code could be easily modified to do something with the other physiological readings.

To detect a pulse, we compute a running mean and standard deviation and compute a z-score for each new data point. When several consecutive data point z-scores are above threshold, we signal that a pulse was detected. With a Teensy 2++ (which has 8k SRAM), we can maintain a buffer size of 1024, which is 1024 * 0.005 = 5.12 seconds. This seems to be enough to give a very stable pulse detection.

Circuit tidbits

  • Outputs are protected from reverse voltages with a diode
  • We used a little monochrome [OLED display] to show what is happening.
  • We need a little circuit to take the scanner data stream (-12v to +12v) and allow it to be safely ready by our 5v microprocessor. We just took the Tx part of this simple RS232 level converter. We used a 2N3904 transistor with emitter to ground, collector to the UART Rx pin on the microcontroller with a 10k pull-up resistor to the 5v line, and the base connected to the Tx line coming from the scanner via a 4.7K resistor, with a 4.7k pull-down to ground.