Example: Bus and Device Creation
Example code and setups¶
This example demonstrates
- how to create a bus and device interface on the Protocol Exerciser.
- how to interact with the controller and fetch information from the target device.
Here we use I3C as an example, but the same principles apply to other bus types.
Create a new file called run_i3c_example.py and copy the following code into it:
import logging
from aqpxlib import AqProtocolExerciser
from aqpxlib.i3c import (
ACUTE_DEFAULT_PID,
PxI3CBus,
PxI3CBaseController,
PxI3C32BitRegisterTarget,
)
from aqpxlib.exceptions import PxAPIError
logger = logging.getLogger(__name__)
def exec_i3c_example():
"""Basic demonstration of how to interact with Acute's Protocol Exerciser.
Steps will be performed in the following order:
1. Create and open a connection to Acute's Protocol Exerciser.
2. Setting proper configuration of voltage levels and pull-up resistors on the bus.
3. Create an I3C Bus Handle Interface.
4. Attach an I3C controller and a target device to the bus.
5. Initialize the I3C bus to perform Dynamic Address Assignment procedure.
6. Perform Write / Read data to the target device.
7. Detach the devices and close the connection to the Protocol Exerciser.
"""
with AqProtocolExerciser.connect(port=60600) as px_session:
logger.info("Created an Exerciser session")
# Configure an I3C Bus on SCL pin 0 and SDA pin 1
i3c_bus = PxI3CBus(px_session, scl=0, sda=1)
logger.info(f"Created {i3c_bus}")
# Setting the voltage level to 3.3V and pull-up resistance to 1.5kΩ
i3c_bus.voltage = 3300
i3c_bus.pullup_resistance = 1500
# Create a Controller device
i3c_controller = PxI3CBaseController(px_session, name="i3c_controller")
try:
i3c_controller.attach_to_bus(i3c_bus)
except PxAPIError:
logger.warning("Multiple controller is not supported, using the existing controller")
i3c_controller = i3c_bus.get_current_controller()
logger.info(f"Added a I3C controller with name {i3c_controller.name}")
# Create a Target Device
bcr = 0x00 # Bus Characteristic Register
dcr = 0x00 # Device Characteristic Register
pid = ACUTE_DEFAULT_PID | (0x01 << 32) | 0 # Provisioned ID
i3c_target = PxI3C32BitRegisterTarget(
px_session,
name="i3c_target",
static_addr=0x12,
sub_address_type=PxI3C32BitRegisterTarget.SubAddressType.NONE,
bcr=bcr,
dcr=dcr,
pid=pid,
)
i3c_target.attach_to_bus(i3c_bus)
logger.info(f"Added a I3C 32-bit register target with name {i3c_target.name}")
# Initialize the I3C bus, which follows the procedure defined in the I3C specification
i3c_controller.init_bus()
logger.info("I3C bus initialized")
# Fetch the target's state to get the target's assigned dynamic address
assigned_address = i3c_target.assigned_address
logger.info(f"Target assigned dynamic address: {assigned_address}")
# Perform Write / Read data to the target device
is_ack = i3c_controller.private_write(assigned_address, [0xDE, 0xAD, 0xBE, 0xEF])
logger.info(f"I3C target" + (" acknowledged the write" if is_ack else " did not acknowledge the write"))
data = i3c_controller.private_read(assigned_address, 4)
logger.info(f"I3C target read data: {[f'{byte:02x}' for byte in data]}")
# Detach the devices from the bus
i3c_target.detach_from_bus()
i3c_controller.detach_from_bus()
logger.info(f"Detached the controller and target from the bus")
if __name__ == "__main__":
logging.basicConfig(format='[%(levelname)s] %(message)s')
logger.setLevel(logging.INFO)
exec_i3c_example()
Run the code by executing the following command in the terminal:
The output should look like the following:
[INFO] Created an Exerciser session
[INFO] Created I3C Bus (SCL: 0, SDA: 1)
[INFO] Added a I3C controller with name i3c_controller
[INFO] Added a I3C 32-bit register target with name i3c_target
[INFO] I3C bus initialized
[INFO] Target assigned dynamic address: 0x08
[INFO] I3C target acknowledged the write
[INFO] I3C target read data: ['0xde', '0xad', '0xbe', '0xef']
[INFO] Detached the controller and target from the bus
Now we will walk through the code step by step and explain each part.
Create a session¶
First, we need to create a session to connect to the Protocol Exerciser. We defined with statement to ensure the session is closed automatically when the block is exited.
with AqProtocolExerciser.connect(port=60600) as px_session:
# session `px_session` is opened
# do something here
...
# session `px_session` is closed
If you want to use AqProtocolExerciser outside of a with block, you will need to disconnect the connection manually.
Create a bus handle¶
Next, we need to configure a bus handle on the Protocol Exerciser. In this example, we will create an I3C bus. We can configure the hardware parameters of the bus. This must depend on the hardware configuration you are using. In this example, we set our I3C bus to 3.3V with an 1.5kΩ pull-up resistors.
A typical bus handle shall include its channel configuration, which we specify by the scl and sda parameters
for the I3C bus.
During the construction of the PxI3CBus object, it automatically sends a request to configure the bus on the Protocol Exerciser.
If successfully configured, the pin definitions on the Protocol Exerciser will be updated to the following:
| PIN 0 | PIN 1 | PIN 2 | PIN 3 | PIN 4 | PIN 5 | PIN 6 | PIN 7 |
|---|---|---|---|---|---|---|---|
| I3C SCL | I3C SDA | (null) | (null) | (null) | (null) | (null) | (null) |
It is also possible to configure the bus with different pin definitions by passing the scl and sda parameters to the PxI3CBus constructor.
Bus Handle Pin Confliction
You are allowed to create multiple bus handles, but you must ensure that the pin definitions are not conflicting with each other. Otherwise, there will be some bus handles that are not created successfully.
Create devices¶
Next, we need to create devices and attach them to the bus. In this example, we will create a controller and a target device.
Here is the snippet of the code from the example above, which creates PxI3CBaseController and PxI3C32BitRegisterTarget devices:
# Create a Controller device
i3c_controller = PxI3CBaseController(px_session, name="i3c_controller")
try:
i3c_controller.attach_to_bus(i3c_bus)
except PxAPIError as e:
i3c_controller = i3c_bus.get_current_controller()
# Create a Target Device
bcr = 0x00 # Bus Characteristic Register
dcr = 0x00 # Device Characteristic Register
pid = ACUTE_DEFAULT_PID | (0x01 << 32) | 0 # Provisioned ID
i3c_target = PxI3C32BitRegisterTarget(
px_session,
name="i3c_target",
static_addr=0x12,
sub_address_type=PxI3C32BitRegisterTarget.SubAddressType.NONE,
bcr=bcr,
dcr=dcr,
pid=pid,
)
i3c_target.attach_to_bus(i3c_bus)
Perform Operations¶
Now our devices are all ready to be used. We can perform operations on the target device by using the controller.
Specifically for I3C, we need to initialize the bus which handles address assignment procedure.
# Initialize the I3C bus, which follows the procedure defined in the I3C specification
i3c_controller.init_bus()
logger.info("I3C bus initialized")
Once this is done, all devices on I3C bus should be usable and are able to fetch their dynamic addresses if assigned by the controller.
# Fetch the target's assigned dynamic address
assigned_address = i3c_target.assigned_address
logger.info(f"Target assigned dynamic address: {assigned_address}")
Now we attempt to write data to the target device and read the data back.
# Perform Write / Read data to the target device
is_ack = i3c_controller.private_write(assigned_address, [0xDE, 0xAD, 0xBE, 0xEF])
logger.info(f"I3C target" + (" acknowledged the write" if is_ack else " did not acknowledge the write"))
data = i3c_controller.private_read(assigned_address, 4)
logger.info(f"I3C target read data: {[f'{byte:02x}' for byte in data]}")