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. + +![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 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 @@ + + + +
...
...
Control task
Control task
Sync Master
Sync Slave
Time (us)
Time (us)
GPIO alternate function
GPIO alternate function
GPIO normal mode
GPIO normal mode effectively blanks repetitions events.
Only the events occuring when master's GPIO is in alternate function are propagated to the slave.
Board A

Real time scheduling
Board B

Real time scheduling
Slave control_task is trigged by receiving sync event.
Master control task is trigged by PWM repetition counter
Busy wait
Busy wait
repetitions events
\ 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;