From df4ae29a42ac4ccf807101b6a23b96ae20c5f6bc Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 25 Feb 2024 12:07:22 -0800 Subject: [PATCH] Implement workaround with changing the default settings. SPI, I2C, and UART tests passing! --- README.md | 30 ++++++++++++++---- ..._ce_cy7c65211_spi_with_notificationled.bin | Bin 0 -> 512 bytes .../mbed_ce_cy7c65211_uart.bin | Bin 512 -> 0 bytes pyproject.toml | 2 +- rules/61-cy-serial-bridge.rules | 5 +++ src/cy_serial_bridge/configuration_block.py | 21 ++++++++++-- src/cy_serial_bridge/driver.py | 13 +++++++- tests/test_driver.py | 4 +++ 8 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 example_config_blocks/mbed_ce_cy7c65211_spi_with_notificationled.bin delete mode 100644 example_config_blocks/mbed_ce_cy7c65211_uart.bin diff --git a/README.md b/README.md index ffe1ae4..42317cc 100644 --- a/README.md +++ b/README.md @@ -42,13 +42,14 @@ Additionally, I assume that it would be possible to brick your CY7C652xx by load - SPI peripheral/slave mode operation - CapSense - GPIO +- JTAG (only supported on the larger dual channel devices) - Scanning & discovering dual channel CY7C652xx devices (e.g. CY7C65215) ## Using the Command-Line Interface This driver installs a command-line interface script, `cy_serial_bridge_cli`. It supports a number of functions: ``` -usage: cy_serial_bridge_cli [-h] [-V VID] [-P PID] [-n NTH] [-s SCB] [-v] {scan,save,load,decode,type,reconfigure} ... +usage: cy_serial_cli [-h] [-V VID] [-P PID] [-n NTH] [-s SCB] [-v] {scan,save,load,decode,type,reconfigure} ... positional arguments: {scan,save,load,decode,type,reconfigure} @@ -69,9 +70,10 @@ options: ``` -In particular, the `reconfigure` option can be used to edit settings of a connected device, then write those settings back. The options you pass tell it what to reconfigure: +### Changing Settings +The `reconfigure` subcommand can be used to edit settings of a connected device, then write those settings back. The options you pass tell it what to reconfigure: ``` -usage: cy_serial_bridge_cli reconfigure [-h] [--randomize-serno] [--set-vid SET_VID] [--set-pid SET_PID] +usage: cy_serial_cli reconfigure [-h] [--randomize-serno] [--set-vid SET_VID] [--set-pid SET_PID] options: -h, --help show this help message and exit @@ -80,6 +82,19 @@ options: --set-pid SET_PID Set the USB Product ID to a given value. Needs a 0x prefix for hex values! ``` +Additionally, the `type` subcommand can be used to change the device's type. However, this is mostly for debugging and for using the serial bridge with other programs, because the type is changed automatically when you open the device in SPI, I2C, or UART mode. + +### Scanning for Devices +The `scan` command can be used to find CY7C652xx devices attached to your system. Since these devices have configurable VIDs and PIDs, a heuristic search is used based on each connected device's USB descriptor. By default, only devices with the default VID and PIDs (see "Setting Up New Devices" below) are considered, but if you use `scan --all`, that will search all USB devices on your system. + +For each detected device, the serial number, type, and (if in UART CDC mode) the corresponding serial port is output. + +``` +> cy_serial_cli scan --all +Detected Devices: +- 04b4:e011 (Type: UART_CDC) (SerNo: 14224672048496620243684302669570) (Type: UART_CDC) (Serial Port: 'COM6') +``` + ## OS-Specific Info ### Windows @@ -115,13 +130,13 @@ However, when using the chips with this driver, we generally want them to have a To get around these issues, I'm adopting the convention that we'll assign CY7C65xx devices the regular Cypress VID, but use an arbitrary new VID of 0xE010. You can set this configuration on a new device with a command like: ```shell -cy_serial_bridge_cli --vid 0x04b4 --pid reconfigure --set-pid 0xE010 +cy_serial_cli --vid 0x04b4 --pid reconfigure --set-pid 0xE010 ``` (note that this has to be run from a `poetry shell` if developing locally) To determine what the VID and PID of your device currently are, you can use: ```shell -cy_serial_bridge_cli scan --all +cy_serial_cli scan --all ``` This will do a heuristic search of all the USB devices on your machine to find ones which "look like" CY7C652xx chips based on their descriptor layout. @@ -129,18 +144,19 @@ Also note that adding `--randomize-serno` to that command will assign a random s Another issue: On Windows, if you have a given VID and PID assigned to use the WinUSB driver via Zadig, Windows will not try and use the USB CDC driver to enumerate COM ports from the device. This means that a device in SPI/I2C mode cannot use the same VID and PID as a device in UART CDC mode. To solve this, this driver automatically uses two PIDs for each device. The even PID is used in SPI/I2C mode, and the odd PID is used in UART CDC mode. -Be careful with this, though. If you plan to use this driver in a real product, this strategy will not be usable, as we are basically "squatting" on Cypress's VID space without paying. You will have to sort out a VID and two consecutive PID values for yourself I'm afraid. +WARNING: This setup is for dev testing only! If you plan to use this driver in a real product, this strategy will not be usable, this VID space is owned by Cypress. You will have to purchase a VID and two consecutive PID values for yourself. ## To Do List / Known Issues - If the I2C lines are not pulled up to 3.3V, I2C read and write bulk transfers hang forever and we don't have error handling for this - Need to understand the formatting of the data argument for SPI when frame size is not 8 bits - I2CNACKError.bytes_written is not correct - Need to characterize the largest transfer size that can be done of each type -- e.g. can we do transfers larger than the 256 byte (SPI/I2C) / 190 byte (UART) buffer on the chip? +- Need to understand the deal with the "notification LED" settings on this chip. It seems like the notification LED is constantly on whenever I enable it. The docs say it "drives a GPIO on both USB transmit and receive", but what does that mean exactly? ### Note about Vendor UART Mode Currently, this driver does not support the "vendor UART" mode of the serial bridge chip. In vendor UART mode, the serial bridge chip still displays the custom "vendor" interface to the PC, and the Python driver has to manually send and receive bytes via the correct endpoints. The problem, however, is that the A in UART stands for Asynchronous: other devices can send bytes to the serial bridge asynchronously whenever they want to, and it's up to the host software to get the bytes off the chip before its 190 byte buffer fills up. -Unfortunately, the way that a naive python driver would work, bytes are only read when a thread calls a read function on the UART driver. They aren't read at any other time. So, if somebody is sending you data over UART and your application code does not poll the UART fast enough, you would lose data. +Unfortunately, the way that a naive python driver would work, bytes would only be read when a thread calls a read function on the UART driver. They aren't read at any other time. So, if somebody is sending you data over UART and your application code does not poll the UART fast enough, you would lose data. One way to fix this would be to have the driver continually submit USB transfers to the CY7C652xx in the background, so that data is transferred to the host machine as soon as the bridge chip makes it available. This would help with the buffering situation a lot, and python-libusb1 does support the async API needed to make it work. However, the threading situation would get complicated: either we would need a new background thread to be in charge of submitting all the transfers and monitoring when they finish (and also this might mean converting every single transfer done anywhere in the driver into an asynchronous transfer so that that thread is the only one processing events), OR we need support for the `handleEventsCompleted()` function, which is [not currently included](https://github.com/vpelletier/python-libusb1/issues/94) in the python-libusb1 wrapper. diff --git a/example_config_blocks/mbed_ce_cy7c65211_spi_with_notificationled.bin b/example_config_blocks/mbed_ce_cy7c65211_spi_with_notificationled.bin new file mode 100644 index 0000000000000000000000000000000000000000..fd2193bf1f785292f36c4f74ba9671149541e315 GIT binary patch literal 512 zcmZ>c3=L*vU}pF!vmwS%fq{Vmge4dl(v*7~nV1JYZln0viP~4hC45Ia!dDlfl$sc4nw# zC}1dJNM$GnLIs9kAS;(4lOdTQpCOMSg`pJ4Dgm;KaG0*b?8}e@)RO|#>&)N^CLpduDjB#I^$DgiA>d<5Uch9}^~mf1rL zi3AFm&6%Cub7r>KYWy&Acm|W-?~ntjuHdEdbLTw77-8+G)twqU*!}LfRnEm^5Yc7*@+e~-4$}(_mi%M~S$oz#G=b-5;mdu#*%t9+?B=yECPkivslrO1I>G^?YuKkcl zd8YDK^o_@q(gi)*1g+z^_kpfvZ|TSvjBj+)B5}u5PiZZiDmB-&Dzul@-Otw_orn!v Cc}lMU diff --git a/pyproject.toml b/pyproject.toml index 6361e6f..151b571 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ packages = [ repository = 'https://github.com/mbed-ce/cy_serial_bridge/' [tool.poetry.scripts] -cy_serial_bridge_cli = 'cy_serial_bridge.cli:main' +cy_serial_cli = 'cy_serial_bridge.cli:main' [tool.poetry.urls] "Tracker" = 'https://github.com/mbed-ce/cy_serial_bridge/issues' diff --git a/rules/61-cy-serial-bridge.rules b/rules/61-cy-serial-bridge.rules index 38e9be0..2d1f6c4 100644 --- a/rules/61-cy-serial-bridge.rules +++ b/rules/61-cy-serial-bridge.rules @@ -7,5 +7,10 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="e010", GROUP="plug # This rule uses an odd PID, so it's for UART CDC mode SUBSYSTEM=="usb", ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="e011", GROUP="plugdev", TAG+="uaccess" +# Also provide rules for factory-new devices with the default VID and PID +SUBSYSTEM=="usb", ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="0002", GROUP="plugdev", TAG+="uaccess" +SUBSYSTEM=="usb", ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="0003", GROUP="plugdev", TAG+="uaccess" +SUBSYSTEM=="usb", ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="0004", GROUP="plugdev", TAG+="uaccess" + # This one grants access to the serial port tty SUBSYSTEM=="tty", ATTRS{idVendor}=="04b4", ATTRS{idProduct}=="e011", MODE="660", GROUP="plugdev", TAG+="uaccess", ENV{ID_MM_DEVICE_IGNORE}="1" diff --git a/src/cy_serial_bridge/configuration_block.py b/src/cy_serial_bridge/configuration_block.py index 8d7a386..19cb3c9 100644 --- a/src/cy_serial_bridge/configuration_block.py +++ b/src/cy_serial_bridge/configuration_block.py @@ -3,7 +3,7 @@ import pathlib import re import struct -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast from cy_serial_bridge.usb_constants import CY_CONFIG_STRING_MAX_LEN_BYTES, CY_DEVICE_CONFIG_SIZE, CyType @@ -245,7 +245,24 @@ def serial_number(self, value: str | None) -> None: self._encode_string_field(0xA8, 0x172, value) @property - def config_bytes(self) -> bytes: + def default_frequency(self) -> int: + """ + Default UART baudrate or SPI/I2C clock frequency when the serial bridge initializes + """ + # Baudrate is a three byte integer so we have to append a 0 MSByte + return cast(int, struct.unpack(" None: + # Frequency may never be higher than 3MHz in any mode + if value > 3e6: + message = "Frequency may not be higher than 3MHz" + raise ValueError(message) + + self._cfg_bytes[0x24:0x27] = struct.pack(" bytearray: """ Get the raw configuration bytes for this buffer. diff --git a/src/cy_serial_bridge/driver.py b/src/cy_serial_bridge/driver.py index 3780de4..b5173f1 100644 --- a/src/cy_serial_bridge/driver.py +++ b/src/cy_serial_bridge/driver.py @@ -431,7 +431,7 @@ def write_config(self, config: ConfigurationBlock) -> int: def change_type(self, new_type: CyType) -> None: """ - Convenience function for changing the CyType of the device. + Changes the CyType of the device. Also makes other config changes needed to make that work. Note: After calling this function, the new CyType will not take effect until you reset the device @@ -454,6 +454,17 @@ def change_type(self, new_type: CyType) -> None: config_block.pid -= 1 log.info("Changing to non-UART_CDC and device has odd PID. Changing PID to 0x%x", config_block.pid) + # I did notice an issue where the region from offset 0x27 to 0x2f seems to be used to store + # mode-specific settings, and the serial bridge internal-errors and doesn't respond to USB requests + # in some cases if these settings are invalid. + # I have not implemented encoding/decoding for this block, but we can program in known good values. + if new_type == CyType.SPI: + # Confirmed working for SPI (and probably I2C) + config_block.config_bytes[0x27:0x30] = b"\x00\x08\x00\x00\x01\x01\x01\x00\x00" + else: + # Confirmed working for UART_CDC and I2C + config_block.config_bytes[0x27:0x30] = b"\x00\x02\x08\x01\x00\x00\x00\x00\x00" + log.info("Writing the following configuration to the device: %s", str(config_block)) self.write_config(config_block) diff --git a/tests/test_driver.py b/tests/test_driver.py index afdff10..5fb09b6 100644 --- a/tests/test_driver.py +++ b/tests/test_driver.py @@ -43,6 +43,7 @@ def test_cfg_block_generation(): assert config_block.product_string == "Mbed CE CY7C65211" assert config_block.serial_number == "14224672048496620243684302669570" assert not config_block.capsense_on + assert config_block.default_frequency == 100000 # Make sure that we can modify the attributes which support being changed config_block.device_type = cy_serial_bridge.CyType.UART_CDC @@ -61,6 +62,9 @@ def test_cfg_block_generation(): assert config_block.product_string == "Turbo Encabulator" assert config_block.serial_number == "1337" + config_block.default_frequency = 2056000 + assert config_block.default_frequency == 2056000 + # Also verify that strings can be changed to None and this works config_block.mfgr_string = None config_block.product_string = None