2-Way Communication with Microcontrollers using C and Julia
In this post, we will be covering an Arduino UNO project which sets up a great base for learning about serial data communication with almost any microcontroller with LibSerialPort.jl, Asynchronous programming, command line interfaces in Julia with ArgParse.jl. It also provides insights on creating data packets for communication.
Communication between Microcontrollers and Julia
For communication between Microcontrollers and Computer, we use various communication protocols like UART, I2C, SPI which are just different methods to send/receive data packets. For this blog we will use a simple Arduino UNO USB connector that simulates a serial port and sends data to/from a microcontroller.
We can think of a serial port as a pipeline from microcontroller to computer where microcontroller adds to the pipe one bit at a time linearly. Here, we have two pipelines that establish send and receive, where one pipeline sends data from the Arduino to the Computer and the other pipeline sends data from the Computer to the Arduino.
Project: What does it do?
We are using BMP100 and DHT11 sensors with Arduino UNO which provide information on temperature, humidity, pressure, altitude. We send this information to the computer in form of a data packet when the push button is pressed. Then a program on computer receives the data packet and processes it. Based on the inputs it sends a response packet to the Arduino which processes the response packet and based on the results it changes LEDs power. In case the inputs about temperature, pressure, etc are outside of a certain range, their corresponding LEDs are lit.
Arduino’s programming is done in C through the Arduino IDE. It’s possible to wrap the arduino’s c library and provide an interface to do the Arduino programming in Julia but Arduino’s IDE is neater and more streamlined.

some messy wiring, but so cool!
Arduino’s Program
We code the UNO through the Arduino’s IDE which provides a streamlined experience of programming Arduino in c and we use several libraries here: Adafruit_BMP085 and DHTStable provide access to BMP and DHT, Wire for I2C connection to BMP, and RegExp by Nick Gammon for regular expressions to identify response packets. Every Arduino program has setup() and loop() methods, where setup() sets up the base of the program and loop() sets up code that runs repeatedly.
The loop contains two major components: a data receiver and a data sender from the microcontroller. Program code for the arduino code can be found here: Link
Wiring diagram is as follows:

Diagram of Arduino’s wiring with BMP and DHT
Julia’s Program for Processing and Response system
After every push button press as shown in the diagram, a data packet similar that’s shown below is sent to the serial port from arduino to computer. It is uniquely identifiable and holds information on packet number, temperature, humidity, pressure, altitude.
“FRAMESTART:PACKETNUM:0,TEMPERATURE:20.60C,HUMIDITY:59.00%,PRESSURE:98363Pa,ALTITUDE:249.75m,FRAMEEND\n”
We have setup a producer that gets the data from the serial port and pushes it to the channel. The data in Channel is accessible to bufferreducer, which processes the data and creates the response. More details on Channel and Asynchronous programming could be found here: Link
LibSerialPort.jl is used for accessing the serial ports which is wrapper of mature C library with same name. We also use ArgParse for setup of Command Line Interface that can take like shown below and has support for default values and custom parsing types:
ashwani@user:~/serial-jl$ julia serial-access.jl - tlow 15 - thigh 50
┌ Info: Parameters:
│ pargs =
│ Dict{String, Any} with 8 entries:
│ "hlow" => 10
│ "thigh" => 50
│ "plow" => 5000
│ "hhigh" => 80
│ "phigh" => 90000
│ "ahigh" => 1000
│ "tlow" => 15
└ "alow" => 200
More details about ArgParse.jl and developing command line interfaces can be found here: Link
The buffer reducer is the main function that processes the frames and sends the response. Furthermore, it uses Regex to identify temperature, humidity, pressure, altitude and check whether they are within or not within a range. In cases where the data from a sensor is outside of a predefined range, we add the instruction to turn on the LED for that sensor.
Here we have an example of a response that tells us that it is a response to packet marked 12, and the data indicates that all the readings for various attributes were out of range and they need to be marked as HIGHs as an alert.
RESPONSESTART:RESPONSENO:12,DATA:1111,RESPONSEEND\n
Julia’s Code:
// Imports
using LibSerialPort
using Distributed
using ArgParse
// holds setup for command line
include("argparsecode.jl")
@info "Parameters:" pargs
portname = "/dev/ttyUSB0"
baudrate = 9600
// Opens a serial port from where we get data
LibSerialPort.open(portname, baudrate) do sp
@info "Port Opened successfully!!"
// This gets data from serial port and checks every 100ms
// if there is new data
function producer(c::Channel)
@info "Buffer Collector Started!!"
while(true)
if bytesavailable(sp) > 0
d = String(read(sp))
push!(c, d)
end
sleep(0.1)
end
end
chnl = Channel(producer);
@info "Channel Created!!"
// response system
function bufferreducer(sp)
@info "Buffer Reducer Started!!"
// holds data in case there are residuals or length of data
// is below a certain range
buffercollect = ""
while(true)
// take! gets data from serial port and add to buffer
buffercollect = buffercollect * take!(chnl)
# println("Buffer Reducer:", length(buffercollect))
if(length(buffercollect) > 100)
// regex to identify frame
reg = r"FRAMESTART:PACKETNUM:(?<packetnum>\d+),TEMPERATURE:(?<temperature>(.*))C,HUMIDITY:(?<humidity>(.*))%,PRESSURE:(?<pressure>\d+)Pa,ALTITUDE:(?<altitude>(.*))m,FRAMEEND\n"
a = match(reg, buffercollect)
if(a === nothing)
println("No match found")
continue
end
// if packet found, we parse our data
@info "Packet Found:" a[:packetnum] a.match
temp = parse(Float32, a[:temperature])
hum = parse(Float32, a[:humidity])
pres = parse(Int32, a[:pressure])
alt = parse(Float32, a[:altitude])
// response creation
resp = """RESPONSESTART:RESPONSENO:$(a[:packetnum]),DATA:$(Int(temp < tempr[1] || temp > tempr[2]))$(Int(hum< humr[1] || hum> humr[2]))$(Int(pres < presr[1] || pres> presr[2]))$(Int(alt< altr[1] || alt > altr[2])),RESPONSEEND\n"""
@info "Response:" a[:packetnum] resp
write(sp, resp) // send data to arduino
// remove processed data from buffer
buffercollect = buffercollect[a.offsets[end]+16:end]
end
sleep(0.1)
end
end
bufferreducer(sp)
end
Results
We can run the processing system with the below shown method and as you can see we can define custom ranges for various types of input data. We specified temperature parameters hence they got updated, otherwise defaults are to be used.
ashwani@user:~/serial-jl$ julia serial-access.jl --tlow 15 --thigh 50
┌ Info: Parameters:
│ pargs =
│ Dict{String, Any} with 8 entries:
│ "hlow" => 10
│ "thigh" => 50
│ "plow" => 5000
│ "hhigh" => 80
│ "phigh" => 90000
│ "ahigh" => 1000
│ "tlow" => 15
└ "alow" => 200
Let’s see the results now where we shown back and forth communication for 2 data packets which were generated by push button press on the arduino board itself:

Results from Julia’s processing system named serial-access.jl

LEDs updated based on 0010 response from response system as shown above. Conclusion
An important component of communication between microcontrollers and computer is the communication channel, which allows both microcontrollers and Julia to access each other’s data by reading/writing from the same channel of communication. This simple example shows how to setup 2-way communication between microcontroller and computer using serial ports. Creation of uniquely identifiable data packets helps a lot in communication too.
More details on the project can be found here. Link