DCCpp
This is the library version of a program for Arduino to control railroading DCC devices.
DccSignalESP32.cpp
1 /*************************************************************
2 project: <DCCpp library>
3 author: <Thierry PARIS>
4 description: <DCCpp signal for ESP32 modules>
5 *************************************************************/
6 
7 #include "Arduino.h"
8 
9 #if defined(ARDUINO_ARCH_ESP32)
10 
11 #include "DCCpp.h"
12 
13 // Define constants for DCC Signal pattern
14 
15 // this controls the timer tick frequency
16 #define DCC_TIMER_PRESCALE 80
17 
18 // number of microseconds for sending a zero via the DCC encoding
19 #define DCC_ZERO_BIT_TOTAL_DURATION 196
20 // number of microseconds for each half of the DCC signal for a zero
21 #define DCC_ZERO_BIT_PULSE_DURATION 98
22 
23 // number of microseconds for sending a one via the DCC encoding
24 #define DCC_ONE_BIT_TOTAL_DURATION 116
25 // number of microseconds for each half of the DCC signal for a one
26 #define DCC_ONE_BIT_PULSE_DURATION 58
27 
29  // DEFINE THE INTERRUPT LOGIC THAT GENERATES THE DCC SIGNAL
31 
32  // The code below will be called every time an interrupt is triggered on OCNB, where N can be 0 or 1.
33  // It is designed to read the current bit of the current register packet and
34  // updates the OCNA and OCNB counters of Timer-N to values that will either produce
35  // a long (200 microsecond) pulse, or a short (116 microsecond) pulse, which respectively represent
36  // DCC ZERO and DCC ONE bits.
37 
38  // These are hardware-driven interrupts that will be called automatically when triggered regardless of what
39  // DCC++ BASE STATION was otherwise processing. But once inside the interrupt, all other interrupt routines are temporarily disabled.
40  // Since a short pulse only lasts for 116 microseconds, and there are TWO separate interrupts
41  // (one for Main Track Registers and one for the Program Track Registers), the interrupt code must complete
42  // in much less than 58 microseconds, otherwise there would be no time for the rest of the program to run. Worse, if the logic
43  // of the interrupt code ever caused it to run longer than 58 microseconds, an interrupt trigger would be missed, the OCNA and OCNB
44  // registers would not be updated, and the net effect would be a DCC signal that keeps sending the same DCC bit repeatedly until the
45  // interrupt code completes and can be called again.
46 
47  // A significant portion of this entire program is designed to do as much of the heavy processing of creating a properly-formed
48  // DCC bit stream upfront, so that the interrupt code below can be as simple and efficient as possible.
49 
50  // Note that we need to create two very similar copies of the code --- one for the Main Track OC1B interrupt and one for the
51  // Programming Track OCOB interrupt. But rather than create a generic function that incurs additional overhead, we create a macro
52  // that can be invoked with proper parameters for each interrupt. This slightly increases the size of the code base by duplicating
53  // some of the logic for each interrupt, but saves additional time.
54 
55  // As structured, the interrupt code below completes at an average of just under 6 microseconds with a worse-case of just under 11 microseconds
56  // when a new register is loaded and the logic needs to switch active register packet pointers.
57 
58  // THE INTERRUPT CODE MACRO: R=REGISTER LIST pointer (&mainRegs or &progRegs)
59 
60 #define DCC_SIGNAL(R)
61  byte nextBitToSendLocal = 0;
62  if(R->currentBit == R->currentReg->activePacket->nBits){ /* IF no more bits in this DCC Packet */
63  R->currentBit = 0; /* reset current bit pointer and determine which Register and Packet to process next--- */
64  if (R->nRepeat > 0 && R->currentReg == R->reg) { /* IF current Register is first Register AND should be repeated */
65  R->nRepeat--; /* decrement repeat count; result is this same Packet will be repeated */
66  }
67  else if (R->nextReg != NULL) { /* ELSE IF another Register has been updated */
68  R->currentReg = R->nextReg; /* update currentReg to nextReg */
69  R->nextReg = NULL; /* reset nextReg to NULL */
70  R->tempPacket = R->currentReg->activePacket; /* flip active and update Packets */
71  R->currentReg->activePacket = R->currentReg->updatePacket;
72  R->currentReg->updatePacket = R->tempPacket;
73  }
74  else { /* ELSE simply move to next Register */
75  if (R->currentReg == R->maxLoadedReg) /* BUT IF this is last Register loaded */
76  R->currentReg = R->reg; /* first reset currentReg to base Register, THEN */
77  R->currentReg++; /* increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */
78  } /* END-ELSE */
79  } /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */
80 
81  if (R->currentReg->activePacket->buf[R->currentBit / 8] & R->bitMask[R->currentBit % 8]) { /* IF bit is a ONE */
82  nextBitToSendLocal = 1;
83  } else{ /* ELSE it is a ZERO */
84  nextBitToSendLocal = 0;
85  } /* END-ELSE */
86 
87  R->currentBit++; /* point to next bit in current Packet */
88 
90 // NOW USE THE ABOVE MACRO TO CREATE THE CODE FOR EACH INTERRUPT
91 
92 class SignalGenerator
93 {
94 public:
95  SignalGenerator(byte inTimerIndex) { this->timerIndex = inTimerIndex; this->_pulseTimer = NULL; }
96 
97  void configureSignal(uint8_t inSignalPin);
98  void startSignal(bool inDebugMode);
99  void stopSignal(void);
100 
101  uint8_t _signalPin;
102  byte timerIndex;
103  volatile RegisterList *pRegisters;
104 
105  hw_timer_t* _pulseTimer;
106 
107  bool halfPulse;
108 };
109 
110 SignalGenerator *pMainSignal = NULL, *pProgSignal = NULL;
111 #ifdef DCCPP_DEBUG_MODE
112 long debugDelay = 0;
113 int curr = LOW;
114 #endif
115 bool pulseMain = false;
116 bool pulseProg = false;
117 
118 void IRAM_ATTR signalGeneratorPulseTimerMain(void)
119 {
120 #ifdef DCCPP_DEBUG_MODE
121  /* if (millis() - debugDelay > 1000)
122  {
123  debugDelay = millis();
124  curr = curr == LOW ? HIGH : LOW;
125  digitalWrite(2, curr);
126  }*/
127 #endif
128 
129  pulseMain = !pulseMain;
130  if (pulseMain)
131  {
132  digitalWrite(pMainSignal->_signalPin, LOW);
133  return;
134  }
135  DCC_SIGNAL(pMainSignal->pRegisters)
136  if (nextBitToSendLocal)
137  timerAlarmWrite(pMainSignal->_pulseTimer, DCC_ONE_BIT_PULSE_DURATION, true);
138  else
139  timerAlarmWrite(pMainSignal->_pulseTimer, DCC_ZERO_BIT_PULSE_DURATION, true);
140 
141  timerWrite(pMainSignal->_pulseTimer, 0);
142  timerAlarmEnable(pMainSignal->_pulseTimer);
143  digitalWrite(pMainSignal->_signalPin, HIGH);
144 }
145 
146 void IRAM_ATTR signalGeneratorPulseTimerProg(void)
147 {
148 #ifdef DCCPP_DEBUG_MODE
149  /* if (millis() - debugDelay > 1000)
150  {
151  debugDelay = millis();
152  curr = curr == LOW ? HIGH : LOW;
153  digitalWrite(2, curr);
154  }*/
155 #endif
156 
157  pulseProg = !pulseProg;
158  if (pulseProg)
159  {
160  digitalWrite(pProgSignal->_signalPin, LOW);
161  return;
162  }
163 
164  DCC_SIGNAL(pProgSignal->pRegisters)
165  if (nextBitToSendLocal)
166  timerAlarmWrite(pProgSignal->_pulseTimer, DCC_ONE_BIT_PULSE_DURATION, true);
167  else
168  timerAlarmWrite(pProgSignal->_pulseTimer, DCC_ZERO_BIT_PULSE_DURATION, true);
169  timerWrite(pProgSignal->_pulseTimer, 0);
170  timerAlarmEnable(pProgSignal->_pulseTimer);
171  digitalWrite(pProgSignal->_signalPin, HIGH);
172 }
173 
174 void SignalGenerator::startSignal(bool inDebugMode)
175 {
176  //log_i("[%s] Configuring Timer(%d) for generating DCC Signal (Half Wave)", _name.c_str(), 2 * timerIndex + 1);
177  this->_pulseTimer = timerBegin(this->timerIndex, inDebugMode ? DCC_TIMER_PRESCALE/10 : DCC_TIMER_PRESCALE, true);
178  //log_i("[%s] Attaching interrupt handler to Timer(%d)", _name.c_str(), 2 * timerIndex + 1);
179  timerAttachInterrupt(this->_pulseTimer, this == pMainSignal ? &signalGeneratorPulseTimerMain : &signalGeneratorPulseTimerProg, true);
180  //log_i("[%s] Configuring alarm on Timer(%d) to %dus", _name.c_str(), 2 * timerIndex + 1, DCC_ONE_BIT_TOTAL_DURATION / 2);
181  timerAlarmWrite(this->_pulseTimer, DCC_ONE_BIT_PULSE_DURATION, true);
182  //log_i("[%s] Setting load on Timer(%d) to zero", _name.c_str(), 2 * timerIndex + 1);
183  timerWrite(this->_pulseTimer, 0);
184 
185  //log_i("[%s] Enabling alarm on Timer(%d)", _name.c_str(), 2 * timerIndex + 1);
186  timerAlarmEnable(this->_pulseTimer);
187 
188 #ifdef DCCPP_DEBUG_MODE
189  Serial.print("Signal started for ");
190  Serial.print(this == pMainSignal ? "Main track" : "Prog track");
191  Serial.print(" on pin ");
192  Serial.println(this->_signalPin);
193 #endif
194 }
195 
196 void SignalGenerator::stopSignal(void)
197 {
198  //log_i("[%s] Shutting down Timer(%d) (Half Wave)", _name.c_str(), 2 * timerIndex + 1);
199  timerStop(this->_pulseTimer);
200  timerAlarmDisable(this->_pulseTimer);
201  timerDetachInterrupt(this->_pulseTimer);
202  timerEnd(this->_pulseTimer);
203 
204 #ifdef DCCPP_DEBUG_MODE
205  Serial.print("Signal stoppedfor ");
206  Serial.print(this == pMainSignal ? "Main track" : "Prog track");
207  Serial.print(" on pin ");
208  Serial.println(this->_signalPin);
209 #endif
210  // give enough time for any timer ISR calls to complete before draining
211  // the packet queue and returning
212  delay(250);
213 }
214 
215 void SignalGenerator::configureSignal(uint8_t inSignalPin)
216 {
217  this->_signalPin = inSignalPin;
218 
219  // force the directionPin to low since it will be controlled by the DCC timer
220  pinMode(inSignalPin, INPUT);
221  digitalWrite(inSignalPin, LOW);
222  pinMode(inSignalPin, OUTPUT);
223  this->startSignal(false);
224 }
225 
226 void DCCpp::beginMainDccSignal(uint8_t inSignalPin)
227 {
228  DCCpp::mainRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
229 
230  pMainSignal = new SignalGenerator(0);
231  pMainSignal->pRegisters = &DCCpp::mainRegs;
232  pMainSignal->configureSignal(inSignalPin);
233 
234  if (DCCppConfig::DirectionMotorA != UNDEFINED_PIN)
235  {
236  pinMode(DCCppConfig::DirectionMotorA, INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
237  digitalWrite(DCCppConfig::DirectionMotorA, LOW);
238  }
239 
240  if (DCCppConfig::SignalEnablePinMain != UNDEFINED_PIN)
241  pinMode(DCCppConfig::SignalEnablePinMain, OUTPUT);
242 
243 #ifdef DCCPP_DEBUG_MODE
244  Serial.println("Main track DCC ESP32 started.");
245 #endif
246 }
247 
248 void DCCpp::beginProgDccSignal(uint8_t inSignalPin)
249 {
250  DCCpp::progRegs.loadPacket(1, RegisterList::idlePacket, 2, 0); // load idle packet into register 1
251 
252  pProgSignal = new SignalGenerator(1);
253  pProgSignal->pRegisters = &DCCpp::progRegs;
254  pProgSignal->configureSignal(inSignalPin);
255 
256  if (DCCppConfig::DirectionMotorB != UNDEFINED_PIN)
257  {
258  pinMode(DCCppConfig::DirectionMotorB, INPUT); // ensure this pin is not active! Direction will be controlled by DCC SIGNAL instead (below)
259  digitalWrite(DCCppConfig::DirectionMotorB, LOW);
260  }
261 
262  if (DCCppConfig::SignalEnablePinProg != UNDEFINED_PIN)
263  pinMode(DCCppConfig::SignalEnablePinProg, OUTPUT);
264 
265 #ifdef DCCPP_DEBUG_MODE
266  Serial.println("Prog track DCC ESP32 started.");
267 #endif
268 }
269 
271 {
272  if (pMainSignal != NULL)
273  {
274  pMainSignal->stopSignal();
275  pMainSignal->startSignal(true);
276  }
277 
278  if (pProgSignal != NULL)
279  {
280  pProgSignal->stopSignal();
281  pProgSignal->startSignal(true);
282  }
283 }
284 
285 #endif
static void setDebugDccMode()
static void beginProgDccSignal(uint8_t inSignalPin)
static void beginMainDccSignal(uint8_t inSignalPin)