From fa07ae64cadd8babca40761166d06ef9a95d6647 Mon Sep 17 00:00:00 2001 From: vsky Date: Wed, 24 Jul 2019 00:29:27 +0200 Subject: [PATCH 1/3] NmraDcc driver clone --- app/driver/NmraDcc.c | 1552 ++++++++++++++++++++++++++++++++++ app/include/driver/NmraDcc.h | 721 ++++++++++++++++ 2 files changed, 2273 insertions(+) create mode 100644 app/driver/NmraDcc.c create mode 100644 app/include/driver/NmraDcc.h diff --git a/app/driver/NmraDcc.c b/app/driver/NmraDcc.c new file mode 100644 index 0000000000..d35123326e --- /dev/null +++ b/app/driver/NmraDcc.c @@ -0,0 +1,1552 @@ +//------------------------------------------------------------------------ +// +// Model Railroading with Arduino - NmraDcc.cpp +// +// Copyright (c) 2008 - 2017 Alex Shepherd +// +// This source file is subject of the GNU general public license 2, +// that is available at the world-wide-web at +// http://www.gnu.org/licenses/gpl.txt +// +//------------------------------------------------------------------------ +// +// file: NmraDcc.cpp +// author: Alex Shepherd +// webpage: http://mrrwa.org/ +// history: 2008-03-20 Initial Version +// 2011-06-26 Migrated into Arduino library from OpenDCC codebase +// 2014 Added getAddr to NmraDcc Geoff Bunza +// 2015-11-06 Martin Pischky (martin@pischky.de): +// Experimental Version to support 14 speed steps +// and new signature of notifyDccSpeed and notifyDccFunc +// 2015-12-16 Version without use of Timer0 by Franz-Peter Müller +// 2016-07-16 handle glitches on DCC line +// 2016-08-20 added ESP8266 support by Sven (littleyoda) +// 2017-01-19 added STM32F1 support by Franz-Peter +// 2017-11-29 Ken West (kgw4449@gmail.com): +// Minor fixes to pass NMRA Baseline Conformance Tests. +// 2018-12-17 added ESP32 support by Trusty (thierry@lapajaparis.net) +// 2019-02-17 added ESP32 specific changes by Hans Tanner +// +//------------------------------------------------------------------------ +// +// purpose: Provide a simplified interface to decode NMRA DCC packets +// and build DCC Mobile and Stationary Decoders +// +//------------------------------------------------------------------------ + +#include "NmraDcc.h" +#ifdef __AVR_MEGA__ +#include +#endif + +// Uncomment to print DEBUG messages +//#define DEBUG_PRINT + +//------------------------------------------------------------------------ +// DCC Receive Routine +// +// Howto: uses two interrupts: a rising edge in DCC polarity triggers INTx +// in INTx handler, Timer0 CompareB with a delay of 80us is started. +// On Timer0 CompareB Match the level of DCC is evaluated and +// parsed. +// +// |<-----116us----->| +// +// DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________ +// ^-INTx +// |----87us--->| +// ^Timer-INT: reads zero +// +// DCC 0: _________XXXXXXXXXXXXXXXXXX__________________ +// ^-INTx +// |----------->| +// ^Timer-INT: reads one +// +// new DCC Receive Routine without Timer0 ........................................................ +// +// Howto: uses only one interrupt at the rising or falling edge of the DCC signal +// The time between two edges is measured to determine the bit value +// Synchronising to the edge of the first part of a bit is done after recognizing the start bit +// During synchronizing each part of a bit is detected ( Interruptmode 'change' ) +// +// |<-----116us----->| +// DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________ +// |<--------146us------>| +// ^-INTx ^-INTx +// less than 138us: its a one-Bit +// +// +// |<-----------------232us----------->| +// DCC 0: _________XXXXXXXXXXXXXXXXXX__________________XXXXXXXX__________ +// |<--------146us------->| +// ^-INTx ^-INTx +// greater than 138us: its a zero bit +// +// +// +// +//------------------------------------------------------------------------ + +#define MAX_ONEBITFULL 146 +#define MAX_PRAEAMBEL 146 +#define MAX_ONEBITHALF 82 +#define MIN_ONEBITFULL 82 +#define MIN_ONEBITHALF 35 +#define MAX_BITDIFF 18 + + +// Debug-Ports +//#define debug // Testpulse for logic analyser +#ifdef debug + #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + #define MODE_TP1 DDRF |= (1<<2) //pinA2 + #define SET_TP1 PORTF |= (1<<2) + #define CLR_TP1 PORTF &= ~(1<<2) + #define MODE_TP2 DDRF |= (1<<3) //pinA3 + #define SET_TP2 PORTF |= (1<<3) + #define CLR_TP2 PORTF &= ~(1<<3) + #define MODE_TP3 DDRF |= (1<<4) //pinA4 + #define SET_TP3 PORTF |= (1<<4) + #define CLR_TP3 PORTF &= ~(1<<4) + #define MODE_TP4 DDRF |= (1<<5) //pinA5 + #define SET_TP4 PORTF |= (1<<5) + #define CLR_TP4 PORTF &= ~(1<<5) + #elif defined(__AVR_ATmega32U4__) + #define MODE_TP1 DDRF |= (1<<4) //A3 + #define SET_TP1 PORTF |= (1<<4) + #define CLR_TP1 PORTF &= ~(1<<4) + #define MODE_TP2 DDRF |= (1<<5) //A2 + #define SET_TP2 PORTF |= (1<<5) + #define CLR_TP2 PORTF &= ~(1<<5) + #define MODE_TP3 + #define SET_TP3 + #define CLR_TP3 + #define MODE_TP4 + #define SET_TP4 + #define CLR_TP4 + #elif defined(__AVR_ATmega328P__) + #define MODE_TP1 DDRC |= (1<<1) //A1 + #define SET_TP1 PORTC |= (1<<1) + #define CLR_TP1 PORTC &= ~(1<<1) + #define MODE_TP2 DDRC |= (1<<2) // A2 + #define SET_TP2 PORTC |= (1<<2) + #define CLR_TP2 PORTC &= ~(1<<2) + #define MODE_TP3 DDRC |= (1<<3) //A3 + #define SET_TP3 PORTC |= (1<<3) + #define CLR_TP3 PORTC &= ~(1<<3) + #define MODE_TP4 DDRC |= (1<<4) //A4 + #define SET_TP4 PORTC |= (1<<4) + #define CLR_TP4 PORTC &= ~(1<<4) + #elif defined(__arm__) && (defined(__MK20DX128__) || defined(__MK20DX256__)) + // Teensys 3.x + #define MODE_TP1 pinMode( A1,OUTPUT ) // A1= PortC, Bit0 + #define SET_TP1 GPIOC_PSOR = 0x01 + #define CLR_TP1 GPIOC_PCOR = 0x01 + #define MODE_TP2 pinMode( A2,OUTPUT ) // A2= PortB Bit0 + #define SET_TP2 GPIOB_PSOR = 0x01 + #define CLR_TP2 GPIOB_PCOR = 0x01 + #define MODE_TP3 pinMode( A3,OUTPUT ) // A3 = PortB Bit1 + #define SET_TP3 GPIOB_PSOR = 0x02 + #define CLR_TP3 GPIOB_PCOR = 0x02 + #define MODE_TP4 pinMode( A4,OUTPUT ) // A4 = PortB Bit3 + #define SET_TP4 GPIOB_PSOR = 0x08 + #define CLR_TP4 GPIOB_PCOR = 0x08 + #elif defined (__STM32F1__) + // STM32F103... + #define MODE_TP1 pinMode( PB12,OUTPUT ) // TP1= PB12 + #define SET_TP1 gpio_write_bit( GPIOB,12, HIGH ); + #define CLR_TP1 gpio_write_bit( GPIOB,12, LOW ); + #define MODE_TP2 pinMode( PB13,OUTPUT ) // TP2= PB13 + #define SET_TP2 gpio_write_bit( GPIOB,13, HIGH ); + #define CLR_TP2 gpio_write_bit( GPIOB,13, LOW ); + #define MODE_TP3 pinMode( PB14,OUTPUT ) // TP3 = PB14 + #define SET_TP3 gpio_write_bit( GPIOB,14, HIGH ); + #define CLR_TP3 gpio_write_bit( GPIOB,14, LOW ); + #define MODE_TP4 pinMode( PB15,OUTPUT ) // TP4 = PB15 + #define SET_TP4 gpio_write_bit( GPIOB,15, HIGH ); + #define CLR_TP4 gpio_write_bit( GPIOB,15, LOW ); + #elif defined(ESP8266) + #define D5 14 + #define D6 12 + #define D7 13 + #define D8 15 + #define MODE_TP1 pinMode( D5,OUTPUT ) ; // GPIO 14 + #define SET_TP1 GPOS = (1 << D5); + #define CLR_TP1 GPOC = (1 << D5); + #define MODE_TP2 pinMode( D6,OUTPUT ) ; // GPIO 12 + #define SET_TP2 GPOS = (1 << D6); + #define CLR_TP2 GPOC = (1 << D6); + #define MODE_TP3 pinMode( D7,OUTPUT ) ; // GPIO 13 + #define SET_TP3 GPOS = (1 << D7); + #define CLR_TP3 GPOC = (1 << D7); + #define MODE_TP4 pinMode( D8,OUTPUT ) ; // GPIO 15 + #define SET_TP4 GPOC = (1 << D8); + #define CLR_TP4 GPOC = (1 << D8); + #elif defined(ESP32) + #define MODE_TP1 pinMode( 33,OUTPUT ) ; // GPIO 33 + #define SET_TP1 GPOS = (1 << 33); + #define CLR_TP1 GPOC = (1 << 33); + #define MODE_TP2 pinMode( 25,OUTPUT ) ; // GPIO 25 + #define SET_TP2 GPOS = (1 << 25); + #define CLR_TP2 GPOC = (1 << 25); + #define MODE_TP3 pinMode( 26,OUTPUT ) ; // GPIO 26 + #define SET_TP3 GPOS = (1 << 26); + #define CLR_TP3 GPOC = (1 << 26); + #define MODE_TP4 pinMode( 27,OUTPUT ) ; // GPIO 27 + #define SET_TP4 GPOC = (1 << 27); + #define CLR_TP4 GPOC = (1 << 27); + + + //#elif defined(__AVR_ATmega128__) ||defined(__AVR_ATmega1281__)||defined(__AVR_ATmega2561__) + #else + #define MODE_TP1 + #define SET_TP1 + #define CLR_TP1 + #define MODE_TP2 + #define SET_TP2 + #define CLR_TP2 + #define MODE_TP3 + #define SET_TP3 + #define CLR_TP3 + #define MODE_TP4 + #define SET_TP4 + #define CLR_TP4 + + #endif +#else + #define MODE_TP1 + #define SET_TP1 + #define CLR_TP1 + #define MODE_TP2 + #define SET_TP2 + #define CLR_TP2 + //#define MODE_TP2 DDRC |= (1<<2) // A2 + //#define SET_TP2 PORTC |= (1<<2) + //#define CLR_TP2 PORTC &= ~(1<<2) + #define MODE_TP3 + #define SET_TP3 + #define CLR_TP3 + #define MODE_TP4 + #define SET_TP4 + #define CLR_TP4 + //#define MODE_TP4 DDRC |= (1<<4) //A4 + //#define SET_TP4 PORTC |= (1<<4) + //#define CLR_TP4 PORTC &= ~(1<<4) + +#endif +#ifdef DEBUG_PRINT + #define DB_PRINT( x, ... ) { char dbgbuf[80]; sprintf_P( dbgbuf, (const char*) F( x ) , ##__VA_ARGS__ ) ; Serial.println( dbgbuf ); } + #define DB_PRINT_( x, ... ) { char dbgbuf[80]; sprintf_P( dbgbuf, (const char*) F( x ) , ##__VA_ARGS__ ) ; Serial.print( dbgbuf ); } +#else + #define DB_PRINT( x, ... ) ; + #define DB_PRINT_( x, ... ) ; +#endif + +#ifdef DCC_DBGVAR +struct countOf_t countOf; +#endif + +#if defined ( __STM32F1__ ) +static ExtIntTriggerMode ISREdge; +#elif defined ( ESP32 ) +static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING +static byte ISRWatch; // Interrupt Handler Edge Filter +#else +static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING +static byte ISRWatch; // Interrupt Handler Edge Filter +#endif +static word bitMax, bitMin; + +typedef enum +{ + WAIT_PREAMBLE = 0, + WAIT_START_BIT, + WAIT_DATA, + WAIT_END_BIT +} +DccRxWaitState ; + +typedef enum +{ + OPS_INS_RESERVED = 0, + OPS_INS_VERIFY_BYTE, + OPS_INS_BIT_MANIPULATION, + OPS_INS_WRITE_BYTE +} +OpsInstructionType; + +struct DccRx_t +{ + DccRxWaitState State ; + uint8_t DataReady ; + uint8_t BitCount ; + uint8_t TempByte ; + DCC_MSG PacketBuf; + DCC_MSG PacketCopy; +} +DccRx ; + +typedef struct +{ + uint8_t Flags ; + uint8_t OpsModeAddressBaseCV ; + uint8_t inServiceMode ; + long LastServiceModeMillis ; + uint8_t PageRegister ; // Used for Paged Operations in Service Mode Programming + uint8_t DuplicateCount ; + DCC_MSG LastMsg ; + uint8_t ExtIntNum; + uint8_t ExtIntPinNum; + int16_t myDccAddress; // Cached value of DCC Address from CVs + uint8_t inAccDecDCCAddrNextReceivedMode; +#ifdef DCC_DEBUG + uint8_t IntCount; + uint8_t TickCount; + uint8_t NestedIrqCount; +#endif +} +DCC_PROCESSOR_STATE ; + +DCC_PROCESSOR_STATE DccProcState ; + +#ifdef ESP32 +portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + +void IRAM_ATTR ExternalInterruptHandler(void) +#elif defined(ESP8266) +void ICACHE_RAM_ATTR ExternalInterruptHandler(void) +#else +void ExternalInterruptHandler(void) +#endif +{ +#ifdef ESP32 +// switch (ISRWatch) +// { +// case RISING: if (digitalRead(DccProcState.ExtIntPinNum)) break; +// case FALLING: if (digitalRead(DccProcState.ExtIntPinNum)) return; break; +// } + // First compare the edge we're looking for to the pin state + switch (ISRWatch) + { + case CHANGE: + break; + + case RISING: + if (digitalRead(DccProcState.ExtIntPinNum) != HIGH) + return; + break; + + case FALLING: + if (digitalRead(DccProcState.ExtIntPinNum) != LOW) + return; + break; + } +#endif +// Bit evaluation without Timer 0 ------------------------------ + uint8_t DccBitVal; + static int8_t bit1, bit2 ; + static unsigned long lastMicros = 0; + static byte halfBit, DCC_IrqRunning; + unsigned long actMicros, bitMicros; + if ( DCC_IrqRunning ) { + // nested DCC IRQ - obviously there are glitches + // ignore this interrupt and increment glitchcounter + CLR_TP3; + #ifdef DCC_DEBUG + DccProcState.NestedIrqCount++; + #endif + SET_TP3; + return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ + } + SET_TP3; + actMicros = micros(); + bitMicros = actMicros-lastMicros; + if ( bitMicros < bitMin ) { + // too short - my be false interrupt due to glitch or false protocol -> ignore + CLR_TP3; + SET_TP4; CLR_TP4; + return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ + } + DccBitVal = ( bitMicros < bitMax ); + lastMicros = actMicros; + #ifdef debug + if(DccBitVal) {SET_TP2;} else {CLR_TP2;}; + #endif + DCC_IrqRunning = true; + interrupts(); // time critical is only the micros() command,so allow nested irq's +#ifdef DCC_DEBUG + DccProcState.TickCount++; +#endif + + switch( DccRx.State ) + { + case WAIT_PREAMBLE: + if( DccBitVal ) + { + SET_TP1; + DccRx.BitCount++; + if( DccRx.BitCount > 10 ) { + DccRx.State = WAIT_START_BIT ; + // While waiting for the start bit, detect halfbit lengths. We will detect the correct + // sync and detect whether we see a false (e.g. motorola) protocol + + #if defined ( __STM32F1__ ) + detachInterrupt( DccProcState.ExtIntNum ); + #endif + #ifdef ESP32 + ISRWatch = CHANGE; + #else + attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE); + #endif + halfBit = 0; + bitMax = MAX_ONEBITHALF; + bitMin = MIN_ONEBITHALF; + CLR_TP1; + } + } else { + SET_TP1; + DccRx.BitCount = 0 ; + CLR_TP1; + } + break; + + case WAIT_START_BIT: + // we are looking for first half "0" bit after preamble + switch ( halfBit ) { + case 0: //SET_TP1; + // check first part + if ( DccBitVal ) { + // is still 1-bit (Preamble) + halfBit=1; + bit1=bitMicros; + } else { + // was "0" half bit, maybe the startbit + SET_TP1; + halfBit = 4; + CLR_TP1; + } + break; + case 1: //SET_TP1; // previous halfbit was '1' + if ( DccBitVal ) { + // its a '1' halfBit -> we are still in the preamble + halfBit = 0; + bit2=bitMicros; + DccRx.BitCount++; + if( abs(bit2-bit1) > MAX_BITDIFF ) { + // the length of the 2 halfbits differ too much -> wrong protokoll + CLR_TP2; + CLR_TP3; + DccRx.State = WAIT_PREAMBLE; + bitMax = MAX_PRAEAMBEL; + bitMin = MIN_ONEBITFULL; + DccRx.BitCount = 0; + SET_TP4; + + #if defined ( __STM32F1__ ) + detachInterrupt( DccProcState.ExtIntNum ); + #endif + #ifdef ESP32 + ISRWatch = ISREdge; + #else + attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); + #endif + SET_TP3; + CLR_TP4; + } + } else { + // first '0' half detected in second halfBit + // wrong sync or not a DCC protokoll + CLR_TP3; + halfBit = 3; + SET_TP3; + } + break; + case 3: //SET_TP1; // previous halfbit was '0' in second halfbit + if ( DccBitVal ) { + // its a '1' halfbit -> we got only a half '0' bit -> cannot be DCC + DccRx.State = WAIT_PREAMBLE; + bitMax = MAX_PRAEAMBEL; + bitMin = MIN_ONEBITFULL; + DccRx.BitCount = 0; + } else { + // we got two '0' halfbits -> it's the startbit + // but sync is NOT ok, change IRQ edge. + if ( ISREdge == RISING ) ISREdge = FALLING; else ISREdge = RISING; + DccRx.State = WAIT_DATA ; + bitMax = MAX_ONEBITFULL; + bitMin = MIN_ONEBITFULL; + DccRx.PacketBuf.Size = 0; + DccRx.PacketBuf.PreambleBits = 0; + for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ ) + DccRx.PacketBuf.Data[i] = 0; + + DccRx.PacketBuf.PreambleBits = DccRx.BitCount; + DccRx.BitCount = 0 ; + DccRx.TempByte = 0 ; + } + SET_TP4; + + #if defined ( __STM32F1__ ) + detachInterrupt( DccProcState.ExtIntNum ); + #endif + #ifdef ESP32 + ISRWatch = ISREdge; + #else + attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); + #endif + CLR_TP1; + CLR_TP4; + break; + case 4: SET_TP1; // previous (first) halfbit was 0 + // if this halfbit is 0 too, we got the startbit + if ( DccBitVal ) { + // second halfbit is 1 -> unknown protokoll + DccRx.State = WAIT_PREAMBLE; + bitMax = MAX_PRAEAMBEL; + bitMin = MIN_ONEBITFULL; + DccRx.BitCount = 0; + } else { + // we got the startbit + DccRx.State = WAIT_DATA ; + bitMax = MAX_ONEBITFULL; + bitMin = MIN_ONEBITFULL; + DccRx.PacketBuf.Size = 0; + DccRx.PacketBuf.PreambleBits = 0; + for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ ) + DccRx.PacketBuf.Data[i] = 0; + + DccRx.PacketBuf.PreambleBits = DccRx.BitCount; + DccRx.BitCount = 0 ; + DccRx.TempByte = 0 ; + } + + CLR_TP1; + SET_TP4; + + #if defined ( __STM32F1__ ) + detachInterrupt( DccProcState.ExtIntNum ); + #endif + #ifdef ESP32 + ISRWatch = ISREdge; + #else + attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); + #endif + + CLR_TP4; + break; + + } + break; + + case WAIT_DATA: + DccRx.BitCount++; + DccRx.TempByte = ( DccRx.TempByte << 1 ) ; + if( DccBitVal ) + DccRx.TempByte |= 1 ; + + if( DccRx.BitCount == 8 ) + { + if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort + { + DccRx.State = WAIT_PREAMBLE ; + bitMax = MAX_PRAEAMBEL; + bitMin = MIN_ONEBITFULL; + DccRx.BitCount = 0 ; + } + else + { + DccRx.State = WAIT_END_BIT ; + DccRx.PacketBuf.Data[ DccRx.PacketBuf.Size++ ] = DccRx.TempByte ; + } + } + break; + + case WAIT_END_BIT: + DccRx.BitCount++; + if( DccBitVal ) // End of packet? + { + CLR_TP3; + DccRx.State = WAIT_PREAMBLE ; + bitMax = MAX_PRAEAMBEL; + bitMin = MIN_ONEBITFULL; +#ifdef ESP32 + portENTER_CRITICAL_ISR(&mux); +#endif + DccRx.PacketCopy = DccRx.PacketBuf ; + DccRx.DataReady = 1 ; +#ifdef ESP32 + portEXIT_CRITICAL_ISR(&mux); +#endif + SET_TP3; + } + else // Get next Byte + // KGW - Abort immediately if packet is too long. + if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort + { + DccRx.State = WAIT_PREAMBLE ; + bitMax = MAX_PRAEAMBEL; + bitMin = MIN_ONEBITFULL; + DccRx.BitCount = 0 ; + } + else + { + DccRx.State = WAIT_DATA ; + + DccRx.BitCount = 0 ; + DccRx.TempByte = 0 ; + } + } + CLR_TP1; + CLR_TP3; + DCC_IrqRunning = false; +} + +void ackCV(void) +{ + if( notifyCVAck ) + notifyCVAck() ; +} + +uint8_t readEEPROM( unsigned int CV ) { + return EEPROM.read(CV) ; +} + +void writeEEPROM( unsigned int CV, uint8_t Value ) { + EEPROM.write(CV, Value) ; + #if defined(ESP8266) + EEPROM.commit(); + #endif + #if defined(ESP32) + EEPROM.commit(); + #endif +} + +bool readyEEPROM() { + #ifdef __AVR_MEGA__ + return eeprom_is_ready(); + #else + return true; + #endif +} + + +uint8_t validCV( uint16_t CV, uint8_t Writable ) +{ + if( notifyCVResetFactoryDefault && (CV == CV_MANUFACTURER_ID ) && Writable ) + notifyCVResetFactoryDefault(); + + if( notifyCVValid ) + return notifyCVValid( CV, Writable ) ; + + uint8_t Valid = 1 ; + + if( CV > MAXCV ) + Valid = 0 ; + + if( Writable && ( ( CV ==CV_VERSION_ID ) || (CV == CV_MANUFACTURER_ID ) ) ) + Valid = 0 ; + + return Valid ; +} + +uint8_t readCV( unsigned int CV ) +{ + uint8_t Value ; + + if( notifyCVRead ) + return notifyCVRead( CV ) ; + + Value = readEEPROM(CV); + return Value ; +} + +uint8_t writeCV( unsigned int CV, uint8_t Value) +{ + switch( CV ) + { + case CV_29_CONFIG: + // copy addressmode Bit to Flags + DccProcState.Flags = ( DccProcState.Flags & ~FLAGS_CV29_BITS) | (Value & FLAGS_CV29_BITS); + // no break, because myDccAdress must also be reset + case CV_ACCESSORY_DECODER_ADDRESS_LSB: // Also same CV for CV_MULTIFUNCTION_PRIMARY_ADDRESS + case CV_ACCESSORY_DECODER_ADDRESS_MSB: + case CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB: + case CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB: + DccProcState.myDccAddress = -1; // Assume any CV Write Operation might change the Address + } + + if( notifyCVWrite ) + return notifyCVWrite( CV, Value ) ; + + if( readEEPROM( CV ) != Value ) + { + writeEEPROM( CV, Value ) ; + + if( notifyCVChange ) + notifyCVChange( CV, Value) ; + + if( notifyDccCVChange && !(DccProcState.Flags & FLAGS_SETCV_CALLED) ) + notifyDccCVChange( CV, Value ); + } + + return readEEPROM( CV ) ; +} + +uint16_t getMyAddr(void) +{ + uint8_t CV29Value ; + + if( DccProcState.myDccAddress != -1 ) // See if we can return the cached value + return( DccProcState.myDccAddress ); + + CV29Value = readCV( CV_29_CONFIG ) ; + + if( CV29Value & CV29_ACCESSORY_DECODER ) // Accessory Decoder? + { + if( CV29Value & CV29_OUTPUT_ADDRESS_MODE ) + DccProcState.myDccAddress = ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) << 8 ) | readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ); + else + DccProcState.myDccAddress = ( ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) & 0b00000111) << 6 ) | ( readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ) & 0b00111111) ; + } + else // Multi-Function Decoder? + { + if( CV29Value & CV29_EXT_ADDRESSING ) // Two Byte Address? + DccProcState.myDccAddress = ( ( readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB ) - 192 ) << 8 ) | readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB ) ; + + else + DccProcState.myDccAddress = readCV( 1 ) ; + } + + return DccProcState.myDccAddress ; +} + +void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value ) +{ + // is it a Byte Operation + if( Cmd & 0x04 ) + { + // Perform the Write Operation + if( Cmd & 0x08 ) + { + if( validCV( CVAddr, 1 ) ) + { + if( writeCV( CVAddr, Value ) == Value ) + ackCV(); + } + } + + else // Perform the Verify Operation + { + if( validCV( CVAddr, 0 ) ) + { + if( readCV( CVAddr ) == Value ) + ackCV(); + } + } + } + // Perform the Bit-Wise Operation + else + { + uint8_t BitMask = (1 << (Value & 0x07) ) ; + uint8_t BitValue = Value & 0x08 ; + uint8_t BitWrite = Value & 0x10 ; + + uint8_t tempValue = readCV( CVAddr ) ; // Read the Current CV Value + + // Perform the Bit Write Operation + if( BitWrite ) + { + if( validCV( CVAddr, 1 ) ) + { + if( BitValue ) + tempValue |= BitMask ; // Turn the Bit On + + else + tempValue &= ~BitMask ; // Turn the Bit Off + + if( writeCV( CVAddr, tempValue ) == tempValue ) + ackCV() ; + } + } + + // Perform the Bit Verify Operation + else + { + if( validCV( CVAddr, 0 ) ) + { + if( BitValue ) + { + if( tempValue & BitMask ) + ackCV() ; + } + else + { + if( !( tempValue & BitMask) ) + ackCV() ; + } + } + } + } +} + +///////////////////////////////////////////////////////////////////////// +#ifdef NMRA_DCC_PROCESS_MULTIFUNCTION +void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Cmd, uint8_t Data1, uint8_t Data2 ) +{ + uint8_t speed ; + uint16_t CVAddr ; + DCC_DIRECTION dir ; + DCC_SPEED_STEPS speedSteps ; + + uint8_t CmdMasked = Cmd & 0b11100000 ; + + // If we are an Accessory Decoder + if( DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER ) + { + // and this isn't an Ops Mode Write or we are NOT faking the Multifunction Ops mode address in CV 33+34 or + // it's not our fake address, then return + if( ( CmdMasked != 0b11100000 ) || ( DccProcState.OpsModeAddressBaseCV == 0 ) ) + return ; + + uint16_t FakeOpsAddr = readCV( DccProcState.OpsModeAddressBaseCV ) | ( readCV( DccProcState.OpsModeAddressBaseCV + 1 ) << 8 ) ; + uint16_t OpsAddr = Addr & 0x3FFF ; + + if( OpsAddr != FakeOpsAddr ) + return ; + } + + // We are looking for FLAGS_MY_ADDRESS_ONLY but it does not match and it is not a Broadcast Address then return + else if( ( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) && ( Addr != getMyAddr() ) && ( Addr != 0 ) ) + return ; + + switch( CmdMasked ) + { + case 0b00000000: // Decoder Control + switch( Cmd & 0b00001110 ) + { + case 0b00000000: + if( notifyDccReset && ( Cmd & 0b00000001 ) ) // Hard Reset + if( notifyDccReset) + notifyDccReset( 1 ) ; + break ; + + case 0b00000010: // Factory Test + break ; + + case 0b00000110: // Set Decoder Flags + break ; + + case 0b00001010: // Set Advanced Addressing + break ; + + case 0b00001110: // Decoder Acknowledgment + break ; + + default: // Reserved + ; + } + break ; + + case 0b00100000: // Advanced Operations + switch( Cmd & 0b00011111 ) + { + case 0b00011111: + if( notifyDccSpeed ) + { + switch( Data1 & 0b01111111 ) + { + case 0b00000000: // 0=STOP + speed = 1 ; // => 1 + break ; + + case 0b00000001: // 1=EMERGENCY_STOP + speed = 0 ; // => 0 + break ; + + default: // 2..127 + speed = (Data1 & 0b01111111) ; + } + dir = (DCC_DIRECTION) ((Data1 & 0b10000000) >> 7) ; + notifyDccSpeed( Addr, AddrType, speed, dir, SPEED_STEP_128 ) ; + } + } + break; + + case 0b01000000: + case 0b01100000: + //TODO should we cache this info in DCC_PROCESSOR_STATE.Flags ? +#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE + speedSteps = (readCV( CV_29_CONFIG ) & CV29_F0_LOCATION) ? SPEED_STEP_28 : SPEED_STEP_14 ; +#else + speedSteps = SPEED_STEP_28 ; +#endif + if( notifyDccSpeed ) + { + switch( Cmd & 0b00011111 ) + { + case 0b00000000: // 0 0000 = STOP + case 0b00010000: // 1 0000 = STOP + speed = 1 ; // => 1 + break ; + + case 0b00000001: // 0 0001 = EMERGENCY STOP + case 0b00010001: // 1 0001 = EMERGENCY STOP + speed = 0 ; // => 0 + break ; + + default: +#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE + if( speedSteps == SPEED_STEP_14 ) + { + speed = (Cmd & 0b00001111) ; // => 2..15 + } + else + { +#endif + speed = (((Cmd & 0b00001111) << 1 ) | ((Cmd & 0b00010000) >> 4)) - 2 ; // => 2..29 +#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE + } +#endif + } + dir = (DCC_DIRECTION) ((Cmd & 0b00100000) >> 5) ; + notifyDccSpeed( Addr, AddrType, speed, dir, speedSteps ) ; + } + if( notifyDccSpeedRaw ) + notifyDccSpeedRaw(Addr, AddrType, Cmd ); + +#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE + if( notifyDccFunc && (speedSteps == SPEED_STEP_14) ) + { + // function light is controlled by this package + uint8_t fn0 = (Cmd & 0b00010000) ; + notifyDccFunc( Addr, AddrType, FN_0, fn0 ) ; + } +#endif + break; + + case 0b10000000: // Function Group 0..4 + if( notifyDccFunc ) + { + // function light is controlled by this package (28 or 128 speed steps) + notifyDccFunc( Addr, AddrType, FN_0_4, Cmd & 0b00011111 ) ; + } + break; + + case 0b10100000: // Function Group 5..8 + if( notifyDccFunc) + { + if (Cmd & 0b00010000 ) + notifyDccFunc( Addr, AddrType, FN_5_8, Cmd & 0b00001111 ) ; + else + notifyDccFunc( Addr, AddrType, FN_9_12, Cmd & 0b00001111 ) ; + } + break; + + case 0b11000000: // Feature Expansion Instruction + switch(Cmd & 0b00011111) + { + case 0b00011110: + if( notifyDccFunc ) + notifyDccFunc( Addr, AddrType, FN_13_20, Data1 ) ; + break; + + case 0b00011111: + if( notifyDccFunc ) + notifyDccFunc( Addr, AddrType, FN_21_28, Data1 ) ; + break; + } + break; + + case 0b11100000: // CV Access + CVAddr = ( ( ( Cmd & 0x03 ) << 8 ) | Data1 ) + 1 ; + + processDirectOpsOperation( Cmd, CVAddr, Data2 ) ; + break; + } +} +#endif + +///////////////////////////////////////////////////////////////////////// +#ifdef NMRA_DCC_PROCESS_SERVICEMODE +void processServiceModeOperation( DCC_MSG * pDccMsg ) +{ + uint16_t CVAddr ; + uint8_t Value ; + if( pDccMsg->Size == 3) // 3 Byte Packets are for Address Only, Register and Paged Mode + { + uint8_t RegisterAddr ; + DB_PRINT("3-BytePkt"); + RegisterAddr = pDccMsg->Data[0] & 0x07 ; + Value = pDccMsg->Data[1] ; + + if( RegisterAddr == 5 ) + { + DccProcState.PageRegister = Value ; + ackCV(); + } + + else + { + if( RegisterAddr == 4 ) + CVAddr = CV_29_CONFIG ; + + else if( ( RegisterAddr <= 3 ) && ( DccProcState.PageRegister > 0 ) ) + CVAddr = ( ( DccProcState.PageRegister - 1 ) * 4 ) + RegisterAddr + 1 ; + + else + CVAddr = RegisterAddr + 1 ; + + if( pDccMsg->Data[0] & 0x08 ) // Perform the Write Operation + { + if( validCV( CVAddr, 1 ) ) + { + if( writeCV( CVAddr, Value ) == Value ) + ackCV(); + } + } + + else // Perform the Verify Operation + { + if( validCV( CVAddr, 0 ) ) + { + if( readCV( CVAddr ) == Value ) + ackCV(); + } + } + } + } + + else if( pDccMsg->Size == 4) // 4 Byte Packets are for Direct Byte & Bit Mode + { + DB_PRINT("BB-Mode"); + CVAddr = ( ( ( pDccMsg->Data[0] & 0x03 ) << 8 ) | pDccMsg->Data[1] ) + 1 ; + Value = pDccMsg->Data[2] ; + + processDirectOpsOperation( pDccMsg->Data[0] & 0b00001100, CVAddr, Value ) ; + } +} +#endif + +///////////////////////////////////////////////////////////////////////// +void resetServiceModeTimer(uint8_t inServiceMode) +{ + if (notifyServiceMode && inServiceMode != DccProcState.inServiceMode) + { + notifyServiceMode(inServiceMode); + } + // Set the Service Mode + DccProcState.inServiceMode = inServiceMode ; + + DccProcState.LastServiceModeMillis = inServiceMode ? millis() : 0 ; + if (notifyServiceMode && inServiceMode != DccProcState.inServiceMode) + { + notifyServiceMode(inServiceMode); + } +} + +///////////////////////////////////////////////////////////////////////// +void clearDccProcState(uint8_t inServiceMode) +{ + resetServiceModeTimer( inServiceMode ) ; + + // Set the Page Register to it's default of 1 only on the first Reset + DccProcState.PageRegister = 1 ; + + // Clear the LastMsg buffer and DuplicateCount in preparation for possible CV programming + DccProcState.DuplicateCount = 0 ; + memset( &DccProcState.LastMsg, 0, sizeof( DCC_MSG ) ) ; +} + +///////////////////////////////////////////////////////////////////////// +#ifdef DEBUG_PRINT +void SerialPrintPacketHex(const __FlashStringHelper *strLabel, DCC_MSG * pDccMsg) +{ + Serial.print( strLabel ); + + for( uint8_t i = 0; i < pDccMsg->Size; i++ ) + { + if( pDccMsg->Data[i] <= 9) + Serial.print('0'); + + Serial.print( pDccMsg->Data[i], HEX ); + Serial.write( ' ' ); + } + Serial.println(); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +void execDccProcessor( DCC_MSG * pDccMsg ) +{ + if( ( pDccMsg->Data[0] == 0 ) && ( pDccMsg->Data[1] == 0 ) ) + { + if( notifyDccReset ) + notifyDccReset( 0 ) ; + +#ifdef NMRA_DCC_PROCESS_SERVICEMODE + // If this is the first Reset then perform some one-shot actions as we maybe about to enter service mode + if( DccProcState.inServiceMode ) + resetServiceModeTimer( 1 ) ; + else + clearDccProcState( 1 ); +#endif + } + + else + { +#ifdef NMRA_DCC_PROCESS_SERVICEMODE + if( DccProcState.inServiceMode && ( pDccMsg->Data[0] >= 112 ) && ( pDccMsg->Data[0] < 128 ) ) + { + resetServiceModeTimer( 1 ) ; + + if( memcmp( pDccMsg, &DccProcState.LastMsg, sizeof( DCC_MSG ) ) ) + { + DccProcState.DuplicateCount = 0 ; + memcpy( &DccProcState.LastMsg, pDccMsg, sizeof( DCC_MSG ) ) ; + } + // Wait until you see 2 identicle packets before acting on a Service Mode Packet + else + { + DccProcState.DuplicateCount++ ; + processServiceModeOperation( pDccMsg ) ; + } + } + + else + { + if( DccProcState.inServiceMode ) + clearDccProcState( 0 ); +#endif + + // Idle Packet + if( ( pDccMsg->Data[0] == 0b11111111 ) && ( pDccMsg->Data[1] == 0 ) ) + { + if( notifyDccIdle ) + notifyDccIdle() ; + } + +#ifdef NMRA_DCC_PROCESS_MULTIFUNCTION + // Multi Function Decoders (7-bit address) + else if( pDccMsg->Data[0] < 128 ) + processMultiFunctionMessage( pDccMsg->Data[0], DCC_ADDR_SHORT, pDccMsg->Data[1], pDccMsg->Data[2], pDccMsg->Data[3] ) ; + + // Basic Accessory Decoders (9-bit) & Extended Accessory Decoders (11-bit) + else if( pDccMsg->Data[0] < 192 ) +#else + else if( ( pDccMsg->Data[0] >= 128 ) && ( pDccMsg->Data[0] < 192 ) ) +#endif + { + if( DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER ) + { + int16_t BoardAddress ; + int16_t OutputAddress ; + uint8_t TurnoutPairIndex ; + +#ifdef DEBUG_PRINT + SerialPrintPacketHex(F( "eDP: AccCmd: "), pDccMsg); +#endif + + BoardAddress = ( ( (~pDccMsg->Data[1]) & 0b01110000 ) << 2 ) | ( pDccMsg->Data[0] & 0b00111111 ) ; + TurnoutPairIndex = (pDccMsg->Data[1] & 0b00000110) >> 1; + DB_PRINT("eDP: BAddr:%d, Index:%d", BoardAddress, TurnoutPairIndex); + + // First check for Legacy Accessory Decoder Configuration Variable Access Instruction + // as it's got a different format to the others + if((pDccMsg->Size == 5) && ((pDccMsg->Data[1] & 0b10001100) == 0b00001100)) + { + DB_PRINT( "eDP: Legacy Accessory Decoder CV Access Command"); + // Check if this command is for our address or the broadcast address + if((BoardAddress != getMyAddr()) && ( BoardAddress < 511 )) + { + DB_PRINT("eDP: Board Address Not Matched"); + return; + } + + uint16_t cvAddress = ((pDccMsg->Data[1] & 0b00000011) << 8) + pDccMsg->Data[2] + 1; + uint8_t cvValue = pDccMsg->Data[3]; + DB_PRINT("eDP: CV:%d Value:%d", cvAddress, cvValue ); + if(validCV( cvAddress, 1 )) + writeCV(cvAddress, cvValue); + return; + } + + + OutputAddress = (((BoardAddress - 1) << 2 ) | TurnoutPairIndex) + 1 ; //decoder output addresses start with 1, packet address range starts with 0 + // ( according to NMRA 9.2.2 ) + DB_PRINT("eDP: OAddr:%d", OutputAddress); + + if( DccProcState.inAccDecDCCAddrNextReceivedMode) + { + if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) + { + DB_PRINT("eDP: Set OAddr:%d", OutputAddress); + //uint16_t storedOutputAddress = OutputAddress + 1; // The value stored in CV1 & 9 for Output Addressing Mode is + 1 + writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(OutputAddress % 256)); + writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(OutputAddress / 256)); + + if( notifyDccAccOutputAddrSet ) + notifyDccAccOutputAddrSet(OutputAddress); + } + else + { + DB_PRINT("eDP: Set BAddr:%d", BoardAddress); + writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(BoardAddress % 64)); + writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(BoardAddress / 64)); + + if( notifyDccAccBoardAddrSet ) + notifyDccAccBoardAddrSet(BoardAddress); + } + + DccProcState.inAccDecDCCAddrNextReceivedMode = 0; // Reset the mode now that we have set the address + } + + // If we're filtering addresses, does the address match our address or is it a broadcast address? If NOT then return + if( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) + { + if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) { + DB_PRINT(" AddrChk: OAddr:%d, BAddr:%d, myAddr:%d Chk=%d", OutputAddress, BoardAddress, getMyAddr(), OutputAddress != getMyAddr() ); + if ( OutputAddress != getMyAddr() && OutputAddress < 2045 ) { + DB_PRINT(" eDP: OAddr:%d, myAddr:%d - no match", OutputAddress, getMyAddr() ); + return; + } + } else { + if( ( BoardAddress != getMyAddr() ) && ( BoardAddress < 511 ) ) { + DB_PRINT(" eDP: BAddr:%d, myAddr:%d - no match", BoardAddress, getMyAddr() ); + return; + } + } + DB_PRINT("eDP: Address Matched"); + } + + + if((pDccMsg->Size == 4) && ((pDccMsg->Data[1] & 0b10001001) == 1)) // Extended Accessory Decoder Control Packet Format + { + // According to the NMRA Dcc Spec the Signal State should only use the lower 5 Bits, + // however some manufacturers seem to allow/use all 8 bits, so we'll relax that constraint for now + uint8_t state = pDccMsg->Data[2] ; + DB_PRINT("eDP: OAddr:%d Extended State:%0X", OutputAddress, state); + if( notifyDccSigOutputState ) + notifyDccSigOutputState(OutputAddress, state); + + // old callback ( for compatibility with 1.4.2, not to be used in new designs ) + if( notifyDccSigState ) + notifyDccSigState( OutputAddress, TurnoutPairIndex, pDccMsg->Data[2] ) ; + } + + else if(pDccMsg->Size == 3) // Basic Accessory Decoder Packet Format + { + uint8_t direction = pDccMsg->Data[1] & 0b00000001; + uint8_t outputPower = (pDccMsg->Data[1] & 0b00001000) >> 3; + + // old callback ( for compatibility with 1.4.2, not to be used in new designs ) + if ( notifyDccAccState ) + notifyDccAccState( OutputAddress, BoardAddress, pDccMsg->Data[1] & 0b00000111, outputPower ); + + if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) + { + DB_PRINT("eDP: OAddr:%d Turnout Dir:%d Output Power:%d", OutputAddress, direction, outputPower); + if( notifyDccAccTurnoutOutput ) + notifyDccAccTurnoutOutput( OutputAddress, direction, outputPower ); + } + else + { + DB_PRINT("eDP: Turnout Pair Index:%d Dir:%d Output Power: ", TurnoutPairIndex, direction, outputPower); + if( notifyDccAccTurnoutBoard ) + notifyDccAccTurnoutBoard( BoardAddress, TurnoutPairIndex, direction, outputPower ); + } + } + else if(pDccMsg->Size == 6) // Accessory Decoder OPS Mode Programming + { + DB_PRINT("eDP: OPS Mode CV Programming Command"); + // Check for unsupported OPS Mode Addressing mode + if(((pDccMsg->Data[1] & 0b10001001) != 1) && ((pDccMsg->Data[1] & 0b10001111) != 0x80)) + { + DB_PRINT("eDP: Unsupported OPS Mode CV Addressing Mode"); + return; + } + + // Check if this command is for our address or the broadcast address + if(DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE) + { + DB_PRINT("eDP: Check Output Address:%d", OutputAddress); + if((OutputAddress != getMyAddr()) && ( OutputAddress < 2045 )) + { + DB_PRINT("eDP: Output Address Not Matched"); + return; + } + } + else + { + DB_PRINT("eDP: Check Board Address:%d", BoardAddress); + if((BoardAddress != getMyAddr()) && ( BoardAddress < 511 )) + { + DB_PRINT("eDP: Board Address Not Matched"); + return; + } + } + + uint16_t cvAddress = ((pDccMsg->Data[2] & 0b00000011) << 8) + pDccMsg->Data[3] + 1; + uint8_t cvValue = pDccMsg->Data[4]; + + OpsInstructionType insType = (OpsInstructionType)((pDccMsg->Data[2] & 0b00001100) >> 2) ; + + DB_PRINT("eDP: OPS Mode Instruction:%d", insType); + switch(insType) + { + case OPS_INS_RESERVED: + case OPS_INS_VERIFY_BYTE: + DB_PRINT("eDP: Unsupported OPS Mode Instruction:%d", insType); + break; // We only support Write Byte or Bit Manipulation + + case OPS_INS_WRITE_BYTE: + DB_PRINT("eDP: CV:%d Value:%d", cvAddress, cvValue); + if(validCV( cvAddress, 1 )) + writeCV(cvAddress, cvValue); + break; + + // 111CDBBB + // Where BBB represents the bit position within the CV, + // D contains the value of the bit to be verified or written, + // and C describes whether the operation is a verify bit or a write bit operation. + // C = "1" WRITE BIT + // C = "0" VERIFY BIT + case OPS_INS_BIT_MANIPULATION: + // Make sure its a Write Bit Manipulation + if((cvValue & 0b00010000) && validCV(cvAddress, 1 )) + { + uint8_t currentValue = readCV(cvAddress); + uint8_t newValueMask = 1 << (cvValue & 0b00000111); + if(cvValue & 0b00001000) + writeCV(cvAddress, currentValue | newValueMask); + else + writeCV(cvAddress, currentValue & ~newValueMask); + } + break; + } + } + } + } + +#ifdef NMRA_DCC_PROCESS_MULTIFUNCTION + // Multi Function Decoders (14-bit address) + else if( pDccMsg->Data[0] < 232 ) + { + uint16_t Address ; + Address = ( ( pDccMsg->Data[0] - 192 ) << 8 ) | pDccMsg->Data[1]; + //TODO should we convert Address to 1 .. 10239 ? + processMultiFunctionMessage( Address, DCC_ADDR_LONG, pDccMsg->Data[2], pDccMsg->Data[3], pDccMsg->Data[4] ) ; + } +#endif +#ifdef NMRA_DCC_PROCESS_SERVICEMODE + } +#endif + } +} + +//////////////////////////////////////////////////////////////////////// +NmraDcc::NmraDcc() +{ +} + +#ifdef digitalPinToInterrupt +void NmraDcc::pin( uint8_t ExtIntPinNum, uint8_t EnablePullup) +{ + pin(digitalPinToInterrupt(ExtIntPinNum), ExtIntPinNum, EnablePullup); +} +#endif + +void NmraDcc::pin( uint8_t ExtIntNum, uint8_t ExtIntPinNum, uint8_t EnablePullup) +{ +#if defined ( __STM32F1__ ) + // with STM32F1 the interuptnumber is equal the pin number + DccProcState.ExtIntNum = ExtIntPinNum; +#else + DccProcState.ExtIntNum = ExtIntNum; +#endif + DccProcState.ExtIntPinNum = ExtIntPinNum; + + pinMode( ExtIntPinNum, INPUT ); + if( EnablePullup ) + digitalWrite(ExtIntPinNum, HIGH); +} + +//////////////////////////////////////////////////////////////////////// +void NmraDcc::initAccessoryDecoder( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ) +{ + init(ManufacturerId, VersionId, Flags | FLAGS_DCC_ACCESSORY_DECODER, OpsModeAddressBaseCV); +} + +//////////////////////////////////////////////////////////////////////// +void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ) +{ + #if defined(ESP8266) + EEPROM.begin(MAXCV); + #endif + #if defined(ESP32) + EEPROM.begin(MAXCV); + #endif + // Clear all the static member variables + memset( &DccRx, 0, sizeof( DccRx) ); + + MODE_TP1; // only for debugging and timing measurement + MODE_TP2; + MODE_TP3; + MODE_TP4; + bitMax = MAX_ONEBITFULL; + bitMin = MIN_ONEBITFULL; + + DccProcState.Flags = Flags ; + DccProcState.OpsModeAddressBaseCV = OpsModeAddressBaseCV ; + DccProcState.myDccAddress = -1; + DccProcState.inAccDecDCCAddrNextReceivedMode = 0; + + ISREdge = RISING; + + #ifdef ESP32 + ISRWatch = ISREdge; + attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE); + #else + attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, RISING); + #endif + + // Set the Bits that control Multifunction or Accessory behaviour + // and if the Accessory decoder optionally handles Output Addressing + // we need to peal off the top two bits + writeCV( CV_29_CONFIG, ( readCV( CV_29_CONFIG ) & ~FLAGS_CV29_BITS ) | (Flags & FLAGS_CV29_BITS) ) ; + + uint8_t doAutoFactoryDefault = 0; + if((Flags & FLAGS_AUTO_FACTORY_DEFAULT) && (readCV(CV_VERSION_ID) == 255) && (readCV(CV_MANUFACTURER_ID) == 255)) + doAutoFactoryDefault = 1; + + writeCV( CV_VERSION_ID, VersionId ) ; + writeCV( CV_MANUFACTURER_ID, ManufacturerId ) ; + + clearDccProcState( 0 ); + + if(notifyCVResetFactoryDefault && doAutoFactoryDefault) + notifyCVResetFactoryDefault(); +} + +//////////////////////////////////////////////////////////////////////// +uint8_t NmraDcc::getCV( uint16_t CV ) +{ + return readCV(CV); +} + +//////////////////////////////////////////////////////////////////////// +uint8_t NmraDcc::setCV( uint16_t CV, uint8_t Value) +{ + DccProcState.Flags |= FLAGS_SETCV_CALLED; + + uint8_t returnValue = writeCV(CV,Value); + + DccProcState.Flags &= ~FLAGS_SETCV_CALLED; + + return returnValue; +} + +//////////////////////////////////////////////////////////////////////// +uint16_t NmraDcc::getAddr(void) +{ + return getMyAddr(); +} + +//////////////////////////////////////////////////////////////////////// +uint8_t NmraDcc::isSetCVReady(void) +{ + if(notifyIsSetCVReady) + return notifyIsSetCVReady(); + return readyEEPROM(); +} + +//////////////////////////////////////////////////////////////////////// +#ifdef DCC_DEBUG +uint8_t NmraDcc::getIntCount(void) +{ + return DccProcState.IntCount; +} + +//////////////////////////////////////////////////////////////////////// +uint8_t NmraDcc::getTickCount(void) +{ + return DccProcState.TickCount; +} + +//////////////////////////////////////////////////////////////////////// +uint8_t NmraDcc::getNestedIrqCount(void) +{ + return DccProcState.NestedIrqCount; +} + +//////////////////////////////////////////////////////////////////////// +uint8_t NmraDcc::getState(void) +{ + return DccRx.State; +} + +//////////////////////////////////////////////////////////////////////// +uint8_t NmraDcc::getBitCount(void) +{ + return DccRx.BitCount; +} +#endif + +//////////////////////////////////////////////////////////////////////// +void NmraDcc::setAccDecDCCAddrNextReceived(uint8_t enable) +{ + DccProcState.inAccDecDCCAddrNextReceivedMode = enable; +} + +//////////////////////////////////////////////////////////////////////// +uint8_t NmraDcc::process() +{ + if( DccProcState.inServiceMode ) + { + if( (millis() - DccProcState.LastServiceModeMillis ) > 20L ) + { + clearDccProcState( 0 ) ; + } + } + + if( DccRx.DataReady ) + { + // We need to do this check with interrupts disabled + //SET_TP4; +#ifdef ESP32 + portENTER_CRITICAL(&mux); +#else + noInterrupts(); +#endif + Msg = DccRx.PacketCopy ; + DccRx.DataReady = 0 ; + +#ifdef ESP32 + portEXIT_CRITICAL(&mux); +#else + interrupts(); +#endif + #ifdef DCC_DBGVAR + countOf.Tel++; + #endif + + uint8_t xorValue = 0 ; + + for(uint8_t i = 0; i < DccRx.PacketCopy.Size; i++) + xorValue ^= DccRx.PacketCopy.Data[i]; + if(xorValue) { + #ifdef DCC_DBGVAR + DB_PRINT("Cerr"); + countOf.Err++; + #endif + return 0 ; + } else { + if( notifyDccMsg ) notifyDccMsg( &Msg ); + + execDccProcessor( &Msg ); + } + return 1 ; + } + + return 0 ; +}; diff --git a/app/include/driver/NmraDcc.h b/app/include/driver/NmraDcc.h new file mode 100644 index 0000000000..527757388d --- /dev/null +++ b/app/include/driver/NmraDcc.h @@ -0,0 +1,721 @@ +//------------------------------------------------------------------------ +// +// Model Railroading with Arduino - NmraDcc.h +// +// Copyright (c) 2008 - 2018 Alex Shepherd +// +// This source file is subject of the GNU general public license 2, +// that is available at the world-wide-web at +// http://www.gnu.org/licenses/gpl.txt +// +//------------------------------------------------------------------------ +// +// file: NmraDcc.h +// author: Alex Shepherd +// webpage: http://mrrwa.org/ +// history: 2008-03-20 Initial Version +// 2011-06-26 Migrated into Arduino library from OpenDCC codebase +// 2014 Added getAddr to NmraDcc Geoff Bunza +// 2015-11-06 Martin Pischky (martin@pischky.de): +// Experimental Version to support 14 speed steps +// and new signature of notifyDccSpeed and notifyDccFunc +// 2017-11-29 Ken West (kgw4449@gmail.com): +// Added method and callback headers. +// +//------------------------------------------------------------------------ +// +// purpose: Provide a simplified interface to decode NMRA DCC packets +// and build DCC MutliFunction and Stationary Decoders +// +//------------------------------------------------------------------------ + +// Uncomment the following Line to Enable Service Mode CV Programming +#define NMRA_DCC_PROCESS_SERVICEMODE + +// Uncomment the following line to Enable MultiFunction Decoder Operations +#define NMRA_DCC_PROCESS_MULTIFUNCTION + +// Uncomment the following line to Enable 14 Speed Step Support +//#define NMRA_DCC_ENABLE_14_SPEED_STEP_MODE + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include "EEPROM.h" + +#ifndef NMRADCC_IS_IN +#define NMRADCC_IS_IN + +#define NMRADCC_VERSION 201 // Version 2.0.1 + +#define MAX_DCC_MESSAGE_LEN 6 // including XOR-Byte + +typedef struct +{ + uint8_t Size ; + uint8_t PreambleBits ; + uint8_t Data[MAX_DCC_MESSAGE_LEN] ; +} DCC_MSG ; + +//-------------------------------------------------------------------------- +// This section contains the NMRA Assigned DCC Manufacturer Id Codes that +// are used in projects +// +// This value is to be used for CV8 +//-------------------------------------------------------------------------- + +#define MAN_ID_JMRI 0x12 +#define MAN_ID_DIY 0x0D +#define MAN_ID_SILICON_RAILWAY 0x21 + +//-------------------------------------------------------------------------- +// This section contains the Product/Version Id Codes for projects +// +// This value is to be used for CV7 +// +// NOTE: Each Product/Version Id Code needs to be UNIQUE for that particular +// the DCC Manufacturer Id Code +//-------------------------------------------------------------------------- + +// Product/Version Id Codes allocated under: MAN_ID_JMRI + +// Product/Version Id Codes allocated under: MAN_ID_DIY + +// Standard CV Addresses +#define CV_ACCESSORY_DECODER_ADDRESS_LSB 1 +#define CV_ACCESSORY_DECODER_ADDRESS_MSB 9 + +#define CV_MULTIFUNCTION_PRIMARY_ADDRESS 1 +#define CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB 17 +#define CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB 18 + +#define CV_VERSION_ID 7 +#define CV_MANUFACTURER_ID 8 +#define CV_29_CONFIG 29 + +#if defined(ESP32) + #include + #define MAXCV SPI_FLASH_SEC_SIZE +#elif defined(ESP8266) + #include + #define MAXCV SPI_FLASH_SEC_SIZE +#elif defined( __STM32F1__) + #define MAXCV (EEPROM_PAGE_SIZE/4 - 1) // number of storage places (CV address could be larger + // because STM32 uses virtual adresses) +#else + #define MAXCV E2END // the upper limit of the CV value currently defined to max memory. +#endif + +typedef enum { + CV29_LOCO_DIR = 0b00000001, /** bit 0: Locomotive Direction: "0" = normal, "1" = reversed */ + CV29_F0_LOCATION = 0b00000010, /** bit 1: F0 location: "0" = bit 4 in Speed and Direction instructions, "1" = bit 4 in function group one instruction */ + CV29_APS = 0b00000100, /** bit 2: Alternate Power Source (APS) "0" = NMRA Digital only, "1" = Alternate power source set by CV12 */ + CV29_ADV_ACK = 0b00001000, /** bit 3: ACK, Advanced Acknowledge mode enabled if 1, disabled if 0 */ + CV29_SPEED_TABLE_ENABLE = 0b00010000, /** bit 4: STE, Speed Table Enable, "0" = values in CVs 2, 4 and 6, "1" = Custom table selected by CV 25 */ + CV29_EXT_ADDRESSING = 0b00100000, /** bit 5: "0" = one byte addressing, "1" = two byte addressing */ + CV29_OUTPUT_ADDRESS_MODE = 0b01000000, /** bit 6: "0" = Decoder Address Mode "1" = Output Address Mode */ + CV29_ACCESSORY_DECODER = 0b10000000, /** bit 7: "0" = Multi-Function Decoder Mode "1" = Accessory Decoder Mode */ +} CV_29_BITS; + +typedef enum { +#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE + SPEED_STEP_14 = 15, /**< ESTOP=0, 1 to 15 */ +#endif + SPEED_STEP_28 = 29, /**< ESTOP=0, 1 to 29 */ + SPEED_STEP_128 = 127 /**< ESTOP=0, 1 to 127 */ +} DCC_SPEED_STEPS; + +typedef enum { + DCC_DIR_REV = 0, /** The locomotive to go in the reverse direction */ + DCC_DIR_FWD = 1, /** The locomotive should move in the forward direction */ +} DCC_DIRECTION; + +typedef enum { + DCC_ADDR_SHORT, /** Short address is used. The range is 0 to 127. */ + DCC_ADDR_LONG, /** Long Address is used. The range is 1 to 10239 */ +} DCC_ADDR_TYPE; + +typedef enum +{ + FN_0_4 = 1, + FN_5_8, + FN_9_12, + FN_13_20, + FN_21_28, +#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE + FN_0 /** function light is controlled by base line package (14 speed steps) */ +#endif +} FN_GROUP; + +#define FN_BIT_00 0x10 +#define FN_BIT_01 0x01 +#define FN_BIT_02 0x02 +#define FN_BIT_03 0x04 +#define FN_BIT_04 0x08 + +#define FN_BIT_05 0x01 +#define FN_BIT_06 0x02 +#define FN_BIT_07 0x04 +#define FN_BIT_08 0x08 + +#define FN_BIT_09 0x01 +#define FN_BIT_10 0x02 +#define FN_BIT_11 0x04 +#define FN_BIT_12 0x08 + +#define FN_BIT_13 0x01 +#define FN_BIT_14 0x02 +#define FN_BIT_15 0x04 +#define FN_BIT_16 0x08 +#define FN_BIT_17 0x10 +#define FN_BIT_18 0x20 +#define FN_BIT_19 0x40 +#define FN_BIT_20 0x80 + +#define FN_BIT_21 0x01 +#define FN_BIT_22 0x02 +#define FN_BIT_23 0x04 +#define FN_BIT_24 0x08 +#define FN_BIT_25 0x10 +#define FN_BIT_26 0x20 +#define FN_BIT_27 0x40 +#define FN_BIT_28 0x80 + +//#define DCC_DBGVAR +#ifdef DCC_DBGVAR +typedef struct countOf_t { + unsigned long Tel; + unsigned long Err; +}countOf_t ; + +extern struct countOf_t countOf; +#endif + +class NmraDcc +{ + private: + DCC_MSG Msg ; + + public: + NmraDcc(); + +// Flag values to be logically ORed together and passed into the init() method +#define FLAGS_MY_ADDRESS_ONLY 0x01 // Only process DCC Packets with My Address +#define FLAGS_AUTO_FACTORY_DEFAULT 0x02 // Call notifyCVResetFactoryDefault() if CV 7 & 8 == 255 +#define FLAGS_SETCV_CALLED 0x10 // only used internally !! +#define FLAGS_OUTPUT_ADDRESS_MODE 0x40 // CV 29/541 bit 6 +#define FLAGS_DCC_ACCESSORY_DECODER 0x80 // CV 29/541 bit 7 + +// Flag Bits that are cloned from CV29 relating the DCC Accessory Decoder +#define FLAGS_CV29_BITS (FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER) + + + /*+ + * pin() is called from setup() and sets up the pin used to receive DCC packets. + * + * Inputs: + * ExtIntNum - Interrupt number of the pin. Use digitalPinToInterrupt(ExtIntPinNum). + * ExtIntPinNum - Input pin number. + * EnablePullup - Set true to enable the pins pullup resistor. + * + * Returns: + * None. + */ + void pin( uint8_t ExtIntNum, uint8_t ExtIntPinNum, uint8_t EnablePullup); + + /*+ + * pin() is called from setup() and sets up the pin used to receive DCC packets. + * This relies on the internal function: digitalPinToInterrupt() to map the input pin number to the right interrupt + * + * Inputs: + * ExtIntPinNum - Input pin number. + * EnablePullup - Set true to enable the pins pullup resistor. + * + * Returns: + * None. + */ +#ifdef digitalPinToInterrupt +void pin( uint8_t ExtIntPinNum, uint8_t EnablePullup); +#endif + + /*+ + * init() is called from setup() after the pin() command is called. + * It initializes the NmDcc object and makes it ready to process packets. + * + * Inputs: + * ManufacturerId - Manufacturer ID returned in CV 8. + * Commonly MAN_ID_DIY. + * VersionId - Version ID returned in CV 7. + * Flags - ORed flags beginning with FLAGS_... + * FLAGS_MY_ADDRESS_ONLY - Only process packets with My Address. + * FLAGS_DCC_ACCESSORY_DECODER - Decoder is an accessory decoder. + * FLAGS_OUTPUT_ADDRESS_MODE - This flag applies to accessory decoders only. + * Accessory decoders normally have 4 paired outputs + * and a single address refers to all 4 outputs. + * Setting FLAGS_OUTPUT_ADDRESS_MODE causes each + * address to refer to a single output. + * OpsModeAddressBaseCV - Ops Mode base address. Set it to 0? + * + * Returns: + * None. + */ + void init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ); + + /*+ + * initAccessoryDecoder() is called from setup() for accessory decoders. + * It calls init() with FLAGS_DCC_ACCESSORY_DECODER ORed into Flags. + * + * Inputs: + * ManufacturerId - Manufacturer ID returned in CV 8. + * Commonly MAN_ID_DIY. + * VersionId - Version ID returned in CV 7. + * Flags - ORed flags beginning with FLAGS_... + * FLAGS_DCC_ACCESSORY_DECODER will be set for init() call. + * OpsModeAddressBaseCV - Ops Mode base address. Set it to 0? + * + * Returns: + * None. + */ + void initAccessoryDecoder( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ); + + /*+ + * process() is called from loop() to process DCC packets. + * It must be called very frequently to keep up with the packets. + * + * Inputs: + * None. + * + * Returns: + * 1 - Packet succesfully parsed on this call to process(). + * 0 - Packet not ready or received packet had an error. + */ + uint8_t process(); + + /*+ + * getCV() returns the selected CV value. + * + * Inputs: + * CV - CV number. It must point to a valid CV. + * + * Returns: + * Value - CV value. Invalid CV numbers will return an undefined result + * since nothing will have been set in that EEPROM position. + * Calls notifyCVRead() if it is defined. + */ + uint8_t getCV( uint16_t CV ); + + /*+ + * setCV() sets the value of a CV. + * + * Inputs: + * CV - CV number. It must point to a valid CV. + * Value - CV value. + * + * Returns: + * Value - CV value set by this call. + * since nothing will have been set in that EEPROM position. + * Calls notifyCVWrite() if it is defined. + * Calls notifyCVChange() if the value is changed by this call. + */ + uint8_t setCV( uint16_t CV, uint8_t Value); + + /*+ + * setAccDecDCCAddrNextReceived() enables/disables the setting of the board address from the next received turnout command + * + * Inputs: + * enable- boolean to enable or disable the mode + * + * Returns: + */ + void setAccDecDCCAddrNextReceived(uint8_t enable); + + /*+ + * isSetCVReady() returns 1 if EEPROM is ready to write. + * + * Inputs: + * CV - CV number. It must point to a valid CV. + * Value - CV value. + * + * Returns: + * ready - 1 if ready to write, 0 otherwise. AVR processor will block + * for several ms. for each write cycle so you should check this to avoid blocks. + * Note: It returns the value returned by notifyIsSetCVReady() if it is defined. + * Calls notifyIsSetCVReady() if it is defined. + */ + uint8_t isSetCVReady( void ); + + /*+ + * getAddr() return the currently active decoder address. + * based on decoder type and current address size. + * + * Inputs: + * None. + * + * Returns: + * Adr - The current decoder address based on decoder type(Multifunction, Accessory) + * and short or long address selection for Multifunction decoders. + */ + uint16_t getAddr(void); + + /*+ + * getX() return debugging data if DCC_DEBUG is defined. + * You would really need to be modifying the library to need them. + * + * Inputs: + * None. + * + * Returns: + * getIntCount - Init to 0 and apparently never incremented? + * getTickCount - Init to 0 and incremented each time interrupt handler + * completes without an error. + * getBitCount - Bit count of valid packet, 0 otherwise. Only valid until + * start of the next packet. + * getState - Current WAIT_... state as defined by DccRxWaitState in NmraDcc.cpp. + * getNestedIrqCount - Init to 0 and incremented each time the interrupt handler + * is called before the previous interrupt was complete. + * This is an error indication and may indicate the system + * is not handling packets fast enough or some other error is occurring. + */ +// #define DCC_DEBUG +#ifdef DCC_DEBUG + uint8_t getIntCount(void); + uint8_t getTickCount(void); + uint8_t getBitCount(void); + uint8_t getState(void); + uint8_t getNestedIrqCount(void); +#endif + +}; + +/************************************************************************************ + Call-back functions +************************************************************************************/ + +#if defined (__cplusplus) + extern "C" { +#endif + +/*+ + * notifyDccReset(uint8_t hardReset) Callback for a DCC reset command. + * + * Inputs: + * hardReset - 0 normal reset command. + * 1 hard reset command. + * + * Returns: + * None + */ +extern void notifyDccReset(uint8_t hardReset ) __attribute__ ((weak)); + +/*+ + * notifyDccIdle() Callback for a DCC idle command. + * + * Inputs: + * None + * + * Returns: + * None + */ +extern void notifyDccIdle(void) __attribute__ ((weak)); + + +/*+ + * notifyDccSpeed() Callback for a multifunction decoder speed command. + * The received speed and direction are unpacked to separate values. + * + * Inputs: + * Addr - Active decoder address. + * AddrType - DCC_ADDR_SHORT or DCC_ADDR_LONG. + * Speed - Decoder speed. 0 = Emergency stop + * 1 = Regular stop + * 2 to SpeedSteps = Speed step 1 to max. + * Dir - DCC_DIR_REV or DCC_DIR_FWD + * SpeedSteps - Highest speed, SPEED_STEP_14 = 15 + * SPEED_STEP_28 = 29 + * SPEED_STEP_128 = 127 + * + * Returns: + * None + */ +extern void notifyDccSpeed( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps ) __attribute__ ((weak)); + +/*+ + * notifyDccSpeedRaw() Callback for a multifunction decoder speed command. + * The value in Raw is the unpacked speed command. + * + * Inputs: + * Addr - Active decoder address. + * AddrType - DCC_ADDR_SHORT or DCC_ADDR_LONG. + * Raw - Raw decoder speed command. + * + * Returns: + * None + */ +extern void notifyDccSpeedRaw( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Raw) __attribute__ ((weak)); + +/*+ + * notifyDccFunc() Callback for a multifunction decoder function command. + * + * Inputs: + * Addr - Active decoder address. + * AddrType - DCC_ADDR_SHORT or DCC_ADDR_LONG. + * FuncGrp - Function group. FN_0 - 14 speed step headlight function. + * Mask FN_BIT_00. + * FN_0_4 - Functions 0 to 4. Mask FN_BIT_00 - FN_BIT_04 + * FN_5_8 - Functions 5 to 8. Mask FN_BIT_05 - FN_BIT_08 + * FN_9_12 - Functions 9 to 12. Mask FN_BIT_09 - FN_BIT_12 + * FN_13_20 - Functions 13 to 20. Mask FN_BIT_13 - FN_BIT_20 + * FN_21_28 - Functions 21 to 28. Mask FN_BIT_21 - FN_BIT_28 + * FuncState - Function state. Bitmask where active functions have a 1 at that bit. + * You must & FuncState with the appropriate + * FN_BIT_nn value to isolate a given bit. + * + * Returns: + * None + */ +extern void notifyDccFunc( uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState) __attribute__ ((weak)); + +/*+ + * notifyDccAccTurnoutBoard() Board oriented callback for a turnout accessory decoder. + * Most useful when CV29_OUTPUT_ADDRESS_MODE is not set. + * Decoders of this type have 4 paired turnout outputs per board. + * OutputPower is 1 if the power is on, and 0 otherwise. + * + * Inputs: + * BoardAddr - Per board address. Equivalent to CV 1 LSB & CV 9 MSB. + * OutputPair - Output pair number. It has a range of 0 to 3. + * Equivalent to upper 2 bits of the 3 DDD bits in the accessory packet. + * Direction - Turnout direction. It has a value of 0 or 1. + * It is equivalent to bit 0 of the 3 DDD bits in the accessory packet. + * OutputPower - Output On/Off. Equivalent to packet C bit. It has these values: + * 0 - Output pair is off. + * 1 - Output pair is on. + * + * Returns: + * None + */ + +extern void notifyDccAccTurnoutBoard( uint16_t BoardAddr, uint8_t OutputPair, uint8_t Direction, uint8_t OutputPower ) __attribute__ ((weak)); +/*+ + * notifyDccAccTurnoutOutput() Output oriented callback for a turnout accessory decoder. + * Most useful when CV29_OUTPUT_ADDRESS_MODE is not set. + * Decoders of this type have 4 paired turnout outputs per board. + * OutputPower is 1 if the power is on, and 0 otherwise. + * + * Inputs: + * Addr - Per output address. There will be 4 Addr addresses + * per board for a standard accessory decoder with 4 output pairs. + * Direction - Turnout direction. It has a value of 0 or 1. + * Equivalent to bit 0 of the 3 DDD bits in the accessory packet. + * OutputPower - Output On/Off. Equivalent to packet C bit. It has these values: + * 0 - Output is off. + * 1 - Output is on. + * + * Returns: + * None + */ +extern void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) __attribute__ ((weak)); + +/*+ + * notifyDccAccBoardAddrSet() Board oriented callback for a turnout accessory decoder. + * This notification is when a new Board Address is set to the + * address of the next DCC Turnout Packet that is received + * + * This is enabled via the setAccDecDCCAddrNextReceived() method above + * + * Inputs: + * BoardAddr - Per board address. Equivalent to CV 1 LSB & CV 9 MSB. + * per board for a standard accessory decoder with 4 output pairs. + * + * Returns: + * None + */ +extern void notifyDccAccBoardAddrSet( uint16_t BoardAddr) __attribute__ ((weak)); + +/*+ + * notifyDccAccOutputAddrSet() Output oriented callback for a turnout accessory decoder. + * This notification is when a new Output Address is set to the + * address of the next DCC Turnout Packet that is received + * + * This is enabled via the setAccDecDCCAddrNextReceived() method above + * + * Inputs: + * Addr - Per output address. There will be 4 Addr addresses + * per board for a standard accessory decoder with 4 output pairs. + * + * Returns: + * None + */ +extern void notifyDccAccOutputAddrSet( uint16_t Addr) __attribute__ ((weak)); + +/*+ + * notifyDccSigOutputState() Callback for a signal aspect accessory decoder. + * Defined in S-9.2.1 as the Extended Accessory Decoder Control Packet. + * + * Inputs: + * Addr - Decoder address. + * State - 6 bit command equivalent to S-9.2.1 00XXXXXX. + * + * Returns: + * None + */ +extern void notifyDccSigOutputState( uint16_t Addr, uint8_t State) __attribute__ ((weak)); + +/*+ + * notifyDccMsg() Raw DCC packet callback. + * Called with raw DCC packet bytes. + * + * Inputs: + * Msg - Pointer to DCC_MSG structure. The values are: + * Msg->Size - Number of Data bytes in the packet. + * Msg->PreambleBits - Number of preamble bits in the packet. + * Msg->Data[] - Array of data bytes in the packet. + * + * Returns: + * None + */ +extern void notifyDccMsg( DCC_MSG * Msg ) __attribute__ ((weak)); + +/*+ + * notifyCVValid() Callback to determine if a given CV is valid. + * This is called when the library needs to determine + * if a CV is valid. Note: If defined, this callback + * MUST determine if a CV is valid and return the + * appropriate value. If this callback is not defined, + * the library will determine validity. + * + * Inputs: + * CV - CV number. + * Writable - 1 for CV writes. 0 for CV reads. + * + * Returns: + * 1 - CV is valid. + * 0 - CV is not valid. + */ +extern uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) __attribute__ ((weak)); + +/*+ + * notifyCVRead() Callback to read a CV. + * This is called when the library needs to read + * a CV. Note: If defined, this callback + * MUST return the value of the CV. + * If this callback is not defined, + * the library will read the CV from EEPROM. + * + * Inputs: + * CV - CV number. + * + * Returns: + * Value - Value of the CV. + */ +extern uint8_t notifyCVRead( uint16_t CV) __attribute__ ((weak)); + +/*+ + * notifyCVWrite() Callback to write a value to a CV. + * This is called when the library needs to write + * a CV. Note: If defined, this callback + * MUST write the Value to the CV and return the value of the CV. + * If this callback is not defined, + * the library will read the CV from EEPROM. + * + * Inputs: + * CV - CV number. + * Value - Value of the CV. + * + * Returns: + * Value - Value of the CV. + */ +extern uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) __attribute__ ((weak)); + +/*+ + * notifyIsSetCVReady() Callback to to determine if CVs can be written. + * This is called when the library needs to determine + * is ready to write without blocking or failing. + * Note: If defined, this callback + * MUST determine if a CV write would block or fail + * return the appropriate value. + * If this callback is not defined, + * the library determines if a write to the EEPROM + * would block. + * + * Inputs: + * None + * + * Returns: + * 1 - CV is ready to be written. + * 0 - CV is not ready to be written. + */ +extern uint8_t notifyIsSetCVReady(void) __attribute__ ((weak)); + +/*+ + * notifyCVChange() Called when a CV value is changed. + * This is called whenever a CV's value is changed. + * notifyDccCVChange() Called only when a CV value is changed by a Dcc packet or a internal lib function. + * it is NOT called if the CV is changed by means of the setCV() method. + * Note: It is not called if notifyCVWrite() is defined + * or if the value in the EEPROM is the same as the value + * in the write command. + * + * Inputs: + * CV - CV number. + * Value - Value of the CV. + * + * Returns: + * None + */ +extern void notifyCVChange( uint16_t CV, uint8_t Value) __attribute__ ((weak)); +extern void notifyDccCVChange( uint16_t CV, uint8_t Value) __attribute__ ((weak)); + +/*+ + * notifyCVResetFactoryDefault() Called when CVs must be reset. + * This is called when CVs must be reset + * to their factory defaults. This callback + * should write the factory default value of + * relevent CVs using the setCV() method. + * setCV() must not block whens this is called. + * Test with isSetCVReady() prior to calling setCV() + * + * Inputs: + * None + * * + * Returns: + * None + */ +extern void notifyCVResetFactoryDefault(void) __attribute__ ((weak)); + +/*+ + * notifyCVAck() Called when a CV write must be acknowledged. + * This callback must increase the current drawn by this + * decoder by at least 60mA for 6ms +/- 1ms. + * + * Inputs: + * None + * * + * Returns: + * None + */ +extern void notifyCVAck(void) __attribute__ ((weak)); +/*+ + * notifyServiceMode(bool) Called when state of 'inServiceMode' changes + * + * Inputs: + * bool state of inServiceMode + * * + * Returns: + * None + */ +extern void notifyServiceMode(bool) __attribute__ ((weak)); + +// Deprecated, only for backward compatibility with version 1.4.2. +// Don't use in new designs. These functions may be dropped in future versions +extern void notifyDccAccState( uint16_t Addr, uint16_t BoardAddr, uint8_t OutputAddr, uint8_t State ) __attribute__ ((weak)); +extern void notifyDccSigState( uint16_t Addr, uint8_t OutputIndex, uint8_t State) __attribute__ ((weak)); + +#if defined (__cplusplus) +} +#endif + +#endif From ddae8bca0ad953e7259a7b80c968fb9f0dcd911a Mon Sep 17 00:00:00 2001 From: vsky Date: Wed, 24 Jul 2019 00:30:13 +0200 Subject: [PATCH 2/3] DCC module initial commit --- app/driver/NmraDcc.c | 1069 +++++++++++----------------------- app/include/driver/NmraDcc.h | 422 +++----------- app/include/user_modules.h | 1 + app/modules/dcc.c | 290 +++++++++ docs/modules/dcc.md | 118 ++++ lua_examples/dcc/dcc.lua | 86 +++ mkdocs.yml | 1 + 7 files changed, 925 insertions(+), 1062 deletions(-) create mode 100644 app/modules/dcc.c create mode 100644 docs/modules/dcc.md create mode 100644 lua_examples/dcc/dcc.lua diff --git a/app/driver/NmraDcc.c b/app/driver/NmraDcc.c index d35123326e..9fb70cb798 100644 --- a/app/driver/NmraDcc.c +++ b/app/driver/NmraDcc.c @@ -21,8 +21,8 @@ // and new signature of notifyDccSpeed and notifyDccFunc // 2015-12-16 Version without use of Timer0 by Franz-Peter Müller // 2016-07-16 handle glitches on DCC line -// 2016-08-20 added ESP8266 support by Sven (littleyoda) -// 2017-01-19 added STM32F1 support by Franz-Peter +// 2016-08-20 added ESP8266 support by Sven (littleyoda) +// 2017-01-19 added STM32F1 support by Franz-Peter // 2017-11-29 Ken West (kgw4449@gmail.com): // Minor fixes to pass NMRA Baseline Conformance Tests. // 2018-12-17 added ESP32 support by Trusty (thierry@lapajaparis.net) @@ -31,17 +31,34 @@ //------------------------------------------------------------------------ // // purpose: Provide a simplified interface to decode NMRA DCC packets -// and build DCC Mobile and Stationary Decoders +// and build DCC Mobile and Stationary Decoders // //------------------------------------------------------------------------ -#include "NmraDcc.h" -#ifdef __AVR_MEGA__ -#include -#endif +// NodeMCU Lua port by @voborsky + +// #define NODE_DEBUG + +#include +#include +#include +#include "platform.h" +#include "user_interface.h" +#include "task/task.h" +#include "driver/NmraDcc.h" + +#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" +#define BYTE_TO_BINARY(byte) \ + (byte & 0x80 ? '1' : '0'), \ + (byte & 0x40 ? '1' : '0'), \ + (byte & 0x20 ? '1' : '0'), \ + (byte & 0x10 ? '1' : '0'), \ + (byte & 0x08 ? '1' : '0'), \ + (byte & 0x04 ? '1' : '0'), \ + (byte & 0x02 ? '1' : '0'), \ + (byte & 0x01 ? '1' : '0') + -// Uncomment to print DEBUG messages -//#define DEBUG_PRINT //------------------------------------------------------------------------ // DCC Receive Routine @@ -88,6 +105,9 @@ // //------------------------------------------------------------------------ +#define abs(a) ((a) > 0 ? (a) : (0-a)) + + #define MAX_ONEBITFULL 146 #define MAX_PRAEAMBEL 146 #define MAX_ONEBITHALF 82 @@ -96,124 +116,25 @@ #define MAX_BITDIFF 18 -// Debug-Ports -//#define debug // Testpulse for logic analyser -#ifdef debug - #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) - #define MODE_TP1 DDRF |= (1<<2) //pinA2 - #define SET_TP1 PORTF |= (1<<2) - #define CLR_TP1 PORTF &= ~(1<<2) - #define MODE_TP2 DDRF |= (1<<3) //pinA3 - #define SET_TP2 PORTF |= (1<<3) - #define CLR_TP2 PORTF &= ~(1<<3) - #define MODE_TP3 DDRF |= (1<<4) //pinA4 - #define SET_TP3 PORTF |= (1<<4) - #define CLR_TP3 PORTF &= ~(1<<4) - #define MODE_TP4 DDRF |= (1<<5) //pinA5 - #define SET_TP4 PORTF |= (1<<5) - #define CLR_TP4 PORTF &= ~(1<<5) - #elif defined(__AVR_ATmega32U4__) - #define MODE_TP1 DDRF |= (1<<4) //A3 - #define SET_TP1 PORTF |= (1<<4) - #define CLR_TP1 PORTF &= ~(1<<4) - #define MODE_TP2 DDRF |= (1<<5) //A2 - #define SET_TP2 PORTF |= (1<<5) - #define CLR_TP2 PORTF &= ~(1<<5) - #define MODE_TP3 - #define SET_TP3 - #define CLR_TP3 - #define MODE_TP4 - #define SET_TP4 - #define CLR_TP4 - #elif defined(__AVR_ATmega328P__) - #define MODE_TP1 DDRC |= (1<<1) //A1 - #define SET_TP1 PORTC |= (1<<1) - #define CLR_TP1 PORTC &= ~(1<<1) - #define MODE_TP2 DDRC |= (1<<2) // A2 - #define SET_TP2 PORTC |= (1<<2) - #define CLR_TP2 PORTC &= ~(1<<2) - #define MODE_TP3 DDRC |= (1<<3) //A3 - #define SET_TP3 PORTC |= (1<<3) - #define CLR_TP3 PORTC &= ~(1<<3) - #define MODE_TP4 DDRC |= (1<<4) //A4 - #define SET_TP4 PORTC |= (1<<4) - #define CLR_TP4 PORTC &= ~(1<<4) - #elif defined(__arm__) && (defined(__MK20DX128__) || defined(__MK20DX256__)) - // Teensys 3.x - #define MODE_TP1 pinMode( A1,OUTPUT ) // A1= PortC, Bit0 - #define SET_TP1 GPIOC_PSOR = 0x01 - #define CLR_TP1 GPIOC_PCOR = 0x01 - #define MODE_TP2 pinMode( A2,OUTPUT ) // A2= PortB Bit0 - #define SET_TP2 GPIOB_PSOR = 0x01 - #define CLR_TP2 GPIOB_PCOR = 0x01 - #define MODE_TP3 pinMode( A3,OUTPUT ) // A3 = PortB Bit1 - #define SET_TP3 GPIOB_PSOR = 0x02 - #define CLR_TP3 GPIOB_PCOR = 0x02 - #define MODE_TP4 pinMode( A4,OUTPUT ) // A4 = PortB Bit3 - #define SET_TP4 GPIOB_PSOR = 0x08 - #define CLR_TP4 GPIOB_PCOR = 0x08 - #elif defined (__STM32F1__) - // STM32F103... - #define MODE_TP1 pinMode( PB12,OUTPUT ) // TP1= PB12 - #define SET_TP1 gpio_write_bit( GPIOB,12, HIGH ); - #define CLR_TP1 gpio_write_bit( GPIOB,12, LOW ); - #define MODE_TP2 pinMode( PB13,OUTPUT ) // TP2= PB13 - #define SET_TP2 gpio_write_bit( GPIOB,13, HIGH ); - #define CLR_TP2 gpio_write_bit( GPIOB,13, LOW ); - #define MODE_TP3 pinMode( PB14,OUTPUT ) // TP3 = PB14 - #define SET_TP3 gpio_write_bit( GPIOB,14, HIGH ); - #define CLR_TP3 gpio_write_bit( GPIOB,14, LOW ); - #define MODE_TP4 pinMode( PB15,OUTPUT ) // TP4 = PB15 - #define SET_TP4 gpio_write_bit( GPIOB,15, HIGH ); - #define CLR_TP4 gpio_write_bit( GPIOB,15, LOW ); - #elif defined(ESP8266) - #define D5 14 - #define D6 12 - #define D7 13 - #define D8 15 - #define MODE_TP1 pinMode( D5,OUTPUT ) ; // GPIO 14 - #define SET_TP1 GPOS = (1 << D5); - #define CLR_TP1 GPOC = (1 << D5); - #define MODE_TP2 pinMode( D6,OUTPUT ) ; // GPIO 12 - #define SET_TP2 GPOS = (1 << D6); - #define CLR_TP2 GPOC = (1 << D6); - #define MODE_TP3 pinMode( D7,OUTPUT ) ; // GPIO 13 - #define SET_TP3 GPOS = (1 << D7); - #define CLR_TP3 GPOC = (1 << D7); - #define MODE_TP4 pinMode( D8,OUTPUT ) ; // GPIO 15 - #define SET_TP4 GPOC = (1 << D8); - #define CLR_TP4 GPOC = (1 << D8); - #elif defined(ESP32) - #define MODE_TP1 pinMode( 33,OUTPUT ) ; // GPIO 33 - #define SET_TP1 GPOS = (1 << 33); - #define CLR_TP1 GPOC = (1 << 33); - #define MODE_TP2 pinMode( 25,OUTPUT ) ; // GPIO 25 - #define SET_TP2 GPOS = (1 << 25); - #define CLR_TP2 GPOC = (1 << 25); - #define MODE_TP3 pinMode( 26,OUTPUT ) ; // GPIO 26 - #define SET_TP3 GPOS = (1 << 26); - #define CLR_TP3 GPOC = (1 << 26); - #define MODE_TP4 pinMode( 27,OUTPUT ) ; // GPIO 27 - #define SET_TP4 GPOC = (1 << 27); - #define CLR_TP4 GPOC = (1 << 27); - - - //#elif defined(__AVR_ATmega128__) ||defined(__AVR_ATmega1281__)||defined(__AVR_ATmega2561__) - #else - #define MODE_TP1 - #define SET_TP1 - #define CLR_TP1 - #define MODE_TP2 - #define SET_TP2 - #define CLR_TP2 - #define MODE_TP3 - #define SET_TP3 - #define CLR_TP3 - #define MODE_TP4 - #define SET_TP4 - #define CLR_TP4 + +#ifdef NODE_DEBUG + #define PULLUP PLATFORM_GPIO_PULLUP + #define OUTPUT PLATFORM_GPIO_OUTPUT + #define HIGH PLATFORM_GPIO_HIGH + #define LOW PLATFORM_GPIO_LOW - #endif + #define MODE_TP1 platform_gpio_mode( 5, OUTPUT, PULLUP ); // GPIO 14 + #define SET_TP1 platform_gpio_write(5, HIGH); + #define CLR_TP1 platform_gpio_write(5, LOW); + #define MODE_TP2 platform_gpio_mode( 6, OUTPUT, PULLUP ); // GPIO 12 + #define SET_TP2 platform_gpio_write(6, HIGH); + #define CLR_TP2 platform_gpio_write(6, LOW); + #define MODE_TP3 platform_gpio_mode( 7, OUTPUT, PULLUP ); // GPIO 13 + #define SET_TP3 platform_gpio_write(7, HIGH); + #define CLR_TP3 platform_gpio_write(7, LOW); + #define MODE_TP4 platform_gpio_mode( 8, OUTPUT, PULLUP ); // GPIO 15 + #define SET_TP4 platform_gpio_write(8, HIGH); + #define CLR_TP4 platform_gpio_write(8, LOW); #else #define MODE_TP1 #define SET_TP1 @@ -221,42 +142,18 @@ #define MODE_TP2 #define SET_TP2 #define CLR_TP2 - //#define MODE_TP2 DDRC |= (1<<2) // A2 - //#define SET_TP2 PORTC |= (1<<2) - //#define CLR_TP2 PORTC &= ~(1<<2) #define MODE_TP3 #define SET_TP3 #define CLR_TP3 #define MODE_TP4 #define SET_TP4 #define CLR_TP4 - //#define MODE_TP4 DDRC |= (1<<4) //A4 - //#define SET_TP4 PORTC |= (1<<4) - //#define CLR_TP4 PORTC &= ~(1<<4) - -#endif -#ifdef DEBUG_PRINT - #define DB_PRINT( x, ... ) { char dbgbuf[80]; sprintf_P( dbgbuf, (const char*) F( x ) , ##__VA_ARGS__ ) ; Serial.println( dbgbuf ); } - #define DB_PRINT_( x, ... ) { char dbgbuf[80]; sprintf_P( dbgbuf, (const char*) F( x ) , ##__VA_ARGS__ ) ; Serial.print( dbgbuf ); } -#else - #define DB_PRINT( x, ... ) ; - #define DB_PRINT_( x, ... ) ; #endif -#ifdef DCC_DBGVAR -struct countOf_t countOf; -#endif +static uint8_t ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING +static int16_t bitMax, bitMin; -#if defined ( __STM32F1__ ) -static ExtIntTriggerMode ISREdge; -#elif defined ( ESP32 ) -static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING -static byte ISRWatch; // Interrupt Handler Edge Filter -#else -static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING -static byte ISRWatch; // Interrupt Handler Edge Filter -#endif -static word bitMax, bitMin; +DCC_MSG Msg ; typedef enum { @@ -279,7 +176,6 @@ OpsInstructionType; struct DccRx_t { DccRxWaitState State ; - uint8_t DataReady ; uint8_t BitCount ; uint8_t TempByte ; DCC_MSG PacketBuf; @@ -296,118 +192,79 @@ typedef struct uint8_t PageRegister ; // Used for Paged Operations in Service Mode Programming uint8_t DuplicateCount ; DCC_MSG LastMsg ; - uint8_t ExtIntNum; - uint8_t ExtIntPinNum; - int16_t myDccAddress; // Cached value of DCC Address from CVs - uint8_t inAccDecDCCAddrNextReceivedMode; + uint8_t IntPin; + uint8_t IntBitmask; + int16_t myDccAddress; // Cached value of DCC Address from CVs + uint8_t inAccDecDCCAddrNextReceivedMode; #ifdef DCC_DEBUG - uint8_t IntCount; - uint8_t TickCount; - uint8_t NestedIrqCount; + uint8_t IntCount; + uint8_t TickCount; #endif } DCC_PROCESSOR_STATE ; DCC_PROCESSOR_STATE DccProcState ; -#ifdef ESP32 -portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; +task_handle_t DataReady_taskid; -void IRAM_ATTR ExternalInterruptHandler(void) -#elif defined(ESP8266) -void ICACHE_RAM_ATTR ExternalInterruptHandler(void) -#else -void ExternalInterruptHandler(void) -#endif +static uint32_t ICACHE_RAM_ATTR InterruptHandler (uint32_t ret_gpio_status) { -#ifdef ESP32 -// switch (ISRWatch) -// { -// case RISING: if (digitalRead(DccProcState.ExtIntPinNum)) break; -// case FALLING: if (digitalRead(DccProcState.ExtIntPinNum)) return; break; -// } - // First compare the edge we're looking for to the pin state - switch (ISRWatch) - { - case CHANGE: - break; - - case RISING: - if (digitalRead(DccProcState.ExtIntPinNum) != HIGH) - return; - break; - - case FALLING: - if (digitalRead(DccProcState.ExtIntPinNum) != LOW) - return; - break; - } -#endif -// Bit evaluation without Timer 0 ------------------------------ - uint8_t DccBitVal; - static int8_t bit1, bit2 ; - static unsigned long lastMicros = 0; - static byte halfBit, DCC_IrqRunning; - unsigned long actMicros, bitMicros; - if ( DCC_IrqRunning ) { - // nested DCC IRQ - obviously there are glitches - // ignore this interrupt and increment glitchcounter - CLR_TP3; - #ifdef DCC_DEBUG - DccProcState.NestedIrqCount++; - #endif - SET_TP3; - return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ - } - SET_TP3; - actMicros = micros(); - bitMicros = actMicros-lastMicros; - if ( bitMicros < bitMin ) { - // too short - my be false interrupt due to glitch or false protocol -> ignore - CLR_TP3; - SET_TP4; CLR_TP4; - return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ - } - DccBitVal = ( bitMicros < bitMax ); - lastMicros = actMicros; - #ifdef debug - if(DccBitVal) {SET_TP2;} else {CLR_TP2;}; - #endif - DCC_IrqRunning = true; - interrupts(); // time critical is only the micros() command,so allow nested irq's -#ifdef DCC_DEBUG - DccProcState.TickCount++; -#endif + // This function really is running at interrupt level with everything + // else masked off. It should take as little time as necessary. + + uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + if ((gpio_status & DccProcState.IntBitmask) == 0) { + return ret_gpio_status; + } + + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & DccProcState.IntBitmask); + uint32_t actMicros = system_get_time(); + ret_gpio_status &= ~(DccProcState.IntBitmask); + + // Bit evaluation without Timer 0 ------------------------------ + uint8_t DccBitVal; + static int8_t bit1, bit2 ; + static unsigned long lastMicros = 0; + static uint8_t halfBit; + unsigned long bitMicros; + SET_TP3; + bitMicros = actMicros-lastMicros; + if ( bitMicros < bitMin ) { + // too short - my be false interrupt due to glitch or false protocol -> ignore + CLR_TP3; + return ret_gpio_status; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ + } + DccBitVal = ( bitMicros < bitMax ); + lastMicros = actMicros; + #ifdef NODE_DEBUG + if(DccBitVal) {SET_TP2;} else {CLR_TP2;}; + #endif + #ifdef DCC_DEBUG + DccProcState.TickCount++; + #endif switch( DccRx.State ) { case WAIT_PREAMBLE: if( DccBitVal ) { - SET_TP1; + SET_TP1; DccRx.BitCount++; - if( DccRx.BitCount > 10 ) { + if( DccRx.BitCount > 10 ) { DccRx.State = WAIT_START_BIT ; // While waiting for the start bit, detect halfbit lengths. We will detect the correct // sync and detect whether we see a false (e.g. motorola) protocol - - #if defined ( __STM32F1__ ) - detachInterrupt( DccProcState.ExtIntNum ); - #endif - #ifdef ESP32 - ISRWatch = CHANGE; - #else - attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE); - #endif + + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), GPIO_PIN_INTR_ANYEDGE); halfBit = 0; bitMax = MAX_ONEBITHALF; bitMin = MIN_ONEBITHALF; CLR_TP1; } } else { - SET_TP1; - DccRx.BitCount = 0 ; - CLR_TP1; + SET_TP1; + DccRx.BitCount = 0 ; + CLR_TP1; } break; @@ -422,9 +279,9 @@ void ExternalInterruptHandler(void) bit1=bitMicros; } else { // was "0" half bit, maybe the startbit - SET_TP1; + SET_TP1; halfBit = 4; - CLR_TP1; + CLR_TP1; } break; case 1: //SET_TP1; // previous halfbit was '1' @@ -441,25 +298,17 @@ void ExternalInterruptHandler(void) bitMax = MAX_PRAEAMBEL; bitMin = MIN_ONEBITFULL; DccRx.BitCount = 0; - SET_TP4; - - #if defined ( __STM32F1__ ) - detachInterrupt( DccProcState.ExtIntNum ); - #endif - #ifdef ESP32 - ISRWatch = ISREdge; - #else - attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); - #endif - SET_TP3; - CLR_TP4; + SET_TP4; + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge); + SET_TP3; + CLR_TP4; } } else { // first '0' half detected in second halfBit // wrong sync or not a DCC protokoll - CLR_TP3; + CLR_TP3; halfBit = 3; - SET_TP3; + SET_TP3; } break; case 3: //SET_TP1; // previous halfbit was '0' in second halfbit @@ -472,7 +321,7 @@ void ExternalInterruptHandler(void) } else { // we got two '0' halfbits -> it's the startbit // but sync is NOT ok, change IRQ edge. - if ( ISREdge == RISING ) ISREdge = FALLING; else ISREdge = RISING; + if ( ISREdge == GPIO_PIN_INTR_POSEDGE ) ISREdge = GPIO_PIN_INTR_NEGEDGE; else ISREdge = GPIO_PIN_INTR_POSEDGE; DccRx.State = WAIT_DATA ; bitMax = MAX_ONEBITFULL; bitMin = MIN_ONEBITFULL; @@ -485,18 +334,10 @@ void ExternalInterruptHandler(void) DccRx.BitCount = 0 ; DccRx.TempByte = 0 ; } - SET_TP4; - - #if defined ( __STM32F1__ ) - detachInterrupt( DccProcState.ExtIntNum ); - #endif - #ifdef ESP32 - ISRWatch = ISREdge; - #else - attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); - #endif + SET_TP4; + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge); CLR_TP1; - CLR_TP4; + CLR_TP4; break; case 4: SET_TP1; // previous (first) halfbit was 0 // if this halfbit is 0 too, we got the startbit @@ -520,20 +361,11 @@ void ExternalInterruptHandler(void) DccRx.BitCount = 0 ; DccRx.TempByte = 0 ; } - + CLR_TP1; - SET_TP4; - - #if defined ( __STM32F1__ ) - detachInterrupt( DccProcState.ExtIntNum ); - #endif - #ifdef ESP32 - ISRWatch = ISREdge; - #else - attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); - #endif - - CLR_TP4; + SET_TP4; + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge); + CLR_TP4; break; } @@ -570,14 +402,9 @@ void ExternalInterruptHandler(void) DccRx.State = WAIT_PREAMBLE ; bitMax = MAX_PRAEAMBEL; bitMin = MIN_ONEBITFULL; -#ifdef ESP32 - portENTER_CRITICAL_ISR(&mux); -#endif DccRx.PacketCopy = DccRx.PacketBuf ; - DccRx.DataReady = 1 ; -#ifdef ESP32 - portEXIT_CRITICAL_ISR(&mux); -#endif + uint8_t param; + task_post_high(DataReady_taskid, (os_param_t) ¶m); SET_TP3; } else // Get next Byte @@ -597,68 +424,27 @@ void ExternalInterruptHandler(void) DccRx.TempByte = 0 ; } } + CLR_TP1; CLR_TP3; - DCC_IrqRunning = false; -} - -void ackCV(void) -{ - if( notifyCVAck ) - notifyCVAck() ; + return ret_gpio_status; } -uint8_t readEEPROM( unsigned int CV ) { - return EEPROM.read(CV) ; -} - -void writeEEPROM( unsigned int CV, uint8_t Value ) { - EEPROM.write(CV, Value) ; - #if defined(ESP8266) - EEPROM.commit(); - #endif - #if defined(ESP32) - EEPROM.commit(); - #endif -} - -bool readyEEPROM() { - #ifdef __AVR_MEGA__ - return eeprom_is_ready(); - #else - return true; - #endif -} - - uint8_t validCV( uint16_t CV, uint8_t Writable ) { if( notifyCVResetFactoryDefault && (CV == CV_MANUFACTURER_ID ) && Writable ) - notifyCVResetFactoryDefault(); - + notifyCVResetFactoryDefault(); + if( notifyCVValid ) return notifyCVValid( CV, Writable ) ; - - uint8_t Valid = 1 ; - - if( CV > MAXCV ) - Valid = 0 ; - - if( Writable && ( ( CV ==CV_VERSION_ID ) || (CV == CV_MANUFACTURER_ID ) ) ) - Valid = 0 ; - - return Valid ; + return 0; } uint8_t readCV( unsigned int CV ) { - uint8_t Value ; - if( notifyCVRead ) return notifyCVRead( CV ) ; - - Value = readEEPROM(CV); - return Value ; + return 0; } uint8_t writeCV( unsigned int CV, uint8_t Value) @@ -669,36 +455,24 @@ uint8_t writeCV( unsigned int CV, uint8_t Value) // copy addressmode Bit to Flags DccProcState.Flags = ( DccProcState.Flags & ~FLAGS_CV29_BITS) | (Value & FLAGS_CV29_BITS); // no break, because myDccAdress must also be reset - case CV_ACCESSORY_DECODER_ADDRESS_LSB: // Also same CV for CV_MULTIFUNCTION_PRIMARY_ADDRESS + case CV_ACCESSORY_DECODER_ADDRESS_LSB: // Also same CV for CV_MULTIFUNCTION_PRIMARY_ADDRESS case CV_ACCESSORY_DECODER_ADDRESS_MSB: case CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB: case CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB: - DccProcState.myDccAddress = -1; // Assume any CV Write Operation might change the Address + DccProcState.myDccAddress = -1; // Assume any CV Write Operation might change the Address } if( notifyCVWrite ) return notifyCVWrite( CV, Value ) ; - - if( readEEPROM( CV ) != Value ) - { - writeEEPROM( CV, Value ) ; - - if( notifyCVChange ) - notifyCVChange( CV, Value) ; - - if( notifyDccCVChange && !(DccProcState.Flags & FLAGS_SETCV_CALLED) ) - notifyDccCVChange( CV, Value ); - } - - return readEEPROM( CV ) ; + return 0; } uint16_t getMyAddr(void) { uint8_t CV29Value ; - if( DccProcState.myDccAddress != -1 ) // See if we can return the cached value - return( DccProcState.myDccAddress ); + if( DccProcState.myDccAddress != -1 ) // See if we can return the cached value + return( DccProcState.myDccAddress ); CV29Value = readCV( CV_29_CONFIG ) ; @@ -717,7 +491,7 @@ uint16_t getMyAddr(void) else DccProcState.myDccAddress = readCV( 1 ) ; } - + return DccProcState.myDccAddress ; } @@ -731,17 +505,7 @@ void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value ) { if( validCV( CVAddr, 1 ) ) { - if( writeCV( CVAddr, Value ) == Value ) - ackCV(); - } - } - - else // Perform the Verify Operation - { - if( validCV( CVAddr, 0 ) ) - { - if( readCV( CVAddr ) == Value ) - ackCV(); + writeCV( CVAddr, Value ); } } } @@ -765,32 +529,12 @@ void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value ) else tempValue &= ~BitMask ; // Turn the Bit Off - if( writeCV( CVAddr, tempValue ) == tempValue ) - ackCV() ; - } - } - - // Perform the Bit Verify Operation - else - { - if( validCV( CVAddr, 0 ) ) - { - if( BitValue ) - { - if( tempValue & BitMask ) - ackCV() ; - } - else - { - if( !( tempValue & BitMask) ) - ackCV() ; - } - } + writeCV( CVAddr, tempValue ); + } } } } -///////////////////////////////////////////////////////////////////////// #ifdef NMRA_DCC_PROCESS_MULTIFUNCTION void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Cmd, uint8_t Data1, uint8_t Data2 ) { @@ -801,9 +545,12 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t uint8_t CmdMasked = Cmd & 0b11100000 ; + // NODE_DBG("[dcc_processMultiFunctionMessage] Addr: %d, Type: %d, Cmd: %d ("BYTE_TO_BINARY_PATTERN"), Data: %d, %d, CmdMasked="BYTE_TO_BINARY_PATTERN"\n", Addr, AddrType, Cmd, BYTE_TO_BINARY(Cmd), Data1, Data2, BYTE_TO_BINARY(CmdMasked)); + // If we are an Accessory Decoder if( DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER ) { + // NODE_DBG("[dcc_processMultiFunctionMessage] DccProcState.Flags & FLAGS_DCC_ACCESSORY_DECODER\n"); // and this isn't an Ops Mode Write or we are NOT faking the Multifunction Ops mode address in CV 33+34 or // it's not our fake address, then return if( ( CmdMasked != 0b11100000 ) || ( DccProcState.OpsModeAddressBaseCV == 0 ) ) @@ -820,6 +567,7 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t else if( ( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) && ( Addr != getMyAddr() ) && ( Addr != 0 ) ) return ; + NODE_DBG("[dcc_processMultiFunctionMessage] CmdMasked: %x\n", CmdMasked); switch( CmdMasked ) { case 0b00000000: // Decoder Control @@ -913,7 +661,7 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t notifyDccSpeed( Addr, AddrType, speed, dir, speedSteps ) ; } if( notifyDccSpeedRaw ) - notifyDccSpeedRaw(Addr, AddrType, Cmd ); + notifyDccSpeedRaw(Addr, AddrType, Cmd ); #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE if( notifyDccFunc && (speedSteps == SPEED_STEP_14) ) @@ -944,18 +692,18 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t break; case 0b11000000: // Feature Expansion Instruction - switch(Cmd & 0b00011111) - { - case 0b00011110: - if( notifyDccFunc ) - notifyDccFunc( Addr, AddrType, FN_13_20, Data1 ) ; - break; - - case 0b00011111: - if( notifyDccFunc ) - notifyDccFunc( Addr, AddrType, FN_21_28, Data1 ) ; - break; - } + switch(Cmd & 0b00011111) + { + case 0b00011110: + if( notifyDccFunc ) + notifyDccFunc( Addr, AddrType, FN_13_20, Data1 ) ; + break; + + case 0b00011111: + if( notifyDccFunc ) + notifyDccFunc( Addr, AddrType, FN_21_28, Data1 ) ; + break; + } break; case 0b11100000: // CV Access @@ -976,14 +724,13 @@ void processServiceModeOperation( DCC_MSG * pDccMsg ) if( pDccMsg->Size == 3) // 3 Byte Packets are for Address Only, Register and Paged Mode { uint8_t RegisterAddr ; - DB_PRINT("3-BytePkt"); + NODE_DBG("[dcc_processServiceModeOperation] 3-BytePkt\n"); RegisterAddr = pDccMsg->Data[0] & 0x07 ; Value = pDccMsg->Data[1] ; if( RegisterAddr == 5 ) { DccProcState.PageRegister = Value ; - ackCV(); } else @@ -1001,17 +748,7 @@ void processServiceModeOperation( DCC_MSG * pDccMsg ) { if( validCV( CVAddr, 1 ) ) { - if( writeCV( CVAddr, Value ) == Value ) - ackCV(); - } - } - - else // Perform the Verify Operation - { - if( validCV( CVAddr, 0 ) ) - { - if( readCV( CVAddr ) == Value ) - ackCV(); + writeCV( CVAddr, Value ); } } } @@ -1019,7 +756,7 @@ void processServiceModeOperation( DCC_MSG * pDccMsg ) else if( pDccMsg->Size == 4) // 4 Byte Packets are for Direct Byte & Bit Mode { - DB_PRINT("BB-Mode"); + NODE_DBG("[dcc_processServiceModeOperation] BB-Mode\n"); CVAddr = ( ( ( pDccMsg->Data[0] & 0x03 ) << 8 ) | pDccMsg->Data[1] ) + 1 ; Value = pDccMsg->Data[2] ; @@ -1028,7 +765,6 @@ void processServiceModeOperation( DCC_MSG * pDccMsg ) } #endif -///////////////////////////////////////////////////////////////////////// void resetServiceModeTimer(uint8_t inServiceMode) { if (notifyServiceMode && inServiceMode != DccProcState.inServiceMode) @@ -1038,14 +774,13 @@ void resetServiceModeTimer(uint8_t inServiceMode) // Set the Service Mode DccProcState.inServiceMode = inServiceMode ; - DccProcState.LastServiceModeMillis = inServiceMode ? millis() : 0 ; + DccProcState.LastServiceModeMillis = inServiceMode ? system_get_time() : 0 ; if (notifyServiceMode && inServiceMode != DccProcState.inServiceMode) { notifyServiceMode(inServiceMode); } } -///////////////////////////////////////////////////////////////////////// void clearDccProcState(uint8_t inServiceMode) { resetServiceModeTimer( inServiceMode ) ; @@ -1058,32 +793,15 @@ void clearDccProcState(uint8_t inServiceMode) memset( &DccProcState.LastMsg, 0, sizeof( DCC_MSG ) ) ; } -///////////////////////////////////////////////////////////////////////// -#ifdef DEBUG_PRINT -void SerialPrintPacketHex(const __FlashStringHelper *strLabel, DCC_MSG * pDccMsg) -{ - Serial.print( strLabel ); - - for( uint8_t i = 0; i < pDccMsg->Size; i++ ) - { - if( pDccMsg->Data[i] <= 9) - Serial.print('0'); - - Serial.print( pDccMsg->Data[i], HEX ); - Serial.write( ' ' ); - } - Serial.println(); -} -#endif - -/////////////////////////////////////////////////////////////////////////////// void execDccProcessor( DCC_MSG * pDccMsg ) { + NODE_DBG("[dcc_execDccProcessor]\n"); + if( ( pDccMsg->Data[0] == 0 ) && ( pDccMsg->Data[1] == 0 ) ) { if( notifyDccReset ) notifyDccReset( 0 ) ; - + #ifdef NMRA_DCC_PROCESS_SERVICEMODE // If this is the first Reset then perform some one-shot actions as we maybe about to enter service mode if( DccProcState.inServiceMode ) @@ -1116,7 +834,7 @@ void execDccProcessor( DCC_MSG * pDccMsg ) else { if( DccProcState.inServiceMode ) - clearDccProcState( 0 ); + clearDccProcState( 0 ); #endif // Idle Packet @@ -1130,7 +848,6 @@ void execDccProcessor( DCC_MSG * pDccMsg ) // Multi Function Decoders (7-bit address) else if( pDccMsg->Data[0] < 128 ) processMultiFunctionMessage( pDccMsg->Data[0], DCC_ADDR_SHORT, pDccMsg->Data[1], pDccMsg->Data[2], pDccMsg->Data[3] ) ; - // Basic Accessory Decoders (9-bit) & Extended Accessory Decoders (11-bit) else if( pDccMsg->Data[0] < 192 ) #else @@ -1143,187 +860,179 @@ void execDccProcessor( DCC_MSG * pDccMsg ) int16_t OutputAddress ; uint8_t TurnoutPairIndex ; -#ifdef DEBUG_PRINT - SerialPrintPacketHex(F( "eDP: AccCmd: "), pDccMsg); +#ifdef NODE_DEBUG + // SerialPrintPacketHex(F( "eDP: AccCmd: "), pDccMsg); #endif BoardAddress = ( ( (~pDccMsg->Data[1]) & 0b01110000 ) << 2 ) | ( pDccMsg->Data[0] & 0b00111111 ) ; TurnoutPairIndex = (pDccMsg->Data[1] & 0b00000110) >> 1; - DB_PRINT("eDP: BAddr:%d, Index:%d", BoardAddress, TurnoutPairIndex); + NODE_DBG("[dcc_execDccProcessor] eDP: BAddr:%d, Index:%d\n", BoardAddress, TurnoutPairIndex); // First check for Legacy Accessory Decoder Configuration Variable Access Instruction // as it's got a different format to the others if((pDccMsg->Size == 5) && ((pDccMsg->Data[1] & 0b10001100) == 0b00001100)) { - DB_PRINT( "eDP: Legacy Accessory Decoder CV Access Command"); + NODE_DBG( "eDP: Legacy Accessory Decoder CV Access Command"); // Check if this command is for our address or the broadcast address if((BoardAddress != getMyAddr()) && ( BoardAddress < 511 )) { - DB_PRINT("eDP: Board Address Not Matched"); + NODE_DBG("[dcc_execDccProcessor] eDP: Board Address Not Matched\n"); return; } uint16_t cvAddress = ((pDccMsg->Data[1] & 0b00000011) << 8) + pDccMsg->Data[2] + 1; - uint8_t cvValue = pDccMsg->Data[3]; - DB_PRINT("eDP: CV:%d Value:%d", cvAddress, cvValue ); - if(validCV( cvAddress, 1 )) + uint8_t cvValue = pDccMsg->Data[3]; + NODE_DBG("[dcc_execDccProcessor] eDP: CV:%d Value:%d\n", cvAddress, cvValue ); + if(validCV( cvAddress, 1 )) writeCV(cvAddress, cvValue); - return; + return; } OutputAddress = (((BoardAddress - 1) << 2 ) | TurnoutPairIndex) + 1 ; //decoder output addresses start with 1, packet address range starts with 0 // ( according to NMRA 9.2.2 ) - DB_PRINT("eDP: OAddr:%d", OutputAddress); + NODE_DBG("[dcc_execDccProcessor] eDP: OAddr:%d\n", OutputAddress); if( DccProcState.inAccDecDCCAddrNextReceivedMode) { - if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) - { - DB_PRINT("eDP: Set OAddr:%d", OutputAddress); - //uint16_t storedOutputAddress = OutputAddress + 1; // The value stored in CV1 & 9 for Output Addressing Mode is + 1 - writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(OutputAddress % 256)); - writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(OutputAddress / 256)); - - if( notifyDccAccOutputAddrSet ) - notifyDccAccOutputAddrSet(OutputAddress); - } - else - { - DB_PRINT("eDP: Set BAddr:%d", BoardAddress); - writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(BoardAddress % 64)); - writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(BoardAddress / 64)); - - if( notifyDccAccBoardAddrSet ) - notifyDccAccBoardAddrSet(BoardAddress); - } - - DccProcState.inAccDecDCCAddrNextReceivedMode = 0; // Reset the mode now that we have set the address + if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) + { + NODE_DBG("[dcc_execDccProcessor] eDP: Set OAddr:%d\n", OutputAddress); + //uint16_t storedOutputAddress = OutputAddress + 1; // The value stored in CV1 & 9 for Output Addressing Mode is + 1 + writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(OutputAddress % 256)); + writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(OutputAddress / 256)); + + if( notifyDccAccOutputAddrSet ) + notifyDccAccOutputAddrSet(OutputAddress); + } + else + { + NODE_DBG("[dcc_execDccProcessor] eDP: Set BAddr:%d\n", BoardAddress); + writeCV(CV_ACCESSORY_DECODER_ADDRESS_LSB, (uint8_t)(BoardAddress % 64)); + writeCV(CV_ACCESSORY_DECODER_ADDRESS_MSB, (uint8_t)(BoardAddress / 64)); + + if( notifyDccAccBoardAddrSet ) + notifyDccAccBoardAddrSet(BoardAddress); + } + + DccProcState.inAccDecDCCAddrNextReceivedMode = 0; // Reset the mode now that we have set the address } // If we're filtering addresses, does the address match our address or is it a broadcast address? If NOT then return if( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) { if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) { - DB_PRINT(" AddrChk: OAddr:%d, BAddr:%d, myAddr:%d Chk=%d", OutputAddress, BoardAddress, getMyAddr(), OutputAddress != getMyAddr() ); + NODE_DBG("[dcc_execDccProcessor] AddrChk: OAddr:%d, BAddr:%d, myAddr:%d Chk=%d\n", OutputAddress, BoardAddress, getMyAddr(), OutputAddress != getMyAddr() ); if ( OutputAddress != getMyAddr() && OutputAddress < 2045 ) { - DB_PRINT(" eDP: OAddr:%d, myAddr:%d - no match", OutputAddress, getMyAddr() ); + NODE_DBG("[dcc_execDccProcessor] eDP: OAddr:%d, myAddr:%d - no match\n", OutputAddress, getMyAddr() ); return; } } else { if( ( BoardAddress != getMyAddr() ) && ( BoardAddress < 511 ) ) { - DB_PRINT(" eDP: BAddr:%d, myAddr:%d - no match", BoardAddress, getMyAddr() ); + NODE_DBG("[dcc_execDccProcessor] eDP: BAddr:%d, myAddr:%d - no match\n", BoardAddress, getMyAddr() ); return; } } - DB_PRINT("eDP: Address Matched"); + NODE_DBG("[dcc_execDccProcessor] eDP: Address Matched\n"); } - if((pDccMsg->Size == 4) && ((pDccMsg->Data[1] & 0b10001001) == 1)) // Extended Accessory Decoder Control Packet Format - { - // According to the NMRA Dcc Spec the Signal State should only use the lower 5 Bits, - // however some manufacturers seem to allow/use all 8 bits, so we'll relax that constraint for now - uint8_t state = pDccMsg->Data[2] ; - DB_PRINT("eDP: OAddr:%d Extended State:%0X", OutputAddress, state); - if( notifyDccSigOutputState ) - notifyDccSigOutputState(OutputAddress, state); - - // old callback ( for compatibility with 1.4.2, not to be used in new designs ) - if( notifyDccSigState ) - notifyDccSigState( OutputAddress, TurnoutPairIndex, pDccMsg->Data[2] ) ; - } - - else if(pDccMsg->Size == 3) // Basic Accessory Decoder Packet Format - { - uint8_t direction = pDccMsg->Data[1] & 0b00000001; - uint8_t outputPower = (pDccMsg->Data[1] & 0b00001000) >> 3; - - // old callback ( for compatibility with 1.4.2, not to be used in new designs ) - if ( notifyDccAccState ) - notifyDccAccState( OutputAddress, BoardAddress, pDccMsg->Data[1] & 0b00000111, outputPower ); - - if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) - { - DB_PRINT("eDP: OAddr:%d Turnout Dir:%d Output Power:%d", OutputAddress, direction, outputPower); - if( notifyDccAccTurnoutOutput ) - notifyDccAccTurnoutOutput( OutputAddress, direction, outputPower ); - } - else - { - DB_PRINT("eDP: Turnout Pair Index:%d Dir:%d Output Power: ", TurnoutPairIndex, direction, outputPower); - if( notifyDccAccTurnoutBoard ) - notifyDccAccTurnoutBoard( BoardAddress, TurnoutPairIndex, direction, outputPower ); - } + if((pDccMsg->Size == 4) && ((pDccMsg->Data[1] & 0b10001001) == 1)) // Extended Accessory Decoder Control Packet Format + { + // According to the NMRA Dcc Spec the Signal State should only use the lower 5 Bits, + // however some manufacturers seem to allow/use all 8 bits, so we'll relax that constraint for now + uint8_t state = pDccMsg->Data[2] ; + NODE_DBG("[dcc_execDccProcessor] eDP: OAddr:%d Extended State:%0X\n", OutputAddress, state); + if( notifyDccSigOutputState ) + notifyDccSigOutputState(OutputAddress, state); } - else if(pDccMsg->Size == 6) // Accessory Decoder OPS Mode Programming - { - DB_PRINT("eDP: OPS Mode CV Programming Command"); - // Check for unsupported OPS Mode Addressing mode - if(((pDccMsg->Data[1] & 0b10001001) != 1) && ((pDccMsg->Data[1] & 0b10001111) != 0x80)) - { - DB_PRINT("eDP: Unsupported OPS Mode CV Addressing Mode"); - return; + + else if(pDccMsg->Size == 3) // Basic Accessory Decoder Packet Format + { + uint8_t direction = pDccMsg->Data[1] & 0b00000001; + uint8_t outputPower = (pDccMsg->Data[1] & 0b00001000) >> 3; + + if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) + { + NODE_DBG("[dcc_execDccProcessor] eDP: OAddr:%d Turnout Dir:%d Output Power:%d\n", OutputAddress, direction, outputPower); + if( notifyDccAccTurnoutOutput ) + notifyDccAccTurnoutOutput( OutputAddress, direction, outputPower ); + } + else + { + NODE_DBG("[dcc_execDccProcessor] eDP: Turnout Pair Index:%d Dir:%d Output Power: %d\n", TurnoutPairIndex, direction, outputPower); + if( notifyDccAccTurnoutBoard ) + notifyDccAccTurnoutBoard( BoardAddress, TurnoutPairIndex, direction, outputPower ); + } + } + else if(pDccMsg->Size == 6) // Accessory Decoder OPS Mode Programming + { + NODE_DBG("[dcc_execDccProcessor] eDP: OPS Mode CV Programming Command\n"); + // Check for unsupported OPS Mode Addressing mode + if(((pDccMsg->Data[1] & 0b10001001) != 1) && ((pDccMsg->Data[1] & 0b10001111) != 0x80)) + { + NODE_DBG("[dcc_execDccProcessor] eDP: Unsupported OPS Mode CV Addressing Mode\n"); + return; } - - // Check if this command is for our address or the broadcast address + + // Check if this command is for our address or the broadcast address if(DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE) { - DB_PRINT("eDP: Check Output Address:%d", OutputAddress); + NODE_DBG("[dcc_execDccProcessor] eDP: Check Output Address:%d\n", OutputAddress); if((OutputAddress != getMyAddr()) && ( OutputAddress < 2045 )) { - DB_PRINT("eDP: Output Address Not Matched"); - return; + NODE_DBG("[dcc_execDccProcessor] eDP: Output Address Not Matched\n"); + return; } } else { - DB_PRINT("eDP: Check Board Address:%d", BoardAddress); + NODE_DBG("[dcc_execDccProcessor] eDP: Check Board Address:%d\n", BoardAddress); if((BoardAddress != getMyAddr()) && ( BoardAddress < 511 )) { - DB_PRINT("eDP: Board Address Not Matched"); - return; + NODE_DBG("[dcc_execDccProcessor] eDP: Board Address Not Matched\n"); + return; } } - uint16_t cvAddress = ((pDccMsg->Data[2] & 0b00000011) << 8) + pDccMsg->Data[3] + 1; - uint8_t cvValue = pDccMsg->Data[4]; - - OpsInstructionType insType = (OpsInstructionType)((pDccMsg->Data[2] & 0b00001100) >> 2) ; - - DB_PRINT("eDP: OPS Mode Instruction:%d", insType); - switch(insType) - { - case OPS_INS_RESERVED: - case OPS_INS_VERIFY_BYTE: - DB_PRINT("eDP: Unsupported OPS Mode Instruction:%d", insType); - break; // We only support Write Byte or Bit Manipulation - - case OPS_INS_WRITE_BYTE: - DB_PRINT("eDP: CV:%d Value:%d", cvAddress, cvValue); - if(validCV( cvAddress, 1 )) - writeCV(cvAddress, cvValue); - break; - - // 111CDBBB - // Where BBB represents the bit position within the CV, - // D contains the value of the bit to be verified or written, - // and C describes whether the operation is a verify bit or a write bit operation. - // C = "1" WRITE BIT - // C = "0" VERIFY BIT - case OPS_INS_BIT_MANIPULATION: - // Make sure its a Write Bit Manipulation - if((cvValue & 0b00010000) && validCV(cvAddress, 1 )) - { - uint8_t currentValue = readCV(cvAddress); - uint8_t newValueMask = 1 << (cvValue & 0b00000111); - if(cvValue & 0b00001000) - writeCV(cvAddress, currentValue | newValueMask); - else - writeCV(cvAddress, currentValue & ~newValueMask); - } - break; - } + uint16_t cvAddress = ((pDccMsg->Data[2] & 0b00000011) << 8) + pDccMsg->Data[3] + 1; + uint8_t cvValue = pDccMsg->Data[4]; + + OpsInstructionType insType = (OpsInstructionType)((pDccMsg->Data[2] & 0b00001100) >> 2) ; + + NODE_DBG("[dcc_execDccProcessor] eDP: OPS Mode Instruction:%d\n", insType); + switch(insType) + { + case OPS_INS_RESERVED: + case OPS_INS_VERIFY_BYTE: + NODE_DBG("[dcc_execDccProcessor] eDP: Unsupported OPS Mode Instruction:%d\n", insType); + break; // We only support Write Byte or Bit Manipulation + + case OPS_INS_WRITE_BYTE: + NODE_DBG("[dcc_execDccProcessor] eDP: CV:%d Value:%d\n", cvAddress, cvValue); + if(validCV( cvAddress, 1 )) + writeCV(cvAddress, cvValue); + break; + + // 111CDBBB + // Where BBB represents the bit position within the CV, + // D contains the value of the bit to be verified or written, + // and C describes whether the operation is a verify bit or a write bit operation. + // C = "1" WRITE BIT + // C = "0" VERIFY BIT + case OPS_INS_BIT_MANIPULATION: + // Make sure its a Write Bit Manipulation + if((cvValue & 0b00010000) && validCV(cvAddress, 1 )) + { + uint8_t currentValue = readCV(cvAddress); + uint8_t newValueMask = 1 << (cvValue & 0b00000111); + if(cvValue & 0b00001000) + writeCV(cvAddress, currentValue | newValueMask); + else + writeCV(cvAddress, currentValue & ~newValueMask); + } + break; + } } } } @@ -1344,48 +1053,52 @@ void execDccProcessor( DCC_MSG * pDccMsg ) } } -//////////////////////////////////////////////////////////////////////// -NmraDcc::NmraDcc() -{ -} - -#ifdef digitalPinToInterrupt -void NmraDcc::pin( uint8_t ExtIntPinNum, uint8_t EnablePullup) -{ - pin(digitalPinToInterrupt(ExtIntPinNum), ExtIntPinNum, EnablePullup); -} -#endif - -void NmraDcc::pin( uint8_t ExtIntNum, uint8_t ExtIntPinNum, uint8_t EnablePullup) -{ -#if defined ( __STM32F1__ ) - // with STM32F1 the interuptnumber is equal the pin number - DccProcState.ExtIntNum = ExtIntPinNum; -#else - DccProcState.ExtIntNum = ExtIntNum; -#endif - DccProcState.ExtIntPinNum = ExtIntPinNum; - - pinMode( ExtIntPinNum, INPUT ); - if( EnablePullup ) - digitalWrite(ExtIntPinNum, HIGH); -} - -//////////////////////////////////////////////////////////////////////// -void NmraDcc::initAccessoryDecoder( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ) +static void process (os_param_t param, uint8_t prio) { - init(ManufacturerId, VersionId, Flags | FLAGS_DCC_ACCESSORY_DECODER, OpsModeAddressBaseCV); + // !!!!!! - this will not happen as we call process task only when data is ready + // if( DccProcState.inServiceMode ) + // { + // if( (system_get_time() - DccProcState.LastServiceModeMillis ) > 20L ) + // { + // clearDccProcState( 0 ) ; + // } + // } + // !!!!!! + + // We need to do this check with interrupts disabled + //SET_TP4; + Msg = DccRx.PacketCopy ; + + #ifdef DCC_DBGVAR + countOf.Tel++; + #endif + + uint8_t xorValue = 0 ; + + for(uint8_t i = 0; i < DccRx.PacketCopy.Size; i++) + xorValue ^= DccRx.PacketCopy.Data[i]; + if(xorValue) { + #ifdef DCC_DBGVAR + NODE_DBG("[dcc_process] Cerr\n"); + NODE_DBG("[dcc_process] Data dump:"); + for(uint8_t i = 0; i < DccRx.PacketCopy.Size; i++) + NODE_DBG(" %x", DccRx.PacketCopy.Data[i]); + NODE_DBG("\n"); + countOf.Err++; + #endif + return;// 0 ; + } else { + NODE_DBG("[dcc_process] Size: %d\tPreambleBits: %d\t%d, %d, %d, %d, %d, %d\n", + Msg.Size, Msg.PreambleBits, Msg.Data[0], Msg.Data[1], Msg.Data[2], Msg.Data[3], Msg.Data[4], Msg.Data[5]); + execDccProcessor( &Msg ); + } + + return;// 1 ; } -//////////////////////////////////////////////////////////////////////// -void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ) +void dcc_setup(uint8_t pin, uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV) { - #if defined(ESP8266) - EEPROM.begin(MAXCV); - #endif - #if defined(ESP32) - EEPROM.begin(MAXCV); - #endif + NODE_DBG("[dcc_setup]\n"); // Clear all the static member variables memset( &DccRx, 0, sizeof( DccRx) ); @@ -1393,31 +1106,38 @@ void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, ui MODE_TP2; MODE_TP3; MODE_TP4; + CLR_TP1; + CLR_TP2; + CLR_TP3; + CLR_TP4; + bitMax = MAX_ONEBITFULL; bitMin = MIN_ONEBITFULL; - DccProcState.Flags = Flags ; DccProcState.OpsModeAddressBaseCV = OpsModeAddressBaseCV ; DccProcState.myDccAddress = -1; DccProcState.inAccDecDCCAddrNextReceivedMode = 0; - ISREdge = RISING; + ISREdge = GPIO_PIN_INTR_POSEDGE; + + DccProcState.IntPin = pin; + DccProcState.IntBitmask = 1 << pin_num[pin]; - #ifdef ESP32 - ISRWatch = ISREdge; - attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE); - #else - attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, RISING); - #endif + platform_gpio_mode(pin, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP); + NODE_DBG("[dcc_setup] platform_gpio_register_intr_hook - pin: %d, mask: %d\n", DccProcState.IntPin, DccProcState.IntBitmask); + platform_gpio_register_intr_hook(DccProcState.IntBitmask, InterruptHandler); + + gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[pin]), GPIO_PIN_INTR_POSEDGE); + // Set the Bits that control Multifunction or Accessory behaviour // and if the Accessory decoder optionally handles Output Addressing // we need to peal off the top two bits - writeCV( CV_29_CONFIG, ( readCV( CV_29_CONFIG ) & ~FLAGS_CV29_BITS ) | (Flags & FLAGS_CV29_BITS) ) ; + writeCV( CV_29_CONFIG, ( readCV( CV_29_CONFIG ) & ~FLAGS_CV29_BITS ) | (Flags & FLAGS_CV29_BITS) ) ; //!!!!! uint8_t doAutoFactoryDefault = 0; if((Flags & FLAGS_AUTO_FACTORY_DEFAULT) && (readCV(CV_VERSION_ID) == 255) && (readCV(CV_MANUFACTURER_ID) == 255)) - doAutoFactoryDefault = 1; + doAutoFactoryDefault = 1; writeCV( CV_VERSION_ID, VersionId ) ; writeCV( CV_MANUFACTURER_ID, ManufacturerId ) ; @@ -1425,128 +1145,17 @@ void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, ui clearDccProcState( 0 ); if(notifyCVResetFactoryDefault && doAutoFactoryDefault) - notifyCVResetFactoryDefault(); -} - -//////////////////////////////////////////////////////////////////////// -uint8_t NmraDcc::getCV( uint16_t CV ) -{ - return readCV(CV); -} - -//////////////////////////////////////////////////////////////////////// -uint8_t NmraDcc::setCV( uint16_t CV, uint8_t Value) -{ - DccProcState.Flags |= FLAGS_SETCV_CALLED; - - uint8_t returnValue = writeCV(CV,Value); - - DccProcState.Flags &= ~FLAGS_SETCV_CALLED; - - return returnValue; -} - -//////////////////////////////////////////////////////////////////////// -uint16_t NmraDcc::getAddr(void) -{ - return getMyAddr(); -} - -//////////////////////////////////////////////////////////////////////// -uint8_t NmraDcc::isSetCVReady(void) -{ - if(notifyIsSetCVReady) - return notifyIsSetCVReady(); - return readyEEPROM(); -} - -//////////////////////////////////////////////////////////////////////// -#ifdef DCC_DEBUG -uint8_t NmraDcc::getIntCount(void) -{ - return DccProcState.IntCount; -} - -//////////////////////////////////////////////////////////////////////// -uint8_t NmraDcc::getTickCount(void) -{ - return DccProcState.TickCount; -} - -//////////////////////////////////////////////////////////////////////// -uint8_t NmraDcc::getNestedIrqCount(void) -{ - return DccProcState.NestedIrqCount; -} - -//////////////////////////////////////////////////////////////////////// -uint8_t NmraDcc::getState(void) -{ - return DccRx.State; -} - -//////////////////////////////////////////////////////////////////////// -uint8_t NmraDcc::getBitCount(void) -{ - return DccRx.BitCount; + notifyCVResetFactoryDefault(); } -#endif -//////////////////////////////////////////////////////////////////////// -void NmraDcc::setAccDecDCCAddrNextReceived(uint8_t enable) +void dcc_close() { - DccProcState.inAccDecDCCAddrNextReceivedMode = enable; + NODE_DBG("[dcc_close]\n"); + platform_gpio_mode(DccProcState.IntPin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_PULLUP); } -//////////////////////////////////////////////////////////////////////// -uint8_t NmraDcc::process() +void dcc_init() { - if( DccProcState.inServiceMode ) - { - if( (millis() - DccProcState.LastServiceModeMillis ) > 20L ) - { - clearDccProcState( 0 ) ; - } - } - - if( DccRx.DataReady ) - { - // We need to do this check with interrupts disabled - //SET_TP4; -#ifdef ESP32 - portENTER_CRITICAL(&mux); -#else - noInterrupts(); -#endif - Msg = DccRx.PacketCopy ; - DccRx.DataReady = 0 ; - -#ifdef ESP32 - portEXIT_CRITICAL(&mux); -#else - interrupts(); -#endif - #ifdef DCC_DBGVAR - countOf.Tel++; - #endif - - uint8_t xorValue = 0 ; - - for(uint8_t i = 0; i < DccRx.PacketCopy.Size; i++) - xorValue ^= DccRx.PacketCopy.Data[i]; - if(xorValue) { - #ifdef DCC_DBGVAR - DB_PRINT("Cerr"); - countOf.Err++; - #endif - return 0 ; - } else { - if( notifyDccMsg ) notifyDccMsg( &Msg ); - - execDccProcessor( &Msg ); - } - return 1 ; - } - - return 0 ; -}; + NODE_DBG("[dcc_init]\n"); + DataReady_taskid = task_get_id((task_callback_t) process); +} \ No newline at end of file diff --git a/app/include/driver/NmraDcc.h b/app/include/driver/NmraDcc.h index 527757388d..b864b259c9 100644 --- a/app/include/driver/NmraDcc.h +++ b/app/include/driver/NmraDcc.h @@ -29,25 +29,20 @@ // //------------------------------------------------------------------------ +// NodeMCU Lua port by @voborsky + +// #define NODE_DEBUG +// #define DCC_DEBUG +// #define DCC_DBGVAR + // Uncomment the following Line to Enable Service Mode CV Programming #define NMRA_DCC_PROCESS_SERVICEMODE // Uncomment the following line to Enable MultiFunction Decoder Operations #define NMRA_DCC_PROCESS_MULTIFUNCTION -// Uncomment the following line to Enable 14 Speed Step Support -//#define NMRA_DCC_ENABLE_14_SPEED_STEP_MODE - -#if defined(ARDUINO) && ARDUINO >= 100 -#include "Arduino.h" -#else -#include "WProgram.h" -#endif - -#include "EEPROM.h" - -#ifndef NMRADCC_IS_IN -#define NMRADCC_IS_IN +// #ifndef NMRADCC_IS_IN +// #define NMRADCC_IS_IN #define NMRADCC_VERSION 201 // Version 2.0.1 @@ -55,9 +50,9 @@ typedef struct { - uint8_t Size ; - uint8_t PreambleBits ; - uint8_t Data[MAX_DCC_MESSAGE_LEN] ; + uint8_t Size ; + uint8_t PreambleBits ; + uint8_t Data[MAX_DCC_MESSAGE_LEN] ; } DCC_MSG ; //-------------------------------------------------------------------------- @@ -96,36 +91,23 @@ typedef struct #define CV_MANUFACTURER_ID 8 #define CV_29_CONFIG 29 -#if defined(ESP32) - #include - #define MAXCV SPI_FLASH_SEC_SIZE -#elif defined(ESP8266) - #include - #define MAXCV SPI_FLASH_SEC_SIZE -#elif defined( __STM32F1__) - #define MAXCV (EEPROM_PAGE_SIZE/4 - 1) // number of storage places (CV address could be larger - // because STM32 uses virtual adresses) -#else - #define MAXCV E2END // the upper limit of the CV value currently defined to max memory. -#endif - typedef enum { - CV29_LOCO_DIR = 0b00000001, /** bit 0: Locomotive Direction: "0" = normal, "1" = reversed */ - CV29_F0_LOCATION = 0b00000010, /** bit 1: F0 location: "0" = bit 4 in Speed and Direction instructions, "1" = bit 4 in function group one instruction */ - CV29_APS = 0b00000100, /** bit 2: Alternate Power Source (APS) "0" = NMRA Digital only, "1" = Alternate power source set by CV12 */ - CV29_ADV_ACK = 0b00001000, /** bit 3: ACK, Advanced Acknowledge mode enabled if 1, disabled if 0 */ - CV29_SPEED_TABLE_ENABLE = 0b00010000, /** bit 4: STE, Speed Table Enable, "0" = values in CVs 2, 4 and 6, "1" = Custom table selected by CV 25 */ - CV29_EXT_ADDRESSING = 0b00100000, /** bit 5: "0" = one byte addressing, "1" = two byte addressing */ - CV29_OUTPUT_ADDRESS_MODE = 0b01000000, /** bit 6: "0" = Decoder Address Mode "1" = Output Address Mode */ - CV29_ACCESSORY_DECODER = 0b10000000, /** bit 7: "0" = Multi-Function Decoder Mode "1" = Accessory Decoder Mode */ + CV29_LOCO_DIR = 0b00000001, /** bit 0: Locomotive Direction: "0" = normal, "1" = reversed */ + CV29_F0_LOCATION = 0b00000010, /** bit 1: F0 location: "0" = bit 4 in Speed and Direction instructions, "1" = bit 4 in function group one instruction */ + CV29_APS = 0b00000100, /** bit 2: Alternate Power Source (APS) "0" = NMRA Digital only, "1" = Alternate power source set by CV12 */ + CV29_ADV_ACK = 0b00001000, /** bit 3: ACK, Advanced Acknowledge mode enabled if 1, disabled if 0 */ + CV29_SPEED_TABLE_ENABLE = 0b00010000, /** bit 4: STE, Speed Table Enable, "0" = values in CVs 2, 4 and 6, "1" = Custom table selected by CV 25 */ + CV29_EXT_ADDRESSING = 0b00100000, /** bit 5: "0" = one byte addressing, "1" = two byte addressing */ + CV29_OUTPUT_ADDRESS_MODE = 0b01000000, /** bit 6: "0" = Decoder Address Mode "1" = Output Address Mode */ + CV29_ACCESSORY_DECODER = 0b10000000, /** bit 7: "0" = Multi-Function Decoder Mode "1" = Accessory Decoder Mode */ } CV_29_BITS; typedef enum { #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE - SPEED_STEP_14 = 15, /**< ESTOP=0, 1 to 15 */ + SPEED_STEP_14 = 15, /**< ESTOP=0, 1 to 15 */ #endif - SPEED_STEP_28 = 29, /**< ESTOP=0, 1 to 29 */ - SPEED_STEP_128 = 127 /**< ESTOP=0, 1 to 127 */ + SPEED_STEP_28 = 29, /**< ESTOP=0, 1 to 29 */ + SPEED_STEP_128 = 127 /**< ESTOP=0, 1 to 127 */ } DCC_SPEED_STEPS; typedef enum { @@ -140,264 +122,97 @@ typedef enum { typedef enum { - FN_0_4 = 1, - FN_5_8, - FN_9_12, - FN_13_20, - FN_21_28, + FN_0_4 = 1, + FN_5_8, + FN_9_12, + FN_13_20, + FN_21_28, #ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE - FN_0 /** function light is controlled by base line package (14 speed steps) */ + FN_0 /** function light is controlled by base line package (14 speed steps) */ #endif } FN_GROUP; -#define FN_BIT_00 0x10 -#define FN_BIT_01 0x01 -#define FN_BIT_02 0x02 -#define FN_BIT_03 0x04 -#define FN_BIT_04 0x08 - -#define FN_BIT_05 0x01 -#define FN_BIT_06 0x02 -#define FN_BIT_07 0x04 -#define FN_BIT_08 0x08 - -#define FN_BIT_09 0x01 -#define FN_BIT_10 0x02 -#define FN_BIT_11 0x04 -#define FN_BIT_12 0x08 - -#define FN_BIT_13 0x01 -#define FN_BIT_14 0x02 -#define FN_BIT_15 0x04 -#define FN_BIT_16 0x08 -#define FN_BIT_17 0x10 -#define FN_BIT_18 0x20 -#define FN_BIT_19 0x40 -#define FN_BIT_20 0x80 - -#define FN_BIT_21 0x01 -#define FN_BIT_22 0x02 -#define FN_BIT_23 0x04 -#define FN_BIT_24 0x08 -#define FN_BIT_25 0x10 -#define FN_BIT_26 0x20 -#define FN_BIT_27 0x40 -#define FN_BIT_28 0x80 - -//#define DCC_DBGVAR +#define FN_BIT_00 0x10 +#define FN_BIT_01 0x01 +#define FN_BIT_02 0x02 +#define FN_BIT_03 0x04 +#define FN_BIT_04 0x08 + +#define FN_BIT_05 0x01 +#define FN_BIT_06 0x02 +#define FN_BIT_07 0x04 +#define FN_BIT_08 0x08 + +#define FN_BIT_09 0x01 +#define FN_BIT_10 0x02 +#define FN_BIT_11 0x04 +#define FN_BIT_12 0x08 + +#define FN_BIT_13 0x01 +#define FN_BIT_14 0x02 +#define FN_BIT_15 0x04 +#define FN_BIT_16 0x08 +#define FN_BIT_17 0x10 +#define FN_BIT_18 0x20 +#define FN_BIT_19 0x40 +#define FN_BIT_20 0x80 + +#define FN_BIT_21 0x01 +#define FN_BIT_22 0x02 +#define FN_BIT_23 0x04 +#define FN_BIT_24 0x08 +#define FN_BIT_25 0x10 +#define FN_BIT_26 0x20 +#define FN_BIT_27 0x40 +#define FN_BIT_28 0x80 + #ifdef DCC_DBGVAR typedef struct countOf_t { unsigned long Tel; unsigned long Err; }countOf_t ; -extern struct countOf_t countOf; +countOf_t countOf; #endif -class NmraDcc -{ - private: - DCC_MSG Msg ; - - public: - NmraDcc(); - // Flag values to be logically ORed together and passed into the init() method -#define FLAGS_MY_ADDRESS_ONLY 0x01 // Only process DCC Packets with My Address -#define FLAGS_AUTO_FACTORY_DEFAULT 0x02 // Call notifyCVResetFactoryDefault() if CV 7 & 8 == 255 +#define FLAGS_MY_ADDRESS_ONLY 0x01 // Only process DCC Packets with My Address +#define FLAGS_AUTO_FACTORY_DEFAULT 0x02 // Call notifyCVResetFactoryDefault() if CV 7 & 8 == 255 #define FLAGS_SETCV_CALLED 0x10 // only used internally !! #define FLAGS_OUTPUT_ADDRESS_MODE 0x40 // CV 29/541 bit 6 #define FLAGS_DCC_ACCESSORY_DECODER 0x80 // CV 29/541 bit 7 // Flag Bits that are cloned from CV29 relating the DCC Accessory Decoder -#define FLAGS_CV29_BITS (FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER) - - - /*+ - * pin() is called from setup() and sets up the pin used to receive DCC packets. - * - * Inputs: - * ExtIntNum - Interrupt number of the pin. Use digitalPinToInterrupt(ExtIntPinNum). - * ExtIntPinNum - Input pin number. - * EnablePullup - Set true to enable the pins pullup resistor. - * - * Returns: - * None. - */ - void pin( uint8_t ExtIntNum, uint8_t ExtIntPinNum, uint8_t EnablePullup); - - /*+ - * pin() is called from setup() and sets up the pin used to receive DCC packets. - * This relies on the internal function: digitalPinToInterrupt() to map the input pin number to the right interrupt - * - * Inputs: - * ExtIntPinNum - Input pin number. - * EnablePullup - Set true to enable the pins pullup resistor. - * - * Returns: - * None. - */ -#ifdef digitalPinToInterrupt -void pin( uint8_t ExtIntPinNum, uint8_t EnablePullup); -#endif +#define FLAGS_CV29_BITS (FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER) - /*+ - * init() is called from setup() after the pin() command is called. - * It initializes the NmDcc object and makes it ready to process packets. - * - * Inputs: - * ManufacturerId - Manufacturer ID returned in CV 8. - * Commonly MAN_ID_DIY. - * VersionId - Version ID returned in CV 7. - * Flags - ORed flags beginning with FLAGS_... - * FLAGS_MY_ADDRESS_ONLY - Only process packets with My Address. - * FLAGS_DCC_ACCESSORY_DECODER - Decoder is an accessory decoder. - * FLAGS_OUTPUT_ADDRESS_MODE - This flag applies to accessory decoders only. - * Accessory decoders normally have 4 paired outputs - * and a single address refers to all 4 outputs. - * Setting FLAGS_OUTPUT_ADDRESS_MODE causes each - * address to refer to a single output. - * OpsModeAddressBaseCV - Ops Mode base address. Set it to 0? - * - * Returns: - * None. - */ - void init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ); - - /*+ - * initAccessoryDecoder() is called from setup() for accessory decoders. - * It calls init() with FLAGS_DCC_ACCESSORY_DECODER ORed into Flags. - * - * Inputs: - * ManufacturerId - Manufacturer ID returned in CV 8. - * Commonly MAN_ID_DIY. - * VersionId - Version ID returned in CV 7. - * Flags - ORed flags beginning with FLAGS_... - * FLAGS_DCC_ACCESSORY_DECODER will be set for init() call. - * OpsModeAddressBaseCV - Ops Mode base address. Set it to 0? - * - * Returns: - * None. - */ - void initAccessoryDecoder( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ); - - /*+ - * process() is called from loop() to process DCC packets. - * It must be called very frequently to keep up with the packets. - * - * Inputs: - * None. - * - * Returns: - * 1 - Packet succesfully parsed on this call to process(). - * 0 - Packet not ready or received packet had an error. - */ - uint8_t process(); - - /*+ - * getCV() returns the selected CV value. - * - * Inputs: - * CV - CV number. It must point to a valid CV. - * - * Returns: - * Value - CV value. Invalid CV numbers will return an undefined result - * since nothing will have been set in that EEPROM position. - * Calls notifyCVRead() if it is defined. - */ - uint8_t getCV( uint16_t CV ); - - /*+ - * setCV() sets the value of a CV. - * - * Inputs: - * CV - CV number. It must point to a valid CV. - * Value - CV value. - * - * Returns: - * Value - CV value set by this call. - * since nothing will have been set in that EEPROM position. - * Calls notifyCVWrite() if it is defined. - * Calls notifyCVChange() if the value is changed by this call. - */ - uint8_t setCV( uint16_t CV, uint8_t Value); - - /*+ - * setAccDecDCCAddrNextReceived() enables/disables the setting of the board address from the next received turnout command - * - * Inputs: - * enable- boolean to enable or disable the mode - * - * Returns: - */ - void setAccDecDCCAddrNextReceived(uint8_t enable); - - /*+ - * isSetCVReady() returns 1 if EEPROM is ready to write. - * - * Inputs: - * CV - CV number. It must point to a valid CV. - * Value - CV value. - * - * Returns: - * ready - 1 if ready to write, 0 otherwise. AVR processor will block - * for several ms. for each write cycle so you should check this to avoid blocks. - * Note: It returns the value returned by notifyIsSetCVReady() if it is defined. - * Calls notifyIsSetCVReady() if it is defined. - */ - uint8_t isSetCVReady( void ); - - /*+ - * getAddr() return the currently active decoder address. - * based on decoder type and current address size. - * - * Inputs: - * None. - * - * Returns: - * Adr - The current decoder address based on decoder type(Multifunction, Accessory) - * and short or long address selection for Multifunction decoders. - */ - uint16_t getAddr(void); - - /*+ - * getX() return debugging data if DCC_DEBUG is defined. - * You would really need to be modifying the library to need them. - * - * Inputs: - * None. - * - * Returns: - * getIntCount - Init to 0 and apparently never incremented? - * getTickCount - Init to 0 and incremented each time interrupt handler - * completes without an error. - * getBitCount - Bit count of valid packet, 0 otherwise. Only valid until - * start of the next packet. - * getState - Current WAIT_... state as defined by DccRxWaitState in NmraDcc.cpp. - * getNestedIrqCount - Init to 0 and incremented each time the interrupt handler - * is called before the previous interrupt was complete. - * This is an error indication and may indicate the system - * is not handling packets fast enough or some other error is occurring. - */ -// #define DCC_DEBUG -#ifdef DCC_DEBUG - uint8_t getIntCount(void); - uint8_t getTickCount(void); - uint8_t getBitCount(void); - uint8_t getState(void); - uint8_t getNestedIrqCount(void); -#endif +#define DCC_RESET 1 +#define DCC_IDLE 2 +#define DCC_SPEED 3 +#define DCC_SPEED_RAW 4 +#define DCC_FUNC 5 +#define DCC_TURNOUT 6 +#define DCC_ACCESSORY 7 +#define DCC_RAW 8 +#define DCC_SERVICEMODE 9 + +#define CV_VALID 10 +#define CV_READ 11 +#define CV_WRITE 12 +#define CV_RESET 13 + + +void dcc_setup(uint8_t pin, uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ); + + +void dcc_close(); + +void dcc_init(); -}; /************************************************************************************ Call-back functions ************************************************************************************/ -#if defined (__cplusplus) - extern "C" { -#endif - /*+ * notifyDccReset(uint8_t hardReset) Callback for a DCC reset command. * @@ -630,45 +445,6 @@ extern uint8_t notifyCVRead( uint16_t CV) __attribute__ ((weak)); */ extern uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) __attribute__ ((weak)); -/*+ - * notifyIsSetCVReady() Callback to to determine if CVs can be written. - * This is called when the library needs to determine - * is ready to write without blocking or failing. - * Note: If defined, this callback - * MUST determine if a CV write would block or fail - * return the appropriate value. - * If this callback is not defined, - * the library determines if a write to the EEPROM - * would block. - * - * Inputs: - * None - * - * Returns: - * 1 - CV is ready to be written. - * 0 - CV is not ready to be written. - */ -extern uint8_t notifyIsSetCVReady(void) __attribute__ ((weak)); - -/*+ - * notifyCVChange() Called when a CV value is changed. - * This is called whenever a CV's value is changed. - * notifyDccCVChange() Called only when a CV value is changed by a Dcc packet or a internal lib function. - * it is NOT called if the CV is changed by means of the setCV() method. - * Note: It is not called if notifyCVWrite() is defined - * or if the value in the EEPROM is the same as the value - * in the write command. - * - * Inputs: - * CV - CV number. - * Value - Value of the CV. - * - * Returns: - * None - */ -extern void notifyCVChange( uint16_t CV, uint8_t Value) __attribute__ ((weak)); -extern void notifyDccCVChange( uint16_t CV, uint8_t Value) __attribute__ ((weak)); - /*+ * notifyCVResetFactoryDefault() Called when CVs must be reset. * This is called when CVs must be reset @@ -686,18 +462,6 @@ extern void notifyDccCVChange( uint16_t CV, uint8_t Value) __attribute__ ((we */ extern void notifyCVResetFactoryDefault(void) __attribute__ ((weak)); -/*+ - * notifyCVAck() Called when a CV write must be acknowledged. - * This callback must increase the current drawn by this - * decoder by at least 60mA for 6ms +/- 1ms. - * - * Inputs: - * None - * * - * Returns: - * None - */ -extern void notifyCVAck(void) __attribute__ ((weak)); /*+ * notifyServiceMode(bool) Called when state of 'inServiceMode' changes * @@ -711,11 +475,5 @@ extern void notifyServiceMode(bool) __attribute__ ((weak)); // Deprecated, only for backward compatibility with version 1.4.2. // Don't use in new designs. These functions may be dropped in future versions -extern void notifyDccAccState( uint16_t Addr, uint16_t BoardAddr, uint8_t OutputAddr, uint8_t State ) __attribute__ ((weak)); -extern void notifyDccSigState( uint16_t Addr, uint8_t OutputIndex, uint8_t State) __attribute__ ((weak)); - -#if defined (__cplusplus) -} -#endif - -#endif +// extern void notifyDccAccState( uint16_t Addr, uint16_t BoardAddr, uint8_t OutputAddr, uint8_t State ) __attribute__ ((weak)); +// extern void notifyDccSigState( uint16_t Addr, uint8_t OutputIndex, uint8_t State) __attribute__ ((weak)); diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 0b28dbe757..8534c25eac 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -21,6 +21,7 @@ //#define LUA_USE_MODULES_COLOR_UTILS //#define LUA_USE_MODULES_CRON //#define LUA_USE_MODULES_CRYPTO +//#define LUA_USE_MODULES_DCC #define LUA_USE_MODULES_DHT //#define LUA_USE_MODULES_ENCODER //#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work. diff --git a/app/modules/dcc.c b/app/modules/dcc.c new file mode 100644 index 0000000000..c979880f92 --- /dev/null +++ b/app/modules/dcc.c @@ -0,0 +1,290 @@ +// NodeMCU Lua port by @voborsky +// Module for handling NMRA DCC protocol +// #define NODE_DEBUG + +#include "module.h" +#include "lauxlib.h" +#include "platform.h" +#include "driver/NmraDcc.h" + +#ifdef LUA_USE_MODULES_DCC +#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE) +#error Must have GPIO_INTERRUPT and GPIO_INTERRUPT_HOOK if using DCC module +#endif +#endif + +#define TYPE "Type" +#define OPERATION "Operation" + +static inline void register_lua_cb(lua_State* L,int* cb_ref){ + int ref=luaL_ref(L, LUA_REGISTRYINDEX); + if( *cb_ref != LUA_NOREF){ + luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref); + } + *cb_ref = ref; +} + +static inline void unregister_lua_cb(lua_State* L, int* cb_ref){ + if(*cb_ref != LUA_NOREF){ + luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref); + *cb_ref = LUA_NOREF; + } +} + +static int notify_cb = LUA_NOREF; +static int CV_cb = LUA_NOREF; + +// DCC commands + +void cbInit(lua_State* L, uint16_t command) { + if(notify_cb == LUA_NOREF) + return; + lua_rawgeti(L, LUA_REGISTRYINDEX, notify_cb); + lua_pushinteger(L, command); + lua_newtable(L); +} + +void cbAddFieldInteger(lua_State* L, uint16_t Value, char *Field) { + lua_pushinteger(L, Value); + lua_setfield(L, -2, Field); +} + +void notifyDccReset(uint8_t hardReset ) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_RESET); + cbAddFieldInteger(L, hardReset, "hardReset"); + lua_call(L, 2, 0); +} + +void notifyDccIdle(void) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_IDLE); + lua_call(L, 2, 0); +} + +void notifyDccSpeed( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps ) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_SPEED); + cbAddFieldInteger(L, Addr, "Addr"); + cbAddFieldInteger(L, AddrType, "AddrType"); + cbAddFieldInteger(L, Speed, "Speed"); + cbAddFieldInteger(L, Dir, "Dir"); + cbAddFieldInteger(L, SpeedSteps, "SpeedSteps"); + lua_call(L, 2, 0); +} + +void notifyDccSpeedRaw( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Raw) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_SPEED_RAW); + cbAddFieldInteger(L, Addr, "Addr"); + cbAddFieldInteger(L, AddrType, "AddrType"); + cbAddFieldInteger(L, Raw, "Raw"); + lua_call(L, 2, 0); +} + +void notifyDccFunc( uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_FUNC); + cbAddFieldInteger(L, Addr, "Addr"); + cbAddFieldInteger(L, AddrType, "AddrType"); + cbAddFieldInteger(L, FuncGrp, "FuncGrp"); + cbAddFieldInteger(L, FuncState, "FuncState"); + lua_call(L, 2, 0); +} + +void notifyDccAccTurnoutBoard( uint16_t BoardAddr, uint8_t OutputPair, uint8_t Direction, uint8_t OutputPower ) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_TURNOUT); + cbAddFieldInteger(L, BoardAddr, "BoardAddr"); + cbAddFieldInteger(L, OutputPair, "OutputPair"); + cbAddFieldInteger(L, Direction, "Direction"); + cbAddFieldInteger(L, OutputPower, "OutputPower"); + lua_call(L, 2, 0); +} + +void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower ) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_TURNOUT); + cbAddFieldInteger(L, Addr, "Addr"); + cbAddFieldInteger(L, Direction, "Direction"); + cbAddFieldInteger(L, OutputPower, "OutputPower"); + lua_call(L, 2, 0); +} + +void notifyDccAccBoardAddrSet( uint16_t BoardAddr) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_ACCESSORY); + cbAddFieldInteger(L, BoardAddr, "BoardAddr"); + lua_call(L, 2, 0); +} + +void notifyDccAccOutputAddrSet( uint16_t Addr) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_ACCESSORY); + cbAddFieldInteger(L, Addr, "Addr"); + lua_call(L, 2, 0); +} + +void notifyDccSigOutputState( uint16_t Addr, uint8_t State) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_ACCESSORY); + cbAddFieldInteger(L, State, "State"); + lua_call(L, 2, 0); +} + +void notifyDccMsg( DCC_MSG * Msg ) { + lua_State* L = lua_getstate(); + cbInit(L, DCC_RAW); + cbAddFieldInteger(L, Msg->Size, "Size"); + cbAddFieldInteger(L, Msg->PreambleBits, "PreambleBits"); + char field[8]; + for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ ) { + ets_sprintf(field, "Data%d", i); + cbAddFieldInteger(L, Msg->Data[i], field); + } + lua_call(L, 2, 0); +} + +void notifyServiceMode(bool InServiceMode){ + lua_State* L = lua_getstate(); + cbInit(L, DCC_SERVICEMODE); + cbAddFieldInteger(L, InServiceMode, "InServiceMode"); + lua_call(L, 2, 0); +} + +// CV handling + +uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) { + lua_State* L = lua_getstate(); + if(notify_cb == LUA_NOREF) + return 0; + lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb); + lua_pushinteger(L, CV_VALID); + lua_newtable(L); + cbAddFieldInteger(L, CV, "CV"); + cbAddFieldInteger(L, Writable, "Writable"); + lua_call(L, 2, 1); + uint8 result = lua_tointeger(L, -1); + lua_pop(L, 1); + return result; +} + +uint8_t notifyCVRead( uint16_t CV) { + lua_State* L = lua_getstate(); + if(notify_cb == LUA_NOREF) + return 0; + lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb); + lua_pushinteger(L, CV_READ); + lua_newtable(L); + cbAddFieldInteger(L, CV, "CV"); + lua_call(L, 2, 1); + uint8 result = lua_tointeger(L, -1); + lua_pop(L, 1); + return result; +} + +uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) { + lua_State* L = lua_getstate(); + if(notify_cb == LUA_NOREF) + return 0; + lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb); + lua_pushinteger(L, CV_WRITE); + lua_newtable(L); + cbAddFieldInteger(L, CV, "CV"); + cbAddFieldInteger(L, Value, "Value"); + lua_call(L, 2, 0); + return Value; +} + +void notifyCVResetFactoryDefault(void) { + lua_State* L = lua_getstate(); + if(notify_cb == LUA_NOREF) + return; + lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb); + lua_pushinteger(L, CV_RESET); + lua_call(L, 1, 0); +} + +static int dcc_lua_setup(lua_State* L) { + NODE_DBG("[dcc_lua_setup]\n"); + if (!lua_isnumber(L, 6) && !lua_isnumber(L, 7)) { + return luaL_error(L, "wrong arg range"); + } + uint8_t pin = luaL_checkinteger(L, 1); + luaL_argcheck(L, platform_gpio_exists(pin) && pin>0, 1, "Invalid interrupt pin"); + + if (lua_type(L, 2) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) + { + lua_pushvalue(L, 2); + register_lua_cb(L, ¬ify_cb); + } + else + { + unregister_lua_cb(L, ¬ify_cb); + } + + uint8_t ManufacturerId = luaL_checkinteger(L, 3); + uint8_t VersionId = luaL_checkinteger(L, 4); + uint8_t Flags = luaL_checkinteger(L, 5); + uint8_t OpsModeAddressBaseCV = luaL_checkinteger(L, 6); + + if (lua_type(L, 7) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) + { + lua_pushvalue(L, 7); + register_lua_cb(L, &CV_cb); + } + else + { + unregister_lua_cb(L, &CV_cb); + } + + NODE_DBG("[dcc_lua_setup] Enabling interrupt on PIN %d\n", pin); + dcc_setup(pin, ManufacturerId, VersionId, Flags, OpsModeAddressBaseCV ); + + return 0; +} + +static int dcc_lua_close(lua_State* L) { + dcc_close(); + unregister_lua_cb(L, ¬ify_cb); + return 0; +} + +int luaopen_dcc( lua_State *L ) { + NODE_DBG("[dcc_luaopen]\n"); + dcc_init(); + //DccRx.lua_cb_ref = LUA_NOREF; + return 0; +} + +// Module function map +LROT_BEGIN( dcc ) + LROT_FUNCENTRY( setup, dcc_lua_setup ) + LROT_FUNCENTRY( close, dcc_lua_close ) + + LROT_NUMENTRY( DCC_RESET, DCC_RESET ) + LROT_NUMENTRY( DCC_IDLE, DCC_IDLE ) + LROT_NUMENTRY( DCC_SPEED, DCC_SPEED ) + LROT_NUMENTRY( DCC_SPEED_RAW, DCC_SPEED_RAW ) + LROT_NUMENTRY( DCC_FUNC, DCC_FUNC ) + LROT_NUMENTRY( DCC_TURNOUT, DCC_TURNOUT ) + LROT_NUMENTRY( DCC_ACCESSORY, DCC_ACCESSORY ) + LROT_NUMENTRY( DCC_RAW, DCC_RAW ) + LROT_NUMENTRY( DCC_SERVICEMODE, DCC_SERVICEMODE ) + + LROT_NUMENTRY( CV_VALID, CV_VALID ) + LROT_NUMENTRY( CV_READ, CV_READ ) + LROT_NUMENTRY( CV_WRITE, CV_WRITE ) + LROT_NUMENTRY( CV_RESET, CV_RESET ) + + LROT_NUMENTRY( MAN_ID_JMRI, MAN_ID_JMRI) + LROT_NUMENTRY( MAN_ID_DIY, MAN_ID_DIY) + LROT_NUMENTRY( MAN_ID_SILICON_RAILWAY, MAN_ID_SILICON_RAILWAY) + + LROT_NUMENTRY( FLAGS_MY_ADDRESS_ONLY, FLAGS_MY_ADDRESS_ONLY ) + LROT_NUMENTRY( FLAGS_AUTO_FACTORY_DEFAULT, FLAGS_AUTO_FACTORY_DEFAULT ) + LROT_NUMENTRY( FLAGS_OUTPUT_ADDRESS_MODE, FLAGS_OUTPUT_ADDRESS_MODE ) + LROT_NUMENTRY( FLAGS_DCC_ACCESSORY_DECODER, FLAGS_DCC_ACCESSORY_DECODER ) +LROT_END( dcc, NULL, 0 ) + +NODEMCU_MODULE(DCC, "dcc", dcc, luaopen_dcc); diff --git a/docs/modules/dcc.md b/docs/modules/dcc.md new file mode 100644 index 0000000000..f2adfb30fd --- /dev/null +++ b/docs/modules/dcc.md @@ -0,0 +1,118 @@ +# DCC module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2019-08-31 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [dcc.c](../../app/modules/dcc.c)| + +The dcc module implements decoder of the [National Model Railroad Association](https://www.nmra.org/) (NMRA) Digital Command Control (DCC) decoder - see [DCC wiki](https://dccwiki.com/Introduction_to_DCC) for details. + +The hardware needed to decode the DCC signal can be built based on different DCC decoders implementation for Arduino, for inspiration see [https://mrrwa.org/dcc-decoder-interface/](https://mrrwa.org/dcc-decoder-interface/). Basically the signal from the DCC bus is connected via an optocoupler to any GPIO pin. The DCC bus can be also used to power the ESP. + +The module is based on the project NmraDcc [https://github.com/mrrwa/NmraDcc](https://github.com/mrrwa/NmraDcc) by Alex Shepherd. The module is based on the version from May 2005, commit [6d12e6cd3f5f520020d49946652a94c1e3473f6b](https://github.com/mrrwa/NmraDcc/tree/6d12e6cd3f5f520020d49946652a94c1e3473f6b). + +## dcc.setup() + +Initializes the dcc module and links callback functions. + +#### Syntax +`dcc.setup(DCC_command, ManufacturerId, VersionId, Flags, OpsModeAddressBaseCV, CV_callback)` + +#### Parameters +- `DCC_command(cmd, params)` calllback function that is called when a DCC command is decoded. `cmd` parameters is one of the following values. `params` contains a collection of parameters specific to given command. + - `dcc.DCC_RESET` no additional parameters, `params` is `nil`. + - `dcc.DCC_IDLE` no additional parameters, `params` is `nil`. + - `dcc.DCC_SPEED` parameters collection members are `Addr`, `AddrType`, `Speed`,`Dir`, `SpeedSteps`. + - `dcc.DCC_SPEED_RAW` parameters collection members are `Addr`, `AddrType`, `Raw`. + - `dcc.DCC_FUNC` parameters collection members are `Addr`, `AddrType`, `FuncGrp`,`FuncState`. + - `dcc.DCC_TURNOUT` parameters collection members are `BoardAddr`, `OutputPair`, `Direction`,`OutputPower` or `Addr`, `Direction`,`OutputPower`. + - `dcc.DCC_ACCESSORY` parameters collection has one member `BoardAddr` or `Addr` or `State`. + - `dcc.DCC_RAW` parameters collection member are `Size`, `PreambleBits`, `Data1` to `Data6`. + - `dcc.DCC_SERVICEMODE` parameters collection has one member `InServiceMode`. +- `ManufacturerId` Manufacturer ID returned in CV 8. Commonly `dcc.MAN_ID_DIY`. +- `VersionId` Version ID returned in CV 7. +- `Flags` one of or combination (OR operator) of + - `dcc.FLAGS_MY_ADDRESS_ONLY`Only process packets with My Address. + - `dcc.FLAGS_DCC_ACCESSORY_DECODER` Decoder is an accessory decode. + - `dcc.FLAGS_OUTPUT_ADDRESS_MODE` This flag applies to accessory decoders only. Accessory decoders normally have 4 paired outputs and a single address refers to all 4 outputs. Setting this flag causes each address to refer to a single output. + - `dcc.FLAGS_AUTO_FACTORY_DEFAULT` Call DCC command callback with `dcc.CV_RESET` command if CV 7 & 8 == 255. +- `OpsModeAddressBaseCV` Ops Mode base address. Set it to 0? +- `CV_callback(operation, param)` callback function that is called when any manipulation with CV ([Configuarion Variable](https://dccwiki.com/Configuration_Variable)) is requested. + - `dcc.CV_VALID`to determine if a given CV is valid. This callback must determine if a CV is valid and return the appropriate value. `param` collection has members `CV` and `Value`. + - `dcc.CV_READ` to read a CV. This callback must return the value of the CV. `param` collection has one member `CV` determing the CV number to be read. + - `dcc.CV_WRITE` to write a value to a CV. This callback must write the Value to the CV and return the value of the CV. `param` collection has members `CV` and `Value`. + - `dcc.CV_RESET` Called when CVs must be reset to their factory defaults. + +#### Returns +`nil` + +#### Example +`bit` module is used in the example though it is not needed for the dcc module functionality. +```lua +local PIN = 2 -- GPIO4 + +local addr = 0x12a + +CV = {[29]=0, + [1]=bit.band(addr, 0x3f), --CV_ACCESSORY_DECODER_ADDRESS_LSB (6 bits) + [9]=bit.band(bit.rshift(addr,6), 0x7) --CV_ACCESSORY_DECODER_ADDRESS_MSB (3 bits) + } + +local function DCC_command(cmd, params) + if cmd == dcc.DCC_IDLE then + return + elseif cmd == dcc.DCC_TURNOUT then + print("Turnout command") + elseif cmd == dcc.DCC_SPEED then + print("Speed command") + elseif cmd == dcc.DCC_FUNC then + print("Function command") + else + print("Other command", cmd) + end + + for i,j in pairs(params) do + print(i, j) + end + print(("="):rep(80)) +end + +local function CV_callback(operation, param) + local oper = "" + local result + if operation == dcc.CV_WRITE then + oper = "Write" + CV[param.CV]=param.Value + elseif operation == dcc.CV_READ then + oper = "Read" + result = CV[param.CV] + elseif operation == dcc.CV_VALID then + oper = "Valid" + result = 1 + elseif operation == CV_RESET then + oper = "Reset" + CV = {} + end + print(("[CV_callback] %s CV %d%s"):format(oper, param.CV or `nil`, param.Value and "\tValue: "..param.Value or "\tValue: nil")) + return result +end + +dcc.setup(PIN, + DCC_command, + dcc.MAN_ID_DIY, 1, + --bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT, dcc.FLAGS_DCC_ACCESSORY_DECODER, dcc.FLAGS_MY_ADDRESS_ONLY), + bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT), + 0, -- ??? + CV_callback) +``` + +## dcc.close() + +Stops the dcc module. + +#### Syntax +`dcc.close()` + +#### Parameters +`nil` + +#### Returns +`nil` \ No newline at end of file diff --git a/lua_examples/dcc/dcc.lua b/lua_examples/dcc/dcc.lua new file mode 100644 index 0000000000..922b89d388 --- /dev/null +++ b/lua_examples/dcc/dcc.lua @@ -0,0 +1,86 @@ +-- Simple example for responding to NMRA DCC commands +-- author @voborsky +local PIN = 2 -- GPIO4 + +local addr = 0x12a + +CV = {[29]=0, + [1]=bit.band(addr, 0x3f), --CV_ACCESSORY_DECODER_ADDRESS_LSB (6 bits) + [9]=bit.band(bit.rshift(addr,6), 0x7) --CV_ACCESSORY_DECODER_ADDRESS_MSB (3 bits) + } + +local function deepcopy(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[deepcopy(orig_key)] = deepcopy(orig_value) + end + setmetatable(copy, deepcopy(getmetatable(orig))) + else -- number, string, boolean, etc + copy = orig + end + return copy +end + +local cmd_last +local params_last + +local function is_new(cmd, params) + if cmd ~= cmd_last then return true end + for i,j in pairs(params) do + if params_last[i] ~= j then return true end + end + return false +end + +local function DCC_command(cmd, params) + if not is_new(cmd, params) then return end + if cmd == dcc.DCC_IDLE then + return + elseif cmd == dcc.DCC_TURNOUT then + print("Turnout command") + elseif cmd == dcc.DCC_SPEED then + print("Speed command") + elseif cmd == dcc.DCC_FUNC then + print("Function command") + else + print("Other command", cmd) + end + + for i,j in pairs(params) do + print(i, j) + end + print(("="):rep(80)) + cmd_last = cmd + params_last = deepcopy(params) +end + +local function CV_callback(operation, param) + local oper = "" + local result + if operation == dcc.CV_WRITE then + oper = "Write" + CV[param.CV]=param.Value + elseif operation == dcc.CV_READ then + oper = "Read" + result = CV[param.CV] + elseif operation == dcc.CV_VALID then + oper = "Valid" + result = 1 + elseif operation == CV_RESET then + oper = "Reset" + CV = {} + end + print(("[CV_callback] %s CV %d%s"):format(oper, param.CV, param.Value and "\tValue: "..param.Value or "\tValue: nil")) + return result +end + +dcc.setup(PIN, + DCC_command, + dcc.MAN_ID_DIY, 1, + --bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT, dcc.FLAGS_DCC_ACCESSORY_DECODER, dcc.FLAGS_MY_ADDRESS_ONLY), -- Accessories (turnouts) decoder + bit.bor(dcc.FLAGS_AUTO_FACTORY_DEFAULT), -- Cab (train) decoder + 0, -- ??? + CV_callback) diff --git a/mkdocs.yml b/mkdocs.yml index 4d704d1751..622fc256d7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -67,6 +67,7 @@ pages: - 'color-utils': 'modules/color-utils.md' - 'cron': 'modules/cron.md' - 'crypto': 'modules/crypto.md' + - 'dcc': 'modules/dcc.md' - 'dht': 'modules/dht.md' - 'encoder': 'modules/encoder.md' - 'enduser setup / captive portal / WiFi manager': 'modules/enduser-setup.md' From 609d52fcd0f42762fe97ce55829c1036ae99cfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Sat, 28 Dec 2019 14:09:48 +0100 Subject: [PATCH 3/3] Adjust inception date as so much time has passed --- docs/modules/dcc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/dcc.md b/docs/modules/dcc.md index f2adfb30fd..2a55fda20d 100644 --- a/docs/modules/dcc.md +++ b/docs/modules/dcc.md @@ -1,7 +1,7 @@ # DCC module | Since | Origin / Contributor | Maintainer | Source | | :----- | :-------------------- | :---------- | :------ | -| 2019-08-31 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [dcc.c](../../app/modules/dcc.c)| +| 2019-12-28 | [vsky279](https://github.com/vsky279) | [vsky279](https://github.com/vsky279) | [dcc.c](../../app/modules/dcc.c)| The dcc module implements decoder of the [National Model Railroad Association](https://www.nmra.org/) (NMRA) Digital Command Control (DCC) decoder - see [DCC wiki](https://dccwiki.com/Introduction_to_DCC) for details. @@ -115,4 +115,4 @@ Stops the dcc module. `nil` #### Returns -`nil` \ No newline at end of file +`nil`