├── .DS_Store ├── Mac_OSX_VCP_Driver.zip ├── Communication protocol of pulse oximeter V7.0.pdf ├── FixationDistance.m ├── LICENSE ├── CMS60CInputCommand.m ├── README.md ├── CMS60CRealTimeDataDecode.m ├── seriald.m ├── HeartRateUSB_serial.m ├── HeartRateUSB.m~ └── HeartRateUSB.m /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfantLab/Contec-Pulse-Oximeter-in-Matlab/HEAD/.DS_Store -------------------------------------------------------------------------------- /Mac_OSX_VCP_Driver.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfantLab/Contec-Pulse-Oximeter-in-Matlab/HEAD/Mac_OSX_VCP_Driver.zip -------------------------------------------------------------------------------- /Communication protocol of pulse oximeter V7.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InfantLab/Contec-Pulse-Oximeter-in-Matlab/HEAD/Communication protocol of pulse oximeter V7.0.pdf -------------------------------------------------------------------------------- /FixationDistance.m: -------------------------------------------------------------------------------- 1 | function distXY = FixationDistance(x,y, ROICentre) 2 | % 3 | % Pass in 2 equal lenght vectors of x and y coordinates 4 | % and the coordinates of a rectangular region of interest 5 | % Get back the 6 | 7 | x2 = (x-ROICentre(1))*(x-ROICentre(1)); 8 | y2 = (y-ROICentre(2))*(y-ROICentre(2)); 9 | distXY = sqrt(x2 + y2); 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012 Caspar Addyman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /CMS60CInputCommand.m: -------------------------------------------------------------------------------- 1 | function [returnString returnUInt8Array] = CMS60CInputCommand(command) 2 | 3 | 4 | % Basic PC to Pulse Oximeter command string structure 5 | % See PDF for details 6 | returnUInt8Array = uint8(0); %must be uint8 data type 7 | returnUInt8Array(1) = uint8(hex2dec('7D')); % //mark start of command 8 | returnUInt8Array(2) = uint8(hex2dec('81')); % //not sure 9 | returnUInt8Array(3) = uint8(hex2dec('80')); % //command goes here 10 | returnUInt8Array(4) = uint8(hex2dec('80')); % //fill up to 8 bytes 11 | returnUInt8Array(5) = uint8(hex2dec('80')); % 12 | returnUInt8Array(6) = uint8(hex2dec('80')); % 13 | returnUInt8Array(7) = uint8(hex2dec('80')); % 14 | returnUInt8Array(8) = uint8(hex2dec('80')); % 15 | returnUInt8Array(9) = uint8(hex2dec('80')); % 16 | 17 | 18 | switch lower(command) 19 | case {'getrealtimedata','realtime', 'start', 'a1', '161'} 20 | returnString = 'Request real time data (161)'; 21 | returnUInt8Array(3) = uint8(161); 22 | case {'stoprealtimedata','stop', 'a2', '162'} 23 | returnString = 'Stop sending real time data (162)'; 24 | returnUInt8Array(3) = uint8(162); 25 | case {'stopstoragedata', 'stopstore', 'a7', '167'} 26 | returnString = 'Stop sending stored data'; 27 | returnUInt8Array(3) = uint8(167); 28 | case {'deviceidentifiers','devid', 'id', 'aa', '170'} 29 | returnString = 'Ask for device identifiers (170)'; 30 | returnUInt8Array(3) = uint8(170); 31 | case {'realtimepisupport','realtimepi', 'ac', '172'} 32 | returnString = 'Request real time data'; 33 | returnUInt8Array(3) = uint8(172); 34 | case {'stayconnected', 'connected', 'af', '175'} 35 | returnString = 'Stay connected (175)'; 36 | returnUInt8Array(3) = uint8(175); 37 | case {'storageidentifiers', 'storeid', 'b0', '176'} 38 | returnString = 'Storage identifiers (176)'; 39 | returnUInt8Array(3) = uint8(176); 40 | otherwise 41 | returnString = 'Unknown method.'; 42 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a small help file explaining how to get started using the Contec CMS60C (http://contecmed.com/) to capture real time heart rate and pulse oximetry data into Matlab. 2 | 3 | Any questions, please contact 4 | Caspar Addyman 5 | 6 | Model number 7 | ============ 8 | 9 | Be aware that different models in the CMS range run at different baud rates: 10 | - CMS60C runs on a 115200 baud rate, parity no 11 | - CMS50D+ (older) runs on a 19200 baud rate, parity No. 12 | - CMS50d+ (newer) runs on a 115200 baud rate, parity No. 13 | 14 | You will need to comment in/out the appropriate lines in the connection script. 15 | Either 16 | configString on line 142 of HeartRateUSB.m 17 | or 18 | serialObj on Line 117 ins HeartRateUSB_serial.m 19 | 20 | 21 | 22 | Requirements: 23 | ============= 24 | 25 | This code should run in any version of Matlab >R2007 26 | It should also work in Octave >v3.1 but this hasn't been verified 27 | 28 | Matlab ( >R2007) 29 | http://www.mathworks.co.uk/ 30 | 31 | Octave ( >v3.1) 32 | http://www.gnu.org/software/octave/ 33 | 34 | Optional: 35 | ========= 36 | 37 | PsychToolbox version 3.0 38 | http://www.psychtoolbox.org/ 39 | 40 | 41 | Installation: 42 | ============= 43 | 44 | Download the contents of this directory, unzip and install the Mac OSX VCP Driver.zip. The heartrate data is collected by calling the HeartRateUSB.m or the HeartRateUSB_serial.m script. (They are essentially identical but the former uses PsychToolBox IOPort function while the latter uses standard matlab serial commands.) 45 | 46 | 47 | 48 | This code is open source and freely available for you to use in your own projects. 49 | 50 | 51 | Any questions, bugs or comments, please post them on github.com or email them to c.addyman@bbk.ac.uk 52 | 53 | 54 | [![DOI](https://zenodo.org/badge/3891/YourBrain/Contec-Pulse-Oximeter-in-Matlab.svg)](http://dx.doi.org/10.5281/zenodo.16277) 55 | 56 | 57 | *This work was supported in part by a joint grant from the French ANR (ANR-10-056 GETPIMA), and the UK ESRC (RES-062-23-0819) within the France-UK ORA framework.* 58 | 59 | 60 | Alternatives 61 | ============ 62 | 63 | Ian Hands has written a java/SWT app for reading CMS Pulse Oximeters 64 | 65 | Info here 66 | http://ian.ahands.org/progs/pulseox/ 67 | 68 | Code here 69 | https://github.com/iphands/PulseOx 70 | 71 | 72 | -------------------------------------------------------------------------------- /CMS60CRealTimeDataDecode.m: -------------------------------------------------------------------------------- 1 | function [RealTimeData RealTimeArray] = CMS60CRealTimeDataDecode(DataPackage) 2 | % 3 | % decode the data package sent by the CMS60C 4 | % based on the specfication in the communication protocol v7.0 5 | % 6 | % input is an 8 byte message from the pulse oximeter 7 | % outputs 8 | % RealTimeData - a matlab data structure 9 | % RealTimeArray - same data as a matlab array 10 | 11 | RealTimeData.decoded = false; 12 | 13 | if length(DataPackage) ~= 8 14 | RealTimeData.Status = 'DataPackage was not 8 bytes long'; 15 | RealTimeArray = zeros(14,1); 16 | return; 17 | end 18 | 19 | %Byte 1 - not sure - think this is just a marker 20 | 21 | %Byte 2 - monitor status info 22 | RealTimeData.SignalStrength = bitand(DataPackage(2), 15); % bits 0-3 23 | RealTimeData.SearchTimeOut = bitand(DataPackage(2), 16) == 16; % bit 4 24 | RealTimeData.LowSPO2 = bitand(DataPackage(2), 32) == 32 ; % bit 5 25 | RealTimeData.PulseBeep = bitand(DataPackage(2), 64) == 64; % bit 6 26 | RealTimeData.ProbeError = bitand(DataPackage(2), 128) == 128; % bit 7 27 | 28 | %Byte 3 - Pulse Waveform Data 29 | RealTimeData.PulseWaveForm = bitand(DataPackage(3), 127); % bits 0-6 30 | RealTimeData.SearchingForPulse = bitand(DataPackage(3), 128) == 128; % bit 7 31 | 32 | %Byte 4 - Bar Graph Data 33 | RealTimeData.BarGraph = bitand(DataPackage(4), 15); % bits 0-3 34 | RealTimeData.PIInvalid = bitand(DataPackage(4), 16) == 16; % bit 4 35 | 36 | %Byte 5 - Pulse Rate 37 | RealTimeData.PulseRate = bitand(DataPackage(5), 255); % bits 0-7 38 | if RealTimeData.PulseRate > 254 % only valid upto 254 39 | RealTimeData.PulseRate = -1; 40 | elseif RealTimeData.PulseRate > 196 %kludge for weird CMS60S data 41 | RealTimeData.PulseRate = RealTimeData.PulseRate - 128; 42 | end 43 | 44 | %Byte 6 - SPO2 45 | RealTimeData.SPO2 = bitand(DataPackage(6), 127); % bits 0-7 46 | if RealTimeData.SPO2 > 100 %only valid upto 100 47 | RealTimeData.SPO2 = -1; 48 | end 49 | 50 | %Byte 7 - LowPI 51 | RealTimeData.LowPI = bitand(DataPackage(7), 255); % bits 0-7 52 | if RealTimeData.LowPI > 220 %not entirely sure about this one. 53 | RealTimeData.LowPI = -1; 54 | end 55 | 56 | %Byte 8 - HighPI 57 | RealTimeData.HighPI = bitand(DataPackage(8), 255); % bits 0-7 58 | if RealTimeData.HighPI > 220 59 | RealTimeData.HighPI = -1; 60 | end 61 | 62 | RealTimeArray = [ 1 63 | RealTimeData.SignalStrength 64 | RealTimeData.SearchTimeOut 65 | RealTimeData.LowSPO2 66 | RealTimeData.PulseBeep 67 | RealTimeData.ProbeError 68 | RealTimeData.PulseWaveForm 69 | RealTimeData.SearchingForPulse 70 | RealTimeData.BarGraph 71 | RealTimeData.PIInvalid 72 | RealTimeData.PulseRate 73 | RealTimeData.SPO2 74 | RealTimeData.LowPI 75 | RealTimeData.HighPI 76 | ]'; 77 | RealTimeData.decoded = true; 78 | RealTimeData.status = 'Success'; -------------------------------------------------------------------------------- /seriald.m: -------------------------------------------------------------------------------- 1 | function wtf = seriald() 2 | %% Using MATLAB with Serial Devices (such as RS-232) 3 | % MATLAB supports communication with serial devices including RS-232 when 4 | % using the Instrument Control Toolbox. There are many devices with serial 5 | % interfaces, including gas chronometers, mass spectrometers, 6 | % imaging devices, pulse oximeters, and instruments. 7 | % 8 | % The toolbox provides a graphical tool that allows you to configure, 9 | % control, and acquire data from your serial device without writing code. 10 | % The tool automatically generates MATLAB code that allows you to reuse 11 | % your work. The code example below was automatically generated by the 12 | % tool. Type "tmtool" at the MATLAB command line to launch this tool. 13 | % 14 | % This code example below shows you how you can communicate with your 15 | % serial device using MATLAB. The "*IDN?" command was used which is a 16 | % typical command for communicating with an instrument. The commands you 17 | % can use will depend on what your serial device supports. 18 | 19 | %% Automatically generating a report in MATLAB 20 | % Press the "Save and Publish to HTML" button in the MATLAB Editor to 21 | % execute this code example and automatically generate a report of your 22 | % work with the serial device. 23 | 24 | %% Automatically generating MATLAB script for your RS-232 device 25 | % To automatically create your own MATLAB script, launch "tmtool". Open the 26 | % "Hardware" node, open the "Serial" node, select your serial port (such 27 | % as COM1, press the "connect" button. Once connected, enter your device 28 | % commands in the right pane, press "Session log" to see the code generated, 29 | % and press "Save Session" to save the code to a MATLAB (.m) file. 30 | 31 | %% MATLAB script automatically generated for the RS-232 device 32 | % The following MATLAB script was automatically generated by interacting 33 | % with the device configuration tool provided by the toolbox. 34 | 35 | % Creation time: 03-Oct-2006 20:36:43 36 | 37 | port='/dev/tty.SLAB_USBtoUART'; 38 | 39 | % Create a serial port object. 40 | obj1 = instrfind('Type', 'serial', 'Port', port, 'Tag', ''); 41 | 42 | 43 | 44 | % Create the serial port object if it does not exist 45 | % otherwise use the object that was found. 46 | if isempty(obj1) 47 | obj1 = serial(port, 'BaudRate',115200); 48 | else 49 | fclose(obj1); 50 | obj1 = obj1(1) 51 | end 52 | 53 | % Connect to instrument object, obj1. 54 | fopen(obj1); 55 | %fseek(obj1,0,1); 56 | 57 | fid = fopen('capture.txt','w'); 58 | 59 | buffer = ones(500, 8); 60 | 61 | % Communicating with instrument object, obj1. 62 | %data1 = query(obj1, '*IDN?'); 63 | %figure; 64 | % hold; 65 | if(obj1.BytesAvailable > 0) 66 | fread(obj1, obj1.BytesAvailable); 67 | end 68 | 69 | while 1 70 | %data1 = fgets(obj1); 71 | if(obj1.BytesAvailable > 0) %clear out buffer 72 | null = fread(obj1, obj1.BytesAvailable); 73 | end 74 | 75 | data1=0; 76 | 77 | while(data1 < 8) 78 | data1 = fread(obj1, 20); %integer 79 | end 80 | 81 | %data1 = fscanf(obj1,'%s'); 82 | if data1 > 0 83 | while ~isempty(data1) 84 | if length(data1) <= 2 85 | break 86 | end 87 | 88 | if data1(1) == hex2dec('AA') && data1(2) == hex2dec('BB') 89 | % disp('data match'); 90 | data1 = data1(3:end-2); 91 | if length(data1) >= 8 92 | 93 | fprintf(fid, '%d, ' ,data1(1:7)); 94 | fprintf(fid, '%d' , data1(8)); 95 | fprintf(fid, '\n'); 96 | 97 | % plot([1:1:8], data1(1:8)); 98 | %stem([1:1:8],data1(1:8)) 99 | data1(1:8); 100 | buffer=buffer([2:end 1], :); 101 | buffer(end, :) = 3.3*data1(1:8)/256; 102 | pause(0.0002); 103 | 104 | for k = 1:8 105 | subplot(8,1,k) 106 | axis([1 length(buffer) 0 3.3]) 107 | plot(1:1:length(buffer), buffer(1:end, k)) 108 | end 109 | %null = fread(obj1, 512); %integer 110 | 111 | data1 = 0; 112 | 113 | break; 114 | end 115 | 116 | end 117 | data1 = data1([2:end]); 118 | end 119 | % data1 120 | %fprintf(fid,'%d, ' ,data1); 121 | % fprintf(fid,'\n'); 122 | end 123 | 124 | end 125 | % Disconnect from instrument object, obj1. 126 | fclose(obj1); 127 | 128 | fclose(fid); 129 | % Clean up all objects. 130 | delete(obj1); -------------------------------------------------------------------------------- /HeartRateUSB_serial.m: -------------------------------------------------------------------------------- 1 | function ret = HeartRateUSB_serial(command) 2 | % 3 | % Attempting to talk to the contec.com.cn CMS60C Pulse Oximeter over USB. 4 | % Caspar Addyman Oct 2011 5 | % 6 | % CMS60C Communication Protocol in the pdf file in this folder. 7 | % ./Communication protocol of pulse oximeter V7.0.pdf 8 | % 9 | % Note: You must first have installed the SiLabs 10 | % USB to UART Bridge Virtual Comm Port (VCP) Drivers 11 | % 12 | % http://www.silabs.com/products/mcu/Pages/USBtoUARTBridgeVCPDrivers.aspx 13 | % 14 | % and PsychoToolBox in order to use the IOPORT function 15 | % 16 | % http://psychtoolbox.org/HomePage 17 | % http://docs.psychtoolbox.org/IOPort 18 | % 19 | % v0.1 - alpha version - just about works (8/11/2011) 20 | 21 | global serialPortOpen; 22 | global serialObj; 23 | global PCtoMonitorBytes; 24 | global port; 25 | 26 | port='/dev/cu.SLAB_USBtoUART'; 27 | 28 | if (nargin < 1) 29 | command = 'livedata'; 30 | end 31 | 32 | switch lower (command) 33 | case 'connect' 34 | ret = connect(); 35 | case 'livedata' 36 | ret = livedata(); 37 | case 'flush' 38 | %clear queued data 39 | %not implemented yet 40 | case 'close' 41 | close(); 42 | otherwise 43 | connect(); 44 | ret = livedata(); 45 | end 46 | end 47 | 48 | function stayconnected() 49 | %need to send stillconnected command at least once every 5 seconds 50 | %can send it more often 51 | 52 | global lastConnected 53 | global serialPortOpen 54 | global serialObj 55 | 56 | if isempty(serialPortOpen) || isempty(lastConnected) 57 | disp('stay connected - reconnect'); 58 | connect(); 59 | end 60 | 61 | if toc(lastConnected) < now - 3 62 | %inform device we are connected 63 | [cmdstr cmdarray] = CMS60CInputCommand('connected'); 64 | % [nwritten, when, errmsg, prewritetime, postwritetime, lastchecktime] = IOPort('Write', monitorIO, cmdarray); 65 | fwrite(serialObj ,cmdarray); 66 | lastConnected = tic; 67 | end 68 | end 69 | 70 | function ret = livedata() 71 | % return livedata (this can build up in queue if request not made often enough) 72 | 73 | global serialPortOpen 74 | global serialObj 75 | 76 | stayconnected(); 77 | %[data, when, errmsg] = IOPort('Read', monitorIO); 78 | data = fread(serialObj, serialObj.BytesAvailable); 79 | 80 | ALLPULSEDATA = []; 81 | NData = length(data); 82 | datablockindices = find(data==1); 83 | NBlocks = length(datablockindices); 84 | for j = 1:NBlocks 85 | % DATA:8 bytes in 1 package, ~60 packages/second 86 | if datablockindices(j)+8 <= NData 87 | packet = data(datablockindices(j)+1:datablockindices(j)+8); 88 | [PulseObj PulseArray] = CMS60CRealTimeDataDecode(packet); 89 | ALLPULSEDATA = [ ALLPULSEDATA ; PulseArray]; 90 | end 91 | end 92 | ret = ALLPULSEDATA; 93 | return; 94 | end 95 | 96 | 97 | function ret = connect() 98 | 99 | global serialPortOpen 100 | global serialObj 101 | global lastConnected 102 | global port 103 | 104 | try 105 | disp(['**** connecting to CMS60C Heart Rate monitor *****']); 106 | disp(['**** on port - ' port ' ****']); 107 | 108 | port='/dev/tty.SLAB_USBtoUART'; 109 | 110 | % Create a serial port object. 111 | serialObj = instrfind('Type', 'serial', 'Port', port, 'Tag', ''); 112 | 113 | % Create the serial port object if it does not exist 114 | % otherwise use the object that was found. 115 | if isempty(serialObj) 116 | %Note that different CMS models connect at different baud rates. You may need to edit the following lines 117 | serialObj = serial(port, 'BaudRate',115200); % CMS60C 118 | % serialObj = serial(port, 'BaudRate',19200); % CMS50D 119 | else 120 | fclose(serialObj); 121 | serialObj = serialObj(1); 122 | end 123 | % Data Format: 1 Start bit + 8 data bits + 1 stop bit, odd; 124 | % Baud Rate: 115200 125 | set(serialObj,'DataBits',8,'StopBits',1,'Parity','none'); 126 | set(serialObj,'OutputBufferSize',512,'InputBufferSize',1024,'RequestToSend','off','DataTerminalReady','on'); 127 | fopen(serialObj); 128 | if ~strcmpi(serialObj.Status, 'open') 129 | error('Error-connecting to heartrate monitor over virtual comm port -- not found...this feature will be disabled'); 130 | end 131 | 132 | if(serialObj.BytesAvailable > 0) %clear out buffer 133 | null = fread(serialObj, serialObj.BytesAvailable); 134 | end 135 | 136 | 137 | %the second sequence command codes sent by the PC software in byte 3 138 | %these are sent indiviually and a response read for each one 139 | Command{1} = 'stopstore'; % stop sending stored data 140 | Command{2} = 'stopreal'; % stop sending real time data 141 | Command{3} = 'storeid'; % ask for storage indentifiers 142 | Command{4} = 'realtimepi'; % ask for for PI realtime support 143 | Command{5} = 'devid'; % ask for device identifiers 144 | Command{6} = 'realtime'; % ask for real time data 145 | 146 | 147 | %send these commands individually 148 | for i = 1:6 149 | [cmdstr cmdarray] = CMS60CInputCommand(Command{i}); 150 | disp(['*** startup command: ' cmdstr]); 151 | % [nwritten, when, errmsg, prewritetime, postwritetime, lastchecktime] = IOPort('Write', monitorIO, cmdarray); 152 | fwrite(serialObj ,cmdarray); 153 | WaitSecs(0.05); 154 | navailable = serialObj.BytesAvailable; 155 | data = fread(serialObj, serialObj.BytesAvailable); 156 | disp('returns '); 157 | disp(num2str(data')); 158 | end 159 | serialPortOpen = true; 160 | lastConnected = tic; 161 | ret = true; 162 | catch exception 163 | disp('failed to connect ') 164 | disp(exception.message) 165 | serialPortOpen = []; 166 | lastConnected = tic - 20; 167 | fclose(serialObj); 168 | ret = false; 169 | end 170 | end 171 | 172 | function close() 173 | global serialObj 174 | % Disconnect from instrument object, obj1. 175 | fclose(serialObj); 176 | % Clean up all objects. 177 | delete(serialObj); 178 | end 179 | -------------------------------------------------------------------------------- /HeartRateUSB.m~: -------------------------------------------------------------------------------- 1 | function ret = HeartRateUSB(command) 2 | % 3 | % Attempting to talk to the contec.com.cn CMS60C Pulse Oximeter over USB. 4 | % Caspar Addyman Oct 2011 5 | % 6 | % CMS60C Communication Protocol in the pdf file in this folder. 7 | % ./Communication protocol of pulse oximeter V7.0.pdf 8 | % 9 | % Note: You must first have installed the SiLabs 10 | % USB to UART Bridge Virtual Comm Port (VCP) Drivers 11 | % 12 | % http://www.silabs.com/products/mcu/Pages/USBtoUARTBridgeVCPDrivers.aspx 13 | % 14 | % and PsychoToolBox in order to use the IOPORT function 15 | % 16 | % http://psychtoolbox.org/HomePage 17 | % http://docs.psychtoolbox.org/IOPort 18 | % 19 | % v0.1 - alpha version - just about works (8/11/2011) 20 | 21 | global ioPortOpen; 22 | global monitorIO; 23 | global PCtoMonitorBytes; 24 | global port; 25 | 26 | port='/dev/cu.SLAB_USBtoUART'; 27 | 28 | if (nargin < 1) 29 | command = 'livedata'; 30 | end 31 | 32 | switch lower (command) 33 | case 'connect' 34 | ret = connect(); 35 | case 'livedata' 36 | ret = livedata(); 37 | case 'flush' 38 | %clear queued data 39 | %not implemented yet 40 | case 'close' 41 | close(); 42 | otherwise 43 | connect(); 44 | ret = livedata(); 45 | end 46 | end 47 | 48 | function stayconnected() 49 | %need to send stillconnected command at least once every 5 seconds 50 | %can send it more often 51 | 52 | global lastConnected 53 | global ioPortOpen 54 | global monitorIO 55 | 56 | if isempty(ioPortOpen) || isempty(lastConnected) 57 | disp('stay connected - reconnect'); 58 | connect(); 59 | end 60 | 61 | if toc(lastConnected) < now - 3 62 | %inform device we are connected 63 | [cmdstr cmdarray] = CMS60CInputCommand('connected'); 64 | [nwritten, when, errmsg, prewritetime, postwritetime, lastchecktime] = IOPort('Write', monitorIO, cmdarray); 65 | lastConnected = tic; 66 | end 67 | end 68 | 69 | function ret = livedata() 70 | % return livedata (this can build up in queue if request not made often enough) 71 | 72 | global ioPortOpen 73 | global monitorIO 74 | 75 | stayconnected(); 76 | [data, when, errmsg] = IOPort('Read', monitorIO); 77 | 78 | ALLPULSEDATA = []; 79 | NData = length(data); 80 | datablockindices = find(data==1); 81 | NBlocks = length(datablockindices); 82 | for j = 1:NBlocks 83 | % DATA:8 bytes in 1 package, ~60 packages/second 84 | if datablockindices(j)+8 <= NData 85 | packet = data(datablockindices(j)+1:datablockindices(j)+8); 86 | [PulseObj PulseArray] = CMS60CRealTimeDataDecode(packet); 87 | ALLPULSEDATA = [ ALLPULSEDATA ; PulseArray]; 88 | end 89 | end 90 | ret = ALLPULSEDATA; 91 | return; 92 | end 93 | 94 | % 95 | % for n = 1:120 96 | % WaitSecs(1/60); 97 | % if mod(n,60) == 0 98 | % [nwritten, when, errmsg, prewritetime, postwritetime, lastchecktime] = IOPort('Write', monitorIO, StillConnected, blocking); 99 | % end 100 | % navailable = IOPort('BytesAvailable', monitorIO); 101 | % [data, when, errmsg] = IOPort('Read', monitorIO); 102 | % NData = length(data); 103 | % datablockindices = find(data==1); 104 | % NBlocks = length(datablockindices); 105 | % 106 | % % DATA:8 bytes in 1 package, ~60 packages/second 107 | % 108 | % 109 | % for j = 1:NBlocks 110 | % if datablockindices(j)+8 <= NData 111 | % packet = data(datablockindices(j)+1:datablockindices(j)+8); 112 | % [PulseObj PulseArray] = CMS60CRealTimeDataDecode(packet); 113 | % ALLPULSEDATA = [ ALLPULSEDATA ; PulseArray]; 114 | % end 115 | % end 116 | % end 117 | % 118 | % 119 | % ret = ALLPULSEDATA; 120 | % 121 | 122 | 123 | 124 | function ret = connect() 125 | 126 | global ioPortOpen 127 | global monitorIO 128 | global lastConnected 129 | global port 130 | 131 | try 132 | IOPort('CloseAll'); 133 | disp(['**** connecting to CMS60C Heart Rate monitor *****']); 134 | disp(['**** on port - ' port ' ****']); 135 | 136 | 137 | % Data Format: 1 Start bit + 8 data bits + 1 stop bit, odd; 138 | % Baud Rate: 115200 139 | configString = 'ReceiverEnable=1 BaudRate=115200 StartBits=1 DataBits=8 StopBits=1 Parity=No OutputBufferSize=512 InputBufferSize=1024 RTS=0 DTR=1'; 140 | [monitorIO, errmsg] = IOPort('OpenSerialPort', port, configString); 141 | if monitorIO < 0 142 | error('Error-connecting to heartrate monitor over virtual comm port -- not found...this feature will be disabled'); 143 | end 144 | 145 | 146 | IOPort('Flush', monitorIO); %flush data queued to send to device 147 | IOPort('Purge', monitorIO); %clear existing data queues. 148 | 149 | 150 | 151 | %the second sequence command codes sent by the PC software in byte 3 152 | %these are sent indiviually and a response read for each one 153 | Command{1} = 'stopstore'; % stop sending stored data 154 | Command{2} = 'stopreal'; % stop sending real time data 155 | Command{3} = 'storeid'; % ask for storage indentifiers 156 | Command{4} = 'realtimepi'; % ask for for PI realtime support 157 | Command{5} = 'devid'; % ask for device identifiers 158 | Command{6} = 'realtime'; % ask for real time data 159 | 160 | 161 | % Command(1) = uint8(hex2dec('A7')); % stop sending stored data 162 | % Command(2) = uint8(hex2dec('A2')); % stop sending real time data 163 | % Command(3) = uint8(hex2dec('A0')); % 164 | % Command(4) = uint8(hex2dec('B0')); % ask for storage indentifiers 165 | % Command(5) = uint8(hex2dec('AC')); % ask for storage indentifiers 166 | % Command(6) = uint8(hex2dec('B3')); % unknown 167 | % Command(7) = uint8(hex2dec('A8')); % unknown 168 | % Command(8) = uint8(hex2dec('AA')); % ask for device identifiers 169 | % Command(9) = uint8(hex2dec('A9')); % unknown 170 | % Command(10) = uint8(hex2dec('A1')); % ask for real time data 171 | 172 | %send these commands individually 173 | for i = 1:6 174 | [cmdstr cmdarray] = CMS60CInputCommand(Command{i}); 175 | disp(['*** startup command: ' cmdstr]); 176 | [nwritten, when, errmsg, prewritetime, postwritetime, lastchecktime] = IOPort('Write', monitorIO, cmdarray); 177 | WaitSecs(0.05); 178 | navailable = IOPort('BytesAvailable', monitorIO); 179 | [data, when, errmsg] = IOPort('Read', monitorIO); 180 | disp(['returns ' num2str(data)]); 181 | end 182 | ioPortOpen = true; 183 | lastConnected = tic; 184 | ret = true; 185 | catch exception 186 | disp(exception.message); 187 | ioPortOpen = []; 188 | lastConnected = tic - 20; 189 | IOPort('CloseAll'); 190 | ret = false; 191 | end 192 | end 193 | 194 | function close() 195 | IOPort('CloseAll'); 196 | end -------------------------------------------------------------------------------- /HeartRateUSB.m: -------------------------------------------------------------------------------- 1 | function ret = HeartRateUSB(command) 2 | % 3 | % Attempting to talk to the contec.com.cn CMS60C Pulse Oximeter over USB. 4 | % Caspar Addyman Oct 2011 5 | % 6 | % CMS60C Communication Protocol in the pdf file in this folder. 7 | % ./Communication protocol of pulse oximeter V7.0.pdf 8 | % 9 | % Note: You must first have installed the SiLabs 10 | % USB to UART Bridge Virtual Comm Port (VCP) Drivers 11 | % 12 | % http://www.silabs.com/products/mcu/Pages/USBtoUARTBridgeVCPDrivers.aspx 13 | % 14 | % and PsychoToolBox in order to use the IOPORT function 15 | % 16 | % http://psychtoolbox.org/HomePage 17 | % http://docs.psychtoolbox.org/IOPort 18 | % 19 | % v0.1 - alpha version - just about works (8/11/2011) 20 | 21 | global ioPortOpen; 22 | global monitorIO; 23 | global PCtoMonitorBytes; 24 | global port; 25 | 26 | port='/dev/cu.SLAB_USBtoUART'; 27 | 28 | if (nargin < 1) 29 | command = 'livedata'; 30 | end 31 | 32 | switch lower (command) 33 | case 'connect' 34 | ret = connect(); 35 | case 'livedata' 36 | ret = livedata(); 37 | case 'flush' 38 | %clear queued data 39 | %not implemented yet 40 | case 'close' 41 | close(); 42 | otherwise 43 | connect(); 44 | ret = livedata(); 45 | end 46 | end 47 | 48 | function stayconnected() 49 | %need to send stillconnected command at least once every 5 seconds 50 | %can send it more often 51 | 52 | global lastConnected 53 | global ioPortOpen 54 | global monitorIO 55 | 56 | if isempty(ioPortOpen) || isempty(lastConnected) 57 | disp('stay connected - reconnect'); 58 | connect(); 59 | end 60 | 61 | if toc(lastConnected) < now - 3 62 | %inform device we are connected 63 | [cmdstr cmdarray] = CMS60CInputCommand('connected'); 64 | [nwritten, when, errmsg, prewritetime, postwritetime, lastchecktime] = IOPort('Write', monitorIO, cmdarray); 65 | lastConnected = tic; 66 | end 67 | end 68 | 69 | function ret = livedata() 70 | % return livedata (this can build up in queue if request not made often enough) 71 | 72 | global ioPortOpen 73 | global monitorIO 74 | 75 | stayconnected(); 76 | [data, when, errmsg] = IOPort('Read', monitorIO); 77 | 78 | ALLPULSEDATA = []; 79 | NData = length(data); 80 | datablockindices = find(data==1); 81 | NBlocks = length(datablockindices); 82 | for j = 1:NBlocks 83 | % DATA:8 bytes in 1 package, ~60 packages/second 84 | if datablockindices(j)+8 <= NData 85 | packet = data(datablockindices(j)+1:datablockindices(j)+8); 86 | [PulseObj PulseArray] = CMS60CRealTimeDataDecode(packet); 87 | ALLPULSEDATA = [ ALLPULSEDATA ; PulseArray]; 88 | end 89 | end 90 | ret = ALLPULSEDATA; 91 | return; 92 | end 93 | 94 | % 95 | % for n = 1:120 96 | % WaitSecs(1/60); 97 | % if mod(n,60) == 0 98 | % [nwritten, when, errmsg, prewritetime, postwritetime, lastchecktime] = IOPort('Write', monitorIO, StillConnected, blocking); 99 | % end 100 | % navailable = IOPort('BytesAvailable', monitorIO); 101 | % [data, when, errmsg] = IOPort('Read', monitorIO); 102 | % NData = length(data); 103 | % datablockindices = find(data==1); 104 | % NBlocks = length(datablockindices); 105 | % 106 | % % DATA:8 bytes in 1 package, ~60 packages/second 107 | % 108 | % 109 | % for j = 1:NBlocks 110 | % if datablockindices(j)+8 <= NData 111 | % packet = data(datablockindices(j)+1:datablockindices(j)+8); 112 | % [PulseObj PulseArray] = CMS60CRealTimeDataDecode(packet); 113 | % ALLPULSEDATA = [ ALLPULSEDATA ; PulseArray]; 114 | % end 115 | % end 116 | % end 117 | % 118 | % 119 | % ret = ALLPULSEDATA; 120 | % 121 | 122 | 123 | 124 | function ret = connect() 125 | 126 | global ioPortOpen 127 | global monitorIO 128 | global lastConnected 129 | global port 130 | 131 | try 132 | IOPort('CloseAll'); 133 | disp(['**** connecting to CMS60C Heart Rate monitor *****']); 134 | disp(['**** on port - ' port ' ****']); 135 | 136 | 137 | % Data Format: 1 Start bit + 8 data bits + 1 stop bit, odd; 138 | % NOTE DIFFERENT CMS Models use different baud rates you may have to edit the following lines 139 | % % Baud Rate: 19200 (CMS50D) 140 | % configString = 'ReceiverEnable=1 BaudRate=19200 StartBits=1 DataBits=8 StopBits=1 Parity=No OutputBufferSize=512 InputBufferSize=1024 RTS=0 DTR=1'; 141 | % Baud Rate: 115200 (CMS60C) 142 | configString = 'ReceiverEnable=1 BaudRate=115200 StartBits=1 DataBits=8 StopBits=1 Parity=No OutputBufferSize=512 InputBufferSize=1024 RTS=0 DTR=1'; 143 | [monitorIO, errmsg] = IOPort('OpenSerialPort', port, configString); 144 | if monitorIO < 0 145 | error('Error-connecting to heartrate monitor over virtual comm port -- not found...this feature will be disabled'); 146 | end 147 | 148 | 149 | IOPort('Flush', monitorIO); %flush data queued to send to device 150 | IOPort('Purge', monitorIO); %clear existing data queues. 151 | 152 | 153 | 154 | %the second sequence command codes sent by the PC software in byte 3 155 | %these are sent indiviually and a response read for each one 156 | Command{1} = 'stopstore'; % stop sending stored data 157 | Command{2} = 'stopreal'; % stop sending real time data 158 | Command{3} = 'storeid'; % ask for storage indentifiers 159 | Command{4} = 'realtimepi'; % ask for for PI realtime support 160 | Command{5} = 'devid'; % ask for device identifiers 161 | Command{6} = 'realtime'; % ask for real time data 162 | 163 | 164 | % Command(1) = uint8(hex2dec('A7')); % stop sending stored data 165 | % Command(2) = uint8(hex2dec('A2')); % stop sending real time data 166 | % Command(3) = uint8(hex2dec('A0')); % 167 | % Command(4) = uint8(hex2dec('B0')); % ask for storage indentifiers 168 | % Command(5) = uint8(hex2dec('AC')); % ask for storage indentifiers 169 | % Command(6) = uint8(hex2dec('B3')); % unknown 170 | % Command(7) = uint8(hex2dec('A8')); % unknown 171 | % Command(8) = uint8(hex2dec('AA')); % ask for device identifiers 172 | % Command(9) = uint8(hex2dec('A9')); % unknown 173 | % Command(10) = uint8(hex2dec('A1')); % ask for real time data 174 | 175 | %send these commands individually 176 | for i = 1:6 177 | [cmdstr cmdarray] = CMS60CInputCommand(Command{i}); 178 | disp(['*** startup command: ' cmdstr]); 179 | [nwritten, when, errmsg, prewritetime, postwritetime, lastchecktime] = IOPort('Write', monitorIO, cmdarray); 180 | WaitSecs(0.05); 181 | navailable = IOPort('BytesAvailable', monitorIO); 182 | [data, when, errmsg] = IOPort('Read', monitorIO); 183 | disp(['returns ' num2str(data)]); 184 | end 185 | ioPortOpen = true; 186 | lastConnected = tic; 187 | ret = true; 188 | catch exception 189 | disp(exception.message); 190 | ioPortOpen = []; 191 | lastConnected = uint64(0); 192 | IOPort('CloseAll'); 193 | ret = false; 194 | end 195 | end 196 | 197 | function close() 198 | IOPort('CloseAll'); 199 | end 200 | --------------------------------------------------------------------------------