Arvont Hill: 2008

Sunday, December 7, 2008

Using Python for Test & Measurement

I picked up the November 2008 issue of Linux Journal after seeing it had an article about using Python for scientific computing. As I read through the article I learned about two modules, numpy and SciPy , that provide "extended" capabilities for math and science operations such as matrix calculations, Fourier transformations or numerical integrals. The article also mentioned two other modules named matplotlib and IPython. Together these 4 packages provide a Python programmer the ability do many scientific calculations and plot results all from a powerful interactive environment similar to matlab with all the power of Python.

Upon browsing the SciPy website, I discovered there were a number of "cookbooks" for using SciPy to do various tasks. The Data Acquisition with NI-DAQmx cookbook interested me because of my present job and familiarity with National Instruments products. I checked it out and decided to get a small data measurement system setup using python and a NI USB-6009 multi-function DAQ module on my Vista Home Basic laptop.

Here is what my setup looks like:

  • python 2.5.2
  • numpy 1.2.1 for python2.5
  • scipy 0.6.0 for python2.5
  • matplotlib 0.98.3win32
  • ipython 0.9.1
  • PyReadline 1.5 (Optional: This provides tab completion in Windows. It also provides colored text in an ipython window.)
  • NI-DAQmx 8.7.1f3 (most recent version should be ok as well)
  • NI USB-6009 Multifunction DAQ

  • This setup also relies on the ctypes module which is a default module in python 2.5. If you are using python 2.4 or less you may have to install this module.

    The example code found on the SciPy website shows an example that takes a C example provided with the installation of DAQmx and re-writes it using ctypes and numpy. My system copies this example and turns it into a module to be used by a higher level script that will acquire data and plot the data.

    Here is the module I named contAcquireNChan

    #contAcquireNChan.py

    import ctypes
    import numpy

    nidaq = ctypes.windll.nicaiu # load the DLL

    ##############################
    # Setup some typedefs and constants
    # to correspond with values in
    # C:\Program Files\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include\NIDAQmx.h

    # the typedefs
    int32 = ctypes.c_long
    uInt32 = ctypes.c_ulong
    uInt64 = ctypes.c_ulonglong
    float64 = ctypes.c_double
    TaskHandle = uInt32
    written = int32()
    pointsRead = uInt32()

    # the constants
    DAQmx_Val_Cfg_Default = int32(-1)
    DAQmx_Val_Volts = 10348
    DAQmx_Val_Rising = 10280
    DAQmx_Val_FiniteSamps = 10178
    DAQmx_Val_GroupByChannel = 0
    DAQmx_Val_ChanForAllLines = 1
    DAQmx_Val_RSE = 10083
    DAQmx_Val_Volts = 10348
    DAQmx_Val_ContSamps = 10123
    DAQmx_Val_GroupByScanNumber = 1
    ##############################

    def CHK(err):
        """a simple error checking routine"""
        if err < 0:
            buf_size = 1000
            buf = ctypes.create_string_buffer('\000' * buf_size)
            nidaq.DAQmxGetErrorString(err,ctypes.byref(buf),buf_size)
            raise RuntimeError('nidaq call failed with error %d: %s'%(err,repr(buf.value)))

    # initialize variables
    taskHandle = TaskHandle(0)
    min = float64(-10.0)
    max = float64(10.0)
    timeout = float64(10.0)
    bufferSize = uInt32(10)
    pointsToRead = bufferSize
    pointsRead = uInt32()
    sampleRate = float64(10000.0)
    samplesPerChan = uInt64(2000)
    chan = ctypes.create_string_buffer('Dev1/ai0')
    clockSource = ctypes.create_string_buffer('OnboardClock')


    data = numpy.zeros((1000,),dtype=numpy.float64)


    # Create Task and Voltage Channel and Configure Sample Clock
    def SetupTask():
        CHK(nidaq.DAQmxCreateTask("",ctypes.byref(taskHandle)))
        CHK(nidaq.DAQmxCreateAIVoltageChan(taskHandle,chan,"",DAQmx_Val_RSE,min,max,
            DAQmx_Val_Volts,None))
        CHK(nidaq.DAQmxCfgSampClkTiming(taskHandle,clockSource,sampleRate,
            DAQmx_Val_Rising,DAQmx_Val_ContSamps,samplesPerChan))
        CHK(nidaq.DAQmxCfgInputBuffer(taskHandle,200000))

    #Start Task
    def StartTask():
        CHK(nidaq.DAQmxStartTask (taskHandle))

    #Read Samples
    def ReadSamples(points):
        bufferSize = uInt32(points)
        pointsToRead = bufferSize
        data = numpy.zeros((points,),dtype=numpy.float64)
        CHK(nidaq.DAQmxReadAnalogF64(taskHandle,pointsToRead,timeout,
                DAQmx_Val_GroupByScanNumber,data.ctypes.data,
                uInt32(2*bufferSize.value),ctypes.byref(pointsRead),None))

        print "Acquired %d pointx(s)"%(pointsRead.value)
        return data

    def StopAndClearTask():
        if taskHandle.value != 0:
            nidaq.DAQmxStopTask(taskHandle)
            nidaq.DAQmxClearTask(taskHandle)
    I wanted to capture a time varying signal and plot the time based signal as well as its spectrum. I created a 100Hz signal with a PIC16F690 that I had on my bench and connected the output to ai0 on the USB-6009. Then I ran this script, measure.py, that calls the contAcquireNChan module. The script plots the results using pylab and also returns that data read from the USB-6009 as a numpy array.



    #measure.py

    from contAcquireNChan import *
    from pylab import *
    import time

    def acquire(points):
        times = []
        SetupTask()
        StartTask()
        data = ReadSamples(points)
        acqTime = points/sampleRate.value
        StopAndClearTask()
        t = linspace(0,acqTime,points)
        clf()
        plot(t,data)
        axis([0,acqTime,-10,10])
        ylabel("Volts")
        xlabel("time (sec)")
        grid(True)
        return data


    I made another script that acquired data and then plotted the spectrum of the same 100Hz signal.



    #plot_example1.py

    from scipy import *
    from pylab import *
    import time


    from contAcquireNChan import *


    def acquire(points):
        SetupTask()
        StartTask()
        tstart = time.time()
        data = ReadSamples(points)
        StopAndClearTask()
        return data

    #Capture 600ms of data
    sample_rate=sampleRate.value
    t=r_[0:0.6:1/sample_rate]
    N=len(t)
    s=acquire(N)
    S=fft(s)
    f=sample_rate*r_[0:(N/2)]/N
    n=len(f)
    clf()
    plot(f,abs(S[0:n])/N)
    show()
    grid(True)

    Sunday, August 3, 2008

    More iRobot Create

    Virtual Wall Search: Trial 1

    The goal of this project was to have the iRobot Create robot spin around until it sees an infrared signal from a "Virtual Wall". Upon seeing the virtual wall signal the Create will stop. After a quick test I had success and changed the requirements so that the Create would always try to keep the virtual wall in its sights.

    I started out by writing a crude python module that would allow me to send iRobot Open Interface (OI) commands from the host computer. Not only does the module send commands but it also processes sensor data transmitted from the Create. So the host computer is actually the brains of the operation.

    Another thing I did was to use my xbee 802.15.4 modules as a wireless serial connection between the host computer and the Create. So now I have remote control of the robot. Check out some pictures here and here.

    Here's the function that's doing all of the work. Python syntax is pretty similar to other languages with one difference...indentations matter and they are used to mark off different blocks of code. If you can't read code there's a translation at the bottom of the post.

    def spinForVirtualWall(debug):
        packetId = 13
        ir = readPacket(packetId)
        time.sleep(0.1)
        spinCw()

        while 1:
            while ir[0] != 1:
                ir = readPacket(packetId)
                if debug:
                    print "ir = "+str(ir[0])
            stopDrive()

            while ir[0] == 1:
                ir = readPacket(packetId)
                if debug:
                    print "ir = "+str(ir[0])
            spinCw()



    First the robot looks for the virtual wall. If it doesn't see it it starts spinning clockwise and stops when (or if) it sees a virtual wall. After stopping, the robot continues looking for the virtual wall signal. If the robot loses sight of the signal then it will continue spinning clockwise until it sees the virtual wall again.

    Tuesday, June 3, 2008

    Virtual Wall

    I built a virtual wall from these plans to see if I could program the iRobot Create to follow a beacon. The virtual wall puts out an infrared signal just like a TV remote control that the iRobot Create can see. I wrote a program in Python on my mac that controls the robot. When the robot sees the virtual wall it stops, when the virtual wall moves the robot starts spinning again to look for the virtual wall.

    Next step...add an ultrasonic sensor and change the program so that the robot can follow the virtual wall in two dimensions instead of just spinning around.

    Sunday, May 18, 2008

    Wubi+Hardy Heron

    Ubuntu 8.04 Hardy Heron came out and I wanted to check it out to see what was new. I only had one machine that was fast enough to evaluate the distro, my Gateway ML3109 laptop with a Celeron M processor and ATI Radeon graphics. It was running Vista and I didn't want to fool around with making new partitions or fooling around with a bootloader. However, after reading an ars technica review of Hardy Heron, I discovered I could use Wubi to install the distro on my laptop as if it was a normal Windows program. Better yet I could easily just uninstall it as well.

    So I ran the installer, the installer downloaded about 500MB or so of files. The next time I booted up the Windows boot loader was presented and gave an option of Ubuntu.

    I had played around with 7.10 on an old P3 with 384MB of RAM and was satisfied with it. I was anxious to see what 8.04 could do on the faster processor.

    Here's my first impression after using it

    • Flash: I wanted to watch a TV show on Hulu but when I got to the landing page it said I was missing a plug-in. I installed the missing plug-in from Firefox and I was soon watching my show.

    • DVDs: This installation can't play DVDs by default. I had to install a bunch of different packages to get a DVD to play on the included Totem Movie Player. There are probably some copyright issues that these packages are excluded. I got hints on what to install from the Ubuntu Help program under the heading Playing DVDs. I followed the steps there to confirm that the DVD would play. Step 2 tells you to run the install-css.sh script. Be careful this will only work if you have the libc6-dev-i386 and libc6-i386 packages installed.

      After I knew I could play a DVD I installed VLC using Synaptic Package Manager. This is a much better choice for playing videos.

    • Compiz: This is a package that let's you do a bunch of visual effects. I wanted to try out the cube so I did some research and found out how to configure it. To get it to work I had to install restricted ATI drivers to take advantage of the 3D capabilities of my graphics card.

    • Sound: The sound volume was low compared to what I was getting with Vista. The DVD audio and other audio seem to be played through the PCM channel on the mixer. A google search gave me hint on how to open up the mixer to adjust the PCM volume. If you double-click the speaker icon it will open the full mixer. I adjusted PCM to full and Master to full and the sound is now adequate. I pretty much have to do the same thing on Vista to get decent sound as well.

    Tuesday, March 25, 2008

    iRobot Create: Logitech Harmony 520 Remote Control


    I bought a iRobot Create programmable robot to play around with and learn a little something about robotics. I had always wanted to have my own robot to play around with and the iRobot seemed to be the best option for me. I didn't want to deal with the mechanics or construction, and I wanted to focus on the modularity of the platform and the software interface.

    One of the first mini-projects I worked on was controlling the Create with a universal remote programmed like the standard iRobot Roomba remote.






    Logitech Harmony 520