data_sheet_to_csv.py - GUI to assist in translating Microchip data sheets to pin mappings¶
This program accepts text copied and pasted from a Microchip data sheet (in PDF format) of the pinout for a given package, then translates it into an Rxy/RPy/ANn/CNm mapping.
To build as an executable:
pyinstaller data_sheet_to_csv.py -y
 
import re
import sys
import csv
import os
 
The excellent PyQt5 library provides the GUI for this package.
from PyQt5 import QtWidgets, QtGui, QtCore, uic
from compact_csv import enumeratePic24Ports
def text_pinout_to_mapping(text):
    pins = text.split()
    RPy = {}
    ANn = {}
    CNm = {}
    last_failure = ''
    last_pin = ''
    for pin in pins:
Look for Rxy. If not found, we can’t do anything with this pin.
        mo = re.search('R([A-K]\d\d?)', pin)
        if mo:
            Rxy = mo.group(1)
        else:
No Rxy pin here, so we can’t map. Do error checking anyway.
            Rxy = ''
 
Look for RPy.
        mo = re.search('RPI?(\d\d?\d?)', pin)
        if mo:
            if not Rxy:
                last_failure = 'Warning: found RPy without finding Rxy in ' + pin
                last_pin = pin
            else:
                RPy[Rxy] = mo.group(1)
 
Look for ANn.
        mo = re.search('AN(\d\d?)', pin)
        if mo:
            if not Rxy:
                last_failure = 'Warning: found ANn without finding Rxy in ' + pin
                last_pin = pin
            else:
                ANn[Rxy] = mo.group(1)
 
Look for CNm.
        mo = re.search('CN(\d\d?)', pin)
        if mo:
            if not Rxy:
                last_failure = 'Warning: found CNm without finding Rxy in ' + pin
                last_pin = pin
            else:
                CNm[Rxy] = mo.group(1)
    return RPy, ANn, CNm, last_failure, last_pin
form_class, base_class = uic.loadUiType('data_sheet_to_csv.ui')
class main_dialog(QtWidgets.QMainWindow, form_class):
    def __init__(self):
Let Qt and PyQt run their init first.
        super().__init__()
        self.setupUi(self)
 
Store portlist, which is used several times.
        self.portlist = enumeratePic24Ports()
        self.portlist.insert(0, 'Device port / pin')
    def parse_gui_text(self):
Read the GUI text fields and parse out pin mapping and procesor name.
        pinout_text = self.pinout_text_edit.toPlainText()
        RPy, ANn, CNm, last_failure, last_pin = text_pinout_to_mapping(pinout_text)
        self.statusBar().showMessage(last_failure)
        if last_pin:
Select failing text, assuming it was unique
            cursor = self.pinout_text_edit.textCursor()
            i = pinout_text.index(last_pin)
            cursor.setPosition(i)
            cursor.setPosition(i + len(last_pin), QtGui.QTextCursor.KeepAnchor)
            self.pinout_text_edit.setTextCursor(cursor)
        processor_names = ' '.join([s for s in self.processors_text_edit.toPlainText().split() if 'PIC' in s])
        return processor_names, RPy, ANn, CNm
    def on_pinout_text_edit_textChanged(self):
        self.update_table()
    def on_processors_text_edit_textChanged(self):
        self.update_table()
 
A helper to create an uneditable QTableWidgetItem
    def QTableWidgetItemUneditable(self, o):
        qtwi = QtWidgets.QTableWidgetItem(o)
        qtwi.setFlags(qtwi.flags() & ~QtCore.Qt.ItemIsEditable)
        return qtwi
 
Display results for the user.
    def update_table(self):
        processor_names, RPy, ANn, CNm = self.parse_gui_text()
        self.results_label.setText('Results: ' + processor_names)
        self.results_table_widget.clearContents()
        self.results_table_widget.setRowCount(128)
        row = 0
        for Rxy in self.portlist[1:]:
            if (Rxy in RPy) or (Rxy in ANn) or (Rxy in CNm):
                self.results_table_widget.setItem(row, 0, self.QTableWidgetItemUneditable(RPy.get(Rxy, '')))
                self.results_table_widget.setItem(row, 1, self.QTableWidgetItemUneditable(ANn.get(Rxy, '')))
                self.results_table_widget.setItem(row, 2, self.QTableWidgetItemUneditable(CNm.get(Rxy, '')))
                self.results_table_widget.setVerticalHeaderItem(row, QtWidgets.QTableWidgetItem(Rxy))
                row += 1
        self.results_table_widget.setRowCount(row)
 
This is called when OK is clicked. Append a CSV entry.
    def on_buttonBox_accepted(self):
        processor_names, RPy, ANn, CNm = self.parse_gui_text()
 
Prepare for appending to the CSV.
        with open('pic24_devices.csv', 'a+', newline='', encoding='utf-8') as outFile:
            csv_dict_writer = csv.DictWriter(outFile, self.portlist)
Write the header only if the file is empty. (Leaving out the seek always reports 0 for the tell).
            outFile.seek(0, os.SEEK_END)
            if outFile.tell() == 0:
                csv_dict_writer.writeheader()
 
Write it to the CSV.
            RPy['Device port / pin'] = processor_names + ' RPy'
            ANn['Device port / pin'] = processor_names + ' ANn'
            CNm['Device port / pin'] = processor_names + ' CNm'
            csv_dict_writer.writerow(RPy)
            csv_dict_writer.writerow(ANn)
            csv_dict_writer.writerow(CNm)
 
Display success, set up for next run.
        self.statusBar().showMessage('CSV updated.', 3000)
        self.processors_text_edit.clear()
        self.pinout_text_edit.clear()
        self.results_label.setText('Results')
 
This routine runs the CodeChat GUI.
def main():
Instantiate the app and GUI.
    app = QtWidgets.QApplication(sys.argv)
    window = main_dialog()
Run the program.
    window.show()
    sys.exit(app.exec_())
if __name__ == '__main__':
    main()