-
Notifications
You must be signed in to change notification settings - Fork 0
/
TPL2.cpp
340 lines (278 loc) · 8.81 KB
/
TPL2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
#include <arduino.h>
#include <stdarg.h>
#define DIAG_ENABLED true
#include "TPL2.h"
#include "TPL.h"
#include <DCC.h>
#include <DIAG.h>
#include <Turnouts.h>
#include <WiThrottle.h>
#include "TPLSensor.h"
const extern PROGMEM byte TPLRouteCode[]; // Will be resolved by user creating ROUTES table
// Statics
byte TPL2::flags[MAX_FLAGS];
int TPL2::signalZeroPin;
byte TPL2::sensorCount;
int TPL2::progtrackLocoId;
TPL2 * TPL2::loopTask=NULL; // loopTask contains the address of ONE of the tasks in a ring.
TPL2 * TPL2::pausingTask=NULL; // Task causing a PAUSE.
// when pausingTask is set, that is the ONLY task that gets any service,
// and all others will have their locos stopped, then resumed after the pausing task resumes.
TPL2::TPL2(byte route) {
progCounter=locateRouteStart(route);
delayTime=0;
loco=0;
speedo=0;
forward=true;
// chain into ring of TPL2s
if (loopTask==NULL) {
loopTask=this;
next=this;
}
else {
next=loopTask->next;
loopTask->next=this;
}
DIAG(F("TPL2 created for Route %d at prog %d, next=%x, loopTask=%x\n"),route,progCounter,next,loopTask);
}
TPL2::~TPL2() {
if (next==this) loopTask=NULL;
else for (TPL2* ring=next;;ring=ring->next) if (ring->next == this) {
ring->next=next;
loopTask=next;
break;
}
}
int TPL2::locateRouteStart(short _route) {
if (_route==0) return 0; // Route 0 is always start of ROUTES for default startup
for (int pcounter=0;;pcounter+=2) {
byte opcode=pgm_read_byte_near(TPLRouteCode+pcounter);
if (opcode==OPCODE_ENDROUTES) return -1;
if (opcode==OPCODE_ROUTE) if( _route==pgm_read_byte_near(TPLRouteCode+pcounter+1)) return pcounter;
}
}
/* static */ void TPL2::begin(
short _signalZeroPin, // arduino pin connected to first signal
short _signals // Number of signals (2 pins each)
) {
DIAG(F("TPL begin sig0=%d,sigs=%d\n"),
_signalZeroPin,_signals);
signalZeroPin = _signalZeroPin;
for (int pin = 0; pin < _signals + _signals ; pin++) {
pinMode(pin + signalZeroPin, OUTPUT);
}
new TPL2(0); // add the startup route
WiThrottle::annotateLeftRight=true; // Prefer left/Right to Open/Closed
DCC::begin();
}
void TPL2::driveLoco(byte speed) {
if (loco<0) return; // Caution, allows broadcast!
DCC::setThrottle(loco,speed, forward^invert);
// TODO... if broadcast speed 0 then pause all other tasks.
}
bool TPL2::readSensor(short id) {
if (id>=MAX_FLAGS) return false;
if (flags[id] & SENSOR_FLAG) return true; // sensor locked on by software
bool s= TPLSensor::read(id); // real hardware sensor (false if not defined)
if (s) DIAG(F("\nSensor %d hit\n"),id);
return s;
}
void TPL2::skipIfBlock() {
short nest = 1;
while (nest > 0) {
progCounter += 2;
byte opcode = pgm_read_byte_near(TPLRouteCode+progCounter);;
switch(opcode) {
case OPCODE_IF:
case OPCODE_IFNOT:
case OPCODE_IFRANDOM:
nest++;
break;
case OPCODE_ENDIF:
nest--;
break;
default:
break;
}
}
}
void TPL2::setSignal(short num, bool go) {
// DIAG(" Signal %d %s", num, go ? "Green" : "Red");
int pinRed = signalZeroPin + num + num;
digitalWrite(pinRed, go ? HIGH : LOW);
digitalWrite(pinRed + 1, go ? LOW : HIGH);
}
/* static */ void TPL2::readLocoCallback(int cv) {
progtrackLocoId=cv;
}
void TPL2::loop() {
DCC::loop();
//DIAG(F("\n+ pausing=%x, looptask=%x"),pausingTask,loopTask);
// Round Robin call to a TPL task each time
if (loopTask==NULL) return;
loopTask=loopTask->next;
// DIAG(F(" next=%x"),loopTask);
if (pausingTask==NULL || pausingTask==loopTask) loopTask->loop2();
}
void TPL2::loop2() {
if (delayTime!=0 && millis()-delayStart < delayTime) return;
byte opcode = pgm_read_byte_near(TPLRouteCode+progCounter);
byte operand = pgm_read_byte_near(TPLRouteCode+progCounter+1);
// Attention: Returning from this switch leaves the program counter unchanged.
// This is used for unfinished waits for timers or sensors.
// Breaking from this switch will step to the next step in the route.
switch (opcode) {
case OPCODE_TL:
case OPCODE_TR:
Turnout::activate(operand, opcode==OPCODE_TL);
break;
case OPCODE_REV:
forward = false;
driveLoco(speedo);
break;
case OPCODE_FWD:
forward = true;
driveLoco(speedo);
break;
case OPCODE_SPEED:
driveLoco(operand);
break;
case OPCODE_INVERT_DIRECTION:
invert= !invert;
break;
case OPCODE_RESERVE:
if (flags[operand] & SECTION_FLAG) {
driveLoco(0);
return;
}
flags[operand] |= SECTION_FLAG;
break;
case OPCODE_FREE:
flags[operand] &= ~SECTION_FLAG;
break;
case OPCODE_AT:
if (readSensor(operand)) break;
delayMe(50);
return;
case OPCODE_AFTER: // waits for sensor to hit and then remain off for 0.5 seconds. (must come after an AT operation)
if (readSensor(operand)) {
// reset timer to half a second and keep waiting
waitAfter=millis();
return;
}
if (millis()-waitAfter < 500 ) return;
break;
case OPCODE_SET:
flags[operand] |= SENSOR_FLAG;
break;
case OPCODE_RESET:
flags[operand] &= ~SENSOR_FLAG;
break;
case OPCODE_PAUSE:
DCC::setThrottle(0,0,true); // pause all locos on the track
pausingTask=this;
break;
case OPCODE_RESUME:
pausingTask=NULL;
driveLoco(speedo);
for (TPL2 * t=next; t!=this;t=t->next) if (t->loco >0) t->driveLoco(t->speedo);
break;
case OPCODE_IF: // do next operand if sensor set
if (!readSensor(operand)) skipIfBlock();
break;
case OPCODE_IFNOT: // do next operand if sensor not set
if (readSensor(operand)) skipIfBlock();
break;
case OPCODE_IFRANDOM: // do block on random percentage
if (random(100)>=operand) skipIfBlock();
break;
case OPCODE_ENDIF:
break;
case OPCODE_DELAY:
delayMe(operand*10);
break;
case OPCODE_RANDWAIT:
delayMe((int)random(operand*10));
break;
case OPCODE_RED:
setSignal(operand,false);
break;
case OPCODE_GREEN:
setSignal(operand,true);
break;
case OPCODE_STOP:
driveLoco(0);
break;
case OPCODE_FON:
DCC::setFn(loco,operand,true);
break;
case OPCODE_FOFF:
DCC::setFn(loco,operand,false);
break;
case OPCODE_FOLLOW:
progCounter=locateRouteStart(operand);
if (progCounter<0) delete this;
return;
case OPCODE_ENDROUTE:
case OPCODE_ENDROUTES:
delete this; // removes this task from the ring buffer
return;
case OPCODE_PROGTRACK:
if (operand>0) {
// TODO TPLDCC1::setProgtrackToMain(false);
// showProg(true);
}
else {
// TODO TPLDCC1::setProgtrackToMain(true);
// showProg(false);
}
break;
case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes
progtrackLocoId=-1;
DCC::getLocoId(readLocoCallback);
break;
case OPCODE_READ_LOCO2:
if (progtrackLocoId<0) return; // still waiting for callback
loco=progtrackLocoId;
speedo=0;
forward=true;
invert=false;
break;
case OPCODE_SCHEDULE:
{
// Create new task and transfer loco.....
// but cheat by swapping prog counters with new task
new TPL2(operand);
int swap=loopTask->progCounter;
loopTask->progCounter=progCounter+2;
progCounter=swap;
}
break;
case OPCODE_SETLOCO:
{
// two bytes of loco address are in the next two OPCODE_PAD operands
int operand2 = pgm_read_byte_near(TPLRouteCode+progCounter+3);
progCounter+=2; // Skip the extra two instructions
loco=operand<<7 | operand2;
speedo=0;
forward=true;
invert=false;
DIAG(F("\n SETLOCO %d \n"),loco);
}
break;
case OPCODE_ROUTE:
DIAG(F("\n Starting Route %d\n"),operand);
break;
case OPCODE_PAD:
// Just a padding for previous opcode needing >1 operad byte.
break;
default:
DIAG(F("\nOpcode %d not supported\n"),opcode);
}
// Falling out of the switch means move on to the next opcode
progCounter+=2;
}
void TPL2::delayMe(int delay) {
delayTime=delay;
delayStart=millis();
}