2013-10-01

Olimex EKG board demo program

Olimex EKG board forum Sample raw ECG data « on: December 24, 2012, 09:12:58 AM »

https://www.olimex.com/forum/index.php?topic=572.0

Stan09:

Just FYI, I posted some of my results with ECG shield  on OLIMEX forum:

https://www.olimex.com/forum/index.php?topic=572.0

As per displaying ECG on LCD, I'd rather go for smthng like Processing application ECG viewer; tiny LCD screen would not reflect any additional information on ECG shape. And also, keep in mind that the cable they provide is of very low quality, so you should not expect good result from it. ECG picture they give in .pdf <somethingGURU> application - I don't know how they actually produced such an image.

...

haha54321:

I am trying to graph ekg on glcd using the mega 2560 and the olimex ekg emg shield (https://www.olimex.com/Products/Duino/Shields/SHIELD-EKG-EMG/). 

The company provides a base code that is supposed to interface with a windows program (ElecGuru), but I am having a hard time understanding it. How would I go about graphing the data on an lcd?

Provided code by Olimex:

/**********************************************************/
#include <compat/deprecated.h>
#include <FlexiTimer2.h>
//http://www.arduino.cc/playground/Main/FlexiTimer2
#include <TimerOne.h>
//http://arduino.cc/playground/Code/Timer1

// All definitions

#define NUMCHANNELS 6
#define HEADERLEN 4
#define PACKETLEN (NUMCHANNELS * 2 + HEADERLEN + 1)
#define SAMPFREQ 256                   // ADC sampling rate 256
#define TIMER2VAL (1024/(SAMPFREQ))    // Set 256Hz sampling frequency
#define PWM_OUT 9                      // Number of pin used for generating CAL_SIG
#define PWMFREQ 10                     
#define LED1  13

// Global constants and variables

char const channel_order[]= { 0, 1, 2, 3, 4, 5 };

volatile unsigned char TXBuf[PACKETLEN];  //The transmission packet
volatile unsigned char TXIndex;           //Next byte to write in the transmission packet.
volatile unsigned char CurrentCh;         //Current channel being sampled.

//~~~~~~~~~~
// Functions
//~~~~~~~~~~

/****************************************************/
/*  Function name: Toggle_LED1                      */
/*  Parameters                                      */
/*    Input   :  No                            */
/*    Output  :  No                                 */
/*    Action: Switches-over LED1.                   */
/****************************************************/
void Toggle_LED1(void){

 if((digitalRead(LED1))==HIGH){ 
   digitalWrite(LED1,LOW); 
  }
  else{ 
   digitalWrite(LED1,HIGH); 
  }
}

/****************************************************/
/*  Function name: setup                            */
/*  Parameters                                      */
/*    Input   :  No                            */
/*    Output  :  No                                 */
/*    Action: Initializes all peripherals           */
/****************************************************/
void setup() {

 noInterrupts();  // Disable all interrupts before initialization

 // LED1
 pinMode(LED1, OUTPUT);  //Setup LED1 direction
 digitalWrite(LED1,LOW); //Setup LED1 state

 //Write packet header and footer
 TXBuf[0] = 0xa5;  //Sync 0
 TXBuf[1] = 0x5a;  //Sync 1
 TXBuf[2] = 2;     //Protocol version
 TXBuf[3] = 0;     //Packet counter

 // ADC
 // Timings for sampling of one 10-bit AD-value:
 // XTAL = 16000000MHz
 // prescaler > ((XTAL / 200kHz) = 80 => 
 // prescaler = 128 (ADPS2 = 1, ADPS1 = 1, ADPS0 = 1)
 // ADCYCLE = XTAL / prescaler = 125000Hz or 8 us/cycle
 // 14 (single conversion) cycles = 112 us
 // 26 (1st conversion) cycles = 208 us
 outb(ADMUX, 0);         //Select channel 0
 outb(ADCSRA, ((1<<ADPS2) | (1<<ADPS1)| (1<<ADPS0))); //Prescaler = 128, free running mode = off, interrupts off.
 sbi(ADCSRA, ADIF);  //Reset any pending ADC interrupts
 sbi(ADCSRA, ADEN);  //Enable the ADC

 // Serial Port
 outb(UBRR0, 16);              //Set speed to 57600 bps
 outb(UCSR0B, (1<<TXEN0));     //Enable USART Transmitter.

 // Timer1 
 // It's used for calibration signal generation: CAL_SIG via PWM.
 // CAL_SIG is used like reference signal when setting-up SHIELD-EKG/EMG's channel gain
 // During normal operation this signal is not required so it can be disabled with uncommenting te row below!
 pinMode(PWM_OUT, OUTPUT);    //Set PWM_OUT direction
 digitalWrite(PWM_OUT,LOW);   //Set PWM_OUT state
 Timer1.initialize((1000000/(PWMFREQ))); // initialize timer1, and set a 1/10 second period = 10Hz

 Timer1.pwm(PWM_OUT, 512);             // setup pwm on pin 9, 50% duty cycle
 //Timer1.disablePwm(PWM_OUT); // Uncomment if CAL_SIG is not requiered

 // Timer2
 // Timer2 is used for setting ADC sampling frequency.
 FlexiTimer2::set(TIMER2VAL, Timer2_Overflow_ISR);
 FlexiTimer2::start();

 // MCU sleep mode = idle.
 outb(MCUCR,(inp(MCUCR) | (1<<SE)) & (~(1<<SM0) | ~(1<<SM1) | ~(1<<SM2)));

 interrupts();  // Enable all interrupts after initialization has been completed
}

/****************************************************/
/*  Function name: Timer2_Overflow_ISR              */
/*  Parameters                                      */
/*    Input   :  No                            */
/*    Output  :  No                                 */
/*    Action: Determines ADC sampling frequency.    */
/****************************************************/
void Timer2_Overflow_ISR()
{
  // Toggle LED1 with ADC sampling frequency /2
  Toggle_LED1();
  
  CurrentCh = 0;
  // Write header and footer:
  // Increase packet counter (fourth byte in header)
  TXBuf[3]++;
  //Get state of switches on PD2..5, if any (last byte in packet).
  TXBuf[2 * NUMCHANNELS + HEADERLEN] = (inp(PIND) >> 2) &0x0F;
  
  cbi(UCSR0B, UDRIE0); //Ensure Data Register Empty Interrupt is disabled.
  sbi(ADCSRA, ADIF);   //Reset any pending ADC interrupts
  sbi(ADCSRA, ADIE);   //Enable ADC interrupts.
  sbi(ADCSRA, ADSC) ;  // Start conversion!!!
  //Next interrupt will be ISR(ADC_vect)
}

/****************************************************/
/*  Function name: ISR(ADC_vect)                    */
/*  Parameters                                      */
/*    Input   :  No                            */
/*    Output  :  No                                 */
/*    Action: Reads ADC's current selected channel  */
/*            and stores its value into TXBuf. When */
/*            TXBuf is full, it starts sending.     */
/****************************************************/
ISR(ADC_vect)
{
 volatile unsigned char i;

 i = 2 * CurrentCh + HEADERLEN;
 TXBuf[i+1] = inp(ADCL);
 TXBuf[i] = inp(ADCH);
 CurrentCh++;
 if (CurrentCh < NUMCHANNELS)
 {
  outb(ADMUX, (channel_order[CurrentCh])); //Select the next channel.
  sbi(ADCSRA, ADSC) ;            //Start conversion!!!
 }
 else
 {
  outb(ADMUX, channel_order[0]);      //Prepare next conversion, on channel 0.
  cbi(ADCSRA, ADIE);    //Disable ADC interrupts to prevent further calls to ISR(ADC_vect).
  outb(UDR0, TXBuf[0]); //Send first Packet's byte: Sync 0
  sbi(UCSR0B, UDRIE0);  //USART Data Register Empty Interrupt Enable
  TXIndex = 1;          //Next interrupt will be ISR(USART_UDRE_vect)
 }
}

/****************************************************/
/*  Function name: ISR(USART_UDRE_vect)             */
/*  Parameters                                      */
/*    Input   :  No                            */
/*    Output  :  No                                 */
/*    Action: Sends remaining part of the Packet.   */
/****************************************************/
ISR(USART_UDRE_vect){

 outb(UDR0, TXBuf[TXIndex]);  //Send next byte
 TXIndex++;
 if (TXIndex == PACKETLEN)    //See if we're done with this packet
 {
   cbi(UCSR0B, UDRIE0);       //USART Data Register Empty Interrupt Disable
                              //Next interrupt will be Timer2_Overflow_ISR()
 }
}

/****************************************************/
/*  Function name: loop                             */
/*  Parameters                                      */
/*    Input   :  No                            */
/*    Output  :  No                                 */
/*    Action: Puts MCU into sleep mode.             */
/****************************************************/
void loop() {
  
 __asm__ __volatile__ ("sleep");

}


> I am trying to understand this code that is supposed to interface with ElecGuru, a windows > program.  Does it? Is that program open source?

The two ISRs are pumping data to the serial port. The PC application listens to the serial port, apparently, and does something with the data. The source for that program would provide a clue, as would simply opening the Serial Monitor, instead.

Stan09:

Just FYI, I posted some of my results with ECG shield  on OLIMEX forum:

https://www.olimex.com/forum/index.php?topic=572.0

As per displaying ECG on LCD, I'd rather go for smthng like Processing application ECG viewer; tiny LCD screen would not reflect any additional information on ECG shape. And also, keep in mind that the cable they provide is of very low quality, so you should not expect good result from it. ECG picture they give in .pdf <somethingGURU> application - I don't know how they actually produced such an image.

... 


Common-mode rejection ratio (CMRR) - Wikipedia

http://en.wikipedia.org/wiki/Common-mode_rejection_ratio

The common-mode rejection ratio (CMRR) of a differential amplifier (or other device) is the rejection by the device of unwanted input signals common to both input leads, relative to the wanted difference signal. An ideal differential amplifier would have infinite CMRR; this is not achievable in practice. A high CMRR is required when a differential signal must be amplified in the presence of a possibly large common-mode input. An example is audio transmission over balanced lines.

Olimex Oximeter 

https://www.olimex.com/Products/Modules/Biofeedback/MOD-PULSE/

.END

No comments:

Post a Comment