Программирование ПЛИС. Плавное изменение яркости светодиодов на Spartan-3E Starter Kit с использованием ШИМ (PWM)

    Эта статья ориентирована на новичков в программировании ПЛИС на языке 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: Обещанные исходники.
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 28
      0
      Собственно делал аналогичное на ардуино с одним диодом. Вопрос: как с малым количеством выводов организовать тоже самое для большого количества диодов? Т.е. я хочу сделать допустить полоску из 100 диодов, по которой последовательно возрастается яркость слева направо.
        0
        упс, новый комент создал вместо ответа…
          +1
          Для управления светодиодными линейками обычно применяют сдвиговые регистры. Регистры каскадируются, так что на любое число диодов нужно всего 2 вывода: CLK и DATA. Если требуется ШИМ-управление, придется с большой частотой обновлять содержимое регистров.
          Давайте посчитаем: пусть тактовая частота регистра 10 МГц, за сто тактов обновляется состояние всех диодов. Таким образом, частота тактирования счетчика будет 100 кГц. Для 8-разрядной ШИМ (256 градаций яркости) несущая частота (частота мерцания) будет 100кГц/256=390Гц. Более-менее приемлемо.
          Можно понизить разрядность ШИМ, несущая частота возрастет.
          Можно разбить 100 диодов на 10 групп по 10, каждую повесить на свой регистр, и управлять группами параллельно. Потребуется уже 11 выводов (1 CLK + 10 DATA), но время обновления снизится тоже в 10 раз.
            +1
            Да, подразумевается, что у вас хватает вычислительной мощности, чтобы рассчитывать состояние 100 ШИМ-каналов с требуемой частотой. Для ПЛИС это элементарно, а вот насчет AVR у меня сомнения… Допустим, мы используем аппаратный счетчик (один на все каналы). Сравнить текущее значение с порогом, вывести 1 или 0 на порт, загрузить следующий порог — тактов десять на каждый канал получается, даже если писать на чистом асме. При Fclk=20МГц получится гнать данные в сдвиговые регистры на частоте 2МГц максимум.
            И это если контроллер не занят больше ничем, кроме обработки ШИМ.
        +1
        Встречный вопрос — а на сколько мало количество выводов? Для 100 диодов можно сделать матрицу 10х10, потребуется всего 20 выводов, но логика описания ШИМ будет посложнее…
          0
          6 аналоговых выходов 8битных там :)
            0
            В смысле не аналоговых, а ШИМ
              0
              если физически 20 ножек есть то 100 диодов уже можно сделать =)
          0
          ммм… то есть физически 6 лап? и на каждую можно байт слать?
            0
            блин, опять я мимо… надо спать идти.
              0
              ну как бы там 14 выходов, но только 6 поддерживают ШИМ, остальные просто «да/нет».
                0
                чтобы сделать ШИМ на любом дискретном выходе потребуется чуть меньше кода чем в программе выше.
            +1
            Спасибо, интересная статья. Если можно, задам пару дилетантских вопросов.

            Краем уха слышал, что в библиотеках есть реализация микропроцессора. К тому же, на одной из Ваших картинок в меню показан пункт: «Embedded processor» Вот на эту тему и вопросы.

            Это действительно то, о чём я думаю — реализация микропроцессора? Если да, то каковы характеристики этого процессора — разрядность и система команд. Есть ли возможность модифицировать систему команд процессора, например, добавить несколько команд? Поддерживает ли этот процессор страничную адресацию памяти и если да, то каков размер страницы? Все ли модели ПЛИС обладают достаточным количеством элементов для реализации микропроцессора? Если на основе ПЛИС сделать микропроцессор, то возможно ли и какова последовательность действий для переноса процессора в кремний?
              +2
              Тут на все вопросы ответы — почти всегда да. Только в самые крошечные ПЛИСки не лезут мелкие процессоры.

              Перенос в кремний — берется рабочая в ПЛИС схема на Verilog-е, и синтезируется в кремний, далее допиливается руками. Софт для синтеза страшно дорог.

              Другой вариант — есть компании которые один в один ПЛИС в кремний переносят — получается чуть быстрее и чуть дешевле при больших объемах. Тогда ничего разрабатывать не нужно.
                +2
                На ПЛИС можно реализовать почти любой процессор. Хватило бы логических элементов.
                Разнообразию и фантазии процессоростроителей иногда можно только позавидовать. В качестве примера приведу сайт http://opencores.org/projects. Готовые к употреблению процессоры находятся во вкладках «Processor», «SoC» (расшифровывается как System-on-a-Chip).

                Рускоговорящие ПЛИСоводы тусуются здесь http://electronix.ru/forum/index.php?showforum=75.

                Упоминаемый Embedded processor — это скорей всего MicroBlaze.

                Сам плисами не занимаюсь, но читая форумы пришел к выводу, что в конечном итоге каждый разработчик на ПЛИС пишет собственный процессор (или начинает использовать чужой), дабы потом упростить себе процесс программирования ПЛИС для реальных задач.
                  0
                  И очень зря так делают.
                  ПЛИС стоят гораздо дороже микроконтроллеров (которые они могут эмулировать) и уж тем более DSP. Чтобы это прочувствовать — нужно поработать и с теми, и с другими и понять, что на ПЛИС не добиться эмулятором такого же быстродействия за схожие деньги.

                  Зато бывают ПЛИС со встроенными DSP или просто контроллерами, которые можно использовать под свои нужды.
                  +2
                  Например, на Марсоходе умельцы сварганили на своей плиске свой проц с системой команд AVR (по проще конечно), причем прошивку для него писали в AVR Studio, а потом заливали в плиску. Поищите там, интересный опыт.

                  marsohod.org вроде бы
                    0
                    есть на opencores.org почти полная реализация AVR с 5 stage pipeline.
                  +1
                  За такое объяснение на лабораторной работе в моем универе отправляли на пересдачу.

                  VHDL — это не я зык программирования. VHDL — это язык описания.

                  По поводу регулировки яркости.
                  Яркость светодиода можно регулировать подачей на него разных значений постоянного напряжения

                  Это не правда. Светодиод — полупроводник. Либо p-n переход открыт, либо закрыт. И не бывает иначе.

                  Если сказать точнее, то просто у светодиода есть переходный процесс при зажигании, то есть загорается он не мгновенно

                  Вообще-то светодиод как раз загорается мгновенно т.к. имеет весьма малую инертность.
                  В статье использована частота ШИМ 10 000 Гц и тут есть вопрос: зачем?.. почему не достаточно сотни герц, которые человеческий глаз уже не различит?
                  При 150 Гц смотреть на диод уже неприятно для глаза. При 200 лично мне смотреть было просто противно.

                  Если затея с 10 кГц была в том, чтобы «ловить переходный процесс при зажигании», то прошу дать чуть более подробные объяснения своих действий. А то как-то получается «смотрите, я легко словил переходный процесс на совершенно не известном мне светодиоде».
                  Гугл говорит, что постоянная времени светодиодов — порядка 50-100 МГц, что гораздо меньше 10 кГц в статье, а значит «словить» переходный процесс на 10 кГц просто не реально.
                    0
                    Переходные процессы тут ни при чем, ШИМ будет работать и с идеальным, мгновенно зажигающимся светодиодом. Дело в инерции человеческого зрения. Глаз воспринимает яркость объекта как интеграл от количества света за некоторый промежуток времени (порядка 10-100 мс). Частоту мерцания выше сотни герц человек не различает, но если 1/4 периода светодиод горит, а 3/4 не горит, это будет восприниматься как свечение с 25% яркостью.
                      +1
                      не 10-100 мс, а 0-30 мс. Большие времена глаз будет уже различать. И то до 100 Гц глаз еще замечает колебания светимости (вспоминаем ЭЛТшные мониторы и телевизоры). Хотя это, конечно, зависит от самого источника света. Люминофор светится гораздо дольше светодиода, но…

                      0
                      Это не правда. Светодиод — полупроводник. Либо p-n переход открыт, либо закрыт. И не бывает иначе.

                      Вы не правы. Попробуйте собрать простейшую схему соединив последовательно светодиод, батарейку и переменный резистор на килоомы.
                        0
                        Согласен. Светимость светодиода можно регулировать не только ШИМом. Но и не столько самим напряжением, сколько током. Собственно ВАХ прямого включения хорошо показывает быстрый рост тока. Те светодиоды, яркость которых регулируется подаваемым напряжением — имеют встроенный в корпус источник тока с фиксированным выходным напряжением и выходным током, зависящим от входного напряжения. Тогда все будет работать так, как пишет автор статьи.
                        Но он описывает просто светодиод безничего. Если бы даже была такая схема, в которой стоял бы переменный резистор, в каких пределах нужно было бы изменять его сопротивление для заметного изменения яркости светодиода?

                        Кстати, по поводу быстродействия. Еще в уривере подключали с другом лазерную указку к PICу, моргали ей с частатой около 7 кГц на советских времен фотодиод. Тот подключали к осциллу. Видели весьма четкие фронты.
                          0
                          Те светодиоды, яркость которых регулируется подаваемым напряжением — имеют встроенный в корпус источник тока.
                          Уже и такие есть? Покажите хотя бы один, пожалуйста.
                            0
                            я имел в виду корпус, как нечто, вклчюающее и светодиод, и источник. Возможно не очень удачно написал. У меня такой дома уже лет 5 висит. От 12V светит так, что ажно слепит. От 5V освещает прихожую ночью, чтоб не убиться обо что-нибудь :)
                            Но он у меня простейший. Резак последовательно со светодиодом.
                              0
                              я, кстати, удивлен, что он так может работать. Видимо хватает ему тока для свечения…
                                +1
                                А что тут удивляться… Все же считается. Возьмите напряжение питания, вычтите из него прямое падение на диоде (практически константа для кристалла, около 4.5В для синих, 2.5В для красных) и разделите на сопротивление резистора.
                                Полученный ток сравните с номинальным (20мА для обычных 5мм bright LEDs).

                                Тем же макаром и переменный можно посчитать.

                                А вообще — есть же драйверы. Там вам и управление PWM/SPI на выбор и ток гарантированный.

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

                      Самое читаемое