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)