1 Commits

Author SHA1 Message Date
Alex Shepherd
4b175e9229 Merge branch 'AdvancedCVAck' into ESP32-IRAM_ATTR
* AdvancedCVAck:
  split out ServiceMode ackCV from Ops Mode AdvancedCVAck as doing a ackCV in Ops Mode is wrong and adds 6ms busy delay add cache of CV29 value
  bumped version to 2.0.2
  reverted changes around lastMicros
  added conditional compilation for ESP8266 to add ICACHE_RAM_ATTR to ExternalInterruptHandler changed storage for Micros to unsigned long
  changed the version to 201 in the header

# Conflicts:
#	NmraDcc.cpp
reverted to unsigned int
2019-08-06 01:23:12 +12:00
5 changed files with 1601 additions and 1810 deletions

2
.gitignore vendored
View File

@@ -1,2 +0,0 @@
.development
*.zip

View File

@@ -2,21 +2,11 @@
//
// Model Railroading with Arduino - NmraDcc.cpp
//
// Copyright (c) 2008 - 2020 Alex Shepherd
// Copyright (c) 2008 - 2017 Alex Shepherd
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// This source file is subject of the GNU general public license 2,
// that is available at the world-wide-web at
// http://www.gnu.org/licenses/gpl.txt
//
//------------------------------------------------------------------------
//
@@ -37,7 +27,7 @@
// Minor fixes to pass NMRA Baseline Conformance Tests.
// 2018-12-17 added ESP32 support by Trusty (thierry@lapajaparis.net)
// 2019-02-17 added ESP32 specific changes by Hans Tanner
// 2020-05-15 changes to pass NMRA Tests ( always search for preamble )
//
//------------------------------------------------------------------------
//
// purpose: Provide a simplified interface to decode NMRA DCC packets
@@ -46,10 +36,12 @@
//------------------------------------------------------------------------
#include "NmraDcc.h"
#include "EEPROM.h"
#ifdef __AVR_MEGA__
#include <avr/eeprom.h>
#endif
// Uncomment to print DEBUG messages
// #define DEBUG_PRINT
//#define DEBUG_PRINT
//------------------------------------------------------------------------
// DCC Receive Routine
@@ -82,26 +74,19 @@
// DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________
// |<--------146us------>|
// ^-INTx ^-INTx
// less than 146us: its a one-Bit
// less than 138us: its a one-Bit
//
//
// |<-----------------232us----------->|
// DCC 0: _________XXXXXXXXXXXXXXXXXX__________________XXXXXXXX__________
// |<--------146us------->|
// ^-INTx ^-INTx
// greater than 146us: its a zero bit
// greater than 138us: its a zero bit
//
//
//
//
//------------------------------------------------------------------------
// if this is commented out, bit synchronisation is only done after a wrong checksum
#define SYNC_ALWAYS
// if this is commented out, Zero-Bit_Stretching is not supported
// ( Bits longer than 2* MAX ONEBIT are treated as error )
#define SUPPORT_ZERO_BIT_STRETCHING
#define MAX_ONEBITFULL 146
#define MAX_PRAEAMBEL 146
@@ -192,7 +177,7 @@
#define SET_TP3 GPOS = (1 << D7);
#define CLR_TP3 GPOC = (1 << D7);
#define MODE_TP4 pinMode( D8,OUTPUT ) ; // GPIO 15
#define SET_TP4 GPOS = (1 << D8);
#define SET_TP4 GPOC = (1 << D8);
#define CLR_TP4 GPOC = (1 << D8);
#elif defined(ESP32)
#define MODE_TP1 pinMode( 33,OUTPUT ) ; // GPIO 33
@@ -205,7 +190,7 @@
#define SET_TP3 GPOS = (1 << 26);
#define CLR_TP3 GPOC = (1 << 26);
#define MODE_TP4 pinMode( 27,OUTPUT ) ; // GPIO 27
#define SET_TP4 GPOS = (1 << 27);
#define SET_TP4 GPOC = (1 << 27);
#define CLR_TP4 GPOC = (1 << 27);
@@ -261,17 +246,12 @@ static byte ISRWatch; // Interrupt Handler Edge Filter
static byte ISREdge; // Holder of the Next Edge we're looking for: RISING or FALLING
static byte ISRWatch; // Interrupt Handler Edge Filter
#endif
byte ISRLevel; // expected Level at DCC input during ISR ( to detect glitches )
byte ISRChkMask; // Flag if Level must be checked
static word bitMax, bitMin;
typedef enum
{
WAIT_PREAMBLE = 0,
WAIT_START_BIT,
#ifndef SYNC_ALWAYS
WAIT_START_BIT_FULL,
#endif
WAIT_DATA,
WAIT_END_BIT
}
@@ -292,7 +272,6 @@ struct DccRx_t
uint8_t DataReady ;
uint8_t BitCount ;
uint8_t TempByte ;
uint8_t chkSum;
DCC_MSG PacketBuf;
DCC_MSG PacketCopy;
}
@@ -309,8 +288,6 @@ typedef struct
DCC_MSG LastMsg ;
uint8_t ExtIntNum;
uint8_t ExtIntPinNum;
volatile uint8_t *ExtIntPort; // use port and bitmask to read input at AVR in ISR
uint8_t ExtIntMask; // digitalRead is too slow on AVR
int16_t myDccAddress; // Cached value of DCC Address from CVs
uint8_t inAccDecDCCAddrNextReceivedMode;
uint8_t cv29Value;
@@ -334,8 +311,6 @@ void ICACHE_RAM_ATTR ExternalInterruptHandler(void)
void ExternalInterruptHandler(void)
#endif
{
SET_TP3;
#ifdef ESP32
// switch (ISRWatch)
// {
@@ -363,7 +338,7 @@ void ExternalInterruptHandler(void)
uint8_t DccBitVal;
static int8_t bit1, bit2 ;
static unsigned int lastMicros = 0;
static byte halfBit, DCC_IrqRunning, preambleBitCount;
static byte halfBit, DCC_IrqRunning;
unsigned int actMicros, bitMicros;
#ifdef ALLOW_NESTED_IRQ
if ( DCC_IrqRunning ) {
@@ -377,53 +352,17 @@ void ExternalInterruptHandler(void)
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
}
#endif
SET_TP3;
actMicros = micros();
bitMicros = actMicros-lastMicros;
CLR_TP3; SET_TP3;
#ifdef __AVR_MEGA__
if ( bitMicros < bitMin || ( DccRx.State != WAIT_START_BIT && (*DccProcState.ExtIntPort & DccProcState.ExtIntMask) != (ISRLevel) ) ) {
#else
if ( bitMicros < bitMin || ( DccRx.State != WAIT_START_BIT && digitalRead( DccProcState.ExtIntPinNum ) != (ISRLevel) ) ) {
#endif
// too short - my be false interrupt due to glitch or false protocol or level does not match RISING / FALLING edge -> ignore this IRQ
if ( bitMicros < bitMin ) {
// too short - my be false interrupt due to glitch or false protocol -> ignore
CLR_TP3;
SET_TP4; /*delayMicroseconds(1); */ CLR_TP4;
SET_TP4; CLR_TP4;
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
}
CLR_TP3; SET_TP3;
lastMicros = actMicros;
#ifndef SUPPORT_ZERO_BIT_STRETCHING
//if ( bitMicros > MAX_ZEROBITFULL ) {
if ( bitMicros > (bitMax*2) ) {
// too long - my be false protocol -> start over
DccRx.State = WAIT_PREAMBLE ;
DccRx.BitCount = 0 ;
preambleBitCount = 0;
// SET_TP2; CLR_TP2;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
#if defined ( __STM32F1__ )
detachInterrupt( DccProcState.ExtIntNum );
#endif
#ifdef ESP32
ISRWatch = ISREdge;
#else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
#endif
// enable level-checking
ISRChkMask = DccProcState.ExtIntMask;
ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ;
CLR_TP3;
//CLR_TP3;
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
}
CLR_TP3;
SET_TP3;
#endif
DccBitVal = ( bitMicros < bitMax );
lastMicros = actMicros;
#ifdef ALLOW_NESTED_IRQ
DCC_IrqRunning = true;
@@ -433,37 +372,43 @@ void ExternalInterruptHandler(void)
#ifdef DCC_DEBUG
DccProcState.TickCount++;
#endif
switch( DccRx.State )
{
case WAIT_PREAMBLE:
// We don't have to do anything special - looking for a preamble condition is done always
SET_TP2;
break;
if( DccBitVal )
{
SET_TP1;
DccRx.BitCount++;
if( DccRx.BitCount > 10 ) {
DccRx.State = WAIT_START_BIT ;
// While waiting for the start bit, detect halfbit lengths. We will detect the correct
// sync and detect whether we see a false (e.g. motorola) protocol
#ifndef SYNC_ALWAYS
case WAIT_START_BIT_FULL:
// wait for startbit without level checking
if ( !DccBitVal ) {
// we got the startbit
CLR_TP2;CLR_TP1;
DccRx.State = WAIT_DATA ;
#if defined ( __STM32F1__ )
detachInterrupt( DccProcState.ExtIntNum );
#endif
#ifdef ESP32
ISRWatch = CHANGE;
#else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE);
#endif
halfBit = 0;
bitMax = MAX_ONEBITHALF;
bitMin = MIN_ONEBITHALF;
CLR_TP1;
// initialize packet buffer
DccRx.PacketBuf.Size = 0;
/*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;*/
DccRx.PacketBuf.PreambleBits = preambleBitCount;
}
} else {
SET_TP1;
DccRx.BitCount = 0 ;
DccRx.chkSum = 0 ;
DccRx.TempByte = 0 ;
//SET_TP1;
CLR_TP1;
}
break;
#endif
case WAIT_START_BIT:
// we are looking for first half "0" bit after preamble
switch ( halfBit ) {
case 0:
case 0: //SET_TP1;
// check first part
if ( DccBitVal ) {
// is still 1-bit (Preamble)
@@ -471,22 +416,24 @@ void ExternalInterruptHandler(void)
bit1=bitMicros;
} else {
// was "0" half bit, maybe the startbit
SET_TP1;
halfBit = 4;
CLR_TP1;
}
break;
case 1: // previous halfbit was '1'
case 1: //SET_TP1; // previous halfbit was '1'
if ( DccBitVal ) {
// its a '1' halfBit -> we are still in the preamble
halfBit = 0;
bit2=bitMicros;
preambleBitCount++;
DccRx.BitCount++;
if( abs(bit2-bit1) > MAX_BITDIFF ) {
// the length of the 2 halfbits differ too much -> wrong protokoll
DccRx.State = WAIT_PREAMBLE;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
preambleBitCount = 0;
// SET_TP2; CLR_TP2;
DccRx.BitCount = 0;
#if defined ( __STM32F1__ )
detachInterrupt( DccProcState.ExtIntNum );
#endif
@@ -494,9 +441,6 @@ void ExternalInterruptHandler(void)
ISRWatch = ISREdge;
#else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
// enable level checking ( with direct port reading @ AVR )
ISRChkMask = DccProcState.ExtIntMask;
ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ;
#endif
SET_TP3;
CLR_TP4;
@@ -509,33 +453,30 @@ void ExternalInterruptHandler(void)
SET_TP3;
}
break;
case 3: // previous halfbit was '0' in second halfbit
case 3: //SET_TP1; // previous halfbit was '0' in second halfbit
if ( DccBitVal ) {
// its a '1' halfbit -> we got only a half '0' bit -> cannot be DCC
DccRx.State = WAIT_PREAMBLE;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
preambleBitCount = 0;
// SET_TP2; CLR_TP2;
DccRx.BitCount = 0;
} else {
// we got two '0' halfbits -> it's the startbit
// but sync is NOT ok, change IRQ edge.
CLR_TP2;CLR_TP1;
if ( ISREdge == RISING ) ISREdge = FALLING; else ISREdge = RISING;
DccRx.State = WAIT_DATA ;
CLR_TP1;
bitMax = MAX_ONEBITFULL;
bitMin = MIN_ONEBITFULL;
DccRx.PacketBuf.Size = 0;
/*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;*/
DccRx.PacketBuf.PreambleBits = preambleBitCount;
DccRx.PacketBuf.PreambleBits = 0;
for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;
DccRx.PacketBuf.PreambleBits = DccRx.BitCount;
DccRx.BitCount = 0 ;
DccRx.chkSum = 0 ;
DccRx.TempByte = 0 ;
//SET_TP1;
}
//SET_TP4;
SET_TP4;
#if defined ( __STM32F1__ )
detachInterrupt( DccProcState.ExtIntNum );
@@ -545,40 +486,34 @@ void ExternalInterruptHandler(void)
#else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
#endif
// enable level-checking
ISRChkMask = DccProcState.ExtIntMask;
ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ;
//CLR_TP4;
CLR_TP1;
CLR_TP4;
break;
case 4: // previous (first) halfbit was 0
case 4: SET_TP1; // previous (first) halfbit was 0
// if this halfbit is 0 too, we got the startbit
if ( DccBitVal ) {
// second halfbit is 1 -> unknown protokoll
DccRx.State = WAIT_PREAMBLE;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
preambleBitCount = 0;
CLR_TP2;CLR_TP1;
DccRx.BitCount = 0;
} else {
// we got the startbit
CLR_TP2;CLR_TP1;
DccRx.State = WAIT_DATA ;
CLR_TP1;
bitMax = MAX_ONEBITFULL;
bitMin = MIN_ONEBITFULL;
// initialize packet buffer
DccRx.PacketBuf.Size = 0;
/*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;*/
DccRx.PacketBuf.PreambleBits = preambleBitCount;
DccRx.PacketBuf.PreambleBits = 0;
for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
DccRx.PacketBuf.Data[i] = 0;
DccRx.PacketBuf.PreambleBits = DccRx.BitCount;
DccRx.BitCount = 0 ;
DccRx.chkSum = 0 ;
DccRx.TempByte = 0 ;
//SET_TP1;
}
//SET_TP4;
CLR_TP1;
SET_TP4;
#if defined ( __STM32F1__ )
detachInterrupt( DccProcState.ExtIntNum );
@@ -588,18 +523,14 @@ void ExternalInterruptHandler(void)
#else
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
#endif
// enable level-checking
ISRChkMask = DccProcState.ExtIntMask;
ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ;
//CLR_TP4;
CLR_TP4;
break;
}
break;
case WAIT_DATA:
CLR_TP2;
DccRx.BitCount++;
DccRx.TempByte = ( DccRx.TempByte << 1 ) ;
if( DccBitVal )
@@ -618,44 +549,30 @@ void ExternalInterruptHandler(void)
{
DccRx.State = WAIT_END_BIT ;
DccRx.PacketBuf.Data[ DccRx.PacketBuf.Size++ ] = DccRx.TempByte ;
DccRx.chkSum ^= DccRx.TempByte;
}
}
break;
case WAIT_END_BIT:
SET_TP2;CLR_TP2;
DccRx.BitCount++;
if( DccBitVal ) { // End of packet?
CLR_TP3; SET_TP4;
if( DccBitVal ) // End of packet?
{
CLR_TP3;
DccRx.State = WAIT_PREAMBLE ;
DccRx.BitCount = 0 ;
bitMax = MAX_PRAEAMBEL;
bitMin = MIN_ONEBITFULL;
SET_TP1;
if ( DccRx.chkSum == 0 ) {
// Packet is valid
#ifdef ESP32
#ifdef ESP32
portENTER_CRITICAL_ISR(&mux);
#endif
#endif
DccRx.PacketCopy = DccRx.PacketBuf ;
DccRx.DataReady = 1 ;
#ifdef ESP32
#ifdef ESP32
portEXIT_CRITICAL_ISR(&mux);
#endif
// SET_TP2; CLR_TP2;
preambleBitCount = 0 ;
} else {
// Wrong checksum
CLR_TP1;
#ifdef DCC_DBGVAR
DB_PRINT("Cerr");
countOf.Err++;
#endif
#endif
SET_TP3;
}
SET_TP3; CLR_TP4;
} else { // Get next Byte
else // Get next Byte
// KGW - Abort immediately if packet is too long.
if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort
{
@@ -672,88 +589,31 @@ void ExternalInterruptHandler(void)
DccRx.TempByte = 0 ;
}
}
}
// unless we're already looking for the start bit
// we always search for a preamble ( ( 10 or more consecutive 1 bits )
// if we found it within a packet, the packet decoding is aborted because
// that much one bits cannot be valid in a packet.
if ( DccRx.State != WAIT_START_BIT ) {
if( DccBitVal )
{
preambleBitCount++;
//SET_TP2;
if( preambleBitCount > 10 ) {
CLR_TP2;
#ifndef SYNC_ALWAYS
if ( DccRx.chkSum == 0 ) {
// sync must be correct if chksum was ok, no need to check sync
DccRx.State = WAIT_START_BIT_FULL;
} else {
#endif
DccRx.State = WAIT_START_BIT ;
SET_TP2;
// 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
ISRChkMask = 0; // AVR level check is always true with this settings
ISRLevel = 0; // ( there cannot be false edge IRQ's with CHANGE )
halfBit = 0;
bitMax = MAX_ONEBITHALF;
bitMin = MIN_ONEBITHALF;
//CLR_TP1;
#ifndef SYNC_ALWAYS
}
#endif
}
} else {
CLR_TP1;
preambleBitCount = 0 ;
// SET_TP2; CLR_TP2;
}
}
#ifdef ALLOW_NESTED_IRQ
DCC_IrqRunning = false;
#endif
//CLR_TP1;
CLR_TP1;
CLR_TP3;
}
void ackCV(void)
{
if( notifyCVAck )
{
DB_PRINT("ackCV: Send Basic ACK");
notifyCVAck() ;
}
}
void ackAdvancedCV(void)
{
if( notifyAdvancedCVAck && (DccProcState.cv29Value & CV29_RAILCOM_ENABLE) )
{
DB_PRINT("ackAdvancedCV: Send RailCom ACK");
if( notifyAdvancedCVAck && (DccProcState.cv29Value & CV29_ADV_ACK) )
notifyAdvancedCVAck() ;
}
}
uint8_t readEEPROM( unsigned int CV )
{
uint8_t readEEPROM( unsigned int CV ) {
return EEPROM.read(CV) ;
}
void writeEEPROM( unsigned int CV, uint8_t Value )
{
void writeEEPROM( unsigned int CV, uint8_t Value ) {
EEPROM.write(CV, Value) ;
#if defined(ESP8266)
EEPROM.commit();
@@ -763,17 +623,15 @@ void writeEEPROM( unsigned int CV, uint8_t Value )
#endif
}
bool readyEEPROM()
{
#if defined ARDUINO_ARCH_MEGAAVR
return bit_is_clear(NVMCTRL.STATUS,NVMCTRL_EEBUSY_bp);
#elif defined __AVR_MEGA__
bool readyEEPROM() {
#ifdef __AVR_MEGA__
return eeprom_is_ready();
#else
#else
return true;
#endif
#endif
}
uint8_t validCV( uint16_t CV, uint8_t Writable )
{
if( notifyCVResetFactoryDefault && (CV == CV_MANUFACTURER_ID ) && Writable )
@@ -810,8 +668,6 @@ uint8_t writeCV( unsigned int CV, uint8_t Value)
{
case CV_29_CONFIG:
// copy addressmode Bit to Flags
Value = Value & ~CV29_RAILCOM_ENABLE; // Bidi (RailCom) Bit must not be enabled,
// because you cannot build a Bidi decoder with this lib.
DccProcState.cv29Value = Value;
DccProcState.Flags = ( DccProcState.Flags & ~FLAGS_CV29_BITS) | (Value & FLAGS_CV29_BITS);
// no break, because myDccAdress must also be reset
@@ -863,7 +719,7 @@ uint16_t getMyAddr(void)
return DccProcState.myDccAddress ;
}
void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void (*ackFunction)() )
void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
{
// is it a Byte Operation
if( Cmd & 0x04 )
@@ -873,9 +729,8 @@ void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void
{
if( validCV( CVAddr, 1 ) )
{
DB_PRINT("CV: %d Byte Write: %02X", CVAddr, Value)
if( writeCV( CVAddr, Value ) == Value )
ackFunction();
ackAdvancedCV();
}
}
@@ -883,9 +738,8 @@ void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void
{
if( validCV( CVAddr, 0 ) )
{
DB_PRINT("CV: %d Byte Read: %02X", CVAddr, Value)
if( readCV( CVAddr ) == Value )
ackFunction();
ackAdvancedCV();
}
}
}
@@ -898,8 +752,6 @@ void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void
uint8_t tempValue = readCV( CVAddr ) ; // Read the Current CV Value
DB_PRINT("CV: %d Current Value: %02X Bit-Wise Mode: %s Mask: %02X Value: %02X", CVAddr, tempValue, BitWrite ? "Write":"Read", BitMask, BitValue);
// Perform the Bit Write Operation
if( BitWrite )
{
@@ -912,7 +764,7 @@ void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void
tempValue &= ~BitMask ; // Turn the Bit Off
if( writeCV( CVAddr, tempValue ) == tempValue )
ackFunction() ;
ackAdvancedCV() ;
}
}
@@ -924,12 +776,12 @@ void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void
if( BitValue )
{
if( tempValue & BitMask )
ackFunction() ;
ackAdvancedCV() ;
}
else
{
if( !( tempValue & BitMask) )
ackFunction() ;
ackAdvancedCV() ;
}
}
}
@@ -972,8 +824,9 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t
switch( Cmd & 0b00001110 )
{
case 0b00000000:
if( notifyDccReset && ( Cmd & 0b00000001 ) ) // Hard Reset
if( notifyDccReset)
notifyDccReset( Cmd & 0b00000001 ) ;
notifyDccReset( 1 ) ;
break ;
case 0b00000010: // Factory Test
@@ -1106,7 +959,7 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t
case 0b11100000: // CV Access
CVAddr = ( ( ( Cmd & 0x03 ) << 8 ) | Data1 ) + 1 ;
processDirectCVOperation( Cmd, CVAddr, Data2, ackAdvancedCV) ;
processDirectOpsOperation( Cmd, CVAddr, Data2 ) ;
break;
}
}
@@ -1121,7 +974,7 @@ 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("CV Address, Register & Paged Mode Operation");
DB_PRINT("3-BytePkt");
RegisterAddr = pDccMsg->Data[0] & 0x07 ;
Value = pDccMsg->Data[1] ;
@@ -1164,11 +1017,11 @@ void processServiceModeOperation( DCC_MSG * pDccMsg )
else if( pDccMsg->Size == 4) // 4 Byte Packets are for Direct Byte & Bit Mode
{
DB_PRINT("CV Direct Byte and Bit Mode Mode Operation");
DB_PRINT("BB-Mode");
CVAddr = ( ( ( pDccMsg->Data[0] & 0x03 ) << 8 ) | pDccMsg->Data[1] ) + 1 ;
Value = pDccMsg->Data[2] ;
processDirectCVOperation( pDccMsg->Data[0] & 0b00001100, CVAddr, Value, ackCV) ;
processDirectOpsOperation( pDccMsg->Data[0] & 0b00001100, CVAddr, Value ) ;
}
}
#endif
@@ -1245,13 +1098,12 @@ void execDccProcessor( DCC_MSG * pDccMsg )
{
resetServiceModeTimer( 1 ) ;
//Only check the DCC Packet "Size" and "Data" fields and ignore the "PreambleBits" as they can be different to the previous packet
if(pDccMsg->Size != DccProcState.LastMsg.Size || memcmp( pDccMsg->Data, &DccProcState.LastMsg.Data, pDccMsg->Size ) != 0 )
if( memcmp( pDccMsg, &DccProcState.LastMsg, sizeof( DCC_MSG ) ) )
{
DccProcState.DuplicateCount = 0 ;
memcpy( &DccProcState.LastMsg, pDccMsg, sizeof( DCC_MSG ) ) ;
}
// Wait until you see 2 identical packets before acting on a Service Mode Packet
// Wait until you see 2 identicle packets before acting on a Service Mode Packet
else
{
DccProcState.DuplicateCount++ ;
@@ -1522,15 +1374,10 @@ void NmraDcc::pin( uint8_t ExtIntNum, uint8_t ExtIntPinNum, uint8_t EnablePullup
DccProcState.ExtIntNum = ExtIntNum;
#endif
DccProcState.ExtIntPinNum = ExtIntPinNum;
#ifdef __AVR_MEGA__
// because digitalRead at AVR is slow, we will read the dcc input in the ISR
// by direct port access.
DccProcState.ExtIntPort = portInputRegister( digitalPinToPort(ExtIntPinNum) );
DccProcState.ExtIntMask = digitalPinToBitMask( ExtIntPinNum );
#else
DccProcState.ExtIntMask = 1;
#endif
pinMode( ExtIntPinNum, EnablePullup ? INPUT_PULLUP : INPUT );
pinMode( ExtIntPinNum, INPUT );
if( EnablePullup )
digitalWrite(ExtIntPinNum, HIGH);
}
////////////////////////////////////////////////////////////////////////
@@ -1564,9 +1411,6 @@ void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, ui
DccProcState.inAccDecDCCAddrNextReceivedMode = 0;
ISREdge = RISING;
// level checking to detect false IRQ's fired by glitches
ISRLevel = DccProcState.ExtIntMask;
ISRChkMask = DccProcState.ExtIntMask;
#ifdef ESP32
ISRWatch = ISREdge;
@@ -1677,6 +1521,7 @@ uint8_t NmraDcc::process()
if( DccRx.DataReady )
{
// We need to do this check with interrupts disabled
//SET_TP4;
#ifdef ESP32
portENTER_CRITICAL(&mux);
#else
@@ -1690,16 +1535,25 @@ uint8_t NmraDcc::process()
#else
interrupts();
#endif
// Checking of the XOR-byte is now done in the ISR already
#ifdef DCC_DBGVAR
countOf.Tel++;
#endif
// Clear trailing bytes
for ( byte i=Msg.Size; i< MAX_DCC_MESSAGE_LEN; i++ ) Msg.Data[i] = 0;
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 ;
}

View File

@@ -2,21 +2,11 @@
//
// Model Railroading with Arduino - NmraDcc.h
//
// Copyright (c) 2008 - 2020 Alex Shepherd
// Copyright (c) 2008 - 2018 Alex Shepherd
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// This source file is subject of the GNU general public license 2,
// that is available at the world-wide-web at
// http://www.gnu.org/licenses/gpl.txt
//
//------------------------------------------------------------------------
//
@@ -54,10 +44,12 @@
#include "WProgram.h"
#endif
#include "EEPROM.h"
#ifndef NMRADCC_IS_IN
#define NMRADCC_IS_IN
#define NMRADCC_VERSION 206 // Version 2.0.6
#define NMRADCC_VERSION 201 // Version 2.0.1
#define MAX_DCC_MESSAGE_LEN 6 // including XOR-Byte
@@ -126,7 +118,7 @@ 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_RAILCOM_ENABLE = 0b00001000, /** bit 3: BiDi ( RailCom ) is active */
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 */
@@ -712,8 +704,9 @@ extern void notifyCVResetFactoryDefault(void) __attribute__ ((weak));
*/
extern void notifyCVAck(void) __attribute__ ((weak));
/*+
* notifyAdvancedCVAck() Called when a CV write must be acknowledged via Advanced Acknowledgement.
* This callback must send the Advanced Acknowledgement via RailComm.
* notifyAdvancedCVAck() 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

View File

@@ -1,8 +1,8 @@
// DCC Stepper Motor Controller ( A4988 ) Example for Model Railroad Turntable Control
//
// See: https://www.dccinterface.com/product/arduino-model-railway-dcc-stepper-motor-controller-a4988-assembled/
// See: https://www.dccinterface.com/how-to/assemblyguide/
//
// Author: Alex Shepherd 2020-06-01
// Author: Alex Shepherd 2017-12-04
//
// This example requires two Arduino Libraries:
//
@@ -22,18 +22,8 @@
// The lines below define the pins used to connect to the A4988 driver module
#define A4988_STEP_PIN 4
#define A4988_DIRECTION_PIN 5
#define A4988_ENABLE_PIN 6
#ifdef A4988_ENABLE_PIN
// Uncomment the next line to enable Powering-Off the Stepper when its not running to reduce heating the motor and driver
#define DISABLE_OUTPUTS_IDLE
#endif
// By default the stepper motor will move the shortest distance to the desired position.
// If you need the turntable to only move in the Positive/Increasing or Negative/Decreasing step numbers to better handle backlash in the mechanism
// Then uncomment the appropriate line below
//#define ALWAYS_MOVE_POSITIVE
//#define ALWAYS_MOVE_NEGATIVE
#define A4988_ENABLE_PIN 6
// The lines below define the stepping speed and acceleration, which you may need to tune for your application
#define STEPPER_MAX_SPEED 800 // Sets the maximum permitted speed
@@ -102,9 +92,6 @@ TurnoutPosition turnoutPositions[] = {
// --------------------------------------------------------------------------------------------
// You shouldn't need to edit anything below this line unless you're needing to make big changes... ;)
// --------------------------------------------------------------------------------------------
#if defined(ALWAYS_MOVE_POSITIVE) && defined(ALWAYS_MOVE_NEGATIVE)
#error ONLY uncomment one of ALWAYS_MOVE_POSITIVE or ALWAYS_MOVE_NEGATIVE but NOT both
#endif
#define MAX_TURNOUT_POSITIONS (sizeof(turnoutPositions) / sizeof(TurnoutPosition))
@@ -117,12 +104,11 @@ NmraDcc Dcc ;
// Variables to store the last DCC Turnout message Address and Direction
uint16_t lastAddr = 0xFFFF ;
uint8_t lastDirection = 0xFF;
int lastStep = 0;
// This function is called whenever a normal DCC Turnout Packet is received
void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower )
{
Serial.print(F("notifyDccAccTurnoutOutput: "));
Serial.print("notifyDccAccTurnoutOutput: ") ;
Serial.print(Addr,DEC) ;
Serial.print(',');
Serial.print(Direction,DEC) ;
@@ -145,50 +131,23 @@ void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t Output
#ifdef A4988_ENABLE_PIN
stepper1.enableOutputs();
#endif
int newStep;
if(Direction)
newStep = turnoutPositions[i].positionFront;
else
newStep = turnoutPositions[i].positionBack;
Serial.print(newStep, DEC);
Serial.print(F(" Last Step: "));
Serial.print(lastStep, DEC);
int diffStep = newStep - lastStep;
Serial.print(F(" Diff Step: "));
Serial.print(diffStep, DEC);
#if defined ALWAYS_MOVE_POSITIVE
Serial.print(F(" Positive"));
if(diffStep < 0)
diffStep += FULL_TURN_STEPS;
#elif defined ALWAYS_MOVE_NEGATIVE
Serial.print(F(" Negative"));
if(diffStep > 0)
diffStep -= FULL_TURN_STEPS;
#else
if(diffStep > HALF_TURN_STEPS)
diffStep = diffStep - FULL_TURN_STEPS;
else if(diffStep < -HALF_TURN_STEPS)
diffStep = diffStep + FULL_TURN_STEPS;
#endif
Serial.print(F(" Move: "));
Serial.println(diffStep, DEC);
stepper1.move(diffStep);
lastStep = newStep;
if (Direction)
{
Serial.println(turnoutPositions[i].positionFront, DEC);
stepper1.moveTo(turnoutPositions[i].positionFront);
break;
}
else
{
Serial.println(turnoutPositions[i].positionBack, DEC);
stepper1.moveTo(turnoutPositions[i].positionBack);
break;
}
}
}
};
#ifdef DISABLE_OUTPUTS_IDLE
#ifdef A4988_ENABLE_PIN
bool lastIsRunningState ;
#endif
@@ -204,10 +163,6 @@ void setupStepperDriver()
stepper1.setSpeed(STEPPER_SPEED); // Sets the desired constant speed for use with runSpeed()
#ifdef A4988_ENABLE_PIN
stepper1.enableOutputs();
#endif
#ifdef DISABLE_OUTPUTS_IDLE
lastIsRunningState = stepper1.isRunning();
#endif
}
@@ -218,19 +173,14 @@ bool moveToHomePosition()
pinMode(HOME_SENSOR_PIN, INPUT_PULLUP);
#ifdef ALWAYS_MOVE_NEGATIVE
stepper1.move(0 - (FULL_TURN_STEPS * 2));
#else
stepper1.move(FULL_TURN_STEPS * 2);
#endif
while(digitalRead(HOME_SENSOR_PIN) != HOME_SENSOR_ACTIVE_STATE)
stepper1.run();
if(digitalRead(HOME_SENSOR_PIN) == HOME_SENSOR_ACTIVE_STATE)
{
stepper1.stop();
stepper1.setCurrentPosition(0);
Serial.println(F("Found Home Position - Setting Current Position to 0"));
stepper1.setCurrentPosition(0);
return true;
}
else
@@ -260,32 +210,28 @@ void setup()
Serial.print(F("Full Rotation Steps: "));
Serial.println(FULL_TURN_STEPS);
Serial.print(F("Movement Strategy: "));
#if defined ALWAYS_MOVE_POSITIVE
Serial.println(F("Positive Direction Only"));
#elif defined ALWAYS_MOVE_NEGATIVE
Serial.println(F("Negative Direction Only"));
#else
Serial.println(F("Shortest Distance"));
#endif
for(uint8_t i = 0; i < MAX_TURNOUT_POSITIONS; i++)
{
Serial.print(F("DCC Addr: "));
Serial.print("DCC Addr: ");
Serial.print(turnoutPositions[i].dccAddress);
Serial.print(F(" Front: "));
Serial.print(" Front: ");
Serial.print(turnoutPositions[i].positionFront);
Serial.print(F(" Back: "));
Serial.print(" Back: ");
Serial.println(turnoutPositions[i].positionBack);
}
setupStepperDriver();
if(moveToHomePosition());
{
setupDCCDecoder();
#ifdef A4988_ENABLE_PIN
stepper1.enableOutputs();
#endif
// Fake a DCC Packet to cause the Turntable to move to Position 1
notifyDccAccTurnoutOutput(POSITION_01_DCC_ADDRESS, 1, 1);
}
@@ -299,7 +245,7 @@ void loop()
// Process the Stepper Library
stepper1.run();
#ifdef DISABLE_OUTPUTS_IDLE
#ifdef A4988_ENABLE_PIN
if(stepper1.isRunning() != lastIsRunningState)
{
lastIsRunningState = stepper1.isRunning();

View File

@@ -1,5 +1,5 @@
name=NmraDcc
version=2.0.6
version=2.0.2
author=Alex Shepherd, Wolfgang Kuffer, Geoff Bunza, Martin Pischky, Franz-Peter Müller, Sven (littleyoda), Hans Tanner
maintainer=Alex Shepherd <kiwi64ajs@gmail.com>
sentence=Enables NMRA DCC Communication