I have played with sampling @ 500 and 100 Hz, neither removed the 2 Hz noise – Another pulse oximeter model also produced a fair amount of noise, however, the 2Hz spike was not as apparent in the frequency spectrum, it was spread out from 1.75- 4.0 Hz.
The original circuit (without the digipot) does not produce any noise, which may indicate there is some underlying noise in both circuits (w/ and w/out the digipot) that is revealed when amplified.
The updated circuit is attached.
Main
#include <avr/wdt.h>
#include <SPI.h>
int speedMaximum = 10 * 1000000; // 10MHz
// interrupt and signal handling
int inputPin = A1;
volatile word signal;
volatile word sampleCounter = 0; // counter for pulse timing
volatile boolean interrupt_flagged = false; // basic flag to check for interrupt
const byte SHUTDOWN = 0x5A; // shutdown byte command
const byte START = 0x01; // start byte command
byte data_buffer[5];
boolean init_setup_flag = false;
// digipot related
byte address = 0x00; // Address of the wiper terminal of the pot, allows you to change the resistance value of the pot
int CS = 18; // The CS, or Chip Select, pin is the SS (slave select) pin for the SPI interface
int digipot_resistance = 0; // (0-128) 0: min resistance, 128 max resistance
void setup()
{
/* Sets up serial, interurupts, SPI, python-arduino handshake.
*/
MCUSR = 0;
WDTCSR |= _BV(WDCE) | _BV(WDE);
WDTCSR = 0;
Serial.begin(115200); // start serial @ baudrade
while (!Serial); // waits for an active serial connection to be established by the PC
byte pyResponse = ard_init(); // handshake protocol
interruptSetup(); // turns on timer interrupts for sampling
init_setup_flag = true;
// SPI
pinMode (SS, OUTPUT); // set the as an output
pinMode (CS, OUTPUT); // set the CS as an output
SPI.begin(); // initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high.
}
void loop()
{
/* Sends data to serial if interrupt/timer flag has been triggerd.
* Adjusts gain according to signal amplitude.
*/
// start setup
if (!init_setup_flag) {
setup();
digitalPotWrite(0);
}
// interrupted by sensor timer
if (interrupt_flagged)
{
// send data to serial
send_buffer_to_serial(signal, sampleCounter, data_buffer);
interrupt_flagged = false; //Wait until next interrupt
}
serialEvent();
}
int digitalPotWrite(int value)
{
/* Write new resistance value to pot.
INPUT: value of (0-128) 0: min resistance, 128 max resistance
*/
// Serial.println("In digitalPotWrite");
digitalWrite(CS, LOW);
SPI.beginTransaction(SPISettings(speedMaximum, MSBFIRST, SPI_MODE0));
SPI.transfer(address);
SPI.transfer(value);
SPI.endTransaction();
digitalWrite(CS, HIGH);
}
byte ard_init()
{
/* ard_init(): Initializes the ino-python handshake by constantly pinging the
serial port
RETURNS: Byte found in COM port
*/
TXLED1; // turn TXLED on
delay(2000);
TXLED0; // turn TXLED off
byte found_byte = 0x00;
//Wait until byte found
while (!Serial.available() && (found_byte = Serial.read()) == START) {
Serial.write(0x01);
TXLED1; // turn TXLED on
delay(150);
TXLED0; // turn TXLED off
delay(150);
}
TXLED0; // make sure TXLED is off
return found_byte;
}
void serialEvent()
{
/* Initializes the ino-python handshake by constantly pinging the
serial port
*/
//If Serial event AND byte == shutdown byte...
byte pyResponse = Serial.read(); // Initiate shutdown sequence
if (pyResponse == SHUTDOWN) { // Wait until Serial "wakes" arduino up
Serial.end();
TXLED1; // turn LED off
delay(100); //this delay is super important... i dont know why
TXLED0; // turn LED on
delay(100);
init_setup_flag = false;
}
TXLED0; // make sure TXLED is off
}
Interrupt
const int FRAME_SIZE = 5; // Header (1 byte) + signal (2 bytes) + count (2 bytes)
const int HEADER = 0xFF;
void interruptSetup(){
// Initializes Timer1 to throw an interrupt every 2ms (500Hz).
TCCR1A = 0x00; // start timer in CTC mode
TCCR1B = 0x0C; // set TOP to OCR1A, set clock prescaler to 256
OCR1A = 0x7C; // timer will count to this number (0x7C = 124), then trigger reset
TIMSK1 = 0x02; // turn on the OCR1A match interrupt
sei(); // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED
}
ISR(TIMER1_COMPA_vect)
{
/* Timer 1 Compare A Interrupt Service Routine
* The ppg value is acquired, the sample counter increases, and the ISR has
* run flag (interrupt_flagged becomes true) when the interrupt is thrown.
*/
cli(); // disables interrupts while we do this
signal = analogRead(inputPin); // reads sensor value
sampleCounter += 1; // increases arduino sample counter
interrupt_flagged = true; // flag interrupt
sei(); // turns on interrupts
}
// Generates an array of header+data bytes
byte* send_buffer_to_serial(word signal, word count, byte output[5])
{
/* INPUT: signal - ppg value (size of 2 bytes) to be sent to serial
* count - counter (size of 2 bytes) to be sent to serial
* output - the array to fill with the bytes sent to serial
*
* OUTPUT: (Header (1 byte) + signal (2 bytes) + count (2 bytes)) to Serial
*/
byte hb = highByte(signal); // takes in a value (signal) and extracts/returns the highest (left-most) byte of a word
byte lb = lowByte(signal); // takes in a value (signal) and extracts/returns the lowest (right-most) byte of a word
byte hb_count = highByte(count); //refer to documentation for more info
byte lb_count = lowByte(count);
output[0] = HEADER;
output[1] = hb;
output[2] = lb;
output[3] = hb_count;
output[4] = lb_count; // to send to serial
Serial.write(output, FRAME_SIZE);
}