Sometimes I ride my bike to and from work. I live in a second story apartment, and it can get a bit tricky holding my bike up while fumblng with my keys to unlock the door. I thought it would be neat if the door would unlock itself for me. But gee wiz, doors are generally pretty dumb and don’t just do that!
Digging around through my junk, I found a small RC servo. A little metal here and some double sided tape there, and I had a servo-controlled deadbolt!
The servo isn’t any good on its own, so I started whipping up a script to detect my presence by periodically pinging my cell phone on the apartment wifi network. In order to connect the script to the servo, I thought I’d try the crappy 433Mhz FM transmitter/receiver pair I picked up for another project but never used.
Laptop->arduino->FM transmitter->FM receiver->arduino->servo
This worked okay, but using up two arduinos was kind of lame. Also, the FM transmitter was pretty weak and the system was generally unreliable. Also, I was using a USB battery to power the receiving arduino/servo, which was not a long-term solution.
The FM transmitter/receiver dumbly passes through whatever digital waveform you give it, so I briefly considered plugging the receiver directly into the servo and generating the RC pulses on the transmitting arduino. I decided against it, though. RC servos tend to jump all over the place when given a nonsensical command signal, and the FM receiver seemed prone to converting random noise into digital pulses. I wouldn’t want my door randomly locking and unlocking when the neighbors’ refridgerator kicks on and off!
The transmitting arduino is just acting as a USB->TTL serial adaptor, so I could at least replace that one with something cheaper. I ordered one of these USB TTL adapters to play with. I plugged in the FM transmitter and it worked just as well as the first attempt. Unfortunately there was still the same FM reliablity issue.
In order to get a halfway-decent link, I would have to either power the FM transmitter with more than 5V (seems like more effort than the hack is worth), or stick the transmitter closer to the receiver. Sticking the transmitter closer to the receiver diminishes the whole point of using FM, and I had to figure out a long-term solution for power anyway. I bit the bullet and decided to run wires to the door. Since I’m running wires, though, I might as well ditch the FM, and if I’m ditching the FM I might as well reconsider having the arduino at the door! Isn’t engineering fun? The new architecture looked like this.
Laptop->arduino->30’ cable->servo
But gosh darnit, I bought a crappy USB TTL adapter just for the project! And all the arduino is doing is converting USB to TTL serial to RC servo pulses. What if we could go straight from TTL serial to the RC servo? They are basically the same thing, 5V square waves. Let’s think about the timing.
The RC servo pulse necessary to unbolt my door looks like this:
———-________———-__________———-__________ .5ms ~20ms
The low time for an RC servo isn’t particularly important, and some servos are less picky than others.
Ok, so we need about 500uS high followed by several milliseconds low. A TTL serial signal has an asyncronous clock which determines the length of a pulse, or data bit. The bitrate, or baud, is specified in bits/sec.
1 / (0.5ms/bit) = 2000 baud
Most UART hardware lets you specify arbitrary baudrates, but operating systems generally make it inconvenient to do so. For now let’s round to the closest standard baudrate, 2400.
1 / 2400 = 0.416ms, close enough?
So now we just need to write some characters to the serial port such that we’ll get a high bit followed by a bunch of low bits. If I recall correctly, serial is LSB first. 0×01? But wait! TTL serial always has a start bit. And if there’s a start bit, there needs to be some idle time before the start bit. In fact, between characters a TTL serial signal will go to 5V, and the start bit is a transition to 0V. That’s pretty lame because the RC servo’s signal idles at 0V.
At the beginning of every byte transmission we’re going to get this waveform regardless of the value written:
——____
We can’t have any extra 5V pulses in the RC servo signal or else the servo will get confused, so we’ll have to somehow morph the 5V idle time between bytes into the RC servo signal itself. Thinking about it, as long as there are characters buffered up, the idle time will probably only be one bit (or slightly longer) in length. One bit or slightly longer is exactly what we need to make the servo work! That just leaves the longer 0V portion of the RC servo waveform. We already have the start bit, and we have 8 bits of character left, so the best we can do is write 0×00.
____——-____________________________________——-____ idle start 0×00
9 * 0.416ms = 3.75ms
That’s nowhere near 20ms, but maybe the servo won’t mind?
How many character should be buffered up? It takes a couple seconds for the servo to fully open the lock.
2 s / (10 bits/byte / 2400 bits/s) = 480 bytes
I wired the servo directly to the USB TTL serial adaptor and blasted a few hundred 0×00’s at it. Miraculously, the servo moved to the unlocked position!
To go for broke, I wondered if I could command the servo to other positions by changing the character. I managed to get 6 steps from swing to swing.
____———____________________________———____ idle start 0×00 ———————__________________________—————____ idle start 0×80 ———————_______________________———————-____ idle start 0xC0 ———————_____________________——————————____ idle start 0xE0 ———————__________________————————————-____ idle start 0xF0 ———————________________———————————————____ idle start 0xF8
Great success! I went back to my phone pinging script and integrated the servo control. The servo stalls out when commanding 0×00, which is great for unlocking the door, but kinda mean to the servo. I decided to command the servo nearly closed with 0×80 for a few seconds, then throw it fully open with a short 0×00 pulse.