Skip to content
Open
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
101 changes: 101 additions & 0 deletions docs/communication_sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
## Introduction

Synchronization (Sync) provides precise alignment of PWM signals and real-time control tasks across multiple SPIN boards.

This is particularly useful for advanced applications that require time-deterministic behavior, such as decentralized control or high-resolution PWM coordination beyond the capability of a single board.

!!! success "Synchronized PWM carriers"
- Using this synchronization features permits to have PWM carriers synched down to **25ns ± 5ns** (results obtained with 30cm CAT5 S-FTP RJ-45 cables).
- Jitter on synched control tasks is on the order of a micro second.

## How it works

**Board A** is defined as the sync Master, it effectively defines the time reference.
The PWM generator of the Master triggers its real time control task based on an internal repetition timer.

When entering the real time control task, the special sync output pin (pin number 42) is set in alternate mode.

This allows to send the next repetition event to **Board B**. Alternate mode is maintained for few microseconds in order to wait for the next incoming PWM event.

The sync pin is then returned to normal mode, effectively halting event propagation until the next real-time control task deadline.

![Working principle of the synchronization](images/communication_sync.svg)

!!! warning "A small delay is to be expected between **Board A** and **Board B** control tasks"
Current sync behavior does introduce a delay, because of the busy wait needed to control event propagation.
However PWM carrier are perfectly synched.


## How to use.

All there is to do is initialize both the Master and the Slave(s).

=== "Initialize the master board"

```
void setup_routine()
{
/* Initialize the PWM engine first */
shield.power.initBuck(ALL);
/* Set up synchronization mode as Master */
communication.sync.initMaster();

/* Declare tasks */
uint32_t background_task_number =
task.createBackground(loop_background_task);

uint32_t app_task_number = task.createBackground(application_task);

/* Make sure the real time task is based on the PWM engine */
task.createCritical(loop_critical_task, control_task_period, source_hrtim);

/* Finally, start tasks */
task.startBackground(background_task_number);
task.startBackground(app_task_number);
task.startCritical();
}
```

=== "Initialize the slave(s) board(s)"
```
void setup_routine()
{
/* Initialize the PWM engine first */
shield.power.initBuck(ALL);
/* Set up synchronization mode as Slave */
communication.sync.initSlave();

/* Declare tasks */
uint32_t background_task_number =
task.createBackground(loop_background_task);

uint32_t app_task_number = task.createBackground(application_task);

/* Make sure the real time task is based on the PWM engine */
task.createCritical(loop_critical_task, control_task_period, source_hrtim);

/* Finally, start tasks */
task.startBackground(background_task_number);
task.startBackground(app_task_number);
task.startCritical();
}
```


## Current Limitations

This synchronization feature is optimized for systems where the real-time control task frequency is significantly lower than the PWM frequency—typically by a factor of 10 or more.

However, in scenarios where the PWM frequency approaches or matches the control task frequency, the current implementation becomes ineffective. This is because:

- The Master board relies on a busy-wait mechanism to control the propagation of the synchronization signal.

- When the timing between PWM events and control tasks becomes too close, this busy-wait duration would need to be extended substantially.

- Such long blocking periods would interfere with real-time responsiveness and potentially degrade system performance.

As a result, the current design does not support synchronization in setups where the control and PWM frequencies are too close.

## API Reference
::: doxy.powerAPI.class
name: SyncCommunication
20 changes: 20 additions & 0 deletions docs/environment_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ Before we start, make sure your machine meets all the requirements below.
- Write permission for the serial port (`/dev/ttyACM0`): See PlatformIO documentation which provides a [udev rules file](https://docs.platformio.org/en/latest/core/installation/udev-rules.html)
- **Internet connection**

=== "RaspberryPi"

- **Git:** If you do not have git installed, get it here [git for Linux](https://git-scm.com/download/linux)
- **Python3:** If you do not have python3 installed, get it here [Python3 Installers](https://docs.python-guide.org/starting/install3/linux/)
- The [pip](https://pip.pypa.io/en/stable/) package installer is needed. If using the system Python (`/usr/bin/python3`), `pip` may not be installed by default.
See [Installing pip with Linux Package Managers](https://packaging.python.org/en/latest/guides/installing-using-linux-tools/).
- The [venv](https://docs.python.org/3/library/venv.html) module is needed.
Warning if using the system Python: although `venv` is part of the Python Standard Library, some Linux distributions such as Debian and Ubuntu don't install it by default.
In that case, make sure that the `python3-venv` package is installed.
- **CMake:** If you do not have CMake installed, get it here [CMake Installer](https://cmake.org/download/)
- 64 bit Linux distribution
- Write permission for the serial port (`/dev/ttyACM0`): See PlatformIO documentation which provides a [udev rules file](https://docs.platformio.org/en/latest/core/installation/udev-rules.html)
- **Internet connection**
- From v5 onward you will need to install the following libraries
```
sudo dpkg --add-architecture armhf
sudo apt update
sudo apt install libc6:armhf libstdc++6:armhf
```


## Setup your work environment

Expand Down
4 changes: 4 additions & 0 deletions docs/images/communication_sync.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 45 additions & 8 deletions owntech/scripts/plot/pre_plot_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,48 @@ class style():
except ImportError:
env.Execute("$PYTHONEXE -m pip install numpy")
try:
import PyQt6
except ImportError:
env.Execute("$PYTHONEXE -m pip install PyQt6")
try:
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('QtAgg')
except ImportError:
env.Execute("$PYTHONEXE -m pip install matplotlib")
import matplotlib
try:
import pandas as pd
except ImportError:
env.Execute("$PYTHONEXE -m pip install pandas")


def has_graphical_display():
"""Return True when a desktop session is available for interactive plots."""
return bool(os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"))


def configure_matplotlib_backend():
"""Prefer Qt on desktop sessions unless GUI plotting is explicitly disabled."""
requested_backend = os.environ.get("MATPLOTLIB_BACKEND")
if requested_backend:
matplotlib.use(requested_backend)
return requested_backend

gui_setting = os.environ.get("OWNTECH_PLOT_GUI", "").lower()
gui_disabled = gui_setting in ("0", "false", "no")
gui_enabled = gui_setting in ("1", "true", "yes")

if not gui_disabled and (gui_enabled or has_graphical_display()):
try:
import PyQt6 # pylint: disable=unused-import
except ImportError:
env.Execute("$PYTHONEXE -m pip install PyQt6")
matplotlib.use("QtAgg")
return "QtAgg"

matplotlib.use("Agg")
return "Agg"


MATPLOTLIB_BACKEND = configure_matplotlib_backend()
import matplotlib.pyplot as plt


def extract_timestamp(filename):
# Extract the timestamp part from the filename
match = re.match(r'^(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})-record\.txt$', filename)
Expand Down Expand Up @@ -167,8 +194,18 @@ def PrintSuccess(target, source, env):
records[record_number]))
fig = plot_df(df)
fig.suptitle(records[record_number])
plt.show()
if MATPLOTLIB_BACKEND == "Agg":
output_path = os.path.join(
".",
"src",
"Data_records",
records[record_number].replace(".txt", ".png"),
)
fig.savefig(output_path)
print(style.SUCCESS + f"Plot saved to {output_path}")
else:
plt.show()

exit(0)
else :
exit(-1)
exit(-1)
4 changes: 2 additions & 2 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ board_version = 1_2_0
# Supported shields:
# - twist
# - ownverter
board_shield = twist
board_shield = ownverter

# Defines shield version
# Refers to shield silkscreen to set shield version.
Expand All @@ -51,7 +51,7 @@ board_shield = twist
# Supported version ownverter:
# - 0_9_0
# - 1_0_0
board_shield_version = 1_4_1
board_shield_version = 1_0_0

# Compiler settings
build_flags =
Expand Down
Loading