diff --git a/docs/communication_sync.md b/docs/communication_sync.md
new file mode 100644
index 00000000..01d85b92
--- /dev/null
+++ b/docs/communication_sync.md
@@ -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.
+
+
+
+!!! 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
diff --git a/docs/environment_setup.md b/docs/environment_setup.md
index 2a98545c..647c87e3 100644
--- a/docs/environment_setup.md
+++ b/docs/environment_setup.md
@@ -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
diff --git a/docs/images/communication_sync.svg b/docs/images/communication_sync.svg
new file mode 100644
index 00000000..f4733b9a
--- /dev/null
+++ b/docs/images/communication_sync.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/owntech/scripts/plot/pre_plot_records.py b/owntech/scripts/plot/pre_plot_records.py
index fbff52b1..2adcd30b 100644
--- a/owntech/scripts/plot/pre_plot_records.py
+++ b/owntech/scripts/plot/pre_plot_records.py
@@ -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)
@@ -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)
\ No newline at end of file
+ exit(-1)
diff --git a/platformio.ini b/platformio.ini
index f93a4e8b..7b8e21ed 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -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.
@@ -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 =
diff --git a/src/main.cpp b/src/main.cpp
index b576a844..6859148c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021-present LAAS-CNRS
+ * Copyright (c) 2025 Pierre Haessig
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
@@ -18,99 +18,406 @@
*/
/**
- * @brief This example shows how to blink the onboard LED of the Spin board.
+ * @brief Application for islanded inverter (open loop),
+ * with fixed, adjustable amplitude and frequency,
+ * using the three-phase OwnVerter board.
*
- * @author Clément Foucher
- * @author Luiz Villa
- * @author Ayoub Farah Hassan
+ * @author Pierre Haessig
*/
/* --------------OWNTECH APIs---------------------------------- */
-#include "SpinAPI.h"
#include "TaskAPI.h"
+#include "ShieldAPI.h"
+#include "SpinAPI.h"
+
+/* OWNTECH CONTROL LIBRARY (including trigonometric functions) */
+#include "control_factory.h"
+#include "transform.h"
+#include "ScopeMimicry.h"
+
+#include "zephyr/console/console.h"
-/* --------------SETUP FUNCTIONS DECLARATION------------------- */
+/* --------------SETUP AND LOOP FUNCTIONS DECLARATION------------------- */
-/* Setups the hardware and software of the system */
+/* Set up of hardware and software at board startup */
void setup_routine();
-/* --------------LOOP FUNCTIONS DECLARATION-------------------- */
+/* Interaction with the user on the serial monitor (slow background task) */
+void user_interface_task();
+/* Displaying board status messages on the serial monitor (slow background task) */
+void status_display_task();
+/* Power converter control (critical periodic task) */
+void control_task();
+/* Compute duty cycles (subroutine of control task)*/
+void compute_duties();
+/* Read analog measurements (subroutine of control task)*/
+void read_measurements();
+
+/* -------------- VARIABLES DECLARATIONS------------------- */
+
+static const float32_t T_control = 100e-6F; // Control task period (s)
+static const uint32_t T_control_micro = (uint32_t)(T_control * 1.e6F); // Control task period (integer number of µs)
+
+/* SINUSOIDAL SIGNAL GENERATION VARIABLES */
+static float32_t v_freq = 50.0; // inverter voltage frequency (Hz)
+static float32_t v_angle = 0.0; // inverter voltage angle (rad)
+const float32_t freq_increment = 10.0; // frequency up or down increment (Hz)
+static float32_t duty_offset = 0.50; // duty cycle offset. Should be close to 50% to offer maximal amplitude.
+static float32_t duty_amplitude = 0.0; // amplitude for sinusoidal duty cycle
+float32_t duty_increment = 0.01; // duty cycle amplitude up or down increment
+static bool square_wave = false; // whether to use square wave modulation, rather than PWM
+static bool trigger = false; // trigger for ScopeMimicry acquisition, set to true in the user interface task when the user presses 't' key
+
+/* BOARD POWER CONVERSION STATE VARIABLES */
+static bool power_enable = false; // Power conversion state of the leg (PWM activation state)
+static float32_t duty_a, duty_b, duty_c; // three-phase PWM duty cycle (phases a, b, c)
+static float32_t max_duty = 0.9; // maximum duty cycle to avoid overmodulation and voltage overshoot, between 0.1 and 0.9
+
+
+/* Possible modes for the OwnTech board */
+enum serial_interface_menu_mode
+{
+ IDLE_MODE = 0,
+ POWER_MODE
+};
+
+uint8_t mode = IDLE_MODE; // Currently user-requested mode
+
+/* COMMUNICATION AND MEASUREMENT VARIABLES */
+
+uint8_t received_serial_char; // Temporary storage for serial monitor character reception
+
+/* Measurement variables */
+
+static float32_t V_high; // High-side voltage (DC bus)
+static float32_t I_high; // High-side current (DC bus current to the legs)
+// static float32_t Va, Vb, Vc; // AC-side phase voltages
+static float32_t Va, Vb, Vc; // AC-side phase voltages
+static float32_t Ia, Ib, Ic; // AC-side phase currents
-/* Code to be executed in the background task */
-void loop_background_task();
-/* Code to be executed in real time in the critical task */
-void loop_critical_task();
+static float meas_data; // Temporary storage for measured value
-/* --------------USER VARIABLES DECLARATIONS------------------- */
+/* V_high filter (5ms lowpass)*/
+static LowPassFirstOrderFilter vHigh_filter = controlLibFactory.lowpassfilter(T_control, 5.0e-3F);
+static float32_t V_high_filt; // High-side voltage (DC bus), smoothed by lowpass filter
+/* ScopeMimicry capture state and helpers. */
+const uint16_t SCOPE_SIZE = 2096;
+uint16_t k_app_idx;
+ScopeMimicry scope(SCOPE_SIZE, 2);
+static bool is_downloading;
+static bool memory_print;
-/* --------------SETUP FUNCTIONS------------------------------- */
+
+bool mytrigger()
+{
+ return (trigger = true);
+}
+
+void dump_scope_datas(ScopeMimicry &scope) {
+ printk("begin record\n");
+ scope.reset_dump();
+ while (scope.get_dump_state() != finished) {
+ printk("%s", scope.dump_datas());
+ task.suspendBackgroundUs(200);
+ }
+ printk("end record\n");
+}
+
+
+/* -------------- SETUP FUNCTION -------------------------------*/
/**
- * This is the setup routine.
- * It is used to call functions that will initialize your spin, power shields
- * and tasks.
- *
- * In this example, we spawn a background task.
- * An optional critical task can be initialized by uncommenting the two
- * commented lines.
+ * Setup routine, called at board startup.
+ * It is used to initialize the board (spin microcontroller and power shield)
+ * and the application (set tasks).
*/
void setup_routine()
{
- /* Declare task */
- uint32_t background_task_number =
- task.createBackground(loop_background_task);
+ spin.led.turnOn(); // Blink LED at board startup
- /* Uncomment following line if you use the critical task */
- /* task.createCritical(loop_critical_task, 500); */
+ /* Set the high switch convention for all legs */
+ shield.power.initBuck(ALL);
+ shield.power.setDutyCycleMin(ALL, 0.0);
+ shield.power.setDutyCycleMax(ALL, 1.0);
- /* Finally, start tasks */
- task.startBackground(background_task_number);
- /* Uncomment following line if you use the critical task */
- /* task.startCritical(); */
+ /* Setup all the measurements */
+ shield.sensors.enableDefaultOwnverterSensors();
+
+ /* Declare tasks */
+ uint32_t app_task_number = task.createBackground(status_display_task);
+ uint32_t com_task_number = task.createBackground(user_interface_task);
+ task.createCritical(control_task, T_control_micro);
+
+
+ scope.connectChannel(Va, "Va"); /* 0 */
+ scope.connectChannel(duty_a, "duty_a"); /* 1 */
+ scope.set_trigger(&mytrigger);
+ scope.set_delay(0.0);
+ scope.start();
+
+
+ /* Start tasks */
+ task.startBackground(app_task_number);
+ task.startBackground(com_task_number);
+ task.startCritical();
}
-/* --------------LOOP FUNCTIONS-------------------------------- */
+/* --------------LOOP FUNCTIONS (TASKS) ------------------------------- */
/**
- * This is the code loop of the background task
- * It runs perpetually. Here a `suspendBackgroundMs` is used to pause during
- * 1000ms between each LED toggles.
- * Hence we expect the LED to blink each second.
+ * User interface task, running in a loop in the background.
+ * It allows controlling the application through the serial monitor.
+ *
+ * It waits for the user to press a key to select an action.
+ * In particular, 'h' displays the help menu.
*/
-void loop_background_task()
+void user_interface_task()
{
- /* Task content */
- spin.led.toggle();
+ received_serial_char = console_getchar();
+ switch (received_serial_char) {
+ case 'h':
+ /* ----------SERIAL INTERFACE MENU----------------------- */
+
+ printk( " ______________________________________________ \n"
+ "| ------- MENU --------- |\n"
+ "| press i : idle mode |\n"
+ "| press p : power mode |\n"
+ "| press s : toggle square wave mode |\n"
+ "| press u/o : duty cycle ampl./offset UP |\n"
+ "| press j/l : duty cycle ampl./offset DOWN |\n"
+ "| press f : frequency UP |\n"
+ "| press v : frequency DOWN |\n"
+ "|______________________________________________|\n\n");
- /* Pause between two runs of the task */
- task.suspendBackgroundMs(1000);
+ /* ------------------------------------------------------ */
+ break;
+ case 'i':
+ printk("Idle mode request\n");
+ mode = IDLE_MODE;
+ break;
+ case 'p':
+ printk("Power mode request\n");
+ mode = POWER_MODE;
+ break;
+ case 's':
+ if (square_wave) {
+ printk("Toggle PWM modulation\n");
+ square_wave = false;
+ } else {
+ printk("Togggle square wave modulation\n");
+ square_wave = true;
+ }
+ break;
+ case 'u':
+ duty_amplitude += duty_increment;
+ printk("Duty cycle amplitude UP (%.2f) \n", (double) duty_amplitude);
+ break;
+ case 'j':
+ duty_amplitude -= duty_increment;
+ printk("Duty cycle amplitude DOWN (%.2f) \n", (double) duty_amplitude);
+ break;
+ case 'o':
+ duty_offset += duty_increment;
+ printk("Duty cycle offset UP (%.2f) \n", (double) duty_offset);
+ break;
+ case 'l':
+ duty_offset -= duty_increment;
+ printk("Duty cycle offset DOWN (%.2f) \n", (double) duty_offset);
+ break;
+ case 'f':
+ v_freq += freq_increment;
+ printk("Frequency UP (%.2f Hz) \n", (double) v_freq);
+ break;
+ case 'v':
+ v_freq -= freq_increment;
+ printk("Frequency DOWN (%.2f Hz) \n", (double) v_freq);
+ break;
+ case 'r':
+ is_downloading = true;
+ trigger = false;
+ break;
+ case 't':
+ /* Relaunch scope acquisition */
+ trigger = true;
+ scope.start();
+ break;
+ case 'q':
+ max_duty += duty_increment;
+ printk("Maximum duty cycle UP (%.2f) \n", (double) max_duty);
+ break;
+ case 'a':
+ max_duty -= duty_increment;
+ printk("Maximum duty cycle DOWN (%.2f) \n", (double) max_duty);
+ break;
+ default:
+ break;
+ }
}
/**
- * Uncomment lines in setup_routine() to use critical task.
- *
- * This is the code loop of the critical task
- * It is executed every 500 micro-seconds defined in the setup_software
- * function. You can use it to execute an ultra-fast code with
- * the highest priority which cannot be interrupted by the background tasks.
- *
- * In the critical task, you can implement your control algorithm that will
- * run in Real Time and control your power flow.
+ * Board status display task, called pseudo-periodically.
+ * It displays board measurements on the serial monitor
+ *
+ * It also sets the board LED (blinking when POWER_MODE).
+ */
+void status_display_task()
+{
+ if (is_downloading) {
+ dump_scope_datas(scope);
+ is_downloading = false;
+ }
+
+ if (mode == IDLE_MODE) {
+ spin.led.turnOn(); // Constantly ON led when IDLE
+ // Display state:
+ printk("IDL: ");
+
+ } else if (mode == POWER_MODE) {
+ spin.led.toggle(); // Blinking LED when POWER
+ // Display state:
+ printk("POW: ");
+ }
+ // Display duty cycle references (if not square wave):
+ if (!square_wave) {
+ printk("duty a=%3.0f%% o=%3.0f%% ",
+ (double) (duty_amplitude*100),
+ (double) (duty_offset*100)
+ );
+ } else {
+ printk("square ");
+ }
+ printk("@%.0f Hz ", (double) v_freq);
+ printk("| ");
+ // Display measurements
+ printk("Vh %5.2f V, ", (double) V_high);
+ printk("Ih %4.2f A, ", (double) I_high);
+ printk("\n");
+ task.suspendBackgroundMs(200);
+}
+
+/* Read measurements from analog sensors, possibly applying some filters,
+ through microcontroller ADCs (Analog to Digital Converters).
+
+ Measured signals:
+ - currents: Ia, Ib, Ic, I_high
+ - voltages: V_high (with smoothed lowpass filtered version)
+ */
+inline void read_measurements()
+{
+ meas_data = shield.sensors.getLatestValue(V1_LOW);
+ if (meas_data != NO_VALUE) {
+ Va = meas_data;
+ }
+
+ meas_data = shield.sensors.getLatestValue(I1_LOW);
+ if (meas_data != NO_VALUE) {
+ Ia = meas_data;
+ }
+
+ meas_data = shield.sensors.getLatestValue(I2_LOW);
+ if (meas_data != NO_VALUE) {
+ Ib = meas_data;
+ }
+
+ meas_data = shield.sensors.getLatestValue(I3_LOW);
+ if (meas_data != NO_VALUE) {
+ Ic = meas_data;
+ }
+
+ meas_data = shield.sensors.getLatestValue(I_HIGH);
+ if (meas_data != NO_VALUE) {
+ I_high = meas_data;
+ }
+
+ meas_data = shield.sensors.getLatestValue(V_HIGH);
+ if (meas_data != NO_VALUE) {
+ V_high = meas_data;
+ }
+
+ /* Apply filters */
+ // Smooth V_high (lowpass)
+ V_high_filt = vHigh_filter.calculateWithReturn(V_high);
+}
+
+/* Compute sinusoidal duty cycles for each phase a,b,c
+
+CODE TO BE MODIFIED! -> DONE
+Instruction: implement three-phase sinusoidal duty cycles
+*/
+inline void compute_duties()
+{
+ // Update inverter phase (∫ω(t).dt, computed with Euler approximation, modulo 2π)
+ float32_t omega = 2*PI*v_freq; // frequency conversion (Hz -> rad/s): ω = 2π.f
+ v_angle = ot_modulo_2pi(v_angle + omega*T_control);
+ // Compute duty cycles: CODE TO BE MODIFIED! -> DONE
+ duty_a = duty_offset + duty_amplitude * ot_sin(v_angle);
+ if (duty_a > 1.0) duty_a = 1.0;
+ else if (duty_a < 0.0) duty_a = 0.0;
+
+ duty_b = duty_offset + duty_amplitude * ot_sin(v_angle - 2.0/3.0*PI);
+ if (duty_b > 1.0) duty_b = 1.0;
+ else if (duty_b < 0.0) duty_b = 0.0;
+
+ duty_c = duty_offset + duty_amplitude * ot_sin(v_angle - 4.0/3.0*PI);
+ if (duty_c > 1.0) duty_c = 1.0;
+ else if (duty_c < 0.0) duty_c = 0.0;
+
+ // Square wave inverter variant
+ if (square_wave) {
+ if (v_angle <= PI) duty_a = max_duty;
+ else duty_a = 0.0;
+ if (ot_modulo_2pi(v_angle - 2.0/3.0*PI) <= PI) duty_b = max_duty;
+ else duty_b = 0.0;
+ if (ot_modulo_2pi(v_angle - 4.0/3.0*PI) <= PI) duty_c = max_duty;
+ else duty_c = 0.0;
+ }
+}
+
+/**
+ * This is the code loop of the critical task.
+ * It is executed every T_control seconds (100 µs by default).
+ *
+ * Actions:
+ * - measure voltage and currents (in subfunction)
+ * - compute duty cycle (in subfunction)
+ * - control the power converter leg (ON/OFF state and duty cycle)
*/
-void loop_critical_task()
+void control_task()
{
+ /* Retrieve sensor values */
+ read_measurements();
+ /* Compute sinusoidal duty cycles*/
+ compute_duties();
+
+ /* Manage POWER/IDLE modes */
+ if (mode == IDLE_MODE) {
+ if (power_enable == true) {
+ shield.power.stop(ALL);
+ }
+ power_enable = false;
+ } else if (mode == POWER_MODE) {
+ /* Set duty cycles of all three legs */
+ shield.power.setDutyCycle(LEG1, duty_a);
+ shield.power.setDutyCycle(LEG2, duty_b);
+ shield.power.setDutyCycle(LEG3, duty_c);
+ /* Set POWER ON */
+ if (!power_enable) {
+ power_enable = true;
+ shield.power.start(ALL);
+ }
+ }
+ scope.acquire();
}
/**
- * This is the main function of this example
+ * Main function of the application.
* This function is generic and does not need editing.
*/
int main(void)
{
- setup_routine();
-
- return 0;
-}
+ setup_routine();
+ return 0;
+}
\ No newline at end of file
diff --git a/zephyr/modules/owntech_hrtim_driver/zephyr/public_api/hrtim_enum.h b/zephyr/modules/owntech_hrtim_driver/zephyr/public_api/hrtim_enum.h
index ec72d44b..27ac9487 100644
--- a/zephyr/modules/owntech_hrtim_driver/zephyr/public_api/hrtim_enum.h
+++ b/zephyr/modules/owntech_hrtim_driver/zephyr/public_api/hrtim_enum.h
@@ -70,6 +70,15 @@ extern "C"
0xFFFD,
0xFFFD,
0xFFFD};
+
+ static const uint32_t SWAP_WINDOW[8] = {0x0020,
+ 0x0010,
+ 0x0008,
+ 0x0004,
+ 0x0002,
+ 0x0002,
+ 0x0002,
+ 0x0002};
/**
@@ -439,6 +448,7 @@ extern "C"
float32_t duty_min_user_float; /* Minimum duty cycle set by the user in float */
float32_t duty_max_user_float; /* Maximum duty cycle set by the user in float */
uint8_t duty_swap; /* Detects if the duty has been swapped */
+ uint16_t swap_window; /* Defines the swap window based on the pre-scaler */
hrtim_pwm_mode_t pwm_mode; /* voltage mode or current mode */
hrtim_external_trigger_t external_trigger; /* event for current mode */
hrtim_burst_clk_t burst_clk; /* clock source for burst mode generator*/
diff --git a/zephyr/modules/owntech_hrtim_driver/zephyr/src/hrtim.c b/zephyr/modules/owntech_hrtim_driver/zephyr/src/hrtim.c
index b0cf8125..425af150 100644
--- a/zephyr/modules/owntech_hrtim_driver/zephyr/src/hrtim.c
+++ b/zephyr/modules/owntech_hrtim_driver/zephyr/src/hrtim.c
@@ -328,11 +328,15 @@ static inline uint32_t _period_ckpsc(uint32_t freq, timer_hrtim_t *tu)
* dead-time generator which have a 868ps resolution... */
tu->pwm_conf.period = (uint16_t)period;
+ tu->pwm_conf.swap_window = SWAP_WINDOW[tu->pwm_conf.ckpsc];
+ tu->pwm_conf.max_period = period - SWAP_WINDOW[tu->pwm_conf.ckpsc];
+ tu->pwm_conf.min_period = min_period;
/* Stores the maximum and minimum duty cycle for the timing unit */
tu->pwm_conf.duty_max = HRTIM_MAX_PER_and_CMP_REG_VALUES[tu->pwm_conf.ckpsc];
tu->pwm_conf.duty_min = HRTIM_MIN_PER_and_CMP_REG_VALUES[tu->pwm_conf.ckpsc];
+
/* Stores the maximum and minimum duty cycle for the user */
tu->pwm_conf.duty_max_user = tu->pwm_conf.period * 0.9;
tu->pwm_conf.duty_min_user = tu->pwm_conf.period * 0.1;
diff --git a/zephyr/modules/owntech_shield_api/zephyr/src/Power.cpp b/zephyr/modules/owntech_shield_api/zephyr/src/Power.cpp
index 816f5727..e8036844 100644
--- a/zephyr/modules/owntech_shield_api/zephyr/src/Power.cpp
+++ b/zephyr/modules/owntech_shield_api/zephyr/src/Power.cpp
@@ -29,7 +29,6 @@
#include "Power.h"
#include "SpinAPI.h"
-
hrtim_tu_number_t PowerAPI::spinNumberToTu(uint16_t spin_number)
{
if(spin_number == 12 || spin_number == 14)
@@ -218,6 +217,7 @@ void PowerAPI::setDutyCycleRaw(leg_t leg, uint16_t duty_value)
{
uint16_t period;
uint8_t swap_state;
+ uint16_t max_period;
hrtim_tu_number_t leg_tu;
uint16_t duty_cycle_max_raw;
uint16_t duty_cycle_min_raw;
@@ -257,9 +257,10 @@ void PowerAPI::setDutyCycleRaw(leg_t leg, uint16_t duty_value)
period = tu_channel[leg_tu]->pwm_conf.period;
swap_state = tu_channel[leg_tu]->pwm_conf.duty_swap;
+ max_period = tu_channel[leg_tu]->pwm_conf.max_period;
/* Implements a logic that allows for a duty cycle of 100% */
- if (duty_value >= period-3){
+ if (duty_value >= max_period){
duty_value = 0;
hrtim_duty_cycle_set(leg_tu, duty_value);
diff --git a/zephyr/modules/owntech_spin_api/zephyr/src/PwmHAL.cpp b/zephyr/modules/owntech_spin_api/zephyr/src/PwmHAL.cpp
index 0d6a5caa..804a842d 100644
--- a/zephyr/modules/owntech_spin_api/zephyr/src/PwmHAL.cpp
+++ b/zephyr/modules/owntech_spin_api/zephyr/src/PwmHAL.cpp
@@ -230,10 +230,11 @@ void PwmHAL::setDutyCycleRaw(hrtim_tu_number_t pwmX, uint16_t duty_cycle)
}
uint16_t period = tu->pwm_conf.period; /* Get PWM period */
+ uint16_t max_period = tu->pwm_conf.max_period; /* Get PWM period */
bool swap_state = tu->pwm_conf.duty_swap; /* Get output swap state */
/* True if near 100% duty */
- bool over_limit = (duty_cycle >= period - 3);
+ bool over_limit = (duty_cycle >= max_period);
/* Force 0% to avoid glitches near 100% */
duty_cycle = over_limit ? 0 : duty_cycle;