diff --git a/pyproject.toml b/pyproject.toml index 6c2eba3..5921891 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,10 +3,11 @@ name = "cy_serial_bridge" # this is used by Ruff to disable "upgrade to feature x" inspections where x was added # after the given version requires-python = ">=3.10" -version = "0.3.2" +version = "0.3.3" description = "Pure Python driver for using and reconfiguring the CY7C652xx family of USB to SPI/I2C/UART bridge ICs." authors = [ {name = "Jamie Smith", email = "smit109@usc.edu"}, + {name = "Richard Unger", email = "runger1@gmail.com"}, ] readme = "README.md" license = {text = "LGPL"} @@ -15,7 +16,7 @@ classifiers = [ # 3 - Alpha # 4 - Beta # 5 - Production/Stable - "Development Status :: 3 - Beta", + "Development Status :: 3 - Alpha", # Indicate who your project is intended for "Intended Audience :: Developers", @@ -33,7 +34,7 @@ classifiers = [ [tool.poetry] name = "cy_serial_bridge" -version = "0.3.2" +version = "0.3.3" description = "Pure Python driver for using and reconfiguring the CY7C652xx family of USB to SPI/I2C/UART bridge ICs." authors = ["Jamie Smith "] readme = "README.md" @@ -45,6 +46,7 @@ repository = 'https://github.com/mbed-ce/cy_serial_bridge/' [tool.poetry.scripts] cy_serial_cli = 'cy_serial_bridge.cli:main' +cy_flash = 'cy_serial_bridge.flash:main' [tool.poetry.urls] "Tracker" = 'https://github.com/mbed-ce/cy_serial_bridge/issues' diff --git a/src/cy_serial_bridge/cli.py b/src/cy_serial_bridge/cli.py index b8a8fdd..52eeeb4 100644 --- a/src/cy_serial_bridge/cli.py +++ b/src/cy_serial_bridge/cli.py @@ -544,6 +544,68 @@ def serial_term( term.close() +# GPIO command +# --------------------------------------------------------------------------------------------- + +GpioArgument = typer.Argument( + help="GPIOs to get/set. A string in the format 'io1 io4 io2=0 io3=1'." +) + +class GpioOutputStyle(str, enum.Enum): + """ + Enum of output styles for the GPIO command + """ + ASCII = "ascii" + PLAIN = "plain" + JSON = "json" + +GpioOutputStyleOption = typer.Option("--output-style", help="Output style to use", case_sensitive=False) + + +@app.command(help="Set/Get GPIO pins on the CY7C652xx") +def gpio( + gpio_opt: Annotated[str, GpioArgument] = "", outstyle: Annotated[GpioOutputStyle, GpioOutputStyleOption] = GpioOutputStyle.ASCII +) -> None: + with cast( + cy_serial_bridge.driver.CyMfgrIface, + context.open_device( + global_opt.vid, global_opt.pid, cy_serial_bridge.OpenMode.MFGR_INTERFACE, global_opt.serial_number + ), + ) as dev: + if outstyle == GpioOutputStyle.JSON: + print("[") + dev.connect() + gpio_opts = gpio_opt.split() + first = True + for opt in gpio_opts: + if "=" in opt: + pin, value = opt.split("=") + pin = int(pin.strip("io")) + value = int(value) + dev.set_gpio(pin, value) + else: + pin = int(opt.strip("io")) + value = dev.get_gpio(pin) + if outstyle == GpioOutputStyle.ASCII: + if not first: + print(" ", end="") + print(f"io{pin}={value}", end="") + elif outstyle == GpioOutputStyle.PLAIN: + print(f"{value}") + elif outstyle == GpioOutputStyle.JSON: + if not first: + print(",") + print(f'{{"pin": {pin}, "value": {value}}}', end="") + first = False + dev.disconnect() + if outstyle == GpioOutputStyle.JSON: + print("") + print("]") + elif outstyle == GpioOutputStyle.ASCII: + print("") + + + def main() -> None: app() diff --git a/src/cy_serial_bridge/cy_scb_context.py b/src/cy_serial_bridge/cy_scb_context.py index 4473bff..6947e40 100644 --- a/src/cy_serial_bridge/cy_scb_context.py +++ b/src/cy_serial_bridge/cy_scb_context.py @@ -72,6 +72,63 @@ def _find_serial_port_name_for_serno(serial_number: str) -> str | None: return None + + + def identify_interface(self, intf: usb1.USBInterface) -> CyType|None: + """ + Identify the current interface of a device. + + This is useful for determining the current mode of a device, as the interface is the only part of the device + that can be queried without opening it. + """ + if intf[0].getClass() == USBClass.CDC: + if intf[0].getSubClass() == 0x2: + return CyType.UART_CDC + # elif intf[0].getSubClass() == ?? + # return CyType.SPI_CDC + elif intf[0].getClass() == 0x0A: + if intf[0].getSubClass() == 0x0: + return CyType.CDC_DATA + elif intf[0].getClass() == 0xFF: + # Check manufacturer interface. + # It has a defined class/subclass and has no endpoints + if intf[0].getNumEndpoints() != 0: + return None + if intf[0].getSubClass() == CyType.MFG: + return CyType.MFG + elif intf[0].getClass() == USBClass.VENDOR: + if intf[0].getSubClass() not in { + CyType.UART_VENDOR.value, + CyType.SPI.value, + CyType.I2C.value, + CyType.JTAG.value, + }: + return None + if intf[0].getNumEndpoints() != 3: + return None + # Bulk host-to-dev endpoint + if ( + not (intf[0].getAddress() in [0x01, 0x04]) + or (intf[0].getAttributes() & 0x3) != 2 + ): + return None + # Bulk dev-to-host endpoint + if ( + not (intf[1].getAddress() in [0x82, 0x85]) + or (intf[1].getAttributes() & 0x3) != 2 + ): + return None + # Interrupt dev-to-host endpoint + if ( + not (intf[2].getAddress() in [0x83, 0x86]) + or (intf[2].getAttributes() & 0x3) != 3 + ): + return None + return CyType(intf[0].getSubClass()) + return None + + + def list_devices( self, vid_pids: Set[tuple[int, int]] | None = DEFAULT_VIDS_PIDS, @@ -110,7 +167,9 @@ def list_devices( # CY7C652xx devices always have either two or three interfaces: potentially one for the USB CDC COM port, # one for the actual USB-serial bridge, and one for the configuration interface. - if cfg.getNumInterfaces() != 2 and cfg.getNumInterfaces() != 3: + # CY7C65215 and CY7C65215A devices have (up to?) 4 interfaces. + # CY7C65215 devices could have 0-2 CDC interfaces, up to one on each SCB + if cfg.getNumInterfaces() != 2 and cfg.getNumInterfaces() != 3 and cfg.getNumInterfaces() != 4: continue usb_cdc_interface_settings: usb1.USBInterfaceSetting | None = None @@ -118,71 +177,39 @@ def list_devices( scb_interface_settings: usb1.USBInterfaceSetting | None = None mfg_interface_settings: usb1.USBInterfaceSetting - if cfg.getNumInterfaces() == 3 and cfg[0][0].getClass() == USBClass.CDC: - # USB CDC mode - usb_cdc_interface_settings = cfg[0][0] - cdc_data_interface_settings = cfg[1][0] - mfg_interface_settings = cfg[2][0] - - # Check USB CDC interface - if usb_cdc_interface_settings.getSubClass() != 0x2: - continue - - # Check CDC Data interface - if cdc_data_interface_settings.getClass() != 0x0A or cdc_data_interface_settings.getSubClass() != 0x0: - continue - - curr_cytype = CyType.UART_CDC - - else: - # USB vendor mode - scb_interface_settings = cfg[0][0] - mfg_interface_settings = cfg[1][0] - - # Check SCB interface -- the Class should be 0xFF (vendor defined/no rules) - # and the SubClass value gives the CyType - if scb_interface_settings.getClass() != USBClass.VENDOR: - continue - if scb_interface_settings.getSubClass() not in { - CyType.UART_VENDOR.value, - CyType.SPI.value, - CyType.I2C.value, - }: - continue - - # Check SCB endpoints - if scb_interface_settings.getNumEndpoints() != 3: - continue - # Bulk host-to-dev endpoint - if ( - scb_interface_settings[0].getAddress() != 0x01 - or (scb_interface_settings[0].getAttributes() & 0x3) != 2 - ): - continue - # Bulk dev-to-host endpoint - if ( - scb_interface_settings[1].getAddress() != 0x82 - or (scb_interface_settings[1].getAttributes() & 0x3) != 2 - ): - continue - # Interrupt dev-to-host endpoint - if ( - scb_interface_settings[2].getAddress() != 0x83 - or (scb_interface_settings[2].getAttributes() & 0x3) != 3 - ): - continue - - curr_cytype = CyType(scb_interface_settings.getSubClass()) - - # Check manufacturer interface. - # It has a defined class/subclass and has no endpoints - if mfg_interface_settings.getClass() != 0xFF: - continue - if mfg_interface_settings.getSubClass() != CyType.MFG: - continue - if mfg_interface_settings.getNumEndpoints() != 0: + for i in range(cfg.getNumInterfaces()): + type = self.identify_interface(cfg[i]) + if type == None: + pass # TODO verbose output + else: + match(type): + case CyType.UART_CDC: # TODO we could have two of these! + usb_cdc_interface_settings = cfg[i][0] + curr_cytype = CyType.UART_CDC + case CyType.CDC_DATA: + cdc_data_interface_settings = cfg[i][0] + case CyType.MFG: + mfg_interface_settings = cfg[i][0] + case CyType.I2C: # TODO we could have two of these! + scb_interface_settings = cfg[i][0] + curr_cytype = CyType.I2C + case CyType.SPI: + scb_interface_settings = cfg[i][0] + curr_cytype = CyType.SPI + case CyType.JTAG: + scb_interface_settings = cfg[i][0] + curr_cytype = CyType.JTAG + case CyType.UART_VENDOR: + scb_interface_settings = cfg[i][0] + curr_cytype = CyType.UART_VENDOR + + if curr_cytype is None or mfg_interface_settings is None \ + or (scb_interface_settings is None and usb_cdc_interface_settings is None): + # TODO verbose output continue + if mfg_interface_settings is not None: curr_cytype = CyType.MFG + # If we got all the way here, it looks like a CY6C652xx device! # Record attributes and add it to the list list_entry = DiscoveredDevice( diff --git a/src/cy_serial_bridge/driver.py b/src/cy_serial_bridge/driver.py index 57ad05a..c0609a9 100644 --- a/src/cy_serial_bridge/driver.py +++ b/src/cy_serial_bridge/driver.py @@ -366,6 +366,46 @@ def read_user_flash(self, addr: int, size: int) -> bytearray: return result_bytes + def set_gpio(self, gpio_nr: int, value: bool) -> None: + """ + Set the value of a GPIO pin. + + :param pin: GPIO pin number to set + :param value: Value to set the pin to + """ + + self.dev.controlWrite( + request_type=CY_VENDOR_REQUEST_HOST_TO_DEVICE, + request=CyVendorCmds.CY_GPIO_SET_VALUE_CMD, + value=gpio_nr, + index=1 if value==True else 0, + data=[], + timeout=self.timeout + ) + # TODO check for errors + + + def get_gpio(self, gpio_nr: int) -> bool: + """ + Get the value of a GPIO pin. + :param pin: GPIO pin number to get + :return: Value of the pin + """ + + result_bytes = self.dev.controlRead( + request_type=CY_VENDOR_REQUEST_DEVICE_TO_HOST, + request=CyVendorCmds.CY_GPIO_GET_VALUE_CMD, + value=gpio_nr, + index=0, + length=CY_GET_GPIO_LEN, + timeout=self.timeout + ) + if len(result_bytes) != CY_GET_GPIO_LEN or result_bytes[0] != 0: + message = f"Error getting GPIO {gpio_nr}" + raise CySerialBridgeError(message) + return (result_bytes[1]==1) + + class CyMfgrIface(CySerBridgeBase): """ Class allowing access to a CY7C652xx in the manufacturing interface mode. diff --git a/src/cy_serial_bridge/usb_constants.py b/src/cy_serial_bridge/usb_constants.py index c26b105..ce133d8 100644 --- a/src/cy_serial_bridge/usb_constants.py +++ b/src/cy_serial_bridge/usb_constants.py @@ -59,7 +59,7 @@ class CyType(IntEnum): 6 # Used to indicate a device which is in CDC UART mode (which will automatically work using an OS driver) ) UART_PHDC = 7 # Used to indicate a device which is in PHDC (Personal Healthcare Device Class) UART mode - + CDC_DATA = 8 # Used to indicate the CDC data interface (which is a separate interface from the CDC UART interface) class CyVendorCmds(IntEnum): CY_GET_VERSION_CMD = 0xB0 @@ -86,8 +86,13 @@ class CyVendorCmds(IntEnum): CY_JTAG_READ_CMD = 0xD2 CY_JTAG_WRITE_CMD = 0xD3 + # From Infineon's forums on Mar 24, 2023, here: https://community.infineon.com/t5/USB-low-full-high-speed/CY7C65215-Get-Set-GPIO-Config/td-p/336658 + # "CY_GPIO_GET_CONFIG_CMD is maintaining in CyUSBCommon.h but it has not been implemented at the + # hoist side and Silicon so this will not work, we apologize for the confusion with this code + # will remove this in the upcoming release so it does not create confusion to the user." CY_GPIO_GET_CONFIG_CMD = 0xD8 CY_GPIO_SET_CONFIG_CMD = 0xD9 + # GET_VALUE and SET_VALUE are implemented and can be used CY_GPIO_GET_VALUE_CMD = 0xDA CY_GPIO_SET_VALUE_CMD = 0xDB @@ -191,7 +196,8 @@ class CyUart(IntEnum): CY_GET_SILICON_ID_LEN = 4 CY_GET_FIRMWARE_VERSION_LEN = 8 CY_GET_SIGNATURE_LEN = 4 - +CY_GET_GPIO_LEN = 2 +CY_SET_GPIO_LEN = 1 # PHDC related macros class CyPhdc(IntEnum):