Block signals on an Arduino Uno
Three IR detectors at the block boundaries and two searchlight signals that show red for an occupied block, yellow when the next one is occupied and green when both are clear. The whole build, an Arduino Uno and 7 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Advanced Runs in your browser. Free, and no account needed.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Block signals: two three-aspect signals following a train through two blocks.
//
// pin 8 detector at the entrance to block A (a beam sensor, HIGH while a car breaks it)
// pin 7 detector at the entrance to block B
// pin 6 detector at the exit of block B
// pins 5, 4 signal 1 at the entrance to A: the red and green legs of a bicolor LED
// pins 3, 2 signal 2 at the entrance to B, the same
//
// Each signal is one bicolor LED, the way many modelers build a searchlight
// signal: red, green, or both legs lit together for yellow.
//
// Automatic block signaling, the simple one-direction kind. Each signal guards
// the block in front of it:
// red that block is occupied: stop
// yellow that block is clear but the next one is not: approach, ready to stop
// green the next two blocks are clear
//
// Real layouts usually detect occupancy by current: a detector sees the small
// current a locomotive motor or a resistor wheelset draws anywhere in the block.
// Mokxi does not model track current, so these are spot detectors at the block
// boundaries, and the sketch remembers which block the train is in: a block
// becomes occupied when the train trips its entrance detector, and clears once
// the train has fully passed the next detector. Past the exit detector is block
// C, beyond this panel, which the sketch counts as occupied for C_CLEARS_MS.
//
// Click a detector's card into its slot to put a car in the beam, and out again.
const int DETECTOR_PIN[3] = {8, 7, 6};
const int SIGNAL_PINS[2][2] = {{5, 4}, {3, 2}}; // red leg, green leg
const unsigned long QUIET_MS = 1500; // a detector clear this long: the train has passed
const unsigned long C_CLEARS_MS = 8000; // how long block C stays occupied after the exit
bool occupied[3]; // blocks A, B and C
bool sawTrain[3]; // detector i has a train over it, or had one recently
unsigned long lastSeen[3];
unsigned long cEnteredAt = 0;
int shown[2] = {-1, -1};
const char *const ASPECTS[] = {"red", "yellow", "green"};
const char BLOCK_NAMES[] = "ABC";
void enter(int block) {
if (occupied[block]) return;
occupied[block] = true;
Serial.print("block ");
Serial.print(BLOCK_NAMES[block]);
Serial.println(" occupied");
}
void leave(int block) {
if (!occupied[block]) return;
occupied[block] = false;
Serial.print("block ");
Serial.print(BLOCK_NAMES[block]);
Serial.println(" clear");
}
void setSignal(int s, int aspect) {
if (shown[s] == aspect) return;
shown[s] = aspect;
// red lights the red leg, green the green one, yellow both
digitalWrite(SIGNAL_PINS[s][0], aspect != 2 ? HIGH : LOW);
digitalWrite(SIGNAL_PINS[s][1], aspect != 0 ? HIGH : LOW);
Serial.print("signal ");
Serial.print(s + 1);
Serial.print(": ");
Serial.println(ASPECTS[aspect]);
}
void setup() {
Serial.begin(9600);
for (int i = 0; i < 3; i++) pinMode(DETECTOR_PIN[i], INPUT);
for (int s = 0; s < 2; s++) {
for (int i = 0; i < 2; i++) pinMode(SIGNAL_PINS[s][i], OUTPUT);
}
Serial.println("Block signals ready: blocks A and B, then C beyond the panel");
}
void loop() {
unsigned long now = millis();
for (int d = 0; d < 3; d++) {
if (digitalRead(DETECTOR_PIN[d]) == HIGH) {
if (!sawTrain[d]) enter(d); // the train is at the entrance to block d
sawTrain[d] = true;
lastSeen[d] = now;
if (d == 2) cEnteredAt = now;
} else if (sawTrain[d] && now - lastSeen[d] >= QUIET_MS) {
// The whole train has passed detector d, so it has left the block behind it.
sawTrain[d] = false;
if (d > 0) leave(d - 1);
}
}
if (occupied[2] && !sawTrain[2] && now - cEnteredAt >= C_CLEARS_MS) leave(2);
// Each signal looks at the block it guards and the one after.
for (int s = 0; s < 2; s++) {
if (occupied[s]) setSignal(s, 0);
else if (occupied[s + 1]) setSignal(s, 1);
else setSignal(s, 2);
}
delay(10);
}
Parts list
10 parts, plus the jumper wires. Every one is in the editor's parts bin.
How it is wired
11 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Ground: Arduino Uno R3 pin GND; Photo interrupter, slot (1) pin GND; Photo interrupter, slot (2) pin GND; Photo interrupter, slot (3) pin GND; Resistor, 220 Ω (1) pin 2; Resistor, 220 Ω (2) pin 2
- Arduino Uno R3 pin 8; Photo interrupter, slot (1) pin SIG
- Arduino Uno R3 pin 7; Photo interrupter, slot (2) pin SIG
- Arduino Uno R3 pin 6; Photo interrupter, slot (3) pin SIG
- Arduino Uno R3 pin 5; Two-color LED, KY-011 (1) pin R
- Arduino Uno R3 pin 4; Two-color LED, KY-011 (1) pin G
- Arduino Uno R3 pin 3; Two-color LED, KY-011 (2) pin R
- Arduino Uno R3 pin 2; Two-color LED, KY-011 (2) pin G
- 5 V: Photo interrupter, slot (1) pin VCC; Photo interrupter, slot (2) pin VCC; Photo interrupter, slot (3) pin VCC
- Two-color LED, KY-011 (1) pin C; Resistor, 220 Ω (1) pin 1
- Two-color LED, KY-011 (2) pin C; Resistor, 220 Ω (2) pin 1
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.