% OptiCAL - Psychtoolbox IOPort interface to the CRS OptiCAL luminance % meter device % % Usage: % >> handle = OptiCAL('Open', port) % >> lum = OptiCAL('Read', handle) % >> OptiCAL('Close', handle) % >> OptiCAL('CloseAll') % % Inputs: % command - string 'Open', 'Read', 'Close', or 'CloseAll' % port - string device (e.g. '/dev/ttyS0') % handle - double handle % % Outputs: % handle - double handle % lum - luminance in cd/m^2 % % Note: % Requires read and write access to the serial port (Ubuntu: add user to % the dialout group). Not yet tested with USB-to-serial adapters. % % References: % [1] Haenel, V. (2010). Pure python interface to CRS OptiCAL. Retrieved % October 18, 2012 from https://github.com/esc/pyoptical. % [2] Cambridge Research Systems. (1995). OptiCAL user's guide v4.02. % Retrieved October 18, 2012 from % http://support.crsltd.com/ics/support/DLRedirect.asp?fileID=63194 % [3] Cambridge Research Systems. (2009). OptiCAL user's guide v4.02 % errata. Retrieved October 18, 2012 from % http://support.crsltd.com/ics/support/DLRedirect.asp?fileID=63194 % % Author: Andreas Widmann, University of Leipzig, 2012 %123456789012345678901234567890123456789012345678901234567890123456789012 % Copyright (C) 2012 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de % % MIT license: % % Permission is hereby granted, free of charge, to any person obtaining a % copy of this software and associated documentation files (the % "Software"), to deal in the Software without restriction, including % without limitation the rights to use, copy, modify, merge, publish, % distribute, sublicense, and/or sell copies of the Software, and to permit % persons to whom the Software is furnished to do so, subject to the % following conditions: % % The above copyright notice and this permission notice shall be included % in all copies or substantial portions of the Software. % % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS % OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF % MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN % NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, % DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR % OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE % USE OR OTHER DEALINGS IN THE SOFTWARE. function [ ret ] = OptiCAL(command, handle) if nargin < 1 error('Not enough input arguments.') end persistent OptiCALCfg; switch command case 'Open' if nargin < 2 error('Not enough input arguments.') end % Open device port = handle; handle = OptiCALOpen(port); % Define configuration structure OptiCALCfg = [OptiCALCfg OptiCALConfiguration(handle)]; ret = handle; case {'Close', 'CloseAll'} if nargin < 2 || isempty(handle) || strcmp(command, 'CloseAll') % Close all devices if used without handle argument or requested CloseAll while ~isempty(OptiCALCfg) OptiCALClose(OptiCALCfg(1).handle); OptiCALCfg(1) = []; end else % Close handle idx = []; if ~isempty(OptiCALCfg) idx = find([OptiCALCfg.handle] == handle); end if isempty(idx) error('Invalid OptiCAL device handle.') end OptiCALClose(OptiCALCfg(idx).handle); OptiCALCfg(idx) = []; end case 'Read' if nargin < 2 || isempty(handle) error('Not enough input arguments.') end idx = []; if ~isempty(OptiCALCfg) idx = find([OptiCALCfg.handle] == handle); end if isempty(idx) error('Invalid OptiCAL device handle.') end % Read raw value adc = OptiCALRead(OptiCALCfg(idx).handle); % Convert to luminance adc_adj = adc - OptiCALCfg(idx).Z_count - 524288; % From OptiCAL manual lum = ((adc_adj / 524288) * OptiCALCfg(idx).V_ref * 1e-6) / (OptiCALCfg(idx).R_feed * OptiCALCfg(idx).K_cal * 1e-15); % From pyoptical! See errata!!! ret = max([0 lum]); otherwise error('Unknown command.') end end function handle = OptiCALOpen(port) % Open serial port OptiCALCfg.port = port; handle = IOPort('OpenSerialPort', OptiCALCfg.port, 'RTS=1'); WaitSecs('YieldSecs', 0.1); % Why needed? Does not reliably work w/o. % Calibrate IOPort('Write', handle, 'C'); data = IOPort('Read', handle, 1, 1); if isempty(data) || data ~= 6 IOPort('Close', handle); error('Could not calibrate OptiCAL device.') end % Set in current Mode IOPort('Write', handle, 'I'); data = IOPort('Read', handle, 1, 1); if isempty(data) || data ~= 6 IOPort('Close', handle); error('Could not set OptiCAL device into current mode.') end end function OptiCALClose(handle) IOPort('Close', handle); end function OptiCALCfg = OptiCALConfiguration(handle) % Defaults OptiCALCfg.handle = handle; % Read configuration OptiCALCfg.prod_type = OptiCALReadEEPROM(handle, 0, 2, 'int'); OptiCALCfg.optical_sn = OptiCALReadEEPROM(handle, 2, 4, 'int'); OptiCALCfg.fw_vers = OptiCALReadEEPROM(handle, 6, 2, 'int'); OptiCALCfg.V_ref = OptiCALReadEEPROM(handle, 16, 4, 'int'); OptiCALCfg.Z_count = OptiCALReadEEPROM(handle, 32, 4, 'int'); OptiCALCfg.R_feed = OptiCALReadEEPROM(handle, 48, 4, 'int'); OptiCALCfg.R_gain = OptiCALReadEEPROM(handle, 64, 4, 'int'); OptiCALCfg.probe_sn = OptiCALReadEEPROM(handle, 80, 16, 'char'); OptiCALCfg.K_cal = OptiCALReadEEPROM(handle, 96, 4, 'int'); end function val = OptiCALReadEEPROM(handle, address, nBytes, toType) offset = 128; % Read field from EEPROM data = zeros(1, nBytes); for iByte = 1:nBytes; IOPort('Write', handle, uint8(offset + address + iByte - 1)); temp = IOPort('Read', handle, 1, 2); data(iByte) = temp(1); end % Cast to type if strcmp(toType, 'char') val = char(data); else val = sum(pow2(0:8:8 * (nBytes - 1)) .* data); end end function adc = OptiCALRead(handle) IOPort('Write', handle, 'L'); % See errata!!! temp = IOPort('Read', handle, 1, 4); adc = sum(pow2([0 8 16]) .* temp(1:3)); end