Реализация PWM-контроллера в VHDL: проектирование, симуляция и FPGA-демо
Широтно-импульсная модуляция (PWM) превращает цифровой вывод FPGA в мощный аналоговый контроллер. Переключая питание включено и выключено на высокой скорости, PWM доставляет точный средний ток, нужный устройству, никогда не генерируя настоящего аналогового напряжения.
Типичные приложения PWM: аудиовывод для динамиков, затемнение светодиодов, управление моментом индукционных двигателей — сервоприводов, компьютерных вентиляторов, насосов и бесколлекторных двигателей постоянного тока для электромобилей.
Смотрите также: RC-сервопривод с PWM с вывода FPGA
Как работает PWM
Когда источник питания переключается между 100 % и 0 % на высокой частоте, усредненный по времени ток, видимый нагрузке, прямо пропорционален доле времени, когда питание включено. Следующая диаграмма иллюстрирует принцип:

Коэффициент заполнения (Duty Cycle)
Коэффициент заполнения определяет, как долго PWM-выход остается высоким в течение каждого периода. Выраженный в процентах, он вычисляется из значения бинарного счетчика:

В VHDL-реализации мы рассматриваем коэффициент заполнения как беззнаковое целое, давая полное разрешение PWM-счетчика. 0 % коэффициент заполнения выключает выход постоянно, 100 % — держит его постоянно включенным. Достигаемое разрешение определяется шириной счетчика.
Для преобразования бинарного значения коэффициента заполнения в проценты используйте:
\mathit{duty\_cycle\_percentage} = \frac{\mathit{commanded\_duty\_cycle} * 100}{2^\mathit{pwm\_bits} - 1}
PWM-частота
PWM-частота — обратная величина к периоду:
\mathit{pwm\_freq} = \frac{1}{\mathit{pwm\_period}}
Выбор правильной частоты зависит от целевого устройства. Для светодиодов достаточно несколько сотен герц, чтобы избежать видимого мерцания. Бесколлекторные двигатели постоянного тока обычно работают в диапазоне десятков килогерц; слишком низкая частота может вызывать механические вибрации, слишком высокая — потерю мощности.

Поскольку MOSFET-ключи медленнее логики FPGA, слишком высокая частота PWM может заставить транзистор проводить время в частично открытом состоянии, рассеивая лишнее тепло.
Модуль генератора PWM
Ниже приведена универсальная VHDL-реализация PWM-контроллера, который можно инстанцировать с любой шириной счетчика и коэффициентом деления тактового сигнала.
Объявление сущности
entity pwm is generic ( pwm_bits : integer; clk_cnt_len : positive := 1 ); port ( clk : in std_logic; rst : in std_logic; duty_cycle : in unsigned(pwm_bits - 1 downto 0); pwm_out : out std_logic ); end pwm;
Порты clk и rst обеспечивают синхронное управление. Вход duty_cycle имеет ширину, определенную pwm_bits, поэтому разрешение счетчика прямо конфигурируемо. Сигнал pwm_out маршрутизируется к выводу FPGA, управляющему затвором MOSFET.
Внутренние сигналы
signal pwm_cnt : unsigned(pwm_bits - 1 downto 0); signal clk_cnt : integer range 0 to clk_cnt_len - 1;
Счетчик pwm_cnt зеркалирует ширину коэффициента заполнения. Счетчик clk_cnt реализует опциональный делитель тактового сигнала; установка clk_cnt_len в 1 отключает делитель и удаляет связанную логику.
Процесс делителя тактового сигнала
CLK_CNT_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then clk_cnt <= 0; else if clk_cnt < clk_cnt_len - 1 then clk_cnt <= clk_cnt + 1; else clk_cnt <= 0; end if; end if; end if; end process;
Со значением по умолчанию clk_cnt_len = 1 инструменты синтеза автоматически устранят этот процесс.
Процесс PWM-выхода
PWM_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pwm_cnt <= (others => '0'); pwm_out <= '0'; else if clk_cnt_len = 1 or clk_cnt = 0 then pwm_cnt <= pwm_cnt + 1; pwm_out <= '0'; if pwm_cnt = unsigned(to_signed(-2, pwm_cnt'length)) then pwm_cnt <= (others => '0'); end if; if pwm_cnt < duty_cycle then pwm_out <= '1'; end if; end if; end if; end if; end process;
Счетчик инкрементируется только когда clk_cnt равен нулю, эффективно дросселируя частоту PWM. Условный оператор, сбрасывающий pwm_cnt на его втором по величине значении, гарантирует достижимость 100 % коэффициента заполнения.
\mathit{pwm\_hz} = \frac{\mathit{clk\_hz}}{(2^\mathit{pwm\_bits} - 1) * \mathit{clk\_cnt\_len}}
Топ-уровень дизайна
Для демонстрации PWM-контроллера дизайн управляет индикатором питания на плате Lattice iCEstick FPGA. 12-мегагерцовый генератор платы питает счетчик, управляющий интенсивностью светодиода по пилообразному закону.

Объявление сущности
entity pwm_led is generic ( pwm_bits : integer := 8; cnt_bits : integer := 25; clk_cnt_len : positive := 47 ); port ( clk : in std_logic; rst_n : in std_logic; led_1 : out std_logic; led_2 : out std_logic; led_3 : out std_logic; led_4 : out std_logic; led_5 : out std_logic ); end pwm_led;
12 МГц такт и выбранные generic-параметры дают PWM-частоту примерно 1 кГц:
\frac{12e6}{(2^8 - 1) * 47} \approx 1 \text{ кГц}
Generic cnt_bits управляет 25-битным пилообразным счетчиком, задающим коэффициент заполнения светодиода. Его период около 2.8 Гц:
\frac{2^{\mathit{cnt\_bits}}}{\mathit{clk\_hz}} = \frac{2^{25}}{12e6} \approx 2.8 \text{ Гц}
Используется только LED 5; остальные притянуты к нулю, чтобы избежать случайного свечения.
Внутренние сигналы и подключение
architecture str of pwm_led is signal rst : std_logic; signal cnt : unsigned(cnt_bits - 1 downto 0); signal pwm_out : std_logic; alias duty_cycle is cnt(cnt'high downto cnt'length - pwm_bits); begin led_1 <= '0'; led_2 <= '0'; led_3 <= '0'; led_4 <= '0'; led_5 <= pwm_out;
Сигнал rst — синхронизированная, неинвертированная версия внешнего сброса. Псевдоним duty_cycle отображает старшие биты cnt на PWM-контроллер.
Инстанциация модулей
RESET : entity work.reset(rtl) port map ( clk => clk, rst_n => rst_n, rst => rst ); PWM : entity work.pwm(rtl) generic map ( pwm_bits => pwm_bits, clk_cnt_len => clk_cnt_len ) port map ( clk => clk, rst => rst, duty_cycle => duty_cycle, pwm_out => pwm_out ); COUNTER : entity work.counter(rtl) generic map ( counter_bits => cnt'length ) port map ( clk => clk, rst => rst, count_enable => '1', counter => cnt );
Симуляция
Generic-параметры упрощают ускорение симуляции. Уменьшая cnt_bits и clk_cnt_len, можно наблюдать полные циклы PWM за несколько микросекунд вместо нескольких секунд.
Инстанциация тестбенча:
DUT : entity work.pwm_led(str) generic map ( pwm_bits => 8, cnt_bits => 16, clk_cnt_len => 1 );
Волновая форма в ModelSim показывает пилообразный счетчик, управляющий яркостью светодиода. Кратковременный импульс «включения» появляется на вершине каждого цикла, когда коэффициент заполнения достигает максимума.

FPGA-реализация
После синтеза в iCEcube2 дизайн потребляет только 5 % из 64 доступных LUT на чипе ice40hx1ktq144:
Resource Usage Report for pwm_led Mapping to part: ice40hx1ktq144 Cell usage: GND 3 uses SB_CARRY 31 uses SB_DFF 5 uses SB_DFFSR 39 uses SB_GB 1 use VCC 3 uses SB_LUT4 64 uses I/O ports: 7 I/O primitives: 7 SB_GB_IO 1 use SB_IO 6 uses I/O Register bits: 0 Register bits not including I/Os: 44 (3%) Total load per clock: pwm_led|clk: 1 @S |Mapping Summary: Total LUTs: 64 (5%)
Программирование FPGA по USB с помощью standalone-программатора Diamond прошло гладко. Получившаяся анимация светодиода показывает плавный нарастание яркости, за которым следует кратковременное затухание перед повторением цикла.

Эффект «дыхания» с синусоидальным коэффициентом заполнения
Замена пилообразного счетчика таблицей поиска синусоиды создает естественный эффект «дыхания», часто встречающийся на индикаторах уведомлений.

В следующей статье мы покажем, как построить генератор синусоиды с использованием блочной памяти и интегрировать его в дизайн pwm_led.
Читайте следующий пост: Как создать эффект «дыхания» светодиода с помощью синусоиды, хранящейся в блочной памяти
Смотрите также: RC-сервопривод с PWM с вывода FPGA
VHDL
- PWM контроллер мощности: сборка драйвера лампы с пульсирующей шириной модуляции
- Создание списков строк в VHDL: лучшие практики и пример
- Создание Tcl-управляемого тестбенча для VHDL-модуля кодового замка
- Корректное завершение симуляции VHDL-тестбенча
- Генерация случайных чисел в VHDL: от Uniform к OSVVM
- Проектирование надежного кольцевого буфера FIFO на VHDL в блочной ОЗУ
- Как создать самопроверяющийся тестбенч в VHDL — практическое руководство
- Реализация динамического связного списка в VHDL с защищенными типами и указателями доступа
- Использование внутрипроцессных процедур для более чистого проектирования FSM на VHDL
- Пошаговое руководство: безопасная и эффективная зарядка конденсаторов