From 17ce7799a7059c14639eb88da23a1b2d3c77f05a Mon Sep 17 00:00:00 2001 From: Matt Mets Date: Wed, 8 Mar 2023 16:20:00 +0100 Subject: [PATCH] Rename programmer library, update VID/PID for programmer --- hw/production_test/Makefile | 2 +- hw/production_test/binaries/main.uf2 | Bin 47616 -> 47616 bytes hw/production_test/iceflasher.py | 364 +++++++++++++++++++++++++ hw/production_test/production_tests.py | 2 +- hw/production_test/pynvcm.py | 2 +- hw/production_test/reset.py | 2 +- 6 files changed, 368 insertions(+), 4 deletions(-) create mode 100755 hw/production_test/iceflasher.py diff --git a/hw/production_test/Makefile b/hw/production_test/Makefile index c64fc7f..c2023e5 100644 --- a/hw/production_test/Makefile +++ b/hw/production_test/Makefile @@ -7,7 +7,7 @@ PYTHON_FILES = \ pybin2nvcm.py \ pynvcm.py \ reset.py \ - usb_test.py + iceflasher.py # autopep8: Fixes simple format errors automatically diff --git a/hw/production_test/binaries/main.uf2 b/hw/production_test/binaries/main.uf2 index 751694e31c72f7679c27d05c05b394cf71c25d60..a2da8813fdbb8953cee13be01f8ea109af5606bb 100644 GIT binary patch delta 713 zcmYjPOK1~O6n$?p(L{(&W`+`>;InNcrHHYP&_x&0S{k&KDnUhDSV2f2V$wv2ZY(ZJ z7K-AP8be!StRdDRrg>=^aM3PQx^PkafM`$(E(9ZX5wVL%yhCTvfy118?mg$-H_Uil z7_SS34Z%uI5Xr;;31{eT57K2q*xQ|iY3GSVh{z0Ra9Qkk2Zo5Wjhq^pXtfA&ll$8; z(+T8O)W2S;2Q>asj5_T4aWwnFgSX(Me22#Pn!IGC%lxsc-9@YXwYRIqNzf%3SfIY< z-|tP8)4_fxeZ>dPF4@Et?0|mJ#o1_wLXW~g?0_{eOr%wQE%w`LUqxDT5_XqIuJ3A< z{lQQu7z;)TKK_yY2MK}fWS?l4ClXMb)PN;bh*hf4c+de_^0wB)y1m_ph;f;h&E9et z4>NeG{j{scRM9KvlC8QU>&=KgY>;W%bfySa6+Ni>Pl@dik K6}?yHcl-ydbqnhN delta 1040 zcmYjQZDiHLDNUkPsnTko6*@6lQ#PsDM6rsx z-Svaw2ST4{Nvl*VRkTX&^a~W4KlO(w*j1sZ$W}}1LKg#DsIGP64GFXZXWp5a`_4J{ zy?OPMT>T{1`I*y*J3uH>zj1rW;v%Fm0Qz)3P*)WQ06a~?#e#gX<=8e5=D@D9Guav3 z`7|%jy>Yb#xdz|A^G*6)>MK5?)5c11vMc0lWjhTQNrO6K7|@WH>UERZMB?h8Y+a-m zah8M-dWb7AHJZp%iL+QwhSd`HKqmhdzpxIENu`QpByLu6aJA;xb`VC@BL_cew4+ET z^gwT_zaCwXz1$wCsr{v&#t5 z&ghM_NSh==y!~q)Dj2`M9tY5ufvhiM^yVy=cB2S6GPDFBuA9yBrzKhX@ej20Lk|MX2Cs1(nm%=edJBGzLE?DN)3@`+MHqOm>8Lj(Mo9g(U zB$9l19&!$!;MN!0-J4@wpT}bkM(@u=ai^lVXGN)8Y*zlpI-!S$lYYoq>Q78D+F8f( z+gY?ToYH bool: + for device in self.context.getDeviceList(): + if device.getVendorID() == 0xcafe and device.getProductID() == 0x4004: + return True + if device.getVendorID() == 0xcafe and device.getProductID() == 0x4010: + return True + + return False + + def __init__(self) -> None: + self.transfer_list: List[Any] = [] + + # See: https://github.com/vpelletier/python-libusb1#usage + self.context = usb1.USBContext() + self.handle = self.context.openByVendorIDAndProductID( + 0x1209, + 0x8886, + skip_on_error=True, + ) + + if self.handle is None: + # Device not present, or user is not allowed to access + # device. + if self._check_for_old_firmware(): + raise ValueError( + 'Programmer with outdated firmware found- please update!') + else: + raise ValueError('Programmer not found') + + # Check the device firmware version + bcd_device = self.handle.getDevice().getbcdDevice() + if bcd_device != 0x0200: + raise ValueError( + 'Pico firmware version out of date- please upgrade') + + self.handle.claimInterface(0) + + self.cs_pin = -1 + + def __del__(self) -> None: + self.close() + + def close(self) -> None: + """ Release the USB device handle """ + self._wait_async() + + if self.handle is not None: + self.handle.close() + self.handle = None + self.context.close() + self.context = None + + def _wait_async(self) -> None: + # Wait until all submitted transfers can be cleared + while any(transfer.isSubmitted() + for transfer in self.transfer_list): + try: + self.context.handleEvents() + except usb1.USBErrorInterrupted: + pass + + for transfer in reversed(self.transfer_list): + if transfer.getStatus() == \ + usb1.TRANSFER_COMPLETED: + self.transfer_list.remove(transfer) + else: + print( + transfer.getStatus(), + usb1.TRANSFER_COMPLETED) + + def _write( + self, + request_id: int, + data: bytes, + nonblocking: bool = False) -> None: + + if nonblocking: + transfer = self.handle.getTransfer() + transfer.setControl( + # usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | + # usb1.RECIPIENT_DEVICE, #request type + 0x40, + request_id, # request + 0, # index + 0, + data, # data + callback=None, # callback functiopn + user_data=None, # userdata + timeout=1000 + ) + transfer.submit() + self.transfer_list.append(transfer) + else: + self.handle.controlWrite( + 0x40, request_id, 0, 0, data, timeout=100) + + def _read(self, request_id: int, length: int) -> bytes: + # self._wait_async() + return self.handle.controlRead( + 0xC0, request_id, 0, 0, length, timeout=100) + + def gpio_set_direction(self, pin: int, direction: bool) -> None: + """Set the direction of a single GPIO pin + + Keyword arguments: + pin -- GPIO pin number + value -- True: Set pin as output, False: set pin as input + """ + msg = struct.pack('>II', + (1 << pin), + ((1 if direction else 0) << pin), + ) + + self._write(self.COMMAND_PIN_DIRECTION, msg) + + def gpio_set_pulls( + self, + pin: int, + pullup: bool, + pulldown: bool) -> None: + """Configure the pullup/down resistors for a single GPIO pin + + Keyword arguments: + pin -- GPIO pin number + pullup -- True: Enable pullup, False: Disable pullup + pulldown -- True: Enable pulldown, False: Disable pulldown + """ + msg = struct.pack('>III', + (1 << pin), + ((1 if pullup else 0) << pin), + ((1 if pulldown else 0) << pin), + ) + + self._write(self.COMMAND_PULLUPS, msg) + + def gpio_put(self, pin: int, val: bool) -> None: + """Set the output level of a single GPIO pin + + Keyword arguments: + pin -- GPIO pin number + val -- True: High, False: Low + """ + msg = struct.pack('>II', + 1 << pin, + (1 if val else 0) << pin, + ) + + self._write(self.COMMAND_PIN_VALUES, msg) + + def gpio_get_all(self) -> int: + """Read the input levels of all GPIO pins""" + msg_in = self._read(self.COMMAND_PIN_VALUES, 4) + [gpio_states] = struct.unpack('>I', msg_in) + + return gpio_states + + def gpio_get(self, pin: int) -> bool: + """Read the input level of a single GPIO pin + + Keyword arguments: + pin -- GPIO pin number + """ + gpio_states = self.gpio_get_all() + + return ((gpio_states >> pin) & 0x01) == 0x01 + + def spi_configure( + self, + sck_pin: int, + cs_pin: int, + mosi_pin: int, + miso_pin: int, + clock_speed: int) -> None: + """Set the pins to use for SPI transfers + + Keyword arguments: + sck_pin -- GPIO pin number to use as the SCK signal + cs_pin -- GPIO pin number to use as the CS signal + mosi_pin -- GPIO pin number to use as the MOSI signal + miso_pin -- GPIO pin number to use as the MISO signal + clock_speed -- SPI clock speed, in MHz + """ + header = struct.pack('>BBBBB', + sck_pin, + cs_pin, + mosi_pin, + miso_pin, + clock_speed) + msg = bytearray() + msg.extend(header) + + self._write(self.COMMAND_SPI_CONFIGURE, msg) + + self.cs_pin = cs_pin + + def spi_write( + self, + buf: bytes, + toggle_cs: bool = True) -> None: + """Write data to the SPI port + + Keyword arguments: + buf -- Byte buffer to send. + toggle_cs -- (Optional) If true, toggle the CS line + """ + self._spi_xfer(buf, toggle_cs, False) + + def spi_rxtx( + self, + buf: bytes, + toggle_cs: bool = True) -> bytes: + """Bitbang a SPI transfer + + Keyword arguments: + buf -- Byte buffer to send. + toggle_cs -- (Optional) If true, toggle the CS line + """ + + return self._spi_xfer(buf, toggle_cs, True) + + def _spi_xfer( + self, + buf: bytes, + toggle_cs: bool, + read_after_write: bool) -> bytes: + + ret = bytearray() + + if len(buf) <= self.SPI_MAX_TRANSFER_SIZE: + return self._spi_xfer_inner( + buf, + toggle_cs, + read_after_write) + + if toggle_cs: + self.gpio_put(self.cs_pin, False) + + for i in range(0, len(buf), self.SPI_MAX_TRANSFER_SIZE): + chunk = buf[i:i + self.SPI_MAX_TRANSFER_SIZE] + ret.extend( + self._spi_xfer_inner( + chunk, + False, + read_after_write)) + + if toggle_cs: + self.gpio_put(self.cs_pin, True) + + return bytes(ret) + + def _spi_xfer_inner( + self, + buf: bytes, + toggle_cs: bool, + read_after_write: bool) -> bytes: + """Bitbang a SPI transfer using the specificed GPIO pins + + Keyword arguments: + buf -- Byte buffer to send. + toggle_cs -- (Optional) If true, toggle the CS line + """ + + if len(buf) > self.SPI_MAX_TRANSFER_SIZE: + raise ValueError( + 'Message too large, ' + + f'size:{len(buf)} max:{self.SPI_MAX_TRANSFER_SIZE}') + + header = struct.pack('>BI', toggle_cs, len(buf)) + msg = bytearray() + msg.extend(header) + msg.extend(buf) + + self._write(self.COMMAND_SPI_XFER, msg) + + if not read_after_write: + return bytes() + + msg_in = self._read( + self.COMMAND_SPI_XFER, + len(buf)) + + return msg_in + + def spi_clk_out(self, byte_count: int) -> None: + """Run the SPI clock without transferring data + + This function is useful for SPI devices that need a clock to + advance their state machines. + + Keyword arguments: + byte_count -- Number of bytes worth of clocks to send + """ + + header = struct.pack('>I', + byte_count) + msg = bytearray() + msg.extend(header) + self._write( + self.COMMAND_SPI_CLKOUT, + msg) + + def adc_read_all(self) -> tuple[float, float, float]: + """Read the voltage values of ADC 0, 1, and 2 + + The firmware will read the values for each input multiple + times, and return averaged values for each input. + """ + msg_in = self._read(self.COMMAND_ADC_READ, 3 * 4) + [ch0, ch1, ch2] = struct.unpack('>III', msg_in) + + return ch0 / 1000000, ch1 / 1000000, ch2 / 1000000 + + def bootloader(self) -> None: + """Reset the programmer to bootloader mode + + After the device is reset, it can be programmed using + picotool, or by copying a file to the uf2 drive. + """ + try: + self._write(self.COMMAND_BOOTLOADER, bytes()) + except usb1.USBErrorIO: + pass diff --git a/hw/production_test/production_tests.py b/hw/production_test/production_tests.py index 8d6398c..5f2ed3d 100755 --- a/hw/production_test/production_tests.py +++ b/hw/production_test/production_tests.py @@ -39,7 +39,7 @@ import serial # type: ignore import serial.tools.list_ports # type: ignore import usb.core # type: ignore import encode_usb_strings -from usb_test import IceFlasher +from iceflasher import IceFlasher # Locations for external utilities and files referenced by the test # program diff --git a/hw/production_test/pynvcm.py b/hw/production_test/pynvcm.py index db8405f..3d2b7b0 100755 --- a/hw/production_test/pynvcm.py +++ b/hw/production_test/pynvcm.py @@ -26,7 +26,7 @@ import os import sys import struct from time import sleep -from usb_test import IceFlasher +from iceflasher import IceFlasher from pybin2nvcm import pybin2nvcm diff --git a/hw/production_test/reset.py b/hw/production_test/reset.py index 179556d..0c15e15 100755 --- a/hw/production_test/reset.py +++ b/hw/production_test/reset.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """Automatically reset a TK-1""" -from usb_test import IceFlasher +from iceflasher import IceFlasher def reset_tk1() -> None: