GP-3 and GNU Plot (Linux)

Plotting with no Programming

The GP-3 has always been pretty multilingual. You can use it in Windows or Linux with just about any langauge you can think of. But I was trying to think how you could use it with Linux without really any programming. Well -- unless you count shell scripting as programming. The results were pretty interesting.

Keep in mind, if you want to use the GP-3 from Linux, you have plenty of ways to do it ranging from a library that you can link in or create a .so from, a Java object (that includes an example with a JavaScript interpreter, to a Gambas object). So this isn't necessarily the best way to do it but it does highlight the flexability of Linux and the GP-3. In particular, the classic Linux mind set is to use a chain of tools to get work done. And that's what we'll do. We'll join the GP-3, a variety of common Linux tools, GNU Plot, and a script found on the Internet to get real time streaming plots of data from the GP3 in about 5 minutes!

The first issue is sending data to the GP-3 and getting data back. Shell scripting is not ideal for this because it likes to deal with strings and the GP-3 wants compact and efficient binary data. But the shell -- BASH, at least -- is well up to the task with a little work. Becuase the GP-3 is a serial port (even the upcoming USB verions look like a serial port), communications isn't a problem. Or is it?

Linux assumes serial ports are hooked up to terminals (a legacy from its heritage with Unix systems). That means it wants to help us by interpreting backspaces and other characters and buffering lines of data. None of that helps the GP-3, of course. In addition, the GP-3 is faster than a common terminal (57600 baud). Luckily, the stty command can fix that for us:

stty -F /dev/ttyS0 57600 cs8 clocal crtscts ignpar raw min 1

This command takes the serial port (ttyS0; yours might be different) and turns on RTS/CTS, turns off all the other stuff, and tells it to expect data a character at a tim at 57600 baud and 8 bits with no parity.

Simple test:

    echo -en '\xd'>/dev/ttyS0

The GP-3 LED should come on. The -en tells echo to understand escape characters and don't emit linefeeds automatically. '\xd' is 0D hex which is the command to turn the LED on. The quotes are important here. With out them you'd need \\xd (because the shell would eat the backslash character before sending it to echo).

So writing to the GP-3 works. What about reading? There are three issues here. First, we need to keep the port open the whole time or we'll lose characters. Second, we need to only read a byte at a time. Third, we are going to have to convert those bytes to strings that the shell can work with.

The first part means we will need a script and we will do the work in a subshell that uses the serial port as stdin. That'll hold it open.

The second part is simple: use dd. The dd program is meant to read tape or disk blocks and move them somewhere else. But it is very flexible and can easily read a single byte out of the serial port and "copy" it to the standard output (where we can catch it and work with it).

The conversion is the trickiest part. The GP-3 will emit all kinds of unprintable characters. For example, the letter "A" is ASCII 65 (or 41 hex, if you prefer). What we need is a program that would read an "A" on its standard input and output 65 to its standard out stream. If we know the character is less than 128 you can simply say:

   printf "%d" "'A"

Try it. You'll get 65. But if you try it with an unprintable character over 127 you'll get a bad answer. Its more work, but od (short for octal dump) knows how to dump file data in lots of formats. Unfortunately it also adds other information (like the offset in the file which we don't care about). So try this:

    echo -n A | od -t u1

65 is in there, but so are the addresses, so:

   echo -n A | od -t u1 | sed -n "s/^.* //gp"

There! So using command substitution and some variables, we can write our script. Rather than just dumping out analog channel readings, the script will create a named pipe and write to it. That way it won't just fill your hard drive with data no one is reading. If the script is running for 10 minutes before somone reads the data, there will only be one stale value and the subsequent reads will be "fresh" which is good.

Here's the script:

# Simple example of providing analog readings via a named pipe
# This sends an analog value down the pipe and then 
# when the other side reads a value (ASCII text, one decimal count per line)
# the script loads up another value

# Usage:
# gp3volt port [channel [named-pipe]]

if [ $# = 0 ] 
   cat <&1
   gp3volt port [channel [named-pipe]]
   Where port is the GP3 serial port (e.g., /dev/ttyS0)
   channel is the A/D channel to read (0-4, default is 0)
   named-pipe is the name of the pipe to use (default ~/gp3-.pipe)
   More info:


if [ "$2" != "" ]
  then channel=$2
  else channel=0

if [ "$3" == "" ]
  then pipe=~/gp3-$channel.pipe
  else pipe=$3

# Clean up named pipe
function cleanup {
  if [[ -a $pipe ]]
    then rm $pipe

# set up serial port
stty -F $port  57600 cs8 clocal crtscts ignpar raw min 1
trap 'cleanup' INT TERM   # run cleanup on exit
# create named pipe
mkfifo -m 644 $pipe
while true
  (   # sub shell so we can keep the serial port open for reading
      # this code cheats. 30 is a hex number
      # but we know channel is always <5 and 30+ 0,1,2,3,4 is 
      # the same if 30 was hex or decimal!
  cmd=`expr 30 + $channel` # add as if 30 were decimal
  cmd=`echo -en "\x$cmd"`   # but here we treat cmd as hex
  printf %c $cmd >$port
  b0=`dd bs=1 count=1 2>/dev/null` 
  # this works because $b0 always <128 (actually always <4
  b0=`printf "%d" "'$b0"`
  # this could be >128 so...
  b1=`dd bs=1 count=1 2>/dev/null | od -t u1 | sed -n "s/^.* //gp"`
  v=`expr $b0 \* 256 + $b1`
  echo $v >$pipe
  ) <$port

Wow, that's a lot, but most of it is just housekeeping. If you followed the above discussion, the script is really straightforward. The cmd variable has the command to send to the GP3. The b0 and b1 variables hold the bytes that come back and these get converted into the v variable using simple math.

If you run the script and then do a "cat" on the named pipe file, you'll see a single A/D value. You'll also cause the script to load a new value into the pipe, but cat will have already given up by then. A good job for tail -f:

  tail -f ~/gp3-0.pipe

That will cause a stream of raw counts (one per line) to stream out. If all you want is to grab some data for a spreadsheet or manipulation with awk then you are done. But I wanted some pretty graphics.

I found this script that oddly enough takes data, one value per line and causes GNUPlot to plot it in real time! Just what I needed. Of course, like cat, it gives up the first time the pipe is empty, so we need tail -f again:

tail -f ~/gp3-0.pipe | 1 64 0 1024
The tail just gives us a steady stream of data. The arguments to the other script tell it that we have 1 stream of data, we want a window that shows 64 points of data at one time, and the data will range from 0 to 1024. Assuming the GP3 script is running, data will start whizzing across the plot window. The screen capture at the start of this page is showing a 3Hz square wave, so you can get an idea fo the speed. Not exactly an oscilloscope, but good enough for a chart recorder. If you need to keep the values for later analysis, look at the "tee" command. Of course, a real chart recorder would only get data at a predetermined interval, right? That's easy. We just need one final script:

# start GP3 server in the background /dev/ttyS0 0 gp3.$$ &
plotkill="kill $!"

# clean up
trap 'rm -f gp3.$$ ; $plotkill ; exit ' INT QUIT TERM
datafile=gp3.$$   # we need this because we might go to a subshell

while true
  cat $datafile
  sleep 1  # 1 second sample rate
  done | 1 60 0 1024
# of if you want a log file:
# done | tee data-log-file.txt | 1 60 0 1024

Here we go back to using cat to get a single sample and then sleep for a second (you can change the sleep command to wait less than a second or more. This time, I tell the plot window I want 60 samples in a window and with the same range). Of course, you might want calculate the voltage from 0 to 5V instead:


# start GP3 server in the background /dev/ttyS0 0 gp3.$$ &
plotkill="kill $!"
# clean up
trap 'rm -f gp3.$$ ; $plotkill ; exit ' INT QUIT TERM
datafile=gp3.$$   # we need this because we might go to a subshell

while true
  echo scale=4\; `cat $datafile` \* 0.004883 | bc
  sleep 1  # 1 second sample rate
  done | 1 60 0 5

But that's just showing off shell scripting! You could have done something similar with awk or Perl too or scaled the counts to some other unit like temperature.

So that was a quick half hour data recorder. There are many other ways you could have done it, but then that's Linux isn't it? Once you get used to thinking of analog data as just another data source, there is a lot you can do. Of course, you could modify the scripts above to do digital, or use the GP-3's counter. And just like turning on the LED, setting up the PWM output, pulses, etc. is a snap. Sometimes for something quick and dirty, scripting is just the thing.

Site contents © 1997-2018 by AWC, Houston TX    (281) 334-4341