NmraDcc port to NodeMCU Lua

Merging @pjsg's ack functionalities and other fixes
This commit is contained in:
Lukáš Voborský 2021-02-04 21:43:58 +01:00 committed by Marcel Stör
parent 3bf8db1306
commit d528333eee
4 changed files with 453 additions and 57 deletions

View File

@ -45,8 +45,59 @@
// //
//------------------------------------------------------------------------ //------------------------------------------------------------------------
// NodeMCU Lua port by @voborsky
// #define NODE_DEBUG
#define NODEMCUDCC
#ifdef NODEMCUDCC
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#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')
#define byte uint8_t
#define word int16_t
#define abs(a) ((a) > 0 ? (a) : (0-a))
#define RISING GPIO_PIN_INTR_POSEDGE
#define FALLING GPIO_PIN_INTR_NEGEDGE
#define CHANGE GPIO_PIN_INTR_ANYEDGE
static uint32_t last_time_overflow_millis;
static uint32_t last_system_time;
uint32_t millis() {
uint32_t now = system_get_time();
if (now < last_system_time) {
// we have an overflow situation
// assume only one overflow
last_time_overflow_millis += (1 << 29) / 125; // (1 << 32) / 1000
}
last_system_time = now;
return last_time_overflow_millis + now / 1000;
}
#else
#include "NmraDcc.h" #include "NmraDcc.h"
#include "EEPROM.h" #include "EEPROM.h"
#endif
// Uncomment to print DEBUG messages // Uncomment to print DEBUG messages
// #define DEBUG_PRINT // #define DEBUG_PRINT
@ -113,6 +164,9 @@
// Debug-Ports // Debug-Ports
//#define debug // Testpulse for logic analyser //#define debug // Testpulse for logic analyser
#ifdef NODE_DEBUG
#define debug
#endif
#ifdef debug #ifdef debug
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define MODE_TP1 DDRF |= (1<<2) //pinA2 #define MODE_TP1 DDRF |= (1<<2) //pinA2
@ -210,6 +264,24 @@
//#elif defined(__AVR_ATmega128__) ||defined(__AVR_ATmega1281__)||defined(__AVR_ATmega2561__) //#elif defined(__AVR_ATmega128__) ||defined(__AVR_ATmega1281__)||defined(__AVR_ATmega2561__)
#elif defined(NODE_DEBUG)
#define PULLUP PLATFORM_GPIO_PULLUP
#define OUTPUT PLATFORM_GPIO_OUTPUT
#define HIGH PLATFORM_GPIO_HIGH
#define LOW PLATFORM_GPIO_LOW
#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 #else
#define MODE_TP1 #define MODE_TP1
#define SET_TP1 #define SET_TP1
@ -241,8 +313,12 @@
#endif #endif
#ifdef DEBUG_PRINT #ifdef DEBUG_PRINT
#ifdef NODEMCUDCC
#define DB_PRINT NODE_DBG
#else
#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.println( dbgbuf ); }
#define DB_PRINT_( x, ... ) { char dbgbuf[80]; sprintf_P( dbgbuf, (const char*) F( x ) , ##__VA_ARGS__ ) ; Serial.print( dbgbuf ); } #define DB_PRINT_( x, ... ) { char dbgbuf[80]; sprintf_P( dbgbuf, (const char*) F( x ) , ##__VA_ARGS__ ) ; Serial.print( dbgbuf ); }
#endif
#else #else
#define DB_PRINT( x, ... ) ; #define DB_PRINT( x, ... ) ;
#define DB_PRINT_( x, ... ) ; #define DB_PRINT_( x, ... ) ;
@ -257,6 +333,10 @@ static ExtIntTriggerMode ISREdge;
#elif defined ( ESP32 ) #elif defined ( ESP32 )
static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING
static byte ISRWatch; // Interrupt Handler Edge Filter static byte ISRWatch; // Interrupt Handler Edge Filter
#elif defined ( NODEMCUDCC )
static uint8_t ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING
static int16_t bitMax, bitMin;
DCC_MSG Msg ;
#else #else
static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING
static byte ISRWatch; // Interrupt Handler Edge Filter static byte ISRWatch; // Interrupt Handler Edge Filter
@ -307,10 +387,15 @@ typedef struct
uint8_t PageRegister ; // Used for Paged Operations in Service Mode Programming uint8_t PageRegister ; // Used for Paged Operations in Service Mode Programming
uint8_t DuplicateCount ; uint8_t DuplicateCount ;
DCC_MSG LastMsg ; DCC_MSG LastMsg ;
#ifdef NODEMCUDCC
uint8_t IntPin;
uint8_t IntBitmask;
#else
uint8_t ExtIntNum; uint8_t ExtIntNum;
uint8_t ExtIntPinNum; uint8_t ExtIntPinNum;
volatile uint8_t *ExtIntPort; // use port and bitmask to read input at AVR in ISR volatile uint8_t *ExtIntPort; // use port and bitmask to read input at AVR in ISR
uint8_t ExtIntMask; // digitalRead is too slow on AVR uint8_t ExtIntMask; // digitalRead is too slow on AVR
#endif
int16_t myDccAddress; // Cached value of DCC Address from CVs int16_t myDccAddress; // Cached value of DCC Address from CVs
uint8_t inAccDecDCCAddrNextReceivedMode; uint8_t inAccDecDCCAddrNextReceivedMode;
uint8_t cv29Value; uint8_t cv29Value;
@ -330,12 +415,27 @@ portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR ExternalInterruptHandler(void) void IRAM_ATTR ExternalInterruptHandler(void)
#elif defined(ESP8266) #elif defined(ESP8266)
void ICACHE_RAM_ATTR ExternalInterruptHandler(void) void ICACHE_RAM_ATTR ExternalInterruptHandler(void)
#elif defined(NODEMCUDCC)
task_handle_t DataReady_taskid;
static uint32_t ICACHE_RAM_ATTR InterruptHandler (uint32_t ret_gpio_status)
#else #else
void ExternalInterruptHandler(void) void ExternalInterruptHandler(void)
#endif #endif
{ {
SET_TP3; SET_TP3;
#ifdef NODEMCUDCC
// 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);
ret_gpio_status &= ~(DccProcState.IntBitmask);
#endif
#ifdef ESP32 #ifdef ESP32
// switch (ISRWatch) // switch (ISRWatch)
// { // {
@ -363,7 +463,11 @@ void ExternalInterruptHandler(void)
uint8_t DccBitVal; uint8_t DccBitVal;
static int8_t bit1, bit2 ; static int8_t bit1, bit2 ;
static unsigned int lastMicros = 0; static unsigned int lastMicros = 0;
#ifdef NODEMCUDCC
static byte halfBit, preambleBitCount;
#else
static byte halfBit, DCC_IrqRunning, preambleBitCount; static byte halfBit, DCC_IrqRunning, preambleBitCount;
#endif
unsigned int actMicros, bitMicros; unsigned int actMicros, bitMicros;
#ifdef ALLOW_NESTED_IRQ #ifdef ALLOW_NESTED_IRQ
if ( DCC_IrqRunning ) { if ( DCC_IrqRunning ) {
@ -377,19 +481,29 @@ void ExternalInterruptHandler(void)
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
} }
#endif #endif
#ifdef NODEMCUDCC
actMicros = system_get_time();
#else
actMicros = micros(); actMicros = micros();
#endif
bitMicros = actMicros-lastMicros; bitMicros = actMicros-lastMicros;
CLR_TP3; SET_TP3; CLR_TP3; SET_TP3;
#ifdef __AVR_MEGA__ #ifdef __AVR_MEGA__
if ( bitMicros < bitMin || ( DccRx.State != WAIT_START_BIT && (*DccProcState.ExtIntPort & DccProcState.ExtIntMask) != (ISRLevel) ) ) { if ( bitMicros < bitMin || ( DccRx.State != WAIT_START_BIT && (*DccProcState.ExtIntPort & DccProcState.ExtIntMask) != (ISRLevel) ) ) {
#elif defined(NODEMCUDCC)
if ( bitMicros < bitMin ) {
#else #else
if ( bitMicros < bitMin || ( DccRx.State != WAIT_START_BIT && digitalRead( DccProcState.ExtIntPinNum ) != (ISRLevel) ) ) { if ( bitMicros < bitMin || ( DccRx.State != WAIT_START_BIT && digitalRead( DccProcState.ExtIntPinNum ) != (ISRLevel) ) ) {
#endif #endif
// too short - my be false interrupt due to glitch or false protocol or level does not match RISING / FALLING edge -> ignore this IRQ // too short - my be false interrupt due to glitch or false protocol or level does not match RISING / FALLING edge -> ignore this IRQ
CLR_TP3; CLR_TP3;
SET_TP4; /*delayMicroseconds(1); */ CLR_TP4; SET_TP4; /*delayMicroseconds(1); */ CLR_TP4;
#ifdef NODEMCUDCC
return ret_gpio_status; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
#else
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
#endif
} }
CLR_TP3; SET_TP3; CLR_TP3; SET_TP3;
@ -409,6 +523,8 @@ void ExternalInterruptHandler(void)
#endif #endif
#ifdef ESP32 #ifdef ESP32
ISRWatch = ISREdge; ISRWatch = ISREdge;
#elif defined(NODEMCUDCC)
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge );
#else #else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
#endif #endif
@ -492,6 +608,8 @@ void ExternalInterruptHandler(void)
#endif #endif
#ifdef ESP32 #ifdef ESP32
ISRWatch = ISREdge; ISRWatch = ISREdge;
#elif defined(NODEMCUDCC)
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge);
#else #else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
// enable level checking ( with direct port reading @ AVR ) // enable level checking ( with direct port reading @ AVR )
@ -542,13 +660,17 @@ void ExternalInterruptHandler(void)
#endif #endif
#ifdef ESP32 #ifdef ESP32
ISRWatch = ISREdge; ISRWatch = ISREdge;
#elif defined(NODEMCUDCC)
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge);
#else #else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
#endif #endif
#ifndef NODEMCUDCC
// enable level-checking // enable level-checking
ISRChkMask = DccProcState.ExtIntMask; ISRChkMask = DccProcState.ExtIntMask;
ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ; ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ;
//CLR_TP4; //CLR_TP4;
#endif
break; break;
case 4: // previous (first) halfbit was 0 case 4: // previous (first) halfbit was 0
// if this halfbit is 0 too, we got the startbit // if this halfbit is 0 too, we got the startbit
@ -585,14 +707,17 @@ void ExternalInterruptHandler(void)
#endif #endif
#ifdef ESP32 #ifdef ESP32
ISRWatch = ISREdge; ISRWatch = ISREdge;
#elif defined(NODEMCUDCC)
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), ISREdge);
#else #else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge ); attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
#endif #endif
#ifndef NODEMCUDCC
// enable level-checking // enable level-checking
ISRChkMask = DccProcState.ExtIntMask; ISRChkMask = DccProcState.ExtIntMask;
ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ; ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ;
//CLR_TP4; //CLR_TP4;
#endif
break; break;
} }
@ -642,6 +767,8 @@ void ExternalInterruptHandler(void)
DccRx.DataReady = 1 ; DccRx.DataReady = 1 ;
#ifdef ESP32 #ifdef ESP32
portEXIT_CRITICAL_ISR(&mux); portEXIT_CRITICAL_ISR(&mux);
#elif defined(NODEMCUDCC)
task_post_high(DataReady_taskid, (os_param_t) 0);
#endif #endif
// SET_TP2; CLR_TP2; // SET_TP2; CLR_TP2;
preambleBitCount = 0 ; preambleBitCount = 0 ;
@ -701,6 +828,8 @@ void ExternalInterruptHandler(void)
#endif #endif
#ifdef ESP32 #ifdef ESP32
ISRWatch = CHANGE; ISRWatch = CHANGE;
#elif defined(NODEMCUDCC)
gpio_pin_intr_state_set(GPIO_ID_PIN(pin_num[DccProcState.IntPin]), CHANGE);
#else #else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE); attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE);
#endif #endif
@ -726,6 +855,9 @@ void ExternalInterruptHandler(void)
#endif #endif
//CLR_TP1; //CLR_TP1;
CLR_TP3; CLR_TP3;
#ifdef NODEMCUDCC
return ret_gpio_status;
#endif
} }
void ackCV(void) void ackCV(void)
@ -747,6 +879,7 @@ void ackAdvancedCV(void)
} }
#ifndef NODEMCUDCC
uint8_t readEEPROM( unsigned int CV ) uint8_t readEEPROM( unsigned int CV )
{ {
return EEPROM.read(CV) ; return EEPROM.read(CV) ;
@ -773,6 +906,7 @@ bool readyEEPROM()
return true; return true;
#endif #endif
} }
#endif
uint8_t validCV( uint16_t CV, uint8_t Writable ) uint8_t validCV( uint16_t CV, uint8_t Writable )
{ {
@ -782,6 +916,9 @@ uint8_t validCV( uint16_t CV, uint8_t Writable )
if( notifyCVValid ) if( notifyCVValid )
return notifyCVValid( CV, Writable ) ; return notifyCVValid( CV, Writable ) ;
#ifdef NODEMCUDCC
return 0;
#else
uint8_t Valid = 1 ; uint8_t Valid = 1 ;
if( CV > MAXCV ) if( CV > MAXCV )
@ -791,17 +928,28 @@ uint8_t validCV( uint16_t CV, uint8_t Writable )
Valid = 0 ; Valid = 0 ;
return Valid ; return Valid ;
#endif
} }
#ifdef NODEMCUDCC
uint16_t readCV( unsigned int CV )
#else
uint8_t readCV( unsigned int CV ) uint8_t readCV( unsigned int CV )
#endif
{ {
#ifndef NODEMCUDCC
uint8_t Value ; uint8_t Value ;
#endif
if( notifyCVRead ) if( notifyCVRead )
return notifyCVRead( CV ) ; return notifyCVRead( CV ) ;
#ifndef NODEMCUDCC
Value = readEEPROM(CV); Value = readEEPROM(CV);
return Value ; return Value ;
#else
return 0;
#endif
} }
uint8_t writeCV( unsigned int CV, uint8_t Value) uint8_t writeCV( unsigned int CV, uint8_t Value)
@ -825,6 +973,9 @@ uint8_t writeCV( unsigned int CV, uint8_t Value)
if( notifyCVWrite ) if( notifyCVWrite )
return notifyCVWrite( CV, Value ) ; return notifyCVWrite( CV, Value ) ;
#ifdef NODEMCUDCC
return 0;
#else
if( readEEPROM( CV ) != Value ) if( readEEPROM( CV ) != Value )
{ {
writeEEPROM( CV, Value ) ; writeEEPROM( CV, Value ) ;
@ -837,6 +988,7 @@ uint8_t writeCV( unsigned int CV, uint8_t Value)
} }
return readEEPROM( CV ) ; return readEEPROM( CV ) ;
#endif
} }
uint16_t getMyAddr(void) uint16_t getMyAddr(void)
@ -896,9 +1048,18 @@ void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void
uint8_t BitValue = Value & 0x08 ; uint8_t BitValue = Value & 0x08 ;
uint8_t BitWrite = Value & 0x10 ; uint8_t BitWrite = Value & 0x10 ;
#ifdef NODEMCUDCC
uint16_t tempValue = readCV( CVAddr ) ; // Read the Current CV Value
#else
uint8_t tempValue = readCV( CVAddr ) ; // Read the Current CV Value uint8_t tempValue = readCV( CVAddr ) ; // Read the Current CV Value
#endif
#ifdef NODEMCUDCC
if (tempValue <= 255) {
DB_PRINT("CV: %d Current Value: %02X Bit-Wise Mode: %s Mask: %02X Value: %02X", CVAddr, tempValue, BitWrite ? "Write":"Read", BitMask, BitValue);
#else
DB_PRINT("CV: %d Current Value: %02X Bit-Wise Mode: %s Mask: %02X Value: %02X", CVAddr, tempValue, BitWrite ? "Write":"Read", BitMask, BitValue); DB_PRINT("CV: %d Current Value: %02X Bit-Wise Mode: %s Mask: %02X Value: %02X", CVAddr, tempValue, BitWrite ? "Write":"Read", BitMask, BitValue);
#endif
// Perform the Bit Write Operation // Perform the Bit Write Operation
if( BitWrite ) if( BitWrite )
@ -915,7 +1076,6 @@ void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void
ackFunction() ; ackFunction() ;
} }
} }
// Perform the Bit Verify Operation // Perform the Bit Verify Operation
else else
{ {
@ -933,6 +1093,9 @@ void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void
} }
} }
} }
#ifdef NODEMCUDCC
}
#endif
} }
} }
@ -947,9 +1110,13 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t
uint8_t CmdMasked = Cmd & 0b11100000 ; 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 we are an Accessory Decoder
if( DccProcState.Flags & FLAGS_DCC_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 // 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 // it's not our fake address, then return
if( ( CmdMasked != 0b11100000 ) || ( DccProcState.OpsModeAddressBaseCV == 0 ) ) if( ( CmdMasked != 0b11100000 ) || ( DccProcState.OpsModeAddressBaseCV == 0 ) )
@ -966,6 +1133,7 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t
else if( ( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) && ( Addr != getMyAddr() ) && ( Addr != 0 ) ) else if( ( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) && ( Addr != getMyAddr() ) && ( Addr != 0 ) )
return ; return ;
NODE_DBG("[dcc_processMultiFunctionMessage] CmdMasked: %x\n", CmdMasked);
switch( CmdMasked ) switch( CmdMasked )
{ {
case 0b00000000: // Decoder Control case 0b00000000: // Decoder Control
@ -1224,6 +1392,7 @@ void SerialPrintPacketHex(const __FlashStringHelper *strLabel, DCC_MSG * pDccMsg
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
void execDccProcessor( DCC_MSG * pDccMsg ) void execDccProcessor( DCC_MSG * pDccMsg )
{ {
NODE_DBG("[dcc_execDccProcessor]\n");
if( ( pDccMsg->Data[0] == 0 ) && ( pDccMsg->Data[1] == 0 ) ) if( ( pDccMsg->Data[0] == 0 ) && ( pDccMsg->Data[1] == 0 ) )
{ {
if( notifyDccReset ) if( notifyDccReset )
@ -1295,7 +1464,7 @@ void execDccProcessor( DCC_MSG * pDccMsg )
BoardAddress = ( ( (~pDccMsg->Data[1]) & 0b01110000 ) << 2 ) | ( pDccMsg->Data[0] & 0b00111111 ) ; BoardAddress = ( ( (~pDccMsg->Data[1]) & 0b01110000 ) << 2 ) | ( pDccMsg->Data[0] & 0b00111111 ) ;
TurnoutPairIndex = (pDccMsg->Data[1] & 0b00000110) >> 1; TurnoutPairIndex = (pDccMsg->Data[1] & 0b00000110) >> 1;
DB_PRINT("eDP: BAddr:%d, Index:%d", BoardAddress, TurnoutPairIndex); DB_PRINT("[dcc_execDccProcessor] eDP: BAddr:%d, Index:%d", BoardAddress, TurnoutPairIndex);
// First check for Legacy Accessory Decoder Configuration Variable Access Instruction // First check for Legacy Accessory Decoder Configuration Variable Access Instruction
// as it's got a different format to the others // as it's got a different format to the others
@ -1305,13 +1474,13 @@ void execDccProcessor( DCC_MSG * pDccMsg )
// 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((BoardAddress != getMyAddr()) && ( BoardAddress < 511 )) if((BoardAddress != getMyAddr()) && ( BoardAddress < 511 ))
{ {
DB_PRINT("eDP: Board Address Not Matched"); DB_PRINT("[dcc_execDccProcessor] eDP: Board Address Not Matched");
return; return;
} }
uint16_t cvAddress = ((pDccMsg->Data[1] & 0b00000011) << 8) + pDccMsg->Data[2] + 1; uint16_t cvAddress = ((pDccMsg->Data[1] & 0b00000011) << 8) + pDccMsg->Data[2] + 1;
uint8_t cvValue = pDccMsg->Data[3]; uint8_t cvValue = pDccMsg->Data[3];
DB_PRINT("eDP: CV:%d Value:%d", cvAddress, cvValue ); DB_PRINT("[dcc_execDccProcessor] eDP: CV:%d Value:%d", cvAddress, cvValue );
if(validCV( cvAddress, 1 )) if(validCV( cvAddress, 1 ))
writeCV(cvAddress, cvValue); writeCV(cvAddress, cvValue);
return; return;
@ -1320,7 +1489,7 @@ void execDccProcessor( DCC_MSG * pDccMsg )
OutputAddress = (((BoardAddress - 1) << 2 ) | TurnoutPairIndex) + 1 ; //decoder output addresses start with 1, packet address range starts with 0 OutputAddress = (((BoardAddress - 1) << 2 ) | TurnoutPairIndex) + 1 ; //decoder output addresses start with 1, packet address range starts with 0
// ( according to NMRA 9.2.2 ) // ( according to NMRA 9.2.2 )
DB_PRINT("eDP: OAddr:%d", OutputAddress); DB_PRINT("[dcc_execDccProcessor] eDP: OAddr:%d", OutputAddress);
if( DccProcState.inAccDecDCCAddrNextReceivedMode) if( DccProcState.inAccDecDCCAddrNextReceivedMode)
{ {
@ -1351,18 +1520,18 @@ void execDccProcessor( DCC_MSG * pDccMsg )
if( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY ) if( DccProcState.Flags & FLAGS_MY_ADDRESS_ONLY )
{ {
if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) { if( DccProcState.Flags & FLAGS_OUTPUT_ADDRESS_MODE ) {
DB_PRINT(" AddrChk: OAddr:%d, BAddr:%d, myAddr:%d Chk=%d", OutputAddress, BoardAddress, getMyAddr(), OutputAddress != getMyAddr() ); DB_PRINT("[dcc_execDccProcessor] AddrChk: OAddr:%d, BAddr:%d, myAddr:%d Chk=%d", OutputAddress, BoardAddress, getMyAddr(), OutputAddress != getMyAddr() );
if ( OutputAddress != getMyAddr() && OutputAddress < 2045 ) { if ( OutputAddress != getMyAddr() && OutputAddress < 2045 ) {
DB_PRINT(" eDP: OAddr:%d, myAddr:%d - no match", OutputAddress, getMyAddr() ); DB_PRINT("[dcc_execDccProcessor] eDP: OAddr:%d, myAddr:%d - no match", OutputAddress, getMyAddr() );
return; return;
} }
} else { } else {
if( ( BoardAddress != getMyAddr() ) && ( BoardAddress < 511 ) ) { if( ( BoardAddress != getMyAddr() ) && ( BoardAddress < 511 ) ) {
DB_PRINT(" eDP: BAddr:%d, myAddr:%d - no match", BoardAddress, getMyAddr() ); DB_PRINT("[dcc_execDccProcessor] eDP: BAddr:%d, myAddr:%d - no match", BoardAddress, getMyAddr() );
return; return;
} }
} }
DB_PRINT("eDP: Address Matched"); DB_PRINT("[dcc_execDccProcessor] eDP: Address Matched");
} }
@ -1491,6 +1660,7 @@ void execDccProcessor( DCC_MSG * pDccMsg )
} }
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
#ifndef NODEMCUDCC
NmraDcc::NmraDcc() NmraDcc::NmraDcc()
{ {
} }
@ -1538,9 +1708,14 @@ void NmraDcc::initAccessoryDecoder( uint8_t ManufacturerId, uint8_t VersionId, u
{ {
init(ManufacturerId, VersionId, Flags | FLAGS_DCC_ACCESSORY_DECODER, OpsModeAddressBaseCV); init(ManufacturerId, VersionId, Flags | FLAGS_DCC_ACCESSORY_DECODER, OpsModeAddressBaseCV);
} }
#endif //#ifndef NODEMCUDCC
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
#ifdef NODEMCUDCC
void dcc_setup(uint8_t pin, uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV)
#else
void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV ) void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV )
#endif
{ {
#if defined(ESP8266) #if defined(ESP8266)
EEPROM.begin(MAXCV); EEPROM.begin(MAXCV);
@ -1564,13 +1739,24 @@ void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, ui
DccProcState.inAccDecDCCAddrNextReceivedMode = 0; DccProcState.inAccDecDCCAddrNextReceivedMode = 0;
ISREdge = RISING; ISREdge = RISING;
#ifdef NODEMCUDCC
DccProcState.IntPin = pin;
DccProcState.IntBitmask = 1 << pin_num[pin];
#else
// level checking to detect false IRQ's fired by glitches // level checking to detect false IRQ's fired by glitches
ISRLevel = DccProcState.ExtIntMask; ISRLevel = DccProcState.ExtIntMask;
ISRChkMask = DccProcState.ExtIntMask; ISRChkMask = DccProcState.ExtIntMask;
#endif
#ifdef ESP32 #ifdef ESP32
ISRWatch = ISREdge; ISRWatch = ISREdge;
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE); attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE);
#elif defined(NODEMCUDCC)
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]), RISING);
#else #else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, RISING); attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, RISING);
#endif #endif
@ -1593,6 +1779,7 @@ void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, ui
notifyCVResetFactoryDefault(); notifyCVResetFactoryDefault();
} }
#ifndef NODEMCUDCC
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
uint8_t NmraDcc::getCV( uint16_t CV ) uint8_t NmraDcc::getCV( uint16_t CV )
{ {
@ -1662,9 +1849,14 @@ void NmraDcc::setAccDecDCCAddrNextReceived(uint8_t enable)
{ {
DccProcState.inAccDecDCCAddrNextReceivedMode = enable; DccProcState.inAccDecDCCAddrNextReceivedMode = enable;
} }
#endif //#ifndef NODEMCUDCC
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
#ifdef NODEMCUDCC
static uint8_t process (os_param_t param, uint8_t prio)
#else
uint8_t NmraDcc::process() uint8_t NmraDcc::process()
#endif
{ {
if( DccProcState.inServiceMode ) if( DccProcState.inServiceMode )
{ {
@ -1679,6 +1871,8 @@ uint8_t NmraDcc::process()
// We need to do this check with interrupts disabled // We need to do this check with interrupts disabled
#ifdef ESP32 #ifdef ESP32
portENTER_CRITICAL(&mux); portENTER_CRITICAL(&mux);
#elif defined(NODEMCUDCC)
ETS_GPIO_INTR_DISABLE();
#else #else
noInterrupts(); noInterrupts();
#endif #endif
@ -1687,6 +1881,8 @@ uint8_t NmraDcc::process()
#ifdef ESP32 #ifdef ESP32
portEXIT_CRITICAL(&mux); portEXIT_CRITICAL(&mux);
#elif defined(NODEMCUDCC)
ETS_GPIO_INTR_ENABLE();
#else #else
interrupts(); interrupts();
#endif #endif
@ -1699,9 +1895,26 @@ uint8_t NmraDcc::process()
if( notifyDccMsg ) notifyDccMsg( &Msg ); if( notifyDccMsg ) notifyDccMsg( &Msg );
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 ); execDccProcessor( &Msg );
return 1 ; return 1 ;
} }
return 0 ; return 0 ;
}; };
#ifdef NODEMCUDCC
void dcc_close()
{
NODE_DBG("[dcc_close]\n");
platform_gpio_mode(DccProcState.IntPin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_PULLUP);
}
void dcc_init()
{
NODE_DBG("[dcc_init]\n");
DataReady_taskid = task_get_id((task_callback_t) process);
}
#endif

View File

@ -39,6 +39,13 @@
// //
//------------------------------------------------------------------------ //------------------------------------------------------------------------
// NodeMCU Lua port by @voborsky
#define NODEMCUDCC
// #define NODE_DEBUG
// #define DCC_DEBUG
// #define DCC_DBGVAR
// Uncomment the following Line to Enable Service Mode CV Programming // Uncomment the following Line to Enable Service Mode CV Programming
#define NMRA_DCC_PROCESS_SERVICEMODE #define NMRA_DCC_PROCESS_SERVICEMODE
@ -50,6 +57,7 @@
#if defined(ARDUINO) && ARDUINO >= 100 #if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h" #include "Arduino.h"
#elif defined(NODEMCUDCC)
#else #else
#include "WProgram.h" #include "WProgram.h"
#endif #endif
@ -204,9 +212,14 @@ typedef struct countOf_t {
unsigned long Err; unsigned long Err;
}countOf_t ; }countOf_t ;
#ifdef NODEMCUDCC
countOf_t countOf;
#else
extern struct countOf_t countOf; extern struct countOf_t countOf;
#endif #endif
#endif
#ifndef NODEMCUDCC
class NmraDcc class NmraDcc
{ {
private: private:
@ -214,6 +227,7 @@ class NmraDcc
public: public:
NmraDcc(); NmraDcc();
#endif
// Flag values to be logically ORed together and passed into the init() method // 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_MY_ADDRESS_ONLY 0x01 // Only process DCC Packets with My Address
@ -225,7 +239,7 @@ class NmraDcc
// Flag Bits that are cloned from CV29 relating the DCC Accessory Decoder // Flag Bits that are cloned from CV29 relating the DCC Accessory Decoder
#define FLAGS_CV29_BITS (FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER) #define FLAGS_CV29_BITS (FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER)
#ifndef NODEMCUDCC
/*+ /*+
* pin() is called from setup() and sets up the pin used to receive DCC packets. * pin() is called from setup() and sets up the pin used to receive DCC packets.
* *
@ -403,6 +417,32 @@ void pin( uint8_t ExtIntPinNum, uint8_t EnablePullup);
}; };
#else
#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
#define CV_ACK_COMPLETE 14
void dcc_setup(uint8_t pin, uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, uint8_t OpsModeAddressBaseCV);
void dcc_close();
void dcc_init();
#endif //#ifndef NODEMCUDCC
/************************************************************************************ /************************************************************************************
Call-back functions Call-back functions
************************************************************************************/ ************************************************************************************/
@ -608,7 +648,11 @@ extern void notifyDccMsg( DCC_MSG * Msg ) __attribute__ ((weak));
* 1 - CV is valid. * 1 - CV is valid.
* 0 - CV is not valid. * 0 - CV is not valid.
*/ */
#ifdef NODEMCUDCC
extern uint16_t notifyCVValid( uint16_t CV, uint8_t Writable ) __attribute__ ((weak));
#else
extern uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) __attribute__ ((weak)); extern uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) __attribute__ ((weak));
#endif
/*+ /*+
* notifyCVRead() Callback to read a CV. * notifyCVRead() Callback to read a CV.
@ -622,10 +666,13 @@ extern uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) __attribute__ ((we
* CV - CV number. * CV - CV number.
* *
* Returns: * Returns:
* Value - Value of the CV. * Value - Value of the CV. Or a value > 255 to indicate error.
*/ */
#ifdef NODEMCUDCC
extern uint16_t notifyCVRead( uint16_t CV) __attribute__ ((weak));
#else
extern uint8_t notifyCVRead( uint16_t CV) __attribute__ ((weak)); extern uint8_t notifyCVRead( uint16_t CV) __attribute__ ((weak));
#endif
/*+ /*+
* notifyCVWrite() Callback to write a value to a CV. * notifyCVWrite() Callback to write a value to a CV.
* This is called when the library needs to write * This is called when the library needs to write
@ -639,10 +686,15 @@ extern uint8_t notifyCVRead( uint16_t CV) __attribute__ ((weak));
* Value - Value of the CV. * Value - Value of the CV.
* *
* Returns: * Returns:
* Value - Value of the CV. * Value - Value of the CV. Or a value > 255 to signal error.
*/ */
#ifdef NODEMCUDCC
extern uint16_t notifyCVWrite( uint16_t CV, uint8_t Value) __attribute__ ((weak));
#else
extern uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) __attribute__ ((weak)); extern uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) __attribute__ ((weak));
#endif
#ifndef NODEMCUDCC
/*+ /*+
* notifyIsSetCVReady() Callback to to determine if CVs can be written. * notifyIsSetCVReady() Callback to to determine if CVs can be written.
* This is called when the library needs to determine * This is called when the library needs to determine
@ -681,6 +733,7 @@ extern uint8_t notifyIsSetCVReady(void) __attribute__ ((weak));
*/ */
extern void notifyCVChange( uint16_t CV, uint8_t Value) __attribute__ ((weak)); extern void notifyCVChange( uint16_t CV, uint8_t Value) __attribute__ ((weak));
extern void notifyDccCVChange( uint16_t CV, uint8_t Value) __attribute__ ((weak)); extern void notifyDccCVChange( uint16_t CV, uint8_t Value) __attribute__ ((weak));
#endif
/*+ /*+
* notifyCVResetFactoryDefault() Called when CVs must be reset. * notifyCVResetFactoryDefault() Called when CVs must be reset.
@ -743,3 +796,4 @@ extern void notifyDccSigState( uint16_t Addr, uint8_t OutputIndex, uint8_t State
#endif #endif
#endif #endif

View File

@ -1,4 +1,4 @@
// NodeMCU Lua port by @voborsky // NodeMCU Lua port by @voborsky, @pjsg
// Module for handling NMRA DCC protocol // Module for handling NMRA DCC protocol
// #define NODE_DEBUG // #define NODE_DEBUG
@ -6,6 +6,7 @@
#include "lauxlib.h" #include "lauxlib.h"
#include "platform.h" #include "platform.h"
#include "driver/NmraDcc.h" #include "driver/NmraDcc.h"
#include "hw_timer.h"
#ifdef LUA_USE_MODULES_DCC #ifdef LUA_USE_MODULES_DCC
#if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE) #if !defined(GPIO_INTERRUPT_ENABLE) || !defined(GPIO_INTERRUPT_HOOK_ENABLE)
@ -16,6 +17,8 @@
#define TYPE "Type" #define TYPE "Type"
#define OPERATION "Operation" #define OPERATION "Operation"
#define TIMER_OWNER (('D' << 8) + 'C')
static inline void register_lua_cb(lua_State* L,int* cb_ref){ static inline void register_lua_cb(lua_State* L,int* cb_ref){
int ref=luaL_ref(L, LUA_REGISTRYINDEX); int ref=luaL_ref(L, LUA_REGISTRYINDEX);
if( *cb_ref != LUA_NOREF){ if( *cb_ref != LUA_NOREF){
@ -33,6 +36,15 @@ static inline void unregister_lua_cb(lua_State* L, int* cb_ref){
static int notify_cb = LUA_NOREF; static int notify_cb = LUA_NOREF;
static int CV_cb = LUA_NOREF; static int CV_cb = LUA_NOREF;
static int CV_ref = LUA_NOREF;
static int8_t ackPin;
static char ackInProgress;
static platform_task_handle_t tasknumber;
typedef struct {
uint16_t cv;
uint8_t value;
} CVData;
// DCC commands // DCC commands
@ -154,8 +166,11 @@ void notifyServiceMode(bool InServiceMode){
// CV handling // CV handling
uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) { uint16_t notifyCVValid( uint16_t CV, uint8_t Writable ) {
lua_State* L = lua_getstate(); lua_State* L = lua_getstate();
if (CV_ref != LUA_NOREF) {
return 1;
}
if(notify_cb == LUA_NOREF) if(notify_cb == LUA_NOREF)
return 0; return 0;
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb); lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
@ -165,13 +180,45 @@ uint8_t notifyCVValid( uint16_t CV, uint8_t Writable ) {
cbAddFieldInteger(L, Writable, "Writable"); cbAddFieldInteger(L, Writable, "Writable");
if (luaL_pcallx(L, 2, 1) != LUA_OK) if (luaL_pcallx(L, 2, 1) != LUA_OK)
return 0; return 0;
uint8 result = lua_tointeger(L, -1); uint8 result = lua_tointeger(L, -1) || lua_toboolean(L, -1);
lua_pop(L, 1); lua_pop(L, 1);
return result; return result;
} }
uint8_t notifyCVRead( uint16_t CV) { static int doDirectCVRead(lua_State *L) {
CVData *data = (CVData*) lua_touserdata(L, -1);
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_ref);
lua_pushinteger(L, data->cv);
lua_gettable(L, -2);
data->value = (uint8_t) luaL_checkinteger(L, -1);
return 0;
}
static int doDirectCVWrite(lua_State *L) {
CVData *data = (CVData*) lua_touserdata(L, -1);
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_ref);
lua_pushinteger(L, data->cv);
lua_pushinteger(L, data->value);
lua_settable(L, -3);
return 0;
}
uint16_t notifyCVRead( uint16_t CV) {
lua_State* L = lua_getstate(); lua_State* L = lua_getstate();
if (CV_ref != LUA_NOREF) {
CVData data;
data.cv = CV;
lua_pushcfunction(L, doDirectCVRead);
lua_pushlightuserdata(L, &data);
if (lua_pcall(L, 1, 0, 0)) {
// An error.
lua_pop(L, 1);
return 256;
}
return data.value;
}
if(notify_cb == LUA_NOREF) if(notify_cb == LUA_NOREF)
return 0; return 0;
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb); lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
@ -185,8 +232,23 @@ uint8_t notifyCVRead( uint16_t CV) {
return result; return result;
} }
uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) { uint16_t notifyCVWrite( uint16_t CV, uint8_t Value) {
lua_State* L = lua_getstate(); lua_State* L = lua_getstate();
if (CV_ref != LUA_NOREF) {
CVData data;
data.cv = CV;
data.value = Value;
lua_pushcfunction(L, doDirectCVWrite);
lua_pushlightuserdata(L, &data);
if (lua_pcall(L, 1, 0, 0)) {
// An error.
lua_pop(L, 1);
return 256;
}
return data.value;
}
if(notify_cb == LUA_NOREF) if(notify_cb == LUA_NOREF)
return 0; return 0;
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb); lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
@ -194,53 +256,106 @@ uint8_t notifyCVWrite( uint16_t CV, uint8_t Value) {
lua_newtable(L); lua_newtable(L);
cbAddFieldInteger(L, CV, "CV"); cbAddFieldInteger(L, CV, "CV");
cbAddFieldInteger(L, Value, "Value"); cbAddFieldInteger(L, Value, "Value");
luaL_pcallx(L, 2, 0); luaL_pcallx(L, 2, 1);
// Return is an optional value (if integer). If nil, then it is old style
if (!lua_isnil(L, -1)) {
Value = lua_tointeger(L, -1);
}
lua_pop(L, 1);
return Value; return Value;
} }
void notifyCVResetFactoryDefault(void) { static void notifyCVNoArgs(int callback_type) {
lua_State* L = lua_getstate(); lua_State* L = lua_getstate();
if(notify_cb == LUA_NOREF) if (notify_cb == LUA_NOREF) {
return; return;
}
lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb); lua_rawgeti(L, LUA_REGISTRYINDEX, CV_cb);
lua_pushinteger(L, CV_RESET); lua_pushinteger(L, callback_type);
luaL_pcallx(L, 1, 0); luaL_pcallx(L, 1, 0);
} }
void notifyCVResetFactoryDefault(void) {
notifyCVNoArgs(CV_RESET);
}
void notifyCVAck(void) {
// Invoked when we should generate an ack pulse (if possible)
if (ackPin >= 0 && !ackInProgress) {
// Duration is 6ms +/- 1ms
platform_hw_timer_arm_us(TIMER_OWNER, 6 * 1000);
platform_gpio_write(ackPin, 1);
ackInProgress = TRUE;
}
}
static void ICACHE_RAM_ATTR cvAckComplete(os_param_t param) {
// Invoked when we should end the ack pulse
ackInProgress = FALSE;
platform_gpio_write(ackPin, 0);
if (CV_ref == LUA_NOREF) {
platform_post_high(tasknumber, CV_ACK_COMPLETE);
}
}
static int dcc_lua_setup(lua_State* L) { static int dcc_lua_setup(lua_State* L) {
NODE_DBG("[dcc_lua_setup]\n"); NODE_DBG("[dcc_lua_setup]\n");
if (!lua_isnumber(L, 6) && !lua_isnumber(L, 7)) { int narg = 1;
return luaL_error(L, "wrong arg range"); uint8_t pin = luaL_checkinteger(L, narg);
} luaL_argcheck(L, platform_gpio_exists(pin) && pin>0, narg, "Invalid interrupt pin");
uint8_t pin = luaL_checkinteger(L, 1); narg++;
luaL_argcheck(L, platform_gpio_exists(pin) && pin>0, 1, "Invalid interrupt pin");
if (lua_type(L, 2) == LUA_TFUNCTION) int8_t ackpin = -1;
{
lua_pushvalue(L, 2); if (lua_type(L, narg) == LUA_TNUMBER) {
register_lua_cb(L, &notify_cb); ackpin = luaL_checkinteger(L, narg);
luaL_argcheck(L, platform_gpio_exists(ackpin), narg, "Invalid ack pin");
narg++;
} }
else
{ if (lua_isfunction(L, narg)) {
lua_pushvalue(L, narg++);
register_lua_cb(L, &notify_cb);
} else {
unregister_lua_cb(L, &notify_cb); unregister_lua_cb(L, &notify_cb);
} }
uint8_t ManufacturerId = luaL_checkinteger(L, 3); uint8_t ManufacturerId = luaL_checkinteger(L, narg++);
uint8_t VersionId = luaL_checkinteger(L, 4); uint8_t VersionId = luaL_checkinteger(L, narg++);
uint8_t Flags = luaL_checkinteger(L, 5); uint8_t Flags = luaL_checkinteger(L, narg++);
uint8_t OpsModeAddressBaseCV = luaL_checkinteger(L, 6); uint8_t OpsModeAddressBaseCV = luaL_checkinteger(L, narg++);
if (lua_type(L, 7) == LUA_TFUNCTION) if (lua_istable(L, narg)) {
{ // This is the raw CV table
lua_pushvalue(L, 7); lua_pushvalue(L, narg++);
register_lua_cb(L, &CV_cb); register_lua_cb(L, &CV_ref);
} else {
unregister_lua_cb(L, &CV_ref);
} }
else
{ if (lua_isfunction(L, narg)) {
lua_pushvalue(L, narg++);
register_lua_cb(L, &CV_cb);
} else {
unregister_lua_cb(L, &CV_cb); unregister_lua_cb(L, &CV_cb);
} }
if (ackpin >= 0) {
// Now start things up
if (!platform_hw_timer_init(TIMER_OWNER, FRC1_SOURCE, FALSE)) {
// Failed to init the timer
luaL_error(L, "Unable to initialize timer");
}
platform_hw_timer_set_func(TIMER_OWNER, cvAckComplete, 0);
platform_gpio_write(ackpin, 0);
platform_gpio_mode(ackpin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT);
}
NODE_DBG("[dcc_lua_setup] Enabling interrupt on PIN %d\n", pin); NODE_DBG("[dcc_lua_setup] Enabling interrupt on PIN %d\n", pin);
ackPin = ackpin;
ackInProgress = FALSE;
dcc_setup(pin, ManufacturerId, VersionId, Flags, OpsModeAddressBaseCV ); dcc_setup(pin, ManufacturerId, VersionId, Flags, OpsModeAddressBaseCV );
return 0; return 0;
@ -249,13 +364,22 @@ static int dcc_lua_setup(lua_State* L) {
static int dcc_lua_close(lua_State* L) { static int dcc_lua_close(lua_State* L) {
dcc_close(); dcc_close();
unregister_lua_cb(L, &notify_cb); unregister_lua_cb(L, &notify_cb);
unregister_lua_cb(L, &CV_cb);
unregister_lua_cb(L, &CV_ref);
return 0; return 0;
} }
int luaopen_dcc( lua_State *L ) { static void dcc_task(os_param_t param, uint8_t prio)
NODE_DBG("[dcc_luaopen]\n"); {
(void) prio;
notifyCVNoArgs(param);
}
int dcc_lua_init( lua_State *L ) {
NODE_DBG("[dcc_lua_init]\n");
dcc_init(); dcc_init();
//DccRx.lua_cb_ref = LUA_NOREF; tasknumber = platform_task_get_id(dcc_task);
return 0; return 0;
} }
@ -278,6 +402,7 @@ LROT_BEGIN(dcc, NULL, 0)
LROT_NUMENTRY( CV_READ, CV_READ ) LROT_NUMENTRY( CV_READ, CV_READ )
LROT_NUMENTRY( CV_WRITE, CV_WRITE ) LROT_NUMENTRY( CV_WRITE, CV_WRITE )
LROT_NUMENTRY( CV_RESET, CV_RESET ) LROT_NUMENTRY( CV_RESET, CV_RESET )
LROT_NUMENTRY( CV_ACK_COMPLETE, CV_ACK_COMPLETE )
LROT_NUMENTRY( MAN_ID_JMRI, MAN_ID_JMRI) LROT_NUMENTRY( MAN_ID_JMRI, MAN_ID_JMRI)
LROT_NUMENTRY( MAN_ID_DIY, MAN_ID_DIY) LROT_NUMENTRY( MAN_ID_DIY, MAN_ID_DIY)
@ -289,4 +414,4 @@ LROT_BEGIN(dcc, NULL, 0)
LROT_NUMENTRY( FLAGS_DCC_ACCESSORY_DECODER, FLAGS_DCC_ACCESSORY_DECODER ) LROT_NUMENTRY( FLAGS_DCC_ACCESSORY_DECODER, FLAGS_DCC_ACCESSORY_DECODER )
LROT_END(dcc, NULL, 0) LROT_END(dcc, NULL, 0)
NODEMCU_MODULE(DCC, "dcc", dcc, luaopen_dcc); NODEMCU_MODULE(DCC, "dcc", dcc, dcc_lua_init);

View File

@ -7,23 +7,25 @@ The dcc module implements decoder of the [National Model Railroad Association](h
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 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). The module is based on the project NmraDcc [https://github.com/mrrwa/NmraDcc](https://github.com/mrrwa/NmraDcc) by Alex Shepherd (@kiwi64ajs). The module is based on latest commit from Oct 2020, commit [7e3b3e346991d74926e6c4f6fb46e27156b08578](https://github.com/mrrwa/NmraDcc/tree/7e3b3e346991d74926e6c4f6fb46e27156b08578).
## dcc.setup() ## dcc.setup()
Initializes the dcc module and links callback functions. Initializes the dcc module and links callback functions.
#### Syntax #### Syntax
`dcc.setup(DCC_command, ManufacturerId, VersionId, Flags, OpsModeAddressBaseCV, CV_callback)` `dcc.setup(Pin, [AckPin, ] DCC_command, ManufacturerId, VersionId, Flags, OpsModeAddressBaseCV [, CV_table] [, CV_callback])`
#### Parameters #### Parameters
- `Pin` the GPIO pin number connected to the DCC detector (must be interrupt capable pin).
- `AckPin` the (optional) GPIO pin number connected to the ACK mechanism. Will be set HIGH to signal an ACK.
- `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_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_RESET` no additional parameters, `params` is `nil`.
- `dcc.DCC_IDLE` 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` parameters collection members are `Addr`, `AddrType`, `Speed`, `Dir`, `SpeedSteps`.
- `dcc.DCC_SPEED_RAW` parameters collection members are `Addr`, `AddrType`, `Raw`. - `dcc.DCC_SPEED_RAW` parameters collection members are `Addr`, `AddrType`, `Raw`.
- `dcc.DCC_FUNC` parameters collection members are `Addr`, `AddrType`, `FuncGrp`,`FuncState`. - `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_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_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_RAW` parameters collection member are `Size`, `PreambleBits`, `Data1` to `Data6`.
- `dcc.DCC_SERVICEMODE` parameters collection has one member `InServiceMode`. - `dcc.DCC_SERVICEMODE` parameters collection has one member `InServiceMode`.
@ -35,11 +37,13 @@ Initializes the dcc module and links callback functions.
- `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_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. - `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? - `OpsModeAddressBaseCV` Ops Mode base address. Set it to 0?
- `CV_table` The CV values will be directly accessed from this table. metamethods will be invoked if needed. Any errors thrown will cause the CV to be considered invalid. Using this option will prevent `CV_VALID`, `CV_READ`, `CV_WRITE` and `CV_ACK_COMPLETE` from happening.
- `CV_callback(operation, param)` callback function that is called when any manipulation with CV ([Configuarion Variable](https://dccwiki.com/Configuration_Variable)) is requested. - `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_VALID` to determine if a given CV is valid and (possibly) writable. This callback must determine if a CV is readable or writable and return the appropriate value(0/1/true/false). The `param` collection has members `CV` and `Writable`.
- `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_READ` to read a CV. This callback must return the value of the CV. The `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_WRITE` to write a value to a CV. This callback must write the Value to the CV and return the value of the CV. The `param` collection has members `CV` and `Value`. Ideally, the final value should be returned -- this may differ from the requested value.
- `dcc.CV_RESET` Called when CVs must be reset to their factory defaults. - `dcc.CV_RESET` Called when CVs must be reset to their factory defaults.
- `dcc.CV_ACK_COMPLETE` Called when an ACK pulse has finished being sent. Only invoked if `AckPin` is specified.
#### Returns #### Returns
`nil` `nil`