In-system programming tool for LPC microcontrollers
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

__init__.py 13KB

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