-
Notifications
You must be signed in to change notification settings - Fork 1
/
LND-8CDU.ino
390 lines (333 loc) · 11.2 KB
/
LND-8CDU.ino
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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
#include <LocoNet.h>
constexpr int PIN_TX = 9;
constexpr int PIN_RX = 8;
constexpr int PIN_BT = 12;
constexpr int PIN_ADC0 = A0;
constexpr int PIN_ADC1 = A1;
constexpr int PIN_VCAP = PIN_ADC0;
constexpr int PIN_VCC = PIN_ADC1;
constexpr int PIN_LED = LED_BUILTIN;
// 9V at VOUT, 3V at ADC (3/5*1024=614 ticks),
// 9000 / (9000/3/5000*1024) = 5000*3/1024
constexpr uint16_t ADC2MV = 15;
constexpr uint16_t MIN_VCC = 7000;
uint16_t vccLevel=0;
uint16_t capLevel=0;
constexpr int ADDR_COUNT = 4;
constexpr int PINS_O[] = {4,5,6,7, 10,11,A4,A5 };
constexpr int PIN_COUNT = 8;
bool states[PIN_COUNT];
LocoNetSystemVariableClass sv;
constexpr uint16_t SV_ADDR_PULSE_DURATION = SV_ADDR_USER_BASE;
constexpr uint16_t SV_ADDR_PULSE_COUNT = SV_ADDR_USER_BASE + 1;
constexpr uint16_t SV_ADDR_RESET = 255;//SV_ADDR_USER_BASE + 250;
constexpr uint16_t DEFAULT_ADDR = 6;
constexpr uint16_t DEFAULT_SERIAL = 0x0001;
constexpr uint8_t ID_MANFR = 13;
constexpr uint8_t ID_DEVLPR = 4;
constexpr uint8_t ID_PRODUCT = 1;
constexpr uint8_t ID_SWVER = 1;
uint16_t startAddr = DEFAULT_ADDR;
uint8_t pulseDurationMs;
uint8_t pulseCount;
bool configMode = false;
uint16_t configVar = 0;
uint32_t ledNextUpdate;
uint8_t ledVal;
constexpr uint16_t BUTTON_LONG_PRESS_DURATION = 3000;
constexpr int LED_INTL_NORMAL = 1000;
constexpr int LED_INTL_CONFIG1 = 400;
constexpr int LED_INTL_CONFIG2 = 150;
void ledFire(uint32_t, uint8_t);
void ledStop();
void setup() {
Serial.begin(115200);
Serial.println(F("LND-8CDU - LocoNet accessory decoder with 8 CDU outputs"));
pinMode(PIN_BT, INPUT);
for (int pin: PINS_O) pinMode(pin, OUTPUT);
pinMode(PIN_LED, OUTPUT);
ledFire(50,0);
LocoNet.init(PIN_TX);
sv.init(ID_MANFR, ID_DEVLPR, ID_PRODUCT, ID_SWVER);
uint16_t serial =
sv.readSVStorage(SV_ADDR_SERIAL_NUMBER_H)<<8
| sv.readSVStorage(SV_ADDR_SERIAL_NUMBER_L);
if(serial != DEFAULT_SERIAL) {
Serial.println(F("Writing factory defaults") );
sv.writeSVStorage(SV_ADDR_SERIAL_NUMBER_H, DEFAULT_SERIAL>>8);
sv.writeSVStorage(SV_ADDR_SERIAL_NUMBER_L, DEFAULT_SERIAL & 0xFF);
sv.writeSVNodeId(DEFAULT_ADDR);
sv.writeSVStorage(SV_ADDR_PULSE_DURATION, 10);
sv.writeSVStorage(SV_ADDR_PULSE_COUNT, 1);
}
startAddr = sv.readSVNodeId();
pulseDurationMs = sv.readSVStorage(SV_ADDR_PULSE_DURATION);
pulseCount = sv.readSVStorage(SV_ADDR_PULSE_COUNT);
Serial.println(F("Init done"));
Serial.print(F("Address: ")); Serial.println(startAddr);
Serial.print(F("Pulse duration (ms): ")); Serial.println(pulseDurationMs);
Serial.print(F("Pulse count: ")); Serial.println(pulseCount);
}
int8_t hex2int(char ch) {
if (ch >= '0' && ch <= '9') { return ch - '0'; }
if (ch >= 'A' && ch <= 'F') { return ch - 'A' + 10; }
if (ch >= 'a' && ch <= 'f') { return ch - 'a' + 10; }
return -1;
}
void ledFire(uint32_t ms, uint8_t val=1) {
ledVal = val;
digitalWrite(PIN_LED, ledVal);
ledNextUpdate = millis()+ms;
}
void ledOn() {
if(ledNextUpdate!=0) return;
ledFire(0,0);
}
void ledStop() {
digitalWrite(PIN_LED, LOW);
ledNextUpdate = 0; // turn off blink
}
void checkButton() {
static uint8_t lastBt;
static long btPressTime = 0;
uint8_t bt = 1-digitalRead(PIN_BT); // it's inverted
if(lastBt==0 && bt==1) {
//Serial.println("Button down");
btPressTime = millis();
if(configMode) Serial.println("Quitting config mode");
configMode=false;
delay(5); // simple debounce
ledStop();
}
if(bt==1 && btPressTime!=0) {
if(millis()-btPressTime>BUTTON_LONG_PRESS_DURATION && !configMode) {
configMode=true;
configVar=0;
Serial.println("Entering config mode");
ledOn(); // start blink again
}
}
if(bt==0 && lastBt==1) {
//Serial.println(String("Button was down for ")+(millis()-btPressTime));
btPressTime = 0;
delay(5); // simple debounce
ledOn(); // start blink again
}
lastBt = bt;
}
void loop() {
if (Serial.available()>0) {
int8_t ch = hex2int(Serial.read());
if(ch>=0 && ch<ADDR_COUNT*2) {
pulsePinFull(ch);
} ;//else notifySVChanged(SV_ADDR_RESET);
}
static bool deferredProcessingNeeded = false;
// Check for any received LocoNet packets
lnMsg *msg = LocoNet.receive() ;
if ( msg!=nullptr ) {
// If this packet was not a Switch or Sensor Message then print a new line
if (!LocoNet.processSwitchSensorMessage(msg)) {
SV_STATUS svStatus = sv.processMessage(msg);
Serial.print(F("LNSV processMessage - Status: "));
Serial.println(svStatus);
deferredProcessingNeeded = (svStatus == SV_DEFERRED_PROCESSING_NEEDED);
} else {
}
}
if(deferredProcessingNeeded)
deferredProcessingNeeded = (sv.doDeferredProcessing() != SV_OK);
checkButton();
static uint32_t lastMs = millis();
if(millis()-lastMs > 25) {
lastMs = millis();
updateVoltages();
/*Serial.print( vccLevel );
Serial.print(',');
Serial.println( capLevel ); */
}
if(ledNextUpdate!=0 && millis()>ledNextUpdate) {
ledVal = 1-ledVal;
digitalWrite(PIN_LED, ledVal);
if(!configMode) {
ledNextUpdate = LED_INTL_NORMAL;
} else {
ledNextUpdate = configVar==0 ? LED_INTL_CONFIG1 : LED_INTL_CONFIG2;
}
ledNextUpdate += millis();
}
}
// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Sensor messages OPC_INPUT_REP
/*
void notifySensor( uint16_t addr, uint8_t State ) {
Serial.print("Sensor: ");
Serial.print(addr, DEC);
Serial.print(" - ");
Serial.println( State ? "Active" : "Inactive" );
}
*/
void updateVoltages() {
uint32_t v;
uint32_t t;
static uint32_t periodStart = millis();
static uint16_t maxVcc = 0;
constexpr uint32_t PERIOD = 10000;
v = analogRead(PIN_VCC)*ADC2MV;
if(v>maxVcc) maxVcc=v;
if(v>vccLevel) vccLevel=v;
if(millis()-periodStart > PERIOD) {
vccLevel = maxVcc;
maxVcc = v;
periodStart = millis();
}
/*t = vccLevel;
if(t==0) vccLevel = v;
else vccLevel = (99*t + 1*v) / 100;*/
v = analogRead(PIN_VCAP)*ADC2MV ;
t = capLevel;
if(t==0) capLevel = v;
else capLevel = (60*t + 40*v) / 100;
static uint32_t lastPrint = millis();
if(millis()-lastPrint>25) {
//Serial.println(String(vccLevel)+","+capLevel);
lastPrint=millis();
}
}
inline void waitCharge() {
updateVoltages();
//Serial.println( String("waitCharge(); Vcap=")+capLevel+"; Vcc="+vccLevel );
while((uint32_t)capLevel*100 < (uint32_t)vccLevel*98) {
delay(1);
updateVoltages();
}
}
inline void pulsePin(uint8_t pin) {
//Serial.println( String("pulsePin(); Vcap=")+capLevel+"; Vcc="+vccLevel );
digitalWrite(PINS_O[pin], HIGH);
ledFire(100);
long startTime = millis();
while(millis()-startTime < pulseDurationMs
&& (uint16_t)analogRead(PIN_VCC)*ADC2MV>MIN_VCC ) {}
digitalWrite(PINS_O[pin], LOW);
//Serial.println( String("pulsePin(); duration(ms): ")+(millis()-startTime) );
}
inline void pulsePinFull(uint8_t ipin) {
for(int i=0; i<pulseCount; i++) {
waitCharge();
pulsePin(ipin);
}
}
void reportSwitchState(uint16_t addr) {
int ipin = (addr-startAddr)*2; // "Closed" pin
addr -= 1;
lnMsg txMsg;
txMsg.srp.command = OPC_SW_REP;
txMsg.srp.sn1 = addr & 0x7F;
txMsg.srp.sn2 = ((addr >> 7) & 0x0F)
| (states[ipin ] ? OPC_SW_REP_CLOSED : 0)
| (states[ipin+1] ? OPC_SW_REP_THROWN : 0);
LocoNet.send(&txMsg);
}
// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Switch Request messages
// OPC_SW_REQ
void notifySwitchRequest( uint16_t addr, uint8_t out, uint8_t dir ) {
bool on = out!=0;
bool thrown = dir==0;
/*Serial.print("Switch Request: ");
Serial.print(addr, DEC);
Serial.print(':');
Serial.print(dir ? "Closed" : "Thrown");
Serial.print(" - ");
Serial.println(out ? "On" : "Off");*/
if(!on) return;
if(configMode) {
if(configVar == 0) {
configVar = addr;
} else {
switch(configVar) {
case 1:
startAddr = addr;
sv.writeSVNodeId(startAddr);
break;
default:
sv.writeSVStorage(SV_ADDR_USER_BASE+configVar-1, addr);
break;
}
configVar=0;
ledFire(2000);
}
return;
}
if(addr >= startAddr && addr<startAddr+ADDR_COUNT) {
int ipin = (addr-startAddr)*2; // requested pin
int ipin_compl; // complementary pin
if(thrown) { ipin_compl = ipin+1; } else {ipin_compl=ipin; ipin++;}
states[ipin] = true;
states[ipin_compl] = false;
digitalWrite(PINS_O[ipin_compl], LOW); // turn off complementary pin
pulsePinFull(ipin);
reportSwitchState(addr);
}
}
void(* reboot) (void) = 0;
void notifySVChanged(uint16_t offs){
Serial.print(F("notifySVChanged: SV"));
Serial.print(offs);
Serial.print(":=");
Serial.println(sv.readSVStorage(offs));
switch(offs) {
case SV_ADDR_RESET:
sv.writeSVStorage(SV_ADDR_SERIAL_NUMBER_H, 0xFF);
sv.writeSVStorage(SV_ADDR_RESET, 0);
delay(10);
reboot();
break;
case SV_ADDR_PULSE_DURATION:
pulseDurationMs = sv.readSVStorage(SV_ADDR_PULSE_DURATION);
break;
case SV_ADDR_PULSE_COUNT:
pulseCount = sv.readSVStorage(SV_ADDR_PULSE_COUNT);
break;
}
}
// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Switch out Report messages
// OPC_SW_REP and !LnPacket->srp.sn2 & OPC_SW_REP_INPUTS)
/*
void notifySwitchoutsReport( uint16_t addr, uint8_t Closedout, uint8_t Thrownout) {
Serial.print("Switch outs Report: ");
Serial.print(addr, DEC);
Serial.print(": Closed - ");
Serial.print(Closedout ? "On" : "Off");
Serial.print(": Thrown - ");
Serial.println(Thrownout ? "On" : "Off");
}
*/
// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Switch Sensor Report messages
// if OPC_SW_REP and LnPacket->srp.sn2 & OPC_SW_REP_INPUTS
/*
void notifySwitchReport( uint16_t addr, uint8_t State, uint8_t Sensor ) {
Serial.print("Switch Sensor Report: ");
Serial.print(addr, DEC);
Serial.print(':');
Serial.print(Sensor ? "Switch" : "Aux");
Serial.print(" - ");
Serial.println( State ? "Active" : "Inactive" );
}
*/
// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Switch State messages
// OPC_SW_STATE
/*
void notifySwitchState( uint16_t addr, uint8_t out, uint8_t dir ) {
Serial.print("Switch State: ");
Serial.print(addr, DEC);
Serial.print(':');
Serial.print(dir ? "Closed" : "Thrown");
Serial.print(" - ");
Serial.println(out ? "On" : "Off");
}
*/