Go Down

Topic: olimex ekg (Read 3086 times) previous topic - next topic

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:
Code: [Select]
/**********************************************************/
#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");

}


haha54321

I am trying to understand this code that is supposed to interface with ElecGuru, a windows program, to display ekg /emg signal coming from the olimex shield (https://www.olimex.com/Products/Duino/Shields/SHIELD-EKG-EMG/). I am trying to modify it so that it can display on a lcd. What part of the code is the actual output signal? I am a newb and find this code extremely confusing.

Code: [Select]

// 17-byte packets are transmitted from Olimexino328 at 256Hz,
// using 1 start bit, 8 data bits, 1 stop bit, no parity, 57600 bits per second.

// Minimial transmission speed is 256Hz * sizeof(Olimexino328_packet) * 10 = 43520 bps.

struct Olimexino328_packet
{
 uint8_t sync0; // = 0xa5
 uint8_t sync1; // = 0x5a
 uint8_t version; // = 2 (packet version)
 uint8_t count; // packet counter. Increases by 1 each packet.
 uint16_t data[6]; // 10-bit sample (= 0 - 1023) in big endian (Motorola) format.
 uint8_t switches; // State of PD5 to PD2, in bits 3 to 0.
};
*/
/**********************************************************/
#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");

}

Nick Gammon

Please do not cross-post. This wastes time and resources as people attempt to answer your question on multiple threads.

Threads merged.

- Moderator
Please post technical questions on the forum, not by personal message. Thanks!

More info:
http://www.gammon.com.au/electronics

PaulS

Quote
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

#4
Dec 24, 2012, 10:14 pm Last Edit: Dec 24, 2012, 10:18 pm by Stan09 Reason: 1
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.

Go Up