forked from MERG-DEV/CANCMDDC-Arduino
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCANCMDDC.ino
More file actions
3028 lines (2790 loc) · 81.9 KB
/
CANCMDDC.ino
File metadata and controls
3028 lines (2790 loc) · 81.9 KB
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
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#define VERSION 1.10
/**
* CANCMDDC - A DC "command station" for use with MERG CBUS systems
* Copyright (c) 2015 Mark Riddoch
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License
*/
/*
* This sketch makes use of the MCP_CAN library, modified to accept
* either an 8MHz clock or a 16MHz clock.
* Hardware requires are an MCP2515 based CBUS interface and a set
* of H-Bridges that can be used to drive the DC tracks.
*/
/*
* Updated and extended by Ian Morgan.
*
* Now uses hardware interrupt to receive incoming CBus messages into a FIFO buffer for later processing.
* Also uses a 100Hz timer interrupt to control LED, buzzer and accelleration and deccelleration.
* This sketch creates one instance of the FIFO buffer class, and 5 instances for the train controller class.
*/
/*
* Updated and extended by David W Radcliffe M3666.
*
* Now creates 6 (and upto 8) instances of the train controller class.
* Use an alternate LCD display: this is the Geekcreit� IIC I2C 2004 204 20 x 4 Character LCD Display Module.
* Uses an (optional) 4 x 3 keypad for local control of the track outputs and their speed.
* Up to 8 CANCMDDCs can be added to one CAN bus, each with its own address and DCC Address range.
* DCC Addresses x001 - x00n are used, and the last digit is used by the keypad to select the loco.
* This allows loco 0 (and 9) to be used for some other (as yet undefined) function - maybe 'select all' ??
*/
/*
Pin Use map:
Digital pin 2 (PWM) PWM0 H1a
Digital pin 3 (PWM) PWM1 H1b
Digital pin 4 (PWM) Enable Buzzer
Digital pin 5 (PWM) PWM2 H2a
Digital pin 6 (PWM) PWM3 H2b
Digital pin 7 (PWM) PWM4 H3a
Digital pin 8 (PWM) PWM5 H3b
Digital pin 10 (PWM) Mode unused - pull low for CANCMD master
Digital pin 11 (PWM) PWM6 unused - H-bridge 4a
Digital pin 12 (PWM) PWM7 unused - H-bridge 4b
Digital pin 13 (PWM) LED (onboard)
Digital pin 14 (TX3) EnableA unused - H-bridge 4a
Digital pin 15 (RX3) EnableB unused - H-bridge 4a
Digital pin 16 (TX2) EnableA unused - H-bridge 4b
Digital pin 17 (RX2) EnableB unused - H-bridge 4b
Digital pin 18 (TX1) Shutdown - pull low when short-circuit detected
Digital pin 19 (RX1) Int'upt CAN
Digital pin 20 (SDA) SDA LCD
Digital pin 21 (SCL) SCL LCD
Digital pin 22 EnableA H1a
Digital pin 23 EnableB H1a
Digital pin 24 EnableA H1b
Digital pin 25 EnableB H1b
Digital pin 26 EnableA H2a
Digital pin 27 EnableB H2a
Digital pin 28 EnableA H2b
Digital pin 29 EnableB H2b
Digital pin 30 EnableA H3a
Digital pin 31 EnableB H3a
Digital pin 32 EnableA H3b
Digital pin 33 EnableB H3b
Digital pin 34 Address 0 - pull low for 1 \
Digital pin 35 Address 1 - pull low for 2 > use in combination for address 0-7
Digital pin 36 Address 2 - pull low for 4 /
Digital pin 37 c0 Keypad - uses odd-numbered pins only so that the 7-way header plugs straight in
Digital pin 39 c1 Keypad
Digital pin 41 c2 Keypad
Digital pin 43 r0 Keypad
Digital pin 45 (PWM) r1 Keypad
Digital pin 47 r2 Keypad
Digital pin 49 r3 Keypad
Digital pin 50 (MISO) SO CAN
Digital pin 51 (MOSI) SI CAN
Digital pin 52 (SCK) Sck CAN
Digital pin 53 (SS) CS CAN
*/
#define DEBUG 1 // set to 0 for no debug messages, 1 for messages to console
#define OLED_DISPLAY 0 // set to 0 if 128x32 OLED display is not present
#define LCD_DISPLAY 1 // set to 0 if 4x20 char LCD display is not present
#define KEYPAD 1 // set to 0 if 4x3 keypad is not present
#define CANBUS 1 // set to 0 if CAN h/w is not present
//include libraries
#include <PWM.h> // Library for controlling PWM Frequency
#include "trainController.h"
#if LCD_DISPLAY || OLED_DISPLAY
#include <Wire.h> // Library for I2C comminications for display
#if OLED_DISPLAY
/* libraries for Adafruit display module */
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
/*
Adafruit 128x32 size display using I2C to communicate
3 pins are required to interface (2 I2C (SCL & SDA)and one reset (user definable))
*/
#define OLED_RESET 22
#endif
#if LCD_DISPLAY
/* libraries for LCD display module */
#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal.h>
#include <LCD.h>
#include <I2CIO.h>
#include <FastIO.h>
/* Geekcreit� IIC I2C 2004 204 20 x 4 Character LCD Display Module using I2C to communicate
2 pins are required to interface(I2C(SCL & SDA))
see http://arduino-info.wikispaces.com/LCD-Blue-I2C Use Sketch from I2C LCD DISPLAY VERSION 1. Do not Forget to adjust contrast!
https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home
*/
#endif
#endif
#if KEYPAD
#include <Keypad.h>
#endif
#define TRUE 1
#define FALSE 0
#define ON 1
#define OFF 0
#define LED 13 // Pin number for the on-board LED
#define SOUNDER 4 // Pin number for the AWD
/* Address select pins. These are active-low inputs */
/* Link pin to 0V to set address. */
#define Addr0 34
#define Addr1 35
#define Addr2 36
/* pin used for indication of power shutdown. */
/* Link pin to 0V when power off. */
#define SHUTDOWN 18
/*
* The following are the PWM outputs used to drive the tracks.
*
* Pins are fed to H Bridges, along with 2 enable pins.
*
* The following pins can handle the high frequency PWM
*/
static int pwmpins[] = {
2, 3, 5, 6, 7, 8, 11, 12
};
#if KEYPAD
/*
* The following are the inputs/outputs used to drive the keypad.
*/
static byte keypins[] = {
37, 39, 41, 43, 45, 47, 49 // uses odd-numbered pins only so that the 7-way header plugs straight in
};
#endif
#define NUM_CONTROLLERS 6 // the number of controllers (tuples of pwmpin and 2 enable pins) Max. 8
#define MAXTIMEOUT 30 // Max number of seconds before session is timed out
// if no stayalive received for the session
#define TIMER_PRELOAD 64911 //59286 //65536 - ((16000000/256)/100); // preload timer 65536-(16MHz/256/100Hz)
#define CAN_RTR_MASK 0x40000000
#define CAN_DEFAULT_ID 0x7C
/**
* The preset tests the jumper in the mode select pin to
* determine the mode of operation. Connect pin to +5V if CANCMD is present.
* Leave pin disconnected for standalone operation.
* It is true if the CANCMDDC is working on a CBUS that has a CANCMD present.
*/
boolean cancmd_present;
volatile byte timer_counter = 0;
volatile byte flash_counter = 0;
volatile byte beep_counter = 0;
volatile byte update_counter = 0;
volatile boolean updateNow = false;
volatile boolean shutdownFlag = false;
/**
* Definitions of the flags bits
*/
#define SF_FORWARDS 0x01 // Train is running forwards
#define SF_REVERSE 0x00 // Train is running in reverse
#define SF_LONG 0xC0 // long DCC address. top 2 bits of high byte. both 1 for long, both 0 for short.
#define SF_INACTIVE -1 // CAB Session is not active
#define SF_UNHANDLED -1 // DCC Address is not associated with this analogue controller (CANCMDDC)
#define SF_LOCAL -2 // DCC Address is operated only by the keypad, and not part of a CAB Session
#define startAddress 1000 // multiplier for DCC address offset from device address. Device 0 uses 1000, device 1 uses 2000,...
byte deviceAddress = 0; // assume only unit on CAN bus (for now)
// NOTE: controllers' index (not the DCC address) is used by the keypad handler. Making the last digit of the DCC address = the index aids clarity for user.
struct {
int session; // CAB session id for this loco
unsigned int DCCAddress; // complete address
byte longAddress; // top 2 bits of address = 1 for long
byte timeout;
boolean shared; // this loco shared by > 1 CAB (this includes the keypad)
struct {
byte address; // DCC short address of consist. 0 = unused.
byte session; // Session id of consist. 0 = unused.
boolean reverse;
} consist;
trainControllerClass trainController;
} controllers[NUM_CONTROLLERS] = {
{SF_INACTIVE, (startAddress * (deviceAddress + 1)) + 1, SF_LONG, 0, false, { 0, 0, false }, trainControllerClass(22, 23, pwmpins[0])}
,{SF_INACTIVE, (startAddress * (deviceAddress + 1)) + 2, SF_LONG, 0, false, { 0, 0, false }, trainControllerClass(24, 25, pwmpins[1])}
,{SF_INACTIVE, (startAddress * (deviceAddress + 1)) + 3, SF_LONG, 0, false, { 0, 0, false }, trainControllerClass(26, 27, pwmpins[2])}
,{SF_INACTIVE, (startAddress * (deviceAddress + 1)) + 4, SF_LONG, 0, false, { 0, 0, false }, trainControllerClass(28, 29, pwmpins[3])}
,{SF_INACTIVE, (startAddress * (deviceAddress + 1)) + 5, SF_LONG, 0, false, { 0, 0, false }, trainControllerClass(30, 31, pwmpins[4])}
,{SF_INACTIVE, (startAddress * (deviceAddress + 1)) + 6, SF_LONG, 0, false, { 0, 0, false }, trainControllerClass(32, 33, pwmpins[5])}
//,{SF_INACTIVE, ((deviceAddress + 1) * startAddress) + 6, SF_LONG, 0, false, trainControllerClass(14, 15, pwmpins[6])}
//,{SF_INACTIVE, ((deviceAddress + 1) * startAddress) + 7, SF_LONG, 0, false, trainControllerClass(16, 17, pwmpins[7])}
};
#if CANBUS
//include libraries
#include <SPI.h> // Library for SPI communications to CAN interface
#include <mcp_can.h>
#include "FIFO.h"
#include "cbusdefs8q.h"
/**
* The following block of #defines configures the pins that are
* used for various special functions:
* CHIPSELECT is the select pin for the CANBUS interface
* CBUSINTPIN is the "interrupt" pin used by the CANBUS interface
* to signal that a CANBUS packet is ready to be read
* MODESELECT is the jumper used to determine the operating mode, standalone or with CANCMD
*/
/* Pins used by SPI interface to CAN module */
#define CHIPSELECT 53
#define CBUSINTPIN 19
/* pin used for manual selection of use with CANCMD or standalone. */
/* Link pin to 0V if CANCMD required. */
#define MODESELECT 10
/**
* The CBUS interface object
*/
MCP_CAN CAN0(CHIPSELECT); // MCP CAN library
// CAN parameters
struct {
byte id;
boolean enumerating;
/* This could be made more memory-efficient by using one bit per CANID for the 127 possible ids.
Would use only 16 bytes of RAM, but would make the code more complex */
byte ids[110];
byte ptr;
} canId = {
CAN_DEFAULT_ID, false, NULL, 0
};
// instantiate a FIFO buffer for the CBus messages
CBusMessageBufferClass fifoMessageBuffer;
boolean bufferOverflow;
boolean cbusActive = true;
messageRecordType nextMessage;
#endif
enum errorStates {
noError,
locoStackFull,
locoTaken,
noSession,
consistEmpty,
locoNotFound,
CANbusError,
invalidRequest,
sessionCancelled
};
#if OLED_DISPLAY
// construct a display object
Adafruit_SSD1306 display(OLED_RESET);
#endif
#if (OLED_DISPLAY && SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
// Display images
#if OLED_DISPLAY
// 'Merg logo_for_adafruit_display'
const unsigned char mergLogo [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x3c, 0x00, 0xe3, 0xff, 0xcf, 0xfc, 0x0f, 0xfe, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x7c, 0x00, 0xe3, 0xff, 0xcf, 0xfe, 0x1f, 0xff, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x62, 0x00, 0x23, 0x00, 0x0c, 0x02, 0x18, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x61, 0x02, 0x23, 0x00, 0x0c, 0x02, 0x18, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x60, 0x84, 0x23, 0x00, 0x08, 0x02, 0x18, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x60, 0x48, 0x23, 0x00, 0x08, 0x02, 0x18, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x60, 0x70, 0x23, 0x00, 0x08, 0x02, 0x18, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x60, 0x20, 0x23, 0x00, 0x08, 0x02, 0x18, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x60, 0x00, 0x23, 0x00, 0x0c, 0x03, 0x18, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x70, 0x00, 0x23, 0xff, 0xcf, 0xff, 0x1c, 0x1e, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x00, 0x23, 0xff, 0xcf, 0xff, 0x9c, 0x1f, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x00, 0x23, 0xc0, 0x0f, 0x01, 0x9e, 0x19, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0x80, 0x0f, 0x01, 0x9e, 0x19, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0x80, 0x0f, 0x00, 0x9e, 0x19, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0x80, 0x0f, 0x00, 0x9e, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0x80, 0x0f, 0x00, 0x9e, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0x80, 0x0f, 0x00, 0x9e, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0x80, 0x0f, 0x00, 0x9e, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0x80, 0x0f, 0x00, 0x9e, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0xc0, 0x0f, 0x00, 0x9e, 0x01, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0xc0, 0x0f, 0x00, 0x9f, 0x03, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0xff, 0x8f, 0x00, 0x9f, 0xfe, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x78, 0x78, 0x23, 0xff, 0x8f, 0x00, 0x8f, 0xfe, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00
};
// 'bnhmrslogo'
const unsigned char bnhmrsLogo [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x70, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1c, 0x03, 0xff, 0x80, 0x00, 0x00, 0x00, 0x3f, 0x80, 0x78, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0c, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x06, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0x01, 0xbc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x80, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x3f, 0xbb, 0xbc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x3f, 0xbb, 0xdc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x60, 0x07, 0xff, 0xf3, 0x3b, 0xfe, 0x3f, 0xc1, 0x9c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x30, 0x03, 0xff, 0xff, 0xff, 0xdf, 0x3f, 0xc1, 0x9c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x07, 0xfd, 0xe6, 0x20, 0x8f, 0x3f, 0xc1, 0x9c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x3f, 0xc1, 0x9c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x3f, 0xc1, 0x9c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xbc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x06, 0x00, 0xff, 0x80, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xfc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0c, 0x01, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xc7, 0xfc, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x3c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x83, 0x3c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x03, 0x38, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x03, 0x78, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x03, 0x70, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x70, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0xe0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x03, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x07, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x1f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 0x00
};
#endif
#if LCD_DISPLAY
// construct a display object
// Set the pins on the I2C chip used for LCD connections:
// addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C display(0x3f, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address
volatile unsigned long previousTurnon = 0;
volatile unsigned long alight = 10000;
volatile boolean barGraphicsLoaded = false;
volatile boolean showingSpeeds = false;
#endif
/****** Function Prototypes *****/
int getSessionIndex (byte session);
int getDCCIndex (unsigned int dcc_address, byte long_address);
void releaseLoco(byte session);
void queryLoco(byte session);
void keepaliveSession(byte session);
void locoRequest(unsigned int address, byte long_address, byte flags);
void locoSession(byte session, unsigned int address, byte long_address, byte direction, byte speed);
void sendPLOC(byte session);
void sendTPower(byte on);
void sendDSPD(byte controllerIndex);
void sendRTR();
void sendError(unsigned int address, byte long_address, byte code);
void sendSessionError(byte session, errorStates code);
void addSessionConsist(byte session, byte consist);
void removeSessionConsist(byte session);
void sendReset(void);
void setSpeedSteps(byte session, byte steps);
void emergencyStopAll(void);
void receiveCBusMessage(void);
void nBeeps (byte numBeeps, int durationMillis);
void setupDisplay(void);
void showSpeeds(void);
void displaySpeed(byte sessionIndex);
void displayImage(const uint8_t *imageBitmap);
void displayVersion(void);
void initialiseDisplay(void);
void customChars(const uint8_t chars[][8]);
byte sid2byte(uint32_t canId);
void(*resetArduino) (void) = 0; //declare reset function at address 0
#if KEYPAD
#pragma region initialise keypad
const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
//define the symbols on the buttons of the keypads
char hexaKeys[ROWS][COLS] = {
{ '1','2','3' },
{ '4','5','6' },
{ '7','8','9' },
{ '*','0','#' }
};
byte rowPins[ROWS] = { keypins[6], keypins[5], keypins[4], keypins[3] }; //connect to the row pinouts of the keypad
byte colPins[COLS] = { keypins[2], keypins[1], keypins[0] }; //connect to the column pinouts of the keypad
//initialize an instance of class NewKeypad
Keypad keyPad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
#pragma endregion
/*
The keys perform loco/speed selection according to the following FSM:
Start in idle mode with loco 0 selected.
Press * - Enter Loco select mode:
Press * to stop current loco, then press # to reverse direction or press * to stop all locos. Press 0-n - Enter Speed select mode with that loco selected (if valid).
Press # - eStop currently selected loco. Press # - eStop all locos.
Press 0-9 - Enter speed digit. Scroll to left on subsequent digits. Press # - accept entered speed. If invalid (> 127), or Press *, or timeout, cancel current input and enter Speed select mode.
Timeout after 3 secs to Speed select mode in all cases except eStop all locos.
*/
enum fsmStates {
idle,
locoSelect,
locoStop,
speedSelect,
speedDigit,
locoEmergStop,
stealOrShare
};
struct {
fsmStates state;
byte currentLoco;
byte previousLoco;
char digits[3];
} keyFSM = {
idle, 255, 255, { ' ', ' ', ' ' }
};
volatile unsigned long previousKeypress = 0;
volatile unsigned long interval = 4000;
#endif
/***************************************************************************************************
* Arduino setup routine
* Configures the I/O pins, initialises the CANBUS library
* and sets up the initial session stack
*/
void setup()
{
#if DEBUG
// set up the IDE Serial Monitor for debugging output
Serial.begin(115200);
Serial.print(F("Initialising"));
Serial.println();
#endif
// set the mode for the Address pins
pinMode(Addr0, INPUT_PULLUP);
pinMode(Addr1, INPUT_PULLUP);
pinMode(Addr2, INPUT_PULLUP);
// set up the address
deviceAddress = ((digitalRead(Addr0) + (digitalRead(Addr1) * 2) + (digitalRead(Addr2) * 4)) ^ 0x07);
if (deviceAddress != 0) // change the default addresses
{
for (byte controllerIndex = 0; controllerIndex < NUM_CONTROLLERS; controllerIndex++)
{
controllers[controllerIndex].DCCAddress = (startAddress* (deviceAddress + 1)) + controllerIndex + 1;
}
}
#if DEBUG
Serial.print(F("Address selected: "));
Serial.println(deviceAddress);
#endif
#if CANBUS
// use a lower ID for non-zero addresses
canId.id = CAN_DEFAULT_ID - deviceAddress;
// set the mode for the CBus interrupt pin
pinMode(CBUSINTPIN, INPUT);
// set the mode for the operating mode setting input
pinMode(MODESELECT, INPUT_PULLUP);
cancmd_present = (digitalRead(MODESELECT) == LOW); // this will be overridden if a message is received from a CANCMD
// Initialise the CAN interface - do this now so that the h/w can trap any error whilst we display logos
if (CAN_OK == CAN0.begin(MCP_STDEXT, CAN_125KBPS, MCP_8MHZ)) // init can bus : baudrate = 125k, 8MHz
{
#if DEBUG
Serial.println(F("CAN BUS init ok!"));
#endif
if (CAN_OK == CAN0.setMode(MCP_NORMAL))
{
#if DEBUG
Serial.println(F("CAN BUS mode ok!"));
#endif
if (cancmd_present == FALSE)
{
// Reset any connected CABs
sendReset();
}
}
else
{
#if DEBUG
Serial.println(F("CAN BUS mode fail"));
#endif
#if KEYPAD
cbusActive = false;
#else // no CAN or Keypad
bomb();
#endif
}
}
else
{
//CAN interface initialisation has failed
#if DEBUG
Serial.println(F("CAN BUS init fail"));
#endif
#if KEYPAD
cbusActive = false;
#else // no CAN or Keypad
bomb();
#endif
}
#else // no CAN
#if !KEYPAD // no Keypad either - can't work!
bomb();
#endif
#endif
// set the mode for the shutdown status indicator
pinMode(SHUTDOWN, INPUT_PULLUP);
// set the mode for the LED status indicator output and sounder
pinMode(LED, OUTPUT);
pinMode(SOUNDER, OUTPUT);
digitalWrite(SOUNDER, HIGH);
// //initialize all timers except for 0, to save time keeping functions
InitTimersSafe();
// initialize timer5 which will be used to increment session timeout counters.
// interrupt should fire every 1/10 second
// noInterrupts(); // disable all interrupts
TCCR5A = 0;
TCCR5B = 0;
TCNT5 = TIMER_PRELOAD;
TCCR5B |= (1 << CS12); // 256 prescaler
TIMSK5 |= (1 << TOIE5); // enable timer overflow interrupt
#if OLED_DISPLAY || LCD_DISPLAY
initialiseDisplay();
#if OLED_DISPLAY
displayImage(mergLogo);
displayImage(bnhmrsLogo);
#endif
#if LCD_DISPLAY
displayMergLogo();
#endif
setupBarGraph();
displayVersion();
// start the speed display.
showSpeeds();
#else
// wait for any CANCABs to finish initialising themselves
delay(2000);
#endif
#if KEYPAD
// wire up keypad events
keyPad.addEventListener(keypadEvent); // Add an event listener for this keypad
#endif
interrupts(); // enable all interrupts
for (byte controllerIndex = 0; controllerIndex < NUM_CONTROLLERS; controllerIndex++)
{
controllers[controllerIndex].trainController.setPWMFrequency ();
}
#if DEBUG
if (cancmd_present == FALSE)
Serial.println(F("Standalone mode"));
Serial.println(F("CANCMDDC started."));
Serial.println(F("====================================="));
#endif
#if CANBUS
if (cbusActive == false)
{
#if !KEYPAD // no Keypad either - can't work!
bomb();
#endif
}
else
{
if (CAN0.errorCountTX() > 0)
{
#if DEBUG
Serial.println(F("CAN Tx error"));
#endif
#if KEYPAD
cbusActive = false;
nBeeps(2, 100); // two long beeps
#else // no Keypad either - can't work!
bomb();
#endif
}
else
{
attachInterrupt(digitalPinToInterrupt(CBUSINTPIN), receiveCBusMessage, FALLING);
}
}
#endif
}
/******************************************************************************************************
* Arduino loop routine. Essentially look for CBUS messages
* and dispatch them to the appropriate handlers
*/
void loop()
{
long unsigned int dcc_address;
byte long_address;
int controllerIndex;
if (digitalRead(SHUTDOWN) == 0) // power has been shutdown
{
if (!shutdownFlag)
{
#if DEBUG
Serial.print(F("Shutdown!!"));
#endif
flash_counter = 20;
#if OLED_DISPLAY || LCD_DISPLAY
display.backlight();
#endif
shutdownFlag = true;
emergencyStopAll();
#if CANBUS
sendTPower(0);
#endif
nBeeps(3, 1000);
delay(500);
}
}
else
{
if (shutdownFlag)
{
#if DEBUG
Serial.print(F("Power!!"));
#endif
shutdownFlag = false;
#if CANBUS
sendTPower(1);
#endif
nBeeps(3, 10);
delay(500);
}
}
#if CANBUS
#if LCD_DISPLAY || OLED_DISPLAY
if (bufferOverflow == true)
{
#if OLED_DISPLAY
display.setCursor(64, 24);
#else
display.setCursor(0, 3);
#endif
display.print(F("CAN Buffer Overflow!"));
bufferOverflow = false;
}
#else
flash_counter = 25;
#endif
if (cbusActive == true)
{
nextMessage = fifoMessageBuffer.getMessage();
#if DEBUG
// the CanId bytes are ppppHHHH LLLxxxxx where p = priority bit, x is unused, H & L are high and low bits
// see: https://www.merg.org.uk/forum/viewtopic.php?f=44&t=3018&p=24032&hilit=re+enumeration#p23986
Serial.print(F("Fifo CanId: "));
Serial.println(nextMessage.canId, HEX);
#endif
if (nextMessage.canId != 0xFFFFFFFF) // message rec'd
{
if (sid2byte(nextMessage.canId) == canId.id) // another node is using the same id, so force enum
{
flash_counter = 10;
sendRTR();
}
if (nextMessage.len == 0) // check to see whether enum-frame is received.
{
if (canId.enumerating)
{
#if DEBUG
Serial.print(F("CAN enum: ID "));
Serial.println(nextMessage.canId, HEX);
#endif
// add the can id to the list
canId.ids[canId.ptr] = sid2byte(nextMessage.canId);
canId.ptr++;
}
}
else // data is received.
{
#if DEBUG
Serial.print(F("CAN msg: ID "));
Serial.print(nextMessage.canId, HEX);
Serial.print(F(" OpCode:"));
for (int i = 0; i < nextMessage.len; i++) // Print each byte of the data
{
if (i == 1)
{
Serial.println();
Serial.print(F(" Data:"));
}
Serial.print(F(" "));
if (nextMessage.rxBuf[i] < 0x10) // If data byte is less than 0x10, add a leading zero
{
Serial.print(F("0"));
}
Serial.print(nextMessage.rxBuf[i], HEX);
}
Serial.println();
#endif
switch (nextMessage.rxBuf[0]) // Perform the action for the received message
{
// -------------------------------------------------------------------
case 0x07:
#if DEBUG
Serial.println(F("System Reset (Sent by CANCMD on power up)"));
#endif
cancmd_present = TRUE; // message came from CANCMD, so must be present
for (controllerIndex = 0; controllerIndex < NUM_CONTROLLERS; controllerIndex++)
{
// release all active controllers
if (controllers[controllerIndex].session >= SF_INACTIVE)
{
controllers[controllerIndex].trainController.emergencyStop(); // Emergency Stop
controllers[controllerIndex].session = SF_INACTIVE;
// update the speed display.
displaySpeed(controllerIndex);
}
// release all consists
controllers[controllerIndex].consist = { 0, false, false };
}
break;
// -------------------------------------------------------------------
case 0x08:
#if DEBUG
Serial.println(F("RTOFF - Request Track Off"));
#endif
stopAll(true);
break;
// -------------------------------------------------------------------
case 0x09:
#if DEBUG
Serial.println(F("RTOFF - Request Track On"));
#endif
break;
// -------------------------------------------------------------------
case 0x21:
#if DEBUG
Serial.println(F("REL - Release loco"));
#endif
releaseLoco(nextMessage.rxBuf[1]);
break;
// -------------------------------------------------------------------
case 0x22:
#if DEBUG
Serial.println(F("QLOC - Query loco"));
#endif
queryLoco(nextMessage.rxBuf[1]);
break;
// -------------------------------------------------------------------
case 0x23:
#if DEBUG
Serial.println(F("DKEEP - keep alive"));
#endif
keepaliveSession(nextMessage.rxBuf[1]);
break;
// -------------------------------------------------------------------
case 0x40:
#if DEBUG
Serial.println(F("RLOC - Request loco session"));
#endif
dcc_address = nextMessage.rxBuf[2] + ((nextMessage.rxBuf[1] & 0x3f) << 8);
long_address = (nextMessage.rxBuf[1] & SF_LONG);
#if DEBUG
Serial.print(F("Req Sess. "));
Serial.print(F(" Addr: "));
Serial.println(dcc_address);
#endif
if (long_address == 0)
{
#if DEBUG
Serial.print(F("Short Consist: "));
Serial.println(dcc_address);
#endif
consistRequest(dcc_address);
}
else
{
#if DEBUG
Serial.print(F("Long Controller: "));
Serial.println(getDCCIndex(dcc_address, long_address));
#endif
if (getDCCIndex(dcc_address, long_address) != SF_UNHANDLED)
{
locoRequest(dcc_address, long_address, 0);
}
}
break;
// -------------------------------------------------------------------
case 0x41:
#if DEBUG
Serial.println(F("Query Consist......."));
#endif
break;
// -------------------------------------------------------------------
case 0x44:
#if DEBUG
Serial.println(F("STMOD - Set speed steps"));
#endif
setSpeedSteps(nextMessage.rxBuf[1], nextMessage.rxBuf[2]);
break;
// -------------------------------------------------------------------
case 0x45:
#if DEBUG
Serial.println(F("PCON - Put loco in Consist"));
#endif
addSessionConsist(nextMessage.rxBuf[1], nextMessage.rxBuf[2]);
break;
// -------------------------------------------------------------------
case 0x46:
#if DEBUG
Serial.println(F("KCON - Remove loco from Consist"));
#endif
removeSessionConsist(nextMessage.rxBuf[1]);
break;
// -------------------------------------------------------------------
case 0x47:
#if DEBUG
Serial.println(F("DSPD - Set speed & direction"));
#endif
{
byte session = nextMessage.rxBuf[1];
byte requestedSpeed = nextMessage.rxBuf[2];
controllerIndex = getSessionIndex(session);
if (controllerIndex > SF_INACTIVE)
{
setSpeedAndDirection(controllerIndex, requestedSpeed, 0);
}
else
{
// session may be a consist, so apply speed to all controllers in it
for (int i = 0; i < NUM_CONTROLLERS; i++)
{
if (controllers[i].consist.session == session)
setSpeedAndDirection(i, requestedSpeed, controllers[i].consist.reverse ? 1 : 0);
}
}
// reset the timeout
keepaliveSession(session);
break;
}
// -------------------------------------------------------------------
case 0x61:
#if DEBUG
Serial.println(F("GLOC - Get loco session (Steal/Share)"));
#endif
dcc_address = nextMessage.rxBuf[2] + ((nextMessage.rxBuf[1] & 0x3f) << 8);
long_address = (nextMessage.rxBuf[1] & SF_LONG);
if (long_address == SF_LONG)
{
locoRequest(dcc_address, long_address, nextMessage.rxBuf[3]);
}
break;
// -------------------------------------------------------------------
case 0xE1:
{
#if DEBUG
Serial.println(F("PLOC - Allocate session from CANCMD"));
#endif
dcc_address = nextMessage.rxBuf[3] + ((nextMessage.rxBuf[2] & 0x3f) << 8);
long_address = (nextMessage.rxBuf[2] & SF_LONG);
#if DEBUG
Serial.print(F("PLOC from CANCMD. Addr: "));
Serial.println(dcc_address);
#endif
if (long_address == 0)
{
#if DEBUG
Serial.print(F("Short"));
#endif
// address may be of a consist, so apply change to all controllers in it
for (int i = 0; i < NUM_CONTROLLERS; i++)
{
if (controllers[i].consist.address == dcc_address)
{
controllers[controllerIndex].consist.session = nextMessage.rxBuf[1];
setSpeedAndDirection(i, nextMessage.rxBuf[4], controllers[i].consist.reverse ? 1 : 0);
}
}
}
else
{
#if DEBUG
Serial.print(F("Long"));
#endif
cancmd_present = TRUE; // message came from CANCMD, so must be present
// only interested in the addresses of our analogue outputs
for (controllerIndex = 0; controllerIndex < NUM_CONTROLLERS; controllerIndex++)
{
if (controllers[controllerIndex].DCCAddress == dcc_address && controllers[controllerIndex].longAddress == long_address)
{
controllers[controllerIndex].session = nextMessage.rxBuf[1];
setSpeedAndDirection(controllerIndex, nextMessage.rxBuf[4], 0);
}
}
}
break;
}
// -------------------------------------------------------------------
case 0x0A:
#if DEBUG
Serial.println(F("RESTP - STOP all"));
#endif
emergencyStopAll();
break;
// -------------------------------------------------------------------
default:
// ignore any other CBus messages
break;
}
}