From 58766117e4be8bb1a996b7d9c926ce43ede896f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20K=C3=B6gl?= Date: Fri, 7 Mar 2014 20:34:26 +0100 Subject: [PATCH] added custom mod_pulse firmware --- mod_pulse_psychose/make.sh | 2 + mod_pulse_psychose/mod_pulse.c | 917 ++++++++++++++++++++++++++ mod_pulse_psychose/mod_pulse_psychose | Bin 0 -> 25812 bytes 3 files changed, 919 insertions(+) create mode 100755 mod_pulse_psychose/make.sh create mode 100644 mod_pulse_psychose/mod_pulse.c create mode 100755 mod_pulse_psychose/mod_pulse_psychose diff --git a/mod_pulse_psychose/make.sh b/mod_pulse_psychose/make.sh new file mode 100755 index 0000000..a03a97d --- /dev/null +++ b/mod_pulse_psychose/make.sh @@ -0,0 +1,2 @@ +msp430-gcc -I/usr/msp430/include -Wall mod_ -mmcu=msp430fg439 -o mod-pulse-pyschose -L /usr/msp430/lib/ldscripts/msp430fg439 -lm -lfp -pipe + diff --git a/mod_pulse_psychose/mod_pulse.c b/mod_pulse_psychose/mod_pulse.c new file mode 100644 index 0000000..cb758c1 --- /dev/null +++ b/mod_pulse_psychose/mod_pulse.c @@ -0,0 +1,917 @@ +//***************************************************************************** +// THIS PROGRAM IS PROVIDED "AS IS". TI MAKES NO WARRANTIES OR +// REPRESENTATIONS, EITHER EXPRESS, IMPLIED OR STATUTORY, +// INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE, LACK OF VIRUSES, ACCURACY OR +// COMPLETENESS OF RESPONSES, RESULTS AND LACK OF NEGLIGENCE. +// TI DISCLAIMS ANY WARRANTY OF TITLE, QUIET ENJOYMENT, QUIET +// POSSESSION, AND NON-INFRINGEMENT OF ANY THIRD PARTY +// INTELLECTUAL PROPERTY RIGHTS WITH REGARD TO THE PROGRAM OR +// YOUR USE OF THE PROGRAM. +// +// IN NO EVENT SHALL TI BE LIABLE FOR ANY SPECIAL, INCIDENTAL, +// CONSEQUENTIAL OR INDIRECT DAMAGES, HOWEVER CAUSED, ON ANY +// THEORY OF LIABILITY AND WHETHER OR NOT TI HAS BEEN ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGES, ARISING IN ANY WAY OUT +// OF THIS AGREEMENT, THE PROGRAM, OR YOUR USE OF THE PROGRAM. +// EXCLUDED DAMAGES INCLUDE, BUT ARE NOT LIMITED TO, COST OF +// REMOVAL OR REINSTALLATION, COMPUTER TIME, LABOR COSTS, LOSS +// OF GOODWILL, LOSS OF PROFITS, LOSS OF SAVINGS, OR LOSS OF +// USE OR INTERRUPTION OF BUSINESS. IN NO EVENT WILL TI'S +// AGGREGATE LIABILITY UNDER THIS AGREEMENT OR ARISING OUT OF +// YOUR USE OF THE PROGRAM EXCEED FIVE HUNDRED DOLLARS +// (U.S.$500). +// +// Unless otherwise stated, the Program written and copyrighted +// by Texas Instruments is distributed as "freeware". You may, +// only under TI's copyright in the Program, use and modify the +// Program without any charge or restriction. You may +// distribute to third parties, provided that you transfer a +// copy of this license to the third party and the third party +// agrees to these terms by its first use of the Program. You +// must reproduce the copyright notice and any other legend of +// ownership on each copy or partial copy, of the Program. +// +// You acknowledge and agree that the Program contains +// copyrighted material, trade secrets and other TI proprietary +// information and is protected by copyright laws, +// international copyright treaties, and trade secret laws, as +// well as other intellectual property laws. To protect TI's +// rights in the Program, you agree not to decompile, reverse +// engineer, disassemble or otherwise translate any object code +// versions of the Program to a human-readable form. You agree +// that in no event will you alter, remove or destroy any +// copyright notice included in the Program. TI reserves all +// rights not specifically granted under this license. Except +// as specifically provided herein, nothing in this agreement +// shall be construed as conferring by implication, estoppel, +// or otherwise, upon you, any license or other right under any +// TI patents, copyrights or trade secrets. +// +// You may not use the Program in non-TI devices. +//***************************************************************************** +// MSP430FG437 based pulse oximeter demonstration - Version II +// V. Chan and S. Underwood +// May 2005 +// Modified by Bhargavi Nisarga +// April 2008 +// All modifications related to Olimex's LCD were made by +// Penko T. Bozhkov, Olimex LTD +// June 2011 +//***************************************************************************** +#include "msp430fg439.h" +#include "stdint.h" +#include "intrinsics.h" +#include "math.h" + +// LCD Segment Configuration +#define seg_a 0x01 +#define seg_b 0x02 +#define seg_c 0x04 +#define seg_d 0x08 +#define seg_e 0x40 +#define seg_f 0x10 +#define seg_g 0x20 +#define seg_h 0x80 + +#define NUM_0 (seg_a | seg_b | seg_c | seg_d | seg_e | seg_f) +#define NUM_1 (seg_b | seg_c) +#define NUM_2 (seg_a | seg_b | seg_d | seg_e | seg_g) +#define NUM_3 (seg_a | seg_b | seg_c | seg_d | seg_g) +#define NUM_4 (seg_b | seg_c | seg_f | seg_g) +#define NUM_5 (seg_a | seg_c | seg_d | seg_f | seg_g) +#define NUM_6 (seg_a | seg_c | seg_d | seg_e | seg_f | seg_g) +#define NUM_7 (seg_a | seg_b | seg_c) +#define NUM_8 (seg_a | seg_b | seg_c | seg_d | seg_e | seg_f | seg_g) +#define NUM_9 (seg_a | seg_b | seg_c | seg_d | seg_f | seg_g) +#define NUM_A (seg_a | seg_b | seg_c | seg_e | seg_f | seg_g) +#define NUM_B (seg_c | seg_d | seg_e | seg_f | seg_g) +#define NUM_C (seg_a | seg_d | seg_e | seg_f) +#define NUM_D (seg_b | seg_c | seg_d | seg_e | seg_g) +#define NUM_E (seg_a | seg_d | seg_e | seg_f | seg_g) +#define NUM_F (seg_a | seg_e | seg_f | seg_g) + + +// ***************************************************************** +// Definitions related to Olimex's LCD Digits and initialization!!!! +// ***************************************************************** +// Definitions for Olimex LCD digits 10 and 11 +#define a 0x10 +#define b 0x01 +#define c 0x04 +#define d 0x08 +#define e 0x40 +#define f 0x20 +#define g 0x02 +#define h 0x80 +// Character generator definition for display digits 10 and 11 +const char char_gen_10_11[] = { + a+b+c+d+e+f, // 0 Displays "0" + b+c, // 1 Displays "1" + a+b+d+e+g, // 2 Displays "2" + a+b+c+d+g, // 3 Displays "3" + b+c+f+g, // 4 Displays "4" + a+c+d+f+g, // 5 Displays "5" + a+c+d+e+f+g, // 6 Displays "6" + a+b+c, // 7 Displays "7" + a+b+c+d+e+f+g, // 8 Displays "8" + a+b+c+d+f+g, // 9 Displays "9" +}; +// undefines +#undef a +#undef b +#undef c +#undef d +#undef e +#undef f +#undef g +#undef h + +// Definitions for Olimex LCD digits 8 and 9 +#define a 0x01 +#define b 0x02 +#define c 0x04 +#define d 0x80 +#define e 0x40 +#define f 0x10 +#define g 0x20 +#define h 0x08 +// Character generator definition for display digits 8 and 9 +const char char_gen_8_9[] = { + a+b+c+d+e+f, // 0 Displays "0" + b+c, // 1 Displays "1" + a+b+d+e+g, // 2 Displays "2" + a+b+c+d+g, // 3 Displays "3" + b+c+f+g, // 4 Displays "4" + a+c+d+f+g, // 5 Displays "5" + a+c+d+e+f+g, // 6 Displays "6" + a+b+c, // 7 Displays "7" + a+b+c+d+e+f+g, // 8 Displays "8" + a+b+c+d+f+g, // 9 Displays "9" +}; +// undefines +#undef a +#undef b +#undef c +#undef d +#undef e +#undef f +#undef g +#undef h + +// Definitions for Olimex LCD digits 1 to 7. Here each digit definition require 2 bytes +#define a 0x0080 +#define b 0x0040 +#define c 0x0020 +#define d 0x0010 +#define e 0x2000 +#define f 0x4000 +#define g 0x0402 +#define h 0x1000 +// Character generator definition for display digits 1 to 7 +const int char_gen_1_7[] = { + a+b+c+d+e+f, // 0 Displays "0" + b+c, // 1 Displays "1" + a+b+d+e+g, // 2 Displays "2" + a+b+c+d+g, // 3 Displays "3" + b+c+f+g, // 4 Displays "4" + a+c+d+f+g, // 5 Displays "5" + a+c+d+e+f+g, // 6 Displays "6" + a+b+c, // 7 Displays "7" + a+b+c+d+e+f+g, // 8 Displays "8" + a+b+c+d+f+g, // 9 Displays "9" +}; +// undefines +#undef a +#undef b +#undef c +#undef d +#undef e +#undef f +#undef g +#undef h + + +int heart_pulse = 0; + +int itobcd(int i) // Convert hex word to BCD. +{ + int bcd = 0; // + char j = 0; // + + while (i > 9) // + { + bcd |= ((i % 10) << j); // + i /= 10; // + j += 4; + } // + return (bcd | (i << j)); // Return converted value +}// itobcd(i) + + +const unsigned char hex_table[] = +{ + NUM_0,NUM_1,NUM_2,NUM_3,NUM_4,NUM_5,NUM_6,NUM_7, + NUM_8,NUM_9,NUM_A,NUM_B,NUM_C,NUM_D,NUM_E,NUM_F +}; + +int32_t mul16(register int16_t x, register int16_t y) { + return ((long) x * y); +} + +//FIR filter coefficient for removing 50/60Hz and 100/120Hz from the signals +#if 0 +static const int16_t coeffs[9] = +{ + 5225, + 5175, + 7255, + 9453, + 11595, + 13507, + 15016, + 15983, + 16315 +}; +#else +static const int16_t coeffs[12] = +{ + 688, + 1283, + 2316, + 3709, + 5439, + 7431, + 9561, + 11666, + 13563, + 15074, + 16047, + 16384 +}; +#endif + +// SaO2 Look-up Table +const unsigned int Lookup [43] = {100,100,100,100,99,99,99,99,99,99,98,98,98,98, + 98,97,97,97,97,97,97,96,96,96,96,96,96,95,95, + 95,95,95,95,94,94,94,94,94,93,93,93,93,93}; +// +// #define FIRST_STAGE_TARGET_HIGH 3900 +// #define FIRST_STAGE_TARGET_LOW 3600 +// #define FIRST_STAGE_TARGET_HIGH_FINE 4096 +// #define FIRST_STAGE_TARGET_LOW_FINE 3500 + +// LED Target Range +#define FIRST_STAGE_TARGET_HIGH 3500 +#define FIRST_STAGE_TARGET_LOW 3000 +#define FIRST_STAGE_TARGET_HIGH_FINE 4096 +#define FIRST_STAGE_TARGET_LOW_FINE 2700 +#define FIRST_STAGE_STEP 5 +#define FIRST_STAGE_FINE_STEP 1 + +// UART Transmission Structure Definition +enum scope_type_e +{ + SCOPE_TYPE_OFF = 0, + SCOPE_TYPE_HEART_SIGNALS, + SCOPE_TYPE_RAW_SIGNALS, + SCOPE_TYPE_LED_DRIVE, +}; +int scope_type = SCOPE_TYPE_HEART_SIGNALS; +//int scope_type = SCOPE_TYPE_RAW_SIGNALS; + +int ir_dc_offset = 2000; +int vs_dc_offset = 2000; +int ir_LED_level; +int vs_LED_level; +int ir_sample; +int vs_sample; +char is_IR; +int ir_heart_signal; +int vs_heart_signal; +int ir_heart_ac_signal; +int vs_heart_ac_signal; +unsigned int rms_ir_heart_ac_signal; +unsigned int rms_vs_heart_ac_signal; +int32_t ir_2nd_dc_register = 0; +int32_t vs_2nd_dc_register = 0; +unsigned long log_sq_ir_heart_ac_signal; +unsigned long log_sq_vs_heart_ac_signal; +unsigned long sq_ir_heart_ac_signal; +unsigned long sq_vs_heart_ac_signal; +unsigned int pos_edge = 0; +unsigned int edge_debounce; +unsigned int heart_beat_counter; +unsigned int log_heart_signal_sample_counter; +unsigned int heart_signal_sample_counter; + +volatile unsigned int j; + +/* The results */ +unsigned int heart_rate; +unsigned int heart_rate_LSB = 0; +unsigned int SaO2, Ratio; +unsigned int SaO2_LSB = 0; + +/* Function prototypes */ +//unsigned long isqrt32(register unsigned long h); +int16_t dc_estimator(register int32_t *p, register int16_t x); +int16_t ir_filter(int16_t sample); +int16_t vs_filter(int16_t sample); +void set_LCD(void); +void display_number(int value, int start, int width); +void display_pulse(int on); +void display_correcting(int x, int on); + +void delay(long cycles){ + while(cycles){ cycles--; } +} + +int main(void) +{ + double f1; + int32_t x; + int32_t y; + + WDTCTL = WDTPW | WDTHOLD; + SCFI0 |= FN_4; // x2 DCO frequency, 8MHz nominal + // DCO + SCFQCTL = 91; // 32768 x 2 x (91 + 1) = 6.03 MHz + FLL_CTL0 = DCOPLUS + XCAP10PF; // DCO+ set so freq = xtal x D x + //(N + 1) + // Loop until 32kHz crystal stabilizes + do + { + IFG1 &= ~OFIFG; // Clear oscillator fault flag + for (j = 50000; j; j--); // Delay + } + while (IFG1 & OFIFG); // Test osc fault flag + + // Setup GPIO + P1DIR = 0xFF; + P1OUT = 0; + P2DIR = 0xFF; + P2DIR |= BIT2 + BIT3; // P2.2 and P2.3 o/p direction - + // drives PNP transistors in H-Bridge + P2OUT = 0; + P3DIR = 0xFF; + P3OUT = 0; + P4DIR = 0xFF; + P4OUT = 0; + P5DIR = 0xFF; + P5OUT = 0; + P6OUT = 0; + + /* Setup LCD */ + set_LCD(); + + /* First amplifier stage - transconductance configuration */ + P6SEL |= (BIT0 | BIT1 | BIT2); // Select OA0O + // -ve=OA0I0, +ve=OA0I1 + OA0CTL0 = OAN_0 | OAP_1 | OAPM_3 | OAADC1; + OA0CTL1 = 0; + + /* Second amplifier stage */ + P6SEL |= (BIT3 | BIT4); // Select 0A1O 0A1I + // -ve=OA1I0, +ve=DAC1 + // -ve=OA1I0, +ve=DAC1 +// OA1CTL0 = OAN_0 | OAP_3 | OAPM_3 | OAADC1; +// OA1CTL1 = 0x00; + // Inverted input internally + // connected to OA0 output + OA1CTL0 = OAN_2 + OAP_3 + OAPM_3 + OAADC1; + OA1CTL1 = OAFBR_7 + OAFC_6; // OA as inv feedback amp, internal + // gain = 15; + + /* Configure DAC 1 to provide bias for the amplifier */ + P6SEL |= BIT7; + DAC12_1CTL = DAC12CALON | DAC12IR | DAC12AMP_7 | DAC12ENC; + DAC12_1DAT = 0; + + /* Configure DAC 0 to provide variable drive to the LEDs */ + DAC12_0CTL = DAC12CALON | DAC12IR | DAC12AMP_7 | DAC12ENC; // VRef+, high speed/current, + // DAC12OPS=0 => DAC12_0 output on P6.6 (pin 5) */ + // Configure P2.2 and P2.3 to + // provide variable drive to LEDs + P2OUT |= BIT2; // turn off source for D2 + P2OUT &= ~BIT3; // turn on source for D3 + DAC12_0DAT = 3340; + + // Set initial values for the LED brightnesses + ir_LED_level = 1300; + vs_LED_level = 1450; + + /* Configure ADC12 */ + ADC12CTL0 &= ~ENC; // Enable conversions + // Turn on the ADC12, and + // set the sampling time + ADC12CTL0 = ADC12ON + MSC + SHT0_4 + REFON + REF2_5V; + ADC12CTL1 = SHP + SHS_1 + CONSEQ_1; // Use sampling timer, single sequence, + // TA1 trigger(SHS_1), start with ADC12MEM0 + ADC12MCTL0 = INCH_1 + SREF_1; // ref+=Vref, channel = A1 = OA0 + ADC12MCTL1 = INCH_3 + SREF_1 + EOS; // ref+=Vref, channel = A3 = OA1 + ADC12IE = BIT1; // ADC12MEM1 interrupt enable + ADC12CTL0 |= ENC; // Enable the ADC + ADC12CTL0 |= ADC12SC; // Start conversion + + /* Configure Timer */ + TACTL = TASSEL0 + TACLR; // ACLK, clear TAR, + TACCTL1 = OUTMOD_2; + TACCTL0 = CCIE; + // This gives a sampling rate of + // 512sps + TACCR0 = 31; // Do two channels, at + // 512sps each. + TACCR1 = 10; // Allow plenty of time for the + // signal to become stable before + // sampling + TACTL |= MC_1; // Timer A on, up mode + + /*Configure USART, so we can report readings to a PC */ + P2DIR |= BIT4; + P2SEL |= BIT4; + + UCTL0 |= SWRST; + ME1 |= UTXE0; // Enable USART1 TXD + UCTL0 |= CHAR; // 8-bit char, SWRST=1 + UTCTL0 |= SSEL1; // UCLK = SMCLK + UBR00 = 52; // 115200 from 6.02MHz = 52.33 + UBR10 = 0x00; + UMCTL0 = 0x45; // Modulation = 0.375 + UCTL0 &= ~SWRST; // Initialise USART + + +/* + // For Olimex's LCD debug purpose only! + int j=999; + for(int i=0;i<10;i++){ + delay(700000); + display_number(j, 3, 3); // The Small digits + display_number(j, 7, 3); // The Large digits + j = j-111; + } + set_LCD(); +*/ + + while(1) + { + __bis_SR_register(LPM0_bits + GIE); + __bis_SR_register(LPM0_bits); // Enter LPM0 needed for UART TX completion + __no_operation(); + + /* Heart Rate Computation */ + f1 = 60.0*512.0*3.0/(float)log_heart_signal_sample_counter; + heart_rate = (unsigned int)f1; + //heart_rate = f1; + display_number(heart_rate, 3, 3); + heart_rate_LSB = heart_rate & 0x00FF; + + /* SaO2 Computation */ + x = log_sq_ir_heart_ac_signal/log_heart_signal_sample_counter; + y = log_sq_vs_heart_ac_signal/log_heart_signal_sample_counter; + Ratio = (unsigned int) (100.0*logf(y)/logf(x)); + if (Ratio > 66) + SaO2 = Lookup[Ratio - 66]; // Ratio - 50 (Look-up Table Offset) - 16 (Ratio offset) + else if (Ratio > 50) + SaO2 = Lookup[Ratio - 50]; // Ratio - 50 (Look-up Table Offset) + else + //SaO2 = 100; + SaO2 = 99; + display_number(SaO2, 7, 3); + SaO2_LSB = SaO2 & 0x00FF; + } + return 0; +} + + +// Timer A0 interrupt service routine +#pragma vector=TIMERA0_VECTOR +__interrupt void Timer_A0(void) +{ + int i; + if ((DAC12_0CTL & DAC12OPS)) // D2 enabled in demo board + { + // Immediately enable the visible + // LED, to allow time for the + // transimpedance amp to settle + DAC12_0CTL &= ~DAC12ENC; + P2OUT &= ~BIT3; // turn on source for D3 + DAC12_0CTL &= ~DAC12OPS; // Disable IR LED, enable visible LED + DAC12_0CTL |= DAC12ENC; + DAC12_0DAT = vs_LED_level; + DAC12_1DAT = vs_dc_offset; // Load op-amp offset value for visible + P2OUT |= BIT2; // turn off source for D2 + + is_IR = 0; // IR LED OFF + + ir_sample = ADC12MEM0; // Read the IR LED results + i = ADC12MEM1; + // Enable the next conversion sequence. + // The sequence is started by TA1 + ADC12CTL0 &= ~ENC; + ADC12CTL0 |= ENC; + + // Filter away 50/60Hz electrical pickup, + // and 100/120Hz room lighting optical pickup + ir_heart_signal = ir_filter(i); + // Filter away the large DC + // component from the sensor */ + ir_heart_ac_signal = ir_heart_signal - dc_estimator(&ir_2nd_dc_register, ir_heart_signal); + + /* Bring the IR signal into range through the second opamp */ + if (i >= 4095) + { + if (ir_dc_offset > 100) + ir_dc_offset--; + } + else if (i < 100) + { + if (ir_dc_offset < 4095) + ir_dc_offset++; + } + + sq_ir_heart_ac_signal += (mul16(ir_heart_ac_signal, ir_heart_ac_signal) >> 10); + + //Tune the LED intensity to keep + //the signal produced by the first + //stage within our target range. + //We don't really care what the + //exact values from the first + //stage are. They need to be + //quite high, because a weak + //signal will give poor results + //in later stages. However, the + //exact value only has to be + //within the range that can be + //handled properly by the next + //stage. */ + + if (ir_sample > FIRST_STAGE_TARGET_HIGH + || + ir_sample < FIRST_STAGE_TARGET_LOW) + { + //We are out of the target range + //Starting kicking the LED + //intensity in the right + //direction to bring us back + //into range. We use fine steps + //when we are close to the target + //range, and coarser steps when + //we are far away. + if (ir_sample > FIRST_STAGE_TARGET_HIGH) + { + if (ir_sample >= FIRST_STAGE_TARGET_HIGH_FINE) + ir_LED_level -= FIRST_STAGE_STEP; + else + ir_LED_level -= FIRST_STAGE_FINE_STEP; + // Clamp to the range of the DAC + if (ir_LED_level < 0) + ir_LED_level = 0; + } + else + { + if (ir_sample < FIRST_STAGE_TARGET_LOW_FINE) + ir_LED_level += FIRST_STAGE_STEP; + else + ir_LED_level += FIRST_STAGE_FINE_STEP; + // Clamp to the range of the DAC + if (ir_LED_level > 4095) + ir_LED_level = 4095; + } + } + + /* UART Transmission - IR heart signals */ + switch (scope_type) + { + case SCOPE_TYPE_HEART_SIGNALS: + i = (ir_heart_ac_signal >> 6) + 128; + // Saturate to a byte + if (i >= 255) // Make sure the data != 0x0 or 0xFF + i = 254; // as 0x0 and 0xFF are used for sync + else if (i <= 0) // bytes in the LABVIEW GUI + i = 1; + + TXBUF0 = 0x00; // Byte 1 - 0x00 (synchronization byte) + while (!(IFG1 & UTXIFG0)); + TXBUF0 = 0xFF; // Byte 2 - 0xFF (synchronization byte) + while (!(IFG1 & UTXIFG0)); + TXBUF0 = i; // Byte 3 - IR Heart signal (AC only) + while (!(IFG1 & UTXIFG0)); + TXBUF0 = heart_rate_LSB; // Byte 4 - Heart rate data + while (!(IFG1 & UTXIFG0)); + TXBUF0 = SaO2_LSB; // Byte 5 - %SaO2 data + while (!(IFG1 & UTXIFG0)); + TXBUF0 = heart_pulse; + break; + + case SCOPE_TYPE_RAW_SIGNALS: + while (!(IFG1 & UTXIFG0)); + TXBUF0 = ir_sample >> 4; + break; + case SCOPE_TYPE_LED_DRIVE: + TXBUF0 = ir_LED_level >> 4; + break; + } + + /* Track the beating of the heart */ + heart_signal_sample_counter++; + if (pos_edge) + { + if (edge_debounce < 120) + { + edge_debounce++; + } + else + { + if (ir_heart_ac_signal < -200) + { + edge_debounce = 0; + pos_edge = 0; + display_pulse(0); + } + } + } + else + { + if (edge_debounce < 120) + { + edge_debounce++; + } + else + { + if (ir_heart_ac_signal > 200) + { + edge_debounce = 0; + pos_edge = 1; + display_pulse(1); + //display_correcting(1, 0); + if (++heart_beat_counter >= 3) + { + log_heart_signal_sample_counter = heart_signal_sample_counter; + log_sq_ir_heart_ac_signal = sq_ir_heart_ac_signal; + log_sq_vs_heart_ac_signal = sq_vs_heart_ac_signal; + heart_signal_sample_counter = 0; + sq_ir_heart_ac_signal = 0; + sq_vs_heart_ac_signal = 0; + heart_beat_counter = 0; + _BIC_SR_IRQ(LPM0_bits); + // Do a dummy wake up roughly + // every 2 seconds + } + } + } + } + } + else //D3 enabled in demoboard + { + //Immediately enable the IR LED, + //to allow time for the + //transimpedance amp to settle */ + DAC12_0CTL &= ~DAC12ENC; + P2OUT &= ~BIT2; //turn on source for D3 + DAC12_0CTL |= DAC12OPS; // Disable visible LED, enable IR LED + DAC12_0CTL |= DAC12ENC; + DAC12_0DAT = ir_LED_level; + DAC12_1DAT = ir_dc_offset; // Load op-amp offset value for IR + P2OUT |= BIT3; //turn off source for D2 + + is_IR = 1; // IR LED ON + + vs_sample = ADC12MEM0; //Read the visible LED results + i = ADC12MEM1; + + //Enable the next conversion sequence. + //The sequence is started by TA1 + ADC12CTL0 &= ~ENC; + ADC12CTL0 |= ENC; + + + //Filter away 50/60Hz electrical + //pickup, and 100/120Hz room + //lighting optical pickup */ + vs_heart_signal = vs_filter(i); + //Filter away the large DC + //component from the sensor */ + vs_heart_ac_signal = vs_heart_signal - dc_estimator(&vs_2nd_dc_register, vs_heart_signal); + + /* Bring the VS signal into range through the second opamp */ + if (i >= 4095) + { + if (vs_dc_offset > 100) + vs_dc_offset--; + } + else if (i < 100) + { + if (vs_dc_offset < 4095) + vs_dc_offset++; + } + + sq_vs_heart_ac_signal += (mul16(vs_heart_ac_signal, vs_heart_ac_signal) >> 10); + + if (vs_sample > FIRST_STAGE_TARGET_HIGH + || + vs_sample < FIRST_STAGE_TARGET_LOW) + { + /* We are out of the target range */ + //display_correcting(1, 1); + if (vs_sample > FIRST_STAGE_TARGET_HIGH) + { + if (vs_sample >= FIRST_STAGE_TARGET_HIGH_FINE) + vs_LED_level -= FIRST_STAGE_STEP; + else + vs_LED_level -= FIRST_STAGE_FINE_STEP; + if (vs_LED_level < 0) + vs_LED_level = 0; + } + else + { + if (vs_sample < FIRST_STAGE_TARGET_LOW_FINE) + vs_LED_level += FIRST_STAGE_STEP; + else + vs_LED_level += FIRST_STAGE_FINE_STEP; + if (vs_LED_level > 4095) + vs_LED_level = 4095; + } + } + } + +} + +#pragma vector=ADC_VECTOR +__interrupt void ADC12ISR(void) +{ + ADC12IFG &= ~BIT1; // Clear the ADC12 interrupt flag + DAC12_0DAT = 0; // Turn OFF the LED + DAC12_1DAT = 0; + // Turn OFF the H-Bridge completely + if(is_IR) // If IR LED was ON in TA0 ISR + P2OUT |= BIT2; // P2.2 = 1 + else // Else if VS LED ON in TA0 ISR + P2OUT |= BIT3; // P2.3 = 1 +} + +int16_t ir_filter(int16_t sample) +{ + static int16_t buf[32]; + static int offset = 0; + int32_t z; + int i; + //Filter hard above a few Hertz, + //using a symmetric FIR. + //This has benign phase + //characteristics */ + buf[offset] = sample; + z = mul16(coeffs[11], buf[(offset - 11) & 0x1F]); + for (i = 0; i < 11; i++) + z += mul16(coeffs[i], buf[(offset - i) & 0x1F] + buf[(offset - 22 + i) & 0x1F]); + offset = (offset + 1) & 0x1F; + return z >> 15; +} + +int16_t vs_filter(int16_t sample) +{ + static int16_t buf[32]; + static int offset = 0; + int32_t z; + int i; + + //Filter hard above a few Hertz, + //using a symmetric FIR. + //This has benign phase + //characteristics */ + buf[offset] = sample; + z = mul16(coeffs[11], buf[(offset - 11) & 0x1F]); + for (i = 0; i < 11; i++) + z += mul16(coeffs[i], buf[(offset - i) & 0x1F] + buf[(offset - 22 + i) & 0x1F]); + offset = (offset + 1) & 0x1F; + return z >> 15; +} + +/*unsigned long isqrt32(register unsigned long h) +{ + register unsigned long x; + register unsigned long y; + register int i; + + //Calculate a 32 bit bit square + //root of a 32 bit integer, + //where the top 16 bits + //of the result is the integer + //part of the result, and the + //low 16 bits are fractional. + x = + y = 0; + for (i = 0; i < 32; i++) + { + x = (x << 1) | 1; + if (y < x) + x -= 2; + else + y -= x; + x++; + y <<= 1; + if ((h & 0x80000000)) + y |= 1; + h <<= 1; + y <<= 1; + if ((h & 0x80000000)) + y |= 1; + h <<= 1; + } + return x; +} */ + +int16_t dc_estimator(register int32_t *p, register int16_t x) +{ + /* Noise shaped DC estimator. */ + *p += ((((int32_t) x << 16) - *p) >> 9); + return (*p >> 16); +} + +/* LCD number Display */ +void display_number(int value, int start, int width) +{ +/* + unsigned int i; + unsigned int Output; + char *pLCD = (char *)&LCDMEM[7-start]; + + for (i = 16, Output = 0; i; i--) // BCD Conversion, 16-Bit + { + Output = __bcd_add_short(Output, Output); + if (value & 0x8000) + Output = __bcd_add_short(Output, 1); + value <<= 1; + } + + for (i = 0; i < width; i++) // Process 4 digits + { + *pLCD++ = hex_table[Output & 0x0f]; // Segments to LCD + Output >>= 4; // Process next digit + } +*/ + + value = itobcd(value); + + if(start == 3){ + // Display heart rate + LCDMEM[2] = char_gen_10_11[value & 0x0f]; // Display current heart rate units -> LCD Digit 11 + LCDMEM[3] = char_gen_10_11[(value & 0xf0) >> 4]; // tens -> LCD Digit 10 + LCDMEM[4] = char_gen_8_9[(value & 0xf00) >> 8]; // hundreds -> LCD Digit 9 + } + else if(start == 7){ + // Display oxigenation + LCDMEM[7] = ((char)(char_gen_1_7[value & 0x0f]>>8)); // LCD -> Digit 7 High Byte + LCDMEM[6] = ((char)(char_gen_1_7[value & 0x0f]&0x00FF)); // LCD -> Digit 7 Low Byte + LCDMEM[9] = ((char)(char_gen_1_7[((value & 0xf0) >> 4)]>>8)); // LCD -> Digit 6 High Byte + LCDMEM[8] = ((char)(char_gen_1_7[((value & 0xf0) >> 4)]&0x00FF)); // LCD -> Digit 6 Low Byte + // Don't display values bigger than 99 + //LCDMEM[11] = ((char)(char_gen_1_7[((value & 0xf00) >> 8)]>>8)); // LCD -> Digit 5 High Byte + //LCDMEM[10] = ((char)(char_gen_1_7[((value & 0xf00) >> 8)]&0x00FF)); // LCD -> Digit 5 Low Byte + } + + +} + +/* LCD Pulse Display */ +void display_pulse(int on) +{ + if (on) { + LCDMEM[1] = 0xF0; // Heart beat detected enable "<^>" on LCD + heart_pulse = 1; + } + else { + heart_pulse = 0; + LCDMEM[1] = 0x00; // Disable "<^>" on LCD for blinking effect + } +} + +/* LCD Correcting info Display */ +void display_correcting(int x, int on) +{ + if (on) + LCDMEM[3] |= ((x) ? seg_a : seg_d); + else + LCDMEM[3] &= ~((x) ? seg_a : seg_d); +} + +/* Configure LCD */ +void set_LCD(void) +{ + volatile unsigned int i; + for(i=0;i<20;i++) // Clear LCD memory + { + LCDMEM[i] = 0x00; + } + + /* Turn on the COM0-COM3 and R03-R33 pins */ + P5SEL |= (BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2); + + LCDCTL = 0x7F; // Selected function: Analog generator on + // Low impedance of AG + // 4Mux active + // all outputs are Seg + // S0-S23 are LCD segment lines + BTCTL = BTFRFQ0; // Start Basic Timer 1s + LCD 64Hz +} + diff --git a/mod_pulse_psychose/mod_pulse_psychose b/mod_pulse_psychose/mod_pulse_psychose new file mode 100755 index 0000000000000000000000000000000000000000..d246eb5b4fb6e805e68272ae3cf6c583edae70f2 GIT binary patch literal 25812 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%0A~k__F_C=YG)F0wv$ZC{%RU>c&N zZ{S3E-4ULn4ALD>hA-!%3iaz2)N%%;)rjbZ(aibj0CL|Bop5Ja*j{84XGCeKGcdL#A?sN|C_O$lN;Lyx)xat}$NJ-!ES$me49K