EPICS serial communication with Arduino


If you’ve followed the instructions here and here, you now have a Raspberry Pi capable of running EPICS+ASYN+StreamDevice, and an Arduino listening for serial commands to control an LED and read-back the voltage drop across an adjacent photo-resistor.  Now we need to add the glue to connect these two halves of the project.

To do this we need two things.  We need to tell EPICS how to perform the appropriate communications with the Arduino.  That is, EPICS needs to know how to build the necessary serial string, and which serial port to send it from.  In addition, we need an EPICS database that defines the Process Variables that will be exposed to the network.

Firstly though, we have to create a new project.

Creating a project

mkdir ~/Apps/epics/helloWorldIOC
cd ~/Apps/epics/helloWorldIOC
makeBaseApp.pl -t ioc helloWorldIOC
makeBaseApp.pl -i -t ioc helloWorldIOC

As before, this last command will ask for the name of the application. Leave this blank, and hit enter.

Now edit configure/RELEASE, and add the following lines so that it knows where to find ASYN and StreamDevice.

ASYN=/usr/local/epics/modules/asyn
STREAM=/usr/local/epics/modules/stream

Protocol File

For this project, the protocol file needs to provide a function that tells EPICS what serial strings to send when their associated PV’s process, and what serial strings to expect as a reply from the Arduino.  Create the following file, called “arduino.proto” in ~/Apps/epics/helloWorldIOC/helloWorldIOCApp/Db/

Terminator = LF;
get_analog {
    out "R";
    in "R %f";
    ExtraInput = Ignore;
}
set_digital {
    out "W%d\n";
    ExtraInput = Ignore;
}

The first line tells EPICS how to terminate strings, and this matches the expectations of the Arduino Serial code.

The “get_analog” function will be used to get the value of the photo-resistor, and tells EPICS to begin by sending “R” over the serial port, and then to expect a string formatted as “R %f” in reply. It is the numerical value of %f that will be stored in the EPICS PV.

The “set_digital” function works in a similar way, and is used to send a value to the LED.  That is, when an EPICS “put” is used to send a value to the LED PV, this function will send it to the Arduino in the format “W%d\n”.

Note that these two functions are replicating structure originally uploaded in the Arduino sketch.

Database definition file

In the same directory as above (i.e., ~/Apps/epics/helloWorldIOC/helloWorldIOCApp/Db/), create a file called arduino.db with the following contents:

record(ao, led:set) {
    field(DESC, "Arduino digi pin 11")
    field(DTYP, "stream")
    field(OUT, "@arduino.proto set_digital() $(PORT)")
    field(DRVL, "0")
    field(DRVH, "255")
}
record(ai, photo:get) {
    field(DESC, "Photo diode's output")
    field(DTYP, "stream")
    field(INP, "@arduino.proto get_analog() $(PORT)")
    field(SCAN, ".5 second")
}

This defines a database with two records — one analog output (ao) called led:set for controlling the value of the LED, and one analog input (ai) called photo:get for reading the value of the photo-resistor voltage drop.  Each record contains a “DESC” field that provides a simple description of its purpose and a “DTYP” field defining the type of the data; in this case these are “stream” devices.

Notice the input (INP) and output (OUT) fields.  These contain three pieces of information — the location of the protocol file (@arduino.proto in this case), the function to use from that file, and the port over which to send communications.  In this case, the port is left defined as a variable that will be set when the IOC boots.

In addition the led:set record also limits the values to lie between 0 (DRVL) and 255 (DRVH) to prevent excessive values being sent to the Arduino.  Remember that the sketch also contained a limit, and so these lines aren’t strictly necessary, but defensive coding is wise when using EPICS.

Environment preparation

In the same directory as the new database and protocol files, there is a Makefile.  Edit this, and after the line that says, #DB += xxx.db, add the following so that EPICS loads the database definition on boot.

DB += arduino.db

Now move to ~/ Apps/epics/helloWorldIOC/helloWorldIOCApp/src and edit the Makefile you find in there. After the line that says, helloWorldIOC_DBD += base.dbd, add the following,

helloWorldIOC_DBD += asyn.dbd
helloWorldIOC_DBD += stream.dbd
helloWorldIOC_DBD += drvAsynIPPort.dbd
helloWorldIOC_DBD += drvAsynSerialPort.dbd

These lines add various database record definitions that EPICS needs to load the appropriate ASYN and StreamDevice functionality.

Near the bottom of the Makefile, where hellowWorldIOC_LIBS is defined, add the following,

helloWorldIOC_LIBS += asyn
helloWorldIOC_LIBS += stream

Now, move to ~/Apps/epics/helloWorldIOC/iocBoot/iochelloWorldIOC

Edit st.cmd.  After “> envPaths” (near the top), add the following,

epicsEnvSet(STREAM_PROTOCOL_PATH,"helloWorldIOCApp/Db")

After the line, ” helloWorldIOC_registerRecordDeviceDriver pdbbase”, add the following,

drvAsynSerialPortConfigure("SERIALPORT","/dev/ttyACM0",0,0,0)
asynSetOption("SERIALPORT",-1,"baud","115200")
asynSetOption("SERIALPORT",-1,"bits","8")
asynSetOption("SERIALPORT",-1,"parity","none")
asynSetOption("SERIALPORT",-1,"stop","1")
asynSetOption("SERIALPORT",-1,"clocal","Y")
asynSetOption("SERIALPORT",-1,"crtscts","N")

These lines configure a serial connection called “SERIALPORT”, linked to /dev/ttyACM0 (where my Arduino is connected), and then set all the options so that the serial communications works in the way that the Arduino expects. Now that this connection has been defined, it can be used in place of the $(PORT) variable referred to in the database definition file. This is done by adding the following line directly after the lines you just wrote,

dbLoadRecords("db/arduino.db","PORT='SERIALPORT'")

In this same file (st.cmd), change any references to $(TOP) to /home/pi/Apps/epics/helloWorldIOC, and any references to $(IOC) to iochelloWorldIOC.

Final step!

Now change to ~/Apps/epics/helloWorldIOC and run “make”.  Then,

chmod +x ./iocBoot/iochelloWorldIOC/st.cmd

And we’re done!

Run the following to get your IOC up and running,

./bin/linux-arm/helloWorldIOC iocBoot/iochelloWorldIOC/st.cmd

From another machine on the network you should be able to caput to led:set to change the LED brightness, and caget photo:set to see the brightness measured by the photo-resistor.

Screen Shot 2015-12-24 at 9.15.20 PM


Leave a comment

One thought on “EPICS serial communication with Arduino