Women's Day Gift by FPGA

    Всем привет! В преддверии 8 марта решил сделать своей возлюбленной небольшой подарок с использованием тех инструментов, которыми чаще всего приходится пользоваться на работе. Имея немного свободного времени, я подумал, а почему бы не написать небольшую статью на хабре по этому поводу. Это отличная возможность поздравить всех дам и, в частности, немногочисленный женский состав сообщества хабрахабр. Статья написана "just for fun" и не имеет никакого научного вклада, не несёт большой смысловой нагрузки, но может быть полезна начинающим разработчикам в области ПЛИС. Я расскажу какие средства использованы для получения конечного результата и что из этого вообще получилось. В статье вы увидите мерцающие сердечки и бегущий текст на светодиодной матрице 8x8, которая управляется небольшой старенькой ПЛИС. В конце статьи вы найдете видео-демонстрацию совместной работы ПЛИС и матрицы светодиодов.



    Инструмент


    Проект реализован на отладочной плате с FPGA Spartan3E. Она содержит простейшую обвязку разными интерфейсами (VGA, PS/2), на ней есть светодиоды и LED-дисплей, а также триггеры-переключатели, кнопки и разные другие выводы для подключения чего-то стороннего. Плата очень дешевая, заказана с ebay на одном из китайских магазинов. Цена — $135,00 с учетом доставки (~4000р по старым ценам).

    Официальный сайт производителя в настоящее время мертв, что неудивительно.

    Основные особенности платы:

    • FPGA Spartan3E (XC3S500E-4PQ208C) — 500К логических вентилей,
    • Источник тактовой частоты CLK = 50 MHz,
    • Внешняя память 64M SDRAM,
    • SPI Flash (M25P80) для хранения прошивки ПЛИС,
    • Матрица светодиодов LED 8х8, линейка светодиодов 8 шт.,
    • 8 триггерных переключателей и 5 кнопок,
    • Разъемы для подключения двух LED-дисплеев,
    • Разъем VGA для подключения монитора,
    • Разъемы PS/2, и т.д.

    Ресурсы кристалла ПЛИС Spartan3E XC3S500E приведены в таблице:



    Для простеньких проектов и начального знакомства с проектированием на ПЛИС этого более, чем достаточно.

    Матрица светодиодов


    К ПЛИС подключена матрица светодиодов LED8x8. Очевидно, что каждый светодиод не подключается напрямую, потому что это потребует использования 64 контактов ПЛИС, что очень дорого и неразумно! Ниже приведена схема из китайского даташита на отладочную плату. Видно, что светодиоды подключены столбцами и строками по 8 штук, причем подключение идет не напрямую, а через PNP-транзисторы. Эмиттер транзистора подключен к напряжению питания, база к контактам ПЛИС, а коллектор к матрице светодиодов. Всего к ПЛИС подключено 16 контактов. Логика управления LED-матрицей — отрицательная, то есть светодиоды зажигаются низким логическим уровнем (лог. 0).



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

    Примеры:

    А) если вектор LEDY = «11111110», а вектор LEDX = «11111110», то загорится единственный светодиод в нижнем правом углу.

    11111111
    11111111
    11111111
    11111111
    11111111
    11111111
    11111111
    11111110


    Б) если вектор LEDY = «11111110», а вектор LEDX = «00000000», то загорится вся нижняя строка.

    11111111
    11111111
    11111111
    11111111
    11111111
    11111111
    11111111
    00000000


    В) если вектор LEDY = «00000000», а вектор LEDX = «11111110», то загорится весь правый столбец.

    11111110
    11111110
    11111110
    11111110
    11111110
    11111110
    11111110
    11111110


    Г) если вектор LEDY = «00000000», а вектор LEDX = «00000000», то загорится вся матрица светодиодов.

    В связи с этим возникает определенная трудность. Как зажечь матрицу светодиодов таким образом, чтобы на неё был выведен определенный символ в виде буквы или цифры? На самом деле это не сложно.

    Вывод символов на светодиоды


    Процесс вывода символов на матрицу светодиодов достаточно прост. Во-первых, необходимо организовать счетчик определенной разрядности и три его старших разряда использовать для переключения вектора LEDY. Почему три разряда? Переключения 8-битного вектора можно описать с помощью конечного автомата с использованием 3 управляющих битов. На языке VHDL это делается так:

    Счетчик

    pr_cnt: process(clk, rst) is
    begin 
    	if (rst = '0') then
    		cnt_led <= (others => '0');
    	elsif rising_edge(clk) then
    		if (rst_reg = '0') then
    			cnt_led <= (others => '0');
    		else
    			cnt_led <= cnt_led + '1';
    		end if;
    	end if;
    end process;

    Первый ресет (rst) — глобальный. Второй сброс (rst_reg) — программный и управляется триггером.

    Конечный автомат для LEDY

    pr_3x8: process(cnt_cmd) is
    begin
    	case cnt_cmd is
    		when	"000"	=> en_xhdl <= "11111110";
    		when	"001"	=> en_xhdl <= "11111101";
    		when	"010"	=> en_xhdl <= "11111011";
    		when	"011"	=> en_xhdl <= "11110111";
    		when	"100"	=> en_xhdl <= "11101111";
    		when	"101"	=> en_xhdl <= "11011111";
    		when	"110"	=> en_xhdl <= "10111111";
    		when	others	=> en_xhdl <= "01111111";
    	end case;
    end process;

    Изменение состояния счетчика включает один бит в столбце светодиодов LEDY. Таким образом, организуется схема "бегущего нуля".

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

    Промежуточный КА

    pr_8x4: process(en_xhdl) is
    begin
    	case en_xhdl is
    		when "11111110"	=> led_cmd <= "000";
    		when "11111101"	=> led_cmd <= "001";
    		when "11111011"	=> led_cmd <= "010";
    		when "11110111"	=> led_cmd <= "011";
    		when "11101111"	=> led_cmd <= "100";
    		when "11011111"	=> led_cmd <= "101";
    		when "10111111"	=> led_cmd <= "110";
    		when others	=> led_cmd <= "111";
    	end case;
    end process;

    КА для вектора LEDX

    pr_ledx: process(led_cmd) is
    begin
    	case led_cmd is		
    		when "000" => ledx <= "01111110";
    		when "001" => ledx <= "10111101";
    		when "010" => ledx <= "11011011";
    		when "011" => ledx <= "11100111";
    		when "100" => ledx <= "11100111";
    		when "101" => ledx <= "11011011";
    		when "110" => ledx <= "10111101";
    		when others =>ledx <= "01111110";
    	end case;
    end process;

    В результате этих действий формируется вектор LEDX. Именно этот вектор вырисовывает символы на светодиодной матрице, а вектор LEDY отвечает за своевременное выключение ненужных светодиодов. В данном примере на светодиодной матрице должен загореться крест или символ «Х», что можно визуально проследить, если внимательно посмотреть на последний конечный автомат. Таким образом, вектор столбцов становится вспомогательным, а вектор строк отрисовывает символы и с его помощью можно «написать» на светодиодной матрице что угодно.

    В проекте сделано два однотипных файла генерации символов на матрицу светодиодов. В первом блоке выводится сердце, а во втором с помощью конструкции ROM-памяти выводится текст «С 8 МАРТА!». Символы текста лежат в памяти и к ним необходимо правильно адресоваться. Поскольку процесс вывода на дисплей достаточно быстрый, то необходимо настроить частоту вывода таким образом, чтобы символы можно было прочитать. Для этого требуется создать дополнительный счетчик на 27 разрядов, который поделит входную частоту 50МГц на 2^27 и позволит нашему глазу наблюдать смену символов на матрице светодиодов.

    Коды символов записаны в памяти в виде двумерного массива. 8-разрядные числа меняются в зависимости от логики конечного автомата (для вывода символа на светодиоды), а также в зависимости от адреса (для того, чтобы изменить символ на матрице светодиодов):

    type rom_type is array (7 downto 0) of std_logic_vector(7 downto 0);
    type rom_8x8 is array (0 to 7) of rom_type;
    
    constant ROM_TEXT: rom_8x8:=( 
    (  
    "11000011",
    "10011001",
    "00111101",
    "00111111",
    "00111111",
    "00111101",
    "10011001",
    "11000011")
    ... /cut
    
    -- вывод данных на матрицу из 2D-массива:
    pr_8x8: process(clk, rst) is
    begin 
        if (rst = '0') then
            data_led <= (others => '0');
        elsif rising_edge(clk) then
            data_led <= ROM_TEXT(CONV_INTEGER(addr_txt(Na-1 downto Na-3)))(CONV_INTEGER(led_cmd));
        end if;
    end process;
    

    Спецэффекты


    Согласитесь, что если на матрице просто зажечь символ, то это несколько скучно? В проект необходимо добавить спецэффекты в виде мигания и бегущих огоньков. Сделать это очень просто. Для этого потребуется всем известный алгоритм ШИМ (широтно-импульсная модуляция). С её помощью можно управлять мощностью, которая подводится к нагрузке (то есть регулировать ток). Это делается путем вариации скважности импульсов при неизменной частоте сигнала.

    Я не буду рассказывать, как делается ШИМ на ПЛИС, об этом написано множество статей. Основной принцип — на нескольких управляемых счетчиках организуется изменение длительности импульсов нулей и единиц. В моем проекте можно управлять как самим ШИМ (изменяя скорость счетчиков путём увеличения счёта), так и менять скорость вывода данных на светодиодную матрицу, меняя разрядность счетчика для вектора столбцов LEDY (путем выбора требуемых битовых полей). В совокупности это позволяет получить различные эффекты мерцания, затухания и бегущих огней, что можно посмотреть на финальном видео.

    Проект на ПЛИС


    Проект на ПЛИС состоит из нескольких частей: это файлы исходного кода на языке VHDL, описывающие алгоритм работы микросхемы, и файл ограничений UCF, задающий подключение контактов ПЛИС, их стандарт, выходной ток, скорость, ограничения на тактовые частоты в проекте и т.д.

    Распиновка контактов в UCF

    ## BUTTONS = ## K1-K5
    NET "KB[5]" LOC = P54;
    NET "KB[4]" LOC = P58;
    NET "KB[3]" LOC = P57;
    NET "KB[2]" LOC = P159;
    NET "KB[1]" LOC = P154;
    
    ## LEDS = ## LED_MATRIX
    NET "LED_X<7>" LOC = "P49" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_X<6>" LOC = "P48" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_X<5>" LOC = "P40" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_X<4>" LOC = "P50" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_X<3>" LOC = "P62" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_X<2>" LOC = "P98" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_X<1>" LOC = "P64" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_X<0>" LOC = "P63" | IOSTANDARD = LVTTL | DRIVE = 8;
                                                      
    NET "LED_Y<7>" LOC = "P75" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_Y<6>" LOC = "P78" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_Y<5>" LOC = "P76" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_Y<4>" LOC = "P69" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_Y<3>" LOC = "P74" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_Y<2>" LOC = "P65" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_Y<1>" LOC = "P68" | IOSTANDARD = LVTTL | DRIVE = 8;
    NET "LED_Y<0>" LOC = "P77" | IOSTANDARD = LVTTL | DRIVE = 8;
    
    NET "RESET" LOC = P148 | IOSTANDARD = LVTTL;
    NET "CLK" LOC = P183 | IOSTANDARD = LVCMOS33;
    
    NET "CLK_IN" TNM_NET = "CLK_TN";
    TIMESPEC TS_CLK = PERIOD "CLK_TN" 20 ns HIGH 50 %;
    

    Файл верхнего уровня VHDL

    entity top_xc3s500e_heart is
    	port(
    		---- SWITCHES ----
    		RESET	:  in  std_logic;  --! asycnchronous reset: SW(0)
    		SW	:  in  std_logic_vector(7 downto 1); --! other switches
    		---- CLOCK 50 MHz ----
    		CLK	:  in  std_logic; --! main clock 50 MHz
    		---- LED DISPLAY ----
    		LED_X	:  out std_logic_vector(7 downto 0); --! LEDs Y
    		LED_Y	:  out std_logic_vector(7 downto 0); --! LEDs X	
    		---- BUTTONS ----
    		KB	:  in  std_logic_vector(5 downto 1); --! Five Buttons
    	);
    end top_xc3s500e_heart;

    Описание портов:

    • RESET — глобальный сброс (переключатель SW[0]),
    • CLK — тактовая частота 50МГц,
    • SW<1> — разрешение работы ШИМ,
    • SW<2> — режим управления «сердцем»,
    • SW<3> — сброс матрицы 8х8,
    • SW<4> — переключение блоков HEART / TEXT,
    • KB<1> — Управление скоростью главного счетчика ШИМ (общий период),
    • KB<2> — Управление скоростью вторичного счетчика ШИМ (скважность импульсов),
    • KB<3> — Управление изменением счетчика матрицы 8x8 (для вектора LEDY).
    • LED_X — вектор строк светодиодной матрицы,
    • LED_Y — вектор столбцов светодиодной матрицы,

    Схемотехнический вид проекта после процесса синтеза представлен на рисунке ниже. Он содержит входные и выходные буферы, узел DCM, три узла устранения дребезга контактов для кнопок и три функциональных узла: управлением ШИМ, генератор символов на матрице светодиодов LED8x8 и вывод сердечка на матрицу.



    После синтеза, размещения и трассировки проект в ПЛИС выглядит вот так:



    Как видно, ПЛИС вообще пустая. По используемым ресурсам результаты также приведены на рисунке выше.

    Результат


    Итак, после того, как в ПЛИС загружена правильная прошивка и нажаты необходимые кнопки, а переключатели переведены в правильное положение, светодиодная матрица должна «ожить» и показывать следующие возможности:

    Картинка: сердечко



    Видео: Мерцающее и бегущее сердечко


    Видео: Текст «С 8 МАРТА !»:


    Проект создан на языке VHDL в САПР Xilinx ISE. Трудозатраты: 1 вечер (создание проекта) + 2 вечера (написание статьи). Исходный код выложен на github, если кому-то будет интересно сделать что-то подобное.

    Еще раз хочу поздравить всех женщин с наступающим праздником. Желаю всем счастья, тепла и любви! В частности передаю привет своей любимой жене.

    Спасибо за внимание!
    • +23
    • 6,4k
    • 9
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 9

      +2
      Истинной даме сердца надо делать подарок как минимум на KCU105, чтоб показать как она дорога!
        0
        Это уже расточительство семейного бюджета, супруга такое не одобрит :)
        0
        Надо было заодно и на семисегментники бегущую строку вывести, а то простаивает неиспользованная :)
          0
          Я думал про это. :)
          Но они, к сожалению, завязаны с LED-матрицей. Джампером переключается вывод либо на светодиоды, либо на индикаторы.
          +1
          С каждой подобной статьей всегда остается один вопрос: где вы таких жен находите?
            +1
            Можно по другому: «Как вас жены еще терпят?» ;)
            0
            У меня вопрос. Как 500 лог. вентилей Зайлинкса перевести в логические элементы, той же Альтеры?
            Хочу попробовать MAX10, у терасика появились платы на нём.

            Статья понравилась.
              0
              Переводить нужно не system gates, а logic cells. Вентили — это все, что содержится в SLICE — мультиплексоры цепочки CARRY, LUT-ы, триггеры и т.д. Чтобы сравнить Xilinx и Altera, в даташите первого смотрим на Logic Cells, а для второго Logic Elements (LE). Для микросхемы XC3S500E Logic Cells — 10,476. Но также следует учитывать технологию, по которой эти логические элементы сделаны. Начинка несколько отличается.
              0
              А кто тебя с дамой познакомил?) в со авторы меня!

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

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