Instrument control package

From Octave
Jump to navigation Jump to search

Instrument-Control is a package for interfacing the outside world of hardware via Serial, i2c or Parallel interfaces. It is currently under development by Andrius Sutas and Stefan Mahr, you can browse the mercurial repository here and download the package here.

Compatibility[edit]

Below is a table showing package compatibility with various platforms:

Linux Windows (Cygwin) Windows (native) FreeBSD Mac OS X
Serial v0.1.0 v0.2.0 v0.2.1 stalled v0.2.2
Parallel v0.1.0 - - stalled -
i2c v0.1.0 - - stalled -
modbus v0.8.0 v0.8.0 v0.8.0 - v0.8.0
spi v0.6.0 - - - -
TCP v0.2.0 v0.2.0 v0.2.0 stalled v0.2.2
UDP v0.3.0 v0.3.0 v0.3.0 - v0.3.0
USBTMC v0.2.0 - - - -
GPIB v0.2.0 (*) v0.2.0 (*) - - -
VXI11 v0.2.0 v0.2.0 (*) - - WIP (*)

Where: (VER) - Included since VER version; (WIP) - Work In Progress and will probably be included in next release; (stalled) - Some work has been done, but it's not tested and no one is actively contributing; (-) - Not supported / Untested.

You might see errors while doing "pkg install", which mean that package is unable to support related interface on your platform (e.g. due to missing header files), therefore one should always check for the supported interfaces before trying to use them:

Code: Check for interface support
pkg load instrument-control
supportedinterfaces = instrhwinfo().SupportedInterfaces

if ! isempty(strfind (supportedinterfaces , "serial"))
    disp("Serial: Supported")
else
    disp("Serial: Unsupported")
endif

#similarly with:
# ! isempty(strfind (supportedinterfaces , "parallel"))
# ! isempty(strfind (supportedinterfaces , "i2c"))

Requirements[edit]

Linux[edit]

For GPIB support, please install linux-gpib before installing instrument-control.

Windows (cygwin)[edit]

For VXI11 support, please install 'rpcgen', 'libtirpc-devel', and 'libtirpc1' before installing instrument-control.

To be able to use GPIB on windows, the linux-gpib library needs to be 'faked'. This can be done by copying the following script and run it in a bash-terminal.

#!/bin/bash
# fake_linux_gpib.sh, Kire Pûdsje, Dec 2017

# This file generates a fake linux-gpib library on Windows, based on the
# installed NI libraries.
# Prerequisits are that during install of the NI drivers, the C/C++ support 
# and the NI Measurement & Automation explorer has been enabled.

# set the base for includes and libraries on your system
# it is usually /usr, but on newer windows installs of octave, it will be
# /mingw64
INSTALL_BASE=/usr

# this define should find the NI header file, if properly installled
# otherwise just point it to the proper header file.
NI_H_FILE="${NIEXTCCOMPILERSUPP}/include/ni4882.h"
NI_DLL_FILE=ni4882.dll
NI_DEF_FILE=/tmp/${0##*/}-$$.def

# symlink NI header file
mkdir -p ${INSTALL_BASE}/include/gpib/
ln -sf "${NI_H_FILE}" ${INSTALL_BASE}/include/gpib/ib.h

# generate .def for all functions in the NI header file with a NI488CC prefix
echo LIBRARY ${NI_DLL_FILE} > ${NI_DEF_FILE}
echo EXPORTS >> ${NI_DEF_FILE}
grep "NI488CC  *[A-Za-z]" ${INSTALL_BASE}/include/gpib/ib.h \
  | sed "s/^.*NI488CC  *//" \
  | sed "s/(.*$//" >> ${NI_DEF_FILE}

# generate the wrapper library simulating gpib-linux
dlltool -d ${NI_DEF_FILE} -l ${INSTALL_BASE}/lib/libgpib.a

#cleanup 
rm -f ${NI_DEF_FILE}

MacOS[edit]

You need to build rpcgen from source [1].

If rpcgen cannot be build from source, the following error might be obtained:

pkg: error running `make' for the instrument-control package.
error: called from
    configure_make at line 99 column 9
    install at line 184 column 7
    pkg at line 437 column 9

If the VXI-11 interface is not required, a workarround is proposed in [2] after the bug reported on [3].

Examples[edit]

Serial[edit]

Configuring interface[edit]

You might want to configure interface in a loopback mode for testing.

  • If you have a Serial adapter, then simply connect pins 3 (Tx) and 2 (Rx) together (assuming your adapter is RS-232 DE9, otherwise check before doing anything). One can also insert an LED in series to view the actual bitstream.
  • If you do not have a Serial adapter then create a virtual port using:
$ socat PTY,link=/dev/ttyS15 PTY,link=/dev/ttyS16

which will open two interconnected interfaces, where one (e.g. /dev/ttyS15) can be opened in Octave and second (e.g. /dev/ttyS16) using something like GNU screen:

$ screen /dev/ttyS16 <baudrate>

do not forget to change interface permissions if you want to use them with Octave/screen without root privileges.

Example: basic use[edit]

Here is a simple example to get started with the serial package. It tests the RxD and TxD lines. Make sure you have defined a loopback or connected the pins of your adapter.

Code: Serial port example
# Opens serial port ttyUSB1 with baudrate of 115200 (config defaults to 8-N-1)
s1 = serial("/dev/ttyUSB1", 115200) 
# Flush input and output buffers
srl_flush(s1); 
# Blocking write call, currently only accepts strings
srl_write(s1, "Hello world!") 
# Blocking read call, returns uint8 array of exactly 12 bytes read
data = srl_read(s1, 12)  
# Convert uint8 array to string, 
char(data)

Changing some configurations is simply done by calling helper functions

Code: Serial port example: helper functions
set(s1, "baudrate", 9600) # Change baudrate
set(s1, "bytesize", 5)    # Change byte size (config becomes 5-N-1)
set(s1, "parity", "E")    # Changes parity checking (config becomes 5-E-1),
                          # possible values [E]ven, [O]dd, [N]one.
set(s1, "stopbits", 2)    # Changes stop bits (config becomes 5-E-2), possible
                          # values 1, 2.

set(s1, "dataterminalready", "on")  # Enables DTR line
set(s1, "requesttosend", "on")      # Enables RTS line
                                    # possible values "on", "off".

Some properties can be set at opening time

Code: Serial port example: constructor call
s2 = serial("/dev/ttyS0", 9600, 10) # Opens serial port ttyS0 in
                                    # 9600 baudrate with 1s read timeout

Do not forget to close the ports when you are done!

fclose(s1) # Closes and releases serial interface object
fclose(s2) # Closes and releases serial interface object

Example: Windows, serial port >COM9[edit]

Opening a serial port higher than COM9 requires special syntax. [4]

Code: open Windows higher serial port example
# Opens serial port COM10
s1 = serial("\\\\.\\COM10");

Parallel[edit]

Configuring interface[edit]

You will need to load following modules:

# modprobe parport
# modprobe ppdev

Now you should see devices like "/dev/parport0". In case you do not, you will need to create them manually and give sufficient privileges for your user:

# mknod /dev/parport0 c 99 0 -m 666

Example 1: LED trace[edit]

For this example you will need to connect 8 LEDs to Parallel interface Data port as shown below:

Parport schematic.png

And then the actual script:

Code: trace.m
delay = 0.1;

pp = parallel("/dev/parport0", 0);
pp_data(pp, 0);

pins = pow2([0, 1, 2, 3, 4, 5, 6, 7]);

while 1
    for p = pins
        pp_data(pp, p);
        sleep(delay);
    endfor	

    # Reverse the order
    pins = pins(end:-1:1);
endwhile

pp_close(pp);

Run it and you should see LEDs "tracing" forwards and backwards.


Example 2: LED dimming a.k.a. PWM control[edit]

Use the same schematic as in Example 1.

Code: dim.m
delay = 0.00005;

pp = parallel("/dev/parport0", 0);
pp_data(pp, 0);

dRange = 0:100;
while 1
    for duty = dRange
        pp_data(pp, 255);		

        for dOn = 0:duty
            sleep(delay);
        endfor	

        pp_data(pp, 0);

        for dOff = 0:(100-duty)
            sleep(delay);
        endfor
    endfor
	
    # Reverse order
    dRange = dRange(end:-1:1);
endwhile

pp_close(pp);

Run it and you should see LEDs changing brightness from lowest to maximum.


Example 3: Logic analyzer[edit]

We can surely make something more interesting, right? Enter basic logic analyzer.

Assume you are working with [this] temperature sensor. Something is not right. You do not have one of those expensive logic analyzers, but you do have a Parallel port! You remember that someone made a package for interfacing it with GNU Octave. So you connect your probes to appropriate Data port terminals and change settings accordingly. In this example:

Probe Port terminal
!CS DATA0
SCK DATA1
SO DATA2

NB: Parallel ports usually have weak pull-ups to +5V even when in "input" mode, so do not do this if unsure. One could potentially use different terminals in Control/Status ports to get true high-impedance inputs.

And write a simple script below:

Code: logic_analyzer.m
#####################################################################
# Settings
#####################################################################

# Channels to capture
#channels = [0, 1, 2, 3, 4, 5, 6, 7];
channels = [2, 1, 0];

# Channel labels
#channel = {"CH0"; "CH1"; "CH2"; "CH3"; "CH4"; "CH5"; "CH6"; "CH7"};
channel = {"SO"; "SCK"; "!CS"};

# Trigger channel
triggerCh = 0;

# When to trigger
trigger = 0; # Capture on low. For high - 1

#####################################################################

samplesTime = [];
samplesValue = [];

#pp_close(pp);
pp = parallel("/dev/parport0", 1);

printf("Waiting for trigger...\n");
fflush(stdout);

data = pp_data(pp);
while (bitget(data, triggerCh + 1) != trigger)
    oldData = data;
    data = pp_data(pp);
endwhile

printf("Capturing...\n");
fflush(stdout);

startTime = time();
samplesTime(end + 1) = 0;
samplesValue(end + 1) = oldData;

while (bitget(data, triggerCh + 1) == trigger)
    data = pp_data(pp);
    samplesTime(end + 1) = time() - startTime;
    samplesValue(end + 1) = data;
endwhile

# Statistics
printf("Average sample rate: %f kHz\n", size(samplesValue)(2) / samplesTime(end) / 1000.0);

pp_close(pp);

# Plotting

figure;
for p = 1:size(channels)(2)
    subplot (size(channels)(2), 1, p)
    plot(samplesTime, bitget(samplesValue, channels(p) + 1))

    ylabel(channel{p});
    axis([-0.01, samplesTime(end)+ 0.01, -1, 2], "manual");
    set(gca(), 'ytick', -1:2);
    set(gca(), 'yticklabel', {''; '0'; '1'; ''});
endfor
xlabel ("t");

If connections and settings are correct you should see something like this:

Logic analyzer.png

Now you can fully debug what is going with your hardware from comfort of GNU Octave.

i2c[edit]

Example with a Si7021 i2c breakout board (https://www.sparkfun.com/products/1376) connected to a i2c master interface.

To work out the devices available (assuming i2ctools are installed) run the i2cdetect command from a terminal window.

$ i2cdetect -l
i2c-1   i2c             i2c-ch341-usb at bus 002 device 008     I2C adapter
i2c-0   unknown         SMBus I801 adapter at 4000              N/

In the example case the temperature sensor is connected to the i2c-ch341-usb device, and it is assumed the user has sufficient permissions to access the device.

According to the datasheet, the temperature device uses address 0x40, so create a i2c device using the linux device and address:

i2cdev = i2c("/dev/i2c-1", 0x40)

To read the temperature, register 0xF3 must be addressed and read (2 bytes of data):

TEMP_MEASURE_NOHOLD = hex2dec("F3");
fwrite (i2cdev, uint8([TEMP_MEASURE_NOHOLD]));
pause (0.02);
data = fread (i2cdev, 3);

The data must be converted to a deg K value by making 16 bit number of the value and masking out unused bits.

value = uint16(data(1))*256 + uint16(data(2));
value = bitand (value, hex2dec("FFFC"));
temp_Code = double(value);

Now convert the temperature to degrees C and display:

C = (175.72*temp_Code/65536)-46.85;
printf ("temperature read %f C\n", C);

TCP[edit]

Example: basic use[edit]

For testing you could start a tcp listener

$ socat TCP-LISTEN:8000 -

Now you can connect your listener.

Code: TCP example
# Open TCP connection to 127.0.0.1:8000
t0 = tcp("127.0.0.1",8000)
# write to listener
tcp_write(t0, "Hello world!") 
# Blocking read call, returns uint8 array of exactly 12 bytes read
data = tcp_read(t0, 12)  
# Convert uint8 array to string, 
char(data)

There are several ways to set timeout of read call.

Code: set TCP timeout
# Open TCP connection to 127.0.0.1:8000 with timeout of 100 ms
t0 = tcp("127.0.0.1",8000,100)
# set timeout to blocking
tcp_timeout(t0, -1) 
# the timeout can be overwritten for single read call, in this case 1000ms
data = tcp_read(t0, 12, 1000)  
# close tcp session
tcp_close(t0)

USBTMC[edit]

Configuring interface[edit]

Recent linux kernels support USBTMC out of the box. Connect your instrument and check if /dev/usbtmc* exists. Set appropriate permissions to /dev/usbtmc*

Example: basic use[edit]

Code: USBTMC example
# Open interface to USB instrument
t0 = usbtmc('/dev/usbtmc0')
# write to listener
usbtmc_write(t0, '*IDN?') 
# Blocking read call, returns uint8 array
data = usbtmc_read(t0, 10000)  
# Convert uint8 array to string, 
char(data) 
# close usbtmc session
usbtmc_close(t0)

VXI11[edit]

Example: basic use[edit]

Code: VXI11 example
# Open VXI11 connection to 192.168.100.100
t0 = vxi11('192.168.100.100')
# write to listener
vxi11_write(t0, '*IDN?') 
# read from instrument, returns uint8 array
data = vxi11_read(t0, 10000)  
# Convert uint8 array to string, 
char(data) 
# close VXI11 session
vxi11_close(t0)

Limitations[edit]

For now,

  • it's not possible to connect more than one instrument per IP address (e.g. VXI11-GPIB gateways)
  • only instrument inst0 can be connected
  • setting timeout is not implemented (defaults: 10 seconds global timeout, 2 seconds read timeout)

GPIB[edit]

Configuring interface[edit]

For using GPIB you need to install and configure the linux-gpib kernel modules and libraries.

Example: basic use[edit]

Code: GPIB example
# Open GPIB instrument with ID 7 and set timeout to 1 second (see ibtmo / NI-488.2 Function Reference)
t0 = gpib(7,11)
# write to listener
gpib_write(t0, '*IDN?') 
# read from instrument, returns uint8 array
data = gpib_read(t0, 10000)
# Convert uint8 array to string, 
char(data) 
# close usbtmc session
gpib_close(t0)

Limitations[edit]

  • Setting minor, sad, send_eoi and eos_mode is not implemented yet.
  • Every read or write command opens and closes a new gpib session, since the linux-gpib session pointer is only valid for single command.

SPI[edit]

Configuring interface[edit]

SPI currently only works in linux.

instrhwinfo will display the avilable devices in the system

Code: SPI example
instrhwinfo("spi")

You will need read/write permissions to the device used.

Example: basic use[edit]

Example opening an performing IO on a device /dev/spidev0.0.

Code: SPI example
# open device
spidev = spi("/dev/spidev0.0", 'clockpolarity', 'idlehigh', 'clockphase', 'firstedge')

# I/O to device

data = writeAndRead (spidev, uint8([0x01 0x00]))

# close the device
clear spidev