{"id":864,"date":"2017-02-20T07:36:54","date_gmt":"2017-02-20T07:36:54","guid":{"rendered":"http:\/\/www.thinkering.de\/cms\/?p=864"},"modified":"2017-02-23T08:12:58","modified_gmt":"2017-02-23T08:12:58","slug":"pulse-oximeter-hack","status":"publish","type":"post","link":"http:\/\/www.thinkering.de\/cms\/?p=864","title":{"rendered":"Pulse Oximeter Hack"},"content":{"rendered":"<p>The pulse oximeter is a very useful and affordable medical gadget. It incorporates a quick and easy way of measuring your heart rate as well as calculating the blood oxygen saturation in one go! After having a look inside the Contec CMS50C and CMS50D which are both MSP430-based devices with OLED displays, it quickly became apparent, which one is easier to hack to extract the data.<br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\nThe CMS50C (the one with the color OLED, available for <15$ on the usual platforms) has a nice line of well accessible and labeled pads, among which is also a UART interface that is streaming all of the useful values such as 7bit photoplethismographic curve data, the \"bar graph\", the SPO2 and heart rate value, a \"beat detection bit\" and various diagnostic bits at a sampling rate of 60Hz. \nAn overview over the entire communication protocol can be found <a href=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/Communication-protocol-pulseox.pdf\">here.<\/a> The serial port settings are a bit unusual: 4800 baud, and EVEN parity, so watch out for that, but other than that it&#8217;s a quite clever protocol that is storing all this information in only 5 bytes.<br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<a href=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132139.jpg\"><img loading=\"lazy\" src=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132139-300x169.jpg\" alt=\"\" width=\"300\" height=\"169\" class=\"alignnone size-medium wp-image-868\" srcset=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132139-300x169.jpg 300w, http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132139-768x432.jpg 768w, http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132139-1024x576.jpg 1024w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<a href=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132501.jpg\"><img loading=\"lazy\" src=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132501-300x169.jpg\" alt=\"\" width=\"300\" height=\"169\" class=\"alignnone size-medium wp-image-866\" srcset=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132501-300x169.jpg 300w, http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132501-768x432.jpg 768w, http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_132501-1024x576.jpg 1024w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<a href=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_140415.jpg\"><img loading=\"lazy\" src=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_140415-300x169.jpg\" alt=\"\" width=\"300\" height=\"169\" class=\"alignnone size-medium wp-image-867\" srcset=\"http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_140415-300x169.jpg 300w, http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_140415-768x432.jpg 768w, http:\/\/www.thinkering.de\/cms\/wp-content\/uploads\/2017\/02\/20170127_140415-1024x576.jpg 1024w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\nThe most essential connections are GND, TX, RST, the right pin of the trigger pushbutton and Batt. After that you should be able to completely control the thing from a microcontroller and harvest the data.<br \/>\nA code sample for Teensy 3.x is found below:<br \/>\n<br style=\u201dclear:both;\u201d \/><br \/>\n<br style=\u201dclear:both;\u201d \/><\/p>\n<pre class=\"brush: cpp; collapse: true; light: false; title: ; toolbar: true; notranslate\" title=\"\">\r\n\r\n\/\/sketch for reading the CMS50C pulse oximeter data using a Teensy 3.x\r\n\r\n#define TRIG_PULSEOX 3 \/\/ has to be pulled to 3.3V to start the CMS50C\r\n#define RST_PULSEOX 15\r\n\/\/vars for pulseOx\r\nchar incomingPulseOxbyte;\r\nunsigned int byteCntPulse = 0;\r\nchar pulseCurve_PulseOx = 0;\r\nchar SPO2;\r\nchar HR_PulseOx;\r\nboolean lastBitPulseOx = 0; \/\/the MSB of the heart rate byte is hidden in a different byte - the one that holds the bar graph data\r\nunsigned long pulseOxTrigTmr=0;\r\nunsigned long pulseOxTrigInt=600000; \/\/ length of the pulse that triggers the CMS50C\r\nunsigned long pulseOxRstTmr=0;\r\nunsigned long pulseOxRstInt=800000;\r\n\r\nboolean triggeredOx = false;\r\nboolean resettedOx = false;\r\n\r\n\/\/packet management bytes for the Amarino Android app\r\nchar startFlag = 18;\r\nchar ack = 19;\r\nchar delimiter = 59; \/\/';' in case we use more than 1 channel\r\n\r\n\r\nvoid setup() {\r\n  \/\/ put your setup code here, to run once:\r\n  Serial1.begin(115200); \/\/ Bluetooth port\r\n  Serial3.begin(4800, SERIAL_8E1); \/\/ Pulse oximeter CONTEC CMS 50C, annoying fact: it has EVEN PARITY!\r\n\r\n   \/\/ make the pins outputs:\r\n  pinMode(RST_PULSEOX, OUTPUT);\r\n  pinMode(TRIG_PULSEOX, OUTPUT);\r\n  \r\n  digitalWrite(RST_PULSEOX, HIGH);\r\n  \r\n  \r\n  triggerOx(); \/\/turn on the PulseOx\r\n\r\n}\r\n\r\nvoid loop() {\r\n\r\n  \r\n  \/\/ put your main code here, to run repeatedly:\r\n   \/\/waiting to pull TRIG_PULSEOX LOW again \r\n  if((micros()-pulseOxTrigTmr)&gt;pulseOxTrigInt &amp;&amp; triggeredOx == true){\r\n    digitalWrite(TRIG_PULSEOX, LOW);\r\n    triggeredOx=false;\r\n  }\r\n  \r\n\r\n   readPulseOx();\r\n\r\n}\r\n\r\nvoid readPulseOx() {\r\n \r\n  \/\/--------------------Pulse Oximeter data analysis----------------\r\n  if (Serial3.available() &gt; 0) {\r\n    \/\/pulseox data\r\n    incomingPulseOxbyte = Serial3.read();\r\n    if (byteCntPulse == 1) {\r\n      \/\/the second byte contains a 7 bit pulse wave value, Fs=60Hz\r\n      pulseCurve_PulseOx = incomingPulseOxbyte;\r\n      byteCntPulse++;\r\n    } else if (byteCntPulse == 2) {\r\n      \/\/the 0-3rd bits of the third byte is the bar graph value .\r\n      \/\/the 6th bit is bit 7 of the heart rate value!\r\n      if ( bitRead(incomingPulseOxbyte, 6)) {\r\n        \/\/if the heart rate is &gt;= 128, this happens\r\n        lastBitPulseOx = 1;\r\n      } else\r\n        lastBitPulseOx = 0;\r\n\r\n      byteCntPulse++; \/\/move on\r\n    } else if (byteCntPulse == 3) {\r\n      \/\/\r\n      if (lastBitPulseOx)\r\n        \/\/if the heart rate is &gt;= 128, this happens\r\n        HR_PulseOx = 128 + incomingPulseOxbyte;\r\n      else\r\n        HR_PulseOx = incomingPulseOxbyte;\r\n\r\n      byteCntPulse++;\r\n    } else if (byteCntPulse == 4) {\r\n      \/\/the last byte contains the most important SPO2 value\r\n      SPO2 = incomingPulseOxbyte;\r\n      byteCntPulse = 0; \/\/we're done the data packet has 5 bytes in total\r\n    } else {\r\n\r\n\r\n      Serial1.print(startFlag);\r\n      Serial1.print(&quot;P&quot;);\r\n      Serial1.print(delimiter);\r\n      Serial1.print(pulseCurve_PulseOx, DEC); \/\/send the pulse curve value\r\n      Serial1.print(delimiter);\r\n      Serial1.print(HR_PulseOx, DEC); \/\/send the heart rate\r\n      Serial1.print(delimiter);\r\n      Serial1.print(SPO2, DEC); \/\/send SPO2 value!\r\n      Serial1.print(ack);\r\n\r\n    }\r\n\r\n    \/\/analyze the first byte. it has plenty of information about the data\r\n    \/\/the fourth bit of the first packet byte means &quot;OK signal&quot; if zero, otherwise it stands for &quot;searching too long&quot;\r\n    \/\/the seventh bit of the first byte has to be always set!!!!! (the only byte where this is the case) all other bytes have a bit 7 == 0\r\n    if ( bitRead(incomingPulseOxbyte, 7)) {\r\n      byteCntPulse++;\r\n    }\r\n  }\r\n\r\n}\r\n\r\nvoid triggerOx(){\r\n\/\/function to trigger the pulse oximeter button\r\n   digitalWrite(TRIG_PULSEOX,HIGH);\r\n   pulseOxTrigTmr=micros();\r\n   triggeredOx=true;\r\n }\r\n\r\n\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>The pulse oximeter is a very useful and affordable medical gadget. It incorporates a quick and easy way of measuring your heart rate as well as calculating the blood oxygen saturation in one go! After having a look inside the Contec CMS50C and CMS50D which are both MSP430-based devices with OLED displays, it quickly became &hellip; <a href=\"http:\/\/www.thinkering.de\/cms\/?p=864\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Pulse Oximeter Hack<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0},"categories":[1,10],"tags":[],"_links":{"self":[{"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=\/wp\/v2\/posts\/864"}],"collection":[{"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=864"}],"version-history":[{"count":9,"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=\/wp\/v2\/posts\/864\/revisions"}],"predecessor-version":[{"id":970,"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=\/wp\/v2\/posts\/864\/revisions\/970"}],"wp:attachment":[{"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=864"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=864"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.thinkering.de\/cms\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=864"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}