Read and display data from a pulseoximeter

Status
Not open for further replies.

Micael

Member
Hello everyone!

I'm working on a project to help a friend with poor eyesight who want to use a PulseOximeter but the display is way to small so I will help him to attach a bigger display. But I have a problem to get the correct data to the display. I can send the data to Arduino IDE serial terminal and it looks like this:
Code:
132
32
3
80  (Pulse)
98 (Oxygenating)
132
29
3
80  (Pulse)
98 (Oxygenating)
132
26
3
80  (Pulse)
98 (Oxygenating)
132

The value 132 above is not always 132 so I can not use that to find the correct values. But it's the same value for each person using the pulseoximeter.

I used this code to generate the above data:

Code:
  Serial.begin(115200);
  HWSERIAL.begin(19200);
  
  while(!Serial);
  while(!HWSERIAL);

//---------- The interesting part of the code -------------------

    if (HWSERIAL.available())
    {
    Serial.println(HWSERIAL.read());    
    }

I'm using Arduino IDE 1.6.13 with Teensy loader 1.46 and a Teensy 3.6

The problem is that I have problem to send the correct values to a 7-segment display. The problem lies in how to filter out the Pulse- and Oxygenatingvalues. Any idea how I should do this?
I'm out of ideas at the moment so any suggestions is welcome.
 
Several hammers for this one. The easy method is to use strings, and use the string functions to fund the pulse and oxygenating chunks and parse from there. That can be exciting in the long term though with strings and micro controllers, so more robust is the character array.
Basic idea is that you load say a 30 deep buffer of char, and each time your HWSerial.Availible line finds a char it shifts all char down the buffer by one, and puts the next char in location 0 of the array.
Then you look at about location 0-6 for '(Pulse)' and if you find it go deeper into the buffer discarding spaces till you find digits, then start writing digits to the display until you find something not a digit and stop. Do the same for oxygenating checking locations 0-11)
Other methods look for the line end character and do the 'find digit' thing then, possibly just by counting how many chars since last line end (if<5 assume it's what ever number the 132 is, if 5-11 then assume pulse and >11 assume oxygen.

Yet another method if there is a time delay between readings that provides structure would be to run a counter, if a char arrives before the counter reaches the 'gap between reads' then throw away and reset counter to zero. Once time between reads reached capture all digits and send to display knowing they are (probably) the first line, once you get a space start throwing chars away again until you get a number again, and assume that's the second line and write them to display until you get a space, and repeat until the last number shows up. Not very pretty but you won't need much code to do it. Will of course fail horribly if this thing produces some other text every so often.
 
Thanks for the reply!
I might have been a bit unclear in the post above. "Pulse" and "Oxygenating" is not sent from the PulseOximeter is something I added for making it easier to see how the data is organized. The other numbers is not interesting in this project..
 
That doesn't seem to have an RS232 output on it. Is that the one you are using and, if so, how are you getting the data from it?

Pete
 
Hi Pete!
That's correct there is no RS232 on it as an option, but it is rather simple to get it. On the bottom side of the circuit board is a few test points and two of this test points are marked TX and RX so I just soldered a wire to the TX and one to the ground pad of a tantalum capacitor so I have common ground with the Teensy 3.6 and the pulseoximeter. By the way it's easy to disassemble you just lift the OLED displaycover and then carefully lift the circuit board. I can take pictures if you like. Have to do it tomorrow it's midnight and I need to get up early.
 
Oh, I see. You are doing a bit of reverse engineering :)
Is each transmission of 5 lines sent in sync with the pulse? If it is, you could measure the number of milliseconds between the reception of each character. The time between the linefeed (or carriage return) after "98" and the "1" of "132" will be longer than that between each of the other characters. That would allow you to determine where the sequence starts.

Pete
 
Hi Pete!
You bet I just love reverse engineering. This one was to easy, but that's fun in another way. With the code I used to read what the pulseoximeter is sending my guess is that it is sending one 'Start sending data" in the example above 132 (or whatever three digit number). I'm going to ask a few friends to try and see what happens with the 132-value. After the start value there is two values that is something else (nothing I need to figure out), then the value for pulse and oxygenating. And then it starts all over again. As you can see in the code I used to read what is transmitted:
Code:
    if (HWSERIAL.available())
    {
    Serial.println(HWSERIAL.read());    
    }
it must be sending one value at a time.
The problem with measuring the time will give me headache when the pulse is larger than 99 and I can not ignore that.
 
From this, I believe the data is coming to you in binary? as the println then converts the data to ascii text... Wonder if the value 3 is beginning or end marker?

So I would suggest doing what el_supremo mentioned and try to see what the timings are... Would not make any difference if the values was 99 or 100, as they are just different binary values for one byte...

I would have tendency to hook up logic analyzer and watch the stream... But you might try something like:

Code:
uint32_t last_time= millis();

void loop() {
    if (HWSERIAL.available()) {
        int ch;
        Serial.print(millis()-last_time, DEC);
        Serial.print(":")
        while ((ch = HWSERIAL.read()) != -1) {
            Serial.print(ch, DEC);
            Serial.print(" ");
        }
        Serial.println();
        last_time = millis();
    }
}

// remove yield overhead for timings
void yield() {
}
Then see if you get any breaks in time between groups of data... If all near 0 or 1 for timing, change millis() to micros()
 
Thanks for the reply KurtE!
I don't have a logic analyzer so I'll try your suggestion and see what I get. Hopefully I get a really useful result.
 
Sorry for the delay but there has been a lot of other things that has been more important. Now I have tried the code provided by KurtE an the result is as follows:
Code:
7788:134 
8881:1 
7788:134 
8881:0 85 96 
7732:134 
8937:0 85 96 
7730:134 
8885:0 85 96 
7781:134 
8885:0 85 96 
7781:134 
8886:0 85 96 
7729:134 
8937:1 
7736:134 
8881:2 
7788:134 
8882:4
The first value is microseconds. For one of the interesting rows the values is as follows:

8886:0 85 96
microseconds(8886): some_uninteresting value(0) Pulse (86) and Oxygenating (96)
The 0 (some_uninteresting value) is not always 0 it can be 3,4,5,6 so it's not an option for solving this problem.

Now I'm going to work on the code so I can find the rows where the Pulse and Oxygenating is sent. That must be possible.....:)


Thanks again for all the help!!

/Micael
 
Looking at it that way the times don't give you much, but would looking for two " " chars or just overall length get you where you need to go? In fact if that's what it produces and option might be to have a 'byte parserStage =0' variable that is 0 when getting stuff, one when you have got the first space, 2 when you have the second space and then resets to 0 when you run out of numbers.

So run the code above, but check if (ch == ' ') after printing it

If it does add 1 parserStage

separately if parserStage==1 feed ch values to wherever you store/display pulse, if it's 2 feed them to whatever stores/displays Oxygen - depending on your display you may need to parse them from char to a number but for just printing to an LCD leave them as is.

and in between the final println and reset of last_time you set parserStage back to 0.

Lots of ways for this to not work but might get you closer.
 
As GremlinWrangler mentioned, these micros may not in themselves fully solve the issue, but: if the values you are always interested in are, in the form: 8881:0 85 96

Another reason for wanting to see the micros/millis value here is to see if the time is > the time it takes to receive a character. That is if I did my quick math, the hardware will take about 1ms to send a character at 9600 baud, so the values of 7000-8800 micros is showing a gap between characters... So looks like packets.

So the code could then, try to read in packets. That is you could change the above code to try to handle the packets... Again, this is quick and dirty, so probably issues.
Code:
#define PACKET_TIMEOUT 4
uint32_t last_time= millis();
uint8_t packet_data[10];    // could setup proper size, but...
uint8_t packet_index = 0;

void loop() {
    if (HWSERIAL.available()) {
        int ch;
        while ((ch = HWSERIAL.read()) != -1) {
            packet_data[packet_index++] = ch;
        }
        last_time = millis();
    } else {
        if ((mills()-last_time) > PACKET_TIMEOUT) {
            if (packet_index == 3) {
                Serial.printf("%d %d %d\n", packet_data[0], packet_data[1], packet_data[2]);
            }
            packet_index = 0;
        }
    }
}

// remove yield overhead for timings
void yield() {
}
Again this code is quick and dirty derived from above and could be cleaned up, array size was hopefully big enough not to overflow or should put in test....
 
Thanks Again KurtE!!

The result from your last code is as follows:
Code:
3 80 95
5 80 95
9 80 95
10 80 95
12 80 95
12 80 95
12 80 95
12 80 95
10 80 95
9 80 95
6 80 95
5 80 95
5 80 95
5 80 95
5 80 95
3 80 95
3 80 95
3 80 95
5 80 95
3 80 95
3 80 95
6 80 95
9 80 95
15 80 95
15 80 95
15 80 95
15 80 95
15 80 95

It looks like this would work for me. Now I just have to send the correct values to my 7-segment displays but that's no problem, I hope. I will let you know the result as soon as possible.

I am very grateful for all the help you have given me. Thank You very much!!! :)


----- UPDATE -------
I have managed to finish (almost, you know how it is, the code is never completely finished). But the the correct values is displayed on the 7-segment displays.
The code is as follows:

Code:
//***********************************************************************************************************
byte digits[10]= {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90};  
byte signs[3]={0xFF,0xBF,0xF7};
//***********************************************************************************************************  
#define PACKET_TIMEOUT 4
uint32_t last_time= millis();
uint8_t packet_data[10];    
uint8_t packet_index = 0;
//*********** Display initiations ***************************************************************************  
#define LATCH 24
#define CLK 25
#define DATA 26
//***********************************************************************************************************
#define HWSERIAL Serial1
//***********************************************************************************************************
void WriteDoubleleDisplay(void);
void WriteToDisplay(int OxyGenating,int Pulse);
//-----------------------------------------------------------------------------------------------------------
void setup() 
  {
  pinMode(LATCH, OUTPUT);
  pinMode(CLK, OUTPUT);
  pinMode(DATA, OUTPUT);
  HWSERIAL.begin(19200);
  WriteDoubleleDisplay(signs[1],signs[1],signs[1],signs[1],signs[1],signs[1]); //Sends "------" to the 6 digit 7-segment display
  }
//-----------------------------------------------------------------------------------------------------------
void loop() 
  {
  if (HWSERIAL.available()) 
    {
    int ch;        
    while ((ch = HWSERIAL.read()) != -1) 
       {
       packet_data[packet_index++] = ch;
       }
       last_time = millis();
    } 
    else 
      {
      if ((millis()-last_time) > PACKET_TIMEOUT) 
        {
        if (packet_index == 3)
           {
           WriteToDisplay(packet_data[1],packet_data[2]);
           if((packet_data[1]<10)||(packet_data[2]<10))
              {
              WriteDoubleleDisplay(signs[1],signs[1],signs[1],signs[1],signs[1],signs[1]);
              }
           }
           packet_index = 0;
          }
      }
  }
//-----------------------------------------------------------------------------------------------------------
void WriteToDisplay(int Pulse, int Oxi)  
  {
  int digit1 = Pulse%10;
  int digit2 = (Pulse/10)%10;
  int digit3 = Pulse/100;
  
  int digit4 = Oxi%10;
  int digit5 = (Oxi/10)%10;
  int digit6 = Oxi/100;
  if((Pulse>=100)&&(Oxi>=100))
    {
    WriteDoubleleDisplay(digits[digit1],digits[digit2],digits[digit3],digits[digit4],digits[digit5],digits[digit6]);      
    }
  if((Pulse<100)&&(Oxi>=100))
    {
    WriteDoubleleDisplay(digits[digit1],digits[digit2],signs[0],digits[digit4],digits[digit5],digits[digit6]);      
    }
  if((Pulse<100)&&(Oxi<100))
    {
    WriteDoubleleDisplay(digits[digit1],digits[digit2],signs[0],digits[digit4],digits[digit5],signs[0]);      
    }
  if((Pulse>=100)&&(Oxi<100))
    {
    WriteDoubleleDisplay(digits[digit1],digits[digit2],digits[digit3],digits[digit4],digits[digit5],signs[0]);      
    }            
  }
//-----------------------------------------------------------------------------------------------------------
void WriteDoubleleDisplay(char digit1,char digit2, char digit3, char digit4, char digit5, char digit6)
  {  
  digitalWrite(LATCH, LOW);
  
  shiftOut(DATA, CLK, MSBFIRST, ~digit1); 
  shiftOut(DATA, CLK, MSBFIRST, ~digit2); 
  shiftOut(DATA, CLK, MSBFIRST, ~digit3); 
  shiftOut(DATA, CLK, MSBFIRST, ~digit4); 
  shiftOut(DATA, CLK, MSBFIRST, ~digit5); 
  shiftOut(DATA, CLK, MSBFIRST, ~digit6); 
  digitalWrite(LATCH, HIGH);  
  }

I'm sure it's possible to make the code better/faster but it's working pretty good.

Thanks to everyone that have been replying and helped me. Now I just hope that I can give something back to this community.

/Micael
 
Last edited:
Status
Not open for further replies.
Back
Top