2013-09-29

Olimex Open Source Hardware Arduino compatible EKG shield learning notes

SHIELD-EKG-EMG - OPEN SOURCE HARDWARE ELECTROCARDIOGRAPHY ELECTROMYOGRAPHY SHIELD FOR DUINOMITE, PINGUINO, MAPLE, ARDUINO LIKE DEVELOPMENT BOARDS

https://www.olimex.com/Products/Duino/Shields/SHIELD-EKG-EMG/

This is an EKG/EMG shield which allows Arduino like boards to capture Electrocardiography Electromiography signals. The shield opens new possibilities to experiment with bio feedback. You can monitor your heartbeat and log your pulse, recognize gestures by monitoring and analize the muscule activity as it is done in this project.

FEATURES

Stackable headers up to 6 channels may be stacked and wired to A0-A6 analogue inputs

Calibration signal generation by D4/D9 digital output

Precise Trimmer potentiometer for calibration

Input connector for normal or Active electrodes

Works with both 3.3V and 5V Arduino boards

DOCUMENTS

SHIELD-EKG-EMG user's manual revision E

HARDWARE

SHIELD-EKG-EMG schematic in PDF format released under Creative Commons Attribution-Share Alike 3.0 United States License

SHIELD-EKG-EMG schematic and board in Eagle format released under Creative Commons Attribution-Share Alike 3.0 United States License

SOFTWARE

//********************************************************************/
//* Demo program for:                                                */
//*    Board: SHIELD-EKG/EMG + PIC32-PINGUINO-OTG/MX220              */
//*  Manufacture: OLIMEX                                      */
//*  COPYRIGHT (C) 2012                                      */
//*  Designed by:  Penko Todorov Bozhkov                      */
//*   Module Name:  Example                                           */
//*   File   Name:  ShieldEkgEmgDemoWithPIC32-Pinguino-OTG_MX220.pde  */
//*   Revision:  Rev.A                                        */
//*   History:  Added support for PIC32-PINGUINO-MX220               */
//*   Date: 09.05.2012                                                */
//*   Built with PinguinoX.3 rev399                     */
//*********************************************************************/
/*************************************************************************************
Purpose of this programme is to give you an easy way to 
connect PIC32-PINGUINO-OTG to ElectricGuru(TM), see:
http://www.realization.org/page/topics/electric_guru.htm
where you'll be able to observe yours own EKG or EMG signal.
It is based on:
**************************************************************************************
* ModularEEG firmware for one-way transmission, v0.5.4-p2
* Copyright (c) 2002-2003, Joerg Hansmann, Jim Peters, Andreas Robinson
* License: GNU General Public License (GPL) v2
**************************************************************************************
For proper communication packet format given below have to be supported:
/////////////////////////////////////////////////////
////////// Packet Format Version 2 ////////////
/////////////////////////////////////////////////////
// 17-byte packets are transmitted from PIC32-PINGUINO-OTG at 256Hz,
// using 1 start bit, 8 data bits, 1 stop bit, no parity, 57600 bits per second.

// Minimial transmission speed is 256Hz * sizeof(PIC32-PINGUINO-OTG_packet) * 10 = 43520 bps.

struct PIC32-PINGUINO-OTG_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. -> This is not used in current application. The value is set to 0x01
};
*/
/***************************************************************************************/
// In the source below was used the example given by:
// Jean-Pierre MANDON 2011
// Using interrupt with Pinguino32
// http://blog.pinguino.cc/

#include <__cdc.c>
#include <interrupt.c>

extern void putUSBUSART(char *data, char Length);

// All definitions
#if defined(PIC32_PINGUINO_220)

 #define SDCD 0
 #define SDWP 1

#elif defined(PIC32_PINGUINO_OTG) 

 #define LED2_ON TRISD &= (~0x0002); LATD |= 0x0002;
 #define LED2_OFF TRISD &= (~0x0002); LATD &= (~0x0002);
 #define LED2_TOGG TRISD &= (~0x0002); LATD ^= 0x0002;

#else

 #error: "The selected board is not supported!!!" 

#endif

#define LED1 13
#define CAL_SIG 9
#define NUMCHANNELS 6
#define HEADERLEN 4
#define PACKETLEN (NUMCHANNELS * 2 + HEADERLEN + 1)


// Global constants and variables
volatile unsigned char TXBuf[PACKETLEN];  //The transmission packet
volatile unsigned char CurrentCh;         //Current channel being sampled.
volatile unsigned char counter = 0; //Additional divider used to generate CAL_SIG
volatile unsigned int ADC_Value = 0; //ADC current value


/****************************************************/
/*  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: toggle_GAL_SIG     */
/*  Parameters                        */
/*    Input   :  No                   */
/*    Output  :  No                   */
/*    Action: Switches-over GAL_SIG.  */
/****************************************************/
void toggle_GAL_SIG(void){
 if(digitalRead(CAL_SIG) == HIGH){ digitalWrite(CAL_SIG, LOW); }
 else{ digitalWrite(CAL_SIG, HIGH); }
}


void ISR_wrapper_vector_4(void) __attribute__ ((section (".vector_4")));
// Put the ISR_wrapper in the good place

void ISR_wrapper_vector_4(void) { Tmr1Interrupt(); }
// ISR_wrapper will call the Tmr1Interrupt()

void Tmr1Interrupt(void) __attribute__ ((interrupt));
// Tmr1Interrupt is declared as an interrupt routine


/*********************************************************/
/*  Function name: Tmr1Interrupt          */
/*  Parameters                            */
/*    Input   :  No                       */
/*    Output  :  No                       */
/*    Action: All actions are done here.  */
/*********************************************************/
void Tmr1Interrupt(void)
{
if (IFS0bits.T1IF) // Timer Interrupt flag
{
TMR1=0; // reset the timer register
IFS0CLR=0x10; // Clear the timer interrupt flag
toggle_LED1(); // Toggle LED1 with frequency 250/2 = 125Hz. LED1 can be used to monitor Timer1 interrupt frequency!
//Read the 6 ADC inputs and store current values in Packet
for(CurrentCh=0;CurrentCh<6;CurrentCh++){
 ADC_Value = analogRead(CurrentCh);
 TXBuf[((2*CurrentCh) + HEADERLEN)] = ((unsigned char)((ADC_Value & 0xFF00) >> 8)); // Write High Byte
 TXBuf[((2*CurrentCh) + HEADERLEN + 1)] = ((unsigned char)(ADC_Value & 0x00FF)); // Write Low Byte
}
 
// Send Packet
putUSBUSART(TXBuf,17);
TXBuf[3]++; // increment packet counter
 
counter++; // increment the devider counter
if(counter == 12){ // 250/12/2 = 10.4Hz ->Toggle frequency
counter = 0;
toggle_GAL_SIG(); // Generate CAL signal with frequ ~10Hz
#if defined(PIC32_PINGUINO_OTG)
LED2_TOGG; // Show CAL_SIG frequency via LED2 blinking
#endif
}
 
}
}

/*********************************************************/
/*  Function name: init_timer1          */
/*  Parameters                          */
/*    Input   :  No                   */
/*    Output  :  No                   */
/*    Action: configure timer1 operation. */
/*********************************************************/
void init_timer1(void)
{
 IntConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR); // interrupt mode (interrupt.c)
 T1CON=0; // reset timer 1 configuration
 TMR1=0; // reset timer 1 counter register
 #if defined(PIC32_PINGUINO_OTG)
PR1=20000; // define the preload register
//PIC32_PINGUINO_OTG: 40000000(Peripheral Clk)/8(Prescaller)/20000(PR1) = 250Hz
 #elif defined(PIC32_PINGUINO_220)
PR1=10000; // define the preload register
//PIC32_PINGUINO_220: 20000000(Peripheral Clk)/8(Prescaller)/10000(PR1) = 250Hz
 #endif
 IPC1SET=0x7; // select interrupt priority and sub-priority
 IFS0CLR=0x10; // clear interrupt flag
 IEC0SET=0x10; // enable timer 1 interrupt
 T1CONSET=0x8010; // start timer 1 and set prescaler

}

/*********************************************************/
/*  Function name: setup                */
/*  Parameters                          */
/*    Input   :  No                   */
/*    Output  :  No                    */
/*    Action: Initializes all peripherals */
/*********************************************************/
void setup()
{
 init_timer1();
 pinMode(LED1, OUTPUT);
 pinMode(CAL_SIG, OUTPUT);
  //Write init whole packet
 TXBuf[0] = 0xa5;   //Sync 0
 TXBuf[1] = 0x5a;   //Sync 1
 TXBuf[2] = 0x02;     //Protocol version
 TXBuf[3] = 0x00;     //Packet counter
 TXBuf[4] = 0x02;     //CH1 High Byte
 TXBuf[5] = 0x00;     //CH1 Low Byte
 TXBuf[6] = 0x02;     //CH2 High Byte
 TXBuf[7] = 0x00;     //CH2 Low Byte
 TXBuf[8] = 0x02;     //CH3 High Byte
 TXBuf[9] = 0x00;     //CH3 Low Byte
 TXBuf[10] = 0x02;    //CH4 High Byte
 TXBuf[11] = 0x00;    //CH4 Low Byte
 TXBuf[12] = 0x02;    //CH5 High Byte
 TXBuf[13] = 0x00;    //CH5 Low Byte
 TXBuf[14] = 0x02;    //CH6 High Byte
 TXBuf[15] = 0x00;    //CH6 Low Byte 
 TXBuf[2 * NUMCHANNELS + HEADERLEN] =  0x01; // Switches state
}

/*********************************************************/
/*  Function name: loop                   */
/*  Parameters                            */
/*    Input   :  No                       */
/*    Output  :  No                       */
/*    Action: Do nothing.         */
/*********************************************************/
void loop()
{
 asm("NOP");
}

.END

No comments:

Post a Comment