Renamed pyc.tty to pyc.pic
| 1 | """ |
| 2 | Interfaces with the RC. |
| 3 | |
| 4 | IFI's protocol is based on AN851 from Microchip. |
| 5 | """ |
| 6 | from serial import * |
| 7 | from sys import stdin, stdout, stderr |
| 8 | from time import sleep, time |
| 9 | from pyc.utils import * |
| 10 | import warnings |
| 11 | |
| 12 | __all__ = 'pic', 'cmdINIT', 'cmdREAD', 'cmdWRITE', 'cmdRESET', 'cmdERASE', \ |
| 13 | 'PacketParseError', 'BadPacketTypeError', 'BadReadError', 'BufferedPic', \ |
| 14 | 'BufferError', |
| 15 | |
| 16 | cmdINIT = 0x00 |
| 17 | cmdREAD = 0x01 |
| 18 | cmdWRITE = 0x02 |
| 19 | cmdRESET = 0x08 |
| 20 | cmdERASE = 0x09 |
| 21 | |
| 22 | VersionMask = 0xFFE0 |
| 23 | RevisionMask = 0x001F |
| 24 | |
| 25 | ## These are using the full word dev ID, with revision bits masked off (val & 0xFFE0) |
| 26 | ## IFI Loader in IFI_Loader.INI uses the dev ID with the revision bits shifted off (val >> 5) |
| 27 | Processors = { |
| 28 | ## 18Fxx20 |
| 29 | 0x0B20 : '18F6520', |
| 30 | 0x0660 : '18F6620', |
| 31 | 0x0620 : '18F6720', |
| 32 | 0x0B00 : '18F8520', |
| 33 | 0x0640 : '18F8620', |
| 34 | 0x0600 : '18F8720', |
| 35 | |
| 36 | ## 18F8722 family |
| 37 | 0x1340 : '18F6527', |
| 38 | 0x1380 : '18F6622', |
| 39 | 0x13C0 : '18F6627', |
| 40 | 0x1400 : '18F6722', |
| 41 | 0x1360 : '18F8527', |
| 42 | 0x13A0 : '18F8622', |
| 43 | 0x13E0 : '18F8627', |
| 44 | 0x1420 : '18F8722', |
| 45 | } |
| 46 | |
| 47 | def _checkCommand(buf): |
| 48 | try: |
| 49 | i = buf.index(0x04) |
| 50 | except ValueError: |
| 51 | i = len(buf) |
| 52 | except: |
| 53 | print >> stderr, `buf` |
| 54 | raise |
| 55 | return [n & 0xFF for n in buf[:i]] |
| 56 | |
| 57 | def _opentty(port, **nargs): |
| 58 | options = { 'baudrate': 115200, |
| 59 | 'bytesize': EIGHTBITS, |
| 60 | 'parity': PARITY_NONE, |
| 61 | 'stopbits': STOPBITS_ONE, |
| 62 | 'xonxoff': False, |
| 63 | 'rtscts': False, |
| 64 | 'dsrdtr': False, |
| 65 | 'timeout': 5, ## Seconds |
| 66 | } |
| 67 | options.update(nargs) |
| 68 | return Serial(port=port, **options)\ |
| 69 | |
| 70 | class PicException(Exception): |
| 71 | """Base type for all exceptions raised by this module.""" |
| 72 | raiseMe = False |
| 73 | data = None |
| 74 | def __init__(self, data=None): |
| 75 | self.data = data |
| 76 | def __str__(self): |
| 77 | return self.__doc__+"\n\t"+repr(self.data) |
| 78 | @classmethod |
| 79 | def mayRaise(cls, data): |
| 80 | if cls.raiseMe: |
| 81 | raise cls(data) |
| 82 | return True |
| 83 | |
| 84 | class PacketParseError(PicException): |
| 85 | """An invalid packet was found.""" |
| 86 | raiseMe = True |
| 87 | |
| 88 | class BadPacketTypeError(PicException): |
| 89 | """ An packet was recieved with an unexpected packet type.""" |
| 90 | raiseMe = True |
| 91 | |
| 92 | class BadReadError(PicException): |
| 93 | """The response data to a READ command was for the wrong location.""" |
| 94 | raiseMe = True |
| 95 | |
| 96 | class TimeOutError(PicException): |
| 97 | """Timed out waiting for a response.""" |
| 98 | raiseMe = False |
| 99 | |
| 100 | class VerifyError(PicException): |
| 101 | """Write verification failed.""" |
| 102 | raiseMe = True |
| 103 | |
| 104 | class pic(object): |
| 105 | """ |
| 106 | Interfaces the pic and handles downloading. |
| 107 | """ |
| 108 | __ser = None |
| 109 | readLog = None |
| 110 | sendLog = None |
| 111 | __version = None |
| 112 | __bootver = None |
| 113 | |
| 114 | __min_put = 8 |
| 115 | __max_put = 100 ## Arbitrary |
| 116 | MIN_WRITE = property((lambda self: self.__min_put), doc="""The minimum number of bytes that can be loaded onto the processor.""") |
| 117 | MAX_WRITE = property((lambda self: self.__max_put), doc="""The maximum number of bytes that can be loaded onto the processor.""") |
| 118 | |
| 119 | verify = False |
| 120 | |
| 121 | def __init__(self, port, flush = True, |
| 122 | log = 'debug.log', |
| 123 | sendLog = 'debug.log', |
| 124 | **nargs): |
| 125 | self.__ser = _opentty(port=port, **nargs) |
| 126 | self.readLog = self.sendLog = file(log, 'w+') |
| 127 | if sendLog != log: |
| 128 | self.sendLog = file(sendLog, 'w+') |
| 129 | if flush: |
| 130 | self.__ser.flushInput() |
| 131 | self.reset() |
| 132 | |
| 133 | def _command(self, cmdNum, data, time=None): |
| 134 | """Formats and sends a command. If time is given, waits that time and returns what is in the read |
| 135 | buffer.""" |
| 136 | cmd = self._formatCommand(cmdNum, data) |
| 137 | self._write(cmd) |
| 138 | print >> self.sendLog, "#>[" + list2hex(cmd) + "]" |
| 139 | self.sendLog.flush() |
| 140 | |
| 141 | if time is not None: |
| 142 | usleep(time) |
| 143 | buf = self._readResponse() |
| 144 | |
| 145 | return buf |
| 146 | else: |
| 147 | return True |
| 148 | |
| 149 | def close(self): |
| 150 | self.__ser.close() |
| 151 | self.__version = None |
| 152 | def open(self): |
| 153 | self.__ser.open() |
| 154 | self.__version = None |
| 155 | |
| 156 | def _read(self, size=None): |
| 157 | """Reads up to size data, returning the data read as a list.""" |
| 158 | #if self.__ser.timeout != 0: print >> stderr, "..." |
| 159 | buf = self.__ser.read(size) |
| 160 | if buf is not None: buf = [ord(x) for x in buf] |
| 161 | return buf |
| 162 | #read = _read |
| 163 | |
| 164 | def _readline(self, size=None, eol='\n'): |
| 165 | """Read a line which is terminated with end-of-line (eol) character |
| 166 | ('\\n' by default) or until timeout.""" |
| 167 | #if self.__ser.timeout != 0: print >> stderr, "..." |
| 168 | buf = self.__ser.readline(size, eol) |
| 169 | if buf is not None: buf = [ord(x) for x in buf] |
| 170 | return buf |
| 171 | #readline = _readline |
| 172 | |
| 173 | def reset(self): |
| 174 | self.__ser.close() |
| 175 | self.__ser.open() |
| 176 | |
| 177 | def _write(self, buf): |
| 178 | data = "".join([chr(c) for c in buf]) |
| 179 | self.__ser.write( data ) |
| 180 | #write = _write |
| 181 | |
| 182 | @staticmethod |
| 183 | def _formatCommand(cmdNumber, data): |
| 184 | """PRIVATE |
| 185 | Returns the sequence needed to be sent to the PIC for the given command and data.""" |
| 186 | rv = [0x0F, 0x0F] |
| 187 | rv += [cmdNumber & 0xFF] |
| 188 | isMeta = (lambda n: n == 0x04 or n == 0x05 or n == 0x0F) |
| 189 | for x in data: |
| 190 | x &= 0xFF |
| 191 | if isMeta(x): rv += [0x05] ## Escape special characters |
| 192 | rv += [x] |
| 193 | ## Hash is based on command and data, excluding escapes |
| 194 | hash = (~(sum(data) + cmdNumber) & 0xFF) + 1 |
| 195 | hash &= 0xFF |
| 196 | if isMeta(hash): rv += [0x05] |
| 197 | rv += [hash] |
| 198 | rv += [0x04] |
| 199 | return rv |
| 200 | |
| 201 | @staticmethod |
| 202 | def _formatRead(addr, length): |
| 203 | if not (0 <= length <= 255): |
| 204 | raise ValueError, "length must be a byte" |
| 205 | data = [length & 0xFF] |
| 206 | data += longToList_LE(addr & 0xFFFFFF, 3)[:3] |
| 207 | return {'cmdNum':cmdREAD, 'data':data} |
| 208 | |
| 209 | @staticmethod |
| 210 | def _formatWrite(addr, data): |
| 211 | rv = [0x02] |
| 212 | rv += longToList_LE(addr & 0xFFFFFF, 3)[:3] |
| 213 | for d in data: |
| 214 | rv += [d & 0xFF] |
| 215 | return {'cmdNum':cmdWRITE, 'data':rv} |
| 216 | |
| 217 | @staticmethod |
| 218 | def _parsePacket(buf): |
| 219 | """PRIVATE |
| 220 | Parses a packet. Returns a tuple containing the command and the data (or None if a parse error was supressed.)""" |
| 221 | if buf[:2] != [0x0F, 0x0F] or buf[-1] != 0x04: |
| 222 | if PacketParseError.mayRaise(buf): |
| 223 | print >> stderr, "Quietly killing parse" |
| 224 | return None |
| 225 | cmd = buf[2] |
| 226 | rdata = buf[3:-2] |
| 227 | hash = buf[-2] |
| 228 | data = [] |
| 229 | escaped = False |
| 230 | for d in rdata: |
| 231 | if d == 0x05 and not escaped: |
| 232 | escaped = True |
| 233 | continue |
| 234 | else: ## a little redundent |
| 235 | data += [d] |
| 236 | escaped = False |
| 237 | ## TODO: Add hash check |
| 238 | return (cmd, data) |
| 239 | |
| 240 | def _readResponse(self): |
| 241 | """PRIVATE |
| 242 | Reads a response from the serial port and returns the parsed packet.""" |
| 243 | ## This frames data and passes it to parsePacket() above |
| 244 | data = [] |
| 245 | done = False |
| 246 | while True: ## Not infinate, exits if 0x04 is found |
| 247 | sleep(0) |
| 248 | newStuff = len(data) |
| 249 | buf = self._readline(eol='\x04') |
| 250 | if buf is None or len(buf) == 0: ## Timed out |
| 251 | print >> stderr, "Time out" |
| 252 | return None |
| 253 | data += buf |
| 254 | try: |
| 255 | idx = data.index(0x04, newStuff) |
| 256 | except: pass |
| 257 | else: |
| 258 | if idx == 0: |
| 259 | print >> stderr, "idx == 0" |
| 260 | continue ## We should be more intelligent than this, but w/e |
| 261 | elif data[idx-1] == 0x05: |
| 262 | #print >> stderr, "escaped 04h" |
| 263 | continue ## Escaped |
| 264 | else: |
| 265 | print >> self.readLog, "#<[" + list2hex(data) + "]" |
| 266 | self.readLog.flush() |
| 267 | return self._parsePacket(data[:idx+1]) ## And lost data? |
| 268 | assert False, "Logic should never go here!" |
| 269 | |
| 270 | def initializePIC(self): |
| 271 | """Attempts to initialize PIC. Returns True if successful, False if not.""" |
| 272 | r = self._command(cmdINIT, [0x02], 0) |
| 273 | if r is not None: |
| 274 | try: |
| 275 | self.__bootver = listToLong_LE(r[2:]) |
| 276 | except: |
| 277 | print >> stderr, "initializePIC():r:", r |
| 278 | raise |
| 279 | return r is not None |
| 280 | |
| 281 | def readMem(self, location, length): |
| 282 | """Reads a location in memory, returning a sequence of bytes.""" |
| 283 | cmd = self._formatRead(location, length) |
| 284 | response = self._command(time=0, **cmd) |
| 285 | if response is None: return None |
| 286 | if response[0] != cmdREAD: |
| 287 | if BadPacketTypeError.mayRaise(response): |
| 288 | return None |
| 289 | data = response[1] |
| 290 | rSize = data[0] |
| 291 | rLoc = listToLong_LE(data[1:4]) |
| 292 | rData = data[4:] |
| 293 | if (rLoc != location or rSize != length or rSize != len(rData)): |
| 294 | print >> stderr, hex(rLoc), hex(location), rSize, length, len(rData) |
| 295 | BadReadError.mayRaise((response[0], list2hex(response[1]))) |
| 296 | return rData |
| 297 | |
| 298 | lastWrite = None |
| 299 | def writeMem(self, location, data): |
| 300 | """Writes data to a location in memory, returning True if successful""" |
| 301 | if not self.MIN_WRITE <= len(data) <= self.MAX_WRITE: |
| 302 | raise ValueError("May only load data of size between MIN_WRITE and MAX_WRITE") |
| 303 | if self.lastWrite == location: |
| 304 | import warnings |
| 305 | warnings.warn("Writing to the same location: %06X" % location) |
| 306 | cmd = self._formatWrite(location, data) |
| 307 | response = self._command(time=0, **cmd) |
| 308 | if response is None: |
| 309 | return False |
| 310 | if response[0] != cmdWRITE: |
| 311 | if BadPacketTypeError.mayRaise(response): |
| 312 | return False |
| 313 | if self.verify: |
| 314 | read = self.readMem(location, len(data)) |
| 315 | if read != data: |
| 316 | VerifyError.mayRaise((hex(location), "["+list2hex(data)+"]", "["+list2hex(read)+"]")) |
| 317 | return True |
| 318 | |
| 319 | def mayWriteMem(self, addr, data): |
| 320 | """ |
| 321 | Tests if writing the given data is allowed |
| 322 | """ |
| 323 | return self.MIN_WRITE <= len(data) <= self.MAX_WRITE |
| 324 | |
| 325 | def DeviceID(self): |
| 326 | """Returns the device ID.""" |
| 327 | if self.__version is None: |
| 328 | data = self.readMem(0x3FFFFE, 2) |
| 329 | self.__version = listToLong_LE(data) |
| 330 | return self.__version |
| 331 | # version = DeviceID |
| 332 | |
| 333 | def BootLoader(self): |
| 334 | return self.__bootver |
| 335 | |
| 336 | def UserID(self): |
| 337 | """Returns the configurable ID (addresses 200000h through 200007h).""" |
| 338 | return self.readMem(0x200000, 8) |
| 339 | |
| 340 | def erase(self): |
| 341 | """Erases PIC.""" |
| 342 | msgs = ( |
| 343 | ## Note that the IFI/Intellitek Loader sends all of these packets |
| 344 | [0xE0, 0x00, 0x08, 0x00, 0x00], ## Vex, part 1 |
| 345 | [0xFF, 0x00, 0x40, 0x00, 0x00], ## Vex, part 2 |
| 346 | [0x01, 0xC0, 0x7F, 0x00, 0x00], ## Vex, part 3 |
| 347 | [0x01, 0x00, 0x00, 0x20, 0x00], ## Vex, part 4 |
| 348 | [0x01, 0x00, 0x00, 0x30, 0x00], ## Vex, part 5 |
| 349 | [0x10, 0x00, 0x00, 0x00, 0x00], ## Vex, part 6 |
| 350 | |
| 351 | [0xE0, 0x00, 0x08, 0x00, 0x00], ## Original C, part 1 |
| 352 | [0xE0, 0x00, 0x40, 0x00, 0x00], ## Original C, part 2 |
| 353 | |
| 354 | [0x8A, 0x00, 0x08, 0x00, 0x00], ##JEB: EDU; PIC18F8520 |
| 355 | |
| 356 | [0xA2, 0x00, 0x02, 0x00, 0x00], ## Another EDU |
| 357 | |
| 358 | [0xE0, 0x00, 0x08, 0x00, 0x00], ## FRC 2007, part 1 |
| 359 | [0xC4, 0x00, 0x40, 0x00, 0x00], ## FRC 2007, part 2 |
| 360 | ) |
| 361 | for msg in msgs: |
| 362 | print >> stderr, "erase():", "trying", list2hex(msg)+"...", |
| 363 | resp = self._command(cmdERASE, msg, 0) |
| 364 | if resp is None: |
| 365 | print >> stderr, "\tFailed (No response)" |
| 366 | #print >> stderr, "erase():", `resp` |
| 367 | if resp[0] == cmdERASE and len(resp[1]) == 0: |
| 368 | print >> stderr, "\tSuccessful" |
| 369 | else: |
| 370 | print >> stderr, "\tFailed:", `resp` |
| 371 | else: |
| 372 | return False |
| 373 | |
| 374 | def resetPIC(self): |
| 375 | self._command(cmdRESET, [0x40]) ## No response |
| 376 | |
| 377 | class BufferError(PicException): |
| 378 | """There is still data to write in the buffer, but it can't be written.""" |
| 379 | raiseMe = True |
| 380 | |
| 381 | class BufferedPic(pic): |
| 382 | """ |
| 383 | Exactly the same as pic, but buffers writes (so that requirements of |
| 384 | size and such are met). |
| 385 | |
| 386 | Please note that because of the discontinuous nature of writing, exactly |
| 387 | when data is buffered is fairly complex. The auto-flush flag is followed on |
| 388 | a best-effort basis. |
| 389 | """ |
| 390 | __preferredSize = 0x10 |
| 391 | preferredSize = property( |
| 392 | (lambda s: s.__preferredSize), |
| 393 | (lambda s,v: setattr(s, "_BufferedPic__preferredSize", v)), ## Pre-munged |
| 394 | doc="""The preferred write size.""" |
| 395 | ) |
| 396 | __buffer_addr = None |
| 397 | __buffer_data = [] |
| 398 | __buffer_puts = 0 ## One of the data points we use for the heuistics |
| 399 | __may_put_stack = [True] |
| 400 | __may_put = True ## Also the end of __may_put_stack |
| 401 | |
| 402 | def formatBuffer(self): |
| 403 | return "%06X:[%s]" % (self.__buffer_addr, list2hex(self.__buffer_data)) |
| 404 | |
| 405 | def pushAutoFlush(self, value): |
| 406 | """bp.pushAutoFlush(bool) -> None |
| 407 | Adds a value to the "automatic flush" stack. |
| 408 | This enables/disables automatic flushing. |
| 409 | """ |
| 410 | self.__may_put_stack += [value] |
| 411 | self.__may_put = value |
| 412 | |
| 413 | def popAutoFlush(self): |
| 414 | """ |
| 415 | Restores the previous "automatic flush" state. |
| 416 | """ |
| 417 | if len(self.__may_put_stack) <= 1: |
| 418 | self.__may_put_stack = [True] |
| 419 | self.__may_put = True |
| 420 | else: |
| 421 | del self.__may_put_stack[-1] |
| 422 | self.__may_put = self.__may_put_stack[-1] |
| 423 | |
| 424 | def __writeBuffer(self, size): |
| 425 | """ |
| 426 | Writes `size` data from the buffer to the controller once. |
| 427 | Returns None (no data written), or whatever pic.writeMem() returns. |
| 428 | Will write as much as it can if size is too big. |
| 429 | |
| 430 | Manages the buffer, too. |
| 431 | """ |
| 432 | d = self.__buffer_data |
| 433 | a = self.__buffer_addr |
| 434 | if a is None: |
| 435 | return None |
| 436 | if len(d) < size: |
| 437 | size = len(d) |
| 438 | if size < self.MIN_WRITE: |
| 439 | return None |
| 440 | if size > self.MAX_WRITE: |
| 441 | size = self.MAX_WRITE |
| 442 | rv = super(BufferedPic, self).writeMem(a, d[:size]) |
| 443 | if rv: |
| 444 | del d[:size] |
| 445 | self.__buffer_addr += size |
| 446 | if len(d) <= 0: |
| 447 | self.__buffer_addr = None |
| 448 | self.__buffer_puts = 0 |
| 449 | return rv |
| 450 | |
| 451 | def __autoWriteSize(self): |
| 452 | """ |
| 453 | Performs auto-flushing logic. |
| 454 | Returns an integer to be passed to __writeBuffer(), or False for no |
| 455 | writing. |
| 456 | """ |
| 457 | if not self.__may_put: |
| 458 | return False |
| 459 | if len(self.__buffer_data) < self.MIN_WRITE: |
| 460 | return False |
| 461 | if len(self.__buffer_data) > self.MAX_WRITE: |
| 462 | return self.MAX_WRITE |
| 463 | if len(self.__buffer_data) > self.preferredSize: |
| 464 | return self.preferredSize |
| 465 | if self.__buffer_puts > 5: |
| 466 | return len(self.__buffer_data) - self.MIN_WRITE |
| 467 | return False |
| 468 | |
| 469 | def __mayAddToBuffer(self, addr, data): |
| 470 | if self.__buffer_addr is None: |
| 471 | return True |
| 472 | if self.__buffer_addr + len(self.__buffer_data) >= addr: |
| 473 | return True |
| 474 | if addr + len(data) >= self.__buffer_addr: |
| 475 | return True |
| 476 | return False |
| 477 | |
| 478 | def __addToBuffer(self, addr, data): |
| 479 | assert self.__mayAddToBuffer(addr, data), "Can't buffer that data" |
| 480 | self.__buffer_puts += 1 |
| 481 | if self.__buffer_addr is None: |
| 482 | self.__buffer_addr = addr |
| 483 | self.__buffer_data = list(data) |
| 484 | else: |
| 485 | offset = addr - self.__buffer_addr |
| 486 | end = offset + len(data) |
| 487 | if offset < 0: |
| 488 | ## Comes before the buffer, but overlaps it |
| 489 | ## So extend the buffer to fully contain it |
| 490 | before = abs(offset) |
| 491 | self.__buffer_data = [0]*before + self.__buffer_data |
| 492 | offset = 0 |
| 493 | end = len(data) |
| 494 | self.__buffer_data[offset:end] = list(data) |
| 495 | |
| 496 | def writeMem(self, location, data): |
| 497 | """ |
| 498 | Writes data to a location in memory, returning True if successful. |
| 499 | """ |
| 500 | realWrite = super(BufferedPic, self).writeMem |
| 501 | if not self.__mayAddToBuffer(location, data): |
| 502 | self.flush() ## Write data in the buffer, so it doesn't hang out |
| 503 | assert self.mayWriteMem(location, data), """Can't handle discontinuous buffering! """+self.formatBuffer() |
| 504 | return realWrite(location, data) |
| 505 | else: |
| 506 | self.__addToBuffer(location, data) |
| 507 | size = self.__autoWriteSize() |
| 508 | while size: |
| 509 | rv = self.__writeBuffer(size) |
| 510 | if not rv and rv is not None: return rv |
| 511 | if rv is None: break |
| 512 | size = self.__autoWriteSize() |
| 513 | return True |
| 514 | |
| 515 | def flush(self, size=None): |
| 516 | """bp.flush([int]) -> bool |
| 517 | Forces the write of the current buffer, if able. |
| 518 | Return True if the flush completed successfully and the buffer is |
| 519 | empty, False if not. |
| 520 | If size is specified, it will use that as the suggested chunk size. |
| 521 | """ |
| 522 | if size is None: |
| 523 | size = self.preferredSize |
| 524 | data = self.__buffer_data |
| 525 | while len(data) >= self.MIN_WRITE: |
| 526 | s = size ## The suggested size |
| 527 | if self.MIN_WRITE <= len(data) <= 2* self.MIN_WRITE: |
| 528 | ## If we're on our last write, and we'll have some left over |
| 529 | s = len(data) |
| 530 | if s > self.MAX_WRITE: |
| 531 | ## Fitting all the data in one go is too much, so split it into two chunks |
| 532 | s /= 2 |
| 533 | assert self.MIN_WRITE <= s <= self.MAX_WRITE, "MIN_WRITE and MAX_WRITE are too close to work with." |
| 534 | self.__writeBuffer(s) ## Do it twice. Really needed? |
| 535 | self.__writeBuffer(s) |
| 536 | |
| 537 | return self.bufferEmpty() |
| 538 | |
| 539 | def bufferEmpty(self): |
| 540 | return len(self.__buffer_data) == 0 |
| 541 | |
| 542 | def close(self): |
| 543 | if not self.flush(): |
| 544 | if BufferError.mayRaise(self.formatBuffer()): |
| 545 | print >> stderr, "Didn't write all the data:", self.formatBuffer() |
| 546 | super(BufferedPic, self).close() |
Copyright © 2010 Geeknet, Inc. All rights reserved. Terms of Use