diff --git a/NmraDcc.cpp b/NmraDcc.cpp index e8155c4..e1a93a9 100644 --- a/NmraDcc.cpp +++ b/NmraDcc.cpp @@ -686,8 +686,17 @@ void execDccProcessor( DCC_MSG * pDccMsg ) if(pDccMsg->Data[1] & 0b10000000) { + uint8_t direction = OutputAddress & 0x01; + uint8_t outputPower = (pDccMsg->Data[1] & 0b00001000) >> 3; + if( notifyDccAccState ) - notifyDccAccState( Address, BoardAddress, OutputAddress, pDccMsg->Data[1] & 0b00001000 ) ; + notifyDccAccState( Address, BoardAddress, OutputAddress, outputPower ) ; + + if( notifyDccAccTurnoutBoard ) + notifyDccAccTurnoutBoard( BoardAddress, OutputIndex, direction, outputPower ); + + if( notifyDccAccTurnoutOutput ) + notifyDccAccTurnoutOutput( Address, direction, outputPower ); } else diff --git a/NmraDcc.h b/NmraDcc.h index eaa0b17..40fae91 100644 --- a/NmraDcc.h +++ b/NmraDcc.h @@ -213,6 +213,8 @@ extern void notifyDccSpeedRaw( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Ra extern void notifyDccFunc( uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState) __attribute__ ((weak)); extern void notifyDccAccState( uint16_t Addr, uint16_t BoardAddr, uint8_t OutputAddr, uint8_t State ) __attribute__ ((weak)); +extern void notifyDccAccTurnoutBoard( uint16_t BoardAddr, uint8_t OutputPair, uint8_t Direction, uint8_t OutputPower ) __attribute__ ((weak)); +extern void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) __attribute__ ((weak)); extern void notifyDccSigState( uint16_t Addr, uint8_t OutputIndex, uint8_t State) __attribute__ ((weak)); diff --git a/examples/NmraDccAccessoryDecoder_1/NmraDccAccessoryDecoder_1.ino b/examples/NmraDccAccessoryDecoder_1/NmraDccAccessoryDecoder_1.ino index 56cecf3..c6ed4d1 100644 --- a/examples/NmraDccAccessoryDecoder_1/NmraDccAccessoryDecoder_1.ino +++ b/examples/NmraDccAccessoryDecoder_1/NmraDccAccessoryDecoder_1.ino @@ -69,6 +69,30 @@ void notifyDccAccState( uint16_t Addr, uint16_t BoardAddr, uint8_t OutputAddr, u Serial.println(State, HEX) ; } +// This function is called whenever a normal DCC Turnout Packet is received +void notifyDccAccTurnoutBoard( uint16_t BoardAddr, uint8_t OutputPair, uint8_t Direction, uint8_t OutputPower ) +{ + Serial.print("notifyDccAccTurnoutBoard: ") ; + Serial.print(BoardAddr,DEC) ; + Serial.print(','); + Serial.print(OutputPair,DEC) ; + Serial.print(','); + Serial.print(Direction,DEC) ; + Serial.print(','); + Serial.println(OutputPower, HEX) ; +} + +// This function is called whenever a normal DCC Turnout Packet is received +void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) +{ + Serial.print("notifyDccAccTurnoutOutput: ") ; + Serial.print(Addr,DEC) ; + Serial.print(','); + Serial.print(Direction,DEC) ; + Serial.print(','); + Serial.println(OutputPower, HEX) ; +} + // This function is called whenever a DCC Signal Aspect Packet is received void notifyDccSigState( uint16_t Addr, uint8_t OutputIndex, uint8_t State) { diff --git a/examples/NmraDccAccessoryDecoder_Pulsed_8/NmraDccAccessoryDecoder_Pulsed_8.ino b/examples/NmraDccAccessoryDecoder_Pulsed_8/NmraDccAccessoryDecoder_Pulsed_8.ino new file mode 100644 index 0000000..c1ff657 --- /dev/null +++ b/examples/NmraDccAccessoryDecoder_Pulsed_8/NmraDccAccessoryDecoder_Pulsed_8.ino @@ -0,0 +1,194 @@ +#include +#include "PinPulser.h" +// This Example shows how to use the library as a DCC Accessory Decoder to drive 8 Pulsed Turnouts + +// You can print every DCC packet by un-commenting the line below +//#define NOTIFY_DCC_MSG + +// You can print every notifyDccAccTurnoutOutput call-back by un-commenting the line below +#define NOTIFY_TURNOUT_MSG + +// You can also print other Debug Messages uncommenting the line below +#define DEBUG_MSG + +NmraDcc Dcc ; +DCC_MSG Packet ; + +struct CVPair +{ + uint16_t CV; + uint8_t Value; +}; + +#define CV_ACCESSORY_DECODER_OUTPUT_PULSE_TIME 2 // CV for the Output Pulse ON ms +#define CV_ACCESSORY_DECODER_CDU_RECHARGE_TIME 3 // CV for the delay in ms to allow a CDU to recharge +#define CV_ACCESSORY_DECODER_ACTIVE_STATE 4 // CV to define the ON Output State + +#define NUM_TURNOUTS 8 // Number of Turnouts + +CVPair FactoryDefaultCVs [] = +{ + {CV_ACCESSORY_DECODER_ADDRESS_LSB, 1}, + {CV_ACCESSORY_DECODER_ADDRESS_MSB, 0}, + {CV_ACCESSORY_DECODER_OUTPUT_PULSE_TIME, 50}, // x 10mS for the output pulse duration + {CV_ACCESSORY_DECODER_CDU_RECHARGE_TIME, 30}, // x 10mS for the CDU recharge delay time + {CV_ACCESSORY_DECODER_ACTIVE_STATE, HIGH}, +}; + +uint8_t FactoryDefaultCVIndex = 0; + +// Un-Comment the line below to force CVs to be written to the Factory Default values defined above +//FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs); + +// This is the Arduino Pin Mapping to Turnout Addresses with 2 pins per turnout +// base address 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 +byte outputs[] = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; +// pins D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 A0 A1 A2 A4 A5 + +PinPulser pinPulser; + +// To enable DCC CV Read capability with a DCC Service Mode CV Programmer un-comment the line below +//#define ENABLE_DCC_ACK +#ifdef ENABLE_DCC_ACK +const int DccAckPin = 3 ; +#endif + +uint16_t BaseTurnoutAddress; // + +// This function is called whenever a normal DCC Turnout Packet is received +void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) +{ +#ifdef NOTIFY_TURNOUT_MSG + Serial.print("notifyDccAccTurnoutOutput: Turnout: ") ; + Serial.print(Addr,DEC) ; + Serial.print(" Direction: "); + Serial.print(Direction ? "Closed" : "Thrown") ; + Serial.print(" Output: "); + Serial.print(OutputPower ? "On" : "Off") ; +#endif + if(( Addr >= BaseTurnoutAddress ) && ( Addr < (BaseTurnoutAddress + NUM_TURNOUTS )) && OutputPower ) + { + uint16_t pinIndex = ( (Addr - BaseTurnoutAddress) << 1 ) + Direction ; + pinPulser.addPin(outputs[pinIndex]); +#ifdef NOTIFY_TURNOUT_MSG + Serial.print(" Pin Index: "); + Serial.print(pinIndex,DEC); + Serial.print(" Pin: "); + Serial.print(outputs[pinIndex],DEC); +#endif + } +#ifdef NOTIFY_TURNOUT_MSG + Serial.println(); +#endif +} + +void setup() +{ + Serial.begin(115200); + + // Configure the DCC CV Programing ACK pin for an output +#ifdef ENABLE_DCC_ACK + pinMode( DccAckPin, OUTPUT ); +#endif + + // Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up + Dcc.pin(0, 2, 1); + + // Call the main DCC Init function to enable the DCC Receiver + Dcc.init( MAN_ID_DIY, 10, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0 ); + + BaseTurnoutAddress = (Dcc.getCV(CV_ACCESSORY_DECODER_ADDRESS_MSB) * 4) + Dcc.getCV(CV_ACCESSORY_DECODER_ADDRESS_LSB) ; +#ifdef DEBUG_MSG + Serial.println("NMRA DCC 8-Turnout Accessory Decoder"); + Serial.print("DCC Turnout Base Address: "); + Serial.println(BaseTurnoutAddress, DEC); +#endif + + for(uint8_t i = 0; i < (NUM_TURNOUTS * 2); i++) + pinMode( outputs[i], OUTPUT ); + + uint16_t onMs = Dcc.getCV(CV_ACCESSORY_DECODER_OUTPUT_PULSE_TIME) * 10; + uint16_t cduRechargeMs = Dcc.getCV(CV_ACCESSORY_DECODER_CDU_RECHARGE_TIME) * 10; + uint8_t activeOutputState = Dcc.getCV(CV_ACCESSORY_DECODER_ACTIVE_STATE); + + pinPulser.init(onMs, cduRechargeMs, activeOutputState); + +#ifdef DEBUG_MSG + Serial.println("Init Done"); +#endif +} + +void loop() +{ + // You MUST call the NmraDcc.process() method frequently from the Arduino loop() function for correct library operation + Dcc.process(); + + pinPulser.process(); + + if( FactoryDefaultCVIndex && Dcc.isSetCVReady()) + { + FactoryDefaultCVIndex--; // Decrement first as initially it is the size of the array + Dcc.setCV( FactoryDefaultCVs[FactoryDefaultCVIndex].CV, FactoryDefaultCVs[FactoryDefaultCVIndex].Value); + } +} + +void notifyCVChange(uint16_t CV, uint8_t Value) +{ +#ifdef DEBUG_MSG + Serial.print("notifyCVChange: CV: ") ; + Serial.print(CV,DEC) ; + Serial.print(" Value: ") ; + Serial.println(Value, DEC) ; +#endif + + Value = Value; // Silence Compiler Warnings... + if((CV == CV_ACCESSORY_DECODER_ADDRESS_MSB) || (CV == CV_ACCESSORY_DECODER_ADDRESS_LSB)) + { + BaseTurnoutAddress = (Dcc.getCV(CV_ACCESSORY_DECODER_ADDRESS_MSB) * 4) + Dcc.getCV(CV_ACCESSORY_DECODER_ADDRESS_LSB) ; + return; + } + + if((CV == CV_ACCESSORY_DECODER_OUTPUT_PULSE_TIME) || (CV == CV_ACCESSORY_DECODER_CDU_RECHARGE_TIME) || (CV == CV_ACCESSORY_DECODER_CDU_RECHARGE_TIME)) + { + uint16_t onMs = Dcc.getCV(CV_ACCESSORY_DECODER_OUTPUT_PULSE_TIME) * 10; + uint16_t cduRechargeMs = Dcc.getCV(CV_ACCESSORY_DECODER_CDU_RECHARGE_TIME) * 10; + uint8_t activeOutputState = Dcc.getCV(CV_ACCESSORY_DECODER_ACTIVE_STATE); + + pinPulser.init(onMs, cduRechargeMs, activeOutputState); + } +} + +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 + FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs)/sizeof(CVPair); +}; + +// 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 +#ifdef ENABLE_DCC_ACK +void notifyCVAck(void) +{ +#ifdef DEBUG_MSG + Serial.println("notifyCVAck") ; +#endif + + digitalWrite( DccAckPin, HIGH ); + delay( 6 ); + digitalWrite( DccAckPin, LOW ); +} +#endif + +#ifdef NOTIFY_DCC_MSG +void notifyDccMsg( DCC_MSG * Msg) +{ + Serial.print("notifyDccMsg: ") ; + for(uint8_t i = 0; i < Msg->Size; i++) + { + Serial.print(Msg->Data[i], HEX); + Serial.write(' '); + } + Serial.println(); +} +#endif diff --git a/examples/NmraDccAccessoryDecoder_Pulsed_8/PinPulser.cpp b/examples/NmraDccAccessoryDecoder_Pulsed_8/PinPulser.cpp new file mode 100644 index 0000000..222e2da --- /dev/null +++ b/examples/NmraDccAccessoryDecoder_Pulsed_8/PinPulser.cpp @@ -0,0 +1,92 @@ +#include "PinPulser.h" + +#define PIN_PULSER_SLOT_EMPTY 255 + +void PinPulser::init(uint16_t onMs, uint16_t cduRechargeMs, uint8_t activeOutputState) +{ + this->onMs = onMs; + this->cduRechargeMs = cduRechargeMs; + this->activeOutputState = activeOutputState; + state = PP_IDLE; + targetMs = 0; + memset(pinQueue, PIN_PULSER_SLOT_EMPTY, PIN_PULSER_MAX_PINS + 1); +} + +uint8_t PinPulser::addPin(uint8_t Pin) +{ +// Serial.print(" PinPulser::addPin: "); Serial.print(Pin,DEC); + + for(uint8_t i = 0; i < PIN_PULSER_MAX_PINS; i++) + { + if(pinQueue[i] == Pin) + { +// Serial.print(" Already in Index: "); Serial.println(i,DEC); + return i; + } + + else if(pinQueue[i] == PIN_PULSER_SLOT_EMPTY) + { +// Serial.print(" pinQueue Index: "); Serial.println(i,DEC); + pinQueue[i] = Pin; + process(); + return i; + } + } + +// Serial.println(); + return PIN_PULSER_SLOT_EMPTY; +} + +PP_State PinPulser::process(void) +{ + unsigned long now; + + switch(state) + { + case PP_IDLE: + if(pinQueue[0] != PIN_PULSER_SLOT_EMPTY) + { +// Serial.print(" PinPulser::process: PP_IDLE: Pin: "); Serial.println(pinQueue[0],DEC); + + digitalWrite(pinQueue[0], activeOutputState); + targetMs = millis() + onMs; + state = PP_OUTPUT_ON_DELAY; + } + break; + + case PP_OUTPUT_ON_DELAY: + now = millis(); + if(now >= targetMs) + { +// Serial.print(" PinPulser::process: PP_OUTPUT_ON_DELAY: Done Deactivate Pin: "); Serial.println(pinQueue[0],DEC); + + digitalWrite(pinQueue[0], !activeOutputState); + targetMs = now + cduRechargeMs; + memmove(pinQueue, pinQueue + 1, PIN_PULSER_MAX_PINS); + state = PP_CDU_RECHARGE_DELAY; + } + break; + + case PP_CDU_RECHARGE_DELAY: + now = millis(); + if(now >= targetMs) + { + if(pinQueue[0] != PIN_PULSER_SLOT_EMPTY) + { +// Serial.print(" PinPulser::process: PIN_PULSER_SLOT_EMPTY: Done Deactivate Pin: "); Serial.println(pinQueue[0],DEC); + + digitalWrite(pinQueue[0], activeOutputState); + targetMs = now + onMs; + state = PP_OUTPUT_ON_DELAY; + } + else + { +// Serial.println(" PinPulser::process: PP_CDU_RECHARGE_DELAY - Now PP_IDLE"); + state = PP_IDLE; + } + } + break; + } + return state; +} + diff --git a/examples/NmraDccAccessoryDecoder_Pulsed_8/PinPulser.h b/examples/NmraDccAccessoryDecoder_Pulsed_8/PinPulser.h new file mode 100644 index 0000000..747fd18 --- /dev/null +++ b/examples/NmraDccAccessoryDecoder_Pulsed_8/PinPulser.h @@ -0,0 +1,27 @@ +#include + +#define PIN_PULSER_MAX_PINS 16 + +enum PP_State +{ + PP_IDLE = 0, + PP_OUTPUT_ON_DELAY, + PP_CDU_RECHARGE_DELAY, +}; + +class PinPulser +{ + private: + uint16_t onMs; + uint16_t cduRechargeMs; + PP_State state = PP_IDLE; + unsigned long targetMs = 0; + uint8_t activeOutputState = HIGH; + uint8_t pinQueue[PIN_PULSER_MAX_PINS + 1]; + + public: + void init(uint16_t onMs, uint16_t cduRechargeMs, uint8_t activeOutputState); + uint8_t addPin(uint8_t pin); + PP_State process(void); +}; + diff --git a/keywords.txt b/keywords.txt index c61a74c..72f070c 100755 --- a/keywords.txt +++ b/keywords.txt @@ -6,54 +6,57 @@ # Datatypes (KEYWORD1) ####################################### -DCC_MSG KEYWORD1 -NmraDcc KEYWORD1 +DCC_MSG KEYWORD1 +NmraDcc KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) ####################################### -NmraDcc KEYWORD2 -pin KEYWORD2 -init KEYWORD2 -process KEYWORD2 -getCV KEYWORD2 -setCV KEYWORD2 -isSetCVReady KEYWORD2 -notifyDccReset KEYWORD2 -notifyDccIdle KEYWORD2 -notifyDccSpeed KEYWORD2 -notifyDccFunc KEYWORD2 -notifyDccAccState KEYWORD2 -notifyDccSigState KEYWORD2 -notifyDccMsg KEYWORD2 -notifyCVValid KEYWORD2 -notifyCVRead KEYWORD2 -notifyCVWrite KEYWORD2 -notifyIsSetCVReady KEYWORD2 -notifyCVChange KEYWORD2 -notifyCVAck KEYWORD2 -notifyCVResetFactoryDefault KEYWORD2 +NmraDcc KEYWORD2 +pin KEYWORD2 +init KEYWORD2 +process KEYWORD2 +getCV KEYWORD2 +setCV KEYWORD2 +isSetCVReady KEYWORD2 +notifyDccReset KEYWORD2 +notifyDccIdle KEYWORD2 +notifyDccSpeed KEYWORD2 +notifyDccSpeedRaw +notifyDccFunc KEYWORD2 +notifyDccAccState KEYWORD2 +notifyDccAccTurnoutBoard +notifyDccAccTurnoutOutput +notifyDccSigState KEYWORD2 +notifyDccMsg KEYWORD2 +notifyCVValid KEYWORD2 +notifyCVRead KEYWORD2 +notifyCVWrite KEYWORD2 +notifyIsSetCVReady KEYWORD2 +notifyCVChange KEYWORD2 +notifyCVAck KEYWORD2 +notifyCVResetFactoryDefault KEYWORD2 ####################################### # Constants (LITERAL1) -MAN_ID_JMRI LITERAL1 -MAN_ID_DIY LITERAL1 -MAN_ID_SILICON_RAILWAY LITERAL1 -FLAGS_MY_ADDRESS_ONLY LITERAL1 -FLAGS_OUTPUT_ADDRESS_MODE LITERAL1 -FLAGS_DCC_ACCESSORY_DECODER LITERAL1 +MAN_ID_JMRI LITERAL1 +MAN_ID_DIY LITERAL1 +MAN_ID_SILICON_RAILWAY LITERAL1 +FLAGS_MY_ADDRESS_ONLY LITERAL1 +FLAGS_OUTPUT_ADDRESS_MODE LITERAL1 +FLAGS_DCC_ACCESSORY_DECODER LITERAL1 -CV_ACCESSORY_DECODER_ADDRESS_LSB LITERAL1 -CV_ACCESSORY_DECODER_ADDRESS_MSB LITERAL1 +CV_ACCESSORY_DECODER_ADDRESS_LSB LITERAL1 +CV_ACCESSORY_DECODER_ADDRESS_MSB LITERAL1 -CV_MULTIFUNCTION_PRIMARY_ADDRESS LITERAL1 -CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB LITERAL1 -CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB LITERAL1 +CV_MULTIFUNCTION_PRIMARY_ADDRESS LITERAL1 +CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB LITERAL1 +CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB LITERAL1 -CV_VERSION_ID LITERAL1 -CV_MANUFACTURER_ID LITERAL1 -CV_29_CONFIG LITERAL1 -CV_OPS_MODE_ADDRESS_LSB LITERAL1 +CV_VERSION_ID LITERAL1 +CV_MANUFACTURER_ID LITERAL1 +CV_29_CONFIG LITERAL1 +CV_OPS_MODE_ADDRESS_LSB LITERAL1 ####################################### diff --git a/library.properties b/library.properties index e8eb40a..e190e6b 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=NmraDcc -version=1.1.0 +version=1.2.0 author=Alex Shepherd, Wolfgang Kuffer, Geoff Bunza, Martin Pischky maintainer=Alex Shepherd sentence=Enables NMRA DCC Communication