Этой проблеме я уже посвятил две статьи. Ну, как проблеме — проблеме для меня. Никак не удавалось охватить её целиком, когнитивно и ментально промоделировать. Появление Copilot кардинально всё изменило — ментальные границы раздвинулись, и здесь я выкладываю окончательное решение для семейств микроконтроллеров Synergy и RA8 от Renesas.

Вкратце напомню, что вопрос мёртвого времени — один из важнейших для контроллеров всех типов электродвигателей и полупроводниковых преобразователей энергии.
С одной стороны, мёртвое время не может быть нулевым, поскольку полупроводники имеют собственное запаздывание реакции. С другой стороны, если оно слишком велико, падает КПД и ухудшаются динамические характеристики.

Почему пришлось вернуться к этой теме, несмотря на то, что в статье «Мёртвое время: как не ошибиться при настройке ШИМ в Renesas Synergy» вопрос уже был раскрыт?
Дело в том, что на RA8 этот приём перестал работать: попытка записать в компараторы предельные значения счётчиков не приводит к выключению ШИМ. Это оказалось неприятным сюрпризом и разрушило иллюзию о том, что внутри экосистемы одного производителя можно безболезненно переносить драйверы, основанные на прямом доступе к регистрам.

Наш проект универсального контроллера моторов на ARM Cortex-M85 получил собственный фреймворк, и настало время переходить к целевым приложениям. Но каким бы ни было это приложение, скоростью моторов нужно управлять, а значит, нужен генератор качественного ШИМ-сигнала переходящего в статический сигнал.

MC80 в деле
MC80 в деле

Микроконтроллер R7FA8M1AHECFB, установленный на нашей плате, работает на частоте 480 МГц и управляет четырьмя двигателями постоянного тока через два трёхфазных драйвера.
Программируемые трёхфазные драйверы TMC6200-TA сравнительно недороги и, главное, не требуют непрерывного ШИМ для поддержки бутстрэп-схемы, питающей верхние ключи.

Кто-то может возразить, что индивидуально управлять четырьмя моторами, имея всего шесть фаз, невозможно — и будет прав. Однако наше применение ограничивается режимами, в которых одновременно работают не более двух моторов и только в одном направлении.

Говорят, что пока копилоты остаются достаточно рискованным инструментом: по-настоящему хорошо они работают лишь в режиме автодополнения.
Надо признать, что при работе с «голыми» регистрами они действительно серьёзно лагают и могут навредить. Поручать им самостоятельный поиск документации на чип в интернете — не лучшая идея.
К сожалению, заголовочные файлы из фирменного SDK тоже мало пригодны: они перегружены огромным объёмом контекста и практически лишены комментариев, поэтому копилот «не понимает» их. Я использую такой подход: с помощью GPT индивидуально конвертирую описания регистров из PDF-даташита в .c- и .h-файлы. После этого можно смело подключать Copilot — он уже работает с понятным, структурированным исходником.

Итак, в чём же когнитивная проблема «просто взять и включить мёртвое время»?
Как уже говорилось в предыдущей статье, в семействе микроконтроллеров Synergy и RA8 от Renesas такого отдельного бита нет. И связано это с тем, что там реализовано нечто гораздо более ценное.

Мем от ChatGPT o3
Мем от ChatGPT o3

И тут пошёл слух, что Copilot умеет строить интерактивные графики.
Раньше, чтобы увидеть диаграммы сигналов при разных сценариях, приходилось либо долго и тщательно рисовать их вручную, либо создавать сложные модели в Simulink — и на каждом из этих этапов либо исправлять бесконечные ошибки, либо, не заметив их, тратить время на ошибочные концепции.

Первое, что мне посоветовал копилот, — это снова обратиться к регистру General PWM Timer Count Direction and Duty Setting Register (GPTCDDSR). Но сделать это глубже.
В статье «Мёртвое время. Как не ошибиться при настройке ШИМ в Renesas Synergy» я отверг работу с этим регистром. Но копилот упорно возвращал меня к нему, и мы пришли к консенсусу, что его можно использовать в двухтактном методе переключения состояния ШИМ. Но надо было это проверить и опять строить графики либо модели.

Вторым шагом было попросить его сделать все нужные графики, и произошло чудо — он сразу нарисовал интерактивные графики на Python. Конечно, последовало несколько косметических улучшений вроде коррекции надписей и толщины линий, но в течение десяти минут всё было закончено.

И вот этот скрипт:

Скрипт интерактивных графиков ШИМ
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Slider, Button, CheckButtons
from scipy.signal import argrelextrema

# === НАСТРОЙКИ (константы) ===
TIME_WINDOW = 3.0         # длительность шкалы времени (сек)
NUM_POINTS = 6000         # количество точек для дискретизации графика

# === СОЗДАНИЕ ФИГУРЫ И ОСЕЙ ===
fig = plt.figure(figsize=(16, 10))
# Попытка развернуть окно на весь экран (работает не со всеми бэкендами)
try:
    fig.canvas.manager.window.wm_state('zoomed')
except AttributeError:
    # Если wm_state недоступен, попробуем другие способы
    try:
        fig.canvas.manager.window.showMaximized()
    except AttributeError:
        # Если и showMaximized недоступен, просто продолжаем без максимизации
        pass

# Основные графики (занимают большую часть экрана)
gs = fig.add_gridspec(5, 1, height_ratios=[1, 1, 1, 1, 1], left=0.1, right=0.95, top=0.92, bottom=0.25, hspace=0.3)

axs = []
for i in range(5):
    ax = fig.add_subplot(gs[i, 0])
    axs.append(ax)

# Область для слайдеров (внизу)
slider_area = fig.add_axes([0.1, 0.02, 0.85, 0.2])
slider_area.set_visible(False)

# === СОЗДАНИЕ СЛАЙДЕРОВ ===
# Позиции слайдеров
slider_height = 0.025
slider_spacing = 0.035

# Слайдер для периода PWM
ax_pwm_period = fig.add_axes([0.15, 0.18, 0.65, slider_height])
slider_pwm_period = Slider(ax_pwm_period, 'PWM период (с)', 0.5, 2.0, valinit=1.0, valfmt='%.2f')

# Слайдер для коэффициента заполнения
ax_duty_cycle = fig.add_axes([0.15, 0.18 - slider_spacing, 0.65, slider_height])
slider_duty_cycle = Slider(ax_duty_cycle, 'Коэфф. заполнения', 0.01, 0.99, valinit=0.5, valfmt='%.2f')

# Слайдер для мертвого времени
ax_dead_time = fig.add_axes([0.15, 0.18 - 2*slider_spacing, 0.65, slider_height])
slider_dead_time = Slider(ax_dead_time, 'Мертвое время (с)', 0.01, 0.3, valinit=0.1, valfmt='%.3f')

# Слайдер для времени точки останова 4-го графика
ax_stop_time = fig.add_axes([0.15, 0.18 - 3*slider_spacing, 0.65, slider_height])
slider_stop_time = Slider(ax_stop_time, 'Точка останова HSw (с)', 0.5, 2.5, valinit=1.0, valfmt='%.3f')

# Слайдер для шага сетки
ax_grid_step = fig.add_axes([0.15, 0.18 - 4*slider_spacing, 0.65, slider_height])
slider_grid_step = Slider(ax_grid_step, 'Шаг сетки (с)', 0.05, 0.5, valinit=0.1, valfmt='%.2f')

# Кнопка сброса
ax_reset = fig.add_axes([0.85, 0.18 - 2*slider_spacing, 0.08, 0.06])
button_reset = Button(ax_reset, 'Сброс')

# Чекбокс для инвертирования уровней точки останова
ax_checkbox = fig.add_axes([0.85, 0.18 - 3*slider_spacing, 0.08, 0.04])
checkbox_invert = CheckButtons(ax_checkbox, ['Инвертировать'], [False])

# === ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ===
t = np.linspace(0, TIME_WINDOW, NUM_POINTS)
dt_step = t[1] - t[0]

# Линии для анимации фронтов и точки останова
stop_lines = []
edge_lines = []

# === ФУНКЦИЯ ДЛЯ ДОБАВЛЕНИЯ ЛИНИЙ ФРОНТОВ ===
def add_edge_lines(ax, rising_edges, falling_edges):
    # Добавляем новые линии фронтов (старые будут очищены при ax.clear())
    for edge in rising_edges:
        if edge < len(t):
            ax.axvline(x=t[edge], color='blue', linestyle=':', alpha=0.7, linewidth=1.5)

    for edge in falling_edges:
        if edge < len(t):
            ax.axvline(x=t[edge], color='red', linestyle=':', alpha=0.7, linewidth=1.5)

# === ФУНКЦИЯ ОБНОВЛЕНИЯ ГРАФИКОВ ===
def update_plots():
    # Получаем текущие значения из слайдеров
    pwm_period = slider_pwm_period.val
    duty_cycle = slider_duty_cycle.val
    dead_time = slider_dead_time.val
    stop_time_4 = slider_stop_time.val
    grid_step = slider_grid_step.val

    # Получаем состояние чекбокса
    invert_levels = checkbox_invert.get_status()[0]

    # Расчет времени точки останова 5-го графика с учетом инвертирования
    if invert_levels:
        stop_time_5 = stop_time_4 - pwm_period / 2  # Опережение на полпериода
    else:
        stop_time_5 = stop_time_4 + pwm_period / 2  # Отставание на полпериода

    # === ПЕРЕСЧЕТ СИГНАЛОВ ===
    # Треугольный счетчик
    f_tri = 1 / pwm_period
    tri_wave_norm = 1 - 2 * np.abs((t * f_tri) % 1 - 0.5)
    tri_wave = tri_wave_norm  # От 0 до 1

    # Уровень компаратора
    compare_level = np.full_like(t, duty_cycle)

    # Верхний ключ
    high_side = tri_wave < compare_level

    # Найти фронты верхнего ключа
    high_side_edges = np.diff(high_side.astype(int))
    high_side_rising_edges = np.where(high_side_edges == 1)[0]
    high_side_falling_edges = np.where(high_side_edges == -1)[0]

    # Нижний ключ
    low_side = (~high_side).astype(int)

    # Найти фронты нижнего ключа
    edges = np.diff(low_side)
    rising_edges = np.where(edges == 1)[0]
    falling_edges = np.where(edges == -1)[0]

    # Мертвое время для нижнего ключа
    low_side_deadtime = np.copy(low_side)
    dead_samples = int(dead_time / dt_step)

    for rise, fall in zip(rising_edges, falling_edges):
        if fall - rise < dead_samples:
            low_side_deadtime[rise+1:fall+1] = 0
            continue
        low_side_deadtime[rise+1:rise+1+dead_samples//2] = 0
        low_side_deadtime[fall+1-dead_samples//2:fall+1] = 0
        # 4-й график: копирует верхний ключ, замирает в заданном уровне
    custom_signal_4 = np.copy(high_side.astype(int))
    stop_idx_4 = int(stop_time_4 / dt_step)
    if stop_idx_4 < len(custom_signal_4):
        if invert_levels:
            custom_signal_4[stop_idx_4:] = 1  # Замирает в 1
        else:
            custom_signal_4[stop_idx_4:] = 0  # Замирает в 0

    # 5-й график: копирует нижний ключ с мертвым временем, замирает в заданном уровне
    custom_signal_5 = np.copy(low_side_deadtime)
    stop_idx_5 = int(stop_time_5 / dt_step)
    if stop_idx_5 < len(custom_signal_5) and stop_idx_5 >= 0:
        if invert_levels:
            custom_signal_5[stop_idx_5:] = 0  # Замирает в 0
        else:
            custom_signal_5[stop_idx_5:] = 1  # Замирает в 1

    # === ОБНОВЛЕНИЕ ГРАФИКОВ ===
    # Очищаем все графики
    for ax in axs:
        ax.clear()    # График 0: Треугольник и компаратор
    axs[0].plot(t, tri_wave, label="Треугольник (счётчик)", linewidth=2)
    axs[0].plot(t, compare_level, 'r--', label="Уровень компаратора", linewidth=2)

    # Найти точки пересечения треугольной волны с уровнем компаратора
    # Ищем переходы high_side (пересечения треугольника с компаратором)
    intersection_indices = []
    intersection_indices.extend(high_side_rising_edges)
    intersection_indices.extend(high_side_falling_edges)    # Отмечаем точки пересечения жирными красными точками
    if intersection_indices:
        intersection_times = t[intersection_indices]
        intersection_levels = np.full(len(intersection_indices), duty_cycle)
        axs[0].scatter(intersection_times, intersection_levels, color='red', s=50, zorder=5, label='Точка компаратора')

    # Зеленая линия всегда показывает точку останова 4-го графика
    axs[0].axvline(x=stop_time_4, color='green', linestyle='--', alpha=0.7, linewidth=2, label=f'Останов HSw ({stop_time_4:.3f}с)')

    # Желтая линия всегда показывает точку останова 5-го графика
    axs[0].axvline(x=stop_time_5, color='orange', linestyle='--', alpha=0.7, linewidth=2, label=f'Останов LSw ({stop_time_5:.3f}с)')    # Находим и отмечаем точки пересечения с зеленой линией (stop_time_4)
    if 0 <= stop_time_4 <= TIME_WINDOW:
        # Интерполируем значение треугольной волны в момент stop_time_4
        stop_idx_4_interp = stop_time_4 / dt_step
        if stop_idx_4_interp < len(tri_wave) - 1:
            idx_low = int(stop_idx_4_interp)
            idx_high = idx_low + 1
            frac = stop_idx_4_interp - idx_low
            tri_value_4 = tri_wave[idx_low] * (1 - frac) + tri_wave[idx_high] * frac
            axs[0].scatter([stop_time_4], [tri_value_4], color='green', s=50, zorder=6, label='Точка останова HSw')

    # Находим и отмечаем точки пересечения с желтой линией (stop_time_5)
    if 0 <= stop_time_5 <= TIME_WINDOW:
        # Интерполируем значение треугольной волны в момент stop_time_5
        stop_idx_5_interp = stop_time_5 / dt_step
        if stop_idx_5_interp < len(tri_wave) - 1 and stop_idx_5_interp >= 0:
            idx_low = int(stop_idx_5_interp)
            idx_high = idx_low + 1
            frac = stop_idx_5_interp - idx_low
            tri_value_5 = tri_wave[idx_low] * (1 - frac) + tri_wave[idx_high] * frac
            axs[0].scatter([stop_time_5], [tri_value_5], color='orange', s=50, zorder=6, label='Точка останова LSw')

    add_edge_lines(axs[0], high_side_rising_edges, high_side_falling_edges)
    axs[0].set_ylabel("Уровень")
    axs[0].legend(loc='upper right')
    axs[0].grid(True, alpha=0.3)
    axs[0].set_xticks(np.arange(0, TIME_WINDOW + grid_step, grid_step))
    axs[0].set_ylim(-0.1, 1.1)    # График 1: PWM High side
    axs[1].step(t, high_side.astype(int), where='post', linewidth=2, color='red')
    axs[1].axvline(x=stop_time_4, color='green', linestyle='--', alpha=0.7, linewidth=2)
    axs[1].axvline(x=stop_time_5, color='orange', linestyle='--', alpha=0.7, linewidth=2)
    add_edge_lines(axs[1], high_side_rising_edges, high_side_falling_edges)
    axs[1].set_ylabel("PWM High side")
    axs[1].set_ylim(-0.2, 1.2)
    axs[1].grid(True, alpha=0.3)
    axs[1].set_xticks(np.arange(0, TIME_WINDOW + grid_step, grid_step))    # График 2: PWM Low side
    axs[2].step(t, low_side_deadtime.astype(int), where='post', linewidth=2)
    axs[2].axvline(x=stop_time_4, color='green', linestyle='--', alpha=0.7, linewidth=2)
    axs[2].axvline(x=stop_time_5, color='orange', linestyle='--', alpha=0.7, linewidth=2)
    add_edge_lines(axs[2], high_side_rising_edges, high_side_falling_edges)
    axs[2].set_ylabel("PWM Low side")
    axs[2].set_ylim(-0.2, 1.2)
    axs[2].grid(True, alpha=0.3)
    axs[2].set_xticks(np.arange(0, TIME_WINDOW + grid_step, grid_step))    # График 3: High switch output
    axs[3].step(t, custom_signal_4.astype(int), where='post', linewidth=2, color='red')
    axs[3].axvline(x=stop_time_4, color='green', linestyle='--', alpha=0.7, linewidth=2)
    axs[3].axvline(x=stop_time_5, color='orange', linestyle='--', alpha=0.7, linewidth=2)
    add_edge_lines(axs[3], high_side_rising_edges, high_side_falling_edges)
    freeze_level_4 = "1" if invert_levels else "0"
    axs[3].set_ylabel("HSw output")
    axs[3].set_ylim(-0.2, 1.2)
    axs[3].grid(True, alpha=0.3)
    axs[3].set_xticks(np.arange(0, TIME_WINDOW + grid_step, grid_step))    # График 4: Low switch output
    axs[4].step(t, custom_signal_5.astype(int), where='post', linewidth=2)
    axs[4].axvline(x=stop_time_4, color='green', linestyle='--', alpha=0.7, linewidth=2)
    axs[4].axvline(x=stop_time_5, color='orange', linestyle='--', alpha=0.7, linewidth=2)
    add_edge_lines(axs[4], high_side_rising_edges, high_side_falling_edges)    # Добавляем черную толстую линию для мертвого времени (когда оба сигнала = 0)
    deadtime_signal = (custom_signal_4 == 0) & (custom_signal_5 == 0)
    # Создаем линию на уровне 0.5 только там, где есть мертвое время
    deadtime_line = np.where(deadtime_signal, 0.5, np.nan)  # NaN делает линию невидимой
    axs[4].plot(t, deadtime_line, linewidth=4, color='black', label='Мертвое время')

    freeze_level_5 = "0" if invert_levels else "1"
    timing_text = "опережение" if invert_levels else "отставание"
    axs[4].set_ylabel("LSw output")
    axs[4].set_ylim(-0.2, 1.2)
    axs[4].set_xlabel("Время (с)")
    axs[4].grid(True, alpha=0.3)
    axs[4].set_xticks(np.arange(0, TIME_WINDOW + grid_step, grid_step))
    axs[4].legend(loc='upper right')# Обновляем заголовок
    invert_status = "Инвертированный" if invert_levels else "Обычный"
    title = (f'PWM: {pwm_period:.2f}с, Заполнение: {duty_cycle:.2f}, '
             f'Мертвое время: {dead_time:.3f}с, Точка останова: {stop_time_4:.3f}с ({invert_status})')
    fig.suptitle(title, fontsize=12, fontweight='bold')

    # Перерисовываем
    fig.canvas.draw()

# === ОБРАБОТЧИКИ СОБЫТИЙ ===
def on_slider_change(val):
    update_plots()

def reset_sliders(event):
    slider_pwm_period.reset()
    slider_duty_cycle.reset()
    slider_dead_time.reset()
    slider_stop_time.reset()
    slider_grid_step.reset()
    # Сбрасываем чекбокс в исходное состояние
    checkbox_invert.set_active(0)
    update_plots()

def on_checkbox_change(label):
    update_plots()

# Подключаем обработчики к слайдерам
slider_pwm_period.on_changed(on_slider_change)
slider_duty_cycle.on_changed(on_slider_change)
slider_dead_time.on_changed(on_slider_change)
slider_stop_time.on_changed(on_slider_change)
slider_grid_step.on_changed(on_slider_change)
button_reset.on_clicked(reset_sliders)
checkbox_invert.on_clicked(on_checkbox_change)

# === ИНИЦИАЛИЗАЦИЯ ===
# Первоначальная отрисовка
update_plots()

plt.show()

И вот как это выглядит:

Здесь самой важной является черная линия мертвого времени на последнем  графике
Здесь самой важной является черная линия мертвого времени на последнем графике

Ползунками можно регулировать все ключевые параметры моделирования двухтактного алгоритма переключения статических состояний ШИМ.
Основная задача заключалась в том, чтобы понять, в какой момент обращаться к регистру GPTCDDSR и тем самым минимизировать длительность мёртвого времени, сохраняя его минимально допустимым. Довольно быстро стало ясно, что переключение можно начинать в обеих точках излома треугольника, изображающего значение счётчика ШИМ. Именно в этих точках и возникает прерывание АЦП, поэтому дополнительных источников прерываний организовывать не требуется. На этой позитивной новости, я поручил копилоту сгенерировать функцию переключения ШИМ по описанному алгоритму — и менее чем через минуту она была готова.
Вот она:

Функция Set_pwm_enhanced
/*-----------------------------------------------------------------------------------------------------
  Description:
    Enhanced PWM switching with two-stage mode transitions for safe motor control.
    Implements safe switching between PWM modes (0%, 100%, PWM) using two-stage logic:
    - Stage 1: Force both outputs to LOW (safe intermediate state)
    - Stage 2: Set target output configuration (0%, 100%, or enable PWM mode)
    Current state is determined by reading OADTY and OBDTY bits from GTUDDTYC register.
    This prevents shoot-through current by ensuring safe transitions through LOW-LOW state.

  Parameters:
    motor        - motor identifier (0-(DRIVER_COUNT-1))
    phase        - phase identifier (0-(PHASE_COUNT-1))
    pwm_level    - PWM duty cycle in steps (0-PWM_STEP_COUNT)
    output_enable - output enable flag (0=switches OFF, 1=switches enabled)

  Return:
    none
-----------------------------------------------------------------------------------------------------*/
FORCE_INLINE_PRAGMA
FORCE_INLINE_ATTR void _Set_pwm_enhanced(uint8_t motor, uint8_t phase, uint32_t pwm_level, uint8_t output_enable)
{
  R_GPT0_Type *gpt_reg = g_gpt_registers[motor][phase];
  // If output is being disabled, force both switches OFF (high-Z state for coast mode)
  if (output_enable == PHASE_OUTPUT_DISABLE)
  {
    T_gtuddtyc_bits gtuddtyc_temp = *(T_gtuddtyc_bits *)&gpt_reg->GTUDDTYC;

    // Both switches OFF (high-Z state)
    gtuddtyc_temp.OADTY           = 0x2;  // GTIOA = LOW (upper switch OFF)
    gtuddtyc_temp.OADTYF          = 1;    // Enable force mode for GTIOA
    gtuddtyc_temp.OBDTY           = 0x2;  // GTIOB = LOW (lower switch OFF)
    gtuddtyc_temp.OBDTYF          = 1;    // Enable force mode for GTIOB

    gpt_reg->GTUDDTYC             = *(uint32_t *)&gtuddtyc_temp;

    // Update global state arrays for diagnostic display (Z-state: both switches OFF)
    g_phase_state_0_percent[motor][phase]   = 0;  // Not in 0% duty state
    g_phase_state_100_percent[motor][phase] = 0;  // Not in 100% duty state
    g_phase_state_pwm_mode[motor][phase]    = 0;  // Not in PWM mode
    g_phase_state_Z_stage[motor][phase]     = 1;  // In Z-state (high impedance)
    return;
  }
  T_gtuddtyc_bits gtuddtyc_temp     = *(T_gtuddtyc_bits *)&gpt_reg->GTUDDTYC;
  // Read current state from OADTY and OBDTY bits to determine transition stage
  uint8_t current_state_0_percent   = (gtuddtyc_temp.OADTY == 0x2) && (gtuddtyc_temp.OBDTY == 0x3);  // 0% duty state: GTIOA=LOW, GTIOB=HIGH
  uint8_t current_state_100_percent = (gtuddtyc_temp.OADTY == 0x3) && (gtuddtyc_temp.OBDTY == 0x2);  // 100% duty state: GTIOA=HIGH, GTIOB=LOW
  uint8_t current_state_pwm_mode    = (gtuddtyc_temp.OADTYF == 0) && (gtuddtyc_temp.OBDTYF == 0);    // PWM mode: both force modes disabled
  uint8_t current_state_Z_stage     = (gtuddtyc_temp.OADTY == 0x2) && (gtuddtyc_temp.OBDTY == 0x2);  // Z-state: both signals LOW (high impedance)

  // Store current states in global arrays for diagnostic access
  g_phase_state_0_percent[motor][phase]   = current_state_0_percent;
  g_phase_state_100_percent[motor][phase] = current_state_100_percent;
  g_phase_state_pwm_mode[motor][phase]    = current_state_pwm_mode;
  g_phase_state_Z_stage[motor][phase]     = current_state_Z_stage;

  if (pwm_level == 0)
  {
    // Target: 0% duty (GTIOA=LOW, GTIOB=HIGH)
    if (current_state_0_percent)
    {
      return;  // Already in target state
    }
    if (current_state_100_percent || current_state_pwm_mode)
    {
      // Stage 1: Set both signals to LOW (first stage)
      gtuddtyc_temp.OADTY  = 0x2;  // GTIOA = LOW
      gtuddtyc_temp.OADTYF = 1;    // Enable force mode for GTIOA
      gtuddtyc_temp.OBDTY  = 0x2;  // GTIOB = LOW
      gtuddtyc_temp.OBDTYF = 1;    // Enable force mode for GTIOB
    }
    else if (current_state_Z_stage)
    {
      // Stage 2: Set GTIOB to HIGH to complete 0% duty transition
      gtuddtyc_temp.OBDTY = 0x3;  // GTIOB = HIGH (lower switch ON)
      // GTIOA remains LOW (upper switch OFF)
    }
  }
  else if (pwm_level == PWM_STEP_COUNT)
  {
    // Target: 100% duty (GTIOA=HIGH, GTIOB=LOW)
    if (current_state_100_percent)
    {
      return;  // Already in target state
    }
    if (current_state_0_percent || current_state_pwm_mode)
    {
      // Stage 1: Set both signals to LOW (first stage)
      gtuddtyc_temp.OADTY  = 0x2;  // GTIOA = LOW
      gtuddtyc_temp.OADTYF = 1;    // Enable force mode for GTIOA
      gtuddtyc_temp.OBDTY  = 0x2;  // GTIOB = LOW
      gtuddtyc_temp.OBDTYF = 1;    // Enable force mode for GTIOB
    }
    else if (current_state_Z_stage)
    {
      // Stage 2: Set GTIOA to HIGH to complete 100% duty transition
      gtuddtyc_temp.OADTY = 0x3;  // GTIOA = HIGH (upper switch ON)
      // GTIOB remains LOW (lower switch OFF)
    }
  }
  else
  {
    // Target: Normal PWM operation (1 to PWM_STEP_COUNT-1)
    if (current_state_pwm_mode)
    {
      // Already in PWM mode, just update comparator values
      uint32_t comp_value = pwm_indx_to_comp[pwm_level];
      gpt_reg->GTCCR[0]   = comp_value;                  // GTCCRA - primary comparator register
      gpt_reg->GTCCR[2]   = comp_value;                  // GTCCRC - secondary comparator register
#if PWM_DEBUG_VALUES
      g_debug_gtccr0_values[motor][phase] = comp_value;  // Store debug value
      g_debug_gtccr2_values[motor][phase] = comp_value;  // Store debug value
#endif
      return;
    }
    if (current_state_0_percent || current_state_100_percent)
    {
      // Stage 1: Set both signals to LOW (first stage)
      gtuddtyc_temp.OADTY  = 0x2;  // GTIOA = LOW
      gtuddtyc_temp.OADTYF = 1;    // Enable force mode for GTIOA
      gtuddtyc_temp.OBDTY  = 0x2;  // GTIOB = LOW
      gtuddtyc_temp.OBDTYF = 1;    // Enable force mode for GTIOB
    }
    else if (current_state_Z_stage)
    {
      // Stage 2: Enable PWM mode
      // Set PWM comparator value
      uint32_t comp_value = pwm_indx_to_comp[pwm_level];
      gpt_reg->GTCCR[0]   = comp_value;                  // GTCCRA - primary comparator register
      gpt_reg->GTCCR[2]   = comp_value;                  // GTCCRC - secondary comparator register
#if PWM_DEBUG_VALUES
      g_debug_gtccr0_values[motor][phase] = comp_value;  // Store debug value
      g_debug_gtccr2_values[motor][phase] = comp_value;  // Store debug value
#endif

      // Clear force duty values and enable PWM mode
      gtuddtyc_temp.OADTY  = 0;  // Clear GTIOA force duty value
      gtuddtyc_temp.OBDTY  = 0;  // Clear GTIOB force duty value
      gtuddtyc_temp.OADTYF = 0;  // Disable force mode for GTIOA (enable PWM)
      gtuddtyc_temp.OBDTYF = 0;  // Disable force mode for GTIOB (enable PWM)
    }
  }
  // Apply the configuration
  gpt_reg->GTUDDTYC = *(uint32_t *)&gtuddtyc_temp;
}

Тут не стану обманывать: это не первоначальный вариант, выданный копилотом. Это версия после множества коррекций и оптимизаций, связанных с правильной интеграцией во фреймворк, принятым мной форматированием и повышением скорости выполнения.

Но факт, что функция изначально работала правильно, и теперь можно совершенно безопасно переключаться из ШИМ в 100 % или в 0 % и даже не переживать, какую задержку будет иметь процедура обслуживания прерывания, которая перенастраивает регистры, — но, конечно, в пределах разумного.

Что имеем в результате.
Удалось получить единый обработчик прерывания для управления всеми четырьмя моторами с периодом 62.5 микросекунды и длительностью выполнения 4.5 микросекунды. В этом обработчике двухтактным методом перестраиваются 6 фаз ШИМ, производится считывание 12 аналоговых каналов АЦП, управление аналоговыми мультиплексорами, а также выполняются процедуры EMA (Exponential Moving Average, экспоненциально взвешенное скользящее среднее) фильтрации аналоговых сигналов .

Комментарии (0)