Files
MarklinController/main.cpp
2022-03-21 21:22:18 +01:00

404 lines
11 KiB
C++

#include <avr/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <avr/interrupt.h>
#include "serial.h"
#include "writepin.h"
#include "train.h"
#include "eeprom.h"
#include "bitrep.h"
#include "ringbuffer.h"
#include "staticvector.h"
#include "turnout.h"
#include "signal.h"
#include "shiftreg.h"
#include "softspim.h"
#include "nfcbord.h"
#include "defines.h"
char buffer[SNPRINTF_BUFFER_SIZE];
SVector<Train, 32> trains;
SVector<Turnout, 32> turnouts;
SVector<Signal, 32> signals;
bool autoff = true;
bool powerIsOn = true;
volatile uint8_t tick = 0;
volatile bool resendEvent = false;
uint8_t itemToResend = 0;
void setPower(bool on);
void save_state();
#include "traindispatch.h"
#include "turnoutdispatch.h"
#include "signaldispatch.h"
ISR(TIMER0_OVF_vect)
{
if(tick == 0)
resendEvent = true;
tick = !tick;
}
void timer0InterruptEnable(const bool enable)
{
if(enable)
TIMSK0 = 0b00000001;
else
TIMSK0 = 0b00000000;
}
void save_state()
{
cli();
EEPROM_write_char( 0, trains.count());
EEPROM_write_char( 1, autoff);
EEPROM_write_char( 2, trains.maxSize());
EEPROM_write_char( 3, turnouts.count());
EEPROM_write_char( 4, turnouts.maxSize());
EEPROM_write_char( 5, signals.count());
EEPROM_write_char( 6, signals.maxSize());
const uint16_t trainOffset = EEPROM_RESERVE;
for(uint8_t i = 0; i < trains.count(); i++)
{
EEPROM_write_char( i*BLOCK+trainOffset, trains[i].getAddress());
EEPROM_write_char( i*BLOCK+trainOffset+1, trains[i].getFunctionMask());
EEPROM_write_char( i*BLOCK+trainOffset+2, trains[i].getQuirks());
}
const uint16_t turnoutOffset = trains.maxSize()*BLOCK+EEPROM_RESERVE;
for(uint8_t i = 0; i < turnouts.count(); i++)
{
EEPROM_write_char( i*BLOCK+turnoutOffset, turnouts[i].getAddress());
EEPROM_write_char( i*BLOCK+turnoutOffset+1, turnouts[i].getSubaddress());
}
const uint16_t signalOffset = trains.maxSize()*BLOCK+turnouts.maxSize()*BLOCK+EEPROM_RESERVE;
for(uint8_t i = 0; i < signals.count(); i++)
{
EEPROM_write_char( i*4+signalOffset, signals[i].getAddress());
EEPROM_write_char( i*4+signalOffset+1, signals[i].getSubaddress());
EEPROM_write_char( i*4+signalOffset+2, signals[i].getType());
}
sei();
}
void restore_state()
{
uint8_t trainCount = EEPROM_read_char(0);
uint8_t turnoutCount = EEPROM_read_char(3);
uint8_t signalCount = EEPROM_read_char(5);
autoff = EEPROM_read_char(1);
trains.clear();
turnouts.clear();
signals.clear();
if(trainCount > trains.maxSize() || trains.maxSize() != EEPROM_read_char(2) ||
turnoutCount > turnouts.maxSize() || turnouts.maxSize() != EEPROM_read_char(4) ||
signalCount > signals.maxSize() || signals.maxSize() != EEPROM_read_char(6))
{
for(uint16_t i = 0; i < EPPROM_SIZE; i++)
EEPROM_write_char(i, 0);
}
else
{
const uint16_t trainOffset = EEPROM_RESERVE;
const uint16_t turnoutOffset = trains.maxSize()*BLOCK+EEPROM_RESERVE;
const uint16_t signalOffset = trains.maxSize()*BLOCK+turnouts.maxSize()*BLOCK+EEPROM_RESERVE;
for(uint8_t i = 0; i < trainCount; i++)
trains.push_back(Train(EEPROM_read_char(trainOffset+i*4),
EEPROM_read_char(trainOffset+1+i*4),
EEPROM_read_char(trainOffset+2+i*4)));
for(uint8_t i = 0; i < turnoutCount; i++)
turnouts.push_back(Turnout(EEPROM_read_char(i*4+turnoutOffset),
EEPROM_read_char(1+i*4+turnoutOffset)));
for(uint8_t i = 0; i < signalCount; i++)
signals.push_back(Signal(EEPROM_read_char(i*4+signalOffset),
EEPROM_read_char(1+i*4+signalOffset),
EEPROM_read_char(2+i*4+signalOffset)));
}
}
inline static void printHelp(Serial* serial)
{
serial->write_p(PSTR("Available Commands: \n\
help : Show this prompt.\n\
train add [address] [functionmask] [quriks] : Add train.\n\
train [nn] delete : Delete last train.\n\
train list : Print list of saved trains.\n\
train [nn] s(top) : Stop nth train.\n\
train [nn] s(peed) [sp] : Set nth train speed.\n\
train [nn] function [x] : Toggle x'ed fuction on train n.\n\
train [nn] r(everse) : Reverse train n.\n\
train [nn] edit [functionmask] [quriks] : Edit train n.\n\
turnout add [address] [subaddress] : Add a turnout\n\
turnout list : List turnouts\n\
turnout [nn] set [left/right] : Set turnout direction\n\
turnout [nn] delete : Delete Turnout\n\
signal add [address] [subaddress] [type] : Add a signal\n\
signal list : List signal\n\
signal [nn] set [status] : Set signal status\n\
nfc probe : Detect newly added nfc reader\n\
nfc list : List nfc readers\n\
nfc debug : Enable nfc debug output\n\
nfc quiet : Disable nfc debug output\n\
nfc enable : Enable tag detection\n\
nfc disable : Disable tag detection\n\
stop : stop all trains\n\
power off : power off the rail\n\
power on : power on the rail\n\
power auto : power off the rail when no trains are moveing\n\
dump : prints epprom contence\n\
erase : Erase epprom.\n"));
}
void setPower(bool on)
{
if(powerIsOn != on)
{
powerIsOn = on;
if(on)
{
Train::setOutput(Train::LOW);
_delay_ms(100);
timer0InterruptEnable(true);
}
else
{
timer0InterruptEnable(false);
Train::setOutput(Train::OFF);
}
}
}
int powerDispatch(char* token, Serial* serial)
{
if(strcmp(token, "off") == 0)
{
setPower(false);
return 0;
}
else if( strcmp(token, "on") == 0)
{
for(uint16_t i = 0; i < trains.count(); i++)
{
trains[i].setSpeed(0);
}
setPower(true);
return 0;
}
else if(strcmp(token, "auto") == 0)
{
token = strtok(NULL, " ");
if(token != NULL && strcmp(token, "on") == 0)
{
autoff = true;
serial->write_p(PSTR("auto power off turned on.\n"));
save_state();
}
else if(token != NULL && strcmp(token, "off") == 0)
{
autoff = false;
serial->write_p(PSTR("auto power off turned off.\n"));
save_state();
}
else
{
serial->write_p(PSTR("argument must be \"on\" or \"off\". This feature is currently "));
autoff ? serial->write_p(PSTR("on.\n")) : serial->write_p(PSTR("off.\n"));
}
return 0;
}
return -1;
}
void serialDispatch(Serial* serial)
{
static uint8_t lastItemToResend = 0;
if(serial->dataIsWaiting())
{
char buffer[COMMAND_BUFFER_SIZE];
unsigned int length = serial->getString(buffer, COMMAND_BUFFER_SIZE);
if(length > 0)
{
int ret = -1;
char* token = strtok(buffer, " ");
if(lastItemToResend == itemToResend)
Item::directSendBlock = true;
if(strcmp(token, "train") == 0 || strcmp(token, "t") == 0 )
{
token = strtok(NULL, " ");
if(token != NULL)
ret = trainDispatch(token, serial);
}
else if(strcmp(token, "turnout") == 0)
{
token = strtok(NULL, " ");
if(token != NULL)
ret = turnoutDispatch(token, serial);
}
else if(strcmp(token, "signal") == 0)
{
token = strtok(NULL, " ");
if(token != NULL)
ret = signalDispatch(token, serial);
}
else if(strcmp(token, "nfc") == 0)
{
token = strtok(NULL, " ");
if(token != NULL)
ret = nfcBoard.dispatch(token);
}
else if(strncmp(token, "erase", 4) == 0)
{
for(uint16_t i = 0; i < EPPROM_SIZE; i++) EEPROM_write_char(i, 0);
serial->write_p(PSTR("EEPROM erased\n"));
trains.clear();
ret = 0;
}
else if(strcmp(token, "dump") == 0)
{
for(uint16_t i = 0; i < EPPROM_SIZE; i++)
{
if(i != 0) serial->putChar(',');
serial->write((uint16_t)EEPROM_read_char(i));
}
serial->putChar('\n');
ret = 0;
}
else if((strcmp(token, "stop") == 0 || strcmp(token, "s") == 0 ))
{
for(uint16_t i = 0; i < trains.count(); i++)
{
cli();
trains[i].stop();
printTrainState(i, serial);
sei();
}
ret = 0;
}
else if(strcmp(token, "power") == 0)
{
token = strtok(NULL, " ");
if(token != NULL)
ret = powerDispatch(token, serial);
}
else if(strcmp(token, "help") == 0)
{
printHelp(serial);
ret = 0;
}
else
{
serial->putChar('\"');
serial->write(buffer, length);
serial->putChar('\"');
serial->write_p(PSTR(" is not a valid command\n"));
ret = -1;
}
if(ret < 0)
{
serial->write_p(PSTR("Command Failed\n"));
}
else
{
lastItemToResend = itemToResend;
/*for(uint8_t i = 0; i < length; ++i)
{
if(buffer[i] == '\0' || buffer[i] == '\n')
buffer[i] = ' ';
}
serial->write_p(PSTR("Sucess \""));
serial->write(buffer, length);
serial->write("\"\n");*/
}
Item::directSendBlock = false;
}
}
}
int main()
{
TCNT0 = 0;
TCCR0B = (1<<CS02); // run timer0 with /256 scaler
DDRD = (1 << PD2) | (1 << PD3) | (1 << PD4) | (1 << PD5);
restore_state();
if(autoff)
{
timer0InterruptEnable(false);
Train::setOutput(Train::OFF);
}
else
{
timer0InterruptEnable(true);
Train::setOutput(Train::LOW);
}
sei();
Serial* serial = Serial::getInstance();
serial->write_p(PSTR("TrainController v0.5 starting\n"));
uint8_t repeatCount = 0;
while(true)
{
if(resendEvent && (trains.count() || turnouts.count() || signals.count()))
{
if(itemToResend < trains.count())
trains[itemToResend].sendData(true);
else if(itemToResend < trains.count() + turnouts.count())
turnouts[itemToResend-trains.count()].sendData(true);
else if(itemToResend < trains.count() + turnouts.count()+signals.count())
signals[itemToResend-trains.count()-turnouts.count()].sendData(true);
resendEvent = false;
if(repeatCount == 0)
{
TCNT0 = 190;
repeatCount = 1;
}
else
{
itemToResend++;
if(trains.count()+turnouts.count()+signals.count() <= itemToResend)
itemToResend = 0;
repeatCount = 0;
}
}
if(autoff)
{
bool trainsRunning = false;
for(uint16_t i = 0; i < trains.count(); i++)
trainsRunning = trainsRunning || trains[i].isActive();
if(!trainsRunning)
{
powerIsOn = false;
timer0InterruptEnable(false);
Train::setOutput(Train::OFF);
}
}
serialDispatch(serial);
nfcBoard.poll(serial);
}
return 0;
}