|
@@ -17,342 +17,42 @@ __author__ = "Clayton G. Hobbs"
|
17
|
17
|
__version__ = "0"
|
18
|
18
|
|
19
|
19
|
import argparse
|
20
|
|
-import struct
|
21
|
20
|
import sys
|
22
|
|
-import time
|
23
|
|
-from enum import Enum
|
24
|
21
|
|
25
|
|
-import serial
|
|
22
|
+from alpaca_isp.chips import chips
|
|
23
|
+from alpaca_isp.exceptions import *
|
|
24
|
+from alpaca_isp.lpc import *
|
26
|
25
|
|
27
|
26
|
|
28
|
|
-_DEFAULT_BAUDRATE = 115200
|
29
|
|
-_DEFAULT_CLOCK = 12000
|
30
|
|
-_DEFAULT_TIMEOUT = 0.1
|
31
|
|
-
|
32
|
|
-
|
33
|
|
-class LPC:
|
34
|
|
- """Interface for LPC in-system programming"""
|
35
|
|
-
|
36
|
|
- def __init__(self, serport, baudrate=_DEFAULT_BAUDRATE,
|
37
|
|
- clock=_DEFAULT_CLOCK, timeout=_DEFAULT_TIMEOUT):
|
38
|
|
- self._serport = serport
|
39
|
|
- self._baudrate = baudrate
|
40
|
|
- self._clock = clock
|
41
|
|
- self._timeout = timeout
|
42
|
|
- self._echo = True
|
43
|
|
-
|
44
|
|
- def open(self):
|
45
|
|
- """Open the serial port to communicate with the microcontroller"""
|
46
|
|
- self._uart = serial.Serial(self._serport, baudrate=self._baudrate,
|
47
|
|
- timeout=self._timeout)
|
48
|
|
-
|
49
|
|
- def _readline(self):
|
50
|
|
- """Read a line terminated with b'\r\n'"""
|
51
|
|
- s = b""
|
52
|
|
- while True:
|
53
|
|
- c = self._uart.read(1)
|
54
|
|
- if not c:
|
55
|
|
- # If we timed out, give up
|
56
|
|
- raise RecvTimeout(s)
|
57
|
|
- s += c
|
58
|
|
- if s.endswith(b"\r\n"):
|
59
|
|
- return s
|
60
|
|
-
|
61
|
|
- def _writeline(self, line, plain=False):
|
62
|
|
- """Write a line to the microcontroller and read the echoed response
|
63
|
|
-
|
64
|
|
- If plain is True, the command is taken to be raw binary data.
|
65
|
|
- """
|
66
|
|
- self._uart.write(line)
|
67
|
|
- self._uart.flush()
|
68
|
|
-
|
69
|
|
- # If echo is disabled, don't try to read back what we sent
|
70
|
|
- if not self.echo:
|
71
|
|
- return
|
72
|
|
-
|
73
|
|
- # Read the response, raising the exception if there is one
|
74
|
|
- if plain:
|
75
|
|
- response = self._uart.read(len(line))
|
76
|
|
- else:
|
77
|
|
- response = self._readline()
|
78
|
|
- # If we got the wrong response, raise an exception
|
79
|
|
- if response != line:
|
80
|
|
- raise ISPError("Wrong text echoed: {}".format(response))
|
81
|
|
-
|
82
|
|
- def _send_command_raw(self, cmd):
|
83
|
|
- """Send a command to the microcontroller, returning bytes"""
|
84
|
|
- self._writeline(cmd)
|
85
|
|
- return self._readline()
|
86
|
|
-
|
87
|
|
- def _send_command(self, cmd):
|
88
|
|
- """Send a command to the microcontroller, returning the result"""
|
89
|
|
- r = self._send_command_raw(cmd)
|
90
|
|
- r = ReturnCode(int(r))
|
91
|
|
- if r != ReturnCode.CMD_SUCCESS:
|
92
|
|
- raise ISPError(r)
|
93
|
|
- return r
|
94
|
|
-
|
95
|
|
- def enter_isp(self, delay=0.01):
|
96
|
|
- """Enter ISP mode by controlling the DTR (/RST) and RTS (/ISP) lines
|
97
|
|
-
|
98
|
|
- This operation is performed synchronously, with delays.
|
99
|
|
- """
|
100
|
|
- self._uart.rts = True
|
101
|
|
- time.sleep(delay)
|
102
|
|
- self._uart.dtr = True
|
103
|
|
- time.sleep(delay)
|
104
|
|
- self._uart.dtr = False
|
105
|
|
- time.sleep(delay)
|
106
|
|
- self._uart.rts = False
|
107
|
|
-
|
108
|
|
- def synchronize(self, verbose=False, max_tries=None):
|
109
|
|
- """Begin communication with the microcontroller
|
110
|
|
-
|
111
|
|
- If verbose is True, prints a . for every synchronization attempt.
|
112
|
|
- If max_tries is an integer, attempt to synchronize at most that many
|
113
|
|
- times before failing by raising RecvTimeout.
|
114
|
|
- """
|
115
|
|
- # Synchronize with the MCU
|
116
|
|
- while True:
|
117
|
|
- # Send a ?
|
118
|
|
- self._uart.write(b"?")
|
119
|
|
- self._uart.flush()
|
120
|
|
- if verbose:
|
121
|
|
- print(".", end="", flush=True)
|
122
|
|
- # Receive a response
|
123
|
|
- try:
|
124
|
|
- s = self._readline()
|
125
|
|
- except RecvTimeout:
|
126
|
|
- if max_tries is not None:
|
127
|
|
- max_tries -= 1
|
128
|
|
- if max_tries <= 0:
|
129
|
|
- raise
|
130
|
|
- continue
|
131
|
|
- # If we got the right response, break
|
132
|
|
- if s == b"Synchronized\r\n":
|
133
|
|
- break
|
134
|
|
- # Tell the MCU we've synchronized
|
135
|
|
- s = self._send_command_raw(b"Synchronized\r\n")
|
136
|
|
- # Next, it should say OK, at which point we're done synchronizing
|
137
|
|
- if s != b"OK\r\n":
|
138
|
|
- raise ISPError("Wrong response during synchronization")
|
139
|
|
-
|
140
|
|
- # Send clock frequency in kHz
|
141
|
|
- s = self._send_command_raw("{:d}\r\n".format(self._clock).encode(
|
142
|
|
- "utf-8"))
|
143
|
|
- # Next, it should say OK
|
144
|
|
- if s != b"OK\r\n":
|
145
|
|
- raise ISPError("Wrong response during synchronization")
|
146
|
|
-
|
147
|
|
- def close(self):
|
148
|
|
- """Close the serial port"""
|
149
|
|
- self._uart.close()
|
150
|
|
-
|
151
|
|
- def unlock(self, code="23130"):
|
152
|
|
- """Unlock the flash write, erase, and go commands"""
|
153
|
|
- self._send_command("U {}\r\n".format(code).encode("utf-8"))
|
154
|
|
-
|
155
|
|
- @property
|
156
|
|
- def baudrate(self):
|
157
|
|
- """The baud rate used for communication"""
|
158
|
|
- return self._uart.baudrate
|
159
|
|
-
|
160
|
|
- @baudrate.setter
|
161
|
|
- def baudrate(self, br):
|
162
|
|
- self._send_command("B {} {}\r\n".format(br,
|
163
|
|
- self._uart.stopbits).encode("utf-8"))
|
164
|
|
- # Update the baud rate for our UART
|
165
|
|
- self._uart.baudrate = br
|
166
|
|
-
|
167
|
|
- @property
|
168
|
|
- def stopbits(self):
|
169
|
|
- """The number of stop bits used for communication"""
|
170
|
|
- return self._uart.stopbits
|
171
|
|
-
|
172
|
|
- @stopbits.setter
|
173
|
|
- def stopbits(self, sb):
|
174
|
|
- self._send_command("B {} {}\r\n".format(self._uart.baudrate,
|
175
|
|
- sb).encode("utf-8"))
|
176
|
|
- # Update the number of stop bits for our UART
|
177
|
|
- self._uart.stopbits = sb
|
178
|
|
-
|
179
|
|
- @property
|
180
|
|
- def echo(self):
|
181
|
|
- """Whether the microcontroller echoes characters back to the host"""
|
182
|
|
- return self._echo
|
183
|
|
-
|
184
|
|
- @echo.setter
|
185
|
|
- def echo(self, setting):
|
186
|
|
- setting = bool(setting)
|
187
|
|
- self._send_command("A {}\r\n".format(int(setting)).encode("utf-8"))
|
188
|
|
- self._echo = setting
|
189
|
|
-
|
190
|
|
- def write_ram(self, start, data, count=None):
|
191
|
|
- """Write count bytes from data to RAM at the given start address
|
192
|
|
-
|
193
|
|
- Start and count must be multiples of four. If count is not specified,
|
194
|
|
- len(data) is used.
|
195
|
|
- """
|
196
|
|
- # Get the length of the data we're writing
|
197
|
|
- if count is None:
|
198
|
|
- count = len(data)
|
199
|
|
- # Ask to write data
|
200
|
|
- self._send_command("W {} {}\r\n".format(start, count).encode("utf-8"))
|
201
|
|
- # Send the data
|
202
|
|
- # NOTE: this is right for LPC8xx chips, not others
|
203
|
|
- self._writeline(data[:count], plain=True)
|
204
|
|
- return
|
205
|
|
-
|
206
|
|
- def read_memory(self, start, count):
|
207
|
|
- """Read count bytes starting at the given address
|
208
|
|
-
|
209
|
|
- Start and count must be multiples of four.
|
210
|
|
- """
|
211
|
|
- self._send_command("R {} {}\r\n".format(start, count).encode("utf-8"))
|
212
|
|
- return self._uart.read(count)
|
213
|
|
-
|
214
|
|
- def prepare_write(self, start, end=None):
|
215
|
|
- """Prepare the the given flash sector(s) for write operations
|
216
|
|
-
|
217
|
|
- If end is not specified, only the start sector is prepared.
|
218
|
|
- """
|
219
|
|
- if end is None:
|
220
|
|
- end = start
|
221
|
|
- self._send_command("P {} {}\r\n".format(start, end).encode("utf-8"))
|
222
|
|
-
|
223
|
|
- def copy_ram_to_flash(self, flash, ram, count):
|
224
|
|
- """Copy count bytes from RAM to flash
|
225
|
|
-
|
226
|
|
- The flash address should be a 64 byte boundary. Count should be a
|
227
|
|
- power of two in [64, 1024].
|
228
|
|
- """
|
229
|
|
- self._send_command("C {} {} {}\r\n".format(flash, ram, count).encode(
|
230
|
|
- "utf-8"))
|
231
|
|
-
|
232
|
|
- def go(self, address=0, mode=b"T"):
|
233
|
|
- """Jump to the given address, in the given mode of execution
|
234
|
|
-
|
235
|
|
- Of course, this function generally causes the ISP command handler to
|
236
|
|
- stop running, so it is typically appropriate to follow this with a call
|
237
|
|
- to LPC.close.
|
238
|
|
- """
|
239
|
|
- self._writeline("G {} {}\r\n".format(address, mode).encode("utf-8"))
|
240
|
|
-
|
241
|
|
- def erase(self, start, end=None):
|
242
|
|
- """Erase the given flash sector(s)
|
243
|
|
-
|
244
|
|
- If end is not specified, only the start sector is erased.
|
245
|
|
- """
|
246
|
|
- if end is None:
|
247
|
|
- end = start
|
248
|
|
- self._send_command("E {} {}\r\n".format(start, end).encode("utf-8"))
|
249
|
|
-
|
250
|
|
- def blank_check(self, start, end=None):
|
251
|
|
- """Check if the given flash sectors are blank
|
252
|
|
-
|
253
|
|
- If end is not specified, only the start sector is checked.
|
254
|
|
-
|
255
|
|
- Returns None if the sector is blank, or a tuple containing the offset
|
256
|
|
- and value of the first non-blank word location if the sector is not
|
257
|
|
- blank. If CRP is enabled, the offset and value are always reported as
|
258
|
|
- zero.
|
259
|
|
- """
|
260
|
|
- if end is None:
|
261
|
|
- end = start
|
262
|
|
- try:
|
263
|
|
- self._send_command("I {} {}\r\n".format(start, end).encode(
|
264
|
|
- "utf-8"))
|
265
|
|
- except ISPError as e:
|
266
|
|
- # Return a tuple for SECTOR_NOT_BLANK
|
267
|
|
- if e.args[0] == ReturnCode.SECTOR_NOT_BLANK:
|
268
|
|
- offset = int(self._readline())
|
269
|
|
- value = int(self._readline())
|
270
|
|
- return (offset, value)
|
271
|
|
- raise
|
272
|
|
-
|
273
|
|
- @property
|
274
|
|
- def part_id(self):
|
275
|
|
- """The identification number for the part"""
|
276
|
|
- self._send_command(b"J\r\n")
|
277
|
|
- return int(self._readline())
|
278
|
|
-
|
279
|
|
- @property
|
280
|
|
- def boot_code_version(self):
|
281
|
|
- """The boot code version number (major, minor)"""
|
282
|
|
- self._send_command(b"K\r\n")
|
283
|
|
- major = int(self._readline())
|
284
|
|
- minor = int(self._readline())
|
285
|
|
- return (major, minor)
|
286
|
|
-
|
287
|
|
- def compare(self, addr1, addr2, count):
|
288
|
|
- """Compart count bytes starting from the two addresses
|
289
|
|
-
|
290
|
|
- Both addresses should be on word boundaries, and count should be a
|
291
|
|
- multiple of four.
|
292
|
|
-
|
293
|
|
- Returns None if the two blocks are equal, or the byte offset of the
|
294
|
|
- first mismatched word if they are not.
|
295
|
|
- """
|
296
|
|
- try:
|
297
|
|
- self._send_command("M {} {} {}\r\n".format(addr1, addr2,
|
298
|
|
- count).encode("utf-8"))
|
299
|
|
- except ISPError as e:
|
300
|
|
- # Return an offset for COMPARE_ERROR
|
301
|
|
- if e.args[0] == ReturnCode.COMPARE_ERROR:
|
302
|
|
- return int(self._readline())
|
303
|
|
- raise
|
304
|
|
-
|
305
|
|
- @property
|
306
|
|
- def uid(self):
|
307
|
|
- """The microcontroller's unique ID, as bytes"""
|
308
|
|
- self._send_command(b"N\r\n")
|
309
|
|
- words = []
|
310
|
|
- for _ in range(4):
|
311
|
|
- words.append(int(self._readline()))
|
312
|
|
- return struct.pack("<4I", *words)
|
313
|
|
-
|
314
|
|
- def read_crc32(self, start, count):
|
315
|
|
- """Compute the CRC checksum of a black of RAM or flash
|
316
|
|
-
|
317
|
|
- Start must be on a word boundary, and count must be a multiple of four.
|
318
|
|
- """
|
319
|
|
- self._send_command("S {} {}\r\n".format(start, count).encode("utf-8"))
|
320
|
|
- return int(self._readline())
|
321
|
|
-
|
322
|
|
-
|
323
|
|
-class ReturnCode(Enum):
|
324
|
|
- """LPC ISP return codes
|
325
|
|
-
|
326
|
|
- From UM10800, section 25.6.1.16.
|
327
|
|
- """
|
328
|
|
- CMD_SUCCESS = 0
|
329
|
|
- INVALID_COMMAND = 1
|
330
|
|
- SRC_ADDR_ERROR = 2
|
331
|
|
- DST_ADDR_ERROR = 3
|
332
|
|
- SRC_ADDR_NOT_MAPPED = 4
|
333
|
|
- DST_ADDR_NOT_MAPPED = 5
|
334
|
|
- COUNT_ERROR = 6
|
335
|
|
- INVALID_SECTOR = 7
|
336
|
|
- SECTOR_NOT_BLANK = 8
|
337
|
|
- SECTOR_NOT_PREPARED_FOR_WRITE_OPERATION = 9
|
338
|
|
- COMPARE_ERROR = 10
|
339
|
|
- BUSY = 11
|
340
|
|
- PARAM_ERROR = 12
|
341
|
|
- ADDR_ERROR = 13
|
342
|
|
- ADDR_NOT_MAPPED = 14
|
343
|
|
- CMD_LOCKED = 15
|
344
|
|
- INVALID_CODE = 16
|
345
|
|
- INVALID_BAUD_RATE = 17
|
346
|
|
- INVALID_STOP_BIT = 18
|
347
|
|
- CODE_READ_PROTECTION_ENABLED = 19
|
|
27
|
+def create_lpc(tty, baudrate=DEFAULT_BAUDRATE, clock=DEFAULT_CLOCK,
|
|
28
|
+ timeout=DEFAULT_TIMEOUT, control=False, try_sync=None,
|
|
29
|
+ verbose=False):
|
|
30
|
+ """Create an object of the appropriate LPC subclass for the chip"""
|
|
31
|
+ # Open the LPC
|
|
32
|
+ lpc = LPC(tty, baudrate=baudrate, clock=clock, timeout=timeout)
|
|
33
|
+ lpc.open()
|
348
|
34
|
|
|
35
|
+ # Enter ISP mode if we've been asked to
|
|
36
|
+ if control:
|
|
37
|
+ lpc.enter_isp()
|
349
|
38
|
|
350
|
|
-class ISPError(IOError):
|
351
|
|
- """Generic error for ISP"""
|
|
39
|
+ # Synchronize with the microcontroller
|
|
40
|
+ if verbose:
|
|
41
|
+ print("Synchronizing", end="", flush=True)
|
|
42
|
+ try:
|
|
43
|
+ lpc.synchronize(max_tries=try_sync, verbose=verbose)
|
|
44
|
+ except RecvTimeout:
|
|
45
|
+ lpc.close()
|
|
46
|
+ if verbose:
|
|
47
|
+ print(" failed")
|
|
48
|
+ sys.exit(1)
|
|
49
|
+ if verbose:
|
|
50
|
+ print()
|
352
|
51
|
|
|
52
|
+ # Turn the LPC object into the right type
|
|
53
|
+ chip = chips[lpc.part_id]
|
|
54
|
+ return chip.family(lpc, chip)
|
353
|
55
|
|
354
|
|
-class RecvTimeout(ISPError):
|
355
|
|
- """Timeout while receiving a command"""
|
356
|
56
|
|
357
|
57
|
|
358
|
58
|
def main():
|
|
@@ -365,13 +65,13 @@ def main():
|
365
|
65
|
parser.add_argument("tty", metavar="tty", type=str,
|
366
|
66
|
help="the tty to which the microcontroller is attached")
|
367
|
67
|
parser.add_argument("-b", "--baudrate", type=int,
|
368
|
|
- default=_DEFAULT_BAUDRATE,
|
|
68
|
+ default=DEFAULT_BAUDRATE,
|
369
|
69
|
help="baud rate used for communication (default: %(default)s)")
|
370
|
|
- parser.add_argument("-c", "--clock-khz", type=int, default=_DEFAULT_CLOCK,
|
|
70
|
+ parser.add_argument("-c", "--clock-khz", type=int, default=DEFAULT_CLOCK,
|
371
|
71
|
help="microcontroller's clock frequency in kHz "
|
372
|
72
|
"(default: %(default)s)")
|
373
|
73
|
parser.add_argument("-t", "--timeout", type=float,
|
374
|
|
- default=_DEFAULT_TIMEOUT,
|
|
74
|
+ default=DEFAULT_TIMEOUT,
|
375
|
75
|
help="timeout for reading data from the microcontroller in "
|
376
|
76
|
"seconds (default: %(default)s)")
|
377
|
77
|
parser.add_argument("-e", "--erase", action="store_true",
|
|
@@ -391,23 +91,22 @@ def main():
|
391
|
91
|
args = parser.parse_args()
|
392
|
92
|
|
393
|
93
|
# Open the LPC
|
394
|
|
- lpc = LPC(args.tty, baudrate=args.baudrate, clock=args.clock_khz,
|
395
|
|
- timeout=args.timeout)
|
396
|
|
- lpc.open()
|
|
94
|
+ lpc = create_lpc(args.tty, baudrate=args.baudrate, clock=args.clock_khz,
|
|
95
|
+ timeout=args.timeout, control=args.control, try_sync=args.try_sync,
|
|
96
|
+ verbose=True)
|
397
|
97
|
|
398
|
|
- # Enter ISP mode if we've been asked to do so
|
399
|
|
- if args.control:
|
400
|
|
- lpc.enter_isp()
|
|
98
|
+ # Unlock the chip
|
|
99
|
+ lpc.unlock()
|
401
|
100
|
|
402
|
|
- # Synchronize with the microcontroller
|
403
|
|
- print("Synchronizing", end="", flush=True)
|
404
|
|
- try:
|
405
|
|
- lpc.synchronize(max_tries=args.try_sync, verbose=True)
|
406
|
|
- except RecvTimeout:
|
407
|
|
- print(" failed")
|
408
|
|
- sys.exit(1)
|
409
|
|
- print()
|
|
101
|
+ # Erase the flash if we've been asked to
|
|
102
|
+ if args.erase:
|
|
103
|
+ lpc.prepare_write()
|
|
104
|
+ lpc.erase()
|
|
105
|
+
|
|
106
|
+ # TODO: write the file to flash
|
410
|
107
|
|
411
|
|
- # TODO: all the interesting stuff
|
|
108
|
+ # Start the program if we haven't been asked not to
|
|
109
|
+ if not args.no_start:
|
|
110
|
+ lpc.go()
|
412
|
111
|
|
413
|
112
|
lpc.close()
|