Browse Source

Major reorganization, main func. more complete

Now the code has been split into several files to keep things better
organized.  Chip definitions are in chips.py, exceptions are in
exceptions.py, and LPC and friends are in lpc.py.  __init__.py has a new
function that provides the best way to create an LPC object, handling
all the boring bits of control, synchronization, and changing the type.
Clara Hobbs 6 years ago
parent
commit
3bc23359fc
4 changed files with 548 additions and 346 deletions
  1. 45
    346
      alpaca_isp/__init__.py
  2. 100
    0
      alpaca_isp/chips.py
  3. 22
    0
      alpaca_isp/exceptions.py
  4. 381
    0
      alpaca_isp/lpc.py

+ 45
- 346
alpaca_isp/__init__.py View File

@@ -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()

+ 100
- 0
alpaca_isp/chips.py View File

@@ -0,0 +1,100 @@
1
+# Copyright 2017 Clayton G. Hobbs
2
+# 
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+# 
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+# 
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+"""Chips supported by Alpaca ISP"""
15
+
16
+from collections import namedtuple
17
+
18
+from alpaca_isp.lpc import LPC8xx
19
+
20
+
21
+class Chip(namedtuple("Chip", [
22
+        # Name of the chip
23
+        "name",
24
+        # Class that represents this chip
25
+        "family",
26
+        # Size of flash in KiB
27
+        "flash",
28
+        # Size of RAM in KiB
29
+        "ram",
30
+        # Address of the start of RAM
31
+        "ram_start",
32
+        # RAM location to which we can safely write
33
+        "ram_base",
34
+        # Maximum number of bytes we can copy
35
+        "max_copy",
36
+        # Sector table
37
+        "sectors"])):
38
+    __slots__ = ()
39
+
40
+    def sectors_used(self, segments):
41
+        """Returns a list of sectors used by the given memory segments
42
+
43
+        segments: a list of (start, end) tuples representing segments of used
44
+            memory
45
+        """
46
+        sector_segments = []
47
+        for i in range(len(self.sectors)):
48
+            sector_segments.append((sum(self.sectors[:i]),
49
+                sum(self.sectors[:i+1])))
50
+
51
+        s = set()
52
+        for dseg in segments:
53
+            for i, sseg in enumerate(sector_segments):
54
+                if ((dseg[0] >= sseg[0] and dseg[1] <= sseg[1])
55
+                        or (dseg[0] < sseg[0] and dseg[1] > sseg[1])
56
+                        or (sseg[0] <= dseg[0] < sseg[1])
57
+                        or (sseg[0] < dseg[1] <= sseg[1])):
58
+                    s.add(i)
59
+
60
+        return sorted(s)
61
+
62
+
63
+chips = {
64
+    0x00008221: Chip(
65
+        name="LPC822M101JHI33",
66
+        family=LPC8xx,
67
+        flash=16,
68
+        ram=4,
69
+        ram_start=0x10000000,
70
+        ram_base=0x10000300,
71
+        max_copy=1024,
72
+        sectors=(1024,)*16),
73
+    0x00008222: Chip(
74
+        name="LPC822M101JDH20",
75
+        family=LPC8xx,
76
+        flash=16,
77
+        ram=4,
78
+        ram_start=0x10000000,
79
+        ram_base=0x10000300,
80
+        max_copy=1024,
81
+        sectors=(1024,)*16),
82
+    0x00008241: Chip(
83
+        name="LPC824M201JHI33",
84
+        family=LPC8xx,
85
+        flash=32,
86
+        ram=8,
87
+        ram_start=0x10000000,
88
+        ram_base=0x10000300,
89
+        max_copy=1024,
90
+        sectors=(1024,)*32),
91
+    0x00008242: Chip(
92
+        name="LPC824M201JDH20",
93
+        family=LPC8xx,
94
+        flash=32,
95
+        ram=8,
96
+        ram_start=0x10000000,
97
+        ram_base=0x10000300,
98
+        max_copy=1024,
99
+        sectors=(1024,)*32)
100
+}

+ 22
- 0
alpaca_isp/exceptions.py View File

@@ -0,0 +1,22 @@
1
+# Copyright 2017 Clayton G. Hobbs
2
+# 
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+# 
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+# 
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+"""Exceptions for Alpaca ISP"""
15
+
16
+
17
+class ISPError(IOError):
18
+    """Generic error for ISP"""
19
+
20
+
21
+class RecvTimeout(ISPError):
22
+    """Timeout while receiving a command"""

+ 381
- 0
alpaca_isp/lpc.py View File

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

Loading…
Cancel
Save