Code covered by the BSD License  

Highlights from
Matlab <-> PSpice Interface

image thumbnail
from Matlab <-> PSpice Interface by Frank Sommerhage
How to call PSpice from Matlab and import the results

readdat(filename)
% imports binary PSpice data from *.dat files (PSpice 6.0, 9.1 and 10.3.0)
%
% Examples
%                  readdat
%           data = readdat
%    [data text] = readdat
%    [data text] = readdat(filename)
%           data = readdat(filename)
%                  readdat(filename)
%
% If you type in just "readdat" and hit enter, the tool will ask you for a
% filename and plot the imported data, e.g. for fast review.
%
% Alternatively you can define a filename for any combination of output.
%
% If you assign the output of this function to only one variable, you'll
% receive a struct that might look as follows:
%
%     data = 
% 
%         Head: [1x1 struct]                -- all the header information
%         Time: [60243x1 double]            -- time (not linearly spaced!)
%         Data: [60243x29 single]           -- all data traces
%         Name: {1x29 cell}                 -- all node names
%
% If you assign two variables to the output, you'll get an array with all
% traces (including time) and also a cell with corresponding node names.
%
%     data =
% 
%              0         0   -0.0000   -0.0000   -0.0304   ...
%         0.0000         0   -0.0000   -0.0000   -0.0304   ...
%         0.0000         0   -0.0000   -0.0000   -0.0304   ...
%         0.0000         0   -0.0000   -0.0000   -0.0304   ...
%         0.0000         0   -0.0000   -0.0000   -0.0304   ...
%         0.0000         0   -0.0000   -0.0000   -0.0304   ...
%         0.0000         0   -0.0000   -0.0000   -0.0304   ...
%            ...       ...       ...       ...       ...
%
%     text = 
% 
%         'Time'
%         'V(R_free:2)'
%         'V(OUT)'
%         'V(FET)'
%         'V(CELL)'
%         'V(PATCH)'
%         ...
%
% Note, that this tool is an early "beta version" and I could not check it
% with all the different versions of PSpice. At least you'll get an idea
% about the general binary *.dat-file structure to add you code or
% modifications.
%
% Suggestions, comments and discussions are welcome...
%
%                                                    by Frank Sommerhage
%                                                  (matlab@sommerhage.com)




function [data text] = readdat(filename)
%
% pSpice *.dat files are hybrids (text and binary). They can be interpret 
% by a MicroSim application called PROBE. Using programs like PSpice A/D to
% open the files and export data to text files and subsequently import it
% into Matlab takes much time...
%
% This m-file imports (at least analog) data from *.dat files into Matlab.
% Also, this code is lightning-fast and returns the complete data (30 data
% traces with about 60000 points each) from a 7.5 MByte file in less than 
% one second from an external HDD (USB 2.0).
%
% Also, for some reason the imported data has one data point more (the very
% first one) than the usual exported data from PSpice. I guess there is a
% tiny bug in PSpice that forgets this very first point. The rest is 100%
% identical with copy-pasted data. 
%
% I worked with transient analysis only (mostly PSpice 10.3.0). If you need
% digital data or measurement results from a pSpice *.dat file, you might
% need to add code to this m-file or change it...
%
%                            REVISION  HISTORY
% -----------------------------------------------------------------------
%  DATE        |  NAME              |  JOB/TASK/MODIFICATION
% =======================================================================
%  2008-08-05  |  Frank Sommerhage  |  initial revision (PSpice 10.3.0)
%  2008-08-07  |  Frank Sommerhage  |  changes in readData() function to
%              |                    |  increase the import speed
%  2008-08-16  |  Frank Sommerhage  |  added compatibility for 
%              |                    |  PSpice 6.0 and PSpice 9.1 Student
%  2008-08-17  |  Frank Sommerhage  |  minor changed in readInventory()
%  2008-09-17  |  Frank Sommerhage  |  improved code (M-Lind)
%  2008-09-21  |  Frank Sommerhage  |  added fclose(fid) - stupid me
% -----------------------------------------------------------------------
%
global oldpath
if nargin < 1 % if missing file-information ask for filename
    % this keeps the recently used path - very useful!
    if ~isvarname('oldpath') || ~isempty(oldpath)
        oldpath = '';
    end
    % open file dialog
    [file, path] = uigetfile({'*.dat', 'PSpice binary files'}, ...
                              'Open PSpice data file', oldpath);
    % check if a file was selcted
    if file == 0
        return
    else % save path for future use and form filename string
        oldpath = path;
        filename = [path file];
    end
end

% open the file
f.id = fopen(filename);

% read a couple of mostly useless flags
f.flags = fread(f.id, 16, 'ubit4')';
% extract file version (I think this is some kind of file version?!?)
f.version = f.flags(15);
% files with different file versions are formated differently:
%
% *** file version 7 ***
% I've got PSpice 6.0 (for DOS), which uses version 7. That is my start. It
% is that old, it has a year 2000-bug. Version 7 is my earliest reference.
%
% *** file version 8 ***
% is used by PSpice 9.1 Student. There, they increased the precision of
% time data from single to double.
%
% *** file version 9 ***
% present in PSpice 10.3.0. The flag (coding for the file size in the
% inventory or the 'distance' to the next container with more S or A data)
% has a higher precision now (single to double). The file inventory has
% longer hex-strings to code for container positions. They also added a new
% variable to the header: "CurTime". 
%
% I haven't worked with more recent versions - Sorry. Provide me a file and
% I'll try to make this m-code compatible to it...
%

% next comes a container with an inventory for data in the file
I = readInventory(f);
f.size = I.flag;

% % add inventory and file info to output struct
% data.Inventory = I;
% data.File = f;
% % (if you want to see it, remove the %-sign

% go ahead with import of header container
data.Head = readHeader(f, I.H);

% move into node name import
data.Name = readNodes(f, I.N);

% import the time line (very important - time is not spaced linearly!!!)
data.Time = readData(f, I.S, 'S', ...
                        data.Head.AnaRows);
                      
% continue on data import
data.Data = readData(f, I.A, 'A', ...
                        data.Head.AnaRows, ...
                        data.Head.AnaCols-1);

% close file
f.id = fclose(f.id);
                    
% the following code checks what you want and formats the data accordingly
switch nargout
    case 2 % return array with traces and a cell with node names
        text = data.Name';
        data = [data.Time, data.Data];
    case 1 % return 1 struct with full file information
        data.Name(1) = []; % time is extra, therefore delete its node name
    otherwise % plot data and return nothing to workspace
        plot(data.Time, data.Data);
        legend(data.Name(2:end));
        clear data
end




%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                              SUBFUNCTIONS                               %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%




% =========================================================================
function I = readInventory(f)
%
% If you open *.dat files with a (hex-)editor, you'll see something like
% this (e.g. PSpice 10.3; older PSpice versions have shorter hex-strings):
%
%     i=0x0000000000000000          -I've got no idea?!?
%     H=0x00000000000000DF          position of the header information
%     h=0x0000000000000000          -IDK?!?
%     N=0x00000000000002A1          position of node information 
%     n=0x0000000000000000          -no idea?!?
%     S=0x00000000000002FB          position of start parameters 
%     s=0x0000000000000000          -nope?!?
%     A=0x0000000000000358          position of analog data 
%     D=0x0000000000000000          position of digital data (I suppose)
%     M=0x0000000000000000          position of measurement results (?)
%
% This function reads the inventory and converts container positions (given
% as hex) into decimal numbers.
%
I = readContainerHead(f, 0, 'I');           % read inventory header
chs = 8 + 4*(f.version>8);                  % adjust container head size
temp = fread(f.id, I.size-chs-1, '*char')'; % read inventory information
temp = reshape(temp, [], 10)';              % reformat inventory string
for i = 1:10                                % 10 entries (see above)
	I.(temp(i,1)) = hex2dec(temp(i,5:end)); % convert hex, add to struct
end




% =========================================================================
function H = readHeader(f, pos)
%
% This is an example for header information. It is stored in *.dat files
% as a list of strings:
%
%     AllCol=0
%     AnaCols=2
%     Analysis=Transient Analysis
%     AnaRows=0x00009974 
%     CirName=* Profile name *  
%     CirSub= 
%     Complex=1 
%     DataType=0 
%     Date=06/18/07 
%     DigCols=0 
%     DigRows=0x00000000 
%     EndSweep=000000000000000000000000000000000000000000000.89 
%     Param1=Time 
%     Param2= 
%     ProgID=<your serial number> 
%     RevNo=10.3 
%     StartSweep=0 
%     SweepMode=1 
%     Temp=27 
%     Time=16:53:11 
%     CurTime=000000000000000000000000000000000000000000000000 
%
% Some of the data is stored as hex, some as dec, there are strings and
% dates. I tried to parse everything. If you have additional information
% in your files, you might want to add some code here to parse the header
% accordingly.
%
head = readContainerHead(f, pos, 'H');          % read container info
chs = 8 + 4*(f.version>8);                      % container head size
temp = fread(f.id, head.size-chs-2, '*char')';  % read header information
while ~isempty(temp)                            % until end of container
    [token temp] = strtok(temp, char(0));       % read a pstring
    spos = strfind(token, '=');                 % find the = sign in string
    name = token(1:spos-1);                     % name is afore '='
    value = token(spos+1:end);                  % value comes past '='
    if isempty(value)                           
        value = [];                             % delete empty strings ('')
    else
        number = str2num(value);                % try to convert to number
        if isempty(number)
            if length(value) > 1 && strcmp(value(1:2), '0x') % check if hex data
                value = hex2dec(value(3:end));  % convert hex to decimal
            end
        elseif strfind(name, 'Time')            % do not convert to number
        elseif strfind(name, 'Date')            % do not convert to number
            if length(value) > 8
                value(7) = [];                  % correct year 2000 error
            end
        elseif strfind(name, 'ProgID')          % do not convert to number     
        elseif strfind(name, 'RevNo')           % do not convert to number
        else
            value = number;                     % or use number as value
        end
    end
    H.(name) = value;                           % add value to struct
end



% =========================================================================
function N = readNodes(f, pos)
%
% Reads the text information with node names for each data trace. I didn't
% need the additional node information. Here some examples:
%
%   Time
%   V(OUT);V1(R_lowpass);V2(C_lowpass);V(OUT);V(C_lowpass:2);V(R_lowpass:1)
%   V(CELL);V(R_series:2);V(R_free:1);V(R_attached:2);V(C_att ...
%   I(V_stm);I(V_stm:+)
%   I(R_lowpass);I(R_lowpass:1)
%   ...
%
% Each line represents one data trace and the additional information is
% useless at this point - at least to me. That is why I cut the names to
% their short form - as you would see them in PSpice. If you need this
% further information, just remove the part between the stars below.
%
head = readContainerHead(f, pos, 'N');          % read container info
chs = 8 + 4*(f.version>8);                      % container head size
temp = fread(f.id, head.size-chs-2, '*char')';  % read header information
N = {};                                         % start names as empty cell
while ~isempty(temp)                            % until end of container
    [token temp] = strtok(temp, char(0));       % read a pstring
    spos = strfind(token, ';');                 % find a ';' (see above)
    if isempty(spos)                            % take it (e.g. 'Time')
        name = token;
    else          % *************               % delete the code between
        name = token(1:spos(1)-1);              % the stars to get the full
    end           % *************               % name information...
    N{end+1} = name;                            % add name to cell
end




% =========================================================================
function data = slowreadData(f, pos, type, r, c)
%
% This is an older version of the data import tool. It uses a while loop to
% read its way through the file. You might want to use it, if you need to
% implement reading for digital data or measurement results. Actually, I am
% not sure, whether it is still working...?!?
%
if nargin < 5, c = 1; end                     % see next function below...
head = readContainerHead(f, pos, type);
switch type                                   
    case 'A', precision = 'single'; psize = 4;
    case 'S', precision = 'double'; psize = 8;
    otherwise, return
end
data = zeros(r, c);                           % create data array
p = 0;                                        % start at pos 0 in array
z = 4 + 4*(f.version>8) + 4,                  % container head size
k = (f.version>8)                            % file version corrections
fseek(f.id, -(z+k), 0);                       % file-pntr to this container
% fread(f.id, 30, '*char')', return
tic                                           % get current time
while head.flag                               % until no more containers
    head = readContainerHead(f, ftell(f.id)-8-1, type); % current flag info
    n = (head.size-z)/psize;                  % # of data in each container
    % read and reshape data in container, and store in data array
    data(p+1:p+n/c,:) = reshape(fread(f.id, n, precision)', c, [])';
    p = p+n/c;                                % increase array pointer
    fseek(f.id, head.flag(1)-head.size-2*~k, 0); % jump to next container
    if toc > 3 && mod(p, 2500)                 % display waitbar if ...
        waitbar(p/r);                         % process takes more than 3
    end                                       % seconds
% fread(f.id, 30, '*char')', return
end
data = data(1:r,1:c);                         % shorten to rows & columns




% =========================================================================
%
% Data is written in various ways. The data is not continuous and appears
% in containers (luckily with constant size - usually 10 points/trace). 
% Time is not linear: check yourself with diff(time) or cumsum(time) !!! 
% I don't know, what digital data or measurement result would look like in
% the file, since I never used it. Therefore D and M containers cannot be
% imported with this code (you would have to add the necessary code below).
%
% This part of the m-code makes this tool so fast. The skip-option in fread
% makes much slower while- or for-loops obsolete. Thank you Mathworks!
%
function data = readData(f, pos, type, r, c)
if nargin < 5, c = 1; end                     % no column? --> make it 1
head = readContainerHead(f, pos, type);       % read container head
switch type
    case 'A', precision = 'single'; psize = 4;% analog data: always single
    case 'S' 
        if f.version > 7
            precision = 'double'; psize = 8;  % since ver 8, time is double
        else
            precision = 'single'; psize = 4;  % older versens are single
        end
%     case 'D', precision = ''; psize = NaN;  % I don't know the parameters
%     case 'M', precision = ''; psize = NaN;  % I don't know the parameters
    otherwise, return % do not import
end
h = 8 + 4*(f.version>8);                      % correct for file version
skip = head.flag(1)-head.size+h;              % skipsize between containers
n = (head.size-h)/psize; % (usually n=10/c)   % # of data in each container
precision = [num2str(n) '*' precision '=>' precision];   % precision string
data = fread(f.id, r*c, precision, skip)';    % read, skip, read, skip, ...
data = reshape(data, c, [])';                 % reorganize data for columns




% =========================================================================
function head = readContainerHead(f, pos, verify)
%
% Some examples for container heads in a *.dat file. They start with a
% character to determine the type of the container. Here's an example of
% a few heads in my files (in a hex/text-editor)
%
%     'I        '                       <--  I for inventory
%     'H          '                       <--  H for header
%     'N        Z   '                       <--  N for node names
%     'S        /   '                       <--  S for time (in my case!)
%     'A        4   '                       <--  A for analog data
%
% The char (e.g. 'H') is followed by a int64 flag. In the inventory, this
% number represents the total filesize in I and in A and S containers it
% represents the distance to the next container with A and S information in
% it (what a waist of disk space). The last part codes for the current
% container's size as int32.
%
if nargin < 3, verify = []; end             % no verify? --> turn it off
fseek(f.id, 8+pos, -1);                     % move file pointer to pos+8(!)
char       = fread(f.id, 1, '*char')';      % verification character
head.pos   = pos;                           % keep position for later
if f.version > 8
    head.flag  = fread(f.id, 1, 'int64')';  % filesize / next container pos
else
    head.flag  = fread(f.id, 1, 'int32')';  % filesize / next container pos
end
head.size  = fread(f.id, 1, 'int32')';      % container size
if char ~= verify                           % stop if wrong container
    error(['Wanted ' verify ' got ' char ' at ' num2str(pos+8) '!']);
end         

Contact us at files@mathworks.com