As a part of my on-going ROV projects, I needed a compact method of sending messages from the
top side unit down to the ROV itself. I don't want too many wires in my umbilical, but I do want
high speed data transfer and relatively long distance. Given that the umbilical is submerged in water,
noise and interference aren't major concerns. To reduce risk I chose good old RS-485, with the intention
of developing a module which can be tightly integrated with the single board computers I was using
(Raspberry Pi!). One goal in the project is to eventually send the video feed from the ROV over this
communication line, so I wanted to support the highest data rate possible with the hardware. To have
a chance at that, I would need as direct communication with the hardware layer as possible, meaning
use of a kernel driver! The other upside of this is easy integration into other standard tools and
software.
Hardware
As for the actual hardware itself, I choose a XR20M1170
which supports a 18MHz SPI interface, and UART data rate of 16Mbps. In addition it has an automatic RS-485 half-duplex Direction
control output, which is exactly what I needed. I coupled this with a
THVD1550. This chip can support up to 50Mbps over a short distance,
and should therefor not impede the XR20M1170 in any way. Other than that I've included a few breakout points for additional GPIO on the Raspberry Pi.
In my up-coming ROV project I've integrated the entire circuit onto the main PCB of the ROV, which helped greatly in reducing the space required.
Software
I had never programmed a kernel module before, but I have programmed on both bare metal microcontrollers,
and some high level C++ projects. Experience in both helped with this project I felt, though I was still
very reliant on example code to get the first "Hello World" module running. Some useful resources in this
regard were:
I implemented the kernel module as a character driver, as this was both easiest, and fit ok with
the intended use as a RS-485 interface. Reads and writes work as they do for regular file types,
along with open and close calls. Various parameters and settings in the hardware layer can be set
via ioctl commands. An option would be to develop this as a regular TTY device driver, however
this appeared to be more work, and is still something I can port my character driver to in the future.
Another alternative I have loosely planned for the future is to implement this as a network driver,
allowing for a simple point-to-point network. Both of these ideas are outside the scope of the project
presented here however.
The kernel module is built around a central thread which communicates with the hardware over SPI. This is
to avoid concurrency issues when communicating with the XR20M1170 chip. Writes are handled by placing data
in a transmit buffer, and signaling the main thread. There, the data is split into hardware buffer sized chunks,
and moved to the XR20M1170 chip. When the XR20M1170 has completed transmitting this buffer, it triggers an
interrupt on one of the Raspberry Pi GPIO pins. This wakes up the main thread, which continues placing the
remaining chunks of transmit buffer into the XR20M1170 chip. Received data is handled in much the same way,
where the chip triggers an interrupt once the receive buffer has either exceeded a limit, or timed out on
data reception. Time-out in this regard simply means the buffer is not full or at the trigger limit, but
has data, and has not received new data during in a certain timespan. The interrupt wakes up the main thread,
which reads all the data in the hardware buffer, and places it in a software buffer in the kernel module.
When the user then decides to read data, it is fetched from this buffer.
Otherwise a few ioctl commands are handled by the kernel module. Two hardware settings, changing of baudrate,
and changing of the SPI communication speed used between the Raspberry Pi and the XR20M1170 chip. In addition
a flush command has been implemented, which empties the software buffers, and resets the XR20M1170 at a hardware
level. This is useful to return the driver to a known state, after e.g. a protocol error. A simple command line tool
was developed to set parameters of the kernel module.
Device Tree
A completely new topic to me was "device trees", which gave me some headaches. These are files which describe hardware
which can be connected to the computer, and interface with the operating system.
The usual Raspberry Pi distro comes
with a SPIdev kernel module, allowing easy access to SPI devices from userspace. However, the kernel module I've developed
needs exclusive access to the SPI bus. For this reason a special device tree is needed which disables SPIdev before
it is loaded by the operating system. Ideally I would have made a custom device tree which actually describes my
RS-485 Interface board, but I haven't achieved it quite yet. So far my attempts at creating a device tree describer
have failed, and I suspect it has something to do with the SPIdev interferring.
Testing
Testing was carried out using a STM32 converted to a USB to RS-485 converter. This worked well enough to
verify basic functionality, but once everything was working properly it quickly became too slow.
After this
tests were run directly between two Raspberry Pi units, using two Python scripts to coordinate the two
Raspberry Pi units. One script runs on the PC, and dictates which unit is master, and which is slave for a
given test. The PC also sets the current baud rate, and starts the test. Then the master sends data to the slave,
which verifies it's contents. If all is well, the PC steps the baud rate, and switches roles of the Raspberry Pies.
This continues until a fault occurs. To get a good idea of the actual throughput to rely, the test must be
run several times, as these communication errors are statistical in nature.
Disclaimer:
I do not take responsibility for any injury, death, hurt ego, or other
forms of personal damage which may result from recreating these
experiments. Projects are merely presented as a source of inspiration,
and should only be conducted by responsible individuals, or under the
supervision of responsible individuals. It is your own life, so proceed
at your own risk! All projects are for noncommercial use only.