├── .gitattributes ├── readme.md ├── picoreg_gpio.py └── picoreg_qt.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | PicoReg: Pi Pico register viewer using SWD 2 | 3 | See https://iosoft.blog/picoreg for details. -------------------------------------------------------------------------------- /picoreg_gpio.py: -------------------------------------------------------------------------------- 1 | # PicoReg: low-level driver to display RP2040 registers using SWD 2 | # 3 | # For detailed description, see https://iosoft.blog/picoreg 4 | # 5 | # Copyright (c) 2020 Jeremy P Bentham 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | import time, sys, signal, RPi.GPIO as GPIO 21 | from ctypes import Structure, c_uint 22 | 23 | swd_hdr = [("Start", c_uint, 1), ("APnDP", c_uint, 1), 24 | ("RnW", c_uint, 1), ("Addr", c_uint, 2), 25 | ("Parity", c_uint, 1), ("Stop", c_uint, 1), 26 | ("Park", c_uint, 1)] 27 | 28 | swd_ack = [("ack", c_uint, 3)] 29 | 30 | swd_val32p = [("value", c_uint, 32), ("parity", c_uint, 1)] 31 | 32 | swd_turn = [("turn", c_uint, 1)] 33 | 34 | NUM_TRIES = 3 # Number of tries if error 35 | SWD_DP = 0 # AP/DP flag bits 36 | SWD_AP = 1 37 | SWD_WR = 0 # RD/WR flag bits 38 | SWD_RD = 1 39 | DP_CORE0 = 0x01002927 # ID to select core 0 and 1 40 | DP_CORE1 = 0x11002927 41 | SWD_ACK_OK = 1 # SWD Ack values 42 | SWD_ACK_WAIT = 2 43 | SWD_ACK_ERROR = 4 44 | 45 | CLK_PIN = 20 # Clock and data BCM pin numbers 46 | DAT_PIN = 21 47 | 48 | test_addr = 0xd0000004 # Address to test (GPIO input) 49 | 50 | # Calculate parity of 32-bit integer 51 | def parity32(i): 52 | i = i - ((i >> 1) & 0x55555555) 53 | i = (i & 0x33333333) + ((i >> 2) & 0x33333333) 54 | i = (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24 55 | return i & 1 56 | 57 | # SWD hardware driver 58 | class GpioDrv(object): 59 | def __init__(self, dev=None): 60 | self.dev = dev 61 | self.txdata = "" 62 | self.rxdata = "" 63 | 64 | # Open driver 65 | def open(self): 66 | GPIO.setwarnings(False) 67 | GPIO.setmode(GPIO.BCM) 68 | GPIO.setup(CLK_PIN, GPIO.OUT) 69 | GPIO.setup(DAT_PIN, GPIO.IN) 70 | return True 71 | 72 | # Close driver 73 | def close(self): 74 | GPIO.setup(CLK_PIN, GPIO.IN) 75 | GPIO.setup(DAT_PIN, GPIO.IN) 76 | return True 77 | 78 | # Add some bits to transmit buffer, dummy bits if a read cycle 79 | def send_bits(self, data, nbits, recv): 80 | n = 0 81 | while n < nbits: 82 | self.txdata += "." if recv else "1" if data & (1 << n) else "0" 83 | n += 1 84 | 85 | # Add some bytes to transmit buffer, dummy bits if a read cycle 86 | def send_bytes(self, data, recv): 87 | for b in data: 88 | self.send_bits(b, 8, recv) 89 | 90 | # Get some bits into receive buffer 91 | def recv_bits(self, nbits): 92 | val = None 93 | if nbits <= len(self.rxdata): 94 | val = n = 0 95 | while n < nbits: 96 | if self.rxdata[n] == "1": 97 | val |= 1 << n 98 | n += 1 99 | self.rxdata = self.rxdata[nbits:] 100 | return val 101 | 102 | # Send bits from transmit buffer, get any receive bits 103 | def xfer(self): 104 | self.rxdata = lastbit = "" 105 | for bit in self.txdata: 106 | if bit == ".": 107 | self.rxdata += ("1" if GPIO.input(DAT_PIN) else "0") 108 | if bit != lastbit: 109 | GPIO.setup(DAT_PIN, GPIO.IN) 110 | GPIO.output(CLK_PIN, 1) 111 | else: 112 | if lastbit == "" or lastbit == ".": 113 | GPIO.setup(DAT_PIN, GPIO.OUT) 114 | GPIO.output(DAT_PIN, 1 if bit=="1" else 0) 115 | GPIO.output(CLK_PIN, 1) 116 | GPIO.output(CLK_PIN, 0) 117 | lastbit = bit 118 | self.txdata = "" 119 | return self.rxdata 120 | 121 | # Class for a register with multiple bit-fields 122 | class BitReg(object): 123 | def __init__(self, fields, recv=True): 124 | self.fields, self.recv = fields, recv 125 | class Struct(Structure): 126 | _fields_ = fields 127 | self.vals = Struct() 128 | self.nbits = sum([f[2] for f in self.fields]) 129 | 130 | # Transmit the bit values 131 | def send_vals(self, drv): 132 | for field in self.fields: 133 | val, n = getattr(self.vals, field[0]), field[2] 134 | drv.send_bits(val, n, self.recv) 135 | 136 | # Receive the bit values 137 | def recv_vals(self, drv): 138 | for field in self.fields: 139 | if self.recv: 140 | val = drv.recv_bits(field[2]) 141 | setattr(self.vals, field[0], val) 142 | 143 | # Return string with field values 144 | def field_vals(self): 145 | s = "" 146 | for field in self.fields: 147 | val = getattr(self.vals, field[0]) 148 | s += " %s=" % field[0] 149 | s += ("%u" % val) if field[2]<8 else ("0x%x" % val) 150 | return s 151 | 152 | # SWD message: header, ack and 32-bit value 153 | class SwdMsg(object): 154 | def __init__(self, drv): 155 | self.drv = drv 156 | self.regs = [] 157 | self.ok = self.verbose = False 158 | self.hdr = BitReg(swd_hdr, False) 159 | self.turn1 = BitReg(swd_turn, True) 160 | self.ack = BitReg(swd_ack, True) 161 | self.turn2 = BitReg(swd_turn, True) 162 | self.val32p = BitReg(swd_val32p, False) 163 | self.hdr.vals.Start = 1 164 | self.hdr.vals.Stop = 0 165 | self.hdr.vals.Park = 1 166 | 167 | # Verbose print of message values 168 | def verbose_print(self, label=""): 169 | if self.verbose: 170 | print("%s %s" % (self.msg_vals(), label)) 171 | 172 | # Leave dormant state: see IHI 0031F page B5-137 173 | def arm_wakeup_msg(self): 174 | self.drv.send_bytes([0xff], False) 175 | self.drv.send_bytes([0x92,0xF3,0x09,0x62,0x95,0x2D,0x85,0x86, 176 | 0xE9,0xAF,0xDD,0xE3,0xA2,0x0E,0xBC,0x19], False) 177 | self.drv.send_bits(0, 4, False) # Idle bits 178 | self.drv.send_bytes([0x1a], False) # Activate SW-DP 179 | return self 180 | 181 | # SWD reset; at least 50 high bits then at least 2 low bits 182 | def swd_reset_msg(self): 183 | self.drv.send_bytes([0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00], False) 184 | if self.verbose: 185 | print("SWD reset") 186 | return self 187 | 188 | # Do an SWD read cycle 189 | def read(self, ap, addr, label=""): 190 | ret = self.set(ap, SWD_RD, addr).send_vals().xfer().recv_vals() 191 | self.verbose_print(label) 192 | return ret 193 | 194 | # Do an SWD write cycle 195 | def write(self, ap, addr, val, label=""): 196 | ret = self.set(ap, SWD_WR, addr, val).send_vals().xfer().recv_vals() 197 | self.verbose_print(label) 198 | return ret 199 | 200 | # Set header for a read or write cycle, with data if write 201 | def set(self, ap, rd, addr, val=0): 202 | self.hdr.vals.APnDP = ap 203 | self.hdr.vals.RnW = rd 204 | self.hdr.vals.Addr = addr >> 2 205 | self.hdr.vals.Parity = (ap ^ rd ^ (addr>>2) ^ (addr>>3)) & 1 206 | self.val32p.vals.value = val; 207 | self.val32p.vals.parity = parity32(val) 208 | self.regs = [self.hdr, self.turn1, self.ack] 209 | self.regs += [self.val32p, self.turn2] if rd else [self.turn2, self.val32p] 210 | self.val32p.recv = rd 211 | return self 212 | 213 | # Send values to transmit buffer 214 | def send_vals(self): 215 | for reg in self.regs: 216 | reg.send_vals(self.drv) 217 | return self 218 | 219 | # Get values from receive buffer 220 | def recv_vals(self): 221 | for reg in self.regs: 222 | reg.recv_vals(self.drv) 223 | return self.check() 224 | 225 | # Transfer values to & from target 226 | def xfer(self): 227 | self.drv.xfer() 228 | return self 229 | 230 | # Check ack-value, and parity if a read cycle 231 | def check(self): 232 | self.ok = (self.ack.vals.ack == 1 and 233 | (self.hdr.vals.RnW == 0 or 234 | parity32(self.val32p.vals.value) == self.val32p.vals.parity)) 235 | return self 236 | 237 | # Return string with header and data values 238 | def field_vals(self): 239 | return (self.hdr.field_vals() + self.ack.field_vals() + 240 | self.val32p.field_vals() + 241 | (" OK" if self.check() else " ERROR")) 242 | 243 | # Return string with key message values 244 | def msg_vals(self): 245 | return ("%s %s %X 0x%08x %s" % ( 246 | "Rd" if self.hdr.vals.RnW else "Wr", 247 | "AP" if self.hdr.vals.APnDP else "DP", 248 | self.hdr.vals.Addr << 2, self.val32p.vals.value, 249 | "OK" if self.ok else "Ack %u" % self.ack.vals.ack)) 250 | 251 | # SWD connection class 252 | class SwdConnection(object): 253 | def __init__(self): 254 | self.drv = self.msg = None 255 | self.connected = False 256 | self.dp = DP_CORE0 257 | 258 | # Open the hardware driver 259 | def open(self): 260 | self.drv = GpioDrv() 261 | if self.drv.open(): 262 | self.msg = SwdMsg(self.drv) 263 | return True 264 | return False 265 | 266 | # Close the hardware driver 267 | def close(self): 268 | self.drv.close() 269 | self.drv = None 270 | 271 | # Enable or disable verbose messages 272 | def verbose(self, enable): 273 | self.msg.verbose = enable 274 | 275 | # Set core number (0 or 1) 276 | def set_core(self, core): 277 | self.dp = DP_CORE1 if core else DP_CORE0 278 | 279 | # Connect to a device, given the Debug Port 280 | # CPU doesn't acknowledge transmission, so assume OK 281 | def connect(self): 282 | print("SWD connection restart") 283 | self.msg.arm_wakeup_msg().swd_reset_msg().xfer() 284 | self.msg.write(SWD_DP, 0xc, self.dp) 285 | return True 286 | 287 | # Disconnect from device 288 | def disconnect(self): 289 | self.msg.swd_reset_msg().xfer() 290 | self.connected = False 291 | 292 | # Get DP ID reg value (0x0bc12477 for RP2040) 293 | def get_dpidr(self): 294 | self.msg.read(SWD_DP, 0, "Read DPIDR") 295 | return self.msg.val32p.vals.value if self.msg.ok else None 296 | 297 | # Power up the interface, return AHB ID reg value 298 | def power_up(self): 299 | self.msg.write(SWD_DP, 0, 0x1e, "Clear error bits") 300 | self.msg.write(SWD_DP, 8, 0, "Set AP and DP bank 0") 301 | self.msg.write(SWD_DP, 4, 0x50000001, "Power up") 302 | self.msg.read (SWD_DP, 4, "Read status") 303 | self.msg.write(SWD_DP, 8, 0xf0, "Set AP bank F, DP bank 0") 304 | self.msg.read (SWD_AP, 0xc, "Read AP addr 0xFC") 305 | self.msg.read (SWD_DP, 0xc, "Read AP result (AHB3-AP IDR)") 306 | idr = self.msg.val32p.vals.value if self.msg.ok else None 307 | self.msg.write(SWD_AP, 0, 0xA2000012, "Auto-increment word values") 308 | self.msg.write(SWD_DP, 8, 0, "Set AP and DP bank 0") 309 | return idr if self.msg.ok else None 310 | 311 | # Read a 32-bit location in the CPU's memory 312 | def peek(self, addr): 313 | self.msg.write(SWD_AP, 4, addr, "Set AP address") 314 | self.msg.read (SWD_AP, 0xc, "AP read cycle") 315 | self.msg.read (SWD_DP, 0xc, "DP read result") 316 | return self.msg.val32p.vals.value if self.msg.ok else None 317 | 318 | # Add a retry loop around connection and user function 319 | # Return value from user function; if no function, just connect 320 | def conn_func_retry(self, func, *args): 321 | val = None 322 | tries = NUM_TRIES 323 | while tries>0 and val is None: 324 | if not self.connected and self.connect(): 325 | self.connected = True 326 | dpidr = self.get_dpidr() 327 | if dpidr is None: 328 | print("Can't read CPU ID") 329 | self.connected = False 330 | else: 331 | print("DPIDR 0x%08x" % dpidr) 332 | apidr = self.power_up() 333 | if apidr is None: 334 | print("Can't connect to AP") 335 | self.connected = False 336 | else: 337 | tries = NUM_TRIES 338 | if not self.connected: 339 | tries -= 1 340 | elif not func: 341 | break 342 | else: 343 | val = func(*args) 344 | if val is None: 345 | self.connected = False 346 | tries -= 1 347 | else: 348 | tries = NUM_TRIES 349 | return val 350 | 351 | # Handle ctrl-C 352 | def sigint_handler(self, *args): 353 | print("Terminating") 354 | self.disconnect() 355 | self.close() 356 | sys.exit(0) 357 | 358 | if __name__ == "__main__": 359 | conn = SwdConnection() 360 | conn.open() 361 | tries = NUM_TRIES 362 | repeat = False 363 | val = None 364 | args = sys.argv[1:] 365 | while args: 366 | if args[0] == "-r": 367 | repeat = True 368 | if args[0] == "-v": 369 | conn.verbose(True) 370 | else: 371 | try: 372 | test_addr = int(args[0], 16) 373 | except: 374 | pass 375 | args.pop(0) 376 | signal.signal(signal.SIGINT, conn.sigint_handler) 377 | 378 | while True: 379 | val = conn.conn_func_retry(conn.peek, test_addr) 380 | if val is not None: 381 | print("0x%08x: 0x%08x" % (test_addr, val)) 382 | if val is None or not repeat: 383 | break 384 | time.sleep(0.1) 385 | conn.disconnect() 386 | 387 | # EOF 388 | -------------------------------------------------------------------------------- /picoreg_qt.py: -------------------------------------------------------------------------------- 1 | # PicoReg: PyQT5 display of RP2040 registers using SWD 2 | # 3 | # For detailed description, see https://iosoft.blog/picoreg 4 | # 5 | # Copyright (c) 2020 Jeremy P Bentham 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | # v0.30 JPB 22/3/21 Set I/O pins as inputs on close 20 | 21 | import sys, re, importlib, os.path 22 | from PyQt5 import QtCore, QtGui, QtXml, QtWidgets 23 | 24 | VERSION = "PicoReg v0.30" 25 | 26 | SVD_FNAME = "rp2040.svd" 27 | SWD_IMPORT = "picoreg_gpio" 28 | CORE_NAMES = "Core 0", "Core 1" 29 | KEY_ELEMENTS = ["peripheral", "register", "field"] 30 | DISP_MSEC = 200 31 | STATUS_MSEC = 100 32 | WIN_SIZE = 640, 400 33 | CLR_DISP = "\x1a" 34 | DESC_EOL = re.compile(r"(\\n)+\n*\s*") 35 | BITRANGE = re.compile("(\d+):(\d+)") 36 | COL_LABELS = 'Peripheral', 'Base', 'Offset', 'Bits', 'Value' 37 | TREE_FONT = QtGui.QFont("Courier", 11) 38 | TEXT_FONT = QtGui.QFont("Courier", 10) 39 | TREE_STYLE = ("QTreeView{selection-color:#FF0000;} " + 40 | "QTreeView{selection-background-color:#FFEEEE;} ") 41 | PERIPH_COL, BASE_COL, OSET_COL = 0, 1, 2 42 | BITS_COL, VAL_COL, DESC_COL = 3, 4, 5 43 | 44 | # Import SWD driver module 45 | def import_module(name): 46 | mod = None 47 | try: 48 | mod = importlib.import_module(name) 49 | except ModuleNotFoundError as error: 50 | print("Error importing %s: %s" % (name, error)) 51 | return mod 52 | 53 | # Handler for XML document 54 | class XmlHandler(QtXml.QXmlDefaultHandler): 55 | def __init__(self, root): 56 | QtXml.QXmlDefaultHandler.__init__(self) 57 | self.root = root 58 | self.elem = None 59 | self.txt = self.err = "" 60 | self.elements = {} 61 | self.texts = [] 62 | self.reg_count = self.field_count = 0 63 | 64 | # Start of an XML document 65 | def startDocument(self): 66 | print("Loading %s... " % SVD_FNAME) 67 | return True 68 | 69 | # End of an XML document 70 | def endDocument(self): 71 | print("Found %u fields in %u registers" % (self.field_count, self.reg_count)) 72 | return True 73 | 74 | # Start of an XML element 75 | def startElement(self, namespace, name, qname, attributes): 76 | # Push previous element text onto stack 77 | self.texts.append(self.txt) 78 | self.txt = "" 79 | # If a key element.. 80 | if name in KEY_ELEMENTS: 81 | # If derived from another branch, copy child elements 82 | if attributes.length() and attributes.index("derivedFrom") >= 0: 83 | dervstr = attributes.value(attributes.index("derivedFrom")) 84 | if dervstr in self.elements: 85 | derv = self.elements[dervstr] 86 | self.elem = QtWidgets.QTreeWidgetItem(self.root) 87 | children = derv.clone().takeChildren() 88 | self.elem.addChildren(children) 89 | # Add key element to tree 90 | else: 91 | self.elem = QtWidgets.QTreeWidgetItem(self.root if self.elem is None else self.elem) 92 | return True 93 | 94 | # End of an XML element 95 | def endElement(self, namespace, name, qname): 96 | if self.elem is not None: 97 | # If this is a peripheral, count registers 98 | if name=='peripheral': 99 | self.reg_count += self.elem.childCount() 100 | # If this is a register, count fields 101 | if name=='register': 102 | self.field_count += self.elem.childCount() 103 | # Keep key elements at same level in tree 104 | if name in KEY_ELEMENTS: 105 | self.elem = self.elem.parent() 106 | # Handle additional elements, not in tree 107 | # Set peripheral/register name, save it for lookup 108 | elif name == 'name': 109 | self.elem.setText(PERIPH_COL, self.txt) 110 | self.elements[self.txt] = self.elem 111 | # Set peripheral base address 112 | elif name == "baseAddress": 113 | self.elem.setText(BASE_COL, self.txt) 114 | # Calculate register address from base & offset 115 | elif name == "addressOffset": 116 | self.elem.setText(OSET_COL, "0x%04x" % int(self.txt, 16)) 117 | # Display bit range 118 | elif name == "bitRange": 119 | self.elem.setText(BITS_COL, self.txt) 120 | # Get description 121 | elif name == "description": 122 | self.elem.setText(DESC_COL, self.txt) 123 | # Restore previous element text 124 | self.txt = self.texts.pop() 125 | return True 126 | 127 | # Handle characters within XML element 128 | def characters(self, text): 129 | self.txt += text 130 | return True 131 | 132 | # Handle parsing error 133 | def fatalError(self, ex): 134 | print("\nParse error in line %d, column %d: %s" % ( 135 | ex.lineNumber(), ex.columnNumber(), ex.message())) 136 | return False 137 | def errorString(self): 138 | return self.err 139 | 140 | # Widget to display tree derived from XML file 141 | class TreeWidget(QtWidgets.QWidget): 142 | def __init__(self, fname): 143 | QtWidgets.QWidget.__init__(self) 144 | self.item = None 145 | layout = QtWidgets.QVBoxLayout(self) 146 | self.tree = QtWidgets.QTreeWidget() 147 | self.tree.setColumnCount(VAL_COL + 1) 148 | self.tree.setHeaderLabels([*COL_LABELS]) 149 | self.tree.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) 150 | self.tree.setSortingEnabled(True) 151 | self.tree.sortByColumn(0, QtCore.Qt.AscendingOrder) 152 | self.tree.setRootIsDecorated(True) 153 | self.tree.setFont(TREE_FONT) 154 | self.tree.setStyleSheet(TREE_STYLE) 155 | layout.addWidget(self.tree) 156 | handler = XmlHandler(self.tree) 157 | reader = QtXml.QXmlSimpleReader() 158 | reader.setContentHandler(handler) 159 | reader.setErrorHandler(handler) 160 | f = QtCore.QFile(fname) 161 | source = QtXml.QXmlInputSource(f) 162 | reader.parse(source) 163 | 164 | # Peripheral/register/field selection has changed (mouse or kbd) 165 | # Print name and description 166 | def itemSelectionChanged(self): 167 | self.item = self.tree.selectedItems()[0] if self.tree.selectedItems() else None 168 | print(CLR_DISP, end="") 169 | if self.item: 170 | name = self.item_name(self.item) 171 | addr = self.item_address(self.item) 172 | bits = self.item_bits(self.item) 173 | desc = self.item_description(self.item) 174 | if name: 175 | print(name, end="") 176 | print(((" 0x%08x" % addr) if addr else ""), end="") 177 | print((" [%u:%u]" % bits) if bits is not None else "") 178 | print(("\n%s" % desc) if desc is not None else "") 179 | 180 | # Return name of selected item 181 | def item_name(self, item): 182 | s = "" 183 | if item: 184 | it = item 185 | while it: 186 | s = it.text(PERIPH_COL) + ("." if s else "") + s 187 | it = it.parent() 188 | return s 189 | 190 | # Return address of selected item 191 | def item_address(self, item): 192 | addr = None 193 | if item and item.text(BITS_COL): 194 | item = item.parent() 195 | if item and item.text(OSET_COL): 196 | addr = int(item.text(OSET_COL), 16) 197 | item = item.parent() 198 | if item and item.text(BASE_COL): 199 | base = int(item.text(BASE_COL), 16) 200 | addr = base if addr is None else base + addr 201 | return addr 202 | 203 | # Return bit range 204 | def item_bits(self, item): 205 | bits = None 206 | if item.text(BITS_COL): 207 | vals = BITRANGE.search(item.text(BITS_COL)) 208 | bits = int(vals.group(1)), int(vals.group(2)) 209 | return bits 210 | 211 | # Return description of item 212 | def item_description(self, item): 213 | return re.sub(DESC_EOL, "\n", item.text(DESC_COL)) if item else "" 214 | 215 | # Set value field of item 216 | def item_value_display(self, item, value): 217 | s = "" 218 | if value is not None: 219 | bits = self.item_bits(item) 220 | bits = bits if bits is not None else [31,0] 221 | nbits = (bits[0] - bits[1] + 1) 222 | val = (value >> bits[1]) & ((1 << nbits) - 1) 223 | s = ((" %u " % val) if nbits<=3 else 224 | (" 0x%1x" % val) if nbits<=4 else 225 | (" 0x%02x" % val) if nbits<=8 else 226 | (" 0x%04x" % val) if nbits<=16 else 227 | (" 0x%08x" % val)) 228 | item.setText(VAL_COL, s) 229 | 230 | # Set bits fields of item 231 | def item_bits_display(self, item, value): 232 | if item and value is not None and item.childCount(): 233 | for n in range(0, item.childCount()): 234 | self.item_value_display(item.child(n), value) 235 | 236 | # Main window 237 | class MainWindow(QtWidgets.QMainWindow): 238 | text_update = QtCore.pyqtSignal(str) 239 | 240 | def __init__(self, parent=None): 241 | # Create main window resources 242 | QtWidgets.QMainWindow.__init__(self, parent) 243 | self.running = False 244 | self.swdrv = self.conn = self.tries = None 245 | self.timer = QtCore.QTimer(self) 246 | self.timer.timeout.connect(self.do_timeout) 247 | self.central = QtWidgets.QWidget(self) 248 | self.textbox = QtWidgets.QTextEdit(self.central) 249 | self.textbox.setFont(TEXT_FONT) 250 | # Redirect print() to text box 251 | self.text_update.connect(self.append_text) 252 | sys.stdout = self 253 | print(VERSION) 254 | # Create layout 255 | vlayout = QtWidgets.QVBoxLayout() 256 | self.widget = TreeWidget(SVD_FNAME) 257 | vlayout.addWidget(self.widget) 258 | btn_layout = QtWidgets.QHBoxLayout() 259 | self.core_sel = QtWidgets.QComboBox(self) 260 | self.core_sel.addItems(CORE_NAMES) 261 | self.verbose_cbox = QtWidgets.QCheckBox("Verbose", self) 262 | self.verbose_cbox.stateChanged.connect(self.do_verbose) 263 | self.conn_btn = QtWidgets.QPushButton('Connect', self) 264 | self.conn_btn.pressed.connect(self.do_conn) 265 | self.single_btn = QtWidgets.QPushButton('Single', self) 266 | self.single_btn.pressed.connect(self.do_single) 267 | self.run_btn = QtWidgets.QPushButton('Run', self) 268 | self.run_btn.pressed.connect(self.do_run) 269 | self.exit_btn = QtWidgets.QPushButton('Exit', self) 270 | self.exit_btn.pressed.connect(self.close) 271 | btn_layout.addWidget(self.core_sel) 272 | btn_layout.addWidget(self.verbose_cbox) 273 | btn_layout.addWidget(self.conn_btn) 274 | btn_layout.addWidget(self.single_btn) 275 | btn_layout.addWidget(self.run_btn) 276 | btn_layout.addWidget(self.exit_btn) 277 | vlayout.addLayout(btn_layout) 278 | vlayout.addWidget(self.textbox) 279 | self.central.setLayout(vlayout) 280 | self.setCentralWidget(self.central) 281 | self.status = self.statusBar() 282 | self.widget.tree.itemSelectionChanged.connect(self.widget.itemSelectionChanged) 283 | self.do_conn(dis = True) 284 | 285 | # Establish SWD connection 286 | def connect(self): 287 | if self.conn: 288 | self.do_verbose() 289 | self.conn.set_core(self.core_sel.currentIndex()) 290 | self.conn.conn_func_retry(None) 291 | return False if not self.conn else self.conn.connected 292 | 293 | # Get single value from CPU register or memory, given address 294 | def get_value(self, addr): 295 | return self.conn.conn_func_retry(self.conn.peek, addr) 296 | 297 | # Stop SWD connection 298 | def disconnect(self): 299 | if self.conn and self.conn.connected: 300 | self.conn.disconnect() 301 | print("Disconnected") 302 | 303 | # Connect/disconnect button 304 | def do_conn(self, dis=False): 305 | # Import & initialise SWD module if not already imported 306 | if self.swdrv is None and not dis: 307 | self.swdrv = import_module(SWD_IMPORT) 308 | if self.swdrv: 309 | self.conn = self.swdrv.SwdConnection() 310 | self.conn.open() 311 | # Toggle connect/disconnect 312 | if self.conn: 313 | if self.conn.connected: 314 | self.do_run(stop=True) 315 | self.disconnect() 316 | elif not dis: 317 | self.connect() 318 | # Enable/disable buttons 319 | connected = self.conn is not None and self.conn.connected 320 | self.conn_btn.setText("Disconnect" if connected else "Connect") 321 | self.single_btn.setEnabled(connected) 322 | self.run_btn.setEnabled(connected) 323 | self.core_sel.setEnabled(not connected) 324 | 325 | # Do single read cycle 326 | def do_single(self): 327 | if self.running: 328 | self.do_run(stop = True) 329 | self.do_read() 330 | 331 | # Do read cycle 332 | def do_read(self): 333 | if self.widget.item is not None: 334 | # Get address in CPU memory space 335 | addr = self.widget.item_address(self.widget.item) 336 | if addr is not None: 337 | # Get value, disconnect if error 338 | val = self.get_value(addr) 339 | if val is None: 340 | print("SWD link failure") 341 | self.do_run(stop = True) 342 | self.do_conn(dis = True) 343 | else: 344 | # Display register value, and bit field values 345 | item = self.widget.item 346 | if item.text(BITS_COL): 347 | item = item.parent() 348 | if item.text(OSET_COL): 349 | self.widget.item_value_display(item, val) 350 | self.widget.item_bits_display(item, val) 351 | self.status.showMessage("Reading...", STATUS_MSEC) 352 | 353 | # Start/stop data transfers 354 | def do_run(self, stop=False): 355 | if self.running: 356 | self.timer.stop() 357 | self.running = False 358 | elif not stop and self.conn and self.conn.connected: 359 | self.timer.start(DISP_MSEC) 360 | self.running = True 361 | self.run_btn.setText("Stop" if self.running else "Run") 362 | 363 | # Timer timeout 364 | def do_timeout(self): 365 | self.do_read() 366 | 367 | # Verbose checkbox state has changed 368 | def do_verbose(self): 369 | if self.conn and self.conn.msg: 370 | self.conn.msg.verbose = self.verbose_cbox.isChecked() 371 | 372 | # Handle sys.stdout.write: update text display 373 | def write(self, text): 374 | self.text_update.emit(str(text)) 375 | def flush(self): 376 | pass 377 | 378 | # Append to text display (for print function) 379 | def append_text(self, text): 380 | if text.startswith(CLR_DISP): 381 | self.textbox.setText("") 382 | text = text[1:] 383 | cur = self.textbox.textCursor() # Move cursor to end of text 384 | cur.movePosition(QtGui.QTextCursor.End) 385 | s = str(text) 386 | while s: 387 | head,sep,s = s.partition("\n") # Split line at LF 388 | cur.insertText(head) # Insert text at cursor 389 | if sep: # New line if LF 390 | cur.insertBlock() 391 | self.textbox.setTextCursor(cur) # Update visible cursor 392 | 393 | # Window is closing 394 | def closeEvent(self, event): 395 | self.do_run(stop=True) 396 | self.disconnect() 397 | if self.conn: 398 | self.conn.close() 399 | 400 | if __name__ == '__main__': 401 | if not os.path.isfile(SVD_FNAME): 402 | print("Can't find %s" % SVD_FNAME) 403 | else: 404 | print("Loading %s... " % SVD_FNAME) 405 | app = QtWidgets.QApplication(sys.argv) 406 | win = MainWindow() 407 | win.resize(*WIN_SIZE) 408 | win.show() 409 | win.setWindowTitle(VERSION) 410 | sys.exit(app.exec_()) 411 | # EOF 412 | --------------------------------------------------------------------------------