Salamek / picpro

PIC K150 programmer software written in Python 3
GNU General Public License v2.0
11 stars 2 forks source link

Programming 12C508A failed #4

Open colbro51 opened 2 years ago

colbro51 commented 2 years ago

Hi again

I finally got around trying to actually program a chip with the following hex file data: :080000002500C70C0200660098 :080008000B0C0600380A690028 :080010006800E802090AE90298 :08001800080A0008050C2A008B :080020000709EA02100A0008BA :08002800190C2A000709EA0285 :08003000100A0008650C2B000A :080038003C0C2A000709EA0252 :080040001E0AEB021C0A000875 :0800480068008605A8026607A6 :08005000260A860400082409B9 :08005800400C2B006A000802B5 :080060002900E902310AEA025D :080068002F0AEB022E0A00082A :080070000E090606460AA6056A :080078002B09A6041409380A43 :080080001A0946050E094604A9 :080088002606400A0A0C2700BD :080090000607480A06064A0AA9 :0800980046050709460426068F :0800A000400AA6052B09A60485 :0800A8001A09E702480A4605A7 :0800B000070946042606400A78 :0800B8001A09570AFF0FFF0FA0 :080400000800080009000100DA :021FFE00EA0FE8 :00000001FF

and the program failed as below:

`picpro program -p /dev/ttyUSB0 -i .prog.hex -t 12C508A

Waiting for user to insert chip into socket with pin 1 at socket pin 13 Chip detected. Traceback (most recent call last): File "/home/colin/.local/bin/picpro", line 8, in sys.exit(main()) File "/home/colin/.local/lib/python3.8/site-packages/picpro/bin/picpro.py", line 434, in main getattr(command, 'chosen')() # Execute the function specified by the user. File "/home/colin/.local/lib/python3.8/site-packages/picpro/bin/picpro.py", line 98, in program program_pic( File "/home/colin/.local/lib/python3.8/site-packages/picpro/bin/picpro.py", line 365, in program_pic rom_data, eeprom_data, id_data, fuse_values = prepare_flash_data_from_hex_file(chip_info, hex_file, pic_id, fuses) File "/home/colin/.local/lib/python3.8/site-packages/picpro/bin/picpro.py", line 294, in prepare_flash_data_from_hex_file rom_data = merge_records(rom_records, rom_blank) File "/home/colin/.local/lib/python3.8/site-packages/picpro/tools.py", line 59, in merge_records raise IndexError('Record out of range.') IndexError: Record out of range.`

Working through the code I found that the ID and fuse data is ending up in the rom data, thus the "record out of range". If I remove both of these from my hex file as a test then the error does not occur. The program does not appear to correctly interpret the hex addressing of ID and flags as per the PIC documentation.

However with this test another issue arises as the output then comes back with "Erasing chip". This chip is not erasable! I would suggest the chipinfo flag is not being checked.

Cheers Colin

colbro51 commented 2 years ago

Finally success at last! By writing myself a dummy serial interface and examining the commands being sent to the K150 I was able to convince myself that by only putting the rom code in the hex file and using the command line fuse overrides and forgetting about the ID value it should work when I commented out the erase, eeprom programming and verification code. By reading the device after programming I was able to verify the write. I attach my dummy serial interface code "cbserial.py".

#
# Serial wrapper - locate in site-packages/picpro directory
#
# Modify picpro.py:
#   line 40:   "import serial" --> "from picpro.cbserial import Serial"
#   line 186:  "s = serial.Serial" --> "s = Serial"   
# Modify ProtocolInterface.py
#   line 4:    "import serial" --> "from picpro.cbserial import Serial"
#   
# Command documentation: http://www.kitsrus.com/zip/softprotocol5.zip
#

import pprint
import sys
import serial
import easygui as eg

class Serial(serial.Serial):
    is_open = True
    _port_handle = None
    timeout = 0
    def __init__(self, *args, **kargs):
        self.rcvd = '__init__'
        self.header = 'Hello programmer!'.encode('utf-8')
        self.socket = False
        self.progRom = False
        self.rom = []
    def read(self, number):
        # Choose either display for single stepping or log for file output
        display = True
        log = False
        if self.progRom:
            if self.progsize:
                if len(self.rcvd) != 32:
                    eg.exceptionbox('Expected 32 bytes in prog mode')
                title = '32 bytes prog data of '+str(self.progsize)
                self.progsize -= 32
                response = b'Y'
                self.rom.append(self.rcvd)
            else:
                title = 'finished prog'
                response = b'P'
                self.progRom = False
        elif self.rcvd == '__init__':
            self.rcvd = b''
            response = b'B\x03'
            title = 'K150 Power-Up'
        elif self.rcvd == b'\x01':
            title = 'Command end / Escape jump table to command start'
            response = b'Q'
        elif self.rcvd == b'P':
            title = 'Goto jump table from command start'
            response = b'P'
        elif self.rcvd and self.rcvd in self.header:
            title = 'Echo character'
            response = self.rcvd
            display = False
        elif self.rcvd == b'\x12':
            title = 'Detect chip in socket - no'
            response = b'A'
            self.socket = True
        elif self.socket:
            title = 'Detect chip in socket - yes'
            response = b'Y'
            self.socket = False
        elif self.rcvd == b'\x04':
            title = 'Turn on programming voltages'
            response = b'V'
        elif self.rcvd == b'\x02\x00\x00\x00\x04\x01\x01\x02\x00\x01\x00':
            title = 'Initialise programming variables'
            response = b'I'
        elif self.rcvd == b'\x0b':
            title = 'Read ROM'
            if not self.rom:
                # a real example value for MOVLW instruction as returned
                response = (b'\xff\x0f')*(number//4 - 1) + b'\x74' + b'\x0C'
            else:
                # CORRECT RESPONSE here commented out
                # response = b''.join(self.rom)[:-2] + b'\x74' + b'\x0C'
                response = b''.join(self.rom)
        elif self.rcvd == b'\x05':
            title = 'Turn off programming voltages'
            response = b'v'
        elif self.rcvd == b'\x0e':
            title = 'Erase chip'
            response = b'Y'
        elif self.rcvd == b'\x06':
            title = 'Program ROM'
            response = b'\xff'  # not checked
        elif self.rcvd == b'\x02\x00':
            title = 'ROM size: word High/Low'
            response = b'Y'
            self.progRom = True
            self.progsize = 512*2  # bytes = 2*words
        elif self.rcvd.startswith(b'00') and self.rcvd.endswith(b'\xff\xff\xff\xff'):
            response = b'Y'
            title = 'Program ID fuses'
        else:
            title = 'Unknown command?'
            response = b'\xff'*number
        msg = 'Rcvd: %s; Want %s bytes; Rtn: %s' % \
                  (repr(self.rcvd), number, repr(response))
        if display:
            if not eg.ccbox(msg, title):
                sys.exit(0)
        elif log:
            with open('k150.log','a') as fobj:
                fobj.write(title+'\n')
                pprint.pprint(msg, stream=fobj)
                fobj.write('\n')
        self.rcvd = b''
        return response
    def write(self, data):
        self.rcvd = data
colbro51 commented 2 years ago

For anyone interested in programming the 12C508A, here is a GUI frontend:

#
#  Simple GUI interface to picpro for PIC12C508A
#  Execute from Linux bash shell:> python3 picPgmr.py
#
"""
Location: 0x1FF (PIC12C508A) contains the MOVLW XX
INTERNAL RC oscillator calibration value.
!!! DO NOT ALTER THIS PRESET VALUE !!!

ID Locations @ 0x200:    ( 0x400 in Intel hex file )
Four memory locations are designated as ID locations
where the user can store checksum or other code identification numbers.
Use only the lower 4 bits of the ID locations and
always program the upper 8 bits as ’0’s.

Config word @ 0xFFF:     ( 0x1FFE in Intel hex file )
bit 4: MCLRE: MCLR enable bit.
1 = MCLR pin enabled
0 = MCLR tied to VDD, (Internally)
bit 3: CP: Code protection bit.
1 = Code protection off
0 = Code protection on
bit 2: WDTE: Watchdog timer enable bit
1 = WDT enabled
0 = WDT disabled
bit 1-0: FOSC1:FOSC0: Oscillator selection bits
11 = EXTRC - external RC oscillator
10 = INTRC - internal RC oscillator
01 = XT oscillator
00 = LP oscillator
"""

import os, subprocess, sys
import easygui as eg
import PySimpleGUI as sg

DUMPF = '._dump_.dat'
HEXF = '._prog_.hex'

SOCKET = '''
 1+---\_/---+40
 2:         :39
 3:         :38
 4:         :37
 5:         :36  Insert PIC into K150
 6:         :35  programming socket
 7:         :34  as shown here !!!
 8:         :33
 9:         :32  Pin 1 of PIC is in
10:         :31  pin 13 of socket.
11:         :30
12:         :29
13:-:1\_/-:-:28
14:-: PIC :-:27
15:-: 12C :-:26
16:-: 508A:-:25
17:         :24
18:         :23
19:         :22
20:_________:21'''

K150 = '\n'*3 + '\n'.join(' '*15+line for line in SOCKET.split('\n'))+'\n'*4

class gui():
    def __init__(self):
        self.setup()
        self.source = ''
        self.code = [int('0xfff',16)]*512
        self.flags = []
        self.page()
        self.loop()
    def setup(self):
        if not os.path.exists('picPgmr.cfg'):
            self.picdir = eg.diropenbox('PIC development root directory')
            with open('picPgmr.cfg','w') as fobj:
                fobj.write(self.picdir)
        else:
            self.picdir = open('picPgmr.cfg','r').read()
    def fuses(self):
        layout = [[sg.Text('Osc:', size=(8,1), tooltip='CPU clock'),
                   sg.InputCombo(('IntRC','ExtRC','LP','XT'),size=(6,1),default_value='IntRC',key='OSC')],
                  [sg.Text('WDT:', size=(8,1), tooltip='Watchdog timer'),
                   sg.InputCombo(('Off','On'),default_value='Off',key='WDT')],
                  [sg.Text('MCLR:', size=(8,1), tooltip='MCLR pin enabled'),
                   sg.InputCombo(('Off','On'),default_value='Off',key='MCLR')],
                  [sg.Text('CP:', size=(8,1), tooltip='Code Protect'),
                   sg.InputCombo(('Off','On'),default_value='Off',key='CP')],
                  [sg.Text('ID:', size=(8,1), tooltip='Identification'),
                   sg.InputText('', size=(5,1), key='ID')],
                  [sg.Text('CkSum:', size=(8,1), tooltip='Checksum'),
                   sg.Text(' '*8,key='CS')]]
        return sg.Frame('Fuses',layout)
    def functions(self):
        column = [[sg.Button('LoadHex',key=':OpenProg')],
                  [sg.Button('Program', key=':Program')],
                  [sg.Button('Verify', key=':Verify')],
                  [sg.Button('Read', key=':Read')],
                  [sg.Button('BlankCheck', key=':IsBlank')],
                  [sg.Button('SaveHex', key=':SaveHex')],
                  [sg.Button('Exit', key=':Close')]]
        layout = sg.Frame('Functions', layout=column)
        return layout
    def display(self):
        lines = []
        for ndx1 in range(64):
            line = []
            line.append('0'+self.hexdigits(8*ndx1,3)+': ')
            for ndx2 in range(8):
                ndx = 8*ndx1 + ndx2
                line.append(self.hexdigits(self.code[ndx],3))
                if ndx2 in [3, 7]:
                    line.append('')
            for ndx2 in range(8):
                ndx = 8*ndx1 + ndx2
                byte = self.code[ndx] % 256
                if chr(byte).isprintable():
                    line.append(chr(byte))
                else:
                    line.append('.')
            lines.append(' '.join(line))
        return '\n'.join(lines)
    def data(self):
        layout = [[sg.Frame(title='Program Code', key='panel1',
                           layout=[[sg.Multiline(default_text=K150,\
                                        font='Courier', key='ml1', size=(60, 28))]]),
                   sg.Frame(title='Select Hex Data File', key='panel2', visible=False,
                           layout=[[sg.Listbox(values=[], key='lb2', size=(60,28),
                                        font='Courier', enable_events=True)]])]]
        return sg.Column(layout)
    def control(self):
        column = [[self.fuses()],
                  [self.functions()]]
        layout = sg.Column(column)
        return layout
    def page(self):
        layout = [[self.data(), self.control()]]
        self.window = sg.Window('PIC12C508A Programmer', layout)
    def loop(self):
        self.exit = False
        timeOut = 5000
        while True:
            event, self.values = self.window.read(timeout=timeOut)
            if not event:
                self.window.close()
                return
            if event == '__TIMEOUT__':
                self.window.find_element('ml1').Update(self.display())
                self.updateCS()
            if event and event[0]==':':
                eval('self.'+event[1:]+'()')
            if event == 'lb2':
                self.OpenProg2()
            if self.exit:
                self.window.close()
                return
            timeOut = None
    def OpenProg(self):
        fileList = []
        for root, dirs, files in os.walk(self.picdir):
           for name in files:
               if name.endswith('.hex') and not name.startswith('.'):
                   fileList.append(os.path.join(root,name).replace('\\','/'))
        if fileList:
            self.window.find_element('panel1').Update(visible=False)
            self.window.find_element('panel2').Update(visible=True)
            self.window.find_element('lb2').Update(values=fileList)
        else:
            sg.popup('No .hex files under PIC root directory')
            os.remove('picPgmr.cfg')
            self.setup()
    def OpenProg2(self):
        self.source = self.values['lb2'][0]
        with open(self.source, 'r') as fobj:
            data = fobj.read()
        if not data:
            sg.popup('File '+os.path.basename(self.source)+' is empty!')
            return
        self.window.find_element('panel2').Update(visible=False)
        if self.loadCode(data):
            self.window.find_element('ml1').Update(self.display())
        self.window.find_element('panel1').Update(visible=True)
    def loadCode(self, data):
        info = self.unpack(data)
        if not info:
            return
        #code
        for ndx in range(0, 1024, 2):
            pair = 256*info[ndx+1]+info[ndx]
            self.code[ndx//2] = pair % 4096
        #id
        IDloc = int('0x400', 16)
        IDval = (hex(info[IDloc])[-1]+hex(info[IDloc+2])[-1]+\
                 hex(info[IDloc+4])[-1]+hex(info[IDloc+6])[-1]).upper()
        self.window.find_element('ID').Update(IDval)
        #config
        CFGloc = int('0x1FFE', 16)
        Config = bin(info[CFGloc])[::-1]
        switch = ('Off', 'On')
        oscval = {'00': 'LP', '01': 'XT', '10': 'IntRC', '11': 'ExtRC'}
        mclr = switch[Config[4] == '1']
        cp = switch[Config[3] == '0']
        wdt = switch[Config[2] == '1']
        osc = oscval[Config[1]+Config[0]]
        self.window.find_element('MCLR').Update(mclr)
        self.window.find_element('CP').Update(cp)
        self.window.find_element('WDT').Update(wdt)
        self.window.find_element('OSC').Update(osc)
        self.updateCS()
        return True
    def updateCS(self):
        # checksum (https://www.microchip.com/forums/m788738.aspx & just use code checksum)
        csum = 0
        for ndx in range(len(self.code)):
            csum += self.code[ndx]
        csum = csum % int('0xFFFF', 16)
        self.window.find_element('CS').Update(self.hexdigits(csum,4))
        self.window.find_element('ID').Update(self.hexdigits(csum,4))
    def unpack(self, data):
        lines = data.replace('\r','').split('\n')
        info = [255] * 8192
        for line in lines:
            if line[0] != ':':
                sg.popup('Invalid hex file format, missing ":"')
                return
            try:
                byteCount = int('0x'+line[1:3], 16)
                address = int('0x'+line[3:7], 16)
                tt = int('0x'+line[7:9], 16)
                if tt == 1:
                    break
                if tt == 0:
                    for ndx in range(0, 2*byteCount, 2):
                        info[address + ndx//2] = \
                            int('0x'+line[9 + ndx]+line[9 + ndx+1], 16)
                cksum = 0
                for ndx in range(0, len(line[1:]), 2):
                    cksum += int('0x'+line[1+ndx:3+ndx], 16)
                if cksum % 256 != 0:
                    sg.popup('Checksum failure')
                    return
            except:
                sg.popup('Invalid hex format')
                return
        return info
    def Program(self):
        if os.path.exists(HEXF):
            os.remove(HEXF)
        self.generateHex(HEXF, full=False)
        cmd = 'picpro program -p /dev/ttyUSB0 -i '+HEXF+' -t 12C508A '+\
              ' '.join(self.flags)
        self.runCommand(cmd)
    def generateHex(self, outfile, full=True):
        #code
        hexdata = []
        osccal, self.code[511] = self.code[511], 4095
        # self.code in words (2*bytes), intel hex addressing in bytes
        for ndx in range(0, 512, 4):
            if self.code[ndx:ndx+4] == [4095]*4:
                continue
            else:
                if ndx < 508:
                    head = ':08'+self.hexdigits(2*ndx,4)+'00'
                    data = [self.hexword(n) for n in self.code[ndx:ndx+4]]
                else:
                    # we do not want to overwrite osccal MOVLW XXX instruction
                    head = ':06'+self.hexdigits(2*ndx,4)+'00'
                    data = [self.hexword(n) for n in self.code[ndx:ndx+3]]
                line = head+''.join(data)
                line += self.makeCS(line)
                hexdata.append(line.upper())
        self.code[511] = osccal
        #ID
        ID = self.values['ID']
        if ID:
            line = ':08040000'+self.hexword(int('0x'+ID[0],16))+\
                               self.hexword(int('0x'+ID[1],16))+\
                               self.hexword(int('0x'+ID[2],16))+\
                               self.hexword(int('0x'+ID[3],16))
        line += self.makeCS(line)
        if full:
            hexdata.append(line.upper())
        #fuses
        config = ['1']*8
        if self.values['MCLR'] == 'Off':
            config[4] = '0'
            self.flags.append('--fuse=MCLRE:Disabled')
        else:
            self.flags.append('--fuse=MCLRE:Enabled')
        if self.values['CP'] == 'On':
            config[3] = '0'
            self.flags.append('"--fuse=Code Protect:Enabled"')
        else:
            self.flags.append('"--fuse=Code Protect:Disabled"')
        if self.values['WDT'] == 'Off':
            config[2] = '0'
            self.flags.append('--fuse=WDT:Disabled')
        else:
            self.flags.append('--fuse=WDT:Enabled')
        if self.values['OSC'] == 'IntRC':
            config[0] = '0'
            self.flags.append('--fuse=Oscillator:IN_RC')
        if self.values['OSC'] == 'XT':
            config[1] = '0'
            self.flags.append('--fuse=Oscillator:XT')
        if self.values['OSC'] == 'LP':
            config[0] = '0'
            config[1] = '0'
            self.flags.append('--fuse=Oscillator:LP')
        if self.values['OSC'] == 'ExtRC':
            self.flags.append('--fuse=Oscillator:EX_RC')
        fuses = hex(int('0b'+''.join(config[::-1]),2))[2:]
        line = ':021FFE00'+fuses+'0F'
        line += self.makeCS(line)
        if full:
            hexdata.append(line.upper())
        hexdata.append(':00000001FF\n')
        with open(outfile, 'w') as fobj:
            fobj.write('\n'.join(hexdata))
    def makeCS(self, line):
        cksum = 0
        for ndx in range(0, len(line[1:]), 2):
            cksum += int('0x'+line[1+ndx:3+ndx], 16)
        cksum = 256 - (cksum % 256)
        return self.hexdigits(cksum,2)
    def hexdigits(self, num, digits):
        return ('000'+hex(num)[2:]).upper()[-digits:]
    def hexword(self, num):
        hi, lo = divmod(num, 256)
        return self.hexdigits(lo,2) + self.hexdigits(hi,2)
    def Verify(self):
        self.codeSave = self.code.copy()
        if not self.Read(update=False):
            sg.popup('Verification check failed')
            return
        for ndx in range(len(self.code)-1):
            if self.code[ndx] != self.codeSave[ndx]:
                sg.popup('First difference at location: 0x'+self.hexdigits(ndx,3))
                self.code = self.codeSave.copy()
                return
        if int('0xC00', 16) <= self.code[-1] < int('0xD00', 16):
            sg.popup('Verification check successful')
        else:
            sg.popup('Bad last MOVLW instruction: 0x'+self.hexdigits(self.code[-1],3))
        self.code = self.codeSave.copy()
    def Read(self, checkBlank=False, update=True):
        if os.path.exists(DUMPF):
            os.remove(DUMPF)
        cmd = 'picpro dump rom -p /dev/ttyUSB0 -b '+DUMPF+' -t 12C508A'
        self.runCommand(cmd)
        if not os.path.exists(DUMPF):
            sg.popup('No dump file created so read failed!')
            return False
        data = open(DUMPF,'rb').read()
        if checkBlank:
            for ndx in range(0, len(data)-2, 2):   # skip OSCCAL word
                val = 256*(data[ndx])+data[ndx+1]
                if val != 4095:
                    sg.popup('Chip is not blank!')
                    return False
            sg.popup('Chip is blank!')
            return 1
        for ndx in range(0, len(data), 2):
            self.code[ndx//2] = 256*(data[ndx])+data[ndx+1]
        if update:
            self.window.find_element('ml1').Update(self.display())
            self.updateCS()
        return True
    def IsBlank(self):
        self.Read(checkBlank=True)
    def SaveHex(self):
        outfile = eg.enterbox('Enter output .hex filename')
        if not outfile.endswith('.hex'):
            outfile = outfile + '.hex'
        if self.source:
            outfile = os.path.join(os.path.dirname(self.source),outfile)
        self.generateHex(outfile)
    def Close(self):
        self.exit=True
    def runCommand(self,cmd):
        print('*'*80)
        print(cmd)
        print('-'*80)
        subprocess.call(cmd, shell=True)
        print ('='*80)

if __name__ == '__main__':
    G = gui()