-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathPIC16_Programmer.ino
More file actions
634 lines (597 loc) · 17.5 KB
/
PIC16_Programmer.ino
File metadata and controls
634 lines (597 loc) · 17.5 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
// ATMega328p docs: http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf
// PIC16F57 programming docs: http://ww1.microchip.com/downloads/en/DeviceDoc/41208C.pdf
// Design limits: 9600 baud transfer rate over pic serial protocol
// Meaning: clock speed of 9.6KHz.
// This gives us 1667 cycles (or 104us) between each clock edge
// to use for computation and whatever else.
//
// Special NOTE: PIC16F57 is a 12-bit core. The ICSP protocol dictates that we write a total of
// 16 bits per write command (a start bit, a stop bit, and 14 data bits). It should be noted
// that the 2 most-significant bits written are ignored by the chip (i.e. the last 2 bits).
//
// Similarly, the read command has a similar format. From our perspective, the 2 most significant
// bits returned are ignored values.
#define SERIAL_CLOCK_PORT 2
#define SERIAL_DATA_PORT 4
unsigned short input_data = 0;
// TODO: Need to make some adjustments to allow for full memory programming. Perhaps a stream-based approach is best here
// Max size to fit on PIC16F57:
// 0x000 : 0x7ff = 2048 words for address space
// 0x800 : 0x803 = 4 words for user ids
// 0xfff = 1 word for configuration
// ------------------------------------
// = 2053 total words
// = 2053 * 12 bits / word = 24,636 bits
// = 24,636 / 8 bits / byte = 3079 bytes minimally.
// = 2053 * 2 bytes (i.e. 16 bits/byte) = 4106 16-bit short types (with wasted high bytes) for simplest approach.
// Arduino uno has 2048 bytes of dynamic memory. As a result, storing full programs in memory is _not_ an option.
// Instead, we should have a streaming serial buffer (that communicates with arduino) when arduino needs more data.
unsigned short program_data[256] = { 0 };
unsigned validWords = 0;
unsigned writtenWords = 0;
// Max line length:
// 2 + 4 + 2 + 255*2 + 2 = 520
unsigned char program_line[520] = { 0 };
unsigned short lineBytes = 0;
short baseAddress = 0;
bool thold2 = false;
bool loaded = false;
bool bulkErased = false;
bool eraseDelay = false;
bool tprog = false;
bool tdis = false;
bool isDumping = false;
bool isProgramming = false;
bool start = false;
bool initialInc = false;
bool failed = false;
bool done = false;
// Upon entering programming mode, currentAddress == 0xfff based on docs.
short currentAddress = 0xfff;
unsigned long debounce = 0;
int currentStep = 0;
enum MODE {
WRITE,
BEGIN_PROG,
END_PROG,
INC_ADDR,
READ,
RESET_PC,
DUMP
};
// TODO: Write configuration word before increment.
MODE mode = WRITE;
inline void mclr(unsigned sig) {
if (sig == HIGH) {
analogWrite(13, 0);
} else {
digitalWrite(13, HIGH);
}
}
inline bool is_clock_low() {
return !(PORTD & _BV(SERIAL_CLOCK_PORT));
}
// True when bit flipped to high
inline bool clock_tick() {
if (!is_clock_low()) {
PORTD &= ~_BV(SERIAL_CLOCK_PORT);
return false;
}
PORTD |= _BV(SERIAL_CLOCK_PORT);
return true;
}
inline void clock_low() {
PORTD &= _BV(SERIAL_CLOCK_PORT);
}
inline void data_low() {
PORTD &= ~_BV(SERIAL_DATA_PORT);
}
inline void data_high() {
PORTD |= _BV(SERIAL_DATA_PORT);
}
inline void waitClockPeriod() {
delayMicroseconds(52);
}
void setup() {
// Enable pins 2 & 4 as output. Pin 2 is clock, pin 4 is data.
DDRD = 0x14;
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
Serial.begin(9600);
// Zero out program_data
for (unsigned i = 0 ; i < sizeof(program_data) / sizeof(short) ; ++i) {
program_data[i] = 0;
}
}
void loop() {
// mclr(HIGH);
// return;
if (failed | done) {
start = false;
}
if (!start && is_clock_low()) {
checkAndToggleStart();
PORTD &= ~(_BV(SERIAL_DATA_PORT) | _BV(SERIAL_CLOCK_PORT));
} else if (clock_tick()) {
// TODO: The control flow should be done here rather than in the global. The actions should
// return a bool as to whether or not their actions have completed.
if (!initialInc) {
incrementAddress();
// Serial.write(0x06);
// writeShort(currentAddress);
} else if (!bulkErased) {
bulkErase(); // Disable code protection
// exitProgrammingMode();
// enterProgrammingMode(); // Re-enter programming mode.
} else {
switch (mode) {
case WRITE:
doWrite();
break;
case BEGIN_PROG:
beginProgramming();
break;
case END_PROG:
endProgramming();
break;
case INC_ADDR:
// mode = WRITE;
incrementAddress();
break;
case READ:
doRead();
break;
case RESET_PC:
resetPC();
break;
case DUMP:
dumpData();
break;
}
}
} else {
if (tprog) {
tprog = false;
tdis = false;
thold2 = false;
// Ensure we're holding clock low if necessary
delay(2); // Min delay is 2ms
} else if (tdis) {
tdis = false;
thold2 = false;
tprog = false;
// Ensure we're holding clock low if necessary
delayMicroseconds(100); // minimum 100us delay required
} else if (thold2) {
// Mislabled. thold2 = tdly2. 1us delay.
thold2 = false;
delayMicroseconds(1);
} else if (eraseDelay) {
eraseDelay = false;
bulkErased = true;
// Ensure we're holding clock low if necessary
clock_low();
delay(10); // at least 10 ms delay for bulk erase
}
}
// Kill the remaining cycles to get to roughly 9.6KHz frequency
waitClockPeriod();
}
inline void parseLine() {
// Hex file is in intel hex format:
// https://en.wikipedia.org/wiki/Intel_HEX
unsigned char numBytes = (program_line[0] << 4) | program_line[1];
unsigned short address = ((program_line[2] << 12) | (program_line[3] << 8) | (program_line[4] << 4) | program_line[5]);
unsigned char recordType = (program_line[6] << 4) | program_line[7];
unsigned char checksum = (program_line[lineBytes - 2] << 4) | program_line[lineBytes - 1];
unsigned calculatedChecksum = numBytes + (address & 0xff) + ((address & 0xff00) >> 8) + recordType;
int idx = 0;
// for (unsigned int i = 0, iter = 0 ; i < 2 * numBytes ; i += 2, iter = ((iter + 1) % 3)) {
// // int idx = (i / 2);
// // Data bytes start at byte offset 8
// // Data word for PIC16F57 is 12-bytes.
// unsigned char topNybble = program_line[8 + i];
// unsigned char botNybble = program_line[8 + i + 1];
// // This is a bit painful... But we're processing here to make tx easier. This is a product of a 12-bit word:
// switch (iter) {
// case 0:
// program_data[idx] = (topNybble << 8) | (botNybble << 4);
// break;
// case 1:
// program_data[idx] |= topNybble;
// idx++;
// program_data[idx] = (botNybble << 8);
// break;
// case 2:
// program_data[idx] |= (topNybble << 4) | botNybble;
// idx++;
// break;
// }
// // Take the low 12 bits
// program_data[idx] &= 0xfff;
// calculatedChecksum += ((topNybble << 4) | botNybble) & 0xff;
// // Remember the number of valid bytes (based on the index)
// validWords = idx + 1;
// }
for (unsigned int i = 0, iter = 0 ; i < 2 * numBytes ; i += 4) {
// int idx = (i / 2);
int idx = i / 4;
// Data bytes start at byte offset 8
// Data word for PIC16F57 is 12-bytes.
// NOTE: Words are stored with LOWER BYTE FIRST.
unsigned char topNybble = program_line[8 + i];
unsigned char botNybble = program_line[8 + i + 1];
unsigned char upperTNybble = program_line[8 + i + 2];
unsigned char upperBNybble = program_line[8 + i + 3];
unsigned char lowerByte = (topNybble << 4) | botNybble;
unsigned char upperByte = (upperTNybble << 4) | upperBNybble;
// Take the low 12 bits
program_data[idx] = ((upperByte << 8) | lowerByte) & 0xfff;
calculatedChecksum += lowerByte + upperByte;
// Remember the number of valid bytes (based on the index)
validWords = idx + 1;
}
calculatedChecksum = ((~calculatedChecksum) + 1) & 0xff;
// TODO: Hack.
if ((address & 0xfff) == 0xffe) {
// Configuration word?
address = 0xfff;
}
baseAddress = address;
if (calculatedChecksum != checksum) {
Serial.write(0x04);
Serial.write(checksum);
Serial.write(calculatedChecksum);
failed = true;
}
if (recordType == 0x01) {
done = true;
Serial.write(0x02);
} else if (recordType == 0x04) {
// 12 bit core... So these records can simply be ignored since the data represents the upper
// 16 bits of an address line (we only have 12...)
} else {
writtenWords = 0;
start = true;
mode = WRITE;
if (!isProgramming) {
// TReset is 10ms. Make sure we wait.
delay(10);
enterProgrammingMode();
}
if (address == 0xfff) {
// Write config word. just re-enter programming mode to reset counter
exitProgrammingMode();
enterProgrammingMode();
initialInc = true;
}
}
}
inline void checkAndToggleStart() {
static bool hasResetAfterDone = false;
// NOTE: To start/stop send any signal over USB serial.
if (failed) {
if (Serial.available() > 0) {
Serial.write(0x03); // Failure byte.
while (Serial.available() > 0) {
Serial.read(); // Throw away buffer bytes
}
}
mclr(LOW);
return;
}
if (done) {
if (!hasResetAfterDone) {
mclr(LOW);
delay(20);
mclr(HIGH);
hasResetAfterDone = true;
}
return;
}
if (Serial.available() > 0) {
char inByte;
while (Serial.available() > 0) {
// It's possible the controlling process has sent many ready responses.
// Just in case, pop all of the consecutive 'R's off.
inByte = Serial.read();
if (inByte == 'R' && Serial.peek() == 'R') {
continue;
} else {
break;
}
}
// NOTE: Non-hex ASCII characters are ok for control signals (except :) since we're sending
// the lines as ASCII characters and converting to their numeric representation.
if (inByte == 'R') {
// Controller said it's ready to send, let controller know we're also ready to receive.
Serial.write(0x01);
isProgramming = false;
// exitProgrammingMode();
} else if (inByte == 'X') {
mode = DUMP;
enterProgrammingMode();
initialInc = bulkErased = start = true;
return;
} else if (inByte == ':') {
lineBytes = 0;
} else if (inByte == '\n' || inByte == '\r') {
parseLine();
} else if (inByte == ' ' || inByte == '\t') {
// Skip.
} else {
char newByte[2] = { 0 };
newByte[0] = inByte;
program_line[lineBytes] = strtol(newByte, NULL, 16);
lineBytes++;
}
if (inByte != 'R' && inByte != '\n' && inByte != '\r') {
// Echo the byte back for verification
Serial.write(inByte);
}
if (!start) {
if (!isProgramming) {
mclr(LOW);
}
} else {
if (!done && !failed && isProgramming) {
mclr(HIGH);
} else if (done) {
Serial.write(0x02); // Succes
start = false;
} else {
start = false;
}
}
} else if (!start && !isProgramming) {
mclr(LOW);
}
}
inline void enterProgrammingMode() {
// Drive clock and data low to re-enter programming mode
clock_low();
data_low();
// Drive MCLR low for 5 microseconds (transistor will disable)
mclr(LOW);
delayMicroseconds(5);
mclr(HIGH);
// Hold clock and data low for 5 microseconds
delayMicroseconds(5);
initialInc = false;
currentAddress = 0xfff;
isProgramming = true;
}
inline void bulkErase() {
DDRD |= 0x10;
if (currentStep == 0) {
data_high();
} else if (currentStep < 3) {
data_low();
} else if (currentStep == 3) {
data_high();
} else if (currentStep < 5) {
data_low();
} else {
eraseDelay = true;
currentStep = -1;
}
currentStep++;
}
inline void dumpData() {
static bool hasDumped = false;
static bool hasRead = false;
static short reportedAddress = 0;
// Everything has been dumped that's not code protected
if (hasDumped && currentAddress == 0x404) {
hasDumped = false;
mode = WRITE;
start = false;
isDumping = false;
Serial.write(0x08);
return;
}
isDumping = true;
// NOTE: We need to clean this code up... a lot...
// But currentStep relies on the artifact that incAddr will update.. doh.
if (!hasRead) {
doRead();
// Ick. Artifact :( write mode means it finished...
if (mode == WRITE) {
mode = DUMP;
hasRead = true;
hasDumped = true;
reportedAddress = currentAddress;
}
} else {
incrementAddress();
if (mode == WRITE) {
mode = DUMP;
hasRead = false;
clock_low(); // Hold clock low during transmission
// Send back to controller
Serial.write(0x07);
sendShort(reportedAddress);
sendShort(input_data);
}
}
}
// Write data to an address
inline void doWrite() {
// Make sure pin 4 is output
int currentIndex = writtenWords;
DDRD |= 0x10;
if (currentStep == 0) {
// Make sure our address is in range to be written, otherwise increment towards it.
// The final condition is to make sure we're writing the correct word.
if (currentAddress != ((baseAddress + writtenWords) % 0x1000)) {
if (writtenWords >= validWords) {
// We're done writing bits sent from this line
start = false;
return;
}
incrementAddress();
mode = INC_ADDR;
return;
}
data_low();
} else if (currentStep == 1) {
data_high();
} else if (currentStep < 4) {
data_low();
} else if (currentStep < 5) {
data_low(); // don't care value
} else if (currentStep == 5) {
data_low(); // don't care value
thold2 = true; // Hold clock signal low before start bit
} else if (currentStep == 6) {
// Start bit
data_low();
} else if (currentStep > 6 && currentStep < 21) {
// Data bits.
int checkBit = currentStep - 7;
if (program_data[currentIndex] & (0x01 << checkBit)) {
data_high();
} else {
data_low();
}
} else {
// Stop bit
data_low();
currentStep = -1;
mode = BEGIN_PROG;
}
currentStep++;
}
// Read data
inline void doRead() {
// Make sure pin 4 is in read mode
if (currentStep < 2) {
DDRD |= 0x10; // Writeable data
input_data = 0; // Reset input data
data_low();
} else if (currentStep == 2) {
DDRD |= 0x10;
data_high();
} else if (currentStep < 5) {
DDRD |= 0x10;
data_low();
} else if (currentStep == 5) {
DDRD |= 0x10;
data_low(); // don't care value
DDRD &= ~0x10;
thold2 = true; // Hold clock signal
} else if (currentStep > 5 && currentStep < 21) {
DDRD &= ~0x10; // Read pin 4
// Ignore stop/start bits
if (currentStep != 6 && currentStep != 20) {
int value = PIND & 0x10;
int checkBit = currentStep - 7;
if (value) {
input_data |= 0x01 << checkBit;
}
}
} else {
currentStep = -1;
mode = WRITE;
// Verify result - skip configuration word for now...
if (!isDumping && input_data != program_data[writtenWords]) {
writtenWords = 0;
failed = true;
Serial.write(0x05);
writeShort(currentAddress);
writeShort(baseAddress);
Serial.write((currentAddress - baseAddress) & 0xff);
writeShort(input_data);
writeShort(program_data[currentAddress - baseAddress]);
}
// Keep track of the fact that we have successfully written a word
writtenWords++;
}
currentStep++;
}
inline void writeShort(short value) {
Serial.write((value & 0xf000) >> 12);
Serial.write((value & 0x0f00) >> 8);
Serial.write((value & 0x00f0) >> 4);
Serial.write((value & 0x000f) >> 0);
}
// Enter programming mode
inline void beginProgramming() {
DDRD |= 0x10;
if (currentStep < 3) {
data_low();
} else if (currentStep == 3) {
data_high();
} else if (currentStep < 5) {
data_low(); // Don't care
} else if (currentStep == 5) {
data_low(); // don't care
tprog = true; // Hold clock cycle-- this time for tprog.
currentStep = -1;
mode = END_PROG;
}
currentStep++;
}
// End programming mode
inline void endProgramming() {
DDRD |= 0x10;
if (currentStep == 0) {
data_low();
} else if (currentStep < 5) {
data_high();
} else if (currentStep == 5) {
data_low(); // don't care
tdis = true; // Hold clock cycle-- this time for tprog.
currentStep = -1;
mode = READ;
}
currentStep++;
}
// Reset PC to base address
inline void resetPC() {
if (currentAddress == baseAddress) {
mode = WRITE;
doWrite();
} else {
incrementAddress();
mode = RESET_PC;
}
}
inline void exitProgrammingMode() {
mclr(LOW);
delay(10);
}
inline void sendShort(short value) {
writeShort(value);
}
// Increment PC address
inline void incrementAddress() {
DDRD |= 0x10;
if (currentStep == 0) {
data_low();
} else if (currentStep < 3) {
data_high();
} else if (currentStep < 5) {
data_low();
} else if (currentStep == 5) {
data_low(); // don't care
currentAddress++;
// Programmer for PIC57 which is a 12-bit core. Rollover at max address.
if (currentAddress > 0xfff) {
currentAddress = 0;
}
// TODO: Remove this limit.
// TODO: Should only program sections we know about.
// if (initialInc && currentAddress >= 0x400) {
// start = false;
// done = true;
// Serial.println("Programming completed successfully!");
// }
thold2 = true; // Hold clock cycle-- this time for tprog.
initialInc = true;
currentStep = -1;
// After inc, go to write mode
mode = WRITE;
}
currentStep++;
}