classdef ExperimentBoard < handle
% ExperimentBoard: Wrapper class for accessing the Velleman K8055 USB
% Experiment Interface Board.
%
% EB = vellboard.ExperimentBoard() creates an ExperimentBoard object that
% will automatically connect to any attached K8055 devices. You can then
% access all the board's functions by using EB.Method, where Method is
% the method you'd like to access.
%
% EB = vellboard.ExperimentBoard('test') creates an ExperimentBoard object
% in test mode. This skips device connection so the class can be used
% without a K8055 device. Note that calling getActiveDevices will connect
% any attached devices, even in test mode.
%
% ExperimentBoard objects can access up to four connected devices. Note
% that a second instance of the ExperimentBoard class accesses the same
% devices, and may cause interference with the other instances.
% Therefore, having two active instances of the class is not recommended.
% MATLAB Support Package for Velleman K8055/VM110
% Version 1.0
% Copyright 2011 The MathWorks, Inc.
%% properties
properties (SetAccess = private)
testmode % Shows whether ExperimentBoard object is in test mode
end
%% constructor
methods
function obj = ExperimentBoard(test)
% ExperimentBoard.ExperimentBoard()
% Constructor class for vellboard.ExperimentBoard. Creates a
% vellboard.ExperimentBoard object that allows access to the K8055
% functionality. See class help for more help on object creation.
if exist('test','var') && strcmp(test,'test')
obj.testmode = 1;
else
obj.testmode = 0;
end
%Check that MATLAB is 32-bit Windows
if ~strcmp(computer,'PCWIN')
error('vellboard:VersionError',...
'This package is only compatible with 32-bit MATLAB on a Windows system')
end
%Attempt to load K8055 library
pref = vellboard.Prefs;
dllloc = pref.K8055_DLL_Location;
if ~libisloaded('K8055D')
if exist(dllloc,'file')
if exist([fileparts(mfilename('fullpath')) '\K8055D_h'],'file')
loadlibrary(dllloc,@vellboard.K8055D_h);
elseif exist('K8055D.h','file')
warning('vellboard:CHeader',...
'Using C header file to load K8055D.dll. Prototype file is faster!')
loadlibrary(dllloc,'K8055D.h')
else
error('vellboard:MissingHeader',...
'Could not find K8055D.h or K8055D_h.m header file')
end
else
error('vellboard:MissingDLL','Could not find K8055D.dll.')
end
end
%Grab DLL version and display (also indicates successful DLL
%load)
version = obj.version;
fprintf('\nDLL Version: %s\n',version);
if ~strcmp(version,'4.0.0.0')
warning('vellboard:DLLVersion',...
['This code was designed for version 4.0.0.0 of the DLL. Other versions may' ...
'work but may be missing functionaility. If you encounter any errors,'...
'please contact classroom-resources@mathworks.com with details.']);
end
% Attempt to open connection to device
% address = 0;
%
% if ~isempty(varargin) && ~isa(varargin(1),{'numeric'})
% address = varargin(1);
% end
% success = calllib('K8055D','OpenDevice',address);
%
% % If conncection attempt fails, close library and abort
% if success == -1
% delete(obj);
% error('Could not establish connection to 8055')
% end
if ~obj.testmode
try
%search for and establish connection to any K8055 devices
devices = obj.getActiveDevices();
if isempty(devices)
error('vellboard:NoDevices','No K8055 devices found');
end
fprintf('Connected device addresses:')
for i = 1:size(devices,2)
fprintf(' %u',devices(i))
end
fprintf('\n')
%set current device as one with smallest address (can be
%changed later)
obj.setCurrentDevice(devices(1));
fprintf('Current device has address %u\n',devices(1))
catch exception
calllib('K8055D','CloseDevice');
unloadlibrary('K8055D');
rethrow(exception);
end
else
fprintf('Running in test mode.\nSimulating four connected devices.\nInput will not affect attached devices.\n');
end
end
%% destructor
function delete(obj) %#ok<*MANU>
% ExperimentBoard.delete()
% Destructor method for the vellboard.ExperimentBoard. Called upon object
% deletion. Disconnects any connected devices and unloads the K8055
% library. Typically called by calling delete(varname), where varname is
% the name of the ExperimentBoard object you want to delete.
% Close device connection and unload library
if libisloaded('K8055D') && ~obj.testmode
calllib('K8055D','CloseDevice');
unloadlibrary('K8055D');
fprintf('\n8055 Experiment Board disconnected\n');
elseif obj.testmode
unloadlibrary('K8055D');
fprintf('\nEnding test mode\n');
end
end
%% Returns an array listing all currently connected devices by
% address
function result = getActiveDevices(obj)
% ExperimentBoard.getActiveDevices()
% Returns an array of the addresses of attached K8055 devices. Also
% connects any unconnected devices so they can be accessed. Note that
% this does not change the current device; use
% ExperimentBoard.setCurrentDevice instead.
if(obj.testmode)
result = [1 2 3 4];
else
searchresult = calllib('K8055D','SearchDevices');
result = [];
for i = 1:4
if bitget(searchresult,i)
result = [result,i-1]; %#ok<AGROW>
end
end
end
end
%% Sets the current device that all board-accessing functions use
function setCurrentDevice(obj,boardaddress)
% ExperimentBoard.setCurrentDevice(boardaddress)
% Sets the current device to the device with the address defined by
% boardaddress. Will throw an error if boardaddress is not the address of
% a currently connected device. Use ExperimentBoard.getActiveDevices to
% get a list of valid values for boardaddress.
if nargin ~= 2
error('vellboard:ImproperArguments',['Calling syntax: obj.setCurrentDevice(boardaddress)' char(10) ...
'Where boardaddress = the address of a connected card (0 <= boardaddress <= 3)'])
end
validateattributes(boardaddress,{'numeric'},{'scalar','integer','>=',0,'<=',3});
if ~obj.testmode
result = calllib('K8055D','SetCurrentDevice',boardaddress);
else
if any(boardaddress == [0 1 2 3])
result = 1;
else
result = -1;
end
end
if result == -1
devices = obj.getActiveDevices();
error('vellemen:InvalidDevice',['Specified device not connected.' ...
'Currently connected devices:' ...
num2str(devices)]);
end
end
%% Reads the value of analog input "channel"
function result = readAnalog(obj,channel)
% result = ExperimentBoard.readAnalog(channel)
% Reads data from the analog input channel defined by channel. Returns a
% integer between 0 and 255 representing a voltage range of 0V to ~5V.
if nargin ~= 2
error('vellboard:ImproperArguments',['Calling syntax: obj.readAnalog(channel)' char(10) ...
'Where channel = analog channel to be read (1 <= channel <= 2)'])
end
validateattributes(channel,{'numeric'},{'scalar','integer','>=',1,'<=',2});
if obj.testmode
result = 0;
else
result = calllib('K8055D','ReadAnalogChannel',channel);
end
end
%% Reads both analog input channels
function result = readAnalogAll(obj)
% result = ExperimentBoard.readAnalogAll()
% Reads data from both analog input channels at once. Returns a 1x2 vector
% containing integers from 0 to 255 representing a voltage range of 0V to
% ~5V. result(1) is the result from channel 1, and result(2) is the result
% from channel 2.
data1 = libpointer('int32Ptr',0);
data2 = libpointer('int32Ptr',0);
if obj.testmode
result = [0 0];
else
calllib('K8055D','ReadAllAnalog',data1,data2);
result = [data1.value data2.value];
end
end
%% Outputs value of "data" to analog output "channel"
function writeAnalog(obj,channel,data)
% ExperimentBoard.writeAnalog(channel,data)
% Outputs the value defined by data to the analog output channel defined
% by channel. Data must be a integer between 0 and 255, with 0 being 0V
% and 255 being ~5V.
if nargin ~= 3
error('vellboard:ImproperArguments',['Calling syntax: obj.writeAnalog(channel,data)' char(10) ...
'Where channel = analog channel to be output to (1 <= channel <= 2)' char(10)...
'and data = data to be outputted (0 <= data <= 255)'])
end
validateattributes(channel,{'numeric'},{'scalar','integer','>=',1,'<=',2});
validateattributes(data,{'numeric'},{'scalar','integer','>=',0,'<=',255});
if ~obj.testmode
calllib('K8055D','OutputAnalogChannel',channel,data);
end
end
%% Outputs contents of "datavec" to both analog outputs
function writeAnalogAll(obj,datavec)
% ExperimentBoard.writeAnalogAll(datavec)
% Outputs to both analog outputs at once. datavec is a 1x2 vector
% containing two integers, both between 0 and 255. datavec(1) is the
% output to analog output channel 1, and datavec(2) is the output to
% analog output channel 2. For example, if datavec = [0 255], channel 1
% will be set to 0V and channel 2 will be set to ~5V.
if nargin ~= 2
error('vellboard:ImproperArguments',['Calling syntax: obj.writeAnalogAll(datavec)' char(10) ...
'Where datavec = 1x2 vector containing data to be output to each channel (0 <= data <= 255)']);
end
validateattributes(datavec,{'numeric'},{'size',[1 2],'integer','>=',0,'<=',255});
if ~obj.testmode
calllib('K8055D','OutputAllAnalog',datavec(1),datavec(2));
end
end
%% Reads the value of digital input channel "channel"
function result = readDigital(obj,channel)
% result = ExperimentBoard.readDigital(channel)
% Reads data from the digital input defined by channel. Returns a 0 or 1
% for a low or high input, respectively.
if nargin ~= 2
error('vellboard:ImproperArguments',['Calling syntax: obj.readDigital(channel)' char(10) ...
'Where channel = digital channel to be read (1 <= channel <= 5)'])
end
validateattributes(channel,{'numeric'},{'scalar','integer','>=',1,'<=',5});
if obj.testmode
result = 0;
else
result = calllib('K8055D','ReadDigitalChannel',channel);
result = cast(result,'int32'); %returns logical; int is handier
end
end
%% Reads the value of all digital inputs and returns 5-bit result
% as a binary vector
function result = readDigitalAll(obj)
% result = ExperimentBoard.readDigitalAll()
% Reads data from all the digital inputs in one function call. Returns a
% 1x5 binary vector with each entry containing a 1 or 0. Each entry
% corresponds with a digital input, with 1 being a high input and 0 being
% a low input. For example, if result(5) = 1, that means that digital
% input 5 is high.
if obj.testmode
result = [0 0 0 0 0];
else
result = vellboard.util.int2binvec(calllib('K8055D','ReadAllDigital'),5);
result = cast(result,'int32'); %returns logical; int is handier.
end
end
%% Writes value of data (1 bit) to digital output "channel"
function writeDigital(obj,channel,data)
% ExperimentBoard.writeDigital(channel,data)
% Writes the value of data to the digital output defined by channel. Data
% should be either a one or zero, with 1 = turn the channel on, and 0 =
% turn the channel off.
if nargin ~= 3
error('vellboard:ImproperArguments',['Calling syntax: obj.writeAnalog(channel,data)' char(10) ...
'Where channel = analog channel to be output to (1 <= channel <= 8)' char(10)...
'and data = data to be outputted (0 <= data <= 1)'])
end
validateattributes(channel,{'numeric'},{'scalar','integer','>=',1,'<=',8});
validateattributes(data,{'numeric'},{'binary'});
if ~obj.testmode
if data
calllib('K8055D','SetDigitalChannel',channel);
else
calllib('K8055D','ClearDigitalChannel',channel);
end
end
end
%% Write 8-bit value to all digital outputs
function writeDigitalAll(obj,datavec)
% ExperimentBoard.writeDigitalAll(datavec)
% Sets the value of all the digital outputs in one function call. Each
% output is set based on the value of its corresponding entry in datavec,
% which is a 1x8 vector, with a one setting the channel high and a zero
% setting it low. For example, if datavec(1) is 1, channel 1 will be set
% high. Note that this means your binary vector will be written from left
% to right, ie. the binary vector of 110 will be [0 1 1]. You can use
% vellboard.util.dec2binvec to convert any integer number to a
% corresponding binary vector.
if nargin ~= 2
error('vellboard:ImproperArguments',['Calling syntax: obj.writeAllDigital(datavec)' char(10) ...
'Where data = 8-bit binary'])
end
validateattributes(datavec,{'numeric'},{'size',[1 8],'binary'});
if ~obj.testmode;
data = vellboard.util.binvec2int(datavec);
calllib('K8055D','WriteAllDigital',data);
end
end
%% Reads the value of pulse counter #"counter"
function result = readCounter(obj,counter)
% result = ExperimentBoard.readCounter(counter)
% Reads the current value for the counter associated with the digital
% input defined by counter (only inputs 1 and 2 have counter
% functionality). The counter counts the number of times the associated
% input has been set high since the last reset. This is a 16-bit counter,
% meaning it can store values up to 65535 (ie. 1111 1111 1111 1111).
if nargin ~= 2
error('vellboard:ImproperArguments',['Calling syntax: obj.readCounter(counter)' char(10) ...
'Where counter = counter to be read (1 <= counter <= 2)'])
end
validateattributes(counter,{'numeric'},{'scalar','integer','>=',1,'<=',2});
if obj.testmode
result = 0;
else
result = calllib('K8055D','ReadCounter',counter);
end
end
%% Reads the value of both pulse counters
function result = readCounterAll(obj)
% result = ExperimentBoard.readCounterAll()
% Reads the current value for both counters in one function call.
% Returns a 1x2 vector, with each entry of the vector corresponding to
% a counter. For example, result(1) is the current value of the counter
% associated with digital input 1.
if obj.testmode
result = [0 0];
else
data1 = calllib('K8055D','ReadCounter',1);
data2 = calllib('K8055D','ReadCounter',2);
result = [data1 data2];
end
end
%% Resets pulse counter #"counter"
function resetCounter(obj,counter)
% ExperimentBoard.resetCounter(counter)
% Resets the counter for the digital input defined by counter (only
% digital inputs 1 and 2 have counter functionality) to 0.
if nargin ~= 2
error('vellboard:ImproperArguments',['Calling syntax: obj.resetCounter(counter)' char(10) ...
'Where counter = counter to be reset (1 <= counter <= 2)'])
end
validateattributes(counter,{'numeric'},{'scalar','integer','>=',1,'<=',2});
if ~obj.testmode
calllib('K8055D','ResetCounter',counter);
end
end
%% Sets the debounce time for counter #"counter" to "debouncetime"
function setCounterDebounceTime(obj,counter,debouncetime)
% ExperimentBoard.setCounterDebounceTime(counter,debouncetime)
% Sets the debounce time for the counter associated with the digital
% input defined by counter (only inputs 1 and 2 have counter
% functionality) to the value set by debouncetime. debouncetime is
% measured in milliseconds and can be any integer between 0 and 5000. The
% counter debounce time represents the time the input must be high for
% the counter to increase. For example, if the debounce time is set to
% 5ms, only pulses that are at least 5ms long will increment the timer.
if nargin ~= 3
error('vellboard:ImproperArguments',['Calling syntax: obj.setCounterDebounceTime(counter,debouncetime)' char(10) ...
'Where counter = counter to be set (1 <= counter <= 2)' char(10) ...
'and debouncetime = debounce time to be set in ms (0 <= debouncetime <= 5000)'])
end
validateattributes(counter,{'numeric'},{'scalar','integer','>=',1,'<=',2});
validateattributes(debouncetime,{'numeric'},{'scalar','integer','>=',0,'<=',5000});
if ~obj.testmode
calllib('K8055D','SetCounterDebounceTime',counter,debouncetime);
end
end
%% Returns the DLL version as a string in X.X.X.X format
function result = version(obj)
% ExperimentBoard.version()
% Returns the version of the K8055 SDK's DLL library as a string in
% X.X.X.X format. Note that the Support Package was written using version
% 4.0.0.0 of this DLL; any other versions may cause compatibility errors.
version = cast(calllib('K8055D','Version'),'int32');
version = typecast(version,'int8');
result = [num2str(version(4)) '.' ...
num2str(version(3)) '.' ...
num2str(version(2)) '.' ...
num2str(version(1))];
end
%% custom display function
function display(obj)
fprintf('\nvellboard.ExperimentBoard object\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard">>Class help</a>\n');
fprintf(' <a href="matlab:vellboard.ExperimentBoard.getMethods()">>Methods</a>\n');
fprintf('\n')
end
end
%% static methods
methods(Static)
%% nicely formatted methods list with help links
function getMethods()
% ExperimentBoard.getMethods()
% Prints a nicely formatted list of ExperimentBoard's methods with links
% to their relevant help dialogs.
fprintf('\nMethods for vellboard.ExperimentBoard:\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.ExperimentBoard">ExperimentBoard()</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.delete">delete()</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.getActiveDevices">getActiveDevices()</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.setCurrentDevice">setCurrentDevice(boardaddress)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.readAnalog">readAnalog(channel)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.readAnalogAll">readAnalogAll()</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.writeAnalog">writeAnalog(channel,data)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.writeAnalogAll">writeAnalogAll(datavec)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.readDigital">readDigital(channel)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.readDigitalAll">readDigitalAll()</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.writeDigital">writeDigital(channel,data)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.writeDigitalAll">writeDigitalAll(datavec)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.readCounter">readCounter(counter)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.readCounterAll">readCounterAll()</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.resetCounter">resetCounter(counter)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.setCounterDebounceTime">setCounterDebounceTime(counter,debouncetime)</a>\n');
fprintf(' <a href="matlab:help vellboard.ExperimentBoard.version">version()</a>\n');
fprintf('\n');
end
end
end