Search This Blog

Sunday, November 21, 2010

BootLoader - Python Code for PC

Still rough looking.  Could use a lot of clean up, optimization, etc.

#/usr/bin/python2.6

from time import *
from uspp import uspp
import sys

class Programmer:
    tty = None
    prog = None
    file = None
    filename = None
    portname = None
    debug = False
    ok = 'k'
    failed = 'n'
    dataStart = 9
    rewriteAddrH = '0F'         # each must be 2 characters
    rewriteAddrL = 'FC'
    startSignal = chr(0xc1)
  
    def __init__(self, args ):
        self.prog = args[0]
        if self.prog.rfind( '/' ) >= 0:
            self.prog = self.prog[self.prog.rfind('/')+1:]
        if len(args) < 3:
            print self.prog + ': usage:', self.prog, '/path/to/serial/port /path/to/myHex.hex [-d]'
            exit(1)
        if len( args ) >3 and args[3] == '-d':
            self.debug = True
        self.portname = args[1]
        self.filename = args[2]
      
    #    
    def writeProgram(self):
        if self.debug == True:
            print '\nProgrammer.writeProgram: PROGRAM: ' + self.prog + '   PORT: "' + self.portname + '"   FILENAME: ' + self.filename + '\n'
        self.openFile( self.filename )
        if self.debug == True:
            print 'Programmer.writeProgram: File open. Reading packets. . .\n'
        packets = self.makePackets( self.file )
        self.file.close()
        if self.debug == True:
            print '\nProgrammer.writeProgram: Packet dump:\n'
            for packet in packets:
                for c in packet:
                    val = hex(ord(c))[2:]
                    if len(val) < 2:
                        val = '0' + val
                    print val,
                print ''
        print '\nProgrammer.writeProgram: File read complete. Ready to reset pic . . .\n'
        self.openPort(self.portname)
        if self.debug == True:
            print 'Programmer.writeProgram: port open. Awaiting start sequence. . .\n'
        self.startSequence()
        for packet in packets:
            self.sendPacket(packet)

        # how to end? The chip will time out waiting and reset itself.
        print 'Programmer.writeProgram: Programming complete.'
  
    # attempt to send a packet to the pic up to three times. Die on no response.
    def sendPacket(self, packet ):
        retries = 3
        while retries >0:
            if self.debug == True:
                print 'Programmer.sendPacket: Sending packet...',
                for c in packet:
                    print hex(ord(c)),
                print ''
  
            self.tty.write( packet )

            while True:
                status = self.endlessWait()
                if 'k' == status:
                    print 'Programmer.sendPacket: Packet send successful.'
                    return
                elif 'n' == status:
                    if self.debug == True:
                        print 'Programmer.sendPacket: Packet send unsuccessful.  Retrying...'
                    retries = retries - 1
                    break
                else:
                    print 'Programmer.sendPacket: An unexpected response from the microchip.  Received: ', ord(status), 'Aborting. . .'
                exit(1)

        # if we get down here, we've failed
        print 'Programmer.sendPacket: Failed to get the packet to the microchip successfully. Aborting. . . '
        exit(1)
  
    # opens the serial port.  sets in in class variable and returns handle too
    def openPort(self, portname ):
        try:
            self.tty = uspp.SerialPort( portname, 0, 9600)
            self.tty.flush() # discard unread bytes
        except Exception:
            print 'Programmer.openPort: Could not open the serial port: ' + portname
            exit(1)
  
    # opens the hex file, stores file handle in class var and returns the file handle
    def openFile(self, filename ):
        try:
            self.file = open( filename, 'r' )
        except Exception:
            print 'Programmer.openFile: Could not open file: ' + filename
            exit(1)
        return self.file
  
  
    def makePackets(self, file ):
        lines = file.readlines()
        packets = []
        for line in lines:
            ind = line.find( '\n' )
            if ind > 0:
                line = line[:ind]
            ni = line[1:3]
          
            intNumInstr = int(ni,16)/2
            # Need to put high and low together
            # as a single hex value, divide by two, then split them.
            addrValue = int(line[3:7],16) / 2
          
            recordType = int(line[7:9],16)
            packet = ''
          
            # Record Type 0: data
            # Record Type 1: end of file
            # Record Type 2 & 3: Extended segment addressing.  throw warning for us...
            # Record Type 4: signals true 32 bit addressing.  throw warning for us...
            # Record Type 5: another extended address thingy.  throw warning for us...
            if recordType == 0: # This is a data line
                # compute address high and low
                addrH = ''
                addrL = ''
                if addrValue < 256:
                    addrH = '0'
                    addrL = hex( int( bin( addrValue ),2 ) )[2:] # take value as binary int & convert to hex string
                else:
                    b = bin(addrValue)[2:]                  # get as binary string but trim off the '0b'
                    addrH = hex( int( b[:len(b)-8], 2 ) )[2:]      # turn binary into --> int --> hex string and trim off '0x'
                    addrL = hex( int( b[len(b)-8:], 2 ) )[2:]
                intAddrH = int( addrH, 16 )
                intAddrL = int( addrL, 16 )
                addrH = chr( intAddrH )
                addrL = chr( intAddrL )
              
                # Now deal with the address given
                if intAddrH > 32:
                    print 'Programmer.makePackets: Not sure how to deal with data memory writes yet.  Skipping. . .'
                    continue
                # config bits will have address high of 32
                elif intAddrH == 32:
                    if self.debug == True:
                        print 'Programmer.makePackets: Config bit instruction. skipping this line'
                        print '\t\t\tConfig bits can\'t be programmed by this bootloader.\n'
                    continue
                # the bootloader will have an address space of 0xfff - 192 = 0xf1f
                # this translates to an address high of 15 and address low of 31
                elif intAddrH >= 15 and intAddrL >= 31:
                    print 'Programmer.makePackets: Your program is trying to overwrite the bootloader.  Aborting...'
                    print '\tThis program will not allow you to overwrite the bootloader program.  You will need to reprogram the chip if you wish to overwrite.'
                    exit(1)
                # need to deal with the first 4 instructions
                elif intAddrH == 0 and intAddrL == 0:
                    self.createFirstInstructionLines( packets, line, intNumInstr )
                else:
                    packet = self.createRegularPacket( line, intNumInstr, addrH, addrL )  
                    packets.append( packet )
              
            elif recordType == 1:  # end of file
                if self.debug == True:
                    print '\nProgrammer.makePackets: Hex file read complete.'
            elif recordType == 4:   # print inhx32 warning
                if self.debug == True:
                    print  'Programmer.makePackets: Found an indicator line to use 32 bit addressing.  Compile with inhx8s or inhx8m. Ignoring this line.\n'
            else:
                if self.debug == True:
                    print 'Programmer.makePackets: Found lines with extended segment addressing. Skipping this line. . .\n'  
        return packets
  
    def computeNumberOfInstructions(self, intNumInstr ):
        computedNumInstr = 8
        if intNumInstr <= 4:
            computedNumInstr = 4
        elif intNumInstr > 8:
            print 'Programmer.makePackets: Number of instructions was greater than 8.  Unexpected. Aborting. . .'
            exit(1)
        return computedNumInstr

    def createFirstInstructionLines(self, packets, line, intNumInstr ):
        """ This is an abysmal function and set of algorithms.  Need to refactor to make use of redundant operations.
Eventually, the redundant operations in this and the createRegularPacket should be combined.  For now, I just want it work."""

        computedNumInstr = self.computeNumberOfInstructions( intNumInstr )
        numInstr = chr( computedNumInstr  )
        # If less than 4 instructions, create special rewrite packet with what we have then pad instructions to 4.
        # The crc will need to be computed to account for the special address.
        # else if 4 or more, create rewrite packet for first 4
        # calculate crc
        if intNumInstr < 4:
            #get the data for all the instructions
            lineOne = line[self.dataStart:self.dataStart+4*intNumInstr]
            numToPad = (4 - intNumInstr) * 2
            for i in range(0,numToPad,1):
                lineOne = lineOne + '00'
        else:
            # create the lineOne data for the first 4 instructions
            lineOne = line[self.dataStart:self.dataStart+16]
        # expect doubleAddress to return 1e82
        crcOne = self.calculateChecksum( '04' + self.rewriteAddrH + self.rewriteAddrL + lineOne )
        packetOne = self.makeChar( self.rewriteAddrH ) + self.makeChar( self.rewriteAddrL ) + self.makeChar( '04' )
        for i in range( 0, len(lineOne), 4 ):
            packetOne = packetOne + self.makeChar( lineOne[i:i+2] ) + self.makeChar( lineOne[i+2:i+4] )
        packetOne = packetOne + self.makeChar( crcOne )
      
        packets.append( packetOne )
            # if more than 4 instructions and less than 9, create second packet with what's left
            # calculate new crc.  Address will be 0x04 for low
        if computedNumInstr == 8:
            start = self.dataStart + 16   #start at 5th instruction
            end = len(line) - 2           # knock off old crc
            lineTwo = line[start:end]
            if intNumInstr < 8:
                pad = computedNumInstr - intNumInstr
                if pad > 0:
                    for i in range(0,pad,1):
                        lineTwo = lineTwo + '0000'
            crcTwo = self.calculateChecksum( '04' + '0004' + lineTwo )
            packetTwo = self.makeChar( '00' ) + self.makeChar( '04' ) + self.makeChar( '04' )
            for i in range( 0, len(lineTwo), 4 ):
                packetTwo = packetTwo + self.makeChar( lineTwo[i:i+2] ) + self.makeChar( lineTwo[i+2:i+4] )
            packetTwo = packetTwo + self.makeChar( crcTwo )
            packets.append( packetTwo )
  
    def calculateChecksum(self, data ):
        tot=0
        for i in range( 2, len(data)+2, 2 ):
            tot = tot + int( data[i-2:i], 16 )
        s = bin( tot )
        if len(s) <10:
            s = s[2:]
        else:
            s = s[len(s)-8:]
        return  hex( 0x100 - int(s,2) )[2:]
  
    def createRegularPacket(self, line, intNumInstr, addrH, addrL ):
        # compute the number of instructions
        computedNumInstr = self.computeNumberOfInstructions( intNumInstr )
          
        numInstr = chr( computedNumInstr )
        packet = '' + addrH + addrL + numInstr
      
        # now add the data packets
        for i in range( self.dataStart, len(line) - 4, 4 ):
            packet = packet + self.makeChar( line[i:i+2] ) + self.makeChar( line[i+2:i+4] )
          
        # need to add padding for short lines
        pad = computedNumInstr - intNumInstr
        if pad > 0:
            for i in range(0,pad,1):
                packet = packet + self.makeChar( '00' ) + self.makeChar( '00' )
              
        # now add in the checksum
        data = hex( computedNumInstr )[2:].zfill(2) + hex( ord(addrH) )[2:].zfill(2) + hex( ord(addrL) )[2:].zfill(2) + line[7:len(line)-2]
        checksum = self.calculateChecksum( data )
#        checksum = line[len(line)-2:]
        packet = packet + self.makeChar( checksum )
        return packet
  
    def makeChar( self, str ):
        return chr(int( '0x' + str, 16) )


    def startSequence(self):
        """Go into endless wait, listening on port for the startSignal.  When it receives the start signal, it
echoes it back to the pic.  It then waits endlessly for the pic to send ok ('k').  It then returns."""
#        self.waitForResponse( self.startSignal )
        self.endlessWait()
        sleep(.002)
        self.tty.write( self.startSignal )
        print 'Programmer.startSequence: Sent the start signal to the pic.'
        # for echo only, write ok back
#        self.tty.write( self.ok )
        self.waitForResponse( self.ok )

    def waitForResponse( self, charWeExpect ):
        c = self.endlessWait()
        if charWeExpect != c:
            print 'Programmer.waitForResponse: We did not receive the character we were expecting.  Looking for:', hex(ord(charWeExpect)), '.  We received:', hex(ord(c)), '. Aborting.'
            exit(0)
        else:
            ch = ''
            try:
                ch = c.decode('ascii')
            except:
                ch = hex( ord(c) )
            print 'Programmer.waitForResponse: Got expected response:', ch
    def endlessWait( self ):
        c = ''
        while True:
            c = self.tty.read()
            if '' != c:
                return c
  
if __name__ == '__main__':
    programmer = Programmer( sys.argv )
    programmer.writeProgram()

No comments:

Post a Comment