Women's Day Gift by FPGA
В преддверии 8 марта решил сделать своей возлюбленной небольшой подарок с использованием тех инструментов, которыми чаще всего приходится пользоваться на работе. Имея немного свободного времени, я подумал, а почему бы не написать небольшую статью на хабре по этому поводу. Это отличная возможность поздравить всех дам и, в частности, немногочисленный женский состав сообщества хабрахабр. Статья написана »just for fun» и не имеет никакого научного вклада, не несёт большой смысловой нагрузки, но может быть полезна начинающим разработчикам в области ПЛИС. Я расскажу какие средства использованы для получения конечного результата и что из этого вообще получилось. В статье вы увидите мерцающие сердечки и бегущий текст на светодиодной матрице 8×8, которая управляется небольшой старенькой ПЛИС. В конце статьи вы найдете видео-демонстрацию совместной работы ПЛИС и матрицы светодиодов.
Инструмент
Проект реализован на отладочной плате с 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 приведены в таблице:
Для простеньких проектов и начального знакомства с проектированием на ПЛИС этого более, чем достаточно.
Матрица светодиодов
К ПЛИС подключена матрица светодиодов LED8×8. Очевидно, что каждый светодиод не подключается напрямую, потому что это потребует использования 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> — Управление изменением счетчика матрицы 8×8 (для вектора LEDY).
- LED_X — вектор строк светодиодной матрицы,
- LED_Y — вектор столбцов светодиодной матрицы,
Схемотехнический вид проекта после процесса синтеза представлен на рисунке ниже. Он содержит входные и выходные буферы, узел DCM, три узла устранения дребезга контактов для кнопок и три функциональных узла: управлением ШИМ, генератор символов на матрице светодиодов LED8×8 и вывод сердечка на матрицу.
После синтеза, размещения и трассировки проект в ПЛИС выглядит вот так:
Как видно, ПЛИС вообще пустая. По используемым ресурсам результаты также приведены на рисунке выше.
Результат
Итак, после того, как в ПЛИС загружена правильная прошивка и нажаты необходимые кнопки, а переключатели переведены в правильное положение, светодиодная матрица должна «ожить» и показывать следующие возможности:
Картинка: сердечко
Видео: Мерцающее и бегущее сердечко
Видео: Текст «С 8 МАРТА !»:
Проект создан на языке VHDL в САПР Xilinx ISE. Трудозатраты: 1 вечер (создание проекта) + 2 вечера (написание статьи). Исходный код выложен на github, если кому-то будет интересно сделать что-то подобное.
Еще раз хочу поздравить всех женщин с наступающим праздником. Желаю всем счастья, тепла и любви! В частности передаю привет своей любимой жене.
Спасибо за внимание!
Комментарии (1)
7 марта 2017 в 09:01
+1↑
↓
Истинной даме сердца надо делать подарок как минимум на KCU105, чтоб показать как она дорога!