Open Boat
An RC boat on an Arduino. It is an open source boat project: simple, and easy to do. The main objective was to make a boat as simple as possible, out of almost whatever came to hand.
The Bluetooth module I used is a BLE 4.0 module, an HM-10, and it is the only slightly delicate element to get hold of. It is also the part you should feel free to change: we can easily replace it with a more classic bluetooth module (HM-05), with a wifi module, or with an RC receiver, depending on what you want and on the components you already have. Nothing else in the boat cares which one you picked.
Parts
| Part | Build | Role |
|---|---|---|
| Arduino | both | controller |
| DC motor | both | propulsion |
| Servo motor | both | rudder, via a bent steel pushrod |
| Radio module | see wiring | command link; the first drawing omits it |
| MOSFET, N-channel | simple | switches the motor on and off |
| NPN transistor | controller | replaces the MOSFET in this variant |
Diode, 1N4007 | controller | flyback across the motor |
| Resistor, 1 kohm | controller | in series with the switching part control pin |
| 9 V pack | controller | motor supply, separate from the logic |
The controller build is the simple one plus a flyback diode, a gate resistor and its own supply, with the switching part swapped. You do not need both switching parts.
The original parts list says NPN transistor while all three drawings show an N-channel MOSFET. They disagree. The drawings are the ones with the wiring on them.
Wiring
Three versions, in the order the drawings are numbered, which is also the order in which I would build them.
The link
Bluetooth was V0. It works, it pairs in a minute, and it hands you something that behaves like a serial port: you write bytes at one end and they arrive at the other, in order, or the link is down and nothing arrives at all. That abstraction is why it is the right first choice, and it is also the whole of what it gives you. Range is short, the link is unauthenticated once paired, and every framing decision is still yours because a byte stream has no frames.
The next version moved to a 2.4 GHz transceiver module, and that changes the problem rather than improving it. A bare radio does not give you a stream. It gives you fixed-size packets, individually addressed, individually acknowledged or not, and everything above that is code you write. So I wrote the protocol.
The frame
The command frame is a C struct with an explicit field layout and explicit padding, declared packed so the compiler is not free to arrange it. Anything that goes on the wire as raw memory has to be laid out on purpose. The real field names and widths are not to hand, so what follows is the shape of the design rather than a transcription of the sketch:
illustrative layout, not the 2020 source struct __attribute__((packed)) frame {
uint8_t magic; // frame start, cheap reject
uint8_t seq; // sequence, wraps at 255
uint8_t flags; // type, ack request, and spare bits
int16_t throttle; // signed: astern is negative
int16_t rudder; // signed: centre is zero
uint8_t pad; // explicit, not compiler-chosen
uint16_t check; // integrity field over everything above
};
Padding is the part worth dwelling on, because it is where this goes wrong silently.
AVR is an 8-bit machine with no alignment requirement at all: every load is a byte load,
so avr-gcc inserts no padding and a struct is exactly the sum of its fields.
Almost every other target you might put on the far end does not behave that way. On ARM
or x86 the compiler pads members out to their natural alignment, so the same declaration
produces a different number of bytes with the fields at different offsets. Members are
never reordered, which is the only guarantee C gives you here; where each one starts is
not. The layout above is ten bytes packed and twelve padded, with a byte inserted before
each of the two-byte fields that would otherwise land on an odd address. A receiver
assuming the wrong one reads garbage that looks like plausible garbage.
Declaring the frame packed pins the layout to the declaration on both sides. On most architectures that trade costs you something, because unaligned access is slower or has to be synthesised. On AVR it costs nothing whatsoever, since there was no alignment to give up. Packing a wire format is close to free on exactly the machine that most needs the format to be unambiguous.
Losing packets on purpose
A 2.4 GHz link in a crowded band drops frames, and it corrupts them as well. Both failures are handled in the frame rather than around it: the format carries an integrity field and the receiver runs a standard error-handling scheme over it rather than anything invented for the occasion. That is the one part of this protocol where rolling your own would have been the wrong instinct.
What the frame carries changes how much a lost one costs. Send absolute positions and a dropped frame costs a single update, because the next one restates the true position. Send increments and every lost frame is integrated into a permanent offset that nothing later corrects. It is the cheapest robustness available on a lossy link, and it is a choice made in the frame layout rather than in the control code.
Handshake and confidentiality
On top of that sits a handshake and a lightweight cipher, both homemade. The handshake establishes a session between a remote and a boat before either will accept traffic from the other; the cipher keeps the command traffic from being read off the air by anything listening on the band.
Confidentiality is the only property a cipher gives you on its own, and it is worth being precise about the gap. Encrypting a frame stops someone reading it. It does not stop them recording one and sending it again later, because a replayed frame is a valid frame: resisting that needs a counter or a nonce in the frame and a receiver that rejects anything it has already seen. Freshness and secrecy are separate problems and only one of them is solved by encrypting.
Both are hobby constructions and worth naming as such: a scheme designed by one person and reviewed by nobody is not a security control, and on an 8-bit part with kilobytes of RAM the cheap primitives are cheap for reasons that show up under analysis. What it does buy is real all the same. It raises the cost of interfering with the boat from "own a 2.4 GHz radio" to "reverse the protocol first", which is the correct bar for a toy and the wrong one for anything else.
The boat
The hull is a block of polystyrene cut by hand, and everything else is taped or wedged into it: the servo with a bent steel pushrod running back along the hull, the motor held down with cloth tape, the radio module and its jumper wires at one end, and BAT written on the foam in pencil where the battery goes. It is ugly, and it floats.
More
For more information, visit the project's github page.