There are no available options for this view.

Parent Directory Parent Directory | Revision Log Revision Log

Revision 1.18 - (show annotations) (download) (as text)
Tue Feb 27 02:38:31 2007 UTC (9 years, 10 months ago) by astronouth7303
Branch: MAIN
CVS Tags: HEAD
Changes since 1.17: +0 -0 lines
File MIME type: text/x-python
FILE REMOVED
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()