Skip to content

Commit

Permalink
Implement workaround with changing the default settings. SPI, I2C, an…
Browse files Browse the repository at this point in the history
…d UART tests passing!
  • Loading branch information
multiplemonomials committed Feb 25, 2024
1 parent 34563f6 commit df4ae29
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 11 deletions.
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -115,32 +130,33 @@ 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 <pid of your device> reconfigure --set-pid 0xE010
cy_serial_cli --vid 0x04b4 --pid <pid of your device> 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.

Also note that adding `--randomize-serno` to that command will assign a random serial number to the chip, which is helpful for provisioning new boards.

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.

Expand Down
Binary file not shown.
Binary file removed example_config_blocks/mbed_ce_cy7c65211_uart.bin
Binary file not shown.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 5 additions & 0 deletions rules/61-cy-serial-bridge.rules
Original file line number Diff line number Diff line change
Expand Up @@ -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"
21 changes: 19 additions & 2 deletions src/cy_serial_bridge/configuration_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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("<I", self._cfg_bytes[0x24:0x27] + b"\x00")[0])

@default_frequency.setter
def default_frequency(self, value: int) -> 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("<I", value)[0:3]

@property
def config_bytes(self) -> bytearray:
"""
Get the raw configuration bytes for this buffer.
Expand Down
13 changes: 12 additions & 1 deletion src/cy_serial_bridge/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions tests/test_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit df4ae29

Please sign in to comment.