API Interface

The API interface is managed over the X2CScope class.

Simplest example

 1"""The simplest usage of the pyX2Cscope library.
 2
 3The script initializes the X2CScope class with a specified serial port and ELF file,
 4retrieves specific variables, reads their values, and writes new values to them.
 5"""
 6
 7from pyx2cscope.utils import get_com_port
 8from pyx2cscope.x2cscope import X2CScope
 9
10# initialize the X2CScope class with serial port, by default baud rate is 115200
11x2c_scope = X2CScope(port=get_com_port())
12# instead of loading directly the elf file, we can import it after instantiating the X2CScope class
13x2c_scope.import_variables(r"..\..\tests\data\qspin_foc_same54.elf")
14
15# Collect some variables.
16speed_reference = x2c_scope.get_variable("motor.apiData.velocityReference")
17speed_measured = x2c_scope.get_variable("motor.apiData.velocityMeasured")
18
19# Read the value of the "motor.apiData.velocityMeasured" variable from the target
20print(speed_measured.get_value())
21# Write a new value to the "motor.apiData.velocityReference" variable on the target
22speed_reference.set_value(1000)

Further examples in the repository: https://github.com/X2Cscope/pyx2cscope/tree/main/pyx2cscope/examples

X2CScope class

  1. Import the X2Cscope class:

from pyx2scope import X2CScope

X2CScope supports multiple communication interfaces: Serial and TCP/IP. CAN support is coming soon.

Communication Interfaces

Serial (UART) Interface

The most common interface for connecting to microcontrollers. Parameters:

Parameter

Type

Default

Description

port

str

“COM1”

Serial port name (e.g., “COM3”, “/dev/ttyUSB0”)

baud_rate

int

115200

Communication speed in bits per second

parity

int

0

Parity setting (0=None)

stop_bit

int

1

Number of stop bits

data_bits

int

8

Number of data bits

Example - Serial connection with default baud rate:

x2c_scope = X2CScope(port="COM16", elf_file="firmware.elf")

Example - Serial connection with custom baud rate:

x2c_scope = X2CScope(port="COM16", baud_rate=9600, elf_file="firmware.elf")

TCP/IP Interface

For network-based connections to embedded systems with Ethernet capability. Parameters:

Parameter

Type

Default

Description

host

str

“localhost”

IP address or hostname of the target device

tcp_port

int

12666

TCP port number for the connection

timeout

float

0.1

Connection timeout in seconds

Example - TCP/IP connection with default tcp_port:

x2c_scope = X2CScope(host="192.168.1.100", elf_file="firmware.elf")

Example - TCP/IP with custom tcp_port:

x2c_scope = X2CScope(host="192.168.1.100", tcp_port=12345, elf_file="firmware.elf")

CAN Interface (Coming Soon)

CAN bus support is under development. The interface will support parameters such as:

  • bus: CAN bus type (e.g., “USB”, “TCP”)

  • channel: CAN channel identifier

  • bitrate: CAN bus bitrate

  • tx_id: Transmit message ID

  • rx_id: Receive message ID

  1. Basic instantiation examples:

# Serial connection (most common)
x2c_scope = X2CScope(port="COM16", elf_file="firmware.elf")

# TCP/IP connection
x2c_scope = X2CScope(host="192.168.1.100", elf_file="firmware.elf")

Load variables

X2Cscope needs to know which variables are currently available on the firmware. The list of variables can be loaded from multiple file formats:

  • Executable and Linkable Format (ELF, .elf, binary)

  • Pickle (PKL, .pkl, binary)

  • Yaml (YML, .yml, text)

See more details at Import and Export variables section. The ELF file is one artifact generated during code compilation. To load the variables, Execute the code below:

x2c_scope.import_variables(r"..\..\tests\data\dsPIC33ak128mc106_foc.elf")

Variable class

X2CScope class handles variables, either for retrieving and writing values as well for plotting graphics. The next step is to get a variable object that will represent the variable inside the microcontroller. Use the method get_variable to link to a desired variable. The only parameter needed for that method is a string containing the variable name.

  1. Create a Variable object for the variable you want to monitor:

variable = x2c_scope.get_variable('variable_name')

Replace ‘variable_name’ with the name of the variable you want to monitor. You can create multiple variable objects as required. To get variables that are underneath a struct, use the “dot” convention: “struct_name.variable”. It is only possible to link to final variables, i.e., it is not possible to link to a structure directly, only to its members.

Reading values

4. Once you have gone through these steps, you can use the method get_value() to retrieve the actual value of the variable:

variable.get_value()

Writing values

  1. To set the value for the respective variable use the method set_value():

variable.set_value(value)

Special Function Registers (SFR)

In addition to firmware variables, pyX2Cscope can access Special Function Registers (SFRs) — hardware peripheral registers with fixed addresses defined in the MCU’s ELF file (e.g. LATD, TMR1, PORTA). SFR access uses the same Variable interface as ordinary variables, so get_value() and set_value() work identically.

Listing available SFRs

Use list_sfr() to retrieve a sorted list of all SFR names parsed from the ELF file:

sfr_names = x2c_scope.list_sfr()
print(sfr_names)
# ['ADCON1', 'ADCON2', ..., 'LATD', ..., 'TMR1', ...]

This is the SFR counterpart of list_variables(), which lists firmware variables only.

Method

Description

list_variables()

Returns all firmware (DWARF) variable names from the ELF symbol table.

list_sfr()

Returns all peripheral register (SFR) names from the ELF register map.

Retrieving an SFR variable

Pass sfr=True to get_variable() to look up the name in the SFR register map instead of the firmware variable table:

latd = x2c_scope.get_variable("LATD", sfr=True)
tmr1 = x2c_scope.get_variable("TMR1", sfr=True)

The returned object is a standard Variable instance — read and write it the same way:

# Read the current register value
value = latd.get_value()
print(f"LATD = 0x{value:04X}")

# Write a new value to the register
latd.set_value(value | (1 << 12))   # set bit 12 (LATE12)

Note

get_variable("NAME") and get_variable("NAME", sfr=False) both search the firmware variable map. get_variable("NAME", sfr=True) searches the SFR register map. The two namespaces are independent — a name can exist in both without conflict.

Full SFR example

 1"""Example to read LATD and TMR1 registers using Special Function Register (SFR) access."""
 2
 3
 4from pyx2cscope.utils import get_com_port, get_elf_file_path
 5from pyx2cscope.x2cscope import X2CScope
 6
 7# Configuration for serial port communication
 8port = get_com_port()  # Select COM port
 9elf_file = get_elf_file_path()
10
11# Initialize the X2CScope with the specified serial port and baud rate
12x2cscope = X2CScope(port=port, elf_file=elf_file)
13
14# Get the LATD register using the sfr parameter
15latd_register = x2cscope.get_variable("LATD", sfr=True)
16
17# Get the TMR1 register using the sfr parameter
18tmr1_register = x2cscope.get_variable("TMR1", sfr=True)
19
20print("Reading LATD and TMR1 registers...")
21print("Press Ctrl+C to stop\n")
22
23
24# Read the current value of LATD register
25latd_value = latd_register.get_value()
26
27# Read the current value of TMR1 register
28tmr1_value = tmr1_register.get_value()
29
30# Print the register values
31print(f"LATD: 0x{latd_value:04X} ({latd_value})")
32print(f"TMR1: 0x{tmr1_value:04X} ({tmr1_value})")
33print("-" * 40)
34
35
36

Import and Export variables

Easiest way to assembly a variable list is to import it from an elf file. The elf file is the output of the firmware compilation and contains all the information about the variables, their addresses, and sizes. Once the list of variables are available from the elf file, it can be exported to a pickle binary file or a YML text file. It is possible to export selected variables or the whole list of variables. Having the exported file (yml or pickle) it is possible to import it back to the X2CScope object. YML is human readable and can be edited with any text editor, while pickle is a binary file and can be used to store the variables in a more secure way.

See the example below:

 1"""Example of importing and exporting variable.
 2
 3The script initializes the X2CScope class with a specified serial port,
 4retrieves specific variables, reads their values, export them to a yaml file
 5and also export all the variables available as a pickle binary file.
 6
 7We define a new instance of X2Cscope, and we reload the variables over the exported
 8files instead of the elf file.
 9"""
10import os
11
12from pyx2cscope.utils import get_com_port
13from pyx2cscope.variable.variable_factory import FileType
14from pyx2cscope.x2cscope import X2CScope
15
16# initialize the X2CScope class with serial port, by default baud rate is 115200
17com_port = get_com_port()  # Get the COM port from the utility function
18x2c_scope = X2CScope(port=com_port)
19# instead of loading directly the elf file, we can import it after instantiating the X2CScope class
20x2c_scope.import_variables(r"..\..\tests\data\qspin_foc_same54.elf")
21
22# Collect some variables, i.e.: from QSPIN on SAME54 MCLV-48V-300W
23angle_reference = x2c_scope.get_variable("mcFocI_ModuleData_gds.dOutput.elecAngle")
24speed_measured = x2c_scope.get_variable("mcFocI_ModuleData_gds.dOutput.elecSpeed")
25
26# Read the value of the "motor.apiData.velocityMeasured" variable from the target
27print(speed_measured.get_value())
28
29# you can export only these two variables as yaml file (plain text)
30x2c_scope.export_variables(filename="my_two_variables", items=[angle_reference, speed_measured])
31# you can export all the variables available. For a different file format, define 'ext' variable, i.e. pickle (binary)
32x2c_scope.export_variables(filename="my_variables", ext=FileType.PICKLE)
33
34# disconnect x2cscope so we can reconnect with another instance
35x2c_scope.disconnect()
36
37# Instantiate a different X2Cscope to ensure we have an empty variable list, i.e. x2c
38x2c = X2CScope(port=com_port)
39# instead of loading the elf file, we load our exported file with all variable
40x2c.import_variables("my_variables.pkl")
41# or we can load only our two variables
42# x2c.import_variables("my_two_variables.yml")
43# or we can load our elf file again
44# x2c.import_variables(filename=r"..\..\tests\data\qspin_foc_same54.elf")
45
46# Collect some variables, i.e.: from QSPIN on SAME54 MCLV-48V-300W
47angle_ref2 = x2c.get_variable("mcFocI_ModuleData_gds.dOutput.elecAngle")
48speed_ref2 = x2c.get_variable("mcFocI_ModuleData_gds.dOutput.elecSpeed")
49
50# Read the value of the "speed_ref2" variable from the target
51print(speed_ref2.get_value())
52
53# housekeeping
54os.remove("my_variables.pkl")
55os.remove("my_two_variables.yml")
56

Scope Channel

X2CScope class provide means to retrieve scope data, i.e., instead of getting the current value of a variable, collect the values during a time frame, triggering according some trigger values or not, and return and array that could be plotted with any available python graphic framework as matplotlib, seaborn, etc.

1. To use the scope functionality, first you need to link a variable as previously explained, and add this variable to the scope by means of the method: add_scope_channel(variable: Variable) :

variable1 = x2c_scope.get_variable("variable1")
variable2 = x2c_scope.get_variable("variable2")

x2c_scope.add_scope_channel(variable1)
x2c_scope.add_scope_channel(variable2)

2. To remove a variable from the scope: remove_scope_channel(variable: Variable), to clear all variables and reset the scope use instead: clear_all_scope_channel()

x2c_scope.remove_scope_channel(variable2)

or

x2c_scope.clear_all_scope_channel()

Up to 8 channels can be added. Each time you add or remove a variable, the number of channels present on the channel are returned.

Getting Data from Scope

To get data from scope channel you need to follow this sequence:

+------------------------------------+
| Add variables to the scope channel |
+------------------------------------+
            |
            v
+--------------------------+
| Request scope data       | <---------------+
+--------------------------+                 |
            |                                |
            v                                |
+--------------------------+                 |
| Is scope data ready?     |                 |
+--------------------------+                 |
    / Yes          \ No                      |
    v               v                        |
+-----------------+  +--------------------+  |
| Handle the data |  | Execute some delay |  |
+-----------------+  +--------------------+  |
        |                 |                  |
        +-----------------+------------------+

Step-by-step you need:

  1. Request to X2CScope to collect data for the variables registered on the scope channels.

x2c_scope.request_scope_data()

2. Check if the data is ready: Returns Scope sampling state. Returns: true if sampling has completed, false if it’s yet in progress.

while not x2c_scope.is_scope_data_ready():
        time.sleep(0.1)
  1. Get the scope data once sampling is completed

data = x2c_scope.get_scope_channel_data()

A simple loop request example to get only 1 frame of scope data is depicted below:

# request scope to start sampling data
x2c_scope.request_scope_data()

# wait while the data is not yet ready for reading
while not x2c_scope.is_scope_data_ready():
        time.sleep(0.1)

for channel, data in x2c_scope.get_scope_channel_data().items():
        # Do something with the data.
        # channel contains the variable name, data is an array of values

Triggering

To set up a Trigger, any variable added to the scope channel can be selected. By default, there is no trigger selected. To set any trigger configuration, you need to pass a TriggerConfig imported from pyx2cscope.x2cscope

trigger_config = TriggerConfig(Variable, trigger_level: float, trigger_mode: int, trigger_delay: int, trigger_edge: int)
x2cscope.set_scope_trigger(trigger_config)

TriggerConfig needs some parameters like the variable and some trigger values like:

  • Variable: the variable which will be monitored

  • Trigger_Level: at which level the trigger will start executing (float)

  • Trigger_mode: 1 for triggered, 0 for Auto (No trigger)

  • Trigger_delay: Value > 0 Pre-trigger, Value < 0 Post trigger

  • Trigger_Edge: Rising (1) or Falling (0)

Additional information on how to change triggers, clear and change sample time, may be found on the API documentation.

Utility Functions

The pyx2cscope.utils module provides helper functions for managing configuration settings used in examples and scripts. These utilities simplify the process of specifying ELF file paths and COM ports without hardcoding them into your scripts.

Configuration File (config.ini)

When any utility function is called for the first time, a config.ini file is automatically generated in the current working directory if it doesn’t already exist. This file contains default placeholder values that you should update with your actual settings:

[ELF_FILE]
path = path_to_your_elf_file

[COM_PORT]
com_port = your_com_port, ex:COM3

[HOST_IP]
host_ip = your_host_ip

After the file is created, edit it to specify your actual ELF file path and COM port.

Available Functions

get_elf_file_path()

Retrieves the ELF file path from the configuration:

from pyx2cscope.utils import get_elf_file_path

elf_path = get_elf_file_path()
if elf_path:
    x2cscope = X2CScope(port="COM8", elf_file=elf_path)

get_com_port()

Retrieves the COM port from the configuration:

from pyx2cscope.utils import get_com_port

port = get_com_port()
if port:
    x2cscope = X2CScope(port=port, elf_file="firmware.elf")

get_host_address()

Retrieves the host IP address for TCP/IP connections:

from pyx2cscope.utils import get_host_address

host = get_host_address()
if host:
    x2cscope = X2CScope(host=host, tcp_port=12666, elf_file="firmware.elf")

Example Usage

The utility functions are particularly useful in example scripts where you want to avoid hardcoding paths:

from pyx2cscope import X2CScope
from pyx2cscope.utils import get_elf_file_path, get_com_port

# Get configuration from config.ini
elf_path = get_elf_file_path()
port = get_com_port()

if not elf_path or not port:
    print("Please configure config.ini with your ELF file path and COM port")
    exit(1)

# Initialize X2CScope with configured values
x2cscope = X2CScope(port=port, elf_file=elf_path)

Note

The utility functions return an empty string if the configuration contains placeholder values (containing “your”). This allows you to check if the configuration has been properly set up before proceeding.