EditDigital Car Decoding
Based upon the Scalextric car
LED signal format, the
critical time is between the
falling edges of detected car signal. Using a microprocessor, it possible to detect and time those edges, and a simple lookup table can provide the appropriate car id. Whilst watching for the next falling edge its a simple matter of deciding when the rising edge occurs, to decode lane change status.
However, there are a number of steps which must occur first to accurately detect, verify and confirm the signal received.
Noise from Ambient Light
The detector circuit will not provide a clean, aka quiet, signal when there isn't a car passing over the detector! Natural light, artificial light (especially incandescent lightbulbs) contain Infra-Red, and as such the detector will also 'see' this. Being random in nature, the detected light noise is just a nuisance, but it must be excluded.
The microprocessor must, therefore, ensure that the signal received is valid and appropriate. The processor must also recognise the transition point where the car LED is only partially detected (which presents like noise), usually as the detector just starts to 'see' the LED and just as the car is moving out of detection zone.
Quiet Zone
As the car's nose first appears over the detector, and the period after the detector until the car body passes over the detector, is a
quiet zone. This is because the physical body blocks out all ambient light from the reaching the detector.
The microprocessor therefore uses this quiet zone to assist in detecting that a car is passing, and a valid LED signal will likely be arriving. The quiet zone which follows the LED also confirms that the car has passed, and is used to restart for decoding another car (which
may have the same car ID, especially when pace cars are in use)
Transient Errors
As the LED just starts to be detected, and as the LED is disappearing from detection, the output from the LED detection circuit is effectively noise. The microprocessor must ensure that it is correctly interpretting this as transient noise, and not 'lose its place' in the detection process.
EditSSD Car ID Decoder Circuit

Decoder Circuit Diagram
| Parts |
|---|
| U1 | PIC 12F629 |
| U2 | 78L05 |
| D1 | 1N4001 or 1N4148 |
| IRD1 | BP104 |
| LED1 | 3mm Red |
| R1 | 100kΩ |
| R2 | 330Ω |
| R3 | 2k2Ω |
| R4 | 1kΩ |
| R5 | 10kΩ |
| R6 | 1kΩ |
| Q1,Q2 | BC547 or equiv |
| C1 | 100μF Elect |
| C2 | 100nF ceramic |
Power Supply
The optional power supply is presented if power is to be taken from the track. Otherwise a clean 5v source must be provided from elsewhere.
Confidence LED
The R6 / LED1 combination is optional, and is present for showing a car has been detected.
Output
The output is standard RS232 signal at 56kbaud. Voltage conversion is required if this is to be interfaced to a PC.
EditCircuit Description
R1/IRD1 pair is a simple bias circuit, which holds the base of Q1 low when no IR appears at the photodiode. As the car LED passes over the photodiode, the diode starts to conduct, thereby pulling R1 towards +ve. Once the voltage reaches about 0.7v, Q1 starts to conduct. Minor noise from the photodiode is lost in this initial voltage step. As the photodiode continues to be driven harder by the passing LED, the increased current flows into Q1, turning it on harder. R2 prevents overdriving Q1 when in saturation.
The R3/R4 pairing is a simple voltage divider bias circuit, which keeps Q2 turned on, thereby pulling low the input (pin 5) to the microprocessor U1. (The internal pull-up resistors of the MCU are enabled) As Q1 turns on, it pulls this voltage divider low, until eventually Q2 is turned off. Once Q2 is turned off, the input to the MCU goes high.
C2 decouples the power supply pins to the MCU, and must be positioned as close as possible to the MCU. R5 holds the MCU master reset (MCLR) high, (The device can be reprogrammed in circuit with an apprioriate programmer) R6/LED1 is an optional confidence circuit, and is lit which a car ID is detected.
D1, C1 and U2 are a standard low-power 5v supply which can be taken direct from the track. D1 rectifies the track voltage, and applies it to C1 as for smoothing. U2 regulates the voltage to the remainder of the circuit and provides about 5v.
MCU pin 7 is an output, supplying RS232 type signals, 0→5v. This can be interfaced directly to another MCU (SSD Controller or similar) or can be voltage converted to standard RS232 signals, by an external circuit (not shown).
EditComments
Why not use a phototransistor?
When building the test circuit, I could not source locally a suitably packaged phototransistor - they were all far too big.
The photodiode selected is perfectly shaped to sit cleanly in the moulding gaps in the track pieces (in particular, the single half-straight) where the rails mounts are formed onto the plastic. It also is manufactured in dark plastic, to reduce the effects of natural light, so doesn't need further filtering.
Can SMD components be used?
Absolutely. All parts here are through-hole, as it started off life as a breadboard project then transferred.
EditPIC Assembler Code
This assembly code for the PIC 12F629 MCU will decode a single track SSD car ID detector.
;**********************************************************************
; *
; Filename: Decoder2.asm *
; Date: *
; File Version: *
; *
; Author: Ian Harding *
; Company: (c) Electric Images, 2009 *
; Permission is given for personal use only *
; *
;**********************************************************************
; *
; Files required: P12F629.INC *
; *
; *
; *
;**********************************************************************
; *
; Notes: *
; Uses internal oscilator - should be 4MHz *
; *
; GP0: Serialised data out (ICSPclk) *
; GP1: Confidence out to LED to gnd (ICSPdata) *
; GP2: IR Detector 1 in, hi on active *
; GP3: (MClr) *
; GP4: *
; GP5: *
; *
; start--v v--change *
; _ _ _ *
; |||||| | |-2-10 1 2 3 4| |-2-10 1 2 3 4| | ||||| *
; |||||| | | | | | | ||||| *
; ||||||___.___| |_____________| |_____________| |__._||||| *
; Car 4 - pulse is 48uS *
; *
; change--v *
; _____________ _____________ _._ *
; |||||| | -2-10 1 2 3|4| -2-10 1 2 3|4| | ||||| *
; |||||| | | | | | | ||||| *
; ||||||___.___| |_| |_| |__||||| *
; Car 4 - pulse is 48uS-53uS *
; *
; Clearly time between rising edges is the critical item *
; Maximum cycle time for 6 cars, est 430uS *
; Therefore use a T0 /4 prescaler, to give 12 units per pulse *
; *
;**********************************************************************
radix dec
;------------------------------------------------------------------------------
; PROCESSOR DECLARATION
;------------------------------------------------------------------------------
LIST P=12F629 ; list directive to define processor
#INCLUDE <P12F629.INC> ; processor specific variable definitions
LIST st=off
; --------------------------------
POS equ C ; +ve STATUS after subtraction
SIGN equ 7 ; True bit if negative value
;---------------------------------
;
; Branch if equal to destination
BEQ MACRO lDest
btfsc STATUS, Z
goto lDest
ENDM
;
; Branch if not equal to destination
BNE MACRO lDest
btfss STATUS, Z
goto lDest
ENDM
;------------------------------------------------------------------------------
#define _BANK1 bsf STATUS, RP0
#define _BANK0 bcf STATUS, RP0
;------------------------------------------------------------------------------
;
;
#define BITSIZE 11 ; number of TMR0 counts to a bitpulse
#define GOODREADS (3 -1) ; number of good reads reqd before send
;------------------------------------------------------------------------------
;
; CONFIGURATION WORD SETUP
;
; The 'CONFIG' directive is used to embed the configuration word within the
; .asm file. The labels following the directive are located in the respective
; .inc file. See the data sheet for additional information on configuration
; word settings.
;
;------------------------------------------------------------------------------
__CONFIG _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_ON & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT ;_XT_OSC
;===================================
; IO Port defines
;
OUTb equ 0 ; Serial out to other system (ICSPdata)
LED1b equ 1 ; Set Hi to light (ICSPclk)
IR1b equ 2 ; Input from IR1, lo on detect
; equ 3 ; unused (MCLR)
; equ 4 ; xtal
; equ 5 ; xtal
#define LED1 GPIO, LED1b
#define OUT GPIO, OUTb
#define IR1 GPIO, IR1b
;------------------------------------------------------------------------------
; VARIABLE DEFINITIONS
;------------------------------------------------------------------------------
_temp res 1
cartime res 1
carcount res 1
car res 1
carhold res 1
carsent res 1
count res 1
halfway res 1
;------------------------------------------------------------------------------
; EEPROM INITIALIZATION
;
; The 12F629 has 128 bytes of non-volatile EEPROM, starting at address 0x2100
;
;------------------------------------------------------------------------------
;
;DATAEE CODE 0x2100
;------------------------------------------------------------------------------
; OSCILLATOR CALIBRATION VALUE
;------------------------------------------------------------------------------
OSC CODE 0x03FF
; Internal RC calibration value is placed at location 0x3FF by Microchip as
; a RETLW K instruction, where the K is a literal value to be loaded into
; the OSCCAL register.
;------------------------------------------------------------------------------
; RESET VECTOR
;------------------------------------------------------------------------------
RESET_VECTOR CODE 0x0000 ; processor reset vector
goto START ; go to beginning of program
;------------------------------------------------------------------------------
; INTERRUPT SERVICE ROUTINE
;------------------------------------------------------------------------------
INT_VECTOR CODE 0x0004 ; interrupt vector location
; movwf W_TEMP ; save off current W register contents
; movf STATUS,w ; move status register into W register
; movwf STATUS_TEMP ; save off contents of STATUS register
; isr code can go here or be located as a call subroutine elsewhere
; movf STATUS_TEMP,w ; retrieve copy of STATUS register
; movwf STATUS ; restore pre-isr STATUS register contents
; swapf W_TEMP,f
; swapf W_TEMP,w ; restore pre-isr W register contents
retfie ; return from interrupt
;------------------------------------------------------------------------------
; MAIN CODE STARTS
;------------------------------------------------------------------------------
MAIN_PROG CODE
; Jump table for the various bit pulse times for cars
; entry: timer value in W,
; return: car # in W
CarCalc
btfsc cartime, 7
retlw 0
movf cartime, W
clrf PCLATH
addwf PCL, f
car_table
local i = 0
local c = 1
while i < (c +3) *BITSIZE -1
dt 0
i += 1
endw
while c <= 6
car_val#v(i) ; set the car values
while i < (c +3) *BITSIZE +(BITSIZE /2)
dt c
i += 1
endw
car_val#v(i) ; set intercar gap
while i < (c +1 +3) *BITSIZE -1
dt 0
i += 1
endw
c += 1
endw
while i < 128
dt 0
i += 1
endw
;------------------------------------------------------------------------------
; MAIN PROGRAM
;------------------------------------------------------------------------------
START
_BANK0
clrf GPIO
movlw 7
movwf CMCON
;------------------------------------------------------------------------------
; OSCCAL RESTORE (not required if internal OSC is not used)
;------------------------------------------------------------------------------
errorlevel -302 ; no warnings about non-bank 0 registers
call 0x3FF ; retrieve factory calibration value
banksel OSCCAL
movwf OSCCAL ; update register with factory cal value
;------------------------------------------------------------------------------
movlw (0 << NOT_GPPU) | (1 << INTEDG) | (0 << T0CS) | (1 << T0SE) | (0 << PSA) | 0x01
movwf OPTION_REG
movlw (1 << IR1b) | (0 << OUTb) | (0 << LED1b)
movwf TRISIO
movwf WPU
errorlevel +302
banksel INTCON
clrf INTCON
movlw (1 << OUTb) | (0 << LED1b)
movwf GPIO ; set a 'space' state on output line, & LEDs off
clrf car
;------------------------------------------------------------------------------
; a long high implies that the car is going over the sensor, and its in darkness
long_silence
movlw GOODREADS
movwf carcount
clrf carsent
clrf carhold
bcf LED1
; wait for start pulse on inport (rising edge)
await_start
clrf TMR0
awaiting_start
btfsc TMR0, 7
goto long_silence
btfss IR1
goto awaiting_start ; (while lo)
btfss IR1
goto awaiting_start ; (while lo)
clrf TMR0
btfss IR1
goto awaiting_start ; (while lo)
; start has occured, now await the changing edge - this defines the 'lane switch or not'
await_change
btfsc IR1
goto await_change
btfsc IR1
goto await_change
btfsc IR1
goto await_change
; edge has restored, now check the duration - this must be 9 or more (11 optimum) Timer cycles to be valid
movf TMR0, W
movwf halfway
movlw BITSIZE -3
subwf halfway, W
btfss STATUS, C
goto await_start ; edge is too short, must be noise, try again
await_next_start
btfsc TMR0, 7 ; if timer over runs, then a long silence has occurred
goto long_silence
btfss IR1
goto await_next_start
btfss IR1
goto await_next_start
btfss IR1
goto await_next_start
; do calcs in here for car id
movf TMR0, W
movwf cartime
clrf TMR0
movlw BITSIZE -3
addwf halfway, W
subwf cartime, W
btfss STATUS, C
goto await_change ; edge is too short, try again
call CarCalc
btfsc STATUS, Z ; check for illegal value
goto await_change
car_send
movwf car
movf carhold, w ; see if we've already got it before
BNE check_send
car_save
movf car, w ; first time or new decode, save for recheck
movwf carhold
movlw GOODREADS
movwf carcount
goto await_change
check_send
movf carhold, w
xorwf car, w ; second or later time, compare both
BNE car_save
decfsz carcount, f ; decode must be the same for GOODREADS times
goto await_change
movf car, w ; see if we already sent this car id
xorwf carsent, w
BEQ await_change
;------------------------------------------------------------------------------
; All match, send the car number out
bsf LED1
movf car, w
movwf carsent
movlw 0x30
iorwf car, f
movlw 12
movwf count
bcf STATUS, C
tx_loop
btfsc STATUS, C
goto $ +4
nop ; send a lo
bcf OUT
goto $ +3
bsf OUT ; send a hi
goto $ +1
call bit_delay ; pause for the remainder of the bit time
bsf STATUS, C ; set a stop bit, and rotate the value
rrf car, f
decfsz count, f ; finish when all 11 bits sent
goto tx_loop
; await a suitable end point, then loop again
await_end
btfsc IR1
goto await_end
btfsc IR1
goto await_end
btfsc IR1
goto await_end
goto await_start
;------------------------------------------------------------------------------
; Delay of: 17.3 (57600bd); -15 cycles (for mainline code, inc call/return)
; for 4MHz crystal (1 MHz cycle)
bit_delay
goto $ +1
retlw 0
END ; 'end of program'