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


* indicates required

Intuit Mailchimp