Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the common S88 sensors. #166

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
6 changes: 5 additions & 1 deletion CommandStation-EX.ino
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/


#include "DCCEX.h"

// Create a serial command parser for the USB connection,
Expand Down Expand Up @@ -101,6 +100,11 @@ void setup()
#endif

LCD(1,F("Ready"));

#ifdef S88_MEGA
(S88Mega::getInstance())->S88_Init(S88_BUS0_LENGTH, S88_BUS1_LENGTH,
S88_BUS2_LENGTH, S88_BUS3_LENGTH);
#endif
}

void loop()
Expand Down
7 changes: 6 additions & 1 deletion DCCEX.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@
#include "RMFT.h"
#define RMFT_ACTIVE
#endif


#ifdef S88_MEGA
#include "S88Mega.h"
#endif


#endif
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,11 @@ in config.h.
* Release cabs from memory command <-> all cabs, <- CAB> for just one loco address
* Automatic slot (register) management
* Automation (coming soon)
* Support for the common S88 sensors.

NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means to you, click [HERE](notes/rewrite.md).

# More information
You can learn more at the [DCC++ EX website](https://dcc-ex.com/)

- November 14, 2020
- June 03, 2021
218 changes: 218 additions & 0 deletions S88Mega.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
//Define from config.h
#if __has_include ( "config.h")
#include "config.h"
#else
#warning config.h not found.Using defaults from config.example.h
#include "config.example.h"
#endif

#ifdef S88_MEGA

#include "S88Mega.h"
#include "DIAG.h"

S88Mega* S88Mega::instance = NULL;
void S88Mega::S88_Init(byte bus0len, byte bus1len, byte bus2len, byte bus3len)
{
byte i;
Timer5_Off();
portreg = 0;
ledcounter = 0;
rm.bus[0] = bus0len;
rm.bus[1] = bus1len;
rm.bus[2] = bus2len;
rm.bus[3] = bus3len;
rm.buslen = 0;
for (i = 0; i < 4; i++) {
if (rm.bus[i] > S88_CHAIN_MAX) rm.bus[i] = S88_CHAIN_MAX;
if ((rm.bus[i] > 0) && (rm.bus[i] < 8)) rm.bus[i] = 8;
if (rm.bus[i] > rm.buslen) rm.buslen = rm.bus[i];
}
memset(RmBytes, 0x00, rm.buslen);
eNextLoopStep = S88_SET_LOAD;
InIndex = 0;
S88_PORTDIR = B11110000; // bit 0 - 3 = input , bit 4 - 7 = output
S88_PORTOUT = B10000000; // LED aus, Reset aus, Load aus, Clock aus
DIAG(F("S88 Initalized"));
for (i = 0; i < 4; i++) {
DIAG(F("Bus length %d = %d"), i, rm.bus[i]);
}
DIAG(F("Bus length max = %d"), rm.buslen);
Timer5_Init();
}


void S88Mega::Timer5_Init(void)
{
#ifdef S88_USE_TIMER
noInterrupts();
Timer5_Off();
TIMER5_100us; // compare 1599, Prescaler 1 100us
//the following lines are alternative timers if your sensors need special timings
// TIMER5_25us; // compare 399, prescaler 1 25us
// TIMER5_50us; // compare 799, Prescaler 1 50us
// TIMER5_200us; // compare 3199, Prescaler 1 200us
// TIMER5_400us; // compare 6399, Prescaler 1 400us
// TIMER5_6400us; // compare 15999, Prescaler 1 1ms
TIMSK5 |= (1 << OCIE5A);
DIAG(F("Timer5 initialized 100us for S88 scan"));
// Serial.println("Timer1 initialized 100us");
interrupts();
#endif
}
#ifdef S88_USE_TIMER

ISR(TIMER5_COMPA_vect)
{
S88Mega* instance = S88Mega::getInstance();
if (instance != NULL) {
instance->loop();
}
}
#endif

void S88Mega::Timer5_Off(void)
{
#ifdef S88_USE_TIMER
TCCR5A = 0;
TCCR5B = 0;
TCNT5 = 0;
TIMSK5 = 0;
#endif
}

void S88Mega::S88_Set_RM(byte value)
{
portreg &= B10001111;
portreg |= value;
S88_PORTOUT = portreg;
}

boolean S88Mega::is_rm(byte rmindex, byte busindex)
{
if (!rm.bus[busindex]) return (false);
if (rm.bus[busindex] < rmindex) return (false);
return (true);
}

void S88Mega::S88_Read(void)
{
byte indata = S88_PORTIN & 0x0F;
for (byte i = 0; i < 4; i++)
{
if (is_rm(InIndex, i))
{
if (bitRead(indata, i) != bitRead(RmBytes[InIndex], i))
{
bitWrite(RmBytes[InIndex], i, bitRead(indata, i));
bitSet(RmBytes[InIndex], i + 4);
}
}
}
}
void S88Mega::S88_CheckChanges(Print* stream)
{
boolean rd;
word addr;
for (byte i = 0; i < rm.buslen; i++)
{
for (byte x = 0; x < 4; x++)
{
if (is_rm(i, x))
{
if (bitRead(RmBytes[i], x + 4))
{
addr = i + (S88AdrBase * x) + S88AdrBase;
bitClear(RmBytes[i], x + 4);
rd = bitRead(RmBytes[i], x);
if (stream != NULL) {
StringFormatter::send(stream, F("<%c %d>"), rd ? 'Q' : 'q', addr);
}
}
}
}
}
}
void S88Mega::S88_Status(void)
{
for (byte i = 0; i < rm.buslen; i++)
{
for (byte x = 0; x < 4; x++)
{
if (is_rm(i, x)) bitSet(RmBytes[i], x + 4);
}
}
}
//overloading the ++ operator, so you can increment an enum
inline S88NextLoopStep& operator++(S88NextLoopStep& currentValue, int)
{
if (currentValue == S88_SET_AFTER_RESET) {
currentValue = static_cast<S88NextLoopStep>(0);
return currentValue;
}
const int i = static_cast<int>(currentValue);
currentValue = static_cast<S88NextLoopStep>(i + 1);
return currentValue;
}

void S88Mega::loop() {
switch (eNextLoopStep)
{
case S88_SET_CLOCK:
S88_CLOCK;
eNextLoopStep++;
break;
case S88_SET_CLEAR_CLOCK:
S88_NOSIGNAL;
S88_Read();
InIndex++;
if (InIndex >= rm.buslen)
{
eNextLoopStep = S88_SET_LOAD;
return;
}
eNextLoopStep = S88_SET_CLOCK;
break;
case S88_SET_LOAD:
ledcounter++;
if (ledcounter > 200)
{
ledcounter = 0;
S88_LED_TOGGLE;
}
InIndex = 0;
S88_LOAD;
eNextLoopStep++;
break;
case S88_SET_LOAD_CLOCK:
S88_LOAD_CLOCK;
eNextLoopStep++;
break;
case S88_SET_LOAD_NOCLOCK:
S88_LOAD;
eNextLoopStep++;
break;
case S88_SET_AFTERLOAD:
S88_NOSIGNAL;
eNextLoopStep++;
break;
case S88_SET_RESET:
InIndex = 0;
S88_Read();
InIndex++;
S88_RESET;
eNextLoopStep++;
break;
case S88_SET_AFTER_RESET:
S88_NOSIGNAL;
eNextLoopStep = S88_SET_CLOCK;
break;
default:
S88_NOSIGNAL;
eNextLoopStep = S88_SET_LOAD;
break;
}
}


#endif
121 changes: 121 additions & 0 deletions S88Mega.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* S88Mega.h
* G�nther Simon
* 29.05.2021
*
* S88Mega.cpp and S88Mega.h are the source files for supporting the common S88 bus sensors.
*
* You can choose to use
* the standard-loop, so you do need an extra timer / int and can use it for other things but the loop may run with variying speed (inacurrate)
* the timer based solution. Just comment in the line
#define S88_USE_TIMER
in the config.h file.
You can connect the pins of the parallel port of the S88 sensors to the pins on the Arduino. You can change the pins by changing
the defines S88_PORTIN and S88_PORTOUT.

Here are the connection details:
ParallelPortPin Meaning Arduino PORTA PORTC PORTL
2 Clock 26 33 45
3 Load 27 32 44
4 Reset 28 31 43
10 Bus0 22 37 49
11 Bus1 23 36 48
12 Bus2 24 35 47
13 Bus3 25 34 46
14 5V 5V ->
*/

//Define from config.h
#ifdef S88_MEGA

//duplicate include guard
#ifndef __S88_Mega_h
#define __S88_Mega_h

#include <Arduino.h>
#pragma once

#ifdef S88_USE_TIMER
// We use the Arduino Timer 5
ISR(TIMER5_COMPA_vect);

#define TIMER5_25us OCR5A = 399;TCCR5B |= ((1 << CS50) | (1 << WGM52)) // compare 399, prescaler 1 25us
#define TIMER5_50us OCR5A = 799;TCCR5B |= ((1 << CS50) | (1 << WGM52)) // compare 799, Prescaler 1 50us
#define TIMER5_100us OCR5A = 1599;TCCR5B |= ((1 << CS50) | (1 << WGM52)) // compare 1599, Prescaler 1 100us
#define TIMER5_200us OCR5A = 3199;TCCR5B |= ((1 << CS50) | (1 << WGM52)) // compare 3199, Prescaler 1 200us
#define TIMER5_400us OCR5A = 6399;TCCR5B |= ((1 << CS50) | (1 << WGM52)) // compare 6399, Prescaler 1 400us
#define TIMER5_6400us OCR5A = 15999;TCCR5B |= ((1 << CS50) | (1 << WGM52)) // compare 15999, Prescaler 1 1ms
#endif

// Port input bits 0 = Bus0, 1 = Bus1, 2 = Bus2, 3 = Bus3
// Port output bits 4 = Clock, 5 = Load, 6 = Reset, 7 = Contoll LED (LED ON = L, LED OFF = H)
#define S88_PORTIN PINA // (Data Input)
#define S88_PORTOUT PORTA // (Data Output)
#define S88_PORTDIR DDRA // (Data Direction)
#define S88AdrBase 100 // Addressbase Bus0 = 100 - ..., Bus1 = 200 - ..., Bus2 = 300 - ..., Bus3 = 400 - ...
#define S88_CHAIN_MAX 16 // max number of sensors on a bus

#define S88_RESET S88_Set_RM(B01000000)
#define S88_LOAD S88_Set_RM(B00100000)
#define S88_LOAD_CLOCK S88_Set_RM(B00110000)
#define S88_NOSIGNAL S88_Set_RM(B00000000)
#define S88_CLOCK S88_Set_RM(B00010000)
#define S88_LED_ON portreg &= B01111111;
#define S88_LED_OFF portreg |= B10000000;
#define S88_LED_TOGGLE bitWrite (portreg, 7, !bitRead (portreg, 7))


typedef struct
{
byte bus[4];
byte buslen;
} S88_RM;

//The loop is doing one step and than continue with the next. Using enum instead of just defines
enum S88NextLoopStep {
S88_SET_CLOCK,
S88_SET_CLEAR_CLOCK,
S88_SET_LOAD,
S88_SET_LOAD_CLOCK,
S88_SET_LOAD_NOCLOCK,
S88_SET_AFTERLOAD,
S88_SET_RESET,
S88_SET_AFTER_RESET
};

class S88Mega {
public:
//Get the only instance. Created at the first call
static S88Mega* getInstance()
{
if (instance == NULL)
{
instance = new S88Mega();
}
return instance;
}
void S88Mega::Timer5_Init(void);
void S88Mega::Timer5_Off(void);
void S88Mega::S88_Set_RM(byte value);
void S88Mega::S88_Init(byte bus0len, byte bus1len, byte bus2len, byte bus3len);
void S88Mega::S88_Status(void);
void S88Mega::S88_CheckChanges(Print* stream);
boolean S88Mega::is_rm(byte rmindex, byte busindex);
void S88Mega::S88_Read(void);
void S88Mega::loop();

private:
static S88Mega* instance;
byte portreg;
//The lower 4 bytes are data of the bux 0-3, the upper 4 bytes remember if the value has changed
byte RmBytes[S88_CHAIN_MAX];
//Index of the next sensor which will be processed
byte InIndex;
S88_RM rm;
byte ledcounter;
S88NextLoopStep eNextLoopStep = S88_SET_CLOCK;
};

#endif // __S88_Mega_h

#endif S88_MEGA
Loading