Corrected bug: ESP32 version stops working when loosing interupts or signal is bad (#48) bumped version to 2.0.7
487 lines
17 KiB
C++
Executable File
487 lines
17 KiB
C++
Executable File
/***********************************************************************************************
|
|
*
|
|
* This sketch tests the NmraDcc library as a multifunction decoder.
|
|
* Author: Kenneth West
|
|
* kgw4449@gmail.com
|
|
*
|
|
* This sketch has added the printf() function to the Print class.
|
|
* You can find instructions for doing this here:
|
|
* http://playground.arduino.cc/Main/Printf
|
|
*
|
|
* This sketch is based on NmraDcc library NmraDccMultiFunctionDecoder_1 example.
|
|
* You can find the library here:
|
|
* https://github.com/mrrwa/NmraDcc
|
|
*
|
|
* This Example shows how to use the library with the Iowa Scaled Engineering ARD-DCCSHIELD
|
|
* You can find out more about this DCC Interface here:
|
|
* http://www.iascaled.com/store/ARD-DCCSHIELD
|
|
*
|
|
* For more information refer to the file: README.md here:
|
|
* https://github.com/IowaScaledEngineering/ard-dccshield
|
|
*
|
|
* This demo assumes the following Jumper settings on the ARD-DCCSHIELD
|
|
*
|
|
* JP1 - I2C Pull-Up Resistors - Don't Care
|
|
* JP2 - (Pins 1-2) I2C /IORST JP2 - Don't-Care
|
|
* JP2 - (Pins 3-4) - DCC Signal to Arduino Pin - OFF
|
|
* JP3 - I2C /INT and /OE - Don't-Care
|
|
* JP4 - DCC Signal to Arduino Pin - D2 ON
|
|
* JP5 - Arduino Powered from DCC - User Choice
|
|
* JP6 - Boards without VIO - User Choice
|
|
* JP7 - Enable Programming ACK - 1-2 ON 3-4 ON
|
|
*
|
|
* The connections are as follows:
|
|
*
|
|
* Pin Name Mode Description
|
|
* ----------------------------------------------------------------------------------
|
|
* D2 DCC_PIN INPUT_PULLUP DCC input signal.
|
|
* A1 ACK_PIN OUTPUT CV acknowledge control.
|
|
* A0 MOTOR_A_PIN OUTPUT Motor output A.
|
|
* D13 MOTOR_B_PIN OUTPUT Motor output B.
|
|
* D12 FUNC_A_PIN OUTPUT Function output A.
|
|
* D11 FUNC_B_PIN OUTPUT Function output B.
|
|
* D4 SCOPE_PIN OUTPUT SCOPE trigger.
|
|
*
|
|
**********************************************************************************************/
|
|
// Column locations.
|
|
//3456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456
|
|
// 1 2 3 4 5 6 7 8 9 9
|
|
//const unsigned long FAKE_LONG = 10000UL; // Fake variable for column positions.
|
|
|
|
#include <NmraDcc.h>
|
|
|
|
// Uncomment this line to print out minimal status information.
|
|
#define DCC_STATUS
|
|
|
|
// Uncomment this line to issue scope trigger at beginning of motor callback.
|
|
#define DO_SCOPE
|
|
|
|
const byte VER_MAJOR = 2; // Major version in CV 7
|
|
const byte VER_MINOR = 1; // Minor version in CV 112
|
|
const byte DCC_PIN = 2; // DCC input pin.
|
|
const int ACK_PIN = A1; // CV acknowledge pin.
|
|
const byte MOTOR_A_PIN = A0; // Motor out A pin.
|
|
const byte MOTOR_B_PIN = 13; // Motor out B pin.
|
|
const byte FUNC_A_PIN = 12; // Function A pin.
|
|
const byte FUNC_B_PIN = 11; // Function B pin.
|
|
const byte SCOPE_PIN = 4; // Scope trigger pin.
|
|
const byte CV_VERSION = 7; // Decoder version.
|
|
const byte CV_MANUF = 8; // Manufacturer ID.
|
|
const byte CV_MANUF_01 = 112; // Manufacturer Unique 01.
|
|
const byte MANUF_ID = MAN_ID_DIY; // Manufacturer ID in CV 8.
|
|
const byte DECODER_ADDR = 3; // Decoder address.
|
|
const byte SP_ESTOP = 0; // Emergency stop speed.
|
|
const byte SP_STOP = 1; // Stop speed value.
|
|
const unsigned long DELAY_TIME = 50; // Delay time in ms.
|
|
|
|
// Note: Set FORCE_CV_WRITE true to force CVs to update if just the address is changed.
|
|
const bool FORCE_CV_WRITE = false; // Set true to force CV write.
|
|
|
|
enum ShowTypes {
|
|
S_IDLE = 0x01, // Show Idle packets.
|
|
S_RESET = 0x02, // Show Reset packets.
|
|
S_DCC = 0x04, // Show DCC information.
|
|
S_ACK = 0x08, // Basic acknowledge off.
|
|
S_ALL = 0x80, // Show raw packet data.
|
|
};
|
|
|
|
bool MotorFwd = false; // Motor direction.
|
|
bool FuncOn = true; // Function on/off.
|
|
byte ShowData = 0x00; // Packet information to show.
|
|
unsigned long EndTime = 0; // End time in ms.
|
|
|
|
struct CVPair
|
|
{
|
|
uint16_t CV;
|
|
uint8_t Value;
|
|
};
|
|
|
|
CVPair FactoryDefaultCVs [] =
|
|
{
|
|
// The CV Below defines the Short DCC Address
|
|
{CV_MULTIFUNCTION_PRIMARY_ADDRESS, DECODER_ADDR}, // Short address.
|
|
|
|
// Reload these just in case they are writeen by accident.
|
|
{CV_VERSION, VER_MAJOR}, // Decoder version.
|
|
{CV_MANUF, MANUF_ID }, // Manufacturer ID.
|
|
|
|
// These two CVs define the Long DCC Address
|
|
{CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB, CALC_MULTIFUNCTION_EXTENDED_ADDRESS_MSB(DECODER_ADDR)},
|
|
{CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB, CALC_MULTIFUNCTION_EXTENDED_ADDRESS_LSB(DECODER_ADDR)},
|
|
|
|
{CV_29_CONFIG, CV29_F0_LOCATION}, // Short Address 28/128 Speed Steps
|
|
{CV_MANUF_01, VER_MINOR}, // Minor decoder version.
|
|
};
|
|
|
|
NmraDcc Dcc ;
|
|
|
|
uint8_t FactoryDefaultCVIndex = 0;
|
|
|
|
void notifyCVResetFactoryDefault()
|
|
{
|
|
// Make FactoryDefaultCVIndex non-zero and equal to num CV's to be reset
|
|
// to flag to the loop() function that a reset to Factory Defaults needs to be done
|
|
Serial.println(F("notifyCVResetFactoryDefault called."));
|
|
FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs)/sizeof(CVPair);
|
|
};
|
|
|
|
// Uncomment the #define below to print all Speed Packets
|
|
#define NOTIFY_DCC_SPEED
|
|
#ifdef NOTIFY_DCC_SPEED
|
|
void notifyDccSpeed( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps )
|
|
{
|
|
#ifdef DO_SCOPE
|
|
digitalWrite(SCOPE_PIN, HIGH);
|
|
digitalWrite(SCOPE_PIN, LOW);
|
|
#endif // DO_SCOPE
|
|
|
|
if (ShowData & S_DCC) {
|
|
Serial.print("notifyDccSpeed: Addr: ");
|
|
Serial.print(Addr,DEC);
|
|
Serial.print( (AddrType == DCC_ADDR_SHORT) ? "-S" : "-L" );
|
|
Serial.print(" Speed: ");
|
|
Serial.print(Speed,DEC);
|
|
Serial.print(" Steps: ");
|
|
Serial.print(SpeedSteps,DEC);
|
|
Serial.print(" Dir: ");
|
|
Serial.println( (Dir == DCC_DIR_FWD) ? "Forward" : "Reverse" );
|
|
}
|
|
|
|
if ((Speed > SP_STOP) && (Dir == DCC_DIR_REV)) {
|
|
setMotor(false);
|
|
}
|
|
else {
|
|
setMotor(true);
|
|
}
|
|
};
|
|
#endif // NOTIFY_DCC_SPEED
|
|
|
|
// Uncomment the #define below to print all Function Packets
|
|
#define NOTIFY_DCC_FUNC
|
|
#ifdef NOTIFY_DCC_FUNC
|
|
void notifyDccFunc(uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState)
|
|
{
|
|
if (ShowData & S_DCC) {
|
|
Serial.print("notifyDccFunc: Addr: ");
|
|
Serial.print(Addr,DEC);
|
|
Serial.print( (AddrType == DCC_ADDR_SHORT) ? 'S' : 'L' );
|
|
Serial.print(" Function Group: ");
|
|
Serial.print(FuncGrp,DEC);
|
|
}
|
|
|
|
switch( FuncGrp ) {
|
|
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
|
|
case FN_0:
|
|
if (ShowData & S_DCC) {
|
|
Serial.print(" FN0: ");
|
|
Serial.println((FuncState & FN_BIT_00) ? "1 " : "0 ");
|
|
}
|
|
break;
|
|
#endif // NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
|
|
|
|
case FN_0_4:
|
|
if (ShowData & S_DCC) {
|
|
Serial.print(" FN 0: ");
|
|
Serial.print((FuncState & FN_BIT_00) ? "1 ": "0 ");
|
|
Serial.print(" FN 1-4: ");
|
|
Serial.print((FuncState & FN_BIT_01) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_02) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_03) ? "1 ": "0 ");
|
|
Serial.println((FuncState & FN_BIT_04) ? "1 ": "0 ");
|
|
}
|
|
|
|
if (FuncState & FN_BIT_00) {
|
|
setFunc(true);
|
|
}
|
|
else {
|
|
setFunc(false);
|
|
}
|
|
break;
|
|
|
|
case FN_5_8:
|
|
if (ShowData & S_DCC) {
|
|
Serial.print(" FN 5-8: ");
|
|
Serial.print((FuncState & FN_BIT_05) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_06) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_07) ? "1 ": "0 ");
|
|
Serial.println((FuncState & FN_BIT_08) ? "1 ": "0 ");
|
|
}
|
|
break;
|
|
|
|
case FN_9_12:
|
|
if (ShowData & S_DCC) {
|
|
Serial.print(" FN 9-12: ");
|
|
Serial.print((FuncState & FN_BIT_09) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_10) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_11) ? "1 ": "0 ");
|
|
Serial.println((FuncState & FN_BIT_12) ? "1 ": "0 ");
|
|
}
|
|
break;
|
|
|
|
case FN_13_20:
|
|
if (ShowData & S_DCC) {
|
|
Serial.print(" FN 13-20: ");
|
|
Serial.print((FuncState & FN_BIT_13) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_14) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_15) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_16) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_17) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_18) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_19) ? "1 ": "0 ");
|
|
Serial.println((FuncState & FN_BIT_20) ? "1 ": "0 ");
|
|
}
|
|
break;
|
|
|
|
case FN_21_28:
|
|
if (ShowData & S_DCC) {
|
|
Serial.print(" FN 21-28: ");
|
|
Serial.print((FuncState & FN_BIT_21) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_22) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_23) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_24) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_25) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_26) ? "1 ": "0 ");
|
|
Serial.print((FuncState & FN_BIT_27) ? "1 ": "0 ");
|
|
Serial.println((FuncState & FN_BIT_28) ? "1 ": "0 ");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
#endif // NOTIFY_DCC_FUNC
|
|
|
|
// Uncomment the #define below to print all Reset Packets.
|
|
#define NOTIFY_DCC_RESET
|
|
#ifdef NOTIFY_DCC_RESET
|
|
void notifyDccReset(uint8_t hardReset) {
|
|
setMotor(true);
|
|
setFunc(false);
|
|
if (ShowData & S_RESET) {
|
|
Serial.printf(F("notifyDccReset: %6s.\n"), hardReset ? "HARD" : "NORMAL");
|
|
}
|
|
}
|
|
#endif // NOTIFY_DCC_RESET
|
|
|
|
// This function is called whenever a DCC Idle packet is received.
|
|
// Uncomment to print Idle Packets
|
|
#define NOTIFY_DCC_IDLE
|
|
#ifdef NOTIFY_DCC_IDLE
|
|
void notifyDccIdle()
|
|
{
|
|
if (ShowData & S_IDLE) { // Show Idle packets if S_IDLE is set.
|
|
Serial.println("notifyDccIdle: Idle received") ;
|
|
}
|
|
}
|
|
#endif // NOTIFY_DCC_IDLE
|
|
|
|
// Uncomment the #define below to print changed CV values.
|
|
#define NOTIFY_CV_CHANGE
|
|
#ifdef NOTIFY_CV_CHANGE
|
|
void notifyCVChange( uint16_t CV, uint8_t Value) {
|
|
Serial.printf(F("notifyCVChange: CV %4u value changed to %3u 0x%02X.\n"), CV, Value, Value);
|
|
}
|
|
#endif // NOTIFY_CV_CHANGE
|
|
|
|
// This function is called when any DCC packet is received.
|
|
// Uncomment to print all DCC Packets
|
|
#define NOTIFY_DCC_MSG
|
|
#ifdef NOTIFY_DCC_MSG
|
|
void notifyDccMsg( DCC_MSG * Msg)
|
|
{
|
|
if (ShowData & S_ALL) { // Show all packets if S_ALL is set.
|
|
Serial.print("notifyDccMsg: ") ;
|
|
for(uint8_t i = 0; i < Msg->Size; i++)
|
|
{
|
|
Serial.print(Msg->Data[i], HEX);
|
|
Serial.write(' ');
|
|
}
|
|
Serial.println();
|
|
}
|
|
}
|
|
#endif // NOTIFY_DCC_MSG
|
|
|
|
// This function is called by the NmraDcc library when a DCC ACK needs to be sent.
|
|
// Calling this function should cause an increased 60ma current drain on
|
|
// the power supply for 6ms to ACK a CV Read
|
|
void notifyCVAck(void)
|
|
{
|
|
#ifdef DO_SCOPE
|
|
digitalWrite(SCOPE_PIN, HIGH);
|
|
digitalWrite(SCOPE_PIN, LOW);
|
|
#endif // DO_SCOPE
|
|
|
|
if ((ShowData & S_ACK) == 0x00) { // Send CV acknowledge current pulse. [
|
|
Serial.println("notifyCVAck: Current pulse sent") ;
|
|
|
|
digitalWrite( ACK_PIN, HIGH );
|
|
delay( 6 );
|
|
digitalWrite( ACK_PIN, LOW );
|
|
}
|
|
else { // Suppress CV acknowledge current pulse.
|
|
Serial.println("notifyCVAck: Current pulse NOT sent") ;
|
|
}
|
|
}
|
|
|
|
void setMotor(bool fwd) {
|
|
#ifdef DCC_STATUS
|
|
if (MotorFwd != fwd) {
|
|
Serial.printf(F("Motor changed to %3s.\n"), fwd ? "FWD" : "REV");
|
|
}
|
|
#endif // DCC_STATUS
|
|
MotorFwd = fwd;
|
|
digitalWrite(MOTOR_A_PIN, fwd);
|
|
digitalWrite(MOTOR_B_PIN, !fwd);
|
|
}
|
|
|
|
void setFunc(bool on) {
|
|
#ifdef DCC_STATUS
|
|
if (FuncOn != on) {
|
|
Serial.printf(F("Function changed to %3s.\n"), on ? "ON" : "OFF");
|
|
}
|
|
#endif // DCC_STATUS
|
|
FuncOn = on;
|
|
digitalWrite(FUNC_A_PIN, !on);
|
|
digitalWrite(FUNC_B_PIN, on);
|
|
}
|
|
|
|
void setup()
|
|
{
|
|
Serial.begin(115200);
|
|
Serial.print(F("NMRA Dcc Loco_Test "));
|
|
Serial.printf(F(" Version %d.%d, Build date %s %s\n"), VER_MAJOR,
|
|
VER_MINOR,
|
|
__DATE__,
|
|
__TIME__);
|
|
Serial.println(F("Cmds: a - All, d - DCC, i - Idle, r - Reset, c - CV Ack off,"));
|
|
Serial.println(F(" <Other> - Everything off."));
|
|
|
|
// Set MotorFwd false and FuncOn true to force the set the output.
|
|
MotorFwd = false;
|
|
FuncOn = true;
|
|
|
|
// Configure motor and fuction output pin pairs.
|
|
pinMode( MOTOR_A_PIN, OUTPUT);
|
|
pinMode( MOTOR_B_PIN, OUTPUT);
|
|
setMotor(true);
|
|
pinMode( FUNC_A_PIN, OUTPUT);
|
|
pinMode( FUNC_B_PIN, OUTPUT);
|
|
setFunc(false);
|
|
|
|
// Configure Scope trigger output.
|
|
#ifdef DO_SCOPE
|
|
pinMode( SCOPE_PIN, OUTPUT);
|
|
digitalWrite(SCOPE_PIN, LOW);
|
|
#endif // DO_SCOPE
|
|
|
|
// Configure the DCC CV Programing ACK and set it LOW to keep the ACK current off.
|
|
pinMode( ACK_PIN, OUTPUT );
|
|
digitalWrite(ACK_PIN, LOW );
|
|
|
|
// Setup which External Interrupt, the Pin it's associated with that we're using
|
|
// and enable the Pull-Up.
|
|
Dcc.pin(digitalPinToInterrupt(DCC_PIN), DCC_PIN, 1);
|
|
|
|
// Reset the CVs to factory default if the manuf. ID or major version do not match.
|
|
// Do this before init() since it sets these CVs.
|
|
if ( ( FORCE_CV_WRITE) ||
|
|
((Dcc.getCV(CV_29_CONFIG) & CV29_ACCESSORY_DECODER) != 0) ||
|
|
( Dcc.getCV(CV_MANUFACTURER_ID) != MANUF_ID) ||
|
|
( Dcc.getCV(CV_VERSION_ID) != VER_MAJOR) ||
|
|
( Dcc.getCV(CV_MANUF_01) != VER_MINOR))
|
|
{
|
|
notifyCVResetFactoryDefault();
|
|
}
|
|
|
|
Dcc.init( MANUF_ID, VER_MAJOR, FLAGS_MY_ADDRESS_ONLY, 0 );
|
|
|
|
// Make sure CV_MANUF_01 CV matches VER_MINOR.
|
|
Dcc.setCV(CV_MANUF_01, VER_MINOR);
|
|
|
|
Serial.println(F("Init Done"));
|
|
|
|
// Flush serial prior to entering loop().
|
|
Serial.flush();
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
// You MUST call the NmraDcc.process() method frequently from the Arduino loop() function for correct library operation
|
|
Dcc.process();
|
|
|
|
if (Serial.available()) {
|
|
// Get the new byte and process it.
|
|
switch ((char)Serial.read()) {
|
|
case 'a':
|
|
if (ShowData & S_ALL) {
|
|
ShowData &= ~S_ALL;
|
|
}
|
|
else {
|
|
ShowData |= S_ALL;
|
|
}
|
|
Serial.println(ShowData & S_ALL ? "All ON" : "All OFF");
|
|
break;
|
|
case 'd':
|
|
if (ShowData & S_DCC) {
|
|
ShowData &= ~S_DCC;
|
|
}
|
|
else {
|
|
ShowData |= S_DCC;
|
|
}
|
|
Serial.println(ShowData & S_DCC ? "DCC ON" : "DCC OFF");
|
|
break;
|
|
case 'i':
|
|
if (ShowData & S_IDLE) {
|
|
ShowData &= ~S_IDLE;
|
|
}
|
|
else {
|
|
ShowData |= S_IDLE;
|
|
}
|
|
Serial.println(ShowData & S_IDLE ? "Idle ON" : "Idle OFF");
|
|
break;
|
|
case 'r':
|
|
if (ShowData & S_RESET) {
|
|
ShowData &= ~S_RESET;
|
|
}
|
|
else {
|
|
ShowData |= S_RESET;
|
|
}
|
|
Serial.println(ShowData & S_RESET ? "Reset ON" : "Reset OFF");
|
|
break;
|
|
case 'c':
|
|
if (ShowData & S_ACK) {
|
|
ShowData &= ~S_ACK;
|
|
}
|
|
else {
|
|
ShowData |= S_ACK;
|
|
}
|
|
Serial.println(ShowData & S_ACK ? "Ack OFF" : "Ack ON");
|
|
break;
|
|
case '\n':
|
|
case '\r':
|
|
break;
|
|
default:
|
|
if (ShowData != 0x00) {
|
|
EndTime = millis() + DELAY_TIME;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((EndTime != 0) && (millis() > EndTime)) {
|
|
Serial.printf(F("Clearing ShowData 0x%02x\n"), ShowData);
|
|
ShowData = 0x00;
|
|
EndTime = 0;
|
|
}
|
|
|
|
if( FactoryDefaultCVIndex && Dcc.isSetCVReady())
|
|
{
|
|
FactoryDefaultCVIndex--; // Decrement first as initially it is the size of the array
|
|
Serial.printf(F("CV %4u reset to factory default %3u 0x%02X.\n"),
|
|
FactoryDefaultCVs[FactoryDefaultCVIndex].CV,
|
|
FactoryDefaultCVs[FactoryDefaultCVIndex].Value,
|
|
FactoryDefaultCVs[FactoryDefaultCVIndex].Value);
|
|
Dcc.setCV( FactoryDefaultCVs[FactoryDefaultCVIndex].CV,
|
|
FactoryDefaultCVs[FactoryDefaultCVIndex].Value);
|
|
}
|
|
}
|
|
|