Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7e3b3e3469 | ||
|
a09c6b1002 | ||
|
29bc2aa1c0 | ||
|
3abb683d8d | ||
|
f3da07c739 | ||
|
dead808af2 | ||
|
38194c0148 | ||
|
7819716ac0 | ||
|
5349a21e2e | ||
|
36418f4d66 | ||
|
a8081526ba | ||
|
7063e4e7e7 | ||
|
d8fb99ed91 | ||
|
794128fe4b | ||
|
1542a6fe6c | ||
|
828b1feaba | ||
|
462025b9fe | ||
|
71bb657e3a | ||
|
f3a2b87693 | ||
|
e06f6b3bce | ||
|
6a7e206032 | ||
|
6d12e6cd3f | ||
|
b249762550 | ||
|
5cee0d28ed | ||
|
5ba1ee3e8e | ||
|
bb2a659ebe | ||
|
865d919802 | ||
|
ff3e24dff4 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.development
|
||||||
|
*.zip
|
582
NmraDcc.cpp
582
NmraDcc.cpp
@@ -2,11 +2,21 @@
|
|||||||
//
|
//
|
||||||
// Model Railroading with Arduino - NmraDcc.cpp
|
// Model Railroading with Arduino - NmraDcc.cpp
|
||||||
//
|
//
|
||||||
// Copyright (c) 2008 - 2017 Alex Shepherd
|
// Copyright (c) 2008 - 2020 Alex Shepherd
|
||||||
//
|
//
|
||||||
// This source file is subject of the GNU general public license 2,
|
// This library is free software; you can redistribute it and/or
|
||||||
// that is available at the world-wide-web at
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
// http://www.gnu.org/licenses/gpl.txt
|
// 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
|
||||||
//
|
//
|
||||||
//------------------------------------------------------------------------
|
//------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -25,7 +35,9 @@
|
|||||||
// 2017-01-19 added STM32F1 support by Franz-Peter
|
// 2017-01-19 added STM32F1 support by Franz-Peter
|
||||||
// 2017-11-29 Ken West (kgw4449@gmail.com):
|
// 2017-11-29 Ken West (kgw4449@gmail.com):
|
||||||
// Minor fixes to pass NMRA Baseline Conformance Tests.
|
// 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
|
// purpose: Provide a simplified interface to decode NMRA DCC packets
|
||||||
@@ -34,12 +46,10 @@
|
|||||||
//------------------------------------------------------------------------
|
//------------------------------------------------------------------------
|
||||||
|
|
||||||
#include "NmraDcc.h"
|
#include "NmraDcc.h"
|
||||||
#ifdef __AVR_MEGA__
|
#include "EEPROM.h"
|
||||||
#include <avr/eeprom.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Uncomment to print DEBUG messages
|
// Uncomment to print DEBUG messages
|
||||||
//#define DEBUG_PRINT
|
// #define DEBUG_PRINT
|
||||||
|
|
||||||
//------------------------------------------------------------------------
|
//------------------------------------------------------------------------
|
||||||
// DCC Receive Routine
|
// DCC Receive Routine
|
||||||
@@ -72,26 +82,33 @@
|
|||||||
// DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________
|
// DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________
|
||||||
// |<--------146us------>|
|
// |<--------146us------>|
|
||||||
// ^-INTx ^-INTx
|
// ^-INTx ^-INTx
|
||||||
// less than 138us: its a one-Bit
|
// less than 146us: its a one-Bit
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// |<-----------------232us----------->|
|
// |<-----------------232us----------->|
|
||||||
// DCC 0: _________XXXXXXXXXXXXXXXXXX__________________XXXXXXXX__________
|
// DCC 0: _________XXXXXXXXXXXXXXXXXX__________________XXXXXXXX__________
|
||||||
// |<--------146us------->|
|
// |<--------146us------->|
|
||||||
// ^-INTx ^-INTx
|
// ^-INTx ^-INTx
|
||||||
// greater than 138us: its a zero bit
|
// greater than 146us: 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_ONEBITFULL 146
|
||||||
#define MAX_PRAEAMBEL 146
|
#define MAX_PRAEAMBEL 146
|
||||||
#define MAX_ONEBITHALF 82
|
#define MAX_ONEBITHALF 82
|
||||||
#define MIN_ONEBITFULL 82
|
#define MIN_ONEBITFULL 82
|
||||||
#define MIN_ONEBITHALF 35
|
#define MIN_ONEBITHALF 35
|
||||||
#define MAX_BITDIFF 18
|
#define MAX_BITDIFF 24
|
||||||
|
|
||||||
|
|
||||||
// Debug-Ports
|
// Debug-Ports
|
||||||
@@ -174,9 +191,22 @@
|
|||||||
#define MODE_TP3 pinMode( D7,OUTPUT ) ; // GPIO 13
|
#define MODE_TP3 pinMode( D7,OUTPUT ) ; // GPIO 13
|
||||||
#define SET_TP3 GPOS = (1 << D7);
|
#define SET_TP3 GPOS = (1 << D7);
|
||||||
#define CLR_TP3 GPOC = (1 << D7);
|
#define CLR_TP3 GPOC = (1 << D7);
|
||||||
#define MODE_TP4 pinMode( D7,OUTPUT ); // GPIO 15
|
#define MODE_TP4 pinMode( D8,OUTPUT ) ; // GPIO 15
|
||||||
#define SET_TP4 GPOC = (1 << D8);
|
#define SET_TP4 GPOS = (1 << D8);
|
||||||
#define CLR_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 GPOS = (1 << 27);
|
||||||
|
#define CLR_TP4 GPOC = (1 << 27);
|
||||||
|
|
||||||
|
|
||||||
//#elif defined(__AVR_ATmega128__) ||defined(__AVR_ATmega1281__)||defined(__AVR_ATmega2561__)
|
//#elif defined(__AVR_ATmega128__) ||defined(__AVR_ATmega1281__)||defined(__AVR_ATmega2561__)
|
||||||
@@ -202,18 +232,12 @@
|
|||||||
#define MODE_TP2
|
#define MODE_TP2
|
||||||
#define SET_TP2
|
#define SET_TP2
|
||||||
#define CLR_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 MODE_TP3
|
||||||
#define SET_TP3
|
#define SET_TP3
|
||||||
#define CLR_TP3
|
#define CLR_TP3
|
||||||
#define MODE_TP4
|
#define MODE_TP4
|
||||||
#define SET_TP4
|
#define SET_TP4
|
||||||
#define CLR_TP4
|
#define CLR_TP4
|
||||||
//#define MODE_TP4 DDRC |= (1<<4) //A4
|
|
||||||
//#define SET_TP4 PORTC |= (1<<4)
|
|
||||||
//#define CLR_TP4 PORTC &= ~(1<<4)
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef DEBUG_PRINT
|
#ifdef DEBUG_PRINT
|
||||||
@@ -230,15 +254,24 @@ struct countOf_t countOf;
|
|||||||
|
|
||||||
#if defined ( __STM32F1__ )
|
#if defined ( __STM32F1__ )
|
||||||
static ExtIntTriggerMode ISREdge;
|
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
|
#else
|
||||||
static byte ISREdge; // 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
|
||||||
#endif
|
#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;
|
static word bitMax, bitMin;
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
WAIT_PREAMBLE = 0,
|
WAIT_PREAMBLE = 0,
|
||||||
WAIT_START_BIT,
|
WAIT_START_BIT,
|
||||||
|
#ifndef SYNC_ALWAYS
|
||||||
|
WAIT_START_BIT_FULL,
|
||||||
|
#endif
|
||||||
WAIT_DATA,
|
WAIT_DATA,
|
||||||
WAIT_END_BIT
|
WAIT_END_BIT
|
||||||
}
|
}
|
||||||
@@ -259,6 +292,7 @@ struct DccRx_t
|
|||||||
uint8_t DataReady ;
|
uint8_t DataReady ;
|
||||||
uint8_t BitCount ;
|
uint8_t BitCount ;
|
||||||
uint8_t TempByte ;
|
uint8_t TempByte ;
|
||||||
|
uint8_t chkSum;
|
||||||
DCC_MSG PacketBuf;
|
DCC_MSG PacketBuf;
|
||||||
DCC_MSG PacketCopy;
|
DCC_MSG PacketCopy;
|
||||||
}
|
}
|
||||||
@@ -275,8 +309,11 @@ typedef struct
|
|||||||
DCC_MSG LastMsg ;
|
DCC_MSG LastMsg ;
|
||||||
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
|
||||||
|
uint8_t ExtIntMask; // digitalRead is too slow on AVR
|
||||||
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;
|
||||||
#ifdef DCC_DEBUG
|
#ifdef DCC_DEBUG
|
||||||
uint8_t IntCount;
|
uint8_t IntCount;
|
||||||
uint8_t TickCount;
|
uint8_t TickCount;
|
||||||
@@ -287,14 +324,48 @@ DCC_PROCESSOR_STATE ;
|
|||||||
|
|
||||||
DCC_PROCESSOR_STATE DccProcState ;
|
DCC_PROCESSOR_STATE DccProcState ;
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
|
||||||
|
void IRAM_ATTR ExternalInterruptHandler(void)
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
void ICACHE_RAM_ATTR ExternalInterruptHandler(void)
|
||||||
|
#else
|
||||||
void ExternalInterruptHandler(void)
|
void ExternalInterruptHandler(void)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
|
SET_TP3;
|
||||||
|
|
||||||
|
#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 ------------------------------
|
// Bit evaluation without Timer 0 ------------------------------
|
||||||
uint8_t DccBitVal;
|
uint8_t DccBitVal;
|
||||||
static int8_t bit1, bit2 ;
|
static int8_t bit1, bit2 ;
|
||||||
static word lastMicros;
|
static unsigned int lastMicros = 0;
|
||||||
static byte halfBit, DCC_IrqRunning;
|
static byte halfBit, DCC_IrqRunning, preambleBitCount;
|
||||||
unsigned int actMicros, bitMicros;
|
unsigned int actMicros, bitMicros;
|
||||||
|
#ifdef ALLOW_NESTED_IRQ
|
||||||
if ( DCC_IrqRunning ) {
|
if ( DCC_IrqRunning ) {
|
||||||
// nested DCC IRQ - obviously there are glitches
|
// nested DCC IRQ - obviously there are glitches
|
||||||
// ignore this interrupt and increment glitchcounter
|
// ignore this interrupt and increment glitchcounter
|
||||||
@@ -305,57 +376,94 @@ void ExternalInterruptHandler(void)
|
|||||||
SET_TP3;
|
SET_TP3;
|
||||||
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
|
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
|
||||||
}
|
}
|
||||||
SET_TP3;
|
#endif
|
||||||
actMicros = micros();
|
actMicros = micros();
|
||||||
bitMicros = actMicros-lastMicros;
|
bitMicros = actMicros-lastMicros;
|
||||||
if ( bitMicros < bitMin ) {
|
|
||||||
// too short - my be false interrupt due to glitch or false protocol -> ignore
|
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
|
||||||
CLR_TP3;
|
CLR_TP3;
|
||||||
SET_TP4; CLR_TP4;
|
SET_TP4; /*delayMicroseconds(1); */ CLR_TP4;
|
||||||
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
|
return; //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> abort IRQ
|
||||||
}
|
}
|
||||||
DccBitVal = ( bitMicros < bitMax );
|
CLR_TP3; SET_TP3;
|
||||||
|
|
||||||
lastMicros = actMicros;
|
lastMicros = actMicros;
|
||||||
#ifdef debug
|
#ifndef SUPPORT_ZERO_BIT_STRETCHING
|
||||||
if(DccBitVal) {SET_TP2;} else {CLR_TP2;};
|
//if ( bitMicros > MAX_ZEROBITFULL ) {
|
||||||
#endif
|
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 );
|
||||||
|
|
||||||
|
#ifdef ALLOW_NESTED_IRQ
|
||||||
DCC_IrqRunning = true;
|
DCC_IrqRunning = true;
|
||||||
interrupts(); // time critical is only the micros() command,so allow nested irq's
|
interrupts(); // time critical is only the micros() command,so allow nested irq's
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef DCC_DEBUG
|
#ifdef DCC_DEBUG
|
||||||
DccProcState.TickCount++;
|
DccProcState.TickCount++;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch( DccRx.State )
|
switch( DccRx.State )
|
||||||
{
|
{
|
||||||
case WAIT_PREAMBLE:
|
case WAIT_PREAMBLE:
|
||||||
if( DccBitVal )
|
// We don't have to do anything special - looking for a preamble condition is done always
|
||||||
{
|
SET_TP2;
|
||||||
SET_TP1;
|
|
||||||
DccRx.BitCount++;
|
|
||||||
if( DccRx.BitCount > 10 ) {
|
|
||||||
DccRx.State = WAIT_START_BIT ;
|
|
||||||
// While waiting for the start bit, detect halfbit lengths. We will detect the correct
|
|
||||||
// sync and detect whether we see a false (e.g. motorola) protocol
|
|
||||||
#if defined ( __STM32F1__ )
|
|
||||||
detachInterrupt( DccProcState.ExtIntNum );
|
|
||||||
#endif
|
|
||||||
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE);
|
|
||||||
halfBit = 0;
|
|
||||||
bitMax = MAX_ONEBITHALF;
|
|
||||||
bitMin = MIN_ONEBITHALF;
|
|
||||||
CLR_TP1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SET_TP1;
|
|
||||||
DccRx.BitCount = 0 ;
|
|
||||||
CLR_TP1;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#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 ;
|
||||||
|
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;
|
||||||
|
DccRx.BitCount = 0 ;
|
||||||
|
DccRx.chkSum = 0 ;
|
||||||
|
DccRx.TempByte = 0 ;
|
||||||
|
//SET_TP1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
case WAIT_START_BIT:
|
case WAIT_START_BIT:
|
||||||
// we are looking for first half "0" bit after preamble
|
// we are looking for first half "0" bit after preamble
|
||||||
switch ( halfBit ) {
|
switch ( halfBit ) {
|
||||||
case 0: //SET_TP1;
|
case 0:
|
||||||
// check first part
|
// check first part
|
||||||
if ( DccBitVal ) {
|
if ( DccBitVal ) {
|
||||||
// is still 1-bit (Preamble)
|
// is still 1-bit (Preamble)
|
||||||
@@ -363,108 +471,135 @@ void ExternalInterruptHandler(void)
|
|||||||
bit1=bitMicros;
|
bit1=bitMicros;
|
||||||
} else {
|
} else {
|
||||||
// was "0" half bit, maybe the startbit
|
// was "0" half bit, maybe the startbit
|
||||||
SET_TP1;
|
halfBit = 4;
|
||||||
halfBit = 4;
|
}
|
||||||
CLR_TP1;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 1: //SET_TP1; // previous halfbit was '1'
|
case 1: // previous halfbit was '1'
|
||||||
if ( DccBitVal ) {
|
if ( DccBitVal ) {
|
||||||
// its a '1' halfBit -> we are still in the preamble
|
// its a '1' halfBit -> we are still in the preamble
|
||||||
halfBit = 0;
|
halfBit = 0;
|
||||||
bit2=bitMicros;
|
bit2=bitMicros;
|
||||||
DccRx.BitCount++;
|
preambleBitCount++;
|
||||||
if( abs(bit2-bit1) > MAX_BITDIFF ) {
|
if( abs(bit2-bit1) > MAX_BITDIFF ) {
|
||||||
// the length of the 2 halfbits differ too much -> wrong protokoll
|
// the length of the 2 halfbits differ too much -> wrong protokoll
|
||||||
CLR_TP2;
|
|
||||||
CLR_TP3;
|
|
||||||
DccRx.State = WAIT_PREAMBLE;
|
DccRx.State = WAIT_PREAMBLE;
|
||||||
bitMax = MAX_PRAEAMBEL;
|
bitMax = MAX_PRAEAMBEL;
|
||||||
bitMin = MIN_ONEBITFULL;
|
bitMin = MIN_ONEBITFULL;
|
||||||
DccRx.BitCount = 0;
|
preambleBitCount = 0;
|
||||||
SET_TP4;
|
// SET_TP2; CLR_TP2;
|
||||||
#if defined ( __STM32F1__ )
|
#if defined ( __STM32F1__ )
|
||||||
detachInterrupt( DccProcState.ExtIntNum );
|
detachInterrupt( DccProcState.ExtIntNum );
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ESP32
|
||||||
|
ISRWatch = ISREdge;
|
||||||
|
#else
|
||||||
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
|
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;
|
SET_TP3;
|
||||||
CLR_TP4;
|
CLR_TP4;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// first '0' half detected in second halfBit
|
// first '0' half detected in second halfBit
|
||||||
// wrong sync or not a DCC protokoll
|
// wrong sync or not a DCC protokoll
|
||||||
CLR_TP3;
|
CLR_TP3;
|
||||||
halfBit = 3;
|
halfBit = 3;
|
||||||
SET_TP3;
|
SET_TP3;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3: //SET_TP1; // previous halfbit was '0' in second halfbit
|
case 3: // previous halfbit was '0' in second halfbit
|
||||||
if ( DccBitVal ) {
|
if ( DccBitVal ) {
|
||||||
// its a '1' halfbit -> we got only a half '0' bit -> cannot be DCC
|
// its a '1' halfbit -> we got only a half '0' bit -> cannot be DCC
|
||||||
DccRx.State = WAIT_PREAMBLE;
|
DccRx.State = WAIT_PREAMBLE;
|
||||||
bitMax = MAX_PRAEAMBEL;
|
bitMax = MAX_PRAEAMBEL;
|
||||||
bitMin = MIN_ONEBITFULL;
|
bitMin = MIN_ONEBITFULL;
|
||||||
DccRx.BitCount = 0;
|
preambleBitCount = 0;
|
||||||
|
// SET_TP2; CLR_TP2;
|
||||||
} else {
|
} else {
|
||||||
// we got two '0' halfbits -> it's the startbit
|
// we got two '0' halfbits -> it's the startbit
|
||||||
// but sync is NOT ok, change IRQ edge.
|
// but sync is NOT ok, change IRQ edge.
|
||||||
|
CLR_TP2;CLR_TP1;
|
||||||
if ( ISREdge == RISING ) ISREdge = FALLING; else ISREdge = RISING;
|
if ( ISREdge == RISING ) ISREdge = FALLING; else ISREdge = RISING;
|
||||||
DccRx.State = WAIT_DATA ;
|
DccRx.State = WAIT_DATA ;
|
||||||
|
CLR_TP1;
|
||||||
bitMax = MAX_ONEBITFULL;
|
bitMax = MAX_ONEBITFULL;
|
||||||
bitMin = MIN_ONEBITFULL;
|
bitMin = MIN_ONEBITFULL;
|
||||||
DccRx.PacketBuf.Size = 0;
|
DccRx.PacketBuf.Size = 0;
|
||||||
DccRx.PacketBuf.PreambleBits = 0;
|
/*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
|
||||||
for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
|
DccRx.PacketBuf.Data[i] = 0;*/
|
||||||
DccRx.PacketBuf.Data[i] = 0;
|
DccRx.PacketBuf.PreambleBits = preambleBitCount;
|
||||||
|
|
||||||
DccRx.PacketBuf.PreambleBits = DccRx.BitCount;
|
|
||||||
DccRx.BitCount = 0 ;
|
DccRx.BitCount = 0 ;
|
||||||
|
DccRx.chkSum = 0 ;
|
||||||
DccRx.TempByte = 0 ;
|
DccRx.TempByte = 0 ;
|
||||||
|
//SET_TP1;
|
||||||
}
|
}
|
||||||
SET_TP4;
|
//SET_TP4;
|
||||||
#if defined ( __STM32F1__ )
|
|
||||||
detachInterrupt( DccProcState.ExtIntNum );
|
#if defined ( __STM32F1__ )
|
||||||
#endif
|
detachInterrupt( DccProcState.ExtIntNum );
|
||||||
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
|
#endif
|
||||||
CLR_TP1;
|
#ifdef ESP32
|
||||||
CLR_TP4;
|
ISRWatch = ISREdge;
|
||||||
|
#else
|
||||||
|
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
|
||||||
|
#endif
|
||||||
|
// enable level-checking
|
||||||
|
ISRChkMask = DccProcState.ExtIntMask;
|
||||||
|
ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ;
|
||||||
|
//CLR_TP4;
|
||||||
break;
|
break;
|
||||||
case 4: SET_TP1; // 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
|
||||||
if ( DccBitVal ) {
|
if ( DccBitVal ) {
|
||||||
// second halfbit is 1 -> unknown protokoll
|
// second halfbit is 1 -> unknown protokoll
|
||||||
DccRx.State = WAIT_PREAMBLE;
|
DccRx.State = WAIT_PREAMBLE;
|
||||||
bitMax = MAX_PRAEAMBEL;
|
bitMax = MAX_PRAEAMBEL;
|
||||||
bitMin = MIN_ONEBITFULL;
|
bitMin = MIN_ONEBITFULL;
|
||||||
|
preambleBitCount = 0;
|
||||||
|
CLR_TP2;CLR_TP1;
|
||||||
DccRx.BitCount = 0;
|
DccRx.BitCount = 0;
|
||||||
} else {
|
} else {
|
||||||
// we got the startbit
|
// we got the startbit
|
||||||
|
CLR_TP2;CLR_TP1;
|
||||||
DccRx.State = WAIT_DATA ;
|
DccRx.State = WAIT_DATA ;
|
||||||
|
CLR_TP1;
|
||||||
bitMax = MAX_ONEBITFULL;
|
bitMax = MAX_ONEBITFULL;
|
||||||
bitMin = MIN_ONEBITFULL;
|
bitMin = MIN_ONEBITFULL;
|
||||||
|
// initialize packet buffer
|
||||||
DccRx.PacketBuf.Size = 0;
|
DccRx.PacketBuf.Size = 0;
|
||||||
DccRx.PacketBuf.PreambleBits = 0;
|
/*for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
|
||||||
for(uint8_t i = 0; i< MAX_DCC_MESSAGE_LEN; i++ )
|
DccRx.PacketBuf.Data[i] = 0;*/
|
||||||
DccRx.PacketBuf.Data[i] = 0;
|
DccRx.PacketBuf.PreambleBits = preambleBitCount;
|
||||||
|
|
||||||
DccRx.PacketBuf.PreambleBits = DccRx.BitCount;
|
|
||||||
DccRx.BitCount = 0 ;
|
DccRx.BitCount = 0 ;
|
||||||
|
DccRx.chkSum = 0 ;
|
||||||
DccRx.TempByte = 0 ;
|
DccRx.TempByte = 0 ;
|
||||||
|
//SET_TP1;
|
||||||
}
|
}
|
||||||
|
|
||||||
CLR_TP1;
|
//SET_TP4;
|
||||||
SET_TP4;
|
|
||||||
#if defined ( __STM32F1__ )
|
#if defined ( __STM32F1__ )
|
||||||
detachInterrupt( DccProcState.ExtIntNum );
|
detachInterrupt( DccProcState.ExtIntNum );
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ESP32
|
||||||
|
ISRWatch = ISREdge;
|
||||||
|
#else
|
||||||
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
|
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, ISREdge );
|
||||||
CLR_TP4;
|
#endif
|
||||||
|
// enable level-checking
|
||||||
|
ISRChkMask = DccProcState.ExtIntMask;
|
||||||
|
ISRLevel = (ISREdge==RISING)? DccProcState.ExtIntMask : 0 ;
|
||||||
|
|
||||||
|
//CLR_TP4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WAIT_DATA:
|
case WAIT_DATA:
|
||||||
|
CLR_TP2;
|
||||||
DccRx.BitCount++;
|
DccRx.BitCount++;
|
||||||
DccRx.TempByte = ( DccRx.TempByte << 1 ) ;
|
DccRx.TempByte = ( DccRx.TempByte << 1 ) ;
|
||||||
if( DccBitVal )
|
if( DccBitVal )
|
||||||
@@ -483,23 +618,44 @@ void ExternalInterruptHandler(void)
|
|||||||
{
|
{
|
||||||
DccRx.State = WAIT_END_BIT ;
|
DccRx.State = WAIT_END_BIT ;
|
||||||
DccRx.PacketBuf.Data[ DccRx.PacketBuf.Size++ ] = DccRx.TempByte ;
|
DccRx.PacketBuf.Data[ DccRx.PacketBuf.Size++ ] = DccRx.TempByte ;
|
||||||
|
DccRx.chkSum ^= DccRx.TempByte;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WAIT_END_BIT:
|
case WAIT_END_BIT:
|
||||||
|
SET_TP2;CLR_TP2;
|
||||||
DccRx.BitCount++;
|
DccRx.BitCount++;
|
||||||
if( DccBitVal ) // End of packet?
|
if( DccBitVal ) { // End of packet?
|
||||||
{
|
CLR_TP3; SET_TP4;
|
||||||
CLR_TP3;
|
|
||||||
DccRx.State = WAIT_PREAMBLE ;
|
DccRx.State = WAIT_PREAMBLE ;
|
||||||
|
DccRx.BitCount = 0 ;
|
||||||
bitMax = MAX_PRAEAMBEL;
|
bitMax = MAX_PRAEAMBEL;
|
||||||
bitMin = MIN_ONEBITFULL;
|
bitMin = MIN_ONEBITFULL;
|
||||||
DccRx.PacketCopy = DccRx.PacketBuf ;
|
SET_TP1;
|
||||||
DccRx.DataReady = 1 ;
|
if ( DccRx.chkSum == 0 ) {
|
||||||
SET_TP3;
|
// Packet is valid
|
||||||
}
|
#ifdef ESP32
|
||||||
else // Get next Byte
|
portENTER_CRITICAL_ISR(&mux);
|
||||||
|
#endif
|
||||||
|
DccRx.PacketCopy = DccRx.PacketBuf ;
|
||||||
|
DccRx.DataReady = 1 ;
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_TP3; CLR_TP4;
|
||||||
|
} else { // Get next Byte
|
||||||
// KGW - Abort immediately if packet is too long.
|
// KGW - Abort immediately if packet is too long.
|
||||||
if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort
|
if( DccRx.PacketBuf.Size == MAX_DCC_MESSAGE_LEN ) // Packet is too long - abort
|
||||||
{
|
{
|
||||||
@@ -515,38 +671,109 @@ void ExternalInterruptHandler(void)
|
|||||||
DccRx.BitCount = 0 ;
|
DccRx.BitCount = 0 ;
|
||||||
DccRx.TempByte = 0 ;
|
DccRx.TempByte = 0 ;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CLR_TP1;
|
|
||||||
CLR_TP3;
|
// 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;
|
DCC_IrqRunning = false;
|
||||||
|
#endif
|
||||||
|
//CLR_TP1;
|
||||||
|
CLR_TP3;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ackCV(void)
|
void ackCV(void)
|
||||||
{
|
{
|
||||||
if( notifyCVAck )
|
if( notifyCVAck )
|
||||||
|
{
|
||||||
|
DB_PRINT("ackCV: Send Basic ACK");
|
||||||
notifyCVAck() ;
|
notifyCVAck() ;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t readEEPROM( unsigned int CV ) {
|
void ackAdvancedCV(void)
|
||||||
|
{
|
||||||
|
if( notifyAdvancedCVAck && (DccProcState.cv29Value & CV29_RAILCOM_ENABLE) )
|
||||||
|
{
|
||||||
|
DB_PRINT("ackAdvancedCV: Send RailCom ACK");
|
||||||
|
notifyAdvancedCVAck() ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t readEEPROM( unsigned int CV )
|
||||||
|
{
|
||||||
return EEPROM.read(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) ;
|
EEPROM.write(CV, Value) ;
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(ESP32)
|
||||||
|
EEPROM.commit();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool readyEEPROM() {
|
bool readyEEPROM()
|
||||||
#ifdef __AVR_MEGA__
|
{
|
||||||
return eeprom_is_ready();
|
#if defined ARDUINO_ARCH_MEGAAVR
|
||||||
#else
|
return bit_is_clear(NVMCTRL.STATUS,NVMCTRL_EEBUSY_bp);
|
||||||
return true;
|
#elif defined __AVR_MEGA__
|
||||||
#endif
|
return eeprom_is_ready();
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
uint8_t validCV( uint16_t CV, uint8_t Writable )
|
uint8_t validCV( uint16_t CV, uint8_t Writable )
|
||||||
{
|
{
|
||||||
if( notifyCVResetFactoryDefault && (CV == CV_MANUFACTURER_ID ) && Writable )
|
if( notifyCVResetFactoryDefault && (CV == CV_MANUFACTURER_ID ) && Writable )
|
||||||
@@ -583,6 +810,9 @@ uint8_t writeCV( unsigned int CV, uint8_t Value)
|
|||||||
{
|
{
|
||||||
case CV_29_CONFIG:
|
case CV_29_CONFIG:
|
||||||
// copy addressmode Bit to Flags
|
// 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);
|
DccProcState.Flags = ( DccProcState.Flags & ~FLAGS_CV29_BITS) | (Value & FLAGS_CV29_BITS);
|
||||||
// no break, because myDccAdress must also be reset
|
// 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
|
||||||
@@ -611,23 +841,19 @@ uint8_t writeCV( unsigned int CV, uint8_t Value)
|
|||||||
|
|
||||||
uint16_t getMyAddr(void)
|
uint16_t getMyAddr(void)
|
||||||
{
|
{
|
||||||
uint8_t CV29Value ;
|
|
||||||
|
|
||||||
if( DccProcState.myDccAddress != -1 ) // See if we can return the cached value
|
if( DccProcState.myDccAddress != -1 ) // See if we can return the cached value
|
||||||
return( DccProcState.myDccAddress );
|
return( DccProcState.myDccAddress );
|
||||||
|
|
||||||
CV29Value = readCV( CV_29_CONFIG ) ;
|
if( DccProcState.cv29Value & CV29_ACCESSORY_DECODER ) // Accessory Decoder?
|
||||||
|
|
||||||
if( CV29Value & CV29_ACCESSORY_DECODER ) // Accessory Decoder?
|
|
||||||
{
|
{
|
||||||
if( CV29Value & CV29_OUTPUT_ADDRESS_MODE )
|
if( DccProcState.cv29Value & CV29_OUTPUT_ADDRESS_MODE )
|
||||||
DccProcState.myDccAddress = ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) << 8 ) | readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB );
|
DccProcState.myDccAddress = ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) << 8 ) | readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB );
|
||||||
else
|
else
|
||||||
DccProcState.myDccAddress = ( ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) & 0b00000111) << 6 ) | ( readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ) & 0b00111111) ;
|
DccProcState.myDccAddress = ( ( readCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) & 0b00000111) << 6 ) | ( readCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ) & 0b00111111) ;
|
||||||
}
|
}
|
||||||
else // Multi-Function Decoder?
|
else // Multi-Function Decoder?
|
||||||
{
|
{
|
||||||
if( CV29Value & CV29_EXT_ADDRESSING ) // Two Byte Address?
|
if( DccProcState.cv29Value & CV29_EXT_ADDRESSING ) // Two Byte Address?
|
||||||
DccProcState.myDccAddress = ( ( readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB ) - 192 ) << 8 ) | readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB ) ;
|
DccProcState.myDccAddress = ( ( readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB ) - 192 ) << 8 ) | readCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB ) ;
|
||||||
|
|
||||||
else
|
else
|
||||||
@@ -637,7 +863,7 @@ uint16_t getMyAddr(void)
|
|||||||
return DccProcState.myDccAddress ;
|
return DccProcState.myDccAddress ;
|
||||||
}
|
}
|
||||||
|
|
||||||
void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
|
void processDirectCVOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value, void (*ackFunction)() )
|
||||||
{
|
{
|
||||||
// is it a Byte Operation
|
// is it a Byte Operation
|
||||||
if( Cmd & 0x04 )
|
if( Cmd & 0x04 )
|
||||||
@@ -647,8 +873,9 @@ void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
|
|||||||
{
|
{
|
||||||
if( validCV( CVAddr, 1 ) )
|
if( validCV( CVAddr, 1 ) )
|
||||||
{
|
{
|
||||||
|
DB_PRINT("CV: %d Byte Write: %02X", CVAddr, Value)
|
||||||
if( writeCV( CVAddr, Value ) == Value )
|
if( writeCV( CVAddr, Value ) == Value )
|
||||||
ackCV();
|
ackFunction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,8 +883,9 @@ void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
|
|||||||
{
|
{
|
||||||
if( validCV( CVAddr, 0 ) )
|
if( validCV( CVAddr, 0 ) )
|
||||||
{
|
{
|
||||||
|
DB_PRINT("CV: %d Byte Read: %02X", CVAddr, Value)
|
||||||
if( readCV( CVAddr ) == Value )
|
if( readCV( CVAddr ) == Value )
|
||||||
ackCV();
|
ackFunction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,6 +898,8 @@ void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
|
|||||||
|
|
||||||
uint8_t tempValue = readCV( CVAddr ) ; // Read the Current CV Value
|
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
|
// Perform the Bit Write Operation
|
||||||
if( BitWrite )
|
if( BitWrite )
|
||||||
{
|
{
|
||||||
@@ -682,7 +912,7 @@ void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
|
|||||||
tempValue &= ~BitMask ; // Turn the Bit Off
|
tempValue &= ~BitMask ; // Turn the Bit Off
|
||||||
|
|
||||||
if( writeCV( CVAddr, tempValue ) == tempValue )
|
if( writeCV( CVAddr, tempValue ) == tempValue )
|
||||||
ackCV() ;
|
ackFunction() ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,12 +924,12 @@ void processDirectOpsOperation( uint8_t Cmd, uint16_t CVAddr, uint8_t Value )
|
|||||||
if( BitValue )
|
if( BitValue )
|
||||||
{
|
{
|
||||||
if( tempValue & BitMask )
|
if( tempValue & BitMask )
|
||||||
ackCV() ;
|
ackFunction() ;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if( !( tempValue & BitMask) )
|
if( !( tempValue & BitMask) )
|
||||||
ackCV() ;
|
ackFunction() ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -742,9 +972,8 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t
|
|||||||
switch( Cmd & 0b00001110 )
|
switch( Cmd & 0b00001110 )
|
||||||
{
|
{
|
||||||
case 0b00000000:
|
case 0b00000000:
|
||||||
if( notifyDccReset && ( Cmd & 0b00000001 ) ) // Hard Reset
|
if( notifyDccReset)
|
||||||
if( notifyDccReset)
|
notifyDccReset( Cmd & 0b00000001 ) ;
|
||||||
notifyDccReset( 1 ) ;
|
|
||||||
break ;
|
break ;
|
||||||
|
|
||||||
case 0b00000010: // Factory Test
|
case 0b00000010: // Factory Test
|
||||||
@@ -793,7 +1022,7 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t
|
|||||||
case 0b01100000:
|
case 0b01100000:
|
||||||
//TODO should we cache this info in DCC_PROCESSOR_STATE.Flags ?
|
//TODO should we cache this info in DCC_PROCESSOR_STATE.Flags ?
|
||||||
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
|
#ifdef NMRA_DCC_ENABLE_14_SPEED_STEP_MODE
|
||||||
speedSteps = (readCV( CV_29_CONFIG ) & CV29_F0_LOCATION) ? SPEED_STEP_28 : SPEED_STEP_14 ;
|
speedSteps = (DccProcState.cv29Value & CV29_F0_LOCATION) ? SPEED_STEP_28 : SPEED_STEP_14 ;
|
||||||
#else
|
#else
|
||||||
speedSteps = SPEED_STEP_28 ;
|
speedSteps = SPEED_STEP_28 ;
|
||||||
#endif
|
#endif
|
||||||
@@ -877,7 +1106,7 @@ void processMultiFunctionMessage( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t
|
|||||||
case 0b11100000: // CV Access
|
case 0b11100000: // CV Access
|
||||||
CVAddr = ( ( ( Cmd & 0x03 ) << 8 ) | Data1 ) + 1 ;
|
CVAddr = ( ( ( Cmd & 0x03 ) << 8 ) | Data1 ) + 1 ;
|
||||||
|
|
||||||
processDirectOpsOperation( Cmd, CVAddr, Data2 ) ;
|
processDirectCVOperation( Cmd, CVAddr, Data2, ackAdvancedCV) ;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -892,7 +1121,7 @@ void processServiceModeOperation( DCC_MSG * pDccMsg )
|
|||||||
if( pDccMsg->Size == 3) // 3 Byte Packets are for Address Only, Register and Paged Mode
|
if( pDccMsg->Size == 3) // 3 Byte Packets are for Address Only, Register and Paged Mode
|
||||||
{
|
{
|
||||||
uint8_t RegisterAddr ;
|
uint8_t RegisterAddr ;
|
||||||
DB_PRINT("3-BytePkt");
|
DB_PRINT("CV Address, Register & Paged Mode Operation");
|
||||||
RegisterAddr = pDccMsg->Data[0] & 0x07 ;
|
RegisterAddr = pDccMsg->Data[0] & 0x07 ;
|
||||||
Value = pDccMsg->Data[1] ;
|
Value = pDccMsg->Data[1] ;
|
||||||
|
|
||||||
@@ -935,11 +1164,11 @@ void processServiceModeOperation( DCC_MSG * pDccMsg )
|
|||||||
|
|
||||||
else if( pDccMsg->Size == 4) // 4 Byte Packets are for Direct Byte & Bit Mode
|
else if( pDccMsg->Size == 4) // 4 Byte Packets are for Direct Byte & Bit Mode
|
||||||
{
|
{
|
||||||
DB_PRINT("BB-Mode");
|
DB_PRINT("CV Direct Byte and Bit Mode Mode Operation");
|
||||||
CVAddr = ( ( ( pDccMsg->Data[0] & 0x03 ) << 8 ) | pDccMsg->Data[1] ) + 1 ;
|
CVAddr = ( ( ( pDccMsg->Data[0] & 0x03 ) << 8 ) | pDccMsg->Data[1] ) + 1 ;
|
||||||
Value = pDccMsg->Data[2] ;
|
Value = pDccMsg->Data[2] ;
|
||||||
|
|
||||||
processDirectOpsOperation( pDccMsg->Data[0] & 0b00001100, CVAddr, Value ) ;
|
processDirectCVOperation( pDccMsg->Data[0] & 0b00001100, CVAddr, Value, ackCV) ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -1016,12 +1245,13 @@ void execDccProcessor( DCC_MSG * pDccMsg )
|
|||||||
{
|
{
|
||||||
resetServiceModeTimer( 1 ) ;
|
resetServiceModeTimer( 1 ) ;
|
||||||
|
|
||||||
if( memcmp( pDccMsg, &DccProcState.LastMsg, sizeof( DCC_MSG ) ) )
|
//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 )
|
||||||
{
|
{
|
||||||
DccProcState.DuplicateCount = 0 ;
|
DccProcState.DuplicateCount = 0 ;
|
||||||
memcpy( &DccProcState.LastMsg, pDccMsg, sizeof( DCC_MSG ) ) ;
|
memcpy( &DccProcState.LastMsg, pDccMsg, sizeof( DCC_MSG ) ) ;
|
||||||
}
|
}
|
||||||
// Wait until you see 2 identicle packets before acting on a Service Mode Packet
|
// Wait until you see 2 identical packets before acting on a Service Mode Packet
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DccProcState.DuplicateCount++ ;
|
DccProcState.DuplicateCount++ ;
|
||||||
@@ -1277,14 +1507,30 @@ void NmraDcc::pin( uint8_t ExtIntNum, uint8_t ExtIntPinNum, uint8_t EnablePullup
|
|||||||
#if defined ( __STM32F1__ )
|
#if defined ( __STM32F1__ )
|
||||||
// with STM32F1 the interuptnumber is equal the pin number
|
// with STM32F1 the interuptnumber is equal the pin number
|
||||||
DccProcState.ExtIntNum = ExtIntPinNum;
|
DccProcState.ExtIntNum = ExtIntPinNum;
|
||||||
|
// because STM32F1 has a NVIC we must set interuptpriorities
|
||||||
|
const nvic_irq_num irqNum2nvic[] = { NVIC_EXTI0, NVIC_EXTI1, NVIC_EXTI2, NVIC_EXTI3, NVIC_EXTI4,
|
||||||
|
NVIC_EXTI_9_5, NVIC_EXTI_9_5, NVIC_EXTI_9_5, NVIC_EXTI_9_5, NVIC_EXTI_9_5,
|
||||||
|
NVIC_EXTI_15_10, NVIC_EXTI_15_10, NVIC_EXTI_15_10, NVIC_EXTI_15_10, NVIC_EXTI_15_10, NVIC_EXTI_15_10 };
|
||||||
|
exti_num irqNum = (exti_num)(PIN_MAP[ExtIntPinNum].gpio_bit);
|
||||||
|
|
||||||
|
// DCC-Input IRQ must be able to interrupt other long low priority ( level15 ) IRQ's
|
||||||
|
nvic_irq_set_priority ( irqNum2nvic[irqNum], PRIO_DCC_IRQ);
|
||||||
|
|
||||||
|
// Systic must be able to interrupt DCC-IRQ to always get correct micros() values
|
||||||
|
nvic_irq_set_priority(NVIC_SYSTICK, PRIO_SYSTIC);
|
||||||
#else
|
#else
|
||||||
DccProcState.ExtIntNum = ExtIntNum;
|
DccProcState.ExtIntNum = ExtIntNum;
|
||||||
#endif
|
#endif
|
||||||
DccProcState.ExtIntPinNum = ExtIntPinNum;
|
DccProcState.ExtIntPinNum = ExtIntPinNum;
|
||||||
|
#ifdef __AVR_MEGA__
|
||||||
pinMode( ExtIntPinNum, INPUT );
|
// because digitalRead at AVR is slow, we will read the dcc input in the ISR
|
||||||
if( EnablePullup )
|
// by direct port access.
|
||||||
digitalWrite(ExtIntPinNum, HIGH);
|
DccProcState.ExtIntPort = portInputRegister( digitalPinToPort(ExtIntPinNum) );
|
||||||
|
DccProcState.ExtIntMask = digitalPinToBitMask( ExtIntPinNum );
|
||||||
|
#else
|
||||||
|
DccProcState.ExtIntMask = 1;
|
||||||
|
#endif
|
||||||
|
pinMode( ExtIntPinNum, EnablePullup ? INPUT_PULLUP : INPUT );
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
@@ -1299,6 +1545,9 @@ void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, ui
|
|||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
EEPROM.begin(MAXCV);
|
EEPROM.begin(MAXCV);
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(ESP32)
|
||||||
|
EEPROM.begin(MAXCV);
|
||||||
|
#endif
|
||||||
// Clear all the static member variables
|
// Clear all the static member variables
|
||||||
memset( &DccRx, 0, sizeof( DccRx) );
|
memset( &DccRx, 0, sizeof( DccRx) );
|
||||||
|
|
||||||
@@ -1306,20 +1555,30 @@ void NmraDcc::init( uint8_t ManufacturerId, uint8_t VersionId, uint8_t Flags, ui
|
|||||||
MODE_TP2;
|
MODE_TP2;
|
||||||
MODE_TP3;
|
MODE_TP3;
|
||||||
MODE_TP4;
|
MODE_TP4;
|
||||||
ISREdge = RISING;
|
|
||||||
bitMax = MAX_ONEBITFULL;
|
bitMax = MAX_ONEBITFULL;
|
||||||
bitMin = MIN_ONEBITFULL;
|
bitMin = MIN_ONEBITFULL;
|
||||||
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, RISING);
|
|
||||||
|
|
||||||
DccProcState.Flags = Flags ;
|
DccProcState.Flags = Flags ;
|
||||||
DccProcState.OpsModeAddressBaseCV = OpsModeAddressBaseCV ;
|
DccProcState.OpsModeAddressBaseCV = OpsModeAddressBaseCV ;
|
||||||
DccProcState.myDccAddress = -1;
|
DccProcState.myDccAddress = -1;
|
||||||
DccProcState.inAccDecDCCAddrNextReceivedMode = 0;
|
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;
|
||||||
|
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, CHANGE);
|
||||||
|
#else
|
||||||
|
attachInterrupt( DccProcState.ExtIntNum, ExternalInterruptHandler, RISING);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Set the Bits that control Multifunction or Accessory behaviour
|
// Set the Bits that control Multifunction or Accessory behaviour
|
||||||
// and if the Accessory decoder optionally handles Output Addressing
|
// and if the Accessory decoder optionally handles Output Addressing
|
||||||
// we need to peal off the top two bits
|
// we need to peal off the top two bits
|
||||||
writeCV( CV_29_CONFIG, ( readCV( CV_29_CONFIG ) & ~FLAGS_CV29_BITS ) | (Flags & FLAGS_CV29_BITS) ) ;
|
DccProcState.cv29Value = writeCV( CV_29_CONFIG, ( readCV( CV_29_CONFIG ) & ~FLAGS_CV29_BITS ) | (Flags & FLAGS_CV29_BITS) ) ;
|
||||||
|
|
||||||
uint8_t doAutoFactoryDefault = 0;
|
uint8_t doAutoFactoryDefault = 0;
|
||||||
if((Flags & FLAGS_AUTO_FACTORY_DEFAULT) && (readCV(CV_VERSION_ID) == 255) && (readCV(CV_MANUFACTURER_ID) == 255))
|
if((Flags & FLAGS_AUTO_FACTORY_DEFAULT) && (readCV(CV_VERSION_ID) == 255) && (readCV(CV_MANUFACTURER_ID) == 255))
|
||||||
@@ -1418,30 +1677,29 @@ uint8_t NmraDcc::process()
|
|||||||
if( DccRx.DataReady )
|
if( DccRx.DataReady )
|
||||||
{
|
{
|
||||||
// We need to do this check with interrupts disabled
|
// We need to do this check with interrupts disabled
|
||||||
//SET_TP4;
|
#ifdef ESP32
|
||||||
|
portENTER_CRITICAL(&mux);
|
||||||
|
#else
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
|
#endif
|
||||||
Msg = DccRx.PacketCopy ;
|
Msg = DccRx.PacketCopy ;
|
||||||
DccRx.DataReady = 0 ;
|
DccRx.DataReady = 0 ;
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
portEXIT_CRITICAL(&mux);
|
||||||
|
#else
|
||||||
interrupts();
|
interrupts();
|
||||||
#ifdef DCC_DBGVAR
|
#endif
|
||||||
countOf.Tel++;
|
// Checking of the XOR-byte is now done in the ISR already
|
||||||
#endif
|
#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 ;
|
if( notifyDccMsg ) notifyDccMsg( &Msg );
|
||||||
|
|
||||||
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 );
|
execDccProcessor( &Msg );
|
||||||
}
|
|
||||||
return 1 ;
|
return 1 ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
57
NmraDcc.h
57
NmraDcc.h
@@ -2,11 +2,21 @@
|
|||||||
//
|
//
|
||||||
// Model Railroading with Arduino - NmraDcc.h
|
// Model Railroading with Arduino - NmraDcc.h
|
||||||
//
|
//
|
||||||
// Copyright (c) 2008 - 2018 Alex Shepherd
|
// Copyright (c) 2008 - 2020 Alex Shepherd
|
||||||
//
|
//
|
||||||
// This source file is subject of the GNU general public license 2,
|
// This library is free software; you can redistribute it and/or
|
||||||
// that is available at the world-wide-web at
|
// modify it under the terms of the GNU Lesser General Public
|
||||||
// http://www.gnu.org/licenses/gpl.txt
|
// 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
|
||||||
//
|
//
|
||||||
//------------------------------------------------------------------------
|
//------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -32,7 +42,7 @@
|
|||||||
// 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
|
||||||
|
|
||||||
// Uncomment the following line to Enable MutliFunction Decoder Operations
|
// Uncomment the following line to Enable MultiFunction Decoder Operations
|
||||||
#define NMRA_DCC_PROCESS_MULTIFUNCTION
|
#define NMRA_DCC_PROCESS_MULTIFUNCTION
|
||||||
|
|
||||||
// Uncomment the following line to Enable 14 Speed Step Support
|
// Uncomment the following line to Enable 14 Speed Step Support
|
||||||
@@ -44,15 +54,15 @@
|
|||||||
#include "WProgram.h"
|
#include "WProgram.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "EEPROM.h"
|
|
||||||
|
|
||||||
#ifndef NMRADCC_IS_IN
|
#ifndef NMRADCC_IS_IN
|
||||||
#define NMRADCC_IS_IN
|
#define NMRADCC_IS_IN
|
||||||
|
|
||||||
#define NMRADCC_VERSION 200 // Version 2.0.0
|
#define NMRADCC_VERSION 206 // Version 2.0.6
|
||||||
|
|
||||||
#define MAX_DCC_MESSAGE_LEN 6 // including XOR-Byte
|
#define MAX_DCC_MESSAGE_LEN 6 // including XOR-Byte
|
||||||
|
|
||||||
|
//#define ALLOW_NESTED_IRQ // uncomment to enable nested IRQ's ( only for AVR! )
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint8_t Size ;
|
uint8_t Size ;
|
||||||
@@ -96,21 +106,27 @@ typedef struct
|
|||||||
#define CV_MANUFACTURER_ID 8
|
#define CV_MANUFACTURER_ID 8
|
||||||
#define CV_29_CONFIG 29
|
#define CV_29_CONFIG 29
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP32)
|
||||||
#include <spi_flash.h>
|
#include <esp_spi_flash.h>
|
||||||
#define MAXCV SPI_FLASH_SEC_SIZE
|
#define MAXCV SPI_FLASH_SEC_SIZE
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <spi_flash.h>
|
||||||
|
#define MAXCV SPI_FLASH_SEC_SIZE
|
||||||
#elif defined( __STM32F1__)
|
#elif defined( __STM32F1__)
|
||||||
#define MAXCV (EEPROM_PAGE_SIZE/4 - 1) // number of storage places (CV address could be larger
|
#define MAXCV (EEPROM_PAGE_SIZE/4 - 1) // number of storage places (CV address could be larger
|
||||||
// because STM32 uses virtual adresses)
|
// because STM32 uses virtual adresses)
|
||||||
|
#undef ALLOW_NESTED_IRQ // This is done with NVIC on STM32
|
||||||
|
#define PRIO_DCC_IRQ 9
|
||||||
|
#define PRIO_SYSTIC 8 // MUST be higher priority than DCC Irq
|
||||||
#else
|
#else
|
||||||
#define MAXCV E2END // the upper limit of the CV value currently defined to max memory.
|
#define MAXCV E2END // the upper limit of the CV value currently defined to max memory.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
CV29_LOCO_DIR = 0b00000001, /** bit 0: Locomotive Direction: "0" = normal, "1" = reversed */
|
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_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_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_RAILCOM_ENABLE = 0b00001000, /** bit 3: BiDi ( RailCom ) is active */
|
||||||
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_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_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_OUTPUT_ADDRESS_MODE = 0b01000000, /** bit 6: "0" = Decoder Address Mode "1" = Output Address Mode */
|
||||||
@@ -651,7 +667,7 @@ extern uint8_t notifyIsSetCVReady(void) __attribute__ ((weak));
|
|||||||
* notifyCVChange() Called when a CV value is changed.
|
* notifyCVChange() Called when a CV value is changed.
|
||||||
* This is called whenever a CV's 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.
|
* 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 chaged by means of the setCV() method.
|
* it is NOT called if the CV is changed by means of the setCV() method.
|
||||||
* Note: It is not called if notifyCVWrite() is defined
|
* Note: It is not called if notifyCVWrite() is defined
|
||||||
* or if the value in the EEPROM is the same as the value
|
* or if the value in the EEPROM is the same as the value
|
||||||
* in the write command.
|
* in the write command.
|
||||||
@@ -695,6 +711,17 @@ extern void notifyCVResetFactoryDefault(void) __attribute__ ((weak));
|
|||||||
* None
|
* None
|
||||||
*/
|
*/
|
||||||
extern void notifyCVAck(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.
|
||||||
|
*
|
||||||
|
* Inputs:
|
||||||
|
* None
|
||||||
|
* *
|
||||||
|
* Returns:
|
||||||
|
* None
|
||||||
|
*/
|
||||||
|
extern void notifyAdvancedCVAck(void) __attribute__ ((weak));
|
||||||
/*+
|
/*+
|
||||||
* notifyServiceMode(bool) Called when state of 'inServiceMode' changes
|
* notifyServiceMode(bool) Called when state of 'inServiceMode' changes
|
||||||
*
|
*
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
// DCC Stepper Motor Controller ( A4988 ) Example for Model Railroad Turntable Control
|
// DCC Stepper Motor Controller ( A4988 ) Example for Model Railroad Turntable Control
|
||||||
//
|
//
|
||||||
// See: https://www.dccinterface.com/how-to/assemblyguide/
|
// See: https://www.dccinterface.com/product/arduino-model-railway-dcc-stepper-motor-controller-a4988-assembled/
|
||||||
//
|
//
|
||||||
// Author: Alex Shepherd 2017-12-04
|
// Author: Alex Shepherd 2020-06-01
|
||||||
//
|
//
|
||||||
// This example requires two Arduino Libraries:
|
// This example requires two Arduino Libraries:
|
||||||
//
|
//
|
||||||
@@ -22,12 +22,22 @@
|
|||||||
// The lines below define the pins used to connect to the A4988 driver module
|
// The lines below define the pins used to connect to the A4988 driver module
|
||||||
#define A4988_STEP_PIN 4
|
#define A4988_STEP_PIN 4
|
||||||
#define A4988_DIRECTION_PIN 5
|
#define A4988_DIRECTION_PIN 5
|
||||||
// Uncomment the next line to enable Powering-Off the Stepper when its not running to reduce heating the motor and driver
|
|
||||||
#define A4988_ENABLE_PIN 6
|
#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
|
||||||
|
|
||||||
// The lines below define the stepping speed and acceleration, which you may need to tune for your application
|
// 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
|
#define STEPPER_MAX_SPEED 800 // Sets the maximum permitted speed
|
||||||
#define STEPPER_ACCELARATION 1000 // Sets the acceleration/deceleration rate
|
#define STEPPER_ACCELARATION 1000 // Sets the acceleration/deceleration rate
|
||||||
#define STEPPER_SPEED 300 // Sets the desired constant speed for use with runSpeed()
|
#define STEPPER_SPEED 300 // Sets the desired constant speed for use with runSpeed()
|
||||||
|
|
||||||
// The line below defines the number of "Full Steps" your stepper motor does for a full rotation
|
// The line below defines the number of "Full Steps" your stepper motor does for a full rotation
|
||||||
@@ -92,6 +102,9 @@ TurnoutPosition turnoutPositions[] = {
|
|||||||
// --------------------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------------------
|
||||||
// You shouldn't need to edit anything below this line unless you're needing to make big changes... ;)
|
// 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))
|
#define MAX_TURNOUT_POSITIONS (sizeof(turnoutPositions) / sizeof(TurnoutPosition))
|
||||||
|
|
||||||
@@ -104,11 +117,12 @@ NmraDcc Dcc ;
|
|||||||
// Variables to store the last DCC Turnout message Address and Direction
|
// Variables to store the last DCC Turnout message Address and Direction
|
||||||
uint16_t lastAddr = 0xFFFF ;
|
uint16_t lastAddr = 0xFFFF ;
|
||||||
uint8_t lastDirection = 0xFF;
|
uint8_t lastDirection = 0xFF;
|
||||||
|
int lastStep = 0;
|
||||||
|
|
||||||
// This function is called whenever a normal DCC Turnout Packet is received
|
// This function is called whenever a normal DCC Turnout Packet is received
|
||||||
void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower )
|
void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t OutputPower )
|
||||||
{
|
{
|
||||||
Serial.print("notifyDccAccTurnoutOutput: ") ;
|
Serial.print(F("notifyDccAccTurnoutOutput: "));
|
||||||
Serial.print(Addr,DEC) ;
|
Serial.print(Addr,DEC) ;
|
||||||
Serial.print(',');
|
Serial.print(',');
|
||||||
Serial.print(Direction,DEC) ;
|
Serial.print(Direction,DEC) ;
|
||||||
@@ -131,23 +145,50 @@ void notifyDccAccTurnoutOutput( uint16_t Addr, uint8_t Direction, uint8_t Output
|
|||||||
#ifdef A4988_ENABLE_PIN
|
#ifdef A4988_ENABLE_PIN
|
||||||
stepper1.enableOutputs();
|
stepper1.enableOutputs();
|
||||||
#endif
|
#endif
|
||||||
if (Direction)
|
|
||||||
{
|
int newStep;
|
||||||
Serial.println(turnoutPositions[i].positionFront, DEC);
|
if(Direction)
|
||||||
stepper1.moveTo(turnoutPositions[i].positionFront);
|
newStep = turnoutPositions[i].positionFront;
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
newStep = turnoutPositions[i].positionBack;
|
||||||
Serial.println(turnoutPositions[i].positionBack, DEC);
|
|
||||||
stepper1.moveTo(turnoutPositions[i].positionBack);
|
Serial.print(newStep, DEC);
|
||||||
break;
|
|
||||||
}
|
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;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef A4988_ENABLE_PIN
|
#ifdef DISABLE_OUTPUTS_IDLE
|
||||||
bool lastIsRunningState ;
|
bool lastIsRunningState ;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -161,8 +202,12 @@ void setupStepperDriver()
|
|||||||
stepper1.setMaxSpeed(STEPPER_MAX_SPEED); // Sets the maximum permitted speed
|
stepper1.setMaxSpeed(STEPPER_MAX_SPEED); // Sets the maximum permitted speed
|
||||||
stepper1.setAcceleration(STEPPER_ACCELARATION); // Sets the acceleration/deceleration rate
|
stepper1.setAcceleration(STEPPER_ACCELARATION); // Sets the acceleration/deceleration rate
|
||||||
stepper1.setSpeed(STEPPER_SPEED); // Sets the desired constant speed for use with runSpeed()
|
stepper1.setSpeed(STEPPER_SPEED); // Sets the desired constant speed for use with runSpeed()
|
||||||
|
|
||||||
#ifdef A4988_ENABLE_PIN
|
#ifdef A4988_ENABLE_PIN
|
||||||
|
stepper1.enableOutputs();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DISABLE_OUTPUTS_IDLE
|
||||||
lastIsRunningState = stepper1.isRunning();
|
lastIsRunningState = stepper1.isRunning();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -173,14 +218,19 @@ bool moveToHomePosition()
|
|||||||
|
|
||||||
pinMode(HOME_SENSOR_PIN, INPUT_PULLUP);
|
pinMode(HOME_SENSOR_PIN, INPUT_PULLUP);
|
||||||
|
|
||||||
|
#ifdef ALWAYS_MOVE_NEGATIVE
|
||||||
|
stepper1.move(0 - (FULL_TURN_STEPS * 2));
|
||||||
|
#else
|
||||||
stepper1.move(FULL_TURN_STEPS * 2);
|
stepper1.move(FULL_TURN_STEPS * 2);
|
||||||
|
#endif
|
||||||
while(digitalRead(HOME_SENSOR_PIN) != HOME_SENSOR_ACTIVE_STATE)
|
while(digitalRead(HOME_SENSOR_PIN) != HOME_SENSOR_ACTIVE_STATE)
|
||||||
stepper1.run();
|
stepper1.run();
|
||||||
|
|
||||||
if(digitalRead(HOME_SENSOR_PIN) == HOME_SENSOR_ACTIVE_STATE)
|
if(digitalRead(HOME_SENSOR_PIN) == HOME_SENSOR_ACTIVE_STATE)
|
||||||
{
|
{
|
||||||
Serial.println(F("Found Home Position - Setting Current Position to 0"));
|
stepper1.stop();
|
||||||
stepper1.setCurrentPosition(0);
|
stepper1.setCurrentPosition(0);
|
||||||
|
Serial.println(F("Found Home Position - Setting Current Position to 0"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -210,28 +260,32 @@ void setup()
|
|||||||
Serial.print(F("Full Rotation Steps: "));
|
Serial.print(F("Full Rotation Steps: "));
|
||||||
Serial.println(FULL_TURN_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++)
|
for(uint8_t i = 0; i < MAX_TURNOUT_POSITIONS; i++)
|
||||||
{
|
{
|
||||||
Serial.print("DCC Addr: ");
|
Serial.print(F("DCC Addr: "));
|
||||||
Serial.print(turnoutPositions[i].dccAddress);
|
Serial.print(turnoutPositions[i].dccAddress);
|
||||||
|
|
||||||
Serial.print(" Front: ");
|
Serial.print(F(" Front: "));
|
||||||
Serial.print(turnoutPositions[i].positionFront);
|
Serial.print(turnoutPositions[i].positionFront);
|
||||||
|
|
||||||
Serial.print(" Back: ");
|
Serial.print(F(" Back: "));
|
||||||
Serial.println(turnoutPositions[i].positionBack);
|
Serial.println(turnoutPositions[i].positionBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupStepperDriver();
|
setupStepperDriver();
|
||||||
|
|
||||||
if(moveToHomePosition());
|
if(moveToHomePosition());
|
||||||
{
|
{
|
||||||
setupDCCDecoder();
|
setupDCCDecoder();
|
||||||
|
|
||||||
#ifdef A4988_ENABLE_PIN
|
|
||||||
stepper1.enableOutputs();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Fake a DCC Packet to cause the Turntable to move to Position 1
|
// Fake a DCC Packet to cause the Turntable to move to Position 1
|
||||||
notifyDccAccTurnoutOutput(POSITION_01_DCC_ADDRESS, 1, 1);
|
notifyDccAccTurnoutOutput(POSITION_01_DCC_ADDRESS, 1, 1);
|
||||||
}
|
}
|
||||||
@@ -245,7 +299,7 @@ void loop()
|
|||||||
// Process the Stepper Library
|
// Process the Stepper Library
|
||||||
stepper1.run();
|
stepper1.run();
|
||||||
|
|
||||||
#ifdef A4988_ENABLE_PIN
|
#ifdef DISABLE_OUTPUTS_IDLE
|
||||||
if(stepper1.isRunning() != lastIsRunningState)
|
if(stepper1.isRunning() != lastIsRunningState)
|
||||||
{
|
{
|
||||||
lastIsRunningState = stepper1.isRunning();
|
lastIsRunningState = stepper1.isRunning();
|
||||||
|
@@ -0,0 +1,314 @@
|
|||||||
|
// NMRA Dcc Multifunction Motor Decoder Demo
|
||||||
|
//
|
||||||
|
// Author: Alex Shepherd 2019-03-30
|
||||||
|
//
|
||||||
|
// This example requires these Arduino Libraries:
|
||||||
|
//
|
||||||
|
// 1) The NmraDcc Library from: http://mrrwa.org/download/
|
||||||
|
//
|
||||||
|
// These libraries can be found and installed via the Arduino IDE Library Manager
|
||||||
|
//
|
||||||
|
// This is a simple demo of how to drive and motor speed and direction using PWM and a motor H-Bridge
|
||||||
|
// It uses vStart and vHigh CV values to customise the PWM values to the motor response
|
||||||
|
// It also uses the Headling Function to drive 2 LEDs for Directional Headlights
|
||||||
|
// Apart from that there's nothing fancy like Lighting Effects or a function matrix or Speed Tables - its just the basics...
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <NmraDcc.h>
|
||||||
|
// Uncomment any of the lines below to enable debug messages for different parts of the code
|
||||||
|
//#define DEBUG_FUNCTIONS
|
||||||
|
//#define DEBUG_SPEED
|
||||||
|
//#define DEBUG_PWM
|
||||||
|
//#define DEBUG_DCC_ACK
|
||||||
|
//#define DEBUG_DCC_MSG
|
||||||
|
|
||||||
|
#if defined(DEBUG_FUNCTIONS) or defined(DEBUG_SPEED) or defined(DEBUG_PWM) or defined(DEBUG_DCC_ACK) or defined(DEBUG_DCC_MSG)
|
||||||
|
#define DEBUG_PRINT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This is the default DCC Address
|
||||||
|
#define DEFAULT_DECODER_ADDRESS 3
|
||||||
|
|
||||||
|
// This section defines the Arduino UNO Pins to use
|
||||||
|
#ifdef __AVR_ATmega328P__
|
||||||
|
|
||||||
|
#define DCC_PIN 2
|
||||||
|
|
||||||
|
#define LED_PIN_FWD 5
|
||||||
|
#define LED_PIN_REV 6
|
||||||
|
#define MOTOR_DIR_PIN 12
|
||||||
|
#define MOTOR_PWM_PIN 3
|
||||||
|
|
||||||
|
// This section defines the Arduino ATTiny85 Pins to use
|
||||||
|
#elif ARDUINO_AVR_ATTINYX5
|
||||||
|
|
||||||
|
#define DCC_PIN 2
|
||||||
|
|
||||||
|
#define LED_PIN_FWD 0
|
||||||
|
#define LED_PIN_REV 1
|
||||||
|
#define MOTOR_DIR_PIN 3
|
||||||
|
#define MOTOR_PWM_PIN 4
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error "Unsupported CPU, you need to add another configuration section for your CPU"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Some global state variables
|
||||||
|
uint8_t newLedState = 0;
|
||||||
|
uint8_t lastLedState = 0;
|
||||||
|
|
||||||
|
uint8_t newDirection = 0;
|
||||||
|
uint8_t lastDirection = 0;
|
||||||
|
|
||||||
|
uint8_t newSpeed = 0;
|
||||||
|
uint8_t lastSpeed = 0;
|
||||||
|
uint8_t numSpeedSteps = SPEED_STEP_128;
|
||||||
|
|
||||||
|
uint8_t vStart;
|
||||||
|
uint8_t vHigh;
|
||||||
|
|
||||||
|
// Structure for CV Values Table
|
||||||
|
struct CVPair
|
||||||
|
{
|
||||||
|
uint16_t CV;
|
||||||
|
uint8_t Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CV Addresses we will be using
|
||||||
|
#define CV_VSTART 2
|
||||||
|
#define CV_VHIGH 5
|
||||||
|
|
||||||
|
// Default CV Values Table
|
||||||
|
CVPair FactoryDefaultCVs [] =
|
||||||
|
{
|
||||||
|
// The CV Below defines the Short DCC Address
|
||||||
|
{CV_MULTIFUNCTION_PRIMARY_ADDRESS, DEFAULT_DECODER_ADDRESS},
|
||||||
|
|
||||||
|
// Three Step Speed Table
|
||||||
|
{CV_VSTART, 120},
|
||||||
|
{CV_VHIGH, 255},
|
||||||
|
|
||||||
|
// These two CVs define the Long DCC Address
|
||||||
|
{CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB, 0},
|
||||||
|
{CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB, DEFAULT_DECODER_ADDRESS},
|
||||||
|
|
||||||
|
// ONLY uncomment 1 CV_29_CONFIG line below as approprate
|
||||||
|
// {CV_29_CONFIG, 0}, // Short Address 14 Speed Steps
|
||||||
|
{CV_29_CONFIG, CV29_F0_LOCATION}, // Short Address 28/128 Speed Steps
|
||||||
|
// {CV_29_CONFIG, CV29_EXT_ADDRESSING | CV29_F0_LOCATION}, // Long Address 28/128 Speed Steps
|
||||||
|
};
|
||||||
|
|
||||||
|
NmraDcc Dcc ;
|
||||||
|
|
||||||
|
uint8_t FactoryDefaultCVIndex = 0;
|
||||||
|
|
||||||
|
// This call-back function is called when a CV Value changes so we can update CVs we're using
|
||||||
|
void notifyCVChange( uint16_t CV, uint8_t Value)
|
||||||
|
{
|
||||||
|
switch(CV)
|
||||||
|
{
|
||||||
|
case CV_VSTART:
|
||||||
|
vStart = Value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CV_VHIGH:
|
||||||
|
vHigh = Value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void notifyCVResetFactoryDefault()
|
||||||
|
{
|
||||||
|
// Make FactoryDefaultCVIndex non-zero and equal to num CV's to be reset
|
||||||
|
// to flag to the loop() function that a reset to Factory Defaults needs to be done
|
||||||
|
FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs)/sizeof(CVPair);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This call-back function is called whenever we receive a DCC Speed packet for our address
|
||||||
|
void notifyDccSpeed( uint16_t Addr, DCC_ADDR_TYPE AddrType, uint8_t Speed, DCC_DIRECTION Dir, DCC_SPEED_STEPS SpeedSteps )
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_SPEED
|
||||||
|
Serial.print("notifyDccSpeed: Addr: ");
|
||||||
|
Serial.print(Addr,DEC);
|
||||||
|
Serial.print( (AddrType == DCC_ADDR_SHORT) ? "-S" : "-L" );
|
||||||
|
Serial.print(" Speed: ");
|
||||||
|
Serial.print(Speed,DEC);
|
||||||
|
Serial.print(" Steps: ");
|
||||||
|
Serial.print(SpeedSteps,DEC);
|
||||||
|
Serial.print(" Dir: ");
|
||||||
|
Serial.println( (Dir == DCC_DIR_FWD) ? "Forward" : "Reverse" );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
newDirection = Dir;
|
||||||
|
newSpeed = Speed;
|
||||||
|
numSpeedSteps = SpeedSteps;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This call-back function is called whenever we receive a DCC Function packet for our address
|
||||||
|
void notifyDccFunc(uint16_t Addr, DCC_ADDR_TYPE AddrType, FN_GROUP FuncGrp, uint8_t FuncState)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_FUNCTIONS
|
||||||
|
Serial.print("notifyDccFunc: Addr: ");
|
||||||
|
Serial.print(Addr,DEC);
|
||||||
|
Serial.print( (AddrType == DCC_ADDR_SHORT) ? 'S' : 'L' );
|
||||||
|
Serial.print(" Function Group: ");
|
||||||
|
Serial.print(FuncGrp,DEC);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(FuncGrp == FN_0_4)
|
||||||
|
{
|
||||||
|
newLedState = (FuncState & FN_BIT_00) ? 1 : 0;
|
||||||
|
#ifdef DEBUG_FUNCTIONS
|
||||||
|
Serial.print(" FN 0: ");
|
||||||
|
Serial.print(newLedState);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifdef DEBUG_FUNCTIONS
|
||||||
|
Serial.println();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// This call-back function is called whenever we receive a DCC Packet
|
||||||
|
#ifdef DEBUG_DCC_MSG
|
||||||
|
void notifyDccMsg( DCC_MSG * Msg)
|
||||||
|
{
|
||||||
|
Serial.print("notifyDccMsg: ") ;
|
||||||
|
for(uint8_t i = 0; i < Msg->Size; i++)
|
||||||
|
{
|
||||||
|
Serial.print(Msg->Data[i], HEX);
|
||||||
|
Serial.write(' ');
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This call-back function is called by the NmraDcc library when a DCC ACK needs to be sent
|
||||||
|
// Calling this function should cause an increased 60ma current drain on the power supply for 6ms to ACK a CV Read
|
||||||
|
// So we will just turn the motor on for 8ms and then turn it off again.
|
||||||
|
|
||||||
|
void notifyCVAck(void)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DCC_ACK
|
||||||
|
Serial.println("notifyCVAck") ;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
digitalWrite(MOTOR_DIR_PIN, HIGH);
|
||||||
|
digitalWrite(MOTOR_PWM_PIN, HIGH);
|
||||||
|
|
||||||
|
delay( 8 );
|
||||||
|
|
||||||
|
digitalWrite(MOTOR_DIR_PIN, LOW);
|
||||||
|
digitalWrite(MOTOR_PWM_PIN, LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_PRINT
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("NMRA Dcc Multifunction Motor Decoder Demo");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Setup the Pins for the Fwd/Rev LED for Function 0 Headlight
|
||||||
|
pinMode(LED_PIN_FWD, OUTPUT);
|
||||||
|
pinMode(LED_PIN_REV, OUTPUT);
|
||||||
|
|
||||||
|
// Setup the Pins for the Motor H-Bridge Driver
|
||||||
|
pinMode(MOTOR_DIR_PIN, OUTPUT);
|
||||||
|
pinMode(MOTOR_PWM_PIN, OUTPUT);
|
||||||
|
|
||||||
|
|
||||||
|
// Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up
|
||||||
|
Dcc.pin(DCC_PIN, 0);
|
||||||
|
|
||||||
|
Dcc.init( MAN_ID_DIY, 10, FLAGS_MY_ADDRESS_ONLY | FLAGS_AUTO_FACTORY_DEFAULT, 0 );
|
||||||
|
|
||||||
|
// Uncomment to force CV Reset to Factory Defaults
|
||||||
|
// notifyCVResetFactoryDefault();
|
||||||
|
|
||||||
|
// Read the current CV values for vStart and vHigh
|
||||||
|
vStart = Dcc.getCV(CV_VSTART);
|
||||||
|
vHigh = Dcc.getCV(CV_VHIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
// You MUST call the NmraDcc.process() method frequently from the Arduino loop() function for correct library operation
|
||||||
|
Dcc.process();
|
||||||
|
|
||||||
|
// Handle Speed changes
|
||||||
|
if(lastSpeed != newSpeed)
|
||||||
|
{
|
||||||
|
lastSpeed = newSpeed;
|
||||||
|
|
||||||
|
// Stop if speed = 0 or 1
|
||||||
|
|
||||||
|
if(newSpeed <= 1)
|
||||||
|
digitalWrite(MOTOR_PWM_PIN, LOW);
|
||||||
|
|
||||||
|
// Calculate PWM value in the range 1..255
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint8_t vScaleFactor;
|
||||||
|
|
||||||
|
if((vHigh > 1) && (vHigh > vStart))
|
||||||
|
vScaleFactor = vHigh - vStart;
|
||||||
|
else
|
||||||
|
vScaleFactor = 255 - vStart;
|
||||||
|
|
||||||
|
uint8_t modSpeed = newSpeed - 1;
|
||||||
|
uint8_t modSteps = numSpeedSteps - 1;
|
||||||
|
|
||||||
|
uint8_t newPwm = (uint8_t) vStart + modSpeed * vScaleFactor / modSteps;
|
||||||
|
|
||||||
|
#ifdef DEBUG_PWM
|
||||||
|
Serial.print("New Speed: vStart: ");
|
||||||
|
Serial.print(vStart);
|
||||||
|
Serial.print(" vHigh: ");
|
||||||
|
Serial.print(vHigh);
|
||||||
|
Serial.print(" modSpeed: ");
|
||||||
|
Serial.print(modSpeed);
|
||||||
|
Serial.print(" vScaleFactor: ");
|
||||||
|
Serial.print(vScaleFactor);
|
||||||
|
Serial.print(" modSteps: ");
|
||||||
|
Serial.print(modSteps);
|
||||||
|
Serial.print(" newPwm: ");
|
||||||
|
Serial.println(newPwm);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
analogWrite(MOTOR_PWM_PIN, newPwm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Direction and Headlight changes
|
||||||
|
if((lastDirection != newDirection) || (lastLedState != newLedState))
|
||||||
|
{
|
||||||
|
lastDirection = newDirection;
|
||||||
|
lastLedState = newLedState;
|
||||||
|
|
||||||
|
digitalWrite(MOTOR_DIR_PIN, newDirection);
|
||||||
|
|
||||||
|
if(newLedState)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_FUNCTIONS
|
||||||
|
Serial.println("LED On");
|
||||||
|
#endif
|
||||||
|
digitalWrite(LED_PIN_FWD, newDirection ? LOW : HIGH);
|
||||||
|
digitalWrite(LED_PIN_REV, newDirection ? HIGH : LOW);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_FUNCTIONS
|
||||||
|
Serial.println("LED Off");
|
||||||
|
#endif
|
||||||
|
digitalWrite(LED_PIN_FWD, LOW);
|
||||||
|
digitalWrite(LED_PIN_REV, LOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle resetting CVs back to Factory Defaults
|
||||||
|
if( FactoryDefaultCVIndex && Dcc.isSetCVReady())
|
||||||
|
{
|
||||||
|
FactoryDefaultCVIndex--; // Decrement first as initially it is the size of the array
|
||||||
|
Dcc.setCV( FactoryDefaultCVs[FactoryDefaultCVIndex].CV, FactoryDefaultCVs[FactoryDefaultCVIndex].Value);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
name=NmraDcc
|
name=NmraDcc
|
||||||
version=2.0.0
|
version=2.0.6
|
||||||
author=Alex Shepherd, Wolfgang Kuffer, Geoff Bunza, Martin Pischky, Franz-Peter Müller, Sven (littleyoda)
|
author=Alex Shepherd, Wolfgang Kuffer, Geoff Bunza, Martin Pischky, Franz-Peter Müller, Sven (littleyoda), Hans Tanner
|
||||||
maintainer=Alex Shepherd <kiwi64ajs@gmail.com>
|
maintainer=Alex Shepherd <kiwi64ajs@gmail.com>
|
||||||
sentence=Enables NMRA DCC Communication
|
sentence=Enables NMRA DCC Communication
|
||||||
paragraph=This library allows you to interface to a NMRA DCC track signal and receive DCC commands. The library has been tested on AVR ATTiny84/85 & ATMega88/168/328/32u4, ESP8266 and Teensy 3.x using the INT0/1 Hardware Interrupt and micros() ONLY and no longer uses Timer0 Compare Match B, which makes it much more portable to other platforms.
|
paragraph=This library allows you to interface to a NMRA DCC track signal and receive DCC commands. The library has been tested on AVR ATTiny84/85 & ATMega88/168/328/32u4, ESP8266 and Teensy 3.x using the INT0/1 Hardware Interrupt and micros() ONLY and no longer uses Timer0 Compare Match B, which makes it much more portable to other platforms.
|
||||||
|
Reference in New Issue
Block a user