Эта статья ориентирована на новичков в программировании ПЛИС на языке VHDL и тех, кто хочет научиться это делать. Ранее на хабре уже была рассмотрена статья с аналогичной задачей, реализованной на PIC-контроллере. А в этой статье речь пойдет об изменении яркости свечения светодиода с помощью ПЛИС.
Итак, цель работы: Освоить понятие ШИМ и применить его в изменении яркости светодиода. Для реализации воспользоваться языком программирования VHDL в среде разработки Xilinx ISE Project Navigator v12.3.


Перейдем к реализации цели


Для реализации нам потребуется какая-нибудь железка с ПЛИС, я выбрал имеющуюся под рукой платку Spartan-3E Starter Kit (DataSheet). Так же необходимо установить Xilinx ISE Project Navigator (у меня установлена версия 12.3). В принципе, все готово работе. Осталось только подключить питание к платке и соединить ее по USB с компьютером для последующего программирования.

Часть 1. Теория изменения яркости светодиода.


Яркость с��етодиода можно регулировать подачей на него разных значений постоянного напряжения (например, переменным резистором). Но на нашей плате установлены светодиоды без переменных резисторов, которые могут принимать значение ‘1’ и светиться в полную яркость, либо ‘0’. Так как же тогда сделать регулировку яркости у такого столь простого девайса? Ответ – ШИМ. Вся суть в том, что мы будем «моргать» этим светодиодом настолько быстро, что моргание даже не будет заметно нашему глазу, а мы просто будем видеть тускло светящийся светодиод. Если сказать точнее, то просто у светодиода есть переходный процесс при зажигании, то есть загорается он не мгновенно. Именно этим мы пользуемся, подавая единицу на очень короткий промежуток времени, так чтобы светодиод не успел загореться в полную яркость.

Часть 2. Создание нового проекта.


Загружаем ISE Project Navigator и тыкаем File -> New Project. Пишем имя проекта (у меня shim_habr), выбираем директорию для сохранения и снизу выбираем Top-level source type: HDL. Жмем Next.



Далее выбираем ПЛИС. В моем случае Family: Spartan3E; Device: XC3S500E; Package: FG320; Speed: -4. Все эти данные можно увидеть на самой микросхеме, либо посмотреть в даташите.



Далее выбираем Preferred Language: VHDL, жмем Next и потом Finish.



Проект создан. Теперь нужно добавить в него модуль, в котором мы будем описывать логику работы светодиода. Слева вверху находим кнопку New Source и жмем на нее:


В появившемся окне выбираем VHDL Module и пишем любое имя файла (можно одноименное с проектом shim_habr). Жмем Next.


Теперь нам нужно задать используемые в проекте ножки. Этого можно не делать сейчас и пропустить этот шаг, а потом написать все руками. Но поскольку для нашего проекта нам потребуется всего лишь три ножки, то я ввел их прямо здесь. Итак, нам потребуется опорная частота от установленного на плате 50 МГц кварца, подключенного к ПЛИС к ножке с именем C9, а так же мы будем использовать два светодиода так же уже установленных на плате. Допустим, это будут два правых светодиода, подключенных к ножкам ПЛИС под именами E12 и F12. Назовем ножку кварца clk, установим Direction: IN т.к. мы будем считывать частоту, а ножки со светодиодами – led1 и led2 со значением Direction: OUT, т.к. мы будем ими управлять.


Жмем Next, потом Finish. Видим открывшийся текстовый редактор с уже заполненной заготовкой I/O портов проекта.
entity shim_habr is
    Port ( clk : in  STD_LOGIC;
           led1 : out  STD_LOGIC;
           led2 : out  STD_LOGIC);
end shim_habr;

Как я уже говорил, можно было пропустить предыдущий шаг и ввести все это вручную. Далее нам нужно сопоставить имена портов с именами ножек ПЛИС. Тыкаем правой кнопкой на имени файла shim_habr.vhd в иерархии и выбираем пункт New Source.



В открывшемся окне выбираем Implementation Constrains File и называем этот файл pin. Жмем Next, затем Finish.



В открывшийся пустой файл пишем следующее:
NET "clk" LOC = "C9";
NET "led1" LOC = "F12";
NET "led2" LOC = "E12";

Сохраняем.
Номера ножек можно посмотреть в даташите, либо в нашем случае нам упростили поиск — номера можно посмотреть прямо на плате рядом с нужной периферией:



Часть 3. Программирование постоянной яркости светодиода.


Переключаемся в файл shim_habr.vhd и пишем код:
architecture Behavioral of shim_habr is
 
constant clk_freq  : integer := 50_000_000; -- частота кварца
constant shim_freq : integer := 10_000;     -- частота ШИМ
constant max_count : integer := clk_freq / shim_freq; -- разрядность ШИМ
 
signal count: integer range 0 to max_count := 0; -- счетчик делителя частоты
constant porog: integer := max_count / 48; -- ширина импульса логической единицы
 
begin
 
process(clk)
begin
  if rising_edge(clk) then
    if count = max_count then
      count <= 0;
    else
      count <= count + 1;
    end if;
  end if;
end process;
 
led1 <= '1when count < porog else '0';
led2 <= '1';
 
end Behavioral;

Теперь разберемся, что делает этот код. Вначале обратите внимание на строчку led2 <= '1'. Мы зажигаем второй светодиод в полную яркость, подавая туда логическую единицу, чтобы нам было с чем сравнивать яркость свечения первого светодиода. Далее смотрим на объявленные регистры и константы. Константа clk_freq хранит частоту кварца в герцах; shim_freq это частота ШИМ в герцах. Соответственно, чтобы получить нужный нам период ШИМ, необходимо поделить тактовую частоту на частоту ШИМ, и мы получим число тактов главного кварца соответствующее периоду ШИМ. По су��и это будет являться разрядностью ШИМ. Результат деления записываем в константу max_count. Далее создаем счетчик count, который будет циклично считать от 0 до max_count на частоте 50МГц. Создаем процесс process(clk). Условие if rising_edge(clk) then ждет очередного «тика» с кварца, и если он произошел выполняет прибавление на единичку счетчика count, проверяя не досчитал ли он до максимального значения. Далее, вне процесса пишем строку
led1 <= '1when count < porog else '0';

То есть на светодиоде висит логическая единица тогда, когда наш счетчик меньше какого-то порогового значения porog (у меня это 1/48 от всего периода, смотри объявление констант), остальную часть периода на светодиоде висит логический ноль. Это наглядно можно показать рисунком:


Часть 4. Прошивка.


Сохраняем все изменения, выбираем в иерархии файл shim_habr.vhd и снизу под иерархией ищем процесс Configure Target Device и запускаем его. Ждем, пока проект оттранслируется в файл прошивки, после чего откроется окно программы iMPACT, с помощью которой мы будем зашивать его в ПЛИС.
Двойной щелчек на Boundary Scan, и если у вас плата подключена к компьютеру по USB, то вы увидите примерно следующее:



Если вам не предложили выбрать файл прошивки для xc3s500e, то кликайте правой кнопкой по соответствующей микросхемке и выбирайте пункт меню Assign Configuration File. В окне выбора файла выбираем н��давно созданный shim_habr.bit. Далее опять правой кнопкой на xc3s500e, затем Program. Запустится процесс зашивки, после чего появится надпись Program Successful. Если все прошло именно так, то можно смотреть на платку =)

Часть 5. Программирование плавного изменения яркости светодиода.


Итак, видим что у нас горят два светодиода – один ярко, другой тускло. Теперь попробуем сделать плавное изменение яркости. Для этого нам нужно porog сделать не константой, а переменной, и плавно ее изменять от минимума к максимуму.
signal porog: integer range 0 to max_count := 0;

Для того, чтобы задать скорость изменения порога, нам нужно опять поделить тактовую частоту и получить более маленькую. Например, нам хочется, чтобы порог увеличивался на 1 каждую 1/600 секунды. Создаем счетчик:
constant max_count_div: integer := clk_freq / 600;
signal count_div: integer range 0 to max_count_div := 0;

и дописываем в process(clk) в момент очередного «тика» if rising_edge(clk) then еще одно условие:
    if count_div = max_count_div then
      count_div <= 0;
      -- здесь частота поделена на 600.
    else
      count_div <= count_div + 1;
    end if;

Теперь нам нужно вписать в место, где частота поделена на 600, увеличение порога на 1 и сброс в 0 если значение достигло максимального:
      if porog = max_count then
        porog <= 0;
      else
        porog <= porog + 1;
      end if;

В результате общая картина будет выглядеть следующим образом:
architecture Behavioral of shim_habr is
 
constant clk_freq  : integer := 50_000_000; -- частота кварца
constant shim_freq : integer := 10_000;     -- частота ШИМ
constant max_count : integer := clk_freq / shim_freq; -- разрядность ШИМ
 
signal count: integer range 0 to max_count := 0; -- счетчик делителя частоты
signal porog: integer range 0 to max_count := 0; -- ширина импульса логической единицы
 
constant max_count_div: integer := clk_freq / 600;
signal count_div: integer range 0 to max_count_div := 0;
 
begin
 
process(clk)
begin
  if rising_edge(clk) then
    if count = max_count then
      count <= 0;
    else
      count <= count + 1;
    end if;
 
    if count_div = max_count_div then
      count_div <= 0;
      if porog = max_count then
        porog <= 0;
      else
        porog <= porog + 1;
      end if;
    else
      count_div <= count_div + 1;
    end if;
  end if;
end process;
 
led1 <= '1when count < porog else '0';
led2 <= '1';
 
end Behavioral;

Транслируем, зашиваем, радуемся успехам. =) Вам может показаться, что светодиод большую часть времени светится ярко, а тусклый он только в начале цикла. Я уже писал об этом эффекте в своей предыдущей статье про цветомузыку. Дело в том, что яркость зависит от п��рога не линейно, а логарифмически. То есть, например, чтобы яркость изменялась плавно от минимума к максимуму с использованием ШИМ разрядностью 1024 необходимо брать последовательно следующие значения переменной porog: 0, 16, 32, 64, 128, 256, 512, 1024.

Домашнее задание.


Как видите, светодиод плавно набирает яркость, а как только набрал – сразу сбрасывает в 0 (собственно, что написали то и получили). В качестве тренировки можно попробовать сделать плавный набор яркости, а по достижении максимальной начать ее плавно уменьшать до нуля. Тем, кто с легкостью справится с этим заданием, можно попытаться сделать «бегущие огни» из восьми имеющихся светодиодов: плавно загорается и гаснет первый диод, потом второй и т.д. Если не получается, но интересно, то спрашивайте — постараюсь ответить и объяснить.

Заключение.


Итак, мы освоили применение ШИМ к светодиоду и научились регулировать яркость светодиода с помощью ПЛИС. Этим же методом можно регулировать скорость вращения двигателя, магнитную силу катушек, и т.д.
Желаю всем успехов в освоении ПЛИС!

P.S.: Прошу прощения, забыл исходник проекта для этой статьи на работе… Выложу как только заберу его от туда (дома просто ничего не установлено). Впрочем он здесь и не сильно нужен, все можно (и желательно — нужно!) сделать самому, с нуля.

UPD: Обещанные исходники.