Настоящие методические указания содержат этапы проектирования ШИМ контроллера в САПР Xilinx ISE Design Suite на языке описания аппаратуры Verilog. Дополнительно приводятся примеры практического применения, разработанного ШИМ контроллера.

СОДЕРЖАНИЕ

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ.

РЕАЛИЗАЦИЯ ШИМ КОНТРОЛЛЕРА В САПР XILINX ISE DESIGN SUITE.

СИМУЛЯЦИЯ.

ДОРАБОТКА ПРОЕКТА.

ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ ШИМ.

САМОСТОЯТЕЛЬНЫЕ ЗАДАНИЯ.

СПИСОК РЕКОМЕНДУЕМЫХ ИСТОЧНИКОВ.

 

ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ

ШИМ или PWM (широтно-импульсная модуляция, по-английски pulse-width modulation) – это способ управления подачей мощности к нагрузке. Управление заключается в изменении длительности импульса при постоянной частоте следования импульсов.

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

Как было сказано выше, принцип регулирования мощности в нагрузке с помощью ШИМ заключается в изменении ширины импульсов при постоянной амплитуде и частоте, таким образом, чем ближе отношение времени включения ( ) к периоду сигнала ( ) к единице, тем больше мощности подается в нагрузку, такое отношение называется – Коэффициент заполнения (далее D).

На рисунке 1 представлены ШИМ сигналы с D = 0.1, 0.5 и 0.9 (можно выражать в процентах).

 

Рисунок 1 – ШИМ сигнал с D = 0.1, 0.5 и 0.9

Таким образом, для реализации ШИМ контроллера, нам необходимо определиться какое количество тактов внешнего кварцевого резонатора будет выделено для периода ШИМ сигнала, а какое количество таков будет выделено для длительности активного уровня сигнала.

  

РЕАЛИЗАЦИЯ ШИМ КОНТРОЛЛЕРА В САПР XILINX ISE DESIGN SUITE

Задание: Разработать в Xilinx ISE Design Suite с использованием языка Verilog модуль ШИМ контроллера, с возможностью задавать частоту ШИМ сигнала и длительность импульса.

Как было сказано выше, задача сводится к тому, что нам необходимо выделить определенное количество таков внешнего тактового сигнала на период ШИМ и меньшее либо равное количество тактов на длительность активного уровня сигнала. Например, в случае, если частота тактового генератора равна 50 МГц, а для периода ШИМ мы выделили 1000 тактов, то период ШИМ сигнала будет составлять: , а если для длительности активного уровня выделить 100 тактов, то D будет рано:  или 10 %, таким образом активный уровень будет длиться 2 мкс.

Назовем разрабатываемый модуль – pwm_controller. В таблице 1 представлен интерфейс разрабатываемого модуля, отражающий входные и выходные сигналы.

Таблица 1 – Интерфейс модуля pwm_controller

 

Название

Направление

Назначение

clk_i

Вход

Внешний тактовый сигнал

period_i[31:0]

Вход

Количество тактов на период

duty_i[31:0]

Вход

Количество тактов на длительность активного уровня

pwm_o

Выход

Выход на котором формируется ШИМ сигнал

 

В соответствии с таблицей 1, создадим модуль pwm_controller на языке Verilog:

module pwm_controller

(

input clk_i,

input [31:0] period_i,

input [31:0] duty_i,

output pwm_o

);

После ключевого слова module указываем имя разрабатываемого нами модуля, затем в скобках перечисляем входные и выходные сигналы с помощью ключевых слов input и output соответственно. Запись [31:0] означаетчто сигнал является 32-х разрядной шиной, в нашем случае, это означает, что количество тактов не может превышать число равное .

Для того, чтобы отсчитывать необходимое нам количество тактов введем 32-х разрядный счетчик и зададим ему начальное значение:

reg [31:0] internal_counter = 0;

Тип reg говорит о том, что счетчик будет сохранять свое состояние до момента времени пока не придет следующий тактовый сигнал.

Далее нам необходимо описать работу счетчика, в данном случае наш счетчик будет считать до значения, которое находится на входе  period_i, после чего будет сбрасываться в ноль и начинать счет заново. Реализуем это с помощью поведенческого блока always @:

always @(posedge clk_i) begin

     if(internal_counter >= (period_i – 1)) begin

          internal_counter <= 0;

     end

     else begin

          internal_counter <= internal_counter + 1'b1;

     end

end

Как видно, по приходу каждого фронта тактового сигнала clk_i происходит проверка достиг ли счетчик значения, которое находится на входе period_i – 1 (необходимо вычитать единицу, так как счетчик начинает считать с нуля)в случае если достиг, счетчик обнуляется, в противном случае, происходит инкремент.

На последнем этапе нам необходимо описать логику формирования активного уровня на необходимое нам время. Так как выход pwm_o является сигналом типа wire, мы не можем использовать его внутри поведенческого блока, для описания его работы необходимо использовать оператор непрерывного присваивания assign.

Логика формирования длительности активного уровня заключается в следующем, если значение счетчика internal_counter меньше, чем значение на входе duty_i, то на выходе pwm_o, будет уровень логической единицы, в противном случае уровень логического нуля, для этого будем использовать тернарный оператор:

assign pwm_o = (internal_counter < (duty_i – 1)) ? 1 : 0;

Полный листинг модуля представлен в таблице 2.

Таблица 2 – Листинг модуля – pwm_controller

module pwm_controller

(

      input clk_i,

      input [31:0] period_i,

      input [31:0] duty_i,

      output pwm_o

);

 

      reg [31:0] internal_counter = 0;

      always @(posedge clk_i) begin

             if(internal_counter >= (period_i - 1)) begin

                   internal_counter <= 0;

             end

             else begin

                   internal_counter <= internal_counter + 1'b1;

             end

      end

 

      assign pwm_o = (internal_counter < (duty_i - 1)) ? 1 : 0;

 

endmodule

 

СИМУЛЯЦИЯ

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

Для создания testbench необходимо правой клавишей мыши нажать на проект, выбрать New Source -> Verilog Test Fixture задать имя проекта и в следующем окне выбрать разработанный ранее модуль.

Для корректной симуляции нам необходимо следующее: изменять тактовый сигнал каждый 10 нс (что соответствует периоду 20 нс и частоте 50 МГц), задать значения period_i и duty_i.

Зададим количество тактов на период равно 1000, а количество тактов на длительность активного уровня равное 100, таким образом D = 0.1:

period_i = 1000;

duty_i = 100;

Изменять состояние тактового сигнала будем следующим образом:

always #10 clk_i = ~clk_i;

Данная запись говорит о том, что каждые 10 нс, тактовый сигнал будет инвертироваться.

Полный листинг testbench представлен в Таблице 3.

Таблица 3 – Листинг testbench

`timescale 1ns / 1ps

module pwm_controller_test;

      // Inputs

      reg clk_i;

      reg [31:0] period_i;

      reg [31:0] duty_i;

      // Outputs

      wire pwm_o;

      // Instantiate the Unit Under Test (UUT)

      pwm_controller uut (

             .clk_i(clk_i),

             .period_i(period_i),

             .duty_i(duty_i),

             .pwm_o(pwm_o)

      );

      initial begin

             // Initialize Inputs

             clk_i = 0;

             period_i = 1000;

             duty_i = 100;

             // Wait 100 ns for global reset to finish

             #100;

      end

      always #10 clk_i = ~clk_i;

endmodule

 

Xilinx ISE Design Suite сам генерирует testbench, необходимо только указать как должны изменяться входные сигналы.

Последним этапом симуляции является анализ временных диаграмм, результат представлен на рисунке 2.

  

Рисунок 2 – Симуляция ШИМ сигнала с D = 0.1

Как видно из рисунка 2, период сигнала составляет , а длительность активного уровня , что соответствует.

Для достоверности корректной работы разработанного модуля, попробуем сформировать ШИМ сигнал с периодом 24 кГц и D = 0.5.

Так как частота 24 кГц соответствует периоду приблизительно равному 41 мкс, необходимо задать period_i = 2100, а для получения D = 0.5 duty_i = 1050 (все расчеты ведутся при условии, что частота тактового сигнала – 50 МГц).

На рисунке 3 представлена временная диаграмма данного сигнала.

Рисунок 3 – ШИМ сигнал с частотой 24 кГц и D = 0.5

Как видно разработанный ШИМ контроллер работает корректно.

 

ДОРАБОТКА ПРОЕКТА

Попробуем немного усовершенствовать разработанный модуль, добавив возможность выбирать активный уровень (т. е. длительность какого логического уровня мы будем задавать на входе duty_i – нуля или единицы), а также добавим сигнал разрешения, который будет активен при подаче логической единицы, а при логическом нуле на входе будет запрещать работу ШИМ контроллера.

Для выбора активного уровня в инициализацию модуля добавим parameter, а также дополнительно добавим вход разрешения:

module pwm_controller

#( parameter POLARITY = 1)

(

     input clk_i,

     input enable_i,

     input [31:0] period_i,

     input [31:0] duty_i,

     output pwm_o  

);

 

Работа счетчика останется неизменной, за исключением того, что теперь он начнет считать только при наличии логической единицы на входе enable_i:

reg [31:0] internal_counter = 0;

always @(posedge clk_i) begin

if(enable_i) begin

          if(internal_counter >= (period_i - 1)) begin

               internal_counter <= 0;

          end

          else begin

               internal_counter <= internal_counter + 1'b1;

          end

     end

     else internal_counter <= 0;

end

 

Описание логики выходного сигнала pwm_o станет немного сложнее:

assign pwm_o = enable_i ? ((internal_counter < (duty_i - 1)) ?

            (POLARITY ? 1 : 0) :

           (POLARITY ? 0 : 1)) : 0;

 Теперь мы изначально проверяем есть ли логическая единица на входе разрешения, если она есть, то проверяем является ли истинным выражение (internal_counter < (duty_i - 1), если данное выражение является истинным, то проверяем значение параметра POLARITY, если он равен единицу, то на выходе будет 1, если он равен нулю, то на выходе будет 0 (именно это выражение и определяет активный уровень сигнала), в случае, если выражение(internal_counter < (duty_i - 1) ложно, то проверяем значение параметра POLARITY, если он равен единицу, то на выходе будет 0, если он равен нулю, то на выходе будет 1. В случае, если на входе разрешения работы уровень нуля, то на выходе pwm_o будет ноль. Для удобства каждое условие и блоки ветвления, которые к нему относятся были выделены одним цветом.

В таблице 4 представлен полный листинг для модуля pwm_controller_v2.

Таблица 4 – Листинг модуля pwm_controller_v2

module pwm_controller

#( parameter POLARITY = 1)

(

      input clk_i,                // input clk

      input enable_i,            // enable signal

      input [31:0] period_i, // pwm period ticks

      input [31:0] duty_i,   // pwm duty ticks

      output pwm_o                // pwm output

);

 

/**

  *

  * PWM_PERIOD    = period_i / clk_i  (s)

  * PWM_DUTY_TIME = duty_i / clk_i    (s)

  * PWM_FREQ      = clk_i / period_i  (Hz)

  *

  */

 

      reg [31:0] internal_counter = 0;

      always @(posedge clk_i) begin

             if(enable_i) begin

                   if(internal_counter >= (period_i - 1)) begin

                          internal_counter <= 0;

                   end

                   else begin

                          internal_counter <= internal_counter + 1'b1;

                   end

             end

             else internal_counter <= 0;

      end

     

      assign pwm_o = enable_i ? ((internal_counter < (duty_i - 1)) ?

                                (POLARITY ? 1 : 0) : (POLARITY ? 0 : 1)) : 0;

 

endmodule

 Для того, чтобы удостовериться в корректности работы, проведем симуляцию ШИМ сигнала, описанного в предыдущей главе, но зададим активный уровень инверсный. На рисунке 4 представлена соответствующая временная диаграмма.

Рисунок 4 – ШИМ сигнал с инверсным активным уровнем

 

Как видно временная диаграмма, представленная на рисунке 4 является инверсной по отношению к временной диаграмме, представленной на рисунке 2. Таким образом можно сказать, что данный ШИМ контроллер работает корректно.

 

ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ ШИМ

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

В данном случае  – это амплитуда ШИМ сигнала (это не всегда обязательно и зависит от схемы, часто – это напряжение питания транзисторов на затворы которых подается ШИМ сигнал).

Попробуем убедиться в этом, подсоединив вывод ПЛИС с которого поступает ШИМ сигнал к ФНЧ на RC цепи. На рисунках 5, 6 представлены осциллограммы ШИМ сигнала и выхода ФНЧ с D = 0.1 и 0.5 соответственно (амплитуда ШИМ сигнала 3.3 В).

Рисунок 5 – ШИМ сигнал с D = 0.1 (желтый), напряжение на выходе ФНЧ = 300 мВ (зеленый)

Рисунок 6 – ШИМ сигнал с D = 0.5 (желтый), напряжение на выходе ФНЧ = 1.65 В (зеленый)

 

Воспользуемся этим свойством и попробуем с помощью ШИМ и ФНЧ реализовать сигнал функции  (модуль синуса) с длительностью полуволны 20 мс.

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

Где:

А – амплитуда, но в нашем случае это количество тактов, выделенное на период ШИМ сигнала (так как когда D = 1 напряжение на выходе ФНЧ максимально)

 – номер отсчета

N – количество отсчетов

 Для примера выделим 40 отсчетов на полуволну синуса, таким образом на половину полуволны у нас будет 20 отсчетов, при N = 20 шаг дискретизации будет равен  (10 мс – это длительность половины полуволны нашего синуса), таким образом нам необходимо раз в 0.5 мс увеличивать D у ШИМ сигнала, а затем зеркально уменьшать. В таблице 5 приведены значения  для 20 отсчетов (их можно получить воспользовавшись библиотечной функцией синуса в любом языке программирования).

 

Таблица 5 – Отсчеты половины полуволны синуса

 

Номер отсчета

Значение

0

1

1

78

2

156

3

233

4

309

5

383

6

454

7

522

8

588

9

649

10

707

11

760

12

809

13

852

14

891

15

924

16

951

17

972

18

987

19

996

20

1000

 

 

Первое значение равно 1, так как мы хотим получить в нулевой момент времени значение 0 В, а сравнение счетчика происходит со значением internal_counter < (duty_i - 1) (так как счет начинается с нуля), поэтому для корректной работы для D = 0, duty_i надо задавать равным 1.

Для реализации второй половины полуволны необходимо просто дополнить данную таблицу еще 20-ю значениями, но зеркально отраженных.

Для реализации данной задачи в Xilinx ISE Design Suite создадим модуль rom, где будут храниться все 40 отсчетов. Данный модуль будет выполнять роль ПЗУ, а каждый новый отсчет будет лежать по следующему адресу. Листинг модуля rom представлен в таблице 6.

Таблица 6 – Листинг модуля rom

module rom

(

      input [7:0] addr,

      output reg [31:0] dout

);

 

      always @(*) begin

             case(addr)

                   8'd0:    dout <= 32'd1;

                   8'd1:    dout <= 32'd78;

                   8'd2:    dout <= 32'd156;

                   8'd3:    dout <= 32'd233;

                   8'd4:    dout <= 32'd309;

                   8'd5:    dout <= 32'd383;

                   8'd6:    dout <= 32'd454;

                   8'd7:    dout <= 32'd522;

                   8'd8:    dout <= 32'd588;

                   8'd9:    dout <= 32'd649;

                   8'd10:   dout <= 32'd707;

                   8'd11:   dout <= 32'd760;

                   8'd12:   dout <= 32'd809;

                   8'd13:   dout <= 32'd852;

                   8'd14:   dout <= 32'd891;

                   8'd15:   dout <= 32'd924;

                   8'd16:   dout <= 32'd951;

                   8'd17:   dout <= 32'd972;

                   8'd18:   dout <= 32'd987;

                   8'd19:   dout <= 32'd996;

                   8'd20:   dout <= 32'd1000;

                   8'd21:   dout <= 32'd996;

                   8'd22:   dout <= 32'd987;

                   8'd23:   dout <= 32'd972;

                   8'd24:   dout <= 32'd951;

                   8'd25:   dout <= 32'd924;

                   8'd26:   dout <= 32'd891;

                   8'd27:   dout <= 32'd852;

                   8'd28:   dout <= 32'd809;

                   8'd29:   dout <= 32'd760;

                   8'd30:   dout <= 32'd707;

                   8'd31:   dout <= 32'd649;

                   8'd32:   dout <= 32'd588;

                   8'd33:   dout <= 32'd522;

                   8'd34:   dout <= 32'd454;

                   8'd35:   dout <= 32'd383;

                   8'd36:   dout <= 32'd309;

                   8'd37:   dout <= 32'd233;

                   8'd38:   dout <= 32'd156;

                   8'd39:   dout <= 32'd78;       

                   default: dout <= 1;

             endcase

      end

endmodule

 

Далее создадим модуль pwm_abs_sin с одним входом clk_i и одним выходом pwm_o, сигнал с которого будет подаваться на вход ФНЧ.

 

module pwm_abs_sin

(

     input clk_i,

     output pwm_o

);

 

Далее добавим локальный параметр с количеством отсчетов для полупериода синуса, счетчик, который будет считать до своего модуля счета 0.5 мс, счетчик адреса, который будет увеличиваться на единицу каждые 0.5 мс (выход данного счетчика будет подключен к адресному входу ПЗУ, таким образом каждые 0.5 мс мы будем запрашивать значение по следующему адресу) и провод для соединения выхода ПЗУ и входа duty_i ШИМ контроллера.

localparam COUNTS_COUNT = 40 - 1;

reg [31:0] counter = 0;

reg [7:0] addr_counter = 0;

wire [31:0] duty;

 

Далее опишем работу счетчика адреса ПЗУ в поведенческом блоке. Нам необходимо каждые 0.5 мс увеличивать его значение на 1, а когда он достигнет адреса последнего отсчета сбросить его в 0 и начать считать заново. Для того, чтобы регистрировать момент времени 0.5 мс, мы будем использовать счетчик counter с модулем счета 25000 (при входной тактовой частоте 50 МГц), каждый раз когда counter досчитывает до 25000 мы сбрасываем counter в ноль и увеличиваем addr_counter на 1:

always @(posedge clk_i) begin

     if(counter == 25_000) begin

          counter <= 0;

          if(addr_counter >= COUNTS_COUNT) begin

               addr_counter <= 0;

          end

          else begin

               addr_counter <= addr_counter + 1'b1;

          end

     end

     else begin

          counter <= counter + 1;

     end

end

Завершающим этапом является подключение модуля ШИМ контроллера и ПЗУ, причем выход ПЗУ должен быть соединен со входом, отвечающим за коэффициент заполнения ШИМ контроллера, частота ШИМ будет равна 50 кГц:

rom counts_rom (.addr(addr_counter), .dout(duty));

pwm_controller #(.POLARITY(1)) pwm (.clk_i(clk_i),

.enable_i(1),    

.period_i(1000),

.duty_i(duty),

.pwm_o(pwm_o));

 

Листинг полного модуля представлен в таблице 7.

Таблица 7 – Листинг модуля pwm_abs_sin

module pwm_abs_sin

(

      input clk_i,

      output pwm_o

);

      localparam COUNTS_COUNT = 40 - 1;

     

      reg [31:0] counter = 0;

      reg [7:0] addr_counter = 0;

      wire [31:0] duty;

 

      always @(posedge clk_i) begin

             if(counter == 25_000) begin

                   counter <= 0;

                   if(addr_counter >= COUNTS_COUNT) begin

                          addr_counter <= 0;

                   end

                   else begin

                          addr_counter <= addr_counter + 1'b1;

                   end

             end

             else begin

                   counter <= counter + 1;

             end

      end

     

 

      rom counts_rom (.addr(addr_counter), .dout(duty));

 

      pwm_controller #(.POLARITY(1)) pwm (.clk_i(clk_i),

                                          .enable_i(1),

                                          .period_i(1000),

                                          .duty_i(duty),

                                          .pwm_o(pwm_o));

 

endmodule

 

Рекомендация: Все постоянные значения лучше всего передавать с помощью localparam или parameter.

На рисунке 7 представлены осциллограммы на входе (желтый) и на выходе ФНЧ (зеленый).

Рисунок 7 – Осциллограмма функции 

 Как видно из рисунка 7, длительность полуволны синуса действительно составляет 20 мс, это значит, что все расчеты и проектирование были проведены верно. Также видно, что на пике полуволны коэффициент заполнения ШИМ сигнала максимальный.

  

САМОСТОЯТЕЛЬНЫЕ ЗАДАНИЯ

  1. Реализовать на основе модуля pwm_controller 4-х канальный ШИМ контроллер с возможностью настраивать период, коэффициент заполнения и активный уровень “0” или “1” отдельно для каждого канала.
  2. Реализовать на основе модуля pwm_controller модуль для управления сервоприводом. Управление сервоприводом осуществлять в соответствии с временными диаграммами, представленными на рисунке 8.

Рисунок 8 – Сигналы для управления сервоприводом

  1. Реализовать с помощью модуля pwm_controller и ФНЧ, модуль для получения синусоидального сигнала с частотой 50 Гц, форма и напряжения сигнала представлены на рисунке 9.

Рисунок 9 – Результирующий синусоидальный сигнал для задания 3

 

  1. Реализовать с помощью модуля pwm_controller и схемы полного моста, модуль для получения синусоидального сигнала с частотой 50 Гц, принципиальная схема полного моста с необходимыми входными сигналами представлена на рисунке 10.

Рисунок 10 – Принципиальная схема полного моста с осциллограммами необходимых сигналов для получения синуса

  

СПИСОК РЕКОМЕНДУЕМЫХ ИСТОЧНИКОВ

  1. Цифровая схемотехника и архитектура компьютера, С. Харрис, Д. Харрис
  2. FPGA prototyping by Verilog examples, Pong P. Chu
  3. Краткий курс HDL, Иосиф Каршенбойм
  4. asic-world.com
  5. Тарасов И.Е. Разработка цифровых устройств на основе ПЛИС Xilinx с применением языка VHDL
  6. Соловьев В.В. - Архитектуры ПЛИС фирмы Xilinx CPLD и FPGA 7-й серии. 2016
  7. Сергиенко А.М. - VHDL для проектирования вычислительных устройств. 2003