Signal Decoder #3 Sketch

Here’s he very latest sketch – this uses CT coils to detect block occupancy, which works a lot better than the original IR sensors. The board also now has a small OLED display attached – I haven’t found a good use for the display yet, but it looks cool.

use with decoder board #3

 

#include <DCC_Decoder.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// signal controller #3 Andy Moore 18/06/2020

#define OLED_RESET 0

Adafruit_SSD1306 display(OLED_RESET);

#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH 16
static const unsigned char PROGMEM logo16_glcd_bmp[] = {
0x00,0x00,0x00,0x20,0x00,0x60,0x00,0xE0,0x00,0xE0,0xEE,0xCC,0x7E,0xCE,0x7F,0xDE,
0x7F,0xFF,0x3F,0xFB,0x3B,0x33,0x11,0x27,0x00,0x0E,0x00,0x0E,0x00,0x0C,0x00,0x00
};

#if (SSD1306_LCDHEIGHT != 32)
#error(“Height incorrect, please fix Adafruit_SSD1306.h!”);
#endif

const int SAMPLESIZE= 500;
int CurrentIndex=0;
long TimerVal=0;
struct s_sensor
{
int Pin;
int EntrySignal;
int ExitSignal;
int PreviousSensor;
bool Occupied;
byte TripCount=0;
};

struct s_DCCSignal
{
int cv;
int RedPin;
int GreenPin;
int RelayPin;
int SemaphorePin;
bool SignalSetToDanger;
bool IsSemaphore;
byte SemaphoreState;

};

const byte CLEAR = 0;
const byte DANGER = 1;
const byte C_UNKNOWN = 2;

const byte num_sensors = 5;
const byte num_signals = 5;

s_sensor sensors[num_sensors];
s_DCCSignal signals[num_signals];

//float RawData[num_sensors][SAMPLESIZE];

//int RelayPin=3;
//int ledPin = 5;
//int GreenPin = 6;
//int buttonApin = 9;
//int buttonBpin = 8;

//int latchPin = 11;
//int clockPin = 10;
//int dataPin = 12;

//byte leds = 0;

unsigned long timer = 0;
unsigned long current_time = millis();
unsigned long SerialCounter =0;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Defines and structures
//
#define kDCC_INTERRUPT 0

//
// Setup
//
void setup()
{
Serial.begin(9600);
DCC.SetBasicAccessoryDecoderPacketHandler(BasicAccDecoderPacket_Handler, true);

DCC.SetupDecoder( 0x00, 0x00, kDCC_INTERRUPT );
// by default, we’ll generate the high voltage from the 3.3v line internally! (neat!)
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3D (for the 128×64)
// init done

// Show image buffer on the display hardware.
// Since the buffer is intialized with an Adafruit splashscreen
// internally, this will display the splashscreen.

display.display();
delay(3000);
display.clearDisplay();

sensors[0].Pin = A0;
sensors[0].EntrySignal = 0;
sensors[0].ExitSignal = 0;
sensors[0].PreviousSensor = 2;

sensors[1].Pin = A1;
sensors[1].EntrySignal = 2;
sensors[1].ExitSignal = 2;
sensors[1].PreviousSensor = 3;

sensors[2].Pin = A2;
sensors[2].EntrySignal = 4;
sensors[2].ExitSignal = 4;
sensors[2].PreviousSensor = 1;

sensors[3].Pin = A3;
sensors[3].EntrySignal = 1;
sensors[3].ExitSignal = 1;
sensors[3].PreviousSensor = 4;

sensors[4].Pin = A6;
sensors[4].EntrySignal = 3;
sensors[4].ExitSignal =3;
sensors[4].PreviousSensor = 0;

signals[0].cv = 7;
signals[0].RedPin = 12;
signals[0].GreenPin = 3;
signals[0].SemaphorePin = 0;
signals[0].SignalSetToDanger = false;
signals[0].RelayPin = 8;
signals[0].IsSemaphore = false;
pinMode(signals[0].RedPin,OUTPUT);
pinMode(signals[0].GreenPin,OUTPUT);

signals[1].cv = 8;
signals[1].RedPin = 10;
signals[1].GreenPin = 11;
signals[1].SemaphorePin = 8;
signals[1].SignalSetToDanger = false;
signals[1].RelayPin = 7;
signals[1].IsSemaphore = true;
signals[1].SemaphoreState = C_UNKNOWN;

signals[2].cv=20;
signals[2].RedPin = 10;
signals[2].GreenPin = 11;
signals[2].SemaphorePin = 0;
signals[2].SignalSetToDanger = false;
signals[2].RelayPin = 7;
signals[2].IsSemaphore = false;
signals[2].SemaphoreState = C_UNKNOWN;
pinMode(signals[2].RedPin,OUTPUT);
pinMode(signals[2].GreenPin,OUTPUT);

signals[3].cv=21;
signals[3].RedPin = 6;
signals[3].GreenPin = 9;
signals[3].SemaphorePin = 0;
signals[3].SignalSetToDanger = false;
signals[3].RelayPin = 7;
signals[3].IsSemaphore = false;
signals[3].SemaphoreState = C_UNKNOWN;
pinMode(signals[3].RedPin,OUTPUT);
pinMode(signals[3].GreenPin,OUTPUT);

signals[4].cv=22;
signals[4].RedPin = 4;
signals[4].GreenPin = 5;
signals[4].SemaphorePin = 0;
signals[4].SignalSetToDanger = false;
signals[4].RelayPin = 7;
signals[4].IsSemaphore = false;
signals[4].SemaphoreState = C_UNKNOWN;
pinMode(signals[4].RedPin,OUTPUT);
pinMode(signals[4].GreenPin,OUTPUT);
pinMode(signals[1].RelayPin, OUTPUT);

pinMode(signals[1].SemaphorePin, OUTPUT);
digitalWrite(signals[1].SemaphorePin, HIGH);

//pinMode(latchPin, OUTPUT);
//pinMode(dataPin, OUTPUT);
//pinMode(clockPin, OUTPUT);

display.setTextSize(1);
display.setTextColor(WHITE);

//SetSignalRed(0);
//SetSignalRed(2);
//SetSignalRed(3);
//SetSignalRed(4);
// display.display();
display.println(“*********************”);
display.println(“*Signal Controller 3*”);
display.println(“*********************”);
display.display();

}

unsigned long GetAnalog(int SensorNum)
{
int i;
int temp;
unsigned long rms=0;
for (i=0;i<SAMPLESIZE;i++)
{
temp=analogRead(sensors[SensorNum].Pin);
rms = rms+(temp*temp);
}
//Serial.println((long)rms,DEC);
rms=(sqrt(rms)*100)/(SAMPLESIZE/10);
// Serial.println((long)rms,DEC);
return (rms);
}

void HandleSensor(int SensorNum)
{
unsigned long rms;
rms=GetAnalog(SensorNum);
// Serial.print((long)rms,DEC);
// Serial.print(“\t”);

if (rms > 115)
{

SensorTripped(SensorNum);
}
else
{

SensorClear(SensorNum);
}

DCC.loop();
}

void loop()
{

// display.clearDisplay();
// display.setCursor(0,0);

DCC.loop();

if (millis()-TimerVal>200)
{

HandleSensor(CurrentIndex);
TimerVal=millis();
CurrentIndex++;
if (CurrentIndex>4)
{
CurrentIndex=0;
// display.clearDisplay();
// display.setCursor(0,0);
//Serial.println();
}
}
}

void SensorTripped(int SensorNum)
{

sensors[SensorNum].TripCount=0;
if (sensors[SensorNum].Occupied==false)
{
// display.print(“Sensor “);
// display.print(SensorNum);
// display.println(” tripped”);
// display.display();
sensors[SensorNum].Occupied=true;
}
SetSignalRed(sensors[SensorNum].EntrySignal);

}

void SensorClear(int SensorNum)
{
if (sensors[SensorNum].TripCount > 2)
{
if (sensors[SensorNum].Occupied)
{
sensors[SensorNum].TripCount=0;
// display.print(“Sensor “);
// display.print(SensorNum);
// display.println(” cleared”);
// display.display();
sensors[SensorNum].Occupied=false;
//if (sensors[sensors[SensorNum].PreviousSensor].Occupied==false)
{
if (!signals[sensors[SensorNum].ExitSignal].SignalSetToDanger)
{
SetSignalGreen(sensors[SensorNum].ExitSignal);
}
}
}
}
else
{
sensors[SensorNum].TripCount++;
}
}

void TwoAspectHandler(int signum, boolean enable)
{
if (enable)
{
signals[signum].SignalSetToDanger = true;
SetSignalRed(signum);
}
else
{
signals[signum].SignalSetToDanger = false;
SetSignalGreen(signum);
}
}
//
// Basic accessory packet handler
//
void BasicAccDecoderPacket_Handler(int address, boolean activate, byte data)
{
// Convert NMRA packet address format to human address
address -= 1;
address *= 4;
address += 1;
address += (data & 0x06) >> 1;
boolean enable = (data & 0x01) ? 1 : 0;
// The DCC Accessory Address is now stored in “address” variable
Serial.print(F(“Basic addr: “));
Serial.println(address, DEC);
Serial.print(F(“Activate Status: “));
Serial.println(enable, DEC);
switch(address)
{
case 7:
{
Serial.print(“Signal 7 “);
TwoAspectHandler(0,enable);

break;
}
case 8:
{
//this is a semaphore signal
Serial.print(“Signal 8 “);
if (enable)
{
if (signals[1].SemaphoreState == DANGER)
{
//already set to danger by sensors – this will clear the signal, so pulse it back to danger
//start a timer here
//pulseSignal( signals[1].SemaphorePin);
}
signals[1].SignalSetToDanger = true;
signals[1].SemaphoreState = DANGER;
digitalWrite(signals[1].RelayPin,LOW);

}
else
{
signals[1].SignalSetToDanger = false;
signals[1].SemaphoreState = CLEAR;
digitalWrite(signals[1].RelayPin,HIGH);
}

break;
}
case 20:
{
Serial.print (“signal 20 “);
TwoAspectHandler(2,enable);
break;
}
case 21:
{
Serial.print (“signal 21 “);
TwoAspectHandler(3,enable);
break;
}
case 22:
{
Serial.print (“signal 22 “);
TwoAspectHandler(4,enable);
break;
}
case 40:
{
//toggle semaphore
pulseSignal( signals[1].SemaphorePin);
break;
}
case 41:
{
//toggle semaphore
break;
}

default:
{
break;
}
}
}

void updateShiftRegister()
{
//digitalWrite(latchPin, LOW);
//shiftOut(dataPin, clockPin, LSBFIRST, leds);
//digitalWrite(latchPin, HIGH);
}

void pulseSignal(int pinNum)
{
//Serial.println(“pulse signal”);
digitalWrite( pinNum,LOW);

delay(300);
digitalWrite( pinNum,HIGH);
//DCC.loop();

}

void SetSignalRed(int Signum)
{

if (signals[Signum].IsSemaphore)
{
if (signals[Signum].SignalSetToDanger)
{
//do nothing
}
else
{
if (signals[Signum].SemaphoreState == CLEAR)
{
//Serial.print(“Pulse signal “);
//Serial.print(Signum, DEC);
//Serial.println(” To danger”);
pulseSignal( signals[Signum].SemaphorePin);
signals[Signum].SemaphoreState = DANGER;
digitalWrite(signals[Signum].RelayPin,LOW);
}
}
}
else
{
//Serial.print(F(“set signal Red: “));
//Serial.println(Signum, DEC);
//analogWrite(signals[Signum].RelayPin, 0);
digitalWrite(signals[Signum].RedPin,LOW);
digitalWrite(signals[Signum].GreenPin,HIGH);

}
}

void SetSignalGreen(int Signum)
{
if (signals[Signum].IsSemaphore)
{
if (signals[Signum].SignalSetToDanger)
{
//do nothing
}
else
{
if (signals[Signum].SemaphoreState == DANGER)
{
// Serial.print(“Pulse signal “);
// Serial.print(Signum, DEC);
// Serial.println(” To clear”);
pulseSignal( signals[Signum].SemaphorePin);
signals[Signum].SemaphoreState = CLEAR;
digitalWrite(signals[Signum].RelayPin,HIGH);
}
}
}
else
{
//Serial.print(F(“SetSignalGreen: “));
//Serial.println(Signum, DEC);
//analogWrite(signals[Signum].RelayPin, 255);
digitalWrite(signals[Signum].RedPin,HIGH);
digitalWrite(signals[Signum].GreenPin,LOW);
}
}

Short update

I’ve not written the next installment of the signal decoder development yet as I have been tinkering. I’ve replaced two short points that crossover at undershelf station with two long electrofrog versions, as the old track was already ballasted this took rather a long time.

I’ve also been having issues with the IR sensors I’ve been using – in daylight they are tripping all over the place, so I’ve been tinkering with a current sensing circuit, this is now working, so I need to disconnect the decoder & wire in 5 new current sensing circuits then rewrite a lot of the software for the Arduino. Once that’s done I’ll get back to writing the development articles.

Arduino hosted block control

Following the success of my experiment with the Arduino Uno using a breadboard, I decided to go ahead & build a permanently fitted signal decoder. This will control 5 signals, controlling 5 individual blocks using ABC braking & IR sensors.

components:

  • Vero board                                  50p
  • DCC Arduino Nano shield        £15
  •  Arduino Nano (Makerhawk)  £5.48
  •  5x IR sensor                               £3.69
  •  8 channel relay block              £9.99
  •  74HC595 shift register             £1
  •  numerous connectors              £5

Total:  £40.66

It’s Saturday today & I’ve managed to get the circuit built & tested. So far 1 signal, two sensors & one ABC  module are plugged in & working. Here are a couple of pictures of the board.

With the DCC supply connected to the Arduino shield, the nano is actually powered by the shield. I have written the software so that all the signals start up at danger, they then get reset by the Railmaster software a little later when it starts up. This ensures that none of the trains travel around the layout uncontrolled before the controller has started up.

Arduino accessory decoder

It’s time to take the next step in automating the railway – feedback.

The Elink doesn’t have any facility for feedback, neither does Railmaster. So I’m looking at building my own decoder that incorporates signal control & block sensing. This will be done using an Arduino UNO.

The plan is to initially get one signal that can be set to danger, or clear from Railmaster using DCC, I will then set up sensors to monitor the section of track in front of the signal – setting it to danger when the section is occupied & setting it back to whatever state Railmaster set it to once the section is clear.

If this works, I’ll keep extending it until the UNO can handle no more.

The DCC & signal control aren’t actually too difficult – the block occupancy will be the tricky bit. I initially thought about using a current sensor to detect occupancy of isolated sections, but this has some drawbacks – rail cutting & lots of wiring for one, and also if a loco stalls due to dirty wheels or track, it will look like the section is empty. So my first attempt is going to use IR detectors to detect entry & exit of the sections.

Parts ordered so far:

Elegoo UNO starter set    £15.99

5x IR detectors                  £3.69

Arduino DCC module      £8.00

Semaphore signals

 

btr

Well it’s been quite a while since any update, but today I’ve added three Dapol semaphore signals to the railway, home signals on platforms 1&2 of Undershelf station & a distant signal on the approach to platform 1.

 

 

 

 

New Department Store opens in Shedend

Grace Brothers are proud to announce the opening of their latest store in the town of Shedend. Ground floor for perfumery, stationary, leather goods, wigs, haberdashery and food. First floor for telephones, gents ready made suits, ties, hats underwear and shoes. Second floor for carpets, travel goods, bedding, materials, soft furnishings, restaurants and ties. After a short opening ceremony, young mr Grace was heard to say “you’ve all done very very well.

bty

Shedend population grows

Following last month’s opening of the Railway Inn & the council’s promotion  of the town, the population has now grown to double figures. The creators of Shedend continue to add more infrastructure and to paint more people….

Here are a few pictures out & about  Shedend.