2 Channel RC Safety Mux
One 3rd party feature I found essential when testing out my Self Driving RC Car was the "Fail Safe Mux" by DIY Drones. It allowed me to use a spare RC servo channel to remotely switch control between my RC receiver and my Arduino Mega-based autopilot. Acting as a control multiplexer" (MUX), or remote switch, it allowed me to take RC control at any time, such as when my autopilot code went off the deep end. While I really like the DIY Drone product, and highly recommend it, it required rather a rat's nest of servo wiring to make all the connections it requires. In addition, being designed more for aerial vehicles than for cars, it lacked a few features I thought might be useful. So, this page describes my efforts to build my own version.
Theory of Operation
The basic principle of a safety MUX, or remote RC switch, is that it routes two sets of control inputs, one from the RC receiver and the other from the autopilot through a switch that can select which of these inputs to route to the car's throttle control and steering servo. This switch, in turn, is controlled by a small micro controller chip that's been programmed to monitor a 3rd RC channel and interpret that channel's signal as a kind of binary switch. That is, if servo pulse is detected on this channel and the width of the pulse if greater than the neutral position (>1.5 ms), the MUX switch is set to one position. Or, if the width of the pulse is less than neutral (<1.5 ms), the switch is set to the other position. Finally, if there is no pulse present, the switch would, ideally, default to the autopilot position so that control of the car could be relinquished by simply turning off the transmitter.
The DIY Drones product uses a 4 channel, 74LS157D IC as its multiplexor switch. I used this same IC in my first design, but later decided that, since I only needed to switch 2 channels, I could simplify the design by using a pair of single channel, SN74LVC1G97DBVR ICs to simplify and reduce the size of the design. The SN74LVC1G97DBVR is an interesting chip, as it's actually designed to perform many different logic functions, depending upon how it's wired into the circuit. The SN74LVC1G97DBVR comes in an SOT-23-6 package, so it's a surface mount chip. But, it's the same size as the ATTIny10 I planned to use for the switch micro controller, so this made for a nice, compact design. The final schematic looks like this:
Click image for larger view
The ATTiny10 monitors the 3rd RC channel on pin 3 and then uses pin4 to output the control signal to the switch. A low output on pin 4 selects the autopilot and a high output selects the RC receiver to control the car. The code for the ATTIny10 looks like this:
.device ATtiny10
; +====+
; (PWMA) PB0 |* | PB3 (RESET)
; GND | | Vcc
; (PWMB) PB1 | | PB2
; +====+
; PB1:Pin 3 - Pulse In
; PB2:Pin 4 - Select Out (LOW selects CPU, HIGH selects Receiver)
;
; Note: neutral servo pulse width is 1500 us
; available fuses: ckout, wdton, rstdisbl
.DSEG
.CSEG
.org 0
;Interrupt vector table
rjmp reset ; All Resets
reti ; External Interrupt Request 0
reti ; Pin Change Interrupt Request 0
reti ; Timer/Counter0 Input Capture
reti ; Timer/Counter0 Overflow
reti ; Timer/Counter0 Compare Match A
reti ; Timer/Counter0 Compare Match B
reti ; Analog Comparator
reti ; Watchdog Time-out
reti ; VCC Voltage Level Monitor
reti ; ADC Conversion Complete
reset:
; Set Stack Pointer (SP)
ldi r16, 0x00
out SPH, r16
ldi r16, 0x5F
out SPL, r16
; Set clock to 8MHz
ldi r16, 0xD8 ; Unprotect CLKPSR reg
out CCP, r16
ldi r16, 0 ; Divide by 1
out CLKPSR, r16
; Calibrate Oscillator
ldi r16, 0x58 ; Value varies from shhip to chip
out OSCCAL, r16
; PB1 is Input, all others Output
ldi r16, (1<<PINB3) | (1<<PINB2) | (0<<PINB1) | (1<<PINB0)
out DDRB, r16
; All Pullups Off
ldi r16, (0<<PINB3) | (0<<PINB2) | (0<<PINB1) | (0<<PINB0)
out PORTB, r16
loop:
rcall pulse_in
cpi r16, 0
brne has_pulse
; get here if no pulse detected (timeout)
cbi PORTB, PINB2 ; set to CPU
rjmp loop
;
has_pulse:
cpi r16, 15
brlo short_pulse ; branch if r16 < 15
long_pulse:
sbi PORTB, PINB2 ; select Receiver
rjmp loop
;
short_pulse:
cbi PORTB, PINB2 ; select CPU
rjmp loop
;
; Wait for pint to go HIGH, then measure length of pulse
; Return r16 as pulse width, else return r16 = 0 if timeout
pulse_in:
ldi r16, 0
ldi r17, 0
ldi r18, 10
pulse_wait:
sbic PINB, PINB1
rjmp pulse_time
inc r16
brne pulse_wait
inc r17
brne pulse_wait
dec r18
brne pulse_wait
ldi r16, 0 ; return r16 = 0 if timeout
ret ; timeout
pulse_time:
ldi r16,0
pulse_loop:
sbis PINB, PINB1 ; skip if PINB1 is HIGH
ret ; pulse ended
rcall delay100 ; delay 100 us
inc r16 ; inc time
rjmp pulse_loop
;
; Delay approx 100 us (using calibrated osc)
delay100:
inc r17
brne delay100
ret
Note: this code is designed to be compiled on my custom ATTiny10 assembler and assumes the on-chip oscillator has been calibrated. However, the timing requirements are not all that precise and you should be able to adapt this design to many other micro controllers.
Safety Mux for my FrSky Telemetry Receiver
Here's a photo of revision 1 of my multiplexer design fitted to my FrSky D8R-II Plus, 8 channel telemetry receiver. The mux PCB sits on top and uses a 90 degree adapter PCB to plug into the receiver. The small board visible in front is a signal inverter used to send telemetry data back to the RC transmitter:
Click for larger view
Note: I later revised this design to use a pair of SN74LVC1G97DBVR chips to handle the multiplexing. However, the version shown above is an earlier design that used a quad, two channel mux. In the near future I plan to revise this board again to add in my ESC Protector circuit.