Interfacing Hardware with Mathematica

Zhe Hu
huzhe@iit.edu

Introduction

Once you get to appreciate the beauty of a programming language like Mathematica, you want to do everything with it. That includes playing card game (Fitch's card trick), going to movie together (movie marathon), and even making you coffee in the morning or feeding your cat.

Yes, Mathematica can do the latter by interfacing with external hardware through the digital input/output ports of your computer, i.e. Mathematica can toggle the digital signal lines of your computer's serial port, parallel port and even the USB port. In essence Mathematica, like many native (or aboriginal) programming languages, can talk with the "low level" hardware components of your computer through which gives you the ability to control the external pieces of hardware in the real physical world.

This ability in most part is due to its inherent "Link" technology, especially the .NetLink coming with the latest version of Mathematica that allows you to call functions from the native MS-Windows dynamic link library (DLL). Many hardware components on your PC are controlled by device drivers under the operating system. And these device drivers sometimes expose their functionality (e.g. in SDK packages) through DLL functions in MS-Window. As long as Mathematica can access the functions in the DLL, it is granted the capability of controlling and communicating with the hardware directly.

In this notebook, I showed some demonstrations of Mathematica code talking with the serial port, parallel port and USB device of your PC. To fully reproduce these demos, you need some electronic parts, which lie in abundance in a regular electronics lab and some working knowledge of electronics to assemble them, since I won't give step by step instructions on building these circuits. Again as you read on, you may not be able to resist the temptation to grab a soldering iron and make the circuits yourself. I won't be responsible for your burning the fingers by grabbing the soldering iron at the wrong end, or even worse, sticking it into the wrong place, such as a friendly and innocent standby who offered to hold your circuit board while you were soldering, which is by the way the main reason why we are in short of undergraduate lab assistants in our lab now.

However even if you follow the "DON'T TRY THIS AT HOME" sign that I formally put up here, you can still appreciate the limitless possible tasks that Mathematica can perform as you keep on reading further. Actually one of the purposes of compiling this notebook is to convince my labmate who is an expert on using LabView software to control various pieces of instrument in our lab that Mathematica is no weaker in this respect. So let's start the journey with Mathematica in the hardware-land.

Serial Port

Serial port is physically the 9-pin connector at the back of your desktop PC. It's also called "RS232" or "UART" by hardware people. In old days, you would plug your modem, keyboard and mouse in it. Later it is often used as the communication port between PC and your mobile device like HP calculator, PDA or LEGO infrared tower, etc. You will also find old oscilloscopes that facilitate the serial port communication, if you happen to be in an electronics lab. More information on the serial port ...

There is this wonderful Mathematica package by Rob Raguet-Schofield called SerialIO that probably uses MathLink technology to provide functions that can read, write and poll the serial port of your computer. It has not only the MS-Windows version, but also the Linux and the MacOS version as well.

Open the Serial Port

If you are running Window XP, you can go into Accessories/System Tools/System Information from the Start menu, within which you can look into "System Summary/Components/Ports/Serial" and find "Name: Communications Port (COM1)". This "COM1" here is the official name you will use under MS-Windows to access your serial port softly.

There is another important item in that pane called "Busy". Once you've opened the serial port (explain later) and refreshed this pane, this item will say "Busy: Yes". If it is busy in the beginning, it means that some other program has the serial port open and has already occupied this resource (for example Microsoft "ActiveSync" for your mobile device). Then you need to close that program to release the port before you can open it in Mathematica. Or even worse, you may have to restart Windows or disable/enable the serial port manually to bring the serial port back to "Busy: No" state.

To load the "SerialIO" package, you can either put it into Mathematica's "Application" folder and run  "<<SerialIO`" shown in the "Example.nb" coming with the package, or in my preferred way, load the "init.m" file of the package into Mathematica manually (your "init.m" shall be at a different location, change the following command accordingly):

<< "C:\\Documents and Settings\\Hu\\Desktop\\SerialIO\\Kernel\\init.m\
"

Since it probably uses MathLink technology, now a open-file-dialog will pop up asking you for the external (.exe) program under MS-Windows. Just rename the file "\\SerialIO\\Windows\\SerialIO" to "\\SerialIO\\Windows\\SerialIO.exe", then you can find it in that open-file-dialog and load the package successfully.

Names["SerialIO`*"]

The first step is to open the serial port (by its official name) before using to it.

ser = SerialOpen["COM1"]

Again if it displays a message saying "SerialOpen::failed: Could not open serial port (No error detected)", the port is either opened by another program or has already been opened by "SerialIO.exe". You need to go into Windows Task Manager to terminate that process and reload the package from the beginning. By the way, you do need to have a physical serial port at the back of your computer, which isn't true for some newer desktop PCs. There is this concept of virtual COM port (VCP), which emulates the physical serial port communication.  I am not going to discuss that here.

One important parameter of the serial communication is the "baud rate". You can't talk too fast to a small external device. This is how you set it.

SerialSetOptions[ser, "BaudRate" -> 9600]

Now that you have the handle "ser" to your serial port, you can read from/write into it directly in Mathematica. And remember to release the port after you finish using it: SerialClose[ser].

Talk with LCD

As described earlier, there are tons of hardware that you can communicate with using the serial port, i.e. RS232 serial protocol. Here is an example: the LCD by Scott Edward Electronics into which you can send the display characters (and control instructions) from the serial port directly.

In the hardware-land, a bug that weren't fixed before the hardware release is called a feature of the system. So controlling the hardware often means dealing with these features, in addition to the limitations of the devices in the real physical world (e.g. limited speed, power, memory size).

An old version of the (4x20 alphanumeric) LCD (BPK-000) has this weird feature that as you send characters to it continuously (using their serial sender program or HyperTerminal ), the characters fill line 1 and then jump to line 3, then line 2 and line 4. Later version (BPP-420L) corrected this problem. So our Mathematica code should hide these annoyance and provide a more intuitive abstraction layer for higher level (software) users, which is the spirit of most hardware programming.

Define LCD functions (for 4x20 alphanumeric LCD)

The following function definitions assume that you've successfully opened the serial port and the handle is stored in

ser

setCursor[address_] := 
      SerialWrite[ser, FromCharacterCode[{254, address}]]

clearScreen[] := 
        SerialWrite[ser, FromCharacterCode[{254, 1}]];

draw[address_, char_] := (setCursor[address]; 
        SerialWrite[ser, char];)

updateScreen[
        old_, new_, 
        screenframe_: {Range[128, 147], Range[192, 211], Range[148, 167], 
            Range[212, 231]}] := 
      Module[{pos}, 
pos = 
          Position[MapThread[SameQ, {old, new}, 2], 
            False]; 
  
MapThread[
          draw, {Extract[screenframe, pos], 
            Extract[new, pos]}]; 
Return[
          new];
]

makeFrame[str_, x_:  20, y_:  4] := 
      Partition[PadRight[Characters[ToString[str]], x*y, " "], 
        x]

printScreen[str_] := (clearScreen[]; 
        Fold[updateScreen, 
          makeFrame[" "], {makeFrame[str]}])

playFrames[frames_, t_:  0.1] := 
      Fold[(Pause[t]; updateScreen[#1, #2]) &, makeFrame[" "], 
        frames]

Use these functions

To display this text message on the LCD,

txt = "Mathematica            is               spoken \
here.";

You can preview the result

makeFrame[txt] // MatrixForm

printScreen[txt];

Here is what will be displayed on the LCD

More mathematical

printScreen[N[π, 79]];

Here is what will be displayed on the LCD

Say, I want to show animations like the movie Matrix effect on the LCD, where columns of letters flush down the screen. If I use printScreen to draw the animation frame by frame, the speed to update the screen (all 80 characters) is too slow. So the animation becomes flicking. The trick is to refresh only the location on the display where it needs to be changed in the new frame. It's very easy to implement such effect in Mathematica as shown in the updateScreen function.

The following function shifts down the letters in a certain column and fills the space with a character randomly chosen from between 0 to 255 (see manual page 5)

shiftFrame[frame_, column_] := 
      Module[{temp = 
            frame}, 
temp[[All, 
              column]] = Append[temp[[All, column]], 
                FromCharacterCode[Random[Integer, {0, 255}]]] // 
              RotateRight // Drop[#, -1] &; 
Return[
          temp]]

Now build multiple frames by randomly pick a column (between 1 to 20) to shift downward

frames = 
        FoldList[shiftFrame, makeFrame[N[π, 80]], 
            Table[Random[Integer, {1, 20}], {i, 600}]] // 
          Take[#, {1, Length[#], 3}] &;

Play time

playFrames[frames];

I hope it will play well on your LCD display. It is very cool. (see if you can watch "matrixEffect.gif")

Discussion

If you are as lucky as I am who found a spare BPP-420L lying in a dusty lab cabinet, you will find that there are three pins at the back that needs to be connected (+5V, GND, SER). Since we can harvest 5V power from the serial port, there is no need for a separate power supply. The whole thing just need to be plugged into the serial port of your PC. I even found a PCB board that were used for earlier projects and it fits into the back of the LCD perfectly. By the way, if you want to draw a PCB yourself, try http://www.expresspcb.com. They provide you with free drawing software within which you can design and order the board directly over the Internet. Here is my schematic for your reference.

If you'd like to interface the PC serial port with a 5V (TTL or CMOS) device, use DS275 or MAX232 which transform the voltage level, such that you can have Mathematica talk with the UART port of most of the MCUs. Hardware people might comment that up till now the signal is only going in one direction, i.e. from PC to the external hardware. There is no hand-shake or interrupt mechanism in the communication. However with this SerialReadyQ function, one can poll the serial port rapidly enough to receive data from the external hardware.

Do[If[SerialReadyQ[ser], temp = SerialRead[ser, 1]; Print[temp]; 
        SerialWrite[ser, temp]]; Pause[0.1], {30}]

This code implements an echo machine that sends back each byte that it receives from the serial port. We use it to test our TI-MSP430 MCU code for its UART communication.

Lastly, close the serial port before you leave.

SerialClose[ser]

Parallel Port

Parallel port is physically the 25-pin connector at the back of your desktop PC. It's also called "Printer Port" because the printer is usually connected to it. More information on the parallel port ...

Since the parallel port provides 8 digital input/output (DIO) lines, it is useful for controlling several things at the same time or serving as address/data lines to e.g. selectively turn on 16 reed relay switches (shown in the photo below). But the DIO line itself can only drive very small amount of current, so if you want to drive something in addition to small LEDs directly, buffer these lines with e.g. 74LS04, even stack two 74LS04 for larger currents (see if you can recognize them in the photo).

There is this wonderful "Inpout32.dll" (you can download it from here) that allows you to write to the parallel port under MS-Windows. The functions in the DLL were supposed to be called within those aboriginal programming languages compiled (.exe) program, but as Todd Gayley showed us (in the mathgroup discussion), Mathematica can access these functions too by the .NetLink technology.

Needs["NETLink`"]

\(\(ParallelRead = 
        DefineDLLFunction["Inp32", "inpout32.dll", "short", {"short"}];\)\)

\(\(ParallelWrite = 
        DefineDLLFunction["Out32", "inpout32.dll", "void", {"short", "short"}];\)\)

Make sure you put this "inpout32.dll" in the place where Windows can find it, e.g. under the "Windows" folder on your PC, otherwise type in its full path. This function "ParallelWrite", which wraps the "Out32" in the DLL, now allows you to write one byte to the parallel port from Mathematica directly. One more thing, you need to find out the parallel port address of your PC, which shows in hex and needs to be converted into decimal when you use ParallelWrite.

My parallel port address is 0x378, which is 888 in decimal

16^^378

ParallelWrite[888, 255]

Now you've just write 255 to the parallel port which sets all eight digital lines of your parallel port to be "1". They should show up as +3.3V or more on most PCs. It is just that simple and elegant.

These eight digital lines are so useful, I am not going to hamper your imagination by showing you any of my demos here.

USB Device

Both the serial and parallel port are great ways to have Mathematica interface with the external hardware. But few notebook PC is equipped with these ports nowadays. So we have to rely on the ubiquitous USB port. Again more information on USB ...

One elegant approach is to use the chips from www.ftdi.com or similarly the kit from www.dlpdesign.com. For example, you bought this DLP-USB245M, did a little bit wiring according to the data sheet (page 8, figure 1), plug it into your PC's USB port, have it detected and its USB device driver installed. Now you have eight digital input/out (DIO) D0 to D7 lines, which you can use just like the 8 lines from the parallel port. The trick is to set the chip in the "Bit-Bang" mode.

Now you may be wondering, yes these are nice features, free device driver, plenty of programming information and source code, but the DLL functions in this D2XX device driver are supposed to be called by the compiled native MS-Windows program written in those aboriginal (low-level) programming languages.

Don't worry. As we've shown in the previous section, with .NetLink technology, Mathematica can call these functions in the DLL as well. So you can actually configure and control your DLP-USB245M hardware in the Mathematica environment, i.e. to have eight DIO lines controlled by Mathematica directly.

Here is how you import these functions (for Bit-Bang mode) from D2XX into Mathematica.

Import the DLL Functions from FTD2XX.dll

Again make sure MS-Windows can find the DLL file "FTD2XX.dll".

Needs["NETLink`"]

FTListDevices = 
      DefineDLLFunction["FT_ListDevices", "FTD2XX.dll", "unsigned \
long", {"unsigned long", "System.Text.StringBuilder", "unsigned \
long"}]

FTOpenEx = 
      DefineDLLFunction["FT_OpenEx", "FTD2XX.dll", "unsigned \
long", {"string", "unsigned long", "out unsigned long"}]

FTClose = 
      DefineDLLFunction["FT_Close", "FTD2XX.dll", "unsigned \
long", {"unsigned long"}]

FTSetBaudRate = 
      DefineDLLFunction["FT_SetBaudRate", "FTD2XX.dll", "unsigned \
long", {"unsigned long", "unsigned long"}]

FTSetBitMode = 
      DefineDLLFunction["FT_SetBitMode", "FTD2XX.dll", "unsigned \
long", {"unsigned long",  "unsigned char", "unsigned \
char"}]

FTWrite = 
      DefineDLLFunction["FT_Write", "FTD2XX.dll", "unsigned \
long", {"unsigned long",  "char[]", "unsigned long", "out \
unsigned long"}]

FTGetBitMode = 
      DefineDLLFunction["FT_GetBitMode", "FTD2XX.dll", "unsigned \
long", {"unsigned long", "out unsigned char"}]

st = NETNew["System.Text.StringBuilder", 100];

Open the Device

If you have your DLP-USB245M connected and recognized successfully as a USB device, the following function should return 0 (success).

FTListDevices[0, st, BitOr[2, 16^^40000000]]

st@ToString[]

Now you can open the device by its official name

FTOpenEx[st@ToString[], 2, 
      handle]

If it opens successfully, the variable handle can then be used to refer to the device softly. The following function sets the chip into asynchronous bit-bang mode and all 8 DIO lines are set as digital output (check D2XX programmer's guide, page 72).

FTSetBitMode[handle, 255, 1]

Set the baud rate to be 9600

FTSetBaudRate[handle, 9600]

If all these functions return 0, we've successfully configured this usb device to run in the bit-bang mode now. Isn't it amazing that it is all done in Mathematica step by step.

Write to the DIO Lines

Since we've configured these eight lines to be output, we can say set them all to be digital "1" by

FTWrite[handle, {255}, 1, written]

written

And this one-byte was written and the signal was latched on the digital lines, just like the good old parallel port lines. If you have a voltage meter, you can measure high (+5V) voltage on D0 to D7 pins of this DLP-USB245M.

Talk to the Wireless Transceiver RF-24G

The real meat of this notebook is this demonstration showing how easy it is for Mathematica to test some hardware piece that confounds many hardware people. We got our Nordic RF-24G transceiver from www.sparkfun.com. And there are plenty of discussions in its forum and also this article about how difficult, though very fun,  it is to configure and use this small wireless module successfully.

This demonstration shows that Mathematica (though very high level language) can interface such hardware device much faster and simpler than the dedicated MCU coding. It is very useful at the early stage of the prototype design during which you just want to quicly see whether the thing works or not.

According to its datasheet, to run the RF-24G module in the ShockBust mode, we only need DATA, CLK1, CS (three lines to clock in the configuration bits) and then set CE to turn on the receiving. When the correct packet is received, DR1 line goes high, we then can use CLK1 to shift the packet out of the DATA line. So we can connect these four digital lines to the DLP-USB245M as follows: D0  -> DATA;  D1 -> CLK1;  
      D2 -> CS;  D3 -> CE. Since RF-24G only accepts 3V logic, we need to wire 3.3V (provided by MAX604 regulator) to DLP-USB245M according to the its datasheet (page 9, figure 3). Here is the RF-24G, DLP-USB245M and MAX604 together on a breadboard.

To configure RF-24G, just shift in the data bit by bit by the clock signal (if you've heard about shift-register idea). It is easy to prepare a table of all the signal lines before hand and then flush out of the USB port. A helpful function

extend[signal_List,  mag_] := 
        Map[Table[#, {mag}] &,  signal] // Flatten;

First after power up the RF-24G, let's configure it into the "direct receive mode" (see its datasheet).

directReceiveMode = (Table[0, {i, 20}]~
          Join~(PadLeft[
                extend[{0, 0, 0, 0, 1, 1, 1, 1}~
                      Join~{0, 0, 0, 0, 0, 0, 0, 1}, 3] + 
                  (2^1)*Flatten[Table[{0, 1, 0}, {i, 16}]], 200, 0] + (2^2))~
          Join~Table[0, {i, 20}]~Join~{(2^3)})

If you are the hardware people, you can "see" clearly that these numbers once sent through lines D0 to D3: raise CS, delay a little bit, clock in 16 configuration bits (0b.0000.1111.0000.0001), lower CS, delay a little bit, raise CE.

Assuming you have already open the USB device, DLP-USB245M:

handle

Set all eight DIO lines to be output

FTSetBitMode[handle, 255, 1]

Now write to the RF-24G through the USB device

FTWrite[handle, directReceiveMode, Length[directReceiveMode], 
      written]

written

Now if you toggle the DATA and CLK1 line to be input lines

FTSetBitMode[handle, 255 - (2^0) - (2^1), 1]

And if you hook two oscilloscope probes to DATA and CLK1 respectively, you can see digital signal going up and down wildly on these two lines. These are wireless packets coming in the air that are directly received by RF-24G. Now we have configured the device successfully, we have the confidence to move on to configuring the device for the "ShockBurst mode", which needs 120 bits to be shifted in. Again prepare the bit pattern before hand:

payload = 
      IntegerDigits[8, 2, 8]~Join~IntegerDigits[8, 2, 8]

(* 
      receive channel 1 address bit pattern 1111 *) 
address = IntegerDigits[15, 2, 80]

addressCRC = 
      IntegerDigits[4*(2^2) + 1*2 + 1, 2, 8]

device = 
      IntegerDigits[1*(2^6) + 3*(2^2) + 3, 2, 8]

rf = IntegerDigits[4*2 + 1, 2, 8]

bitpattern = 
      Join[payload,  address, addressCRC,  device, rf]

clk = Table[{0, 1, 0}, {i, Length[bitpattern]}] // Flatten

shockBurstReceiveMode = 
      Table[0, {i, 20}]~
        Join~(PadLeft[(2*clk + extend[bitpattern, 3]), 400, 0] + 4)~
        Join~Table[0, {i, 20}]~Join~{8}

Again set DATA and CLK1 line to be output.

FTSetBitMode[handle, 255, 1]

FTWrite[handle, shockBurstReceiveMode, Length[shockBurstReceiveMode], 
      written]

written

Now the RF-24G should be listening on its address for coming 8-bit packet.

Here is the fun part. I call it "wireless fishing". You can hook a voltage meter on line DR1 and just sit there waiting. Sooner or later, this line will go high to 3.3V indicating a "valid" packet has been catched. Now you can shift the packet out from the DATA line. Set DATA line as input and since it is a 8-bit packet as soon as you clocked out these 8 bits, the DR1 line drops low again.

FTSetBitMode[handle, 255 - 1, 1]

Do[FTGetBitMode[handle, char]; 
      FTWrite[handle, {0, 1, 0}*2 + (2^3), 3, written]; 
      Print[IntegerDigits[char, 2, 8][[-1]]], {i, 8}]

It is certainly not some kind of mysterious message from the outer space. Because we set only 4-bit address, it is very easy to get false detections of the wireless signal. You can increase the address bit numbers (up to 40 bits long) and now you have to wait longer. As long as you have patience, you will catch the "big fish". Anyway it isn't difficult at all to configure and run this wireless module RF-24G in Mathematica. I've spent too much time "fishing". I have to close the port and leave now.

FTClose[handle]

Summary

I shall say this notebook is "based on a true story". My real world project involves controlling a custom made neural stimulation chip and the corresponding hardware system for neural stimulation and recording. That chip uses three-wire synchronous serial communication protocol (Neural Talk). It is easy to prepare the bit pattern in Mathematica and send it out by this USB device DLP-USB245M.

We also have a new digital microscope that takes high magnification digital picture at various focal plane and stacks the images together to enhance for its depth of field greatly . The software package that moves the microscope lens, takes the picture and composes the filtered image is very expensive. Since the microscope lens controller is connected to the serial port and the digital camera is connected to the USB port of the PC. I have the impression that once Mathematica can access their DLL functions correctly, all the image collection can be done within Mathematica in a few lines of code and then Mathematica's image processing capability can be utilized to process the final image (which is probably not too much complex than a high pass filtering).

To access the functions in a DLL using .NetLink technology, it is important to define the arguments and return type correctly. The documentation on "Specifying Arguments and Return Values" is very helpful.

Links

http://www.dlpdesign.com/

http://www.sparkfun.com/commerce/categories.php

http://www.ftdichip.com/