Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "[email protected]"},
{name = "Richard Unger", email = "[email protected]"},
]
readme = "README.md"
license = {text = "LGPL"}
Expand All @@ -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",
Expand All @@ -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 <[email protected]>"]
readme = "README.md"
Expand All @@ -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'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is pointing to a script that doesn't exist (in this PR at least?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh sorry - it's a utility for flashing NOR flash chips and similar devices that I'm working on. You need something a bit more complicated than the SPI write of the general CLI, and there are many options, so I thought a separate cy_flash cli program is the way to go.
Its a work in progress, the branch for it is here: https://github.com/runger1101001/cy_serial_bridge/tree/cy_flash_utility

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I'll make a separate PR when its ready so for now I will back out this change from this PR.


[tool.poetry.urls]
"Tracker" = 'https://github.com/mbed-ce/cy_serial_bridge/issues'
Expand Down
62 changes: 62 additions & 0 deletions src/cy_serial_bridge/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
155 changes: 91 additions & 64 deletions src/cy_serial_bridge/cy_scb_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -110,79 +167,49 @@ 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
cdc_data_interface_settings: usb1.USBInterfaceSetting | None = None
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(
Expand Down
40 changes: 40 additions & 0 deletions src/cy_serial_bridge/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions src/cy_serial_bridge/usb_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random question, do you have a source for what this interface actually does? I don't actually know, and I think I wasn't able to find out easily back when I was looking into this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I was curious myself... There is a CDC defintion for SPI devices also, maybe its related to that?


class CyVendorCmds(IntEnum):
CY_GET_VERSION_CMD = 0xB0
Expand All @@ -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

Expand Down Expand Up @@ -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):
Expand Down