Interfacing LINUX with microcontrollers
Introduction
I am increasingly asked to work on small spare time projects where a user needs to control some device over the INTERNET. Recently, a friend needed to control heater relays and measure the temperature of its geographically distant secondary house. Another case relates to the control of a pan tilt home monitoring camera. A last one is the control of an old XY plotter DACs.
In both applications, the user wants to access the system over a web browser using HTTP. From the user standpoint, one benefit is that no specific client software is required (except a web browser, of course). That is, the user can access the control system interface from any terminal at home or work, including its mobile phone. Another advantage is that no modification is required to an existing network infrastructure as HTTP is usually well integrated in enterprise infrastructure.
On the device side however, implementing the whole protocol stack required to handle HTTP is more CPU and memory demanding than simpler protocols. It requires efforts to be done on small microcontrollers, leaving few resources for extra features. Plus, it is likely the user requires more features to be added as the systems proves to work: HTTP based authentication, email - or even SMS - based notification ...
The solution I adopted is to let a resourceful board handle the outside world connectivity, while a microcontroller deals with acquisition and actuation related tasks.
Currently, the connectivity board is a RASPBERRY PI running a LINUX system:
The microcontroller board is a TEENSY 3.0:
http://www.pjrc.com/store/teensy3.html
While other boards could have fit, they were chosen for their relatively low cost (42$ and 19$ respectively) and their support from an opensource community. The setup is summarized by the following picture:
This article describes the userspace software mechanisms offered by LINUX to interface with microcontrollers. The MASL API is defined and implemented in a library to favor reusability across projects.
Digression
Before digging into the MASL details, let's digress for a moment and present the other software components.
I do not give much details on how to setup a LINUX system for the RASPBERRY PI. There are already some guides, for instance:
http://www.raspberrypi.org/downloads
Any distribution should do, but I personnally use LFS, a tool to build LINUX systems from scratch that I developped recently. The repository is available here:
I recommend CROSSTOOL-NG for building a toolchain, as it supports RASPBERRY PI:
Regarding the HTTP server. I started with serveral libraries to enable HTTP support direclty inside the application:
http://www.gnu.org/software/libmicrohttpd
http://code.google.com/p/mongoose
As user asked for more features, I moved to NODEJS:
NODEJS allows one to compose a dedicated self contained HTTP server quite easily via a system of software packages. I could find all the packages I needed, especially one to send emails over various transport layers. While NODEJS is not a light solution, it runs very well on the RASPBERRY PI.
It is one main advantage of using LINUX on a decent hardware: a developper benefits from all the existing software suite, making it easier for a solution to adapt to the evolving user requirements.
Interfacing LINUX with microcontrollers
As previously stated, this article aims at presenting LINUX software mechanisms to interface with microcontrollers, and one possible implementation reusable across different projects. To this end, I created a simple library that I called MASL. The source repository is available here:
https://github.com/texane/masl
It hides the low level communication details between one MAster and several SLaves. In this case, the master is the RASPBERRY PI, the TEENSY is the slave. Having a library allows me to keep the same software as I change one or more system component (main board, MCUs, communication bus ...).
MASL has the following features:
- single master, multiple slave design,
- the master can program and reboot slaves,
- the master can read write data from to slaves,
- slaves can signal the master,
- implemented entirely as a userspace library.
Here is the wiring I use for the single slave configuration:
The following sections present the library API and its underlying implementation. Please refer to the repository when reading the code samples, especially:
https://github.com/texane/masl/blob/master/src/masl.h
https://github.com/texane/masl/blob/master/src/masl.c
A master unit test is available here:
https://github.com/texane/masl/blob/master/src/main.c
A testing slave code for TEENSY is here:
https://github.com/texane/masl/blob/master/teensy_main/main.c
Library initialization
masl_err_t masl_init(masl_handle_t**);
masl_err_t masl_fini(masl_handle_t*);
Initialize (resp. finalize) the library and allocate (resp. release) a handle opaque to the programmer. The initialization routine should be called once per application and before any other routine.
Slave programming software
masl_err_t masl_program_slave(masl_handle_t*, unsigned int, const char* filename);
The microcontroller programming software has been implemented by the TEENSY board author, and is available here:
https://github.com/texane/masl/blob/master/src/load_linux_only.c
Only some minor modifications were made, the original code being used 'as is'. It consists of a LINUX command line based program accessing the microcontroller over USB. Note that I would have favored a JTAG based mechanism to access the microntroller FLASH directly from the LINUX, but it is not possible due to the way the TEENSY is designed.
Slave reset
masl_err_t masl_reset_slave(masl_handle_t*, unsigned int);
This routine resets the slave. It currently uses a GPIO to control the TEENSY dedicated reset pin.
Master slave data exchange
The master can read and write data from to the slaves. This is achieved using the 2 following routines:
masl_err_t masl_write_slave(masl_handle_t*, unsigned int, const void*, size_t);
masl_err_t masl_read_slave(masl_handle_t*, unsigned int, void*, size_t);
Note that MASL does not define the protocol used between master and slaves to exchange data. I often use a crude { resource_id, op, data, checksum } based message passing, and I will eventually specify it in the library.
As both the RASPBERRY PI and the TEENSY processors have hardware modules to handle SPI, this is the current communication bus used by MASL.
SPI (master mode) is natively supported by LINUX, and a userspace application can benefit from it using the standard VFS system calls, plus a set of dedicated IO controls. Futhermore, a kernel module already exists for the Broadcom BCM2835 SOC SPI module.
First, a file descriptor is opened for the SPI device:
const int spi_fd = open("/dev/spidev0.0", O_RDWR);
Predefined IOCTL codes allow to get and adjust SPI clocking and framing settings:
SPI_IOC_{RD,WR}_MAX_SPEED_HZ /* clock frequency */
SPI_IOC_{RD,WR}_MODE /* clocking polarity, sampling edge */
SPI_IOC_{RD,WR}_LSB_FIRST /* is LSB first */
SPI_IOC_{RD,WR}_BITS_PER_WORD /* frame word size */
For instance, MASL sets the clock frequency to 500khz using:
#include < linux/spi/spidev.h >
const unsigned int hz = 500000;
ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &hz);
Reading and writing data is then done using:
read(spi_fd, data, size);
write(spi_fd, data, size);
Master GPIOs acess
Accessing GPIOs is not directly exposed by the MASL API, but is used internally.
There are different ways of accessing GPIOs from LINUX userspace. MASL current implementation uses the SYSFS file based interface. SYSFS is a general LINUX mechanism that exports kernel objects information to userspace. It relies on a pseudo filesystem, whose structure is quite standard, but can still change from a LINUX version to the other. The documentation can be found here:
http://lxr.free-electrons.com/source/Documentation/filesystems/sysfs.txt
The documentation specifc to GPIO support in SYSFS can be found here:
http://lxr.free-electrons.com/source/Documentation/gpio.txt (section 'Sysfs Interface for Userspace')
and more directly here:
http://lxr.free-electrons.com/source/Documentation/ABI/testing/sysfs-gpio
The GPIO SYSFS interface allows to:
- set the direction of a port,
- read and write a port value,
- modify interrupt on change settings.
You will find complete example in the MASL library source code.
Note that there are other methods to access GPIOs from userland. While the SYSFS method is somewhat more 'portable', it does not fit everywhere, especially in low latency applications. For instance, there are cases when one wants to generate waveforms using GPIOs from userspace (ie. I2C, SPI, PWM software implementation ...). Here, a physical memory mapping scheme is prefered, using mmap + /dev/mem. You can find more information here:
http://elinux.org/RPi_Low-level_peripherals
Slave signaling and master event model
masl_err_t masl_loop(masl_handle_t*, masl_slavefn_t, void*, int);
masl_err_t masl_wait_slave(masl_handle_t*, unsigned int*, int);
These 2 routines are used by the master to wait for a slave signal, and use the GPIOs interrupt on change feature.
masl_loop endlessly waits for slave notifications and calls a user provided routine along with the information needed for processing.
Similarly, masl_wait_slave waits for a slave notification and returns the slave identifier. The 2 routines return if an optionnal timeout is reached without any slave signal detected.
These routines are implemented on top of the LINUX epoll routines family, whose purpose is similar to select and poll routines. These routines allow a process to block (ie. sleep) until some file descriptor related condition gets ready. The condition and resulting event triggering depends on the function arguments, and I redirect the reader to the epoll manual for more information:
http://linux.die.net/man/4/epoll
Note that 'poll' name is misleading: the process is actually sleeping while waiting the condition readiness, and there is no active busy waiting loop.
As said above, note that we could have used select or poll routines. One advantage of epoll is that a context can be associated with an event. This context is given back by the kernel upon condition readiness, removing the need for a lookup from the userland process.
In MASL, the master wants to be notified when the value of a GPIO changes, as a result of a slave signal. To achieve this, the GPIO interrupt on change behavior is configured using SYSFS, as described previously. Then, a file descriptor is opened on its value, and can be used by epoll.
Registering the GPIO file descriptor to epoll is done using:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
More specifically in the case of interrupt state changing:
struct epoll_event ev;
ev.events = EPOLLPRI;
ev.data.u32 = 0;
int_fd = gpio_get_value_fd(&h->int_gpio[0]);
err = epoll_ctl(h->epoll_fd, EPOLL_CTL_ADD, int_fd, &ev);
Then, epoll_wait is used to be notified as interrupt changes state:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
Conclusion
While the presented design may not fit everyone requirements, I hope that this introduction to the software mechanisms provided by LINUX will help the reader in her own projects.
- Comments
- Write a Comment Select to add a comment
http://processors.wiki.ti.com/index.php/CC3000
The dev boards are cheap too, about 30$ each
I am aware other and more lightweight and slightly cheaper solutions exist
to achieve the same goal, as long as no 'complex' software is required on
the connectivity side. Having full TCP/IP stack is not the only point, and as
I said, I possibly want to run a HTTP(s) server, some SMTP client (again,
with a secure transport layer). While it may be done with a decent MCUs
(at least for HTTP and SMTP without SSL part), I know I can implement
nearly everything using LINUX without time and effort. It is very important,
as these projects are done on spare (read: rare :/ ) time.
Of course, I am not saying that dimensioning the hardware is a bad thing,
and I encourage anyone to choose the best trade-off according to the
project's actual needs. But when it comes to a 'fit them all' approach, I
would better favor spending a few extra money and time in a design that
won't be limiting afterwards (ie. resources and time).
To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.
Please login (on the right) if you already have an account on this platform.
Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: