918 lines
33 KiB
C
918 lines
33 KiB
C
//*****************************************************************************
|
|
// 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
|
|
}
|
|
|