Initialisation depot
This commit is contained in:
239
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.cpp
Normal file
239
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/**********************************************************************
|
||||
|
||||
Accessories.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION can keep track of the direction of any turnout that is controlled
|
||||
by a DCC stationary accessory decoder. All turnouts, as well as any other DCC accessories
|
||||
connected in this fashion, can always be operated using the DCC BASE STATION Accessory command:
|
||||
|
||||
<a ADDRESS SUBADDRESS ACTIVATE>
|
||||
|
||||
However, this general command simply sends the appropriate DCC instruction packet to the main tracks
|
||||
to operate connected accessories. It does not store or retain any information regarding the current
|
||||
status of that accessory.
|
||||
|
||||
To have this sketch store and retain the direction of DCC-connected turnouts, as well as automatically
|
||||
invoke the required <a> command as needed, first define/edit/delete such turnouts using the following
|
||||
variations of the "T" command:
|
||||
|
||||
<T ID ADDRESS SUBADDRESS>: creates a new turnout ID, with specified ADDRESS and SUBADDRESS
|
||||
if turnout ID already exists, it is updated with specificed ADDRESS and SUBADDRESS
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. out of memory)
|
||||
|
||||
<T ID>: deletes definition of turnout ID
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
|
||||
|
||||
<T>: lists all defined turnouts
|
||||
returns: <H ID ADDRESS SUBADDRESS THROW> for each defined turnout or <X> if no turnouts defined
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the turnout to control
|
||||
ADDRESS: the primary address of the decoder controlling this turnout (0-511)
|
||||
SUBADDRESS: the subaddress of the decoder controlling this turnout (0-3)
|
||||
|
||||
Once all turnouts have been properly defined, use the <E> command to store their definitions to EEPROM.
|
||||
If you later make edits/additions/deletions to the turnout definitions, you must invoke the <E> command if you want those
|
||||
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> command.
|
||||
|
||||
To "throw" turnouts that have been defined use:
|
||||
|
||||
<T ID THROW>: sets turnout ID to either the "thrown" or "unthrown" position
|
||||
returns: <H ID THROW>, or <X> if turnout ID does not exist
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the turnout to control
|
||||
THROW: 0 (unthrown) or 1 (thrown)
|
||||
|
||||
When controlled as such, the Arduino updates and stores the direction of each Turnout in EEPROM so
|
||||
that it is retained even without power. A list of the current directions of each Turnout in the form <H ID THROW> is generated
|
||||
by this sketch whenever the <s> status command is invoked. This provides an efficient way of initializing
|
||||
the directions of any Turnouts being monitored or controlled by a separate interface or GUI program.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Accessories.h"
|
||||
#include "SerialCommand.h"
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "EEStore.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::activate(int s){
|
||||
char c[20];
|
||||
data.tStatus=(s>0); // if s>0 set turnout=ON, else if zero or negative set turnout=OFF
|
||||
sprintf(c,"a %d %d %d",data.address,data.subAddress,data.tStatus);
|
||||
SerialCommand::parse(c);
|
||||
if(num>0)
|
||||
EEPROM.put(num,data.tStatus);
|
||||
INTERFACE.print("<H");
|
||||
INTERFACE.print(data.id);
|
||||
if(data.tStatus==0)
|
||||
INTERFACE.print(" 0>");
|
||||
else
|
||||
INTERFACE.print(" 1>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Turnout* Turnout::get(int n){
|
||||
Turnout *tt;
|
||||
for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;tt=tt->nextTurnout);
|
||||
return(tt);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::remove(int n){
|
||||
Turnout *tt,*pp;
|
||||
|
||||
for(tt=firstTurnout;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextTurnout);
|
||||
|
||||
if(tt==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
if(tt==firstTurnout)
|
||||
firstTurnout=tt->nextTurnout;
|
||||
else
|
||||
pp->nextTurnout=tt->nextTurnout;
|
||||
|
||||
free(tt);
|
||||
|
||||
INTERFACE.print("<O>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::show(int n){
|
||||
Turnout *tt;
|
||||
|
||||
if(firstTurnout==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
for(tt=firstTurnout;tt!=NULL;tt=tt->nextTurnout){
|
||||
INTERFACE.print("<H");
|
||||
INTERFACE.print(tt->data.id);
|
||||
if(n==1){
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.address);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.subAddress);
|
||||
}
|
||||
if(tt->data.tStatus==0)
|
||||
INTERFACE.print(" 0>");
|
||||
else
|
||||
INTERFACE.print(" 1>");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::parse(char *c){
|
||||
int n,s,m;
|
||||
Turnout *t;
|
||||
|
||||
switch(sscanf(c,"%d %d %d",&n,&s,&m)){
|
||||
|
||||
case 2: // argument is string with id number of turnout followed by zero (not thrown) or one (thrown)
|
||||
t=get(n);
|
||||
if(t!=NULL)
|
||||
t->activate(s);
|
||||
else
|
||||
INTERFACE.print("<X>");
|
||||
break;
|
||||
|
||||
case 3: // argument is string with id number of turnout followed by an address and subAddress
|
||||
create(n,s,m,1);
|
||||
break;
|
||||
|
||||
case 1: // argument is a string with id number only
|
||||
remove(n);
|
||||
break;
|
||||
|
||||
case -1: // no arguments
|
||||
show(1); // verbose show
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::load(){
|
||||
struct TurnoutData data;
|
||||
Turnout *tt;
|
||||
|
||||
for(int i=0;i<EEStore::eeStore->data.nTurnouts;i++){
|
||||
EEPROM.get(EEStore::pointer(),data);
|
||||
tt=create(data.id,data.address,data.subAddress);
|
||||
tt->data.tStatus=data.tStatus;
|
||||
tt->num=EEStore::pointer();
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Turnout::store(){
|
||||
Turnout *tt;
|
||||
|
||||
tt=firstTurnout;
|
||||
EEStore::eeStore->data.nTurnouts=0;
|
||||
|
||||
while(tt!=NULL){
|
||||
tt->num=EEStore::pointer();
|
||||
EEPROM.put(EEStore::pointer(),tt->data);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
tt=tt->nextTurnout;
|
||||
EEStore::eeStore->data.nTurnouts++;
|
||||
}
|
||||
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Turnout *Turnout::create(int id, int add, int subAdd, int v){
|
||||
Turnout *tt;
|
||||
|
||||
if(firstTurnout==NULL){
|
||||
firstTurnout=(Turnout *)calloc(1,sizeof(Turnout));
|
||||
tt=firstTurnout;
|
||||
} else if((tt=get(id))==NULL){
|
||||
tt=firstTurnout;
|
||||
while(tt->nextTurnout!=NULL)
|
||||
tt=tt->nextTurnout;
|
||||
tt->nextTurnout=(Turnout *)calloc(1,sizeof(Turnout));
|
||||
tt=tt->nextTurnout;
|
||||
}
|
||||
|
||||
if(tt==NULL){ // problem allocating memory
|
||||
if(v==1)
|
||||
INTERFACE.print("<X>");
|
||||
return(tt);
|
||||
}
|
||||
|
||||
tt->data.id=id;
|
||||
tt->data.address=add;
|
||||
tt->data.subAddress=subAdd;
|
||||
tt->data.tStatus=0;
|
||||
if(v==1)
|
||||
INTERFACE.print("<O>");
|
||||
return(tt);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Turnout *Turnout::firstTurnout=NULL;
|
||||
|
||||
|
||||
39
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.h
Normal file
39
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Accessories.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**********************************************************************
|
||||
|
||||
Accessories.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#ifndef Accessories_h
|
||||
#define Accessories_h
|
||||
|
||||
struct TurnoutData {
|
||||
byte tStatus;
|
||||
byte subAddress;
|
||||
int id;
|
||||
int address;
|
||||
};
|
||||
|
||||
struct Turnout{
|
||||
static Turnout *firstTurnout;
|
||||
int num;
|
||||
struct TurnoutData data;
|
||||
Turnout *nextTurnout;
|
||||
void activate(int s);
|
||||
static void parse(char *c);
|
||||
static Turnout* get(int);
|
||||
static void remove(int);
|
||||
static void load();
|
||||
static void store();
|
||||
static Turnout *create(int, int, int, int=0);
|
||||
static void show(int=0);
|
||||
}; // Turnout
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Comm.h
Normal file
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Comm.h
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
|
||||
Comm.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#if COMM_TYPE == 1 // Ethernet Shield Card Selected
|
||||
|
||||
#if COMM_INTERFACE == 1
|
||||
#define COMM_SHIELD_NAME "ARDUINO-CC ETHERNET SHIELD (WIZNET 5100)"
|
||||
#include <Ethernet.h> // built-in Arduino.cc library
|
||||
|
||||
#elif COMM_INTERFACE == 2
|
||||
#define COMM_SHIELD_NAME "ARDUINO-ORG ETHERNET-2 SHIELD (WIZNET 5500)"
|
||||
#include <Ethernet2.h> // https://github.com/arduino-org/Arduino
|
||||
|
||||
#elif COMM_INTERFACE == 3
|
||||
#define COMM_SHIELD_NAME "SEEED STUDIO ETHERNET SHIELD (WIZNET 5200)"
|
||||
#include <EthernetV2_0.h> // https://github.com/Seeed-Studio/Ethernet_Shield_W5200
|
||||
|
||||
#endif
|
||||
|
||||
extern EthernetServer INTERFACE;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
58
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Config.h
Normal file
58
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Config.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/**********************************************************************
|
||||
|
||||
Config.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE MOTOR_SHIELD_TYPE ACCORDING TO THE FOLLOWING TABLE:
|
||||
//
|
||||
// 0 = ARDUINO MOTOR SHIELD (MAX 18V/2A PER CHANNEL)
|
||||
// 1 = POLOLU MC33926 MOTOR SHIELD (MAX 28V/3A PER CHANNEL)
|
||||
|
||||
#define MOTOR_SHIELD_TYPE 0
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE NUMBER OF MAIN TRACK REGISTER
|
||||
|
||||
#define MAX_MAIN_REGISTERS 12
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE COMMUNICATIONS INTERFACE
|
||||
//
|
||||
// 0 = Built-in Serial Port
|
||||
// 1 = Arduino.cc Ethernet/SD-Card Shield
|
||||
// 2 = Arduino.org Ethernet/SD-Card Shield
|
||||
// 3 = Seeed Studio Ethernet/SD-Card Shield W5200
|
||||
|
||||
#define COMM_INTERFACE 0
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
|
||||
//
|
||||
|
||||
//#define IP_ADDRESS { 192, 168, 1, 200 }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE PORT TO USE FOR ETHERNET COMMUNICATIONS INTERFACE
|
||||
//
|
||||
|
||||
#define ETHERNET_PORT 2560
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
|
||||
//
|
||||
|
||||
#define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**********************************************************************
|
||||
|
||||
CurrentMonitor.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "CurrentMonitor.h"
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CurrentMonitor::CurrentMonitor(int pin, char *msg){
|
||||
this->pin=pin;
|
||||
this->msg=msg;
|
||||
current=0;
|
||||
} // CurrentMonitor::CurrentMonitor
|
||||
|
||||
boolean CurrentMonitor::checkTime(){
|
||||
if(millis()-sampleTime<CURRENT_SAMPLE_TIME) // no need to check current yet
|
||||
return(false);
|
||||
sampleTime=millis(); // note millis() uses TIMER-0. For UNO, we change the scale on Timer-0. For MEGA we do not. This means CURENT_SAMPLE_TIME is different for UNO then MEGA
|
||||
return(true);
|
||||
} // CurrentMonitor::checkTime
|
||||
|
||||
void CurrentMonitor::check(){
|
||||
current=analogRead(pin)*CURRENT_SAMPLE_SMOOTHING+current*(1.0-CURRENT_SAMPLE_SMOOTHING); // compute new exponentially-smoothed current
|
||||
if(current>CURRENT_SAMPLE_MAX && digitalRead(SIGNAL_ENABLE_PIN_PROG)==HIGH){ // current overload and Prog Signal is on (or could have checked Main Signal, since both are always on or off together)
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW); // disable both Motor Shield Channels
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW); // regardless of which caused current overload
|
||||
INTERFACE.print(msg); // print corresponding error message
|
||||
}
|
||||
} // CurrentMonitor::check
|
||||
|
||||
long int CurrentMonitor::sampleTime=0;
|
||||
|
||||
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.h
Normal file
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/CurrentMonitor.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/**********************************************************************
|
||||
|
||||
CurrentMonitor.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef CurrentMonitor_h
|
||||
#define CurrentMonitor_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#define CURRENT_SAMPLE_SMOOTHING 0.01
|
||||
#define CURRENT_SAMPLE_MAX 300
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
|
||||
#define CURRENT_SAMPLE_TIME 10
|
||||
#else // Configuration for MEGA
|
||||
#define CURRENT_SAMPLE_TIME 1
|
||||
#endif
|
||||
|
||||
struct CurrentMonitor{
|
||||
static long int sampleTime;
|
||||
int pin;
|
||||
float current;
|
||||
char *msg;
|
||||
CurrentMonitor(int, char *);
|
||||
static boolean checkTime();
|
||||
void check();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
135
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.h
Normal file
135
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/**********************************************************************
|
||||
|
||||
DCCpp_Uno.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#ifndef DCCpp_Uno_h
|
||||
#define DCCpp_Uno_h
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// RELEASE VERSION
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define VERSION "1.2.1+"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// AUTO-SELECT ARDUINO BOARD
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical)
|
||||
#define ARDUINO_AVR_MEGA2560
|
||||
#endif
|
||||
|
||||
#if defined ARDUINO_AVR_UNO
|
||||
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
|
||||
#define DCC_SIGNAL_PIN_MAIN 10 // Ardunio Uno - uses OC1B
|
||||
#define DCC_SIGNAL_PIN_PROG 5 // Arduino Uno - uses OC0B
|
||||
|
||||
#if COMM_INTERFACE != 0 // Serial was not selected
|
||||
|
||||
#error CANNOT COMPILE - DCC++ FOR THE UNO CAN ONLY USE SERIAL COMMUNICATION - PLEASE SELECT THIS IN THE CONFIG FILE
|
||||
|
||||
#endif
|
||||
|
||||
#elif defined ARDUINO_AVR_MEGA2560
|
||||
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
|
||||
#define DCC_SIGNAL_PIN_MAIN 12 // Arduino Mega - uses OC1B
|
||||
#define DCC_SIGNAL_PIN_PROG 2 // Arduino Mega - uses OC3B
|
||||
|
||||
#else
|
||||
|
||||
#error CANNOT COMPILE - DCC++ ONLY WORKS WITH AN ARDUINO UNO OR AN ARDUINO MEGA 1280/2560
|
||||
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// SELECT MOTOR SHIELD
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if MOTOR_SHIELD_TYPE == 0
|
||||
|
||||
#define MOTOR_SHIELD_NAME "ARDUINO MOTOR SHIELD"
|
||||
|
||||
#define SIGNAL_ENABLE_PIN_MAIN 3
|
||||
#define SIGNAL_ENABLE_PIN_PROG 11
|
||||
|
||||
#define CURRENT_MONITOR_PIN_MAIN A0
|
||||
#define CURRENT_MONITOR_PIN_PROG A1
|
||||
|
||||
#define DIRECTION_MOTOR_CHANNEL_PIN_A 12
|
||||
#define DIRECTION_MOTOR_CHANNEL_PIN_B 13
|
||||
|
||||
#elif MOTOR_SHIELD_TYPE == 1
|
||||
|
||||
#define MOTOR_SHIELD_NAME "POLOLU MC33926 MOTOR SHIELD"
|
||||
|
||||
#define SIGNAL_ENABLE_PIN_MAIN 9
|
||||
#define SIGNAL_ENABLE_PIN_PROG 11
|
||||
|
||||
#define CURRENT_MONITOR_PIN_MAIN A0
|
||||
#define CURRENT_MONITOR_PIN_PROG A1
|
||||
|
||||
#define DIRECTION_MOTOR_CHANNEL_PIN_A 7
|
||||
#define DIRECTION_MOTOR_CHANNEL_PIN_B 8
|
||||
|
||||
#else
|
||||
|
||||
#error CANNOT COMPILE - PLEASE SELECT A PROPER MOTOR SHIELD TYPE
|
||||
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// SELECT COMMUNICATION INTERACE
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if COMM_INTERFACE == 0
|
||||
|
||||
#define COMM_TYPE 0
|
||||
#define INTERFACE Serial
|
||||
|
||||
#elif (COMM_INTERFACE==1) || (COMM_INTERFACE==2) || (COMM_INTERFACE==3)
|
||||
|
||||
#define COMM_TYPE 1
|
||||
#define INTERFACE eServer
|
||||
#define SDCARD_CS 4
|
||||
|
||||
#else
|
||||
|
||||
#error CANNOT COMPILE - Please select a proper value for COMM_INTERFACE in CONFIG.H file
|
||||
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// SET WHETHER TO SHOW PACKETS - DIAGNOSTIC MODE ONLY
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// If SHOW_PACKETS is set to 1, then for select main operations track commands that modify an internal DCC packet register,
|
||||
// if printFlag for that command is also set to 1, DCC++ BASE STATION will additionally return the
|
||||
// DCC packet contents of the modified register in the following format:
|
||||
|
||||
// <* REG: B1 B2 ... Bn CSUM / REPEAT>
|
||||
//
|
||||
// REG: the number of the main operations track packet register that was modified
|
||||
// B1: the first hexidecimal byte of the DCC packet
|
||||
// B2: the second hexidecimal byte of the DCC packet
|
||||
// Bn: the nth hexidecimal byte of the DCC packet
|
||||
// CSUM: a checksum byte that is required to be the final byte in any DCC packet
|
||||
// REPEAT: the number of times the DCC packet was re-transmitted to the tracks after its iniital transmission
|
||||
|
||||
#define SHOW_PACKETS 0 // set to zero to disable printing of every packet for select main operations track commands
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
563
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.ino
Normal file
563
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/DCCpp_Uno.ino
Normal file
@@ -0,0 +1,563 @@
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see http://www.gnu.org/licenses
|
||||
|
||||
**********************************************************************/
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION is a C++ program written for the Arduino Uno and Arduino Mega
|
||||
using the Arduino IDE 1.6.6.
|
||||
|
||||
It allows a standard Arduino Uno or Mega with an Arduino Motor Shield (as well as others)
|
||||
to be used as a fully-functioning digital command and control (DCC) base station
|
||||
for controlling model train layouts that conform to current National Model
|
||||
Railroad Association (NMRA) DCC standards.
|
||||
|
||||
This version of DCC++ BASE STATION supports:
|
||||
|
||||
* 2-byte and 4-byte locomotive addressing
|
||||
* Simultaneous control of multiple locomotives
|
||||
* 128-step speed throttling
|
||||
* Cab functions F0-F28
|
||||
* Activate/de-activate accessory functions using 512 addresses, each with 4 sub-addresses
|
||||
- includes optional functionailty to monitor and store of the direction of any connected turnouts
|
||||
* Programming on the Main Operations Track
|
||||
- write configuration variable bytes
|
||||
- set/clear specific configuration variable bits
|
||||
* Programming on the Programming Track
|
||||
- write configuration variable bytes
|
||||
- set/clear specific configuration variable bits
|
||||
- read configuration variable bytes
|
||||
|
||||
DCC++ BASE STATION is controlled with simple text commands received via
|
||||
the Arduino's serial interface. Users can type these commands directly
|
||||
into the Arduino IDE Serial Monitor, or can send such commands from another
|
||||
device or computer program.
|
||||
|
||||
When compiled for the Arduino Mega, an Ethernet Shield can be used for network
|
||||
communications instead of using serial communications.
|
||||
|
||||
DCC++ CONTROLLER, available separately under a similar open-source
|
||||
license, is a Java program written using the Processing library and Processing IDE
|
||||
that provides a complete and configurable graphic interface to control model train layouts
|
||||
via the DCC++ BASE STATION.
|
||||
|
||||
With the exception of a standard 15V power supply that can be purchased in
|
||||
any electronics store, no additional hardware is required.
|
||||
|
||||
Neither DCC++ BASE STATION nor DCC++ CONTROLLER use any known proprietary or
|
||||
commercial hardware, software, interfaces, specifications, or methods related
|
||||
to the control of model trains using NMRA DCC standards. Both programs are wholly
|
||||
original, developed by the author, and are not derived from any known commercial,
|
||||
free, or open-source model railroad control packages by any other parties.
|
||||
|
||||
However, DCC++ BASE STATION and DCC++ CONTROLLER do heavily rely on the IDEs and
|
||||
embedded libraries associated with Arduino and Processing. Tremendous thanks to those
|
||||
responsible for these terrific open-source initiatives that enable programs like
|
||||
DCC++ to be developed and distributed in the same fashion.
|
||||
|
||||
REFERENCES:
|
||||
|
||||
NMRA DCC Standards: http://www.nmra.org/index-nmra-standards-and-recommended-practices
|
||||
Arduino: http://www.arduino.cc/
|
||||
Processing: http://processing.org/
|
||||
GNU General Public License: http://opensource.org/licenses/GPL-3.0
|
||||
|
||||
BRIEF NOTES ON THE THEORY AND OPERATION OF DCC++ BASE STATION:
|
||||
|
||||
DCC++ BASE STATION for the Uno configures the OC0B interrupt pin associated with Timer 0,
|
||||
and the OC1B interupt pin associated with Timer 1, to generate separate 0-5V
|
||||
unipolar signals that each properly encode zero and one bits conforming with
|
||||
DCC timing standards. When compiled for the Mega, DCC++ BASE STATION uses OC3B instead of OC0B.
|
||||
|
||||
Series of DCC bit streams are bundled into Packets that each form the basis of
|
||||
a standard DCC instruction. Packets are stored in Packet Registers that contain
|
||||
methods for updating and queuing according to text commands sent by the user
|
||||
(or another program) over the serial interface. There is one set of registers that controls
|
||||
the main operations track and one that controls the programming track.
|
||||
|
||||
For the main operations track, packets to store cab throttle settings are stored in
|
||||
registers numbered 1 through MAX_MAIN_REGISTERS (as defined in DCCpp_Uno.h).
|
||||
It is generally considered good practice to continuously send throttle control packets
|
||||
to every cab so that if an engine should momentarily lose electrical connectivity with the tracks,
|
||||
it will very quickly receive another throttle control signal as soon as connectivity is
|
||||
restored (such as when a trin passes over rough portion of track or the frog of a turnout).
|
||||
|
||||
DCC++ Base Station therefore sequentially loops through each main operations track packet regsiter
|
||||
that has been loaded with a throttle control setting for a given cab. For each register, it
|
||||
transmits the appropriate DCC packet bits to the track, then moves onto the next register
|
||||
without any pausing to ensure continuous bi-polar power is being provided to the tracks.
|
||||
Updates to the throttle setting stored in any given packet register are done in a double-buffered
|
||||
fashion and the sequencer is pointed to that register immediately after being changes so that updated DCC bits
|
||||
can be transmitted to the appropriate cab without delay or any interruption in the bi-polar power signal.
|
||||
The cabs identified in each stored throttle setting should be unique across registers. If two registers
|
||||
contain throttle setting for the same cab, the throttle in the engine will oscillate between the two,
|
||||
which is probably not a desireable outcome.
|
||||
|
||||
For both the main operations track and the programming track there is also a special packet register with id=0
|
||||
that is used to store all other DCC packets that do not require continious transmittal to the tracks.
|
||||
This includes DCC packets to control decoder functions, set accessory decoders, and read and write Configuration Variables.
|
||||
It is common practice that transmittal of these one-time packets is usually repeated a few times to ensure
|
||||
proper receipt by the receiving decoder. DCC decoders are designed to listen for repeats of the same packet
|
||||
and provided there are no other packets received in between the repeats, the DCC decoder will not repeat the action itself.
|
||||
Some DCC decoders actually require receipt of sequential multiple identical one-time packets as a way of
|
||||
verifying proper transmittal before acting on the instructions contained in those packets
|
||||
|
||||
An Arduino Motor Shield (or similar), powered by a standard 15V DC power supply and attached
|
||||
on top of the Arduino Uno or Mega, is used to transform the 0-5V DCC logic signals
|
||||
produced by the Uno's Timer interrupts into proper 0-15V bi-polar DCC signals.
|
||||
|
||||
This is accomplished on the Uno by using one small jumper wire to connect the Uno's OC1B output (pin 10)
|
||||
to the Motor Shield's DIRECTION A input (pin 12), and another small jumper wire to connect
|
||||
the Uno's OC0B output (pin 5) to the Motor Shield's DIRECTION B input (pin 13).
|
||||
|
||||
For the Mega, the OC1B output is produced directly on pin 12, so no jumper is needed to connect to the
|
||||
Motor Shield's DIRECTION A input. However, one small jumper wire is needed to connect the Mega's OC3B output (pin 2)
|
||||
to the Motor Shield's DIRECTION B input (pin 13).
|
||||
|
||||
Other Motor Shields may require different sets of jumper or configurations (see Config.h and DCCpp_Uno.h for details).
|
||||
|
||||
When configured as such, the CHANNEL A and CHANNEL B outputs of the Motor Shield may be
|
||||
connected directly to the tracks. This software assumes CHANNEL A is connected
|
||||
to the Main Operations Track, and CHANNEL B is connected to the Programming Track.
|
||||
|
||||
DCC++ BASE STATION in split into multiple modules, each with its own header file:
|
||||
|
||||
DCCpp_Uno: declares required global objects and contains initial Arduino setup()
|
||||
and Arduino loop() functions, as well as interrput code for OC0B and OC1B.
|
||||
Also includes declarations of optional array of Turn-Outs and optional array of Sensors
|
||||
|
||||
SerialCommand: contains methods to read and interpret text commands from the serial line,
|
||||
process those instructions, and, if necessary call appropriate Packet RegisterList methods
|
||||
to update either the Main Track or Programming Track Packet Registers
|
||||
|
||||
PacketRegister: contains methods to load, store, and update Packet Registers with DCC instructions
|
||||
|
||||
CurrentMonitor: contains methods to separately monitor and report the current drawn from CHANNEL A and
|
||||
CHANNEL B of the Arduino Motor Shield's, and shut down power if a short-circuit overload
|
||||
is detected
|
||||
|
||||
Accessories: contains methods to operate and store the status of any optionally-defined turnouts controlled
|
||||
by a DCC stationary accessory decoder.
|
||||
|
||||
Sensor: contains methods to monitor and report on the status of optionally-defined infrared
|
||||
sensors embedded in the Main Track and connected to various pins on the Arudino Uno
|
||||
|
||||
Outputs: contains methods to configure one or more Arduino pins as an output for your own custom use
|
||||
|
||||
EEStore: contains methods to store, update, and load various DCC settings and status
|
||||
(e.g. the states of all defined turnouts) in the EEPROM for recall after power-up
|
||||
|
||||
DCC++ BASE STATION is configured through the Config.h file that contains all user-definable parameters
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
// BEGIN BY INCLUDING THE HEADER FILES FOR EACH MODULE
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "PacketRegister.h"
|
||||
#include "CurrentMonitor.h"
|
||||
#include "Sensor.h"
|
||||
#include "SerialCommand.h"
|
||||
#include "Accessories.h"
|
||||
#include "EEStore.h"
|
||||
#include "Config.h"
|
||||
#include "Comm.h"
|
||||
|
||||
void showConfiguration();
|
||||
|
||||
// SET UP COMMUNICATIONS INTERFACE - FOR STANDARD SERIAL, NOTHING NEEDS TO BE DONE
|
||||
|
||||
#if COMM_TYPE == 1
|
||||
byte mac[] = MAC_ADDRESS; // Create MAC address (to be used for DHCP when initializing server)
|
||||
EthernetServer INTERFACE(ETHERNET_PORT); // Create and instance of an EnternetServer
|
||||
#endif
|
||||
|
||||
// NEXT DECLARE GLOBAL OBJECTS TO PROCESS AND STORE DCC PACKETS AND MONITOR TRACK CURRENTS.
|
||||
// NOTE REGISTER LISTS MUST BE DECLARED WITH "VOLATILE" QUALIFIER TO ENSURE THEY ARE PROPERLY UPDATED BY INTERRUPT ROUTINES
|
||||
|
||||
volatile RegisterList mainRegs(MAX_MAIN_REGISTERS); // create list of registers for MAX_MAIN_REGISTER Main Track Packets
|
||||
volatile RegisterList progRegs(2); // create a shorter list of only two registers for Program Track Packets
|
||||
|
||||
CurrentMonitor mainMonitor(CURRENT_MONITOR_PIN_MAIN,"<p2>"); // create monitor for current on Main Track
|
||||
CurrentMonitor progMonitor(CURRENT_MONITOR_PIN_PROG,"<p3>"); // create monitor for current on Program Track
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// MAIN ARDUINO LOOP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void loop(){
|
||||
|
||||
SerialCommand::process(); // check for, and process, and new serial commands
|
||||
|
||||
if(CurrentMonitor::checkTime()){ // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks
|
||||
mainMonitor.check();
|
||||
progMonitor.check();
|
||||
}
|
||||
|
||||
Sensor::check(); // check sensors for activate/de-activate
|
||||
|
||||
} // loop
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// INITIAL SETUP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void setup(){
|
||||
|
||||
Serial.begin(115200); // configure serial interface
|
||||
Serial.flush();
|
||||
|
||||
#ifdef SDCARD_CS
|
||||
pinMode(SDCARD_CS,OUTPUT);
|
||||
digitalWrite(SDCARD_CS,HIGH); // Deselect the SD card
|
||||
#endif
|
||||
|
||||
EEStore::init(); // initialize and load Turnout and Sensor definitions stored in EEPROM
|
||||
|
||||
pinMode(A5,INPUT); // if pin A5 is grounded upon start-up, print system configuration and halt
|
||||
digitalWrite(A5,HIGH);
|
||||
if(!digitalRead(A5))
|
||||
showConfiguration();
|
||||
|
||||
Serial.print("<iDCC++ BASE STATION FOR ARDUINO "); // Print Status to Serial Line regardless of COMM_TYPE setting so use can open Serial Monitor and check configurtion
|
||||
Serial.print(ARDUINO_TYPE);
|
||||
Serial.print(" / ");
|
||||
Serial.print(MOTOR_SHIELD_NAME);
|
||||
Serial.print(": V-");
|
||||
Serial.print(VERSION);
|
||||
Serial.print(" / ");
|
||||
Serial.print(__DATE__);
|
||||
Serial.print(" ");
|
||||
Serial.print(__TIME__);
|
||||
Serial.print(">");
|
||||
|
||||
#if COMM_TYPE == 1
|
||||
#ifdef IP_ADDRESS
|
||||
Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address
|
||||
#else
|
||||
Ethernet.begin(mac); // Start networking using DHCP to get an IP Address
|
||||
#endif
|
||||
INTERFACE.begin();
|
||||
#endif
|
||||
|
||||
SerialCommand::init(&mainRegs, &progRegs, &mainMonitor); // create structure to read and parse commands from serial line
|
||||
|
||||
Serial.print("<N");
|
||||
Serial.print(COMM_TYPE);
|
||||
Serial.print(": ");
|
||||
|
||||
#if COMM_TYPE == 0
|
||||
Serial.print("SERIAL>");
|
||||
#elif COMM_TYPE == 1
|
||||
Serial.print(Ethernet.localIP());
|
||||
Serial.print(">");
|
||||
#endif
|
||||
|
||||
// CONFIGURE TIMER_1 TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC1B INTERRUPT PINS
|
||||
|
||||
// Direction Pin for Motor Shield Channel A - MAIN OPERATIONS TRACK
|
||||
// Controlled by Arduino 16-bit TIMER 1 / OC1B Interrupt Pin
|
||||
// Values for 16-bit OCR1A and OCR1B registers calibrated for 1:1 prescale at 16 MHz clock frequency
|
||||
// Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
|
||||
|
||||
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER1 3199
|
||||
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER1 1599
|
||||
|
||||
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER1 1855
|
||||
#define DCC_ONE_BIT_PULSE_DURATION_TIMER1 927
|
||||
|
||||
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_A,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
|
||||
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_A,LOW);
|
||||
|
||||
pinMode(DCC_SIGNAL_PIN_MAIN, OUTPUT); // THIS ARDUINO OUPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-A OF MOTOR CHANNEL-A
|
||||
|
||||
bitSet(TCCR1A,WGM10); // set Timer 1 to FAST PWM, with TOP=OCR1A
|
||||
bitSet(TCCR1A,WGM11);
|
||||
bitSet(TCCR1B,WGM12);
|
||||
bitSet(TCCR1B,WGM13);
|
||||
|
||||
bitSet(TCCR1A,COM1B1); // set Timer 1, OC1B (pin 10/UNO, pin 12/MEGA) to inverting toggle (actual direction is arbitrary)
|
||||
bitSet(TCCR1A,COM1B0);
|
||||
|
||||
bitClear(TCCR1B,CS12); // set Timer 1 prescale=1
|
||||
bitClear(TCCR1B,CS11);
|
||||
bitSet(TCCR1B,CS10);
|
||||
|
||||
OCR1A=DCC_ONE_BIT_TOTAL_DURATION_TIMER1;
|
||||
OCR1B=DCC_ONE_BIT_PULSE_DURATION_TIMER1;
|
||||
|
||||
pinMode(SIGNAL_ENABLE_PIN_MAIN,OUTPUT); // master enable for motor channel A
|
||||
|
||||
mainRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
|
||||
|
||||
bitSet(TIMSK1,OCIE1B); // enable interrupt vector for Timer 1 Output Compare B Match (OCR1B)
|
||||
|
||||
// CONFIGURE EITHER TIMER_0 (UNO) OR TIMER_3 (MEGA) TO OUTPUT 50% DUTY CYCLE DCC SIGNALS ON OC0B (UNO) OR OC3B (MEGA) INTERRUPT PINS
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
|
||||
|
||||
// Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK
|
||||
// Controlled by Arduino 8-bit TIMER 0 / OC0B Interrupt Pin
|
||||
// Values for 8-bit OCR0A and OCR0B registers calibrated for 1:64 prescale at 16 MHz clock frequency
|
||||
// Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with as-close-as-possible to 50% duty cycle
|
||||
|
||||
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER0 49
|
||||
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER0 24
|
||||
|
||||
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER0 28
|
||||
#define DCC_ONE_BIT_PULSE_DURATION_TIMER0 14
|
||||
|
||||
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
|
||||
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW);
|
||||
|
||||
pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
|
||||
|
||||
bitSet(TCCR0A,WGM00); // set Timer 0 to FAST PWM, with TOP=OCR0A
|
||||
bitSet(TCCR0A,WGM01);
|
||||
bitSet(TCCR0B,WGM02);
|
||||
|
||||
bitSet(TCCR0A,COM0B1); // set Timer 0, OC0B (pin 5) to inverting toggle (actual direction is arbitrary)
|
||||
bitSet(TCCR0A,COM0B0);
|
||||
|
||||
bitClear(TCCR0B,CS02); // set Timer 0 prescale=64
|
||||
bitSet(TCCR0B,CS01);
|
||||
bitSet(TCCR0B,CS00);
|
||||
|
||||
OCR0A=DCC_ONE_BIT_TOTAL_DURATION_TIMER0;
|
||||
OCR0B=DCC_ONE_BIT_PULSE_DURATION_TIMER0;
|
||||
|
||||
pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B
|
||||
|
||||
progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
|
||||
|
||||
bitSet(TIMSK0,OCIE0B); // enable interrupt vector for Timer 0 Output Compare B Match (OCR0B)
|
||||
|
||||
#else // Configuration for MEGA
|
||||
|
||||
// Directon Pin for Motor Shield Channel B - PROGRAMMING TRACK
|
||||
// Controlled by Arduino 16-bit TIMER 3 / OC3B Interrupt Pin
|
||||
// Values for 16-bit OCR3A and OCR3B registers calibrated for 1:1 prescale at 16 MHz clock frequency
|
||||
// Resulting waveforms are 200 microseconds for a ZERO bit and 116 microseconds for a ONE bit with exactly 50% duty cycle
|
||||
|
||||
#define DCC_ZERO_BIT_TOTAL_DURATION_TIMER3 3199
|
||||
#define DCC_ZERO_BIT_PULSE_DURATION_TIMER3 1599
|
||||
|
||||
#define DCC_ONE_BIT_TOTAL_DURATION_TIMER3 1855
|
||||
#define DCC_ONE_BIT_PULSE_DURATION_TIMER3 927
|
||||
|
||||
pinMode(DIRECTION_MOTOR_CHANNEL_PIN_B,INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
|
||||
digitalWrite(DIRECTION_MOTOR_CHANNEL_PIN_B,LOW);
|
||||
|
||||
pinMode(DCC_SIGNAL_PIN_PROG,OUTPUT); // THIS ARDUINO OUTPUT PIN MUST BE PHYSICALLY CONNECTED TO THE PIN FOR DIRECTION-B OF MOTOR CHANNEL-B
|
||||
|
||||
bitSet(TCCR3A,WGM30); // set Timer 3 to FAST PWM, with TOP=OCR3A
|
||||
bitSet(TCCR3A,WGM31);
|
||||
bitSet(TCCR3B,WGM32);
|
||||
bitSet(TCCR3B,WGM33);
|
||||
|
||||
bitSet(TCCR3A,COM3B1); // set Timer 3, OC3B (pin 2) to inverting toggle (actual direction is arbitrary)
|
||||
bitSet(TCCR3A,COM3B0);
|
||||
|
||||
bitClear(TCCR3B,CS32); // set Timer 3 prescale=1
|
||||
bitClear(TCCR3B,CS31);
|
||||
bitSet(TCCR3B,CS30);
|
||||
|
||||
OCR3A=DCC_ONE_BIT_TOTAL_DURATION_TIMER3;
|
||||
OCR3B=DCC_ONE_BIT_PULSE_DURATION_TIMER3;
|
||||
|
||||
pinMode(SIGNAL_ENABLE_PIN_PROG,OUTPUT); // master enable for motor channel B
|
||||
|
||||
progRegs.loadPacket(1,RegisterList::idlePacket,2,0); // load idle packet into register 1
|
||||
|
||||
bitSet(TIMSK3,OCIE3B); // enable interrupt vector for Timer 3 Output Compare B Match (OCR3B)
|
||||
|
||||
#endif
|
||||
|
||||
} // setup
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1.
|
||||
// It is designed to read the current bit of the current register packet and
|
||||
// updates the OCNA and OCNB counters of Timer-N to values that will either produce
|
||||
// a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent
|
||||
// DCC ZERO and DCC ONE bits.
|
||||
|
||||
// These are hardware-driven interrupts that will be called automatically when triggered regardless of what
|
||||
// DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily diabled.
|
||||
// Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts
|
||||
// (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete
|
||||
// in much less than 58 microsends, otherwise there would be no time for the rest of the program to run. Worse, if the logic
|
||||
// of the interrupt code ever caused it to run longer than 58 microsends, an interrupt trigger would be missed, the OCNA and OCNB
|
||||
// registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the
|
||||
// interrupt code completes and can be called again.
|
||||
|
||||
// A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed
|
||||
// DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible.
|
||||
|
||||
// Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the
|
||||
// Programming Track OCOB interrupt. But rather than create a generic function that incurrs additional overhead, we create a macro
|
||||
// that can be invoked with proper paramters for each interrupt. This slightly increases the size of the code base by duplicating
|
||||
// some of the logic for each interrupt, but saves additional time.
|
||||
|
||||
// As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds
|
||||
// when a new register is loaded and the logic needs to switch active register packet pointers.
|
||||
|
||||
// THE INTERRUPT CODE MACRO: R=REGISTER LIST (mainRegs or progRegs), and N=TIMER (0 or 1)
|
||||
|
||||
#define DCC_SIGNAL(R,N) \
|
||||
if(R.currentBit==R.currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */ \
|
||||
R.currentBit=0; /* reset current bit pointer and determine which Register and Packet to process next--- */ \
|
||||
if(R.nRepeat>0 && R.currentReg==R.reg){ /* IF current Register is first Register AND should be repeated */ \
|
||||
R.nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */ \
|
||||
} else if(R.nextReg!=NULL){ /* ELSE IF another Register has been updated */ \
|
||||
R.currentReg=R.nextReg; /* update currentReg to nextReg */ \
|
||||
R.nextReg=NULL; /* reset nextReg to NULL */ \
|
||||
R.tempPacket=R.currentReg->activePacket; /* flip active and update Packets */ \
|
||||
R.currentReg->activePacket=R.currentReg->updatePacket; \
|
||||
R.currentReg->updatePacket=R.tempPacket; \
|
||||
} else{ /* ELSE simply move to next Register */ \
|
||||
if(R.currentReg==R.maxLoadedReg) /* BUT IF this is last Register loaded */ \
|
||||
R.currentReg=R.reg; /* first reset currentReg to base Register, THEN */ \
|
||||
R.currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */ \
|
||||
} /* END-ELSE */ \
|
||||
} /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */ \
|
||||
\
|
||||
if(R.currentReg->activePacket->buf[R.currentBit/8] & R.bitMask[R.currentBit%8]){ /* IF bit is a ONE */ \
|
||||
OCR ## N ## A=DCC_ONE_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ONE bit */ \
|
||||
OCR ## N ## B=DCC_ONE_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ONE but */ \
|
||||
} else{ /* ELSE it is a ZERO */ \
|
||||
OCR ## N ## A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER ## N; /* set OCRA for timer N to full cycle duration of DCC ZERO bit */ \
|
||||
OCR ## N ## B=DCC_ZERO_BIT_PULSE_DURATION_TIMER ## N; /* set OCRB for timer N to half cycle duration of DCC ZERO bit */ \
|
||||
} /* END-ELSE */ \
|
||||
\
|
||||
R.currentBit++; /* point to next bit in current Packet */
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT
|
||||
|
||||
ISR(TIMER1_COMPB_vect){ // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
|
||||
DCC_SIGNAL(mainRegs,1)
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
|
||||
|
||||
ISR(TIMER0_COMPB_vect){ // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track
|
||||
DCC_SIGNAL(progRegs,0)
|
||||
}
|
||||
|
||||
#else // Configuration for MEGA
|
||||
|
||||
ISR(TIMER3_COMPB_vect){ // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track
|
||||
DCC_SIGNAL(progRegs,3)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// PRINT CONFIGURATION INFO TO SERIAL PORT REGARDLESS OF INTERFACE TYPE
|
||||
// - ACTIVATED ON STARTUP IF SHOW_CONFIG_PIN IS TIED HIGH
|
||||
|
||||
void showConfiguration(){
|
||||
|
||||
int mac_address[]=MAC_ADDRESS;
|
||||
|
||||
Serial.print("\n*** DCC++ CONFIGURATION ***\n");
|
||||
|
||||
Serial.print("\nVERSION: ");
|
||||
Serial.print(VERSION);
|
||||
Serial.print("\nCOMPILED: ");
|
||||
Serial.print(__DATE__);
|
||||
Serial.print(" ");
|
||||
Serial.print(__TIME__);
|
||||
|
||||
Serial.print("\nARDUINO: ");
|
||||
Serial.print(ARDUINO_TYPE);
|
||||
|
||||
Serial.print("\n\nMOTOR SHIELD: ");
|
||||
Serial.print(MOTOR_SHIELD_NAME);
|
||||
|
||||
Serial.print("\n\nDCC SIG MAIN: ");
|
||||
Serial.print(DCC_SIGNAL_PIN_MAIN);
|
||||
Serial.print("\n DIRECTION: ");
|
||||
Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_A);
|
||||
Serial.print("\n ENABLE: ");
|
||||
Serial.print(SIGNAL_ENABLE_PIN_MAIN);
|
||||
Serial.print("\n CURRENT: ");
|
||||
Serial.print(CURRENT_MONITOR_PIN_MAIN);
|
||||
|
||||
Serial.print("\n\nDCC SIG PROG: ");
|
||||
Serial.print(DCC_SIGNAL_PIN_PROG);
|
||||
Serial.print("\n DIRECTION: ");
|
||||
Serial.print(DIRECTION_MOTOR_CHANNEL_PIN_B);
|
||||
Serial.print("\n ENABLE: ");
|
||||
Serial.print(SIGNAL_ENABLE_PIN_PROG);
|
||||
Serial.print("\n CURRENT: ");
|
||||
Serial.print(CURRENT_MONITOR_PIN_PROG);
|
||||
|
||||
Serial.print("\n\nNUM TURNOUTS: ");
|
||||
Serial.print(EEStore::eeStore->data.nTurnouts);
|
||||
Serial.print("\n SENSORS: ");
|
||||
Serial.print(EEStore::eeStore->data.nSensors);
|
||||
Serial.print("\n OUTPUTS: ");
|
||||
Serial.print(EEStore::eeStore->data.nOutputs);
|
||||
|
||||
Serial.print("\n\nINTERFACE: ");
|
||||
#if COMM_TYPE == 0
|
||||
Serial.print("SERIAL");
|
||||
#elif COMM_TYPE == 1
|
||||
Serial.print(COMM_SHIELD_NAME);
|
||||
Serial.print("\nMAC ADDRESS: ");
|
||||
for(int i=0;i<5;i++){
|
||||
Serial.print(mac_address[i],HEX);
|
||||
Serial.print(":");
|
||||
}
|
||||
Serial.print(mac_address[5],HEX);
|
||||
Serial.print("\nPORT: ");
|
||||
Serial.print(ETHERNET_PORT);
|
||||
Serial.print("\nIP ADDRESS: ");
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
Ethernet.begin(mac,IP_ADDRESS); // Start networking using STATIC IP Address
|
||||
#else
|
||||
Ethernet.begin(mac); // Start networking using DHCP to get an IP Address
|
||||
#endif
|
||||
|
||||
Serial.print(Ethernet.localIP());
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
Serial.print(" (STATIC)");
|
||||
#else
|
||||
Serial.print(" (DHCP)");
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Serial.print("\n\nPROGRAM HALTED - PLEASE RESTART ARDUINO");
|
||||
|
||||
while(true);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
83
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.cpp
Normal file
83
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
/**********************************************************************
|
||||
|
||||
EEStore.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "EEStore.h"
|
||||
#include "Accessories.h"
|
||||
#include "Sensor.h"
|
||||
#include "Outputs.h"
|
||||
#include <EEPROM.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::init(){
|
||||
|
||||
|
||||
eeStore=(EEStore *)calloc(1,sizeof(EEStore));
|
||||
|
||||
EEPROM.get(0,eeStore->data); // get eeStore data
|
||||
|
||||
if(strncmp(eeStore->data.id,EESTORE_ID,sizeof(EESTORE_ID))!=0){ // check to see that eeStore contains valid DCC++ ID
|
||||
sprintf(eeStore->data.id,EESTORE_ID); // if not, create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM
|
||||
eeStore->data.nTurnouts=0;
|
||||
eeStore->data.nSensors=0;
|
||||
eeStore->data.nOutputs=0;
|
||||
EEPROM.put(0,eeStore->data);
|
||||
}
|
||||
|
||||
reset(); // set memory pointer to first free EEPROM space
|
||||
Turnout::load(); // load turnout definitions
|
||||
Sensor::load(); // load sensor definitions
|
||||
Output::load(); // load output definitions
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::clear(){
|
||||
|
||||
sprintf(eeStore->data.id,EESTORE_ID); // create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM
|
||||
eeStore->data.nTurnouts=0;
|
||||
eeStore->data.nSensors=0;
|
||||
eeStore->data.nOutputs=0;
|
||||
EEPROM.put(0,eeStore->data);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::store(){
|
||||
reset();
|
||||
Turnout::store();
|
||||
Sensor::store();
|
||||
Output::store();
|
||||
EEPROM.put(0,eeStore->data);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::advance(int n){
|
||||
eeAddress+=n;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void EEStore::reset(){
|
||||
eeAddress=sizeof(EEStore);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int EEStore::pointer(){
|
||||
return(eeAddress);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
EEStore *EEStore::eeStore=NULL;
|
||||
int EEStore::eeAddress=0;
|
||||
|
||||
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.h
Normal file
35
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/EEStore.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/**********************************************************************
|
||||
|
||||
EEStore.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef EEStore_h
|
||||
#define EEStore_h
|
||||
|
||||
#define EESTORE_ID "DCC++"
|
||||
|
||||
struct EEStoreData{
|
||||
char id[sizeof(EESTORE_ID)];
|
||||
int nTurnouts;
|
||||
int nSensors;
|
||||
int nOutputs;
|
||||
};
|
||||
|
||||
struct EEStore{
|
||||
static EEStore *eeStore;
|
||||
EEStoreData data;
|
||||
static int eeAddress;
|
||||
static void init();
|
||||
static void reset();
|
||||
static int pointer();
|
||||
static void advance(int);
|
||||
static void store();
|
||||
static void clear();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
256
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.cpp
Normal file
256
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
/**********************************************************************
|
||||
|
||||
Outputs.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION supports optional OUTPUT control of any unused Arduino Pins for custom purposes.
|
||||
Pins can be activited or de-activated. The default is to set ACTIVE pins HIGH and INACTIVE pins LOW.
|
||||
However, this default behavior can be inverted for any pin in which case ACTIVE=LOW and INACTIVE=HIGH.
|
||||
|
||||
Definitions and state (ACTIVE/INACTIVE) for pins are retained in EEPROM and restored on power-up.
|
||||
The default is to set each defined pin to active or inactive according to its restored state.
|
||||
However, the default behavior can be modified so that any pin can be forced to be either active or inactive
|
||||
upon power-up regardless of its previous state before power-down.
|
||||
|
||||
To have this sketch utilize one or more Arduino pins as custom outputs, first define/edit/delete
|
||||
output definitions using the following variation of the "Z" command:
|
||||
|
||||
<Z ID PIN IFLAG>: creates a new output ID, with specified PIN and IFLAG values.
|
||||
if output ID already exists, it is updated with specificed PIN and IFLAG.
|
||||
note: output state will be immediately set to ACTIVE/INACTIVE and pin will be set to HIGH/LOW
|
||||
according to IFLAG value specifcied (see below).
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. out of memory)
|
||||
|
||||
<Z ID>: deletes definition of output ID
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
|
||||
|
||||
<Z>: lists all defined output pins
|
||||
returns: <Y ID PIN IFLAG STATE> for each defined output pin or <X> if no output pins defined
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the output
|
||||
PIN: the arduino pin number to use for the output
|
||||
STATE: the state of the output (0=INACTIVE / 1=ACTIVE)
|
||||
IFLAG: defines the operational behavior of the output based on bits 0, 1, and 2 as follows:
|
||||
|
||||
IFLAG, bit 0: 0 = forward operation (ACTIVE=HIGH / INACTIVE=LOW)
|
||||
1 = inverted operation (ACTIVE=LOW / INACTIVE=HIGH)
|
||||
|
||||
IFLAG, bit 1: 0 = state of pin restored on power-up to either ACTIVE or INACTIVE depending
|
||||
on state before power-down; state of pin set to INACTIVE when first created
|
||||
1 = state of pin set on power-up, or when first created, to either ACTIVE of INACTIVE
|
||||
depending on IFLAG, bit 2
|
||||
|
||||
IFLAG, bit 2: 0 = state of pin set to INACTIVE uponm power-up or when first created
|
||||
1 = state of pin set to ACTIVE uponm power-up or when first created
|
||||
|
||||
Once all outputs have been properly defined, use the <E> command to store their definitions to EEPROM.
|
||||
If you later make edits/additions/deletions to the output definitions, you must invoke the <E> command if you want those
|
||||
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> command.
|
||||
|
||||
To change the state of outputs that have been defined use:
|
||||
|
||||
<Z ID STATE>: sets output ID to either ACTIVE or INACTIVE state
|
||||
returns: <Y ID STATE>, or <X> if turnout ID does not exist
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the turnout to control
|
||||
STATE: the state of the output (0=INACTIVE / 1=ACTIVE)
|
||||
|
||||
When controlled as such, the Arduino updates and stores the direction of each output in EEPROM so
|
||||
that it is retained even without power. A list of the current states of each output in the form <Y ID STATE> is generated
|
||||
by this sketch whenever the <s> status command is invoked. This provides an efficient way of initializing
|
||||
the state of any outputs being monitored or controlled by a separate interface or GUI program.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Outputs.h"
|
||||
#include "SerialCommand.h"
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "EEStore.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::activate(int s){
|
||||
data.oStatus=(s>0); // if s>0, set status to active, else inactive
|
||||
digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
|
||||
if(num>0)
|
||||
EEPROM.put(num,data.oStatus);
|
||||
INTERFACE.print("<Y");
|
||||
INTERFACE.print(data.id);
|
||||
if(data.oStatus==0)
|
||||
INTERFACE.print(" 0>");
|
||||
else
|
||||
INTERFACE.print(" 1>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Output* Output::get(int n){
|
||||
Output *tt;
|
||||
for(tt=firstOutput;tt!=NULL && tt->data.id!=n;tt=tt->nextOutput);
|
||||
return(tt);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::remove(int n){
|
||||
Output *tt,*pp;
|
||||
|
||||
for(tt=firstOutput;tt!=NULL && tt->data.id!=n;pp=tt,tt=tt->nextOutput);
|
||||
|
||||
if(tt==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
if(tt==firstOutput)
|
||||
firstOutput=tt->nextOutput;
|
||||
else
|
||||
pp->nextOutput=tt->nextOutput;
|
||||
|
||||
free(tt);
|
||||
|
||||
INTERFACE.print("<O>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::show(int n){
|
||||
Output *tt;
|
||||
|
||||
if(firstOutput==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
for(tt=firstOutput;tt!=NULL;tt=tt->nextOutput){
|
||||
INTERFACE.print("<Y");
|
||||
INTERFACE.print(tt->data.id);
|
||||
if(n==1){
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.pin);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.iFlag);
|
||||
}
|
||||
if(tt->data.oStatus==0)
|
||||
INTERFACE.print(" 0>");
|
||||
else
|
||||
INTERFACE.print(" 1>");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::parse(char *c){
|
||||
int n,s,m;
|
||||
Output *t;
|
||||
|
||||
switch(sscanf(c,"%d %d %d",&n,&s,&m)){
|
||||
|
||||
case 2: // argument is string with id number of output followed by zero (LOW) or one (HIGH)
|
||||
t=get(n);
|
||||
if(t!=NULL)
|
||||
t->activate(s);
|
||||
else
|
||||
INTERFACE.print("<X>");
|
||||
break;
|
||||
|
||||
case 3: // argument is string with id number of output followed by a pin number and invert flag
|
||||
create(n,s,m,1);
|
||||
break;
|
||||
|
||||
case 1: // argument is a string with id number only
|
||||
remove(n);
|
||||
break;
|
||||
|
||||
case -1: // no arguments
|
||||
show(1); // verbose show
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::load(){
|
||||
struct OutputData data;
|
||||
Output *tt;
|
||||
|
||||
for(int i=0;i<EEStore::eeStore->data.nOutputs;i++){
|
||||
EEPROM.get(EEStore::pointer(),data);
|
||||
tt=create(data.id,data.pin,data.iFlag);
|
||||
tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):data.oStatus; // restore status to EEPROM value is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag
|
||||
digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0));
|
||||
pinMode(tt->data.pin,OUTPUT);
|
||||
tt->num=EEStore::pointer();
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Output::store(){
|
||||
Output *tt;
|
||||
|
||||
tt=firstOutput;
|
||||
EEStore::eeStore->data.nOutputs=0;
|
||||
|
||||
while(tt!=NULL){
|
||||
tt->num=EEStore::pointer();
|
||||
EEPROM.put(EEStore::pointer(),tt->data);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
tt=tt->nextOutput;
|
||||
EEStore::eeStore->data.nOutputs++;
|
||||
}
|
||||
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Output *Output::create(int id, int pin, int iFlag, int v){
|
||||
Output *tt;
|
||||
|
||||
if(firstOutput==NULL){
|
||||
firstOutput=(Output *)calloc(1,sizeof(Output));
|
||||
tt=firstOutput;
|
||||
} else if((tt=get(id))==NULL){
|
||||
tt=firstOutput;
|
||||
while(tt->nextOutput!=NULL)
|
||||
tt=tt->nextOutput;
|
||||
tt->nextOutput=(Output *)calloc(1,sizeof(Output));
|
||||
tt=tt->nextOutput;
|
||||
}
|
||||
|
||||
if(tt==NULL){ // problem allocating memory
|
||||
if(v==1)
|
||||
INTERFACE.print("<X>");
|
||||
return(tt);
|
||||
}
|
||||
|
||||
tt->data.id=id;
|
||||
tt->data.pin=pin;
|
||||
tt->data.iFlag=iFlag;
|
||||
tt->data.oStatus=0;
|
||||
|
||||
if(v==1){
|
||||
tt->data.oStatus=bitRead(tt->data.iFlag,1)?bitRead(tt->data.iFlag,2):0; // sets status to 0 (INACTIVE) is bit 1 of iFlag=0, otherwise set to value of bit 2 of iFlag
|
||||
digitalWrite(tt->data.pin,tt->data.oStatus ^ bitRead(tt->data.iFlag,0));
|
||||
pinMode(tt->data.pin,OUTPUT);
|
||||
INTERFACE.print("<O>");
|
||||
}
|
||||
|
||||
return(tt);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Output *Output::firstOutput=NULL;
|
||||
|
||||
39
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.h
Normal file
39
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Outputs.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**********************************************************************
|
||||
|
||||
Outputs.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#ifndef Outputs_h
|
||||
#define Outputs_h
|
||||
|
||||
struct OutputData {
|
||||
byte oStatus;
|
||||
int id;
|
||||
byte pin;
|
||||
byte iFlag;
|
||||
};
|
||||
|
||||
struct Output{
|
||||
static Output *firstOutput;
|
||||
int num;
|
||||
struct OutputData data;
|
||||
Output *nextOutput;
|
||||
void activate(int s);
|
||||
static void parse(char *c);
|
||||
static Output* get(int);
|
||||
static void remove(int);
|
||||
static void load();
|
||||
static void store();
|
||||
static Output *create(int, int, int, int=0);
|
||||
static void show(int=0);
|
||||
}; // Output
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
476
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.cpp
Normal file
476
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.cpp
Normal file
@@ -0,0 +1,476 @@
|
||||
/**********************************************************************
|
||||
|
||||
PacketRegister.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "PacketRegister.h"
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Register::initPackets(){
|
||||
activePacket=packet;
|
||||
updatePacket=packet+1;
|
||||
} // Register::initPackets
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
RegisterList::RegisterList(int maxNumRegs){
|
||||
this->maxNumRegs=maxNumRegs;
|
||||
reg=(Register *)calloc((maxNumRegs+1),sizeof(Register));
|
||||
for(int i=0;i<=maxNumRegs;i++)
|
||||
reg[i].initPackets();
|
||||
regMap=(Register **)calloc((maxNumRegs+1),sizeof(Register *));
|
||||
speedTable=(int *)calloc((maxNumRegs+1),sizeof(int *));
|
||||
currentReg=reg;
|
||||
regMap[0]=reg;
|
||||
maxLoadedReg=reg;
|
||||
nextReg=NULL;
|
||||
currentBit=0;
|
||||
nRepeat=0;
|
||||
} // RegisterList::RegisterList
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// LOAD DCC PACKET INTO TEMPORARY REGISTER 0, OR PERMANENT REGISTERS 1 THROUGH DCC_PACKET_QUEUE_MAX (INCLUSIVE)
|
||||
// CONVERTS 2, 3, 4, OR 5 BYTES INTO A DCC BIT STREAM WITH PREAMBLE, CHECKSUM, AND PROPER BYTE SEPARATORS
|
||||
// BITSTREAM IS STORED IN UP TO A 10-BYTE ARRAY (USING AT MOST 76 OF 80 BITS)
|
||||
|
||||
void RegisterList::loadPacket(int nReg, byte *b, int nBytes, int nRepeat, int printFlag) volatile {
|
||||
|
||||
nReg=nReg%((maxNumRegs+1)); // force nReg to be between 0 and maxNumRegs, inclusive
|
||||
|
||||
while(nextReg!=NULL); // pause while there is a Register already waiting to be updated -- nextReg will be reset to NULL by interrupt when prior Register updated fully processed
|
||||
|
||||
if(regMap[nReg]==NULL) // first time this Register Number has been called
|
||||
regMap[nReg]=maxLoadedReg+1; // set Register Pointer for this Register Number to next available Register
|
||||
|
||||
Register *r=regMap[nReg]; // set Register to be updated
|
||||
Packet *p=r->updatePacket; // set Packet in the Register to be updated
|
||||
byte *buf=p->buf; // set byte buffer in the Packet to be updated
|
||||
|
||||
b[nBytes]=b[0]; // copy first byte into what will become the checksum byte
|
||||
for(int i=1;i<nBytes;i++) // XOR remaining bytes into checksum byte
|
||||
b[nBytes]^=b[i];
|
||||
nBytes++; // increment number of bytes in packet to include checksum byte
|
||||
|
||||
buf[0]=0xFF; // first 8 bytes of 22-byte preamble
|
||||
buf[1]=0xFF; // second 8 bytes of 22-byte preamble
|
||||
buf[2]=0xFC + bitRead(b[0],7); // last 6 bytes of 22-byte preamble + data start bit + b[0], bit 7
|
||||
buf[3]=b[0]<<1; // b[0], bits 6-0 + data start bit
|
||||
buf[4]=b[1]; // b[1], all bits
|
||||
buf[5]=b[2]>>1; // b[2], bits 7-1
|
||||
buf[6]=b[2]<<7; // b[2], bit 0
|
||||
|
||||
if(nBytes==3){
|
||||
p->nBits=49;
|
||||
} else{
|
||||
buf[6]+=b[3]>>2; // b[3], bits 7-2
|
||||
buf[7]=b[3]<<6; // b[3], bit 1-0
|
||||
if(nBytes==4){
|
||||
p->nBits=58;
|
||||
} else{
|
||||
buf[7]+=b[4]>>3; // b[4], bits 7-3
|
||||
buf[8]=b[4]<<5; // b[4], bits 2-0
|
||||
if(nBytes==5){
|
||||
p->nBits=67;
|
||||
} else{
|
||||
buf[8]+=b[5]>>4; // b[5], bits 7-4
|
||||
buf[9]=b[5]<<4; // b[5], bits 3-0
|
||||
p->nBits=76;
|
||||
} // >5 bytes
|
||||
} // >4 bytes
|
||||
} // >3 bytes
|
||||
|
||||
nextReg=r;
|
||||
this->nRepeat=nRepeat;
|
||||
maxLoadedReg=max(maxLoadedReg,nextReg);
|
||||
|
||||
if(printFlag && SHOW_PACKETS) // for debugging purposes
|
||||
printPacket(nReg,b,nBytes,nRepeat);
|
||||
|
||||
} // RegisterList::loadPacket
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::setThrottle(char *s) volatile{
|
||||
byte b[5]; // save space for checksum byte
|
||||
int nReg;
|
||||
int cab;
|
||||
int tSpeed;
|
||||
int tDirection;
|
||||
byte nB=0;
|
||||
|
||||
if(sscanf(s,"%d %d %d %d",&nReg,&cab,&tSpeed,&tDirection)!=4)
|
||||
return;
|
||||
|
||||
if(nReg<1 || nReg>maxNumRegs)
|
||||
return;
|
||||
|
||||
if(cab>127)
|
||||
b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
|
||||
b[nB++]=lowByte(cab);
|
||||
b[nB++]=0x3F; // 128-step speed control byte
|
||||
if(tSpeed>=0)
|
||||
b[nB++]=tSpeed+(tSpeed>0)+tDirection*128; // max speed is 126, but speed codes range from 2-127 (0=stop, 1=emergency stop)
|
||||
else{
|
||||
b[nB++]=1;
|
||||
tSpeed=0;
|
||||
}
|
||||
|
||||
loadPacket(nReg,b,nB,0,1);
|
||||
|
||||
INTERFACE.print("<T");
|
||||
INTERFACE.print(nReg); INTERFACE.print(" ");
|
||||
INTERFACE.print(tSpeed); INTERFACE.print(" ");
|
||||
INTERFACE.print(tDirection);
|
||||
INTERFACE.print(">");
|
||||
|
||||
speedTable[nReg]=tDirection==1?tSpeed:-tSpeed;
|
||||
|
||||
} // RegisterList::setThrottle()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::setFunction(char *s) volatile{
|
||||
byte b[5]; // save space for checksum byte
|
||||
int cab;
|
||||
int fByte, eByte;
|
||||
int nParams;
|
||||
byte nB=0;
|
||||
|
||||
nParams=sscanf(s,"%d %d %d",&cab,&fByte,&eByte);
|
||||
|
||||
if(nParams<2)
|
||||
return;
|
||||
|
||||
if(cab>127)
|
||||
b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
|
||||
b[nB++]=lowByte(cab);
|
||||
|
||||
if(nParams==2){ // this is a request for functions FL,F1-F12
|
||||
b[nB++]=(fByte | 0x80) & 0xBF; // for safety this guarantees that first nibble of function byte will always be of binary form 10XX which should always be the case for FL,F1-F12
|
||||
} else { // this is a request for functions F13-F28
|
||||
b[nB++]=(fByte | 0xDE) & 0xDF; // for safety this guarantees that first byte will either be 0xDE (for F13-F20) or 0xDF (for F21-F28)
|
||||
b[nB++]=eByte;
|
||||
}
|
||||
|
||||
loadPacket(0,b,nB,4,1);
|
||||
|
||||
} // RegisterList::setFunction()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::setAccessory(char *s) volatile{
|
||||
byte b[3]; // save space for checksum byte
|
||||
int aAdd; // the accessory address (0-511 = 9 bits)
|
||||
int aNum; // the accessory number within that address (0-3)
|
||||
int activate; // flag indicated whether accessory should be activated (1) or deactivated (0) following NMRA recommended convention
|
||||
|
||||
if(sscanf(s,"%d %d %d",&aAdd,&aNum,&activate)!=3)
|
||||
return;
|
||||
|
||||
b[0]=aAdd%64+128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||
b[1]=((((aAdd/64)%8)<<4) + (aNum%4<<1) + activate%2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
|
||||
|
||||
loadPacket(0,b,2,4,1);
|
||||
|
||||
} // RegisterList::setAccessory()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeTextPacket(char *s) volatile{
|
||||
|
||||
int nReg;
|
||||
byte b[6];
|
||||
int nBytes;
|
||||
volatile RegisterList *regs;
|
||||
|
||||
nBytes=sscanf(s,"%d %x %x %x %x %x",&nReg,b,b+1,b+2,b+3,b+4)-1;
|
||||
|
||||
if(nBytes<2 || nBytes>5){ // invalid valid packet
|
||||
INTERFACE.print("<mInvalid Packet>");
|
||||
return;
|
||||
}
|
||||
|
||||
loadPacket(nReg,b,nBytes,0,1);
|
||||
|
||||
} // RegisterList::writeTextPacket()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::readCV(char *s) volatile{
|
||||
byte bRead[4];
|
||||
int bValue;
|
||||
int c,d,base;
|
||||
int cv, callBack, callBackSub;
|
||||
|
||||
if(sscanf(s,"%d %d %d",&cv,&callBack,&callBackSub)!=3) // cv = 1-1024
|
||||
return;
|
||||
cv--; // actual CV addresses are cv-1 (0-1023)
|
||||
|
||||
bRead[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
bRead[1]=lowByte(cv);
|
||||
|
||||
bValue=0;
|
||||
|
||||
for(int i=0;i<8;i++){
|
||||
|
||||
c=0;
|
||||
d=0;
|
||||
base=0;
|
||||
|
||||
for(int j=0;j<ACK_BASE_COUNT;j++)
|
||||
base+=analogRead(CURRENT_MONITOR_PIN_PROG);
|
||||
base/=ACK_BASE_COUNT;
|
||||
|
||||
bRead[2]=0xE8+i;
|
||||
|
||||
loadPacket(0,resetPacket,2,3); // NMRA recommends starting with 3 reset packets
|
||||
loadPacket(0,bRead,3,5); // NMRA recommends 5 verfy packets
|
||||
loadPacket(0,resetPacket,2,1); // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
|
||||
|
||||
for(int j=0;j<ACK_SAMPLE_COUNT;j++){
|
||||
c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
|
||||
if(c>ACK_SAMPLE_THRESHOLD)
|
||||
d=1;
|
||||
}
|
||||
|
||||
bitWrite(bValue,i,d);
|
||||
}
|
||||
|
||||
c=0;
|
||||
d=0;
|
||||
base=0;
|
||||
|
||||
for(int j=0;j<ACK_BASE_COUNT;j++)
|
||||
base+=analogRead(CURRENT_MONITOR_PIN_PROG);
|
||||
base/=ACK_BASE_COUNT;
|
||||
|
||||
bRead[0]=0x74+(highByte(cv)&0x03); // set-up to re-verify entire byte
|
||||
bRead[2]=bValue;
|
||||
|
||||
loadPacket(0,resetPacket,2,3); // NMRA recommends starting with 3 reset packets
|
||||
loadPacket(0,bRead,3,5); // NMRA recommends 5 verfy packets
|
||||
loadPacket(0,resetPacket,2,1); // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
|
||||
|
||||
for(int j=0;j<ACK_SAMPLE_COUNT;j++){
|
||||
c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
|
||||
if(c>ACK_SAMPLE_THRESHOLD)
|
||||
d=1;
|
||||
}
|
||||
|
||||
if(d==0) // verify unsuccessful
|
||||
bValue=-1;
|
||||
|
||||
INTERFACE.print("<r");
|
||||
INTERFACE.print(callBack);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(callBackSub);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(cv+1);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(bValue);
|
||||
INTERFACE.print(">");
|
||||
|
||||
} // RegisterList::readCV()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeCVByte(char *s) volatile{
|
||||
byte bWrite[4];
|
||||
int bValue;
|
||||
int c,d,base;
|
||||
int cv, callBack, callBackSub;
|
||||
|
||||
if(sscanf(s,"%d %d %d %d",&cv,&bValue,&callBack,&callBackSub)!=4) // cv = 1-1024
|
||||
return;
|
||||
cv--; // actual CV addresses are cv-1 (0-1023)
|
||||
|
||||
bWrite[0]=0x7C+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
bWrite[1]=lowByte(cv);
|
||||
bWrite[2]=bValue;
|
||||
|
||||
loadPacket(0,resetPacket,2,1);
|
||||
loadPacket(0,bWrite,3,4);
|
||||
loadPacket(0,resetPacket,2,1);
|
||||
loadPacket(0,idlePacket,2,10);
|
||||
|
||||
c=0;
|
||||
d=0;
|
||||
base=0;
|
||||
|
||||
for(int j=0;j<ACK_BASE_COUNT;j++)
|
||||
base+=analogRead(CURRENT_MONITOR_PIN_PROG);
|
||||
base/=ACK_BASE_COUNT;
|
||||
|
||||
bWrite[0]=0x74+(highByte(cv)&0x03); // set-up to re-verify entire byte
|
||||
|
||||
loadPacket(0,resetPacket,2,3); // NMRA recommends starting with 3 reset packets
|
||||
loadPacket(0,bWrite,3,5); // NMRA recommends 5 verfy packets
|
||||
loadPacket(0,resetPacket,2,1); // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
|
||||
|
||||
for(int j=0;j<ACK_SAMPLE_COUNT;j++){
|
||||
c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
|
||||
if(c>ACK_SAMPLE_THRESHOLD)
|
||||
d=1;
|
||||
}
|
||||
|
||||
if(d==0) // verify unsuccessful
|
||||
bValue=-1;
|
||||
|
||||
INTERFACE.print("<r");
|
||||
INTERFACE.print(callBack);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(callBackSub);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(cv+1);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(bValue);
|
||||
INTERFACE.print(">");
|
||||
|
||||
} // RegisterList::writeCVByte()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeCVBit(char *s) volatile{
|
||||
byte bWrite[4];
|
||||
int bNum,bValue;
|
||||
int c,d,base;
|
||||
int cv, callBack, callBackSub;
|
||||
|
||||
if(sscanf(s,"%d %d %d %d %d",&cv,&bNum,&bValue,&callBack,&callBackSub)!=5) // cv = 1-1024
|
||||
return;
|
||||
cv--; // actual CV addresses are cv-1 (0-1023)
|
||||
bValue=bValue%2;
|
||||
bNum=bNum%8;
|
||||
|
||||
bWrite[0]=0x78+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
bWrite[1]=lowByte(cv);
|
||||
bWrite[2]=0xF0+bValue*8+bNum;
|
||||
|
||||
loadPacket(0,resetPacket,2,1);
|
||||
loadPacket(0,bWrite,3,4);
|
||||
loadPacket(0,resetPacket,2,1);
|
||||
loadPacket(0,idlePacket,2,10);
|
||||
|
||||
c=0;
|
||||
d=0;
|
||||
base=0;
|
||||
|
||||
for(int j=0;j<ACK_BASE_COUNT;j++)
|
||||
base+=analogRead(CURRENT_MONITOR_PIN_PROG);
|
||||
base/=ACK_BASE_COUNT;
|
||||
|
||||
bitClear(bWrite[2],4); // change instruction code from Write Bit to Verify Bit
|
||||
|
||||
loadPacket(0,resetPacket,2,3); // NMRA recommends starting with 3 reset packets
|
||||
loadPacket(0,bWrite,3,5); // NMRA recommends 5 verfy packets
|
||||
loadPacket(0,resetPacket,2,1); // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
|
||||
|
||||
for(int j=0;j<ACK_SAMPLE_COUNT;j++){
|
||||
c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
|
||||
if(c>ACK_SAMPLE_THRESHOLD)
|
||||
d=1;
|
||||
}
|
||||
|
||||
if(d==0) // verify unsuccessful
|
||||
bValue=-1;
|
||||
|
||||
INTERFACE.print("<r");
|
||||
INTERFACE.print(callBack);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(callBackSub);
|
||||
INTERFACE.print("|");
|
||||
INTERFACE.print(cv+1);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(bNum);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(bValue);
|
||||
INTERFACE.print(">");
|
||||
|
||||
} // RegisterList::writeCVBit()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeCVByteMain(char *s) volatile{
|
||||
byte b[6]; // save space for checksum byte
|
||||
int cab;
|
||||
int cv;
|
||||
int bValue;
|
||||
byte nB=0;
|
||||
|
||||
if(sscanf(s,"%d %d %d",&cab,&cv,&bValue)!=3)
|
||||
return;
|
||||
cv--;
|
||||
|
||||
if(cab>127)
|
||||
b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
|
||||
b[nB++]=lowByte(cab);
|
||||
b[nB++]=0xEC+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
b[nB++]=lowByte(cv);
|
||||
b[nB++]=bValue;
|
||||
|
||||
loadPacket(0,b,nB,4);
|
||||
|
||||
} // RegisterList::writeCVByteMain()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::writeCVBitMain(char *s) volatile{
|
||||
byte b[6]; // save space for checksum byte
|
||||
int cab;
|
||||
int cv;
|
||||
int bNum;
|
||||
int bValue;
|
||||
byte nB=0;
|
||||
|
||||
if(sscanf(s,"%d %d %d %d",&cab,&cv,&bNum,&bValue)!=4)
|
||||
return;
|
||||
cv--;
|
||||
|
||||
bValue=bValue%2;
|
||||
bNum=bNum%8;
|
||||
|
||||
if(cab>127)
|
||||
b[nB++]=highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
|
||||
b[nB++]=lowByte(cab);
|
||||
b[nB++]=0xE8+(highByte(cv)&0x03); // any CV>1023 will become modulus(1024) due to bit-mask of 0x03
|
||||
b[nB++]=lowByte(cv);
|
||||
b[nB++]=0xF0+bValue*8+bNum;
|
||||
|
||||
loadPacket(0,b,nB,4);
|
||||
|
||||
} // RegisterList::writeCVBitMain()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegisterList::printPacket(int nReg, byte *b, int nBytes, int nRepeat) volatile {
|
||||
|
||||
INTERFACE.print("<*");
|
||||
INTERFACE.print(nReg);
|
||||
INTERFACE.print(":");
|
||||
for(int i=0;i<nBytes;i++){
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(b[i],HEX);
|
||||
}
|
||||
INTERFACE.print(" / ");
|
||||
INTERFACE.print(nRepeat);
|
||||
INTERFACE.print(">");
|
||||
} // RegisterList::printPacket()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
byte RegisterList::idlePacket[3]={0xFF,0x00,0}; // always leave extra byte for checksum computation
|
||||
byte RegisterList::resetPacket[3]={0x00,0x00,0};
|
||||
|
||||
byte RegisterList::bitMask[]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}; // masks used in interrupt routine to speed the query of a single bit in a Packet
|
||||
64
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.h
Normal file
64
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/PacketRegister.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/**********************************************************************
|
||||
|
||||
PacketRegister.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef PacketRegister_h
|
||||
#define PacketRegister_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
// Define constants used for reading CVs from the Programming Track
|
||||
|
||||
#define ACK_BASE_COUNT 100 // number of analogRead samples to take before each CV verify to establish a baseline current
|
||||
#define ACK_SAMPLE_COUNT 500 // number of analogRead samples to take when monitoring current after a CV verify (bit or byte) has been sent
|
||||
#define ACK_SAMPLE_SMOOTHING 0.2 // exponential smoothing to use in processing the analogRead samples after a CV verify (bit or byte) has been sent
|
||||
#define ACK_SAMPLE_THRESHOLD 30 // the threshold that the exponentially-smoothed analogRead samples (after subtracting the baseline current) must cross to establish ACKNOWLEDGEMENT
|
||||
|
||||
// Define a series of registers that can be sequentially accessed over a loop to generate a repeating series of DCC Packets
|
||||
|
||||
struct Packet{
|
||||
byte buf[10];
|
||||
byte nBits;
|
||||
}; // Packet
|
||||
|
||||
struct Register{
|
||||
Packet packet[2];
|
||||
Packet *activePacket;
|
||||
Packet *updatePacket;
|
||||
void initPackets();
|
||||
}; // Register
|
||||
|
||||
struct RegisterList{
|
||||
int maxNumRegs;
|
||||
Register *reg;
|
||||
Register **regMap;
|
||||
Register *currentReg;
|
||||
Register *maxLoadedReg;
|
||||
Register *nextReg;
|
||||
Packet *tempPacket;
|
||||
byte currentBit;
|
||||
byte nRepeat;
|
||||
int *speedTable;
|
||||
static byte idlePacket[];
|
||||
static byte resetPacket[];
|
||||
static byte bitMask[];
|
||||
RegisterList(int);
|
||||
void loadPacket(int, byte *, int, int, int=0) volatile;
|
||||
void setThrottle(char *) volatile;
|
||||
void setFunction(char *) volatile;
|
||||
void setAccessory(char *) volatile;
|
||||
void writeTextPacket(char *) volatile;
|
||||
void readCV(char *) volatile;
|
||||
void writeCVByte(char *) volatile;
|
||||
void writeCVBit(char *) volatile;
|
||||
void writeCVByteMain(char *) volatile;
|
||||
void writeCVBitMain(char *s) volatile;
|
||||
void printPacket(int, byte *, int, int) volatile;
|
||||
};
|
||||
|
||||
#endif
|
||||
248
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.cpp
Normal file
248
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
/**********************************************************************
|
||||
|
||||
Sensor.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
/**********************************************************************
|
||||
|
||||
DCC++ BASE STATION supports Sensor inputs that can be connected to any Aruidno Pin
|
||||
not in use by this program. Sensors can be of any type (infrared, magentic, mechanical...).
|
||||
The only requirement is that when "activated" the Sensor must force the specified Arduino
|
||||
Pin LOW (i.e. to ground), and when not activated, this Pin should remain HIGH (e.g. 5V),
|
||||
or be allowed to float HIGH if use of the Arduino Pin's internal pull-up resistor is specified.
|
||||
|
||||
To ensure proper voltage levels, some part of the Sensor circuitry
|
||||
MUST be tied back to the same ground as used by the Arduino.
|
||||
|
||||
The Sensor code below utilizes exponential smoothing to "de-bounce" spikes generated by
|
||||
mechanical switches and transistors. This avoids the need to create smoothing circuitry
|
||||
for each sensor. You may need to change these parameters through trial and error for your specific sensors.
|
||||
|
||||
To have this sketch monitor one or more Arduino pins for sensor triggers, first define/edit/delete
|
||||
sensor definitions using the following variation of the "S" command:
|
||||
|
||||
<S ID PIN PULLUP>: creates a new sensor ID, with specified PIN and PULLUP
|
||||
if sensor ID already exists, it is updated with specificed PIN and PULLUP
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. out of memory)
|
||||
|
||||
<S ID>: deletes definition of sensor ID
|
||||
returns: <O> if successful and <X> if unsuccessful (e.g. ID does not exist)
|
||||
|
||||
<S>: lists all defined sensors
|
||||
returns: <Q ID PIN PULLUP> for each defined sensor or <X> if no sensors defined
|
||||
|
||||
where
|
||||
|
||||
ID: the numeric ID (0-32767) of the sensor
|
||||
PIN: the arduino pin number the sensor is connected to
|
||||
PULLUP: 1=use internal pull-up resistor for PIN, 0=don't use internal pull-up resistor for PIN
|
||||
|
||||
Once all sensors have been properly defined, use the <E> command to store their definitions to EEPROM.
|
||||
If you later make edits/additions/deletions to the sensor definitions, you must invoke the <E> command if you want those
|
||||
new definitions updated in the EEPROM. You can also clear everything stored in the EEPROM by invoking the <e> command.
|
||||
|
||||
All sensors defined as per above are repeatedly and sequentially checked within the main loop of this sketch.
|
||||
If a Sensor Pin is found to have transitioned from one state to another, one of the following serial messages are generated:
|
||||
|
||||
<Q ID> - for transition of Sensor ID from HIGH state to LOW state (i.e. the sensor is triggered)
|
||||
<q ID> - for transition of Sensor ID from LOW state to HIGH state (i.e. the sensor is no longer triggered)
|
||||
|
||||
Depending on whether the physical sensor is acting as an "event-trigger" or a "detection-sensor," you may
|
||||
decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "Sensor.h"
|
||||
#include "EEStore.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Comm.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::check(){
|
||||
Sensor *tt;
|
||||
|
||||
for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||
tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY;
|
||||
|
||||
if(!tt->active && tt->signal<0.5){
|
||||
tt->active=true;
|
||||
INTERFACE.print("<Q");
|
||||
INTERFACE.print(tt->data.snum);
|
||||
INTERFACE.print(">");
|
||||
} else if(tt->active && tt->signal>0.9){
|
||||
tt->active=false;
|
||||
INTERFACE.print("<q");
|
||||
INTERFACE.print(tt->data.snum);
|
||||
INTERFACE.print(">");
|
||||
}
|
||||
} // loop over all sensors
|
||||
|
||||
} // Sensor::check
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Sensor *Sensor::create(int snum, int pin, int pullUp, int v){
|
||||
Sensor *tt;
|
||||
|
||||
if(firstSensor==NULL){
|
||||
firstSensor=(Sensor *)calloc(1,sizeof(Sensor));
|
||||
tt=firstSensor;
|
||||
} else if((tt=get(snum))==NULL){
|
||||
tt=firstSensor;
|
||||
while(tt->nextSensor!=NULL)
|
||||
tt=tt->nextSensor;
|
||||
tt->nextSensor=(Sensor *)calloc(1,sizeof(Sensor));
|
||||
tt=tt->nextSensor;
|
||||
}
|
||||
|
||||
if(tt==NULL){ // problem allocating memory
|
||||
if(v==1)
|
||||
INTERFACE.print("<X>");
|
||||
return(tt);
|
||||
}
|
||||
|
||||
tt->data.snum=snum;
|
||||
tt->data.pin=pin;
|
||||
tt->data.pullUp=(pullUp==0?LOW:HIGH);
|
||||
tt->active=false;
|
||||
tt->signal=1;
|
||||
pinMode(pin,INPUT); // set mode to input
|
||||
digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor
|
||||
|
||||
if(v==1)
|
||||
INTERFACE.print("<O>");
|
||||
return(tt);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Sensor* Sensor::get(int n){
|
||||
Sensor *tt;
|
||||
for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;tt=tt->nextSensor);
|
||||
return(tt);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::remove(int n){
|
||||
Sensor *tt,*pp;
|
||||
|
||||
for(tt=firstSensor;tt!=NULL && tt->data.snum!=n;pp=tt,tt=tt->nextSensor);
|
||||
|
||||
if(tt==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
if(tt==firstSensor)
|
||||
firstSensor=tt->nextSensor;
|
||||
else
|
||||
pp->nextSensor=tt->nextSensor;
|
||||
|
||||
free(tt);
|
||||
|
||||
INTERFACE.print("<O>");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::show(){
|
||||
Sensor *tt;
|
||||
|
||||
if(firstSensor==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||
INTERFACE.print("<Q");
|
||||
INTERFACE.print(tt->data.snum);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.pin);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(tt->data.pullUp);
|
||||
INTERFACE.print(">");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::status(){
|
||||
Sensor *tt;
|
||||
|
||||
if(firstSensor==NULL){
|
||||
INTERFACE.print("<X>");
|
||||
return;
|
||||
}
|
||||
|
||||
for(tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||
INTERFACE.print(tt->active?"<Q":"<q");
|
||||
INTERFACE.print(tt->data.snum);
|
||||
INTERFACE.print(">");
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::parse(char *c){
|
||||
int n,s,m;
|
||||
Sensor *t;
|
||||
|
||||
switch(sscanf(c,"%d %d %d",&n,&s,&m)){
|
||||
|
||||
case 3: // argument is string with id number of sensor followed by a pin number and pullUp indicator (0=LOW/1=HIGH)
|
||||
create(n,s,m,1);
|
||||
break;
|
||||
|
||||
case 1: // argument is a string with id number only
|
||||
remove(n);
|
||||
break;
|
||||
|
||||
case -1: // no arguments
|
||||
show();
|
||||
break;
|
||||
|
||||
case 2: // invalid number of arguments
|
||||
INTERFACE.print("<X>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::load(){
|
||||
struct SensorData data;
|
||||
Sensor *tt;
|
||||
|
||||
for(int i=0;i<EEStore::eeStore->data.nSensors;i++){
|
||||
EEPROM.get(EEStore::pointer(),data);
|
||||
tt=create(data.snum,data.pin,data.pullUp);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::store(){
|
||||
Sensor *tt;
|
||||
|
||||
tt=firstSensor;
|
||||
EEStore::eeStore->data.nSensors=0;
|
||||
|
||||
while(tt!=NULL){
|
||||
EEPROM.put(EEStore::pointer(),tt->data);
|
||||
EEStore::advance(sizeof(tt->data));
|
||||
tt=tt->nextSensor;
|
||||
EEStore::eeStore->data.nSensors++;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Sensor *Sensor::firstSensor=NULL;
|
||||
|
||||
41
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.h
Normal file
41
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/Sensor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/**********************************************************************
|
||||
|
||||
Sensor.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef Sensor_h
|
||||
#define Sensor_h
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#define SENSOR_DECAY 0.03
|
||||
|
||||
struct SensorData {
|
||||
int snum;
|
||||
byte pin;
|
||||
byte pullUp;
|
||||
};
|
||||
|
||||
struct Sensor{
|
||||
static Sensor *firstSensor;
|
||||
SensorData data;
|
||||
boolean active;
|
||||
float signal;
|
||||
Sensor *nextSensor;
|
||||
static void load();
|
||||
static void store();
|
||||
static Sensor *create(int, int, int, int=0);
|
||||
static Sensor* get(int);
|
||||
static void remove(int);
|
||||
static void show();
|
||||
static void status();
|
||||
static void parse(char *c);
|
||||
static void check();
|
||||
}; // Sensor
|
||||
|
||||
#endif
|
||||
|
||||
571
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.cpp
Normal file
571
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.cpp
Normal file
@@ -0,0 +1,571 @@
|
||||
/**********************************************************************
|
||||
|
||||
SerialCommand.cpp
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
// DCC++ BASE STATION COMMUNICATES VIA THE SERIAL PORT USING SINGLE-CHARACTER TEXT COMMANDS
|
||||
// WITH OPTIONAL PARAMTERS, AND BRACKETED BY < AND > SYMBOLS. SPACES BETWEEN PARAMETERS
|
||||
// ARE REQUIRED. SPACES ANYWHERE ELSE ARE IGNORED. A SPACE BETWEEN THE SINGLE-CHARACTER
|
||||
// COMMAND AND THE FIRST PARAMETER IS ALSO NOT REQUIRED.
|
||||
|
||||
// See SerialCommand::parse() below for defined text commands.
|
||||
|
||||
#include "SerialCommand.h"
|
||||
#include "DCCpp_Uno.h"
|
||||
#include "Accessories.h"
|
||||
#include "Sensor.h"
|
||||
#include "Outputs.h"
|
||||
#include "EEStore.h"
|
||||
#include "Comm.h"
|
||||
|
||||
extern int __heap_start, *__brkval;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
char SerialCommand::commandString[MAX_COMMAND_LENGTH+1];
|
||||
volatile RegisterList *SerialCommand::mRegs;
|
||||
volatile RegisterList *SerialCommand::pRegs;
|
||||
CurrentMonitor *SerialCommand::mMonitor;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void SerialCommand::init(volatile RegisterList *_mRegs, volatile RegisterList *_pRegs, CurrentMonitor *_mMonitor){
|
||||
mRegs=_mRegs;
|
||||
pRegs=_pRegs;
|
||||
mMonitor=_mMonitor;
|
||||
sprintf(commandString,"");
|
||||
} // SerialCommand:SerialCommand
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void SerialCommand::process(){
|
||||
char c;
|
||||
|
||||
#if COMM_TYPE == 0
|
||||
|
||||
while(INTERFACE.available()>0){ // while there is data on the serial line
|
||||
c=INTERFACE.read();
|
||||
if(c=='<') // start of new command
|
||||
sprintf(commandString,"");
|
||||
else if(c=='>') // end of new command
|
||||
parse(commandString);
|
||||
else if(strlen(commandString)<MAX_COMMAND_LENGTH) // if comandString still has space, append character just read from serial line
|
||||
sprintf(commandString,"%s%c",commandString,c); // otherwise, character is ignored (but continue to look for '<' or '>')
|
||||
} // while
|
||||
|
||||
#elif COMM_TYPE == 1
|
||||
|
||||
EthernetClient client=INTERFACE.available();
|
||||
|
||||
if(client){
|
||||
while(client.connected() && client.available()){ // while there is data on the network
|
||||
c=client.read();
|
||||
if(c=='<') // start of new command
|
||||
sprintf(commandString,"");
|
||||
else if(c=='>') // end of new command
|
||||
parse(commandString);
|
||||
else if(strlen(commandString)<MAX_COMMAND_LENGTH) // if comandString still has space, append character just read from network
|
||||
sprintf(commandString,"%s%c",commandString,c); // otherwise, character is ignored (but continue to look for '<' or '>')
|
||||
} // while
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // SerialCommand:process
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void SerialCommand::parse(char *com){
|
||||
|
||||
switch(com[0]){
|
||||
|
||||
/***** SET ENGINE THROTTLES USING 128-STEP SPEED CONTROL ****/
|
||||
|
||||
case 't': // <t REGISTER CAB SPEED DIRECTION>
|
||||
/*
|
||||
* sets the throttle for a given register/cab combination
|
||||
*
|
||||
* REGISTER: an internal register number, from 1 through MAX_MAIN_REGISTERS (inclusive), to store the DCC packet used to control this throttle setting
|
||||
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
|
||||
* SPEED: throttle speed from 0-126, or -1 for emergency stop (resets SPEED to 0)
|
||||
* DIRECTION: 1=forward, 0=reverse. Setting direction when speed=0 or speed=-1 only effects directionality of cab lighting for a stopped train
|
||||
*
|
||||
* returns: <T REGISTER SPEED DIRECTION>
|
||||
*
|
||||
*/
|
||||
mRegs->setThrottle(com+1);
|
||||
break;
|
||||
|
||||
/***** OPERATE ENGINE DECODER FUNCTIONS F0-F28 ****/
|
||||
|
||||
case 'f': // <f CAB BYTE1 [BYTE2]>
|
||||
/*
|
||||
* turns on and off engine decoder functions F0-F28 (F0 is sometimes called FL)
|
||||
* NOTE: setting requests transmitted directly to mobile engine decoder --- current state of engine functions is not stored by this program
|
||||
*
|
||||
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
|
||||
*
|
||||
* To set functions F0-F4 on (=1) or off (=0):
|
||||
*
|
||||
* BYTE1: 128 + F1*1 + F2*2 + F3*4 + F4*8 + F0*16
|
||||
* BYTE2: omitted
|
||||
*
|
||||
* To set functions F5-F8 on (=1) or off (=0):
|
||||
*
|
||||
* BYTE1: 176 + F5*1 + F6*2 + F7*4 + F8*8
|
||||
* BYTE2: omitted
|
||||
*
|
||||
* To set functions F9-F12 on (=1) or off (=0):
|
||||
*
|
||||
* BYTE1: 160 + F9*1 +F10*2 + F11*4 + F12*8
|
||||
* BYTE2: omitted
|
||||
*
|
||||
* To set functions F13-F20 on (=1) or off (=0):
|
||||
*
|
||||
* BYTE1: 222
|
||||
* BYTE2: F13*1 + F14*2 + F15*4 + F16*8 + F17*16 + F18*32 + F19*64 + F20*128
|
||||
*
|
||||
* To set functions F21-F28 on (=1) of off (=0):
|
||||
*
|
||||
* BYTE1: 223
|
||||
* BYTE2: F21*1 + F22*2 + F23*4 + F24*8 + F25*16 + F26*32 + F27*64 + F28*128
|
||||
*
|
||||
* returns: NONE
|
||||
*
|
||||
*/
|
||||
mRegs->setFunction(com+1);
|
||||
break;
|
||||
|
||||
/***** OPERATE STATIONARY ACCESSORY DECODERS ****/
|
||||
|
||||
case 'a': // <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
/*
|
||||
* turns an accessory (stationary) decoder on or off
|
||||
*
|
||||
* ADDRESS: the primary address of the decoder (0-511)
|
||||
* SUBADDRESS: the subaddress of the decoder (0-3)
|
||||
* ACTIVATE: 1=on (set), 0=off (clear)
|
||||
*
|
||||
* Note that many decoders and controllers combine the ADDRESS and SUBADDRESS into a single number, N,
|
||||
* from 1 through a max of 2044, where
|
||||
*
|
||||
* N = (ADDRESS - 1) * 4 + SUBADDRESS + 1, for all ADDRESS>0
|
||||
*
|
||||
* OR
|
||||
*
|
||||
* ADDRESS = INT((N - 1) / 4) + 1
|
||||
* SUBADDRESS = (N - 1) % 4
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
mRegs->setAccessory(com+1);
|
||||
break;
|
||||
|
||||
/***** CREATE/EDIT/REMOVE/SHOW & OPERATE A TURN-OUT ****/
|
||||
|
||||
case 'T': // <T ID THROW>
|
||||
/*
|
||||
* <T ID THROW>: sets turnout ID to either the "thrown" or "unthrown" position
|
||||
*
|
||||
* ID: the numeric ID (0-32767) of the turnout to control
|
||||
* THROW: 0 (unthrown) or 1 (thrown)
|
||||
*
|
||||
* returns: <H ID THROW> or <X> if turnout ID does not exist
|
||||
*
|
||||
* *** SEE ACCESSORIES.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "T" COMMAND
|
||||
* USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS
|
||||
*/
|
||||
Turnout::parse(com+1);
|
||||
break;
|
||||
|
||||
/***** CREATE/EDIT/REMOVE/SHOW & OPERATE AN OUTPUT PIN ****/
|
||||
|
||||
case 'Z': // <Z ID ACTIVATE>
|
||||
/*
|
||||
* <Z ID ACTIVATE>: sets output ID to either the "active" or "inactive" state
|
||||
*
|
||||
* ID: the numeric ID (0-32767) of the output to control
|
||||
* ACTIVATE: 0 (active) or 1 (inactive)
|
||||
*
|
||||
* returns: <Y ID ACTIVATE> or <X> if output ID does not exist
|
||||
*
|
||||
* *** SEE OUTPUTS.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "O" COMMAND
|
||||
* USED TO CREATE/EDIT/REMOVE/SHOW TURNOUT DEFINITIONS
|
||||
*/
|
||||
Output::parse(com+1);
|
||||
break;
|
||||
|
||||
/***** CREATE/EDIT/REMOVE/SHOW A SENSOR ****/
|
||||
|
||||
case 'S':
|
||||
/*
|
||||
* *** SEE SENSOR.CPP FOR COMPLETE INFO ON THE DIFFERENT VARIATIONS OF THE "S" COMMAND
|
||||
* USED TO CREATE/EDIT/REMOVE/SHOW SENSOR DEFINITIONS
|
||||
*/
|
||||
Sensor::parse(com+1);
|
||||
break;
|
||||
|
||||
/***** SHOW STATUS OF ALL SENSORS ****/
|
||||
|
||||
case 'Q': // <Q>
|
||||
/*
|
||||
* returns: the status of each sensor ID in the form <Q ID> (active) or <q ID> (not active)
|
||||
*/
|
||||
Sensor::status();
|
||||
break;
|
||||
|
||||
/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/
|
||||
|
||||
case 'w': // <w CAB CV VALUE>
|
||||
/*
|
||||
* writes, without any verification, a Configuration Variable to the decoder of an engine on the main operations track
|
||||
*
|
||||
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024)
|
||||
* VALUE: the value to be written to the Configuration Variable memory location (0-255)
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
mRegs->writeCVByteMain(com+1);
|
||||
break;
|
||||
|
||||
/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON MAIN OPERATIONS TRACK ****/
|
||||
|
||||
case 'b': // <b CAB CV BIT VALUE>
|
||||
/*
|
||||
* writes, without any verification, a single bit within a Configuration Variable to the decoder of an engine on the main operations track
|
||||
*
|
||||
* CAB: the short (1-127) or long (128-10293) address of the engine decoder
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024)
|
||||
* BIT: the bit number of the Configurarion Variable regsiter to write (0-7)
|
||||
* VALUE: the value of the bit to be written (0-1)
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
mRegs->writeCVBitMain(com+1);
|
||||
break;
|
||||
|
||||
/***** WRITE CONFIGURATION VARIABLE BYTE TO ENGINE DECODER ON PROGRAMMING TRACK ****/
|
||||
|
||||
case 'W': // <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
||||
/*
|
||||
* writes, and then verifies, a Configuration Variable to the decoder of an engine on the programming track
|
||||
*
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024)
|
||||
* VALUE: the value to be written to the Configuration Variable memory location (0-255)
|
||||
* CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function
|
||||
* CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function
|
||||
*
|
||||
* returns: <r CALLBACKNUM|CALLBACKSUB|CV Value)
|
||||
* where VALUE is a number from 0-255 as read from the requested CV, or -1 if verificaiton read fails
|
||||
*/
|
||||
pRegs->writeCVByte(com+1);
|
||||
break;
|
||||
|
||||
/***** WRITE CONFIGURATION VARIABLE BIT TO ENGINE DECODER ON PROGRAMMING TRACK ****/
|
||||
|
||||
case 'B': // <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
||||
/*
|
||||
* writes, and then verifies, a single bit within a Configuration Variable to the decoder of an engine on the programming track
|
||||
*
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to write to (1-1024)
|
||||
* BIT: the bit number of the Configurarion Variable memory location to write (0-7)
|
||||
* VALUE: the value of the bit to be written (0-1)
|
||||
* CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function
|
||||
* CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function
|
||||
*
|
||||
* returns: <r CALLBACKNUM|CALLBACKSUB|CV BIT VALUE)
|
||||
* where VALUE is a number from 0-1 as read from the requested CV bit, or -1 if verificaiton read fails
|
||||
*/
|
||||
pRegs->writeCVBit(com+1);
|
||||
break;
|
||||
|
||||
/***** READ CONFIGURATION VARIABLE BYTE FROM ENGINE DECODER ON PROGRAMMING TRACK ****/
|
||||
|
||||
case 'R': // <R CV CALLBACKNUM CALLBACKSUB>
|
||||
/*
|
||||
* reads a Configuration Variable from the decoder of an engine on the programming track
|
||||
*
|
||||
* CV: the number of the Configuration Variable memory location in the decoder to read from (1-1024)
|
||||
* CALLBACKNUM: an arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs that call this function
|
||||
* CALLBACKSUB: a second arbitrary integer (0-32767) that is ignored by the Base Station and is simply echoed back in the output - useful for external programs (e.g. DCC++ Interface) that call this function
|
||||
*
|
||||
* returns: <r CALLBACKNUM|CALLBACKSUB|CV VALUE)
|
||||
* where VALUE is a number from 0-255 as read from the requested CV, or -1 if read could not be verified
|
||||
*/
|
||||
pRegs->readCV(com+1);
|
||||
break;
|
||||
|
||||
/***** TURN ON POWER FROM MOTOR SHIELD TO TRACKS ****/
|
||||
|
||||
case '1': // <1>
|
||||
/*
|
||||
* enables power from the motor shield to the main operations and programming tracks
|
||||
*
|
||||
* returns: <p1>
|
||||
*/
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_PROG,HIGH);
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_MAIN,HIGH);
|
||||
INTERFACE.print("<p1>");
|
||||
break;
|
||||
|
||||
/***** TURN OFF POWER FROM MOTOR SHIELD TO TRACKS ****/
|
||||
|
||||
case '0': // <0>
|
||||
/*
|
||||
* disables power from the motor shield to the main operations and programming tracks
|
||||
*
|
||||
* returns: <p0>
|
||||
*/
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW);
|
||||
digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW);
|
||||
INTERFACE.print("<p0>");
|
||||
break;
|
||||
|
||||
/***** READ MAIN OPERATIONS TRACK CURRENT ****/
|
||||
|
||||
case 'c': // <c>
|
||||
/*
|
||||
* reads current being drawn on main operations track
|
||||
*
|
||||
* returns: <a CURRENT>
|
||||
* where CURRENT = 0-1024, based on exponentially-smoothed weighting scheme
|
||||
*/
|
||||
INTERFACE.print("<a");
|
||||
INTERFACE.print(int(mMonitor->current));
|
||||
INTERFACE.print(">");
|
||||
break;
|
||||
|
||||
/***** READ STATUS OF DCC++ BASE STATION ****/
|
||||
|
||||
case 's': // <s>
|
||||
/*
|
||||
* returns status messages containing track power status, throttle status, turn-out status, and a version number
|
||||
* NOTE: this is very useful as a first command for an interface to send to this sketch in order to verify connectivity and update any GUI to reflect actual throttle and turn-out settings
|
||||
*
|
||||
* returns: series of status messages that can be read by an interface to determine status of DCC++ Base Station and important settings
|
||||
*/
|
||||
if(digitalRead(SIGNAL_ENABLE_PIN_PROG)==LOW) // could check either PROG or MAIN
|
||||
INTERFACE.print("<p0>");
|
||||
else
|
||||
INTERFACE.print("<p1>");
|
||||
|
||||
for(int i=1;i<=MAX_MAIN_REGISTERS;i++){
|
||||
if(mRegs->speedTable[i]==0)
|
||||
continue;
|
||||
INTERFACE.print("<T");
|
||||
INTERFACE.print(i); INTERFACE.print(" ");
|
||||
if(mRegs->speedTable[i]>0){
|
||||
INTERFACE.print(mRegs->speedTable[i]);
|
||||
INTERFACE.print(" 1>");
|
||||
} else{
|
||||
INTERFACE.print(-mRegs->speedTable[i]);
|
||||
INTERFACE.print(" 0>");
|
||||
}
|
||||
}
|
||||
INTERFACE.print("<iDCC++ BASE STATION FOR ARDUINO ");
|
||||
INTERFACE.print(ARDUINO_TYPE);
|
||||
INTERFACE.print(" / ");
|
||||
INTERFACE.print(MOTOR_SHIELD_NAME);
|
||||
INTERFACE.print(": V-");
|
||||
INTERFACE.print(VERSION);
|
||||
INTERFACE.print(" / ");
|
||||
INTERFACE.print(__DATE__);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(__TIME__);
|
||||
INTERFACE.print(">");
|
||||
|
||||
INTERFACE.print("<N");
|
||||
INTERFACE.print(COMM_TYPE);
|
||||
INTERFACE.print(": ");
|
||||
|
||||
#if COMM_TYPE == 0
|
||||
INTERFACE.print("SERIAL>");
|
||||
#elif COMM_TYPE == 1
|
||||
INTERFACE.print(Ethernet.localIP());
|
||||
INTERFACE.print(">");
|
||||
#endif
|
||||
|
||||
Turnout::show();
|
||||
Output::show();
|
||||
|
||||
break;
|
||||
|
||||
/***** STORE SETTINGS IN EEPROM ****/
|
||||
|
||||
case 'E': // <E>
|
||||
/*
|
||||
* stores settings for turnouts and sensors EEPROM
|
||||
*
|
||||
* returns: <e nTurnouts nSensors>
|
||||
*/
|
||||
|
||||
EEStore::store();
|
||||
INTERFACE.print("<e ");
|
||||
INTERFACE.print(EEStore::eeStore->data.nTurnouts);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(EEStore::eeStore->data.nSensors);
|
||||
INTERFACE.print(" ");
|
||||
INTERFACE.print(EEStore::eeStore->data.nOutputs);
|
||||
INTERFACE.print(">");
|
||||
break;
|
||||
|
||||
/***** CLEAR SETTINGS IN EEPROM ****/
|
||||
|
||||
case 'e': // <e>
|
||||
/*
|
||||
* clears settings for Turnouts in EEPROM
|
||||
*
|
||||
* returns: <O>
|
||||
*/
|
||||
|
||||
EEStore::clear();
|
||||
INTERFACE.print("<O>");
|
||||
break;
|
||||
|
||||
/***** PRINT CARRIAGE RETURN IN SERIAL MONITOR WINDOW ****/
|
||||
|
||||
case ' ': // < >
|
||||
/*
|
||||
* simply prints a carriage return - useful when interacting with Ardiuno through serial monitor window
|
||||
*
|
||||
* returns: a carriage return
|
||||
*/
|
||||
INTERFACE.println("");
|
||||
break;
|
||||
|
||||
///
|
||||
/// THE FOLLOWING COMMANDS ARE NOT NEEDED FOR NORMAL OPERATIONS AND ARE ONLY USED FOR TESTING AND DEBUGGING PURPOSES
|
||||
/// PLEASE SEE SPECIFIC WARNINGS IN EACH COMMAND BELOW
|
||||
///
|
||||
|
||||
/***** ENTER DIAGNOSTIC MODE ****/
|
||||
|
||||
case 'D': // <D>
|
||||
/*
|
||||
* changes the clock speed of the chip and the pre-scaler for the timers so that you can visually see the DCC signals flickering with an LED
|
||||
* SERIAL COMMUNICAITON WILL BE INTERUPTED ONCE THIS COMMAND IS ISSUED - MUST RESET BOARD OR RE-OPEN SERIAL WINDOW TO RE-ESTABLISH COMMS
|
||||
*/
|
||||
|
||||
Serial.println("\nEntering Diagnostic Mode...");
|
||||
delay(1000);
|
||||
|
||||
bitClear(TCCR1B,CS12); // set Timer 1 prescale=8 - SLOWS NORMAL SPEED BY FACTOR OF 8
|
||||
bitSet(TCCR1B,CS11);
|
||||
bitClear(TCCR1B,CS10);
|
||||
|
||||
#ifdef ARDUINO_AVR_UNO // Configuration for UNO
|
||||
|
||||
bitSet(TCCR0B,CS02); // set Timer 0 prescale=256 - SLOWS NORMAL SPEED BY A FACTOR OF 4
|
||||
bitClear(TCCR0B,CS01);
|
||||
bitClear(TCCR0B,CS00);
|
||||
|
||||
#else // Configuration for MEGA
|
||||
|
||||
bitClear(TCCR3B,CS32); // set Timer 3 prescale=8 - SLOWS NORMAL SPEED BY A FACTOR OF 8
|
||||
bitSet(TCCR3B,CS31);
|
||||
bitClear(TCCR3B,CS30);
|
||||
|
||||
#endif
|
||||
|
||||
CLKPR=0x80; // THIS SLOWS DOWN SYSYEM CLOCK BY FACTOR OF 256
|
||||
CLKPR=0x08; // BOARD MUST BE RESET TO RESUME NORMAL OPERATIONS
|
||||
|
||||
break;
|
||||
|
||||
/***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/
|
||||
|
||||
case 'M': // <M REGISTER BYTE1 BYTE2 [BYTE3] [BYTE4] [BYTE5]>
|
||||
/*
|
||||
* writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the main operations track
|
||||
* FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER
|
||||
*
|
||||
* REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet
|
||||
* BYTE1: first hexidecimal byte in the packet
|
||||
* BYTE2: second hexidecimal byte in the packet
|
||||
* BYTE3: optional third hexidecimal byte in the packet
|
||||
* BYTE4: optional fourth hexidecimal byte in the packet
|
||||
* BYTE5: optional fifth hexidecimal byte in the packet
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
mRegs->writeTextPacket(com+1);
|
||||
break;
|
||||
|
||||
/***** WRITE A DCC PACKET TO ONE OF THE REGSITERS DRIVING THE MAIN OPERATIONS TRACK ****/
|
||||
|
||||
case 'P': // <P REGISTER BYTE1 BYTE2 [BYTE3] [BYTE4] [BYTE5]>
|
||||
/*
|
||||
* writes a DCC packet of two, three, four, or five hexidecimal bytes to a register driving the programming track
|
||||
* FOR DEBUGGING AND TESTING PURPOSES ONLY. DO NOT USE UNLESS YOU KNOW HOW TO CONSTRUCT NMRA DCC PACKETS - YOU CAN INADVERTENTLY RE-PROGRAM YOUR ENGINE DECODER
|
||||
*
|
||||
* REGISTER: an internal register number, from 0 through MAX_MAIN_REGISTERS (inclusive), to write (if REGISTER=0) or write and store (if REGISTER>0) the packet
|
||||
* BYTE1: first hexidecimal byte in the packet
|
||||
* BYTE2: second hexidecimal byte in the packet
|
||||
* BYTE3: optional third hexidecimal byte in the packet
|
||||
* BYTE4: optional fourth hexidecimal byte in the packet
|
||||
* BYTE5: optional fifth hexidecimal byte in the packet
|
||||
*
|
||||
* returns: NONE
|
||||
*/
|
||||
pRegs->writeTextPacket(com+1);
|
||||
break;
|
||||
|
||||
/***** ATTEMPTS TO DETERMINE HOW MUCH FREE SRAM IS AVAILABLE IN ARDUINO ****/
|
||||
|
||||
case 'F': // <F>
|
||||
/*
|
||||
* measure amount of free SRAM memory left on the Arduino based on trick found on the internet.
|
||||
* Useful when setting dynamic array sizes, considering the Uno only has 2048 bytes of dynamic SRAM.
|
||||
* Unfortunately not very reliable --- would be great to find a better method
|
||||
*
|
||||
* returns: <f MEM>
|
||||
* where MEM is the number of free bytes remaining in the Arduino's SRAM
|
||||
*/
|
||||
int v;
|
||||
INTERFACE.print("<f");
|
||||
INTERFACE.print((int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval));
|
||||
INTERFACE.print(">");
|
||||
break;
|
||||
|
||||
/***** LISTS BIT CONTENTS OF ALL INTERNAL DCC PACKET REGISTERS ****/
|
||||
|
||||
case 'L': // <L>
|
||||
/*
|
||||
* lists the packet contents of the main operations track registers and the programming track registers
|
||||
* FOR DIAGNOSTIC AND TESTING USE ONLY
|
||||
*/
|
||||
INTERFACE.println("");
|
||||
for(Register *p=mRegs->reg;p<=mRegs->maxLoadedReg;p++){
|
||||
INTERFACE.print("M"); INTERFACE.print((int)(p-mRegs->reg)); INTERFACE.print(":\t");
|
||||
INTERFACE.print((int)p); INTERFACE.print("\t");
|
||||
INTERFACE.print((int)p->activePacket); INTERFACE.print("\t");
|
||||
INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t");
|
||||
for(int i=0;i<10;i++){
|
||||
INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t");
|
||||
}
|
||||
INTERFACE.println("");
|
||||
}
|
||||
for(Register *p=pRegs->reg;p<=pRegs->maxLoadedReg;p++){
|
||||
INTERFACE.print("P"); INTERFACE.print((int)(p-pRegs->reg)); INTERFACE.print(":\t");
|
||||
INTERFACE.print((int)p); INTERFACE.print("\t");
|
||||
INTERFACE.print((int)p->activePacket); INTERFACE.print("\t");
|
||||
INTERFACE.print(p->activePacket->nBits); INTERFACE.print("\t");
|
||||
for(int i=0;i<10;i++){
|
||||
INTERFACE.print(p->activePacket->buf[i],HEX); INTERFACE.print("\t");
|
||||
}
|
||||
INTERFACE.println("");
|
||||
}
|
||||
INTERFACE.println("");
|
||||
break;
|
||||
|
||||
} // switch
|
||||
}; // SerialCommand::parse
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
31
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.h
Normal file
31
DCC-Centrale/Firmware/BaseStation/DCCpp_Uno/SerialCommand.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/**********************************************************************
|
||||
|
||||
SerialCommand.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
|
||||
Part of DCC++ BASE STATION for the Arduino
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef SerialCommand_h
|
||||
#define SerialCommand_h
|
||||
|
||||
#include "PacketRegister.h"
|
||||
#include "CurrentMonitor.h"
|
||||
|
||||
#define MAX_COMMAND_LENGTH 30
|
||||
|
||||
struct SerialCommand{
|
||||
static char commandString[MAX_COMMAND_LENGTH+1];
|
||||
static volatile RegisterList *mRegs, *pRegs;
|
||||
static CurrentMonitor *mMonitor;
|
||||
static void init(volatile RegisterList *, volatile RegisterList *, CurrentMonitor *);
|
||||
static void parse(char *);
|
||||
static void process();
|
||||
}; // SerialCommand
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user