-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathlink.cpp
781 lines (603 loc) · 29.7 KB
/
link.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
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
//=============================================================================
// This class handles communications with the master system. This particular
// implementation uses a set of shared I/O lines. Three lines are dedicated
// handshake lines, and eight are bi-directional data lines. This isn't a
// sophisticated protocol at all, and can be improved. Note that this was
// the second piece of code, after the main loop, that was written, so has the
// oldest, least sophisticated, least-RAM using, and therefore most prime for
// improvements code.
//
// This is the only class that knows anything about the low level transport
// mechanism, everything else deals with Event objects.
//
// This class also maintains the list of free Events. This should probably
// be moved into another class, but works fine here for this simplistic
// system.
//
// August 2014 - Bob Applegate, bob@corshamtech.com
//
//=============================================================================
// Ports and how they're used. Note that we operate on whole ports for some
// things, bits for others.
//
// This is a very basic communocation protocol using three control bits and
// eight data bits.
//
// DIRECTION - High if master controls the bus, low if we do.
// STROBE - From the master. Indicates either data is available if host
// is sending, or ACK if we're sending.
// ACK - To the master. This is our ACK when the master is sending us
// data, or a strobe to the host when we're sending data.
//
// This is a very basic mapping of ports between the processors
//
// 653x 6821 Arduino Use
// d0 pd0 rx
// d1 pd1 tx
// B0 d2 pd2 DIRECTION
// B1 d3 pd3 STROBE from 6821
// B2 d4 pd4 ACK to 6821
// d5 pd5
// A6 d6 pd6 DATA 6
// A7 d7 pd7 DATA 7
// d8 pb0
// d9 pb1
// d10 pb2 Select for I2C?
// d11 pb3 mosi
// d12 pb4 miso
// d13 pb5 sck
// A0 a0 pc0 DATA 0
// A1 a1 pc1 DATA 1
// A2 a2 pc2 DATA 2
// A3 a3 pc3 DATA 3
// A4 a4 pc4 DATA 4
// A5 a5 pc5 DATA 5
#include "link.h"
#include <Arduino.h>
// Various debug options. These should all be left as undefined or
// else performance will suffer.
#undef DEBUG_LINK_RAW
extern unsigned getSectorSize(byte code);
#define PROTOCOL_VERSION 1
extern bool debounceInputPin(int pin);
//=============================================================================
// Define all the ports in symbolic terms so it'll be easy to move ports/pins
// in the future.
#define LOWER_WRITE PORTC
#define LOWER_READ PINC
#define LOWER_DDR DDRC
#define LOWER_MASK 0xff
//#define LOWER_MASK 0x3f
#define UPPER_WRITE PORTD
#define UPPER_READ PIND
#define UPPER_DDR DDRD
#define UPPER_MASK 0xc0
#define DIRECTION 47
#define STROBE 48
#define ACK 49
// The possible states for the inbound state machine:
typedef enum
{
STATE_CMD = 1, // waiting for a command
STATE_WAIT_NULL, // get bytes until a NULL
STATE_GET_ONE, // get exactly one byte of data
STATE_GET_TWO,
STATE_GET_THREE,
STATE_GET_FOUR,
STATE_GET_FIVE,
STATE_GET_SIX,
STATE_GET_DRV_NUMBER_TO_MOUNT,
STATE_GET_DRV_NAME, // get drive number
STATE_APPEND_SECTOR, // add sector data to end
STATE_GET_LENGTH,
} STATE;
//=============================================================================
// Constructor. This does basically nothing, as the hardware gets set up in
// another method.
Link::Link(void)
{
}
//=============================================================================
// This does the start-up work and must be called before any other methods in
// this class.
void Link::begin(void)
{
hasEvent = false;
// Set the ACK to output, DIRECTION and STROBE to input
digitalWrite(ACK, LOW);
pinMode(DIRECTION, INPUT);
pinMode(STROBE, INPUT);
pinMode(ACK, OUTPUT);
// The slave always starts in READ mode...
prepareRead();
Serial.println("LINK is initialized");
// Make sure we've got an event
freeEvent = new Event();
}
//=============================================================================
Link::~Link(void)
{
Serial.println("LINK destructor called");
}
//=============================================================================
// This is the polling function. It looks or data from the master, assembles
// the data into messages and sets the hasEvent flag if a complete message has
// arrived.
//
// Returns true if there is an event waiting to be processed, false it not.
bool Link::poll(void)
{
word data;
// Strobe goes high if the host has put data on the data pins. Something
// to consider for a future fix is a timeout here. If the STROBE line is
// floating then the code might get stuck here forever waiting for a byte
// to arrive.
if (debounceInputPin(STROBE))
{
// There is a strobe, so get the byte from the host and
// then send it to the state machine for processing.
data = readByte();
//Serial.print("Got byte: ");
//Serial.println((byte)data, HEX);
// Let the state machine process the byte of data.
stateMachine(data);
}
return hasEvent;
}
//=============================================================================
// This sets the data bits to an input state in preparation for reading data
// from the master.
void Link::prepareRead(void)
{
LOWER_DDR = (~LOWER_MASK) & 0xff;
}
//=============================================================================
// This sets the data bits to the output state in preparation for writing data
// to the host. This will not set the bits to drive unless the host has
// indicated it has turned off its drivers and is in input mode.
void Link::prepareWrite(void)
{
// Before setting the data bits to output, make sure the other
// side has indicating it's in read mode or else we might have
// both drivers fighting each other.
while (debounceInputPin(DIRECTION))
;
LOWER_DDR = LOWER_MASK;
}
//=============================================================================
// Write a single byte to the master. Assumes prepareForWrite() has been
// called already. This does all the necessary handshaking.
void Link::writeByte(byte data)
{
#ifdef DEBUG_LINK_RAW
Serial.print("Link writeByte: ");
Serial.println(data, HEX);
#endif // DEBUG_LINK_RAW
// Put the byte onto the data port
LOWER_WRITE = data;
// raise ACK to indicate data is present, then wait for
// strobe to go high
digitalWrite(ACK, HIGH);
while (debounceInputPin(STROBE) == LOW)
;
digitalWrite(ACK, LOW);
while (debounceInputPin(STROBE) == HIGH);
;
}
//=============================================================================
// This gets a single byte from the master using all the proper handshaking.
byte Link::readByte(void)
{
byte data;
// Wait for STROBE to go high, indicating a byte is ready.
while (debounceInputPin(STROBE) == LOW)
;
// Data is available, so grab it right away, then ACK it.
data = LOWER_READ;
digitalWrite(ACK, HIGH);
// Wait for host to lower strobe
while (debounceInputPin(STROBE))
;
// Lower ACK and we're done.
digitalWrite(ACK, LOW);
#ifdef DEBUG_LINK_RAW
Serial.print("Link readByte: ");
Serial.println(data, HEX);
#endif // DEBUG_LINK_RAW
return data;
}
//=============================================================================
// This is used to get the next event waiting, or NULL if there is none.
Event *Link::getEvent(void)
{
Event *eptr = event; // temp save of event
event = NULL; // indicate no more waiting
hasEvent = false; // so its clear there are no more
return eptr;
}
//=============================================================================
// This is the state machine for incoming messages from the master. This is
// given one token at a time. It can set event to point to a new event, and
// will set hasEvent when an event is ready to be read.
void Link::stateMachine(word token)
{
static STATE state = STATE_CMD;
bool transactionDone = false; // set true if this is end of transaction
static unsigned int count;
//Serial.println((byte)token, HEX);
switch (state)
{
case STATE_CMD:
// This is a command byte.
uInt->sendEvent(UI_TRANSACTION_START);
switch (token)
{
case PROTO_VERSION:
prepareWrite();
writeByte(PROTO_VERSION); // reply
writeByte(PROTOCOL_VERSION);
prepareRead();
transactionDone = true;
break;
case PROTO_PING: // ping request
// Pings are handled immediately,
// not sent to the main loop.
Serial.println("Got PING");
prepareWrite();
writeByte(PROTO_PONG); // reply
prepareRead();
transactionDone = true;
break;
#if 0
case PROTO_LED_ON: // turn on LED
event = getAnEvent();
event->clean(EVT_LED_ON);
hasEvent = true;
transactionDone = true;
break;
case PROTO_LED_OFF: // turn off LED
event = getAnEvent();
event->clean(EVT_LED_OFF);
hasEvent = true;
transactionDone = true;
break;
#endif
case PROTO_READ_FILE:
event = getAnEvent();
event->clean(EVT_TYPE_FILE);
state = STATE_WAIT_NULL;
hasEvent = false;
break;
case PROTO_READ_BYTES:
event = getAnEvent();
event->clean(EVT_SEND_DATA);
state = STATE_GET_ONE;
hasEvent = false;
break;
case PROTO_GET_DIR:
event = getAnEvent();
event->clean(EVT_GET_DIRECTORY);
hasEvent = true;
break;
case PROTO_MOUNT: // mount a drive
Serial.println("Got a MOUNT");
// Next is the drive number, then the read-only
// flag and then the filename to mount
event = getAnEvent();
event->clean(EVT_MOUNT);
hasEvent = false;
state = STATE_GET_DRV_NUMBER_TO_MOUNT;
break;
case PROTO_UNMOUNT: // unmount a drive
Serial.println("Got an UNMOUNT");
event = getAnEvent();
event->clean(EVT_UNMOUNT);
hasEvent = false;
state = STATE_GET_ONE; // add the drive
break;
case PROTO_READ_SECTOR:
// This is followed by five more bytes:
// (1) Drive (zero based)
// (2) Sector size (1 = 128, 2 = 256, 3 = 512, 4 = 1024)
// (3) Track (zero based)
// (4) Sector (zero based)
// (5) Number of sectors per track, one based
event = getAnEvent();
event->clean(EVT_READ_SECTOR);
state = STATE_GET_FIVE;
break;
case PROTO_READ_SECTOR_LONG:
// This is followed by five more bytes:
// (1) Drive (zero based)
// (2) Sector size (1 = 128, 2 = 256, 3 = 512, 4 = 1024)
// (3) Sector # MSB - zero based
// (4) Sector #
// (5) Sector #
// (6) Sector # LSB
event = getAnEvent();
event->clean(EVT_READ_SECTOR_LONG);
state = STATE_GET_SIX;
break;
case PROTO_WRITE_SECTOR:
// This is followed by five more bytes:
// (1) Drive (zero based)
// (2) Sector size (1 = 128, 2 = 256, 3 = 512, 4 = 1024)
// (3) Track (zero based)
// (4) Sector (zero based)
// (5) Number of sectors per track, one based
//
// ...and then the sector data
event = getAnEvent();
event->clean(EVT_WRITE_SECTOR);
state = STATE_GET_FIVE;
count = 0; // no bytes received yet
break;
case PROTO_WRITE_SECTOR_LONG:
// This is followed by five more bytes:
// (1) Drive (zero based)
// (2) Sector size (1 = 128, 2 = 256, 3 = 512, 4 = 1024)
// (3) Sector # MSB - zero based
// (4) Sector #
// (5) Sector #
// (6) Sector # LSB
//
// ...and then the sector data
event = getAnEvent();
event->clean(EVT_WRITE_SECTOR_LONG);
state = STATE_GET_SIX;
count = 0; // no bytes received yet
break;
case PROTO_DONE: // Also PROTO_ABORT
transactionDone = true;
event = getAnEvent();
event->clean(EVT_DONE);
break;
case PROTO_GET_STATUS:
event = getAnEvent();
event->clean(EVT_GET_STATUS);
state = STATE_GET_ONE;
break;
case PROTO_GET_VERSION:
event = getAnEvent();
event->clean(EVT_GET_VERSION);
hasEvent = true;
break;
case PROTO_GET_MOUNTED_LIST:
event = getAnEvent();
event->clean(EVT_GET_MOUNTED);
hasEvent = true;
break;
case PROTO_GET_CLOCK:
event = getAnEvent();
event->clean(EVT_GET_CLOCK);
hasEvent = true;
break;
case PROTO_SET_CLOCK:
event = getAnEvent();
event->clean(EVT_SET_CLOCK);
count = 8;
state = STATE_APPEND_SECTOR;
break;
case PROTO_WRITE_FILE:
event = getAnEvent();
event->clean(EVT_WRITE_FILE);
state = STATE_WAIT_NULL;
hasEvent = false;
break;
case PROTO_WRITE_BYTES:
event = getAnEvent();
event->clean(EVT_WRITE_BYTES);
state = STATE_GET_LENGTH;
hasEvent = false;
break;
case PROTO_SAVE_CONFIG:
event = getAnEvent();
event->clean(EVT_SAVE_CONFIG);
hasEvent = true;
break;
case PROTO_SET_TIMER:
event = getAnEvent();
event->clean(EVT_SET_TIMER);
state = STATE_GET_ONE;
break;
default:
Serial.print("Got unknown command code: ");
Serial.println((byte)token, HEX);
transactionDone = true;
}
break;
case STATE_WAIT_NULL:
// This keeps adding bytes until a 0x00 is seen, then
// it sends the message and returns to the main state.
event->addByte(token); // always add it, even if null
if (token == 0x00) // if null, end of the data
{
state = STATE_CMD;
hasEvent = true;
}
break;
case STATE_GET_SIX:
event->addByte(token);
state = STATE_GET_FIVE;
break;
case STATE_GET_FIVE:
event->addByte(token);
state = STATE_GET_FOUR;
break;
case STATE_GET_FOUR:
// Adds this byte, then moves to get three more.
event->addByte(token);
state = STATE_GET_THREE;
break;
case STATE_GET_THREE:
event->addByte(token);
state = STATE_GET_TWO;
break;
case STATE_GET_TWO:
event->addByte(token);
state = STATE_GET_ONE;
break;
case STATE_GET_ONE:
// This gets one more byte, sends the message, then
// returns to the main state.
event->addByte(token);
// Some event types need special processing to get
// additional bytes. This logic handles those cases.
if (event->getType() == EVT_WRITE_SECTOR || event->getType() == EVT_WRITE_SECTOR_LONG)
{
// Get the whole sector's worth of data
count = 256; // need to calculate from message
state = STATE_APPEND_SECTOR;
}
else
{
state = STATE_CMD;
hasEvent = true;
}
break;
case STATE_GET_DRV_NUMBER_TO_MOUNT:
// This is the drive number they want to mount
event->addByte(token);
state = STATE_GET_DRV_NAME;
break;
case STATE_GET_DRV_NAME:
// This is the read-only flag for the drive they want to mount,
// so add it to the message, then get the filename.
event->addByte(token);
state = STATE_WAIT_NULL;
break;
case STATE_APPEND_SECTOR:
// A sector's worth of data is next.
event->addByte(token);
if (--count == 0)
{
state = STATE_CMD;
hasEvent = true;
}
break;
case STATE_GET_LENGTH:
// This is data for an open write file. This byte is the length
// and is either 1-255 for 1-255 bytes to follow, or 0 to indicate
// 256 bytes follow. Set up the count but put the original value
// into the event.
count = (token == 0 ? 256 : token);
event->addByte(token);
state = STATE_APPEND_SECTOR;
break;
}
// If this is the end of a transaction, indicate it on the UI.
if (transactionDone)
{
uInt->sendEvent(UI_TRANSACTION_STOP);
}
}
//=============================================================================
// Send a message back to the host. The message has to be reformatted to the
// line side protocol. The event is freed after being sent. It is safe to
// assume this blocks until the event has been sent.
void Link::sendEvent(Event *eptr)
{
byte *bptr;
prepareWrite(); // get ready to write and for host to read
switch (eptr->getType())
{
case EVT_ACK:
writeByte(PROTO_ACK);
break;
case EVT_NAK:
// A NAK is always followed by a single byte
// reason code, so send the NAK and then the
// reason byte.
writeByte(PROTO_NAK); // NAK
bptr = eptr->getData();
writeByte(*bptr); // reason code
break;
case EVT_FILE_DATA:
{
// This is a request to send a message back to the host. The
// data buffer contains a byte count (one byte, 0-255) followed
// by that number of bytes to send to the host. Note that the
// length can be zero, indicating end of file.
writeByte(PROTO_FILE_DATA); // send the command
byte *dptr= eptr->getData(); // pointer to the data
byte msgLength = *dptr++; // number of bytes to send
writeByte(msgLength); // length of data to follow
while (msgLength--)
{
writeByte(*dptr++);
}
break;
}
case EVT_DIR_INFO:
{
writeByte(PROTO_DIR);
byte *dptr= eptr->getData(); // pointer to the data
do
{
writeByte(*dptr);
}
while (*dptr++);
break;
}
case EVT_DIR_END:
writeByte(PROTO_DIR_END);
break;
case EVT_READ_SECTOR:
{
// Sector data heading back to host. Always 256 bytes.
writeByte(PROTO_SECTOR_DATA);
byte *dptr = eptr->getData();
unsigned int size = getSectorSize(*dptr++);
while (size--)
{
writeByte(*dptr++);
}
break;
}
case EVT_DISK_STATUS:
{
writeByte(PROTO_STATUS);
byte *dptr = eptr->getData();
writeByte(*dptr++);
break;
}
case EVT_MOUNTED:
{
writeByte(PROTO_MOUNT_INFO);
byte *dptr = eptr->getData();
writeByte(*dptr++); // drive number
writeByte(*dptr++); // read-only flag
while (*dptr)
{
writeByte(*dptr++);
}
writeByte(0);
break;
}
case EVT_CLOCK_DATA:
writeByte(PROTO_CLOCK_DATA);
byte *dptr = eptr->getData();
for (int i = 0; i < 8; i++)
{
writeByte(*dptr++);
}
break;
}
prepareRead(); // back to read mode
uInt->sendEvent(UI_TRANSACTION_STOP);
freeAnEvent(eptr); // all done with event
}
//=============================================================================
// Rather than constantly freeing and new'ing Events, maintain a set of free
// ones and just ask for a new one. This is called to get one, or NULL if
// none is available.
Event *Link::getAnEvent(void)
{
return freeEvent;
}
//=============================================================================
// When done using an Event, use this function to free it for future use.
void Link::freeAnEvent(Event *eptr)
{
freeEvent = eptr;
}