Matt Reid
Electronics Design Specialist
Relay Control System
1.0 PREAMBLE
A couple months ago a colleague and I were talking about hobbies we do outside of work and he told me that he was into designing rockets. At first I assumed he was talking about the smaller pipe rockets that only travel up a couple hundred feet or so. This wasn't the case at all. The kind of rockets he build require liquid oxygen, an air clearance permit from Transport Canada to launch, take months, if not years or preparation, and can have over 1000 pounds of thrust.
Rocket engines of this size often require many subsystems to run which include pumps,valves, solenoids, and actuators. Controlling them all is not easy so this is why I was asked to help him design something which could help control them not only from the launch pad but also from mission control which is about 500 feet away behind a protective barrier. With all this in mind, I knew in order to safely control these things, there definitely had to be two way communication between the two consoles. Controlling these things by sending data only one way from mission control to the launch pad would have been the easiest way to build this system, but definitely not the safest. I knew there had to be some sort of feedback.
The build of this has been a collaborative effort. While I designed how the system functioned, he was able to choose how everything was going to be mounted, and how it would look aesthetically, so unfortunately I cannot take credit for the amazingly retro Dr.Strangelove-esque look it has. The following write-up will focus on the parts of this project I have designed.
System Diagrams(click to magnify)
2.0 HOW IT FUNCTIONS
This system consists two units; the main terminal, and the remote terminal, both powered by the same battery. The main terminal is located at the launch pad and has 8 controllable relays. The remote terminal controls those 8 relays from a safe distance around 500 feet away in a guarded area where the test is monitored(Aka Mission control). The two units are connected to eachother using 500 foot long shielded twisted pair cable. Although we could of made this system wireless, for safety reasons, standard practice in the rocket community is to hard wire any coms when possible.
The 8 relays in the main terminal each have an individual input connector and output connector. The reason all 8 of the relay channels have separate input and output connectors is because we wanted each channel to be isolated from one another which allows this system to be flexible enough to be able to control different voltages from different sources, whether that be AC from a generator or DC from multiple batteries.
Each relay is controlled using 8 momentary toggle switches on the front of the unit. The status of each relay is displayed on the dual colored LED right above the switch where green indicates the relay is on, and red indicates it is off. This same interface is mirrored at the remote terminal so if relay channel 1 is turned on at the main terminal, the LED on channel 1 of the remote terminal will also be on. The inverse is also true, so if you turn on channel 1 at the remote terminal , channel 1 will turn on at the main terminal. Each of the terminals have an LCD display. The LCD at the main terminal displays the com status/mode, the battery voltage, and battery level warnings.
The display at the remote terminal displays battery voltage and battery level warnings. Since this whole system has its own 14V dedicated battery I felt it was necessary to display the health of the battery at both ends of the system. When the battery is above 12VDC the LCD indicates "BAT OK". In an effort to preserve battery battery, I used latching relays. The way the most common type of relay works is the contact is closed only when there is voltage applied to the coil and when the voltage at the coil disappears the contact springs back to its open position. However the disadvantage to this type of relay in a battery powered system is that when a relay contact is closed for a long periods of time, its coil is constantly drawing from the battery. This is why I chose to use latching relays. The way a latching relay works is when you apply a brief pulse to the coil the relay contact closes. To open the relay contact up again you need to apply a brief pulse on the coil again, except with opposite polarity. This enables us to save much more battery power. The disadvantage though with latching relays is that when the battery dies, the channels that are on will stay on and wont be able to be turned off until the battery is charged. To avoid this issue I have the system monitor battery voltage levels and indicate when the battery is getting low. When the battery goes below a certain threshold all the relays turn off.
If the com cable is not connected the system can also function in stand alone mode or "SL-Mode" as it shows on the lcd screen of the main unit at the launch pad. Under normal operation its lcd will display "COM OK" meaning that the other control side is connected and they are communicating properly. If it displays "SL-mode" that means that either the remote terminal is not connected or there is a com error caused by a shorted or open wire in the cable.
3.0 THE DESIGN
3.1 The Main PCB's
When I first started brainstorming some ideas on how I was going to design this project, I first thought I'd have one large PCB at the launch pad and I'd have a smaller PCB on the mission control side. Then I thought it would be easier and much cheaper to design the same board that will work for both sides and program them differently.
The PCB used on both com ends use a PIC18F2585 micro controller to control all operations. There is also a 74HC595 shift register which is used to drive the dual color LEDs. The LEDs are wired so a 1 at the shift register output will turn them green, and a 0 will turn them red. The data being shifted out of the shift register along with the rest of the bus is connected to the header X4 which is only used on the launch pad side to connect to the two relay boards.
The only difference on the Mission Control setup besides the fact that there are no relay boards, is the key switch which was added to lock out that terminal. The key switch is connected to the programming header (X2) and a jumper wire is soldered to test pad TP1. This means when the unit is programmed the key switch needs to temporarily be disconnected.
The boards communicate using a SN65176 differential bus transceiver as well. Since the cable connecting the two sides is 500 ft long I did this because I didn't want any noise affecting the data . See this wikipedia artictle on how differential signalling works here.
One thing to note is that the schematic shows that a linear regulator is being used to provide the VDD to the pic. After I had the boards manufactured I decided to use a switching regulator instead so I decided to mount it off to the side and connect a 3 wire ribbon cable to the spot on the board where the linear regulator is supposed to be(IC1). I ended up using a LM1596 switching regulator which I found to be far more efficient. I posted a blog article here on some of the efficiency testing I did.
Main PCB
3.1 The Relay Boards
At the launch pad side there are 2 relay boards with 4 relays each. Each relay is driven using two photo-mosfets which are used to briefly send a pulse across the coil in either polarity. According to the data sheet a pulse of at least 20ms is required to make the relay switch positions. See this article on how latching relays work here.
Each relay board has a 74HC595 shift register which connects to the same bus that that is used to drive the LED's on the main board. Its important to note though that each pair of mosfets which drive they relay should never be on at the same time. So to turn on all the relays on one board one would load the shift register with the binary value 01010101 . The inverse is also true so to turn off all 4 relays on one board you'd load the value 10101010 into the shift register. I connected the master reset to only control the shift registers on the relay boards and not the shift register on the main PCB because I wanted to have a quick way to adjust the pulse width going to all the relay coils.
Relay PCB
4.0 THE CODE
4.1 Battery Management
When the unit at the launch pad starts up, it initializes all the relays by loading in an OFF pulse to all the shift registers to make sure all of the relays are in a known state.
It then checks the battery level and displays one of the status indictations: BAT OK, BAT LOW, ALL OFF. Battery OK means the battery has a good amount of charge and that you still have many hours of operation. BAT LOW means that you still have some time to use the unit before all the relays turn off due to low power. ALL OFF, means all the relays are off and that you should charge the battery to the unit. These status indications are displayed on both ends of the system.
To figure out the battery threshold levels I wrote a modification of the main program to simulate regular use of the unit by automatically switch on 5 relays a minute for an extended period of time. I sent the output the battery level value over to a serial terminal and plotted the results:
4.2 Transmitting
The first thing the microcontroller does when it is getting ready to send a byte is it sets the direction pin to 1. This configures the SN65176 differential bus transceiver into transmit mode, meaning all data going out of the dataout pin(RC0) will be transferred over the differential pair of wires. It then sends an initialization pulse that is 5ms long. After that it sends 8 more pulses. Each of the 8 pulses corresponds to the status of a relay. If the pulse is 3ms long, that means the relay is on. If the pulse is 1ms long that means the relay is off.
4.3 Recieving
On the other end when it is ready to receive data it sets the direction pin to 0. This configures the SN65176 bus transceiver into receive mode meaning that any data on the differential pair will appear at the datain pin(RC1). It will then wait for any incoming pulses that are exactly 5ms long and will ignore anything that isn't. It will continue to wait until a valid initialization pulse is seen. As soon as it sees this initialization pulse, it is ready to receive the actual data. It then measures the width of the next 8 incoming pulses. If one of these pulses is not either exactly 3ms or 1ms that means there is noise on the line and it sets the relay status to the last full valid byte of data that was received. To get precise pulse measurements timer1 in the pic is being used.
4.4 Error Detection and Timeout Periods
One of the differences between the way the mission control side receives data and the way the launch pad side receives data is the amount of time that it waits for the initialization pulse. The launch pad side starts its program loop by sending a byte to mission control and waits a small period of time for a response. If it does not receive a response, it moves on and displays that it is in "SL-Mode" meaning the mission control side is not connected and that it is operating in stand alone mode.
On the mission control side though It doesn't have this time out period. It will continue to wait for an initialization pulse indefinitely. This is done to ensure that the two units are always synchronized. The status LED next to the LCD is toggled when there is a valid initialization pulse received. This makes it easy to detect if there is a com error if it stays on indefinitely.
The LEDs on the mission control side are also updated with the received value and not with the sent value. This is done to make sure the LEDs always reflect the actual status of the relays at the other end.
On both sides there is a timeout period that it will wait for actual data once it has received the initialization pulse. For instance if it is half way through receiving a byte and the line hangs up with either a 0 or 1 value, it will continue on and it will use the last complete byte it had received.
5.0 FINAL THOUGHTS
As far as future developments go, we thought it would be neat if you could have one switch control a sequence of relays. Ideally I'd like to have the end user have the ability to customize the sequence without having to actually reprogram the microcontroller themselves. A way I thought I might go about doing this is to use the unused pins from the PIC18F2585 (which I've created test pads for TP1 to TP5) to communicate with an SD card. Changing the relay sequence would be as simple as editing a text file on the SD card. Alternatively I could use one of the serial Bluetooth modules to set up relay sequences using a cell phone.
Anyways I am looking forward to seeing in action in the field. I'm sure there will be small developments on this system throughout the coming years so I will try to keep this write-up up to date. See below for the code for the launch pad unit followed by the code for the mission control side written using PIcbasic Pro.
*code viewed best on desktop, spacing is off on mobile
'****************************************************************
'* Name : PIC18F2585 Launch Pad Station
'* Author : Matthew Reid
'* Date : 2017-02-10
'* Version : 1.0
'* Notes :
'****************************************************************
include "modedefs.bas"
#CONFIG
__CONFIG _CONFIG1H, _OSC_ECIO_1H & _FCMEN_OFF_1H & _IESO_OFF_1H
__CONFIG _CONFIG2L, _BOREN_OFF_2L
__CONFIG _CONFIG2H, _WDT_OFF_2H
__CONFIG _CONFIG3H, _PBADEN_OFF_3H & _MCLRE_OFF_3H & _LPT1OSC_OFF_3H
__CONFIG _CONFIG4L, _LVP_OFF_4L
#ENDCONFIG
osccon = %11010000
Define OSC 16 ' 16Mhz
pause 500
'--------a to d stuff---------------
Define ADC_BITS 10 ' Set number of bits in result
Define ADC_CLOCK 3 ' Set clock source (3=rc)
Define ADC_SAMPLEUS 50 ' Set sampling time in uS
adval Var long
t var word
adcon0 = %00000011
adcon1 = %00001110
'------------------------
rval var byte:rval = 0
rvalbuf var byte:rvalbuf = 0
tvalbuf var byte:tvalbuf = 0
portbbuf var byte:portbbuf = 0
button_buf var byte: button_buf = 39
relayout1 var byte: relayout1 = 0
relayout2 var byte: relayout2 = 0
err var bit: err = 0
lcdcount var byte: lcdcount = 0
w var long: w = 0
x var long: x = 0
y var long: y = 0
z var long: z = 0
i var byte: i = 0
b var bit: b = 0
ready var portc.6: ready = 0
pulsewidth var word: pulsewidth = 0
slmode var word: slmode = 10000 'timeout to determine when in SL mode
tval var byte:tval = 0 'transmit variables
lcd_out var portc.7
dataout var portc.0
datain var portc.1
data_direction var porta.6 ' 0 for data in, 1 for data out
INTCON2.7 = 0 'enable port b weak pullups
shiftclk var portc.2: shiftclk = 0
shiftdat var portc.3: shiftdat = 0
shiftlat var portc.4: shiftlat = 0
shiftmr var portc.5: shiftmr=1 'only resets external shift registers
'The TMR1CS bit of the T1CON register is used to select
'the clock source. When TMR1CS = 0, the clock source
'is FOSC/4. When TMR1CS = 1, the clock source is
'supplied externally.
'If TMR1ACS = 0 that means FOSC/4. If TMR1ACS = 1 it increments on FOSC
T1CON.2 = 0
'T1 prescaler
'11 = 1:8 Prescale Value
'10 = 1:4 Prescale Value
'01 = 1:2 Prescale Value
'00 = 1:1 Prescale Value
T1CON.5 = 1
T1CON.4 = 0
'set direction
TRISA =%00000001
TRISC =%00000010
PORTB =%11111111
gosub cleartimer
serout lcd_out,N9600,[$1b,$30]
serout lcd_out,N9600,[$1b,$2a,250]
pause 1000
'initialize all relays in the off position, and turn off all LEDs
shiftout shiftdat,shiftclk,msbfirst, [0,0,0]' for some reason I had to add this, the initialize wouldnt shift out on reset
shiftlat = 1
shiftlat = 0
pause 1000
'initialize all relays in the off position, and turn off all LEDs
shiftout shiftdat,shiftclk,msbfirst, [%10101010,%10101010,%00000000]
shiftlat = 1
shiftlat = 0
shiftmr = 0
main:
gosub get_ad
gosub transmit
gosub recieve
'the follow logic ensures the relay only changes when it detects that a
'button changes states otherwise if the button was held down, it continuously
'toggle between on and off every loop
IF adval > 10 then
if portb = $ff then
tval = rval
elseif portb=portbbuf then
tval = rval
ELSE
tval = (~portb) ^ rval
ENDIF
portbbuf = portb
else
tval= 0
endif
'The following code makes sure "On" or "OFF" pulse is sent to only the channel
'being activated. Otherwise if Tval was shifted out, it would send an off or on
'pulse to all relays at the same time
if tvalbuf.0 != tval.0 then
relayout1.0 = tval.0
relayout1.1 = not(tval.0)
endif
if tvalbuf.1 != tval.1 then
relayout1.2 = tval.1
relayout1.3 = not(tval.1)
endif
if tvalbuf.2 != tval.2 then
relayout1.4 = tval.2
relayout1.5 = not(tval.2)
endif
if tvalbuf.3 != tval.3 then
relayout1.6 = tval.3
relayout1.7 = not(tval.3)
endif
if tvalbuf.4 != tval.4 then
relayout2.0 = tval.4
relayout2.1 = not(tval.4)
endif
if tvalbuf.5 != tval.5 then
relayout2.2 = tval.5
relayout2.3 = not(tval.5)
endif
if tvalbuf.6 != tval.6 then
relayout2.4 = tval.6
relayout2.5 = not(tval.6)
endif
if tvalbuf.7 != tval.7 then
relayout2.6 = tval.7
relayout2.7 = not(tval.7)
shiftmr = 1
endif
'shift out LED status and relay value
shiftout shiftdat,shiftclk,msbfirst, [relayout2,relayout1,tval]
shiftlat = 1
shiftlat = 0
'clear relay boards
shiftmr = 0
'clear relay value so the ON pulse or OFF pulse for all channels isnt sent every time
relayout1 = 0
relayout2 = 0
'all debug values appear on a large display on the 3rd line and below
'the final lcd will only be a 16 x 2 display. The debug display is 16 x 4
serout lcd_out,N9600,[$1b, $33]
serout lcd_out,N9600,[#rval, " "]
serout lcd_out,N9600,[$1b, $34]
serout lcd_out,N9600,[#w, " ", #x, " ",#y, " ", #z ]
goto main
recieve:
ready = 1
'set data direction
data_direction = 0
'set portc.1 as input-
trisc.1= 1
rvalbuf = rval
'start recieve pulse
w=0
while datain=0 and w < slmode
w = w + 1
wend
x= 0
T1CON.0=1
while datain=1 and x < slmode
x = x+1
wend
T1CON.0=0
pulsewidth.highbyte=tmr1h
pulsewidth.lowbyte=tmr1l
gosub cleartimer
'check if its a real initialization pulse or just noise blip
if (w+x) < 10000 then
If pulsewidth <4950 or pulsewidth>5050 then
goto recieve
endif
endif
ready = 0
For i = 0 to 7
y= 0
while datain=0 and y < 300
y = y+1
wend
z= 0
T1CON.0=1
while datain=1 and z < 300
z = z+1
wend
T1CON.0=0
pulsewidth.highbyte=tmr1h
pulsewidth.lowbyte=tmr1l
gosub cleartimer
'find the binary value of the incoming data byte
if pulsewidth > 2900 and pulsewidth < 3100 then
b = 1
elseif pulsewidth > 900 and pulsewidth < 1100 then
b = 0
else
err = 1
endif
Select Case i
case 0
rval.0 = b
case 1
rval.1 = b
case 2
rval.2 = b
case 3
rval.3 = b
case 4
rval.4 = b
case 5
rval.5 = b
case 6
rval.6 = b
case else
rval.7 = b
end select
'if for whatever reason the transmit sends the start pulse and starts to send
'byte but stops half way through the reciever doesnt get hung up and it continues
'and sends the last good value it got.
if y > 395 or z > 395 or err = 1 then
rval = rvalbuf
err = 0
endif
next i
'if the slave unit is unplugged master unit can still function indpendently
if (w+x)>=slmode then
rval = tvalbuf
serout lcd_out,N9600,[$fe,$80]
serout lcd_out,N9600,["SL MOD"]
else
serout lcd_out,N9600,[$fe,$80]
serout lcd_out,N9600,["COM OK"]
endif
if adval => 12 then
serout lcd_out,N9600,["-BAT OK "]
elseif adval = 11 then
serout lcd_out,N9600,["-BAT LOW "]
else
serout lcd_out,N9600,["-ALL OFF "]
tval = 0
endif
return
transmit:
'set data direction
data_direction = 1
'start pulse
dataout = 0
pause 1
'tval is used so master unit can function alone
tvalbuf = tval
'start pulse
dataout = 1
pause 5
dataout = 0
'begin data output
For i = 0 to 7
dataout = 0
pause 5
dataout = 1
if tval.0 = 1 then
pause 3
else
pause 1
endif
tval= tval >> 1
next i
dataout = 0
return
cleartimer:
tmr1l = 0
tmr1h = 0
return
get_ad:
ADCIN 0, adval
adval = ((adval*3)/100)+76
t =adval
adval = adval/100
t= t-(adval*100)
serout lcd_out,N9600,[$1b, $32]
'slow down lcd refresh rate
If lcdcount = 0 then
serout lcd_out,N9600,[#adval,".",#t," VOLTS "]
lcdcount = 0
else
lcdcount = lcdcount + 1
endif
return
'****************************************************************
'* Name : PIC18F2585 Mission Control Station
'* Author : Matthew Reid
'* Date : 2017-02-10
'* Version : 1.0
'* Notes :
'* set config separate, mlclr inpyut pin, ecio. rest is default *
'****************************************************************
include "modedefs.bas"
osccon = %11010000
Define OSC 16 ' 16Mhz
DEFINE CHAR_PACING 10
pause 500
'--------a to d stuff---------------
Define ADC_BITS 10 ' Set number of bits in result
Define ADC_CLOCK 3 ' Set clock source (3=rc)
Define ADC_SAMPLEUS 50 ' Set sampling time in uS
adval Var long
t var word
adcon0 = %00000011
adcon1 = %00001110
'------------------------
rval var byte:rval = 0
tval var byte:tval = 0
rvalbuf var byte:rval = 0
portbbuf var byte:portbbuf = 0
button_buf var byte: button_buf = 39
lcdcount var byte: lcdcount = 0
w var byte: w = 0
y var byte: y = 0
i var byte: i = 0
b var bit: b = 0
debugg var word[8]
pulsewidth var word: pulsewidth = 0
countt var word: countt = 0 'variable used for battery drain test
ready var portc.6: ready = 0
lock var porta.1
debug_out var portc.5
lcd_out var portc.7
dataout var portc.0
datain var portc.1
data_direction var porta.6 ' 0 for data in, 1 for data out
shiftclk var portc.2
shiftdat var portc.3
shiftlat var portc.4: shiftlat = 0
INTCON2.7 = 0 'enable port b weak pullups
'The TMR1CS bit of the T1CON register is used to select
'the clock source. When TMR1CS = 0, the clock source
'is FOSC/4. When TMR1CS = 1, the clock source is
'supplied externally.
'If TMR1ACS = 0 that means FOSC/4. If TMR1ACS = 1 it increments on FOSC
T1CON.2 = 0
'T1 prescaler
'11 = 1:8 Prescale Value
'10 = 1:4 Prescale Value
'01 = 1:2 Prescale Value
'00 = 1:1 Prescale Value
T1CON.5 = 1
T1CON.4 = 0
PORTC =%00000000
TRISA =%00000011
TRISC =%00000010
PORTB =%11111111
gosub cleartimer
'lcd stuff
serout lcd_out,N9600,[$1b,$30]
serout lcd_out,N9600,[$1b,$2a,250]
main:
gosub recieve
serout debug_out,T9600,[#adval,".",#t," VOLTS ",10]
if portb = $ff then
tval = rval
elseif portb=portbbuf then
tval = rval
ELSE
if lock = 1 then
tval = (~portb) ^ rval
else
tval = rval
endif
ENDIF
portbbuf = portb
serout lcd_out,N9600,[$fe,$80]
IF lock = 1 then
serout lcd_out,N9600,["UNLOCKED-"]
else
serout lcd_out,N9600,["LOCKED -"]
endif
if adval => 12 then
serout lcd_out,N9600,["BAT OK "]
elseif adval = 11 then
serout lcd_out,N9600,["BAT LOW"]
else
serout lcd_out,N9600,["ALL OFF"]
tval = 0
endif
gosub transmit
shiftout shiftdat,shiftclk,msbfirst, [rval]
shiftlat = 1
shiftlat = 0
gosub get_ad
goto main
cleartimer:
tmr1l = 0
tmr1h = 0
return
recieve:
'set data direction
data_direction = 0
'set portc.1 as input
trisc.1= 1
rvalbuf = rval
'start recieve pulse
ready = 1
while datain=0 : wend
T1CON.0=1
while datain=1 : wend
T1CON.0=0
pulsewidth.highbyte=tmr1h
pulsewidth.lowbyte=tmr1l
gosub cleartimer
debugg[0] = pulsewidth
'check if its a real initialization pulse or just noise blip
If pulsewidth <4900 or pulsewidth>5100 then
goto recieve
endif
ready = 0
For i = 0 to 7
w= 0
while datain=0 and w < 300
w = w+1
wend
y= 0
T1CON.0=1
while datain=1 and y < 300
y = y+1
wend
T1CON.0=0
pulsewidth.highbyte=tmr1h
pulsewidth.lowbyte=tmr1l
gosub cleartimer
debugg[i+1]= pulsewidth
if pulsewidth > 2900 and pulsewidth < 3100 then
b = 1
else
b = 0
endif
Select Case i
case 0
rval.0 = b
case 1
rval.1 = b
case 2
rval.2 = b
case 3
rval.3 = b
case 4
rval.4 = b
case 5
rval.5 = b
case 6
rval.6 = b
case else
rval.7 = b
end select
next i
'send timing value of each bit to serial debugg
'For i = 0 to 8
' serout debug_out,T9600,["bit ", #i," is ",#debugg[i], 10]
' serout debug_out,T9600,[#w," ", #y]
'next i
pause 20
return
transmit:
'set data direction
data_direction = 1
'start pulse
dataout = 0
pause 1
'start pulse
dataout = 1
pause 5
dataout = 0
'begin data output
For i = 0 to 7
dataout = 0
pause 5
dataout = 1
if tval.0 = 1 then
pause 3
else
pause 1
endif
tval= tval >> 1
next i
dataout = 0
return
get_ad:
ADCIN 0, adval
adval = ((adval*3)/100)+ 200
t =adval
adval = adval/100
t= t-(adval*100)
serout lcd_out,N9600,[$1b, $32]
'slow down lcd refresh rate
If lcdcount = 4 then
serout lcd_out,N9600,[#adval,".",#t," VOLTS "]
lcdcount = 0
else
lcdcount = lcdcount + 1
endif
return
return