Знакомство с FPGA iCE40 UltraPlus Mobile Development Platform от фирмы Lattice Semiconductor

Введение


Всем доброго времени суток, друзья! Недавно на работе обзавелись новенькой навороченной платой iCE40 UltraPlus Mobile Development Platform от фирмы Lattice Semiconductor. Со слов разработчиков на официальном сайте iCE40 UltraPlus MDP — это плата, на которой расположены 4 ПЛИС iCE40 UltraPlus, каждая из которых управляет своим набором периферии. В набор входят:

  • мобильный дисплей с разрешением 240x240 с интерфейсом MIPI DSI;
  • датчик изображения с разрешение 640x480 (OVM7692);
  • малопотребляющие микрофоны в количестве 4 штук;
  • BLE модуль для беспроводной передачи данных;
  • программируемая SPI Flash память;
  • пак различных датчиков (давления, компас, гироскоп и акселерометр);
  • ну и всякие кнопочки и лампочки.

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

UltraPlus MDP




В качестве тестового примера, поморгаем RGB светодиодом (D13 на схеме, выделено красным на картинке слева). Просмотрев документацию, делаем вывод, что светодиод управляется ПЛИСом под номером U3 (выделено красным на картинке справа). Из документации также узнаем, что светодиод управляется встроенным ШИМ модулятором и токовым драйвером.

Данную информацию берем на заметку.

Настройка платы и написание программы


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



Порядок действия следующий:

  1. Перевести переключатель SW5 в положение ON/OFF
  2. Две перемычки на J19 горизонтально
  3. Установить на J26 перемычки так, чтобы замкнуть пины 1-2 и 3-4 (как указано на рисунке. Начало следования пинов начинается с белой стрелочки, указанной на плате)
  4. На джамперах J17, J25, J27 установить перемычки в положение 9-10 (но это вроде необязательно)

Да, понимаю, это все нудно, но без этого не заработает.

Также, чтобы подключить генератор тактовых сигналов необходимо установить перемычку джампера J23 в положение 2-3 (нумерация идет сверху).



Теперь программа. Для создания битового файла для прошивки iCE40 UltraPlus MDP необходима среда разработки Lattice iCE cube 2 (ссылочка на страницу с продуктом) и для прошивки самой платы Programmer and Deployment Tool. Продукт лицензионный, но после регистрации лицензию можно получить здесь www.latticesemi.com/Support/Licensing/DiamondAndiCEcube2SoftwareLicensing/iceCube2

Редактор в IDE весьма неудобный, поэтому я писал в Sublime Text, но каждому свое.

Вот общая схема, которая давала понимание что и куда надо делать:



Вот и всплыли ШИМ модулятор и токовый драйвер, о которых я упоминал ранее. Эти два устройства являются внутренними модулями. Необходимо написать устройство управления логики и отправки данных, чтобы вся эта кухня правильно работала. Начнем по порядку, описываем «черный ящик»:

entity DriverRGB is
	port (
		-- RGB Led:
		LED0 	: out std_logic;
		LED1 	: out std_logic;
		LED2 	: out std_logic );
end DriverRGB;

В «черном ящике» отсутствует инициализация синхросигнала. Для этого используется внутренний модуль, который объявляется следующим образом:

-- Generator clock:
component SB_HFOSC is
	generic (
		CLKHF_DIV	: string := "0b00" );
	port (
		CLKHFPU	: in std_logic;
		CLKHFEN	: in std_logic;

		CLKHF 	: out std_logic );
end component;

imageДанный модуль может генерировать сигнал с частотами 48МГц, 24МГц, 12МГц и 6МГц. За коэффициент деление частоты отвечает параметр CLKHF_DIV («0b00», «0b01», «0b10», «0b11» соответственно). CLKHFPU и CLKHFEN разрешение работы модуля. CLKHF — тактовый сигнал.

Далее объявляем ШИМ модулятор и токовый драйвер:

-- Embedded PWM IP:
component SB_LEDDA_IP is
	port (
		LEDDCS		: in std_logic;
		LEDDCLK		: in std_logic;
		LEDDDAT7	: in std_logic;
		LEDDDAT6	: in std_logic;
		LEDDDAT5	: in std_logic;
		LEDDDAT4	: in std_logic;
		LEDDDAT3	: in std_logic;
		LEDDDAT2	: in std_logic;
		LEDDDAT1	: in std_logic;
		LEDDDAT0	: in std_logic;
		LEDDADDR3	: in std_logic;
		LEDDADDR2	: in std_logic;
		LEDDADDR1	: in std_logic;
		LEDDADDR0	: in std_logic;
		LEDDDEN 	: in std_logic;
		LEDDEXE		: in std_logic;
		LEDDRST		: in std_logic;

		PWMOUT0		: out std_logic;
		PWMOUT1		: out std_logic;
		PWMOUT2		: out std_logic;
		LEDDON		: out std_logic );
end component;

-- RGB Driver:
component SB_RGBA_DRV is
	generic (
		CURRENT_MODE	: string := "0b0";
		RGB0_CURRENT	: string := "0b000000";
		RGB1_CURRENT	: string := "0b000000";
		RGB2_CURRENT	: string := "0b000000" );
	port (
		CURREN		: in std_logic;
		RGBLEDEN	: in std_logic;
		RGB0PWM		: in std_logic;
		RGB1PWM		: in std_logic;
		RGB2PWM		: in std_logic;

		RGB0 		: out std_logic;
		RGB1 		: out std_logic;
		RGB2 		: out std_logic );
end component;

imageШИМ модулятору необходимо скормить адрес и данные, которые отвечают за режимы работы светодиода и некоторые управляющие сигналы. Выходы затем обрабатываются токовым драйвером RGB, который уже зажигает светодиод.

Токовый драйвер обрабатывает данные с ШИМ модулятора и регулирует значение тока, подаваемое к светодиоду. Параметры RGB0_CURRENT, RGB1_CURRENT, RGB2_CURRENT задают величину тока к каждому цвету. CURRENT_MODE — режим питания (полный или половинный).

image

Да, круто. Есть адреса, есть данные. Ну а что на них отправлять? В общем, разработчики Lattice в своей документации дают достаточно подробное описание, но оно весьма объемное. Постараюсь сжать все в несколько строчек описания и код для наглядности.

ШИМ модулятор ожидает 9 адресов. Каждый из них отвечает за определенную функцию для поддержания работы светодиода. Ниже приведена таблица, в которой указаны значения и названия адресов:



Для отправки данных реализуем автомат конечных состояний:

type LED_Driver is (IDLE, LEDDBR, LEDDONR, LEDDOFR, LEDDBCRR, LEDDBCFR, LEDDPWRR, LEDDPWRG, LEDDPWRB, LEDDCR0, DONE);

Первым шагом запишем данные в регистр LEDDBR. В него записывается значение для частоты тактового сигнала ШИМ. Считается она следующим образом:

Register Value N = Fsys/64kHz-1

Структура записи данных выглядит следующим образом:



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

when LEDDBR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "1001";
		DAT_Bits(7 downto 0)	<= "11101101"; -- Генерация тактового сигнала ШИМ (без двух старших бит)
		PWM_state_next		<= LEDDONR;

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

when LEDDONR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "1010";
		DAT_Bits(7 downto 0)	<= "00010001"; --  Светодиод активен (0.5 c)

Регистр LEDDOFR содержит данные, сколько времени неактивен светодиод. Точно такие же значения, как и в LEDDONR.

when LEDDOFR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "1011";
		DAT_Bits(7 downto 0)	<= "00010001"; --  Светодиод неактивен (0.5 c)
		PWM_state_next		<= LEDDBCRR;

LEDDBCRR — данные о длительности плавного включения светодиода.

when LEDDBCRR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0101";
		DAT_Bits(7)		<= '1'; -- Режим включения (вкл)
		DAT_Bits(6)		<= '1'; -- Бит регулировки включения
		DAT_Bits(5)		<= '1'; -- Изменение скорости увеличения яркости
		DAT_Bits(4)		<= '0'; -- RESERVED
		DAT_Bits(3 downto 0)	<= "0011"; -- Длительность включения (0.5 с)
		PWM_state_next		<= LEDDBCFR;

LEDDBCRR — данные о длительности плавного выключения светодиода.

when LEDDBCFR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0110";
		DAT_Bits(7)		<= '1'; -- Режим выключения (вкл) (disable/enable)
		DAT_Bits(6)		<= '0'; -- PWM Range Extend
		DAT_Bits(5)		<= '1'; -- Изменение скорости уменьшения яркости
		DAT_Bits(4)		<= '0'; -- RESERVED
		DAT_Bits(3 downto 0)	<= "0011"; -- Длительность выключения (0.5 с)
		PWM_state_next		<= LEDDPWRR;

В регистры LEDDPWRR, LEDDPWRG и LEDDPWRB записываются данные о яркости красного, синего и зеленого цвета светодиода соответственно. Значение яркости считается в процентах следующей формулой:

ADC(%) = PulseWidth/256

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

when LEDDPWRR =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0001";
		DAT_Bits(7 downto 0)	<= "00000001"; -- RED Pulse Width
		PWM_state_next		<= LEDDPWRG;

when LEDDPWRG =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0010";
		DAT_Bits(7 downto 0)	<= "11111111"; -- GREEN Pulse Width
		PWM_state_next		<= LEDDPWRB;

when LEDDPWRB =>
		led_en 			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "0011";
		DAT_Bits(7 downto 0)	<= "00011111"; -- BLUE Pulse Width
		PWM_state_next		<= LEDDCR0;

Ну и в последний регистр LEDDCR0 записывается разрешающая информация и два старших бита значения частоты тактового сигнала ШИМ:

when LEDDCR0 =>
		led_en			<= '1';
		led_cs			<= '1';
		led_exe			<= '0';
		LEDD_ADR		<= "1000";
		DAT_Bits(7)		<= '1'; -- Включение светодиода (вкл)
		DAT_Bits(6)		<= '1'; -- Flick Rate Select Bit (125/250 Hz)
		DAT_Bits(5)		<= '0'; -- Полярность выхода ШИМ (1/0)
		DAT_Bits(4) 		<= '0'; -- Режим снижения шумов при переключении ШИМ
		DAT_Bits(3)		<= '1'; -- Blinking Sequence Quick Stop Enable Bit
		DAT_Bits(2)		<= '0'; -- PWM Mode Selection Bit
		DAT_Bits(1 downto 0)	<= "10"; -- Два старших бита частоты
		PWM_state_next		<= DONE;

Примеры реализации


RGB



Purple/White



Подведение итогов


Ну вот и все. Меняя параметры можно добиваться красивого эффекта дыхания светодиода при разной цветовой гамме и яркости, изменяя значения в регистрах LEDDPWRR, LEDDPWRG, LEDDPWRB или величину тока RGB драйвера. Ниже ссылки на код на GitHub и на всю необходимую документацию.

В дальнейшем планирую тестить другие плюшки и по возможности выкладывать сюда на обозрение.

Evaluation Board User Guide
iCE40 LED Driver Usage Guide
Код

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

    0
    Спасибо. Интересная плата. И реально — не дорогая (если сравнивать вообще с этим классом EVB). Единственное, поправьте меня, вроде у Латисов нет демонстрационной IDE, да и весь софт под Win? Или уже что то изменилось за 10 лет, а с платой идет лицензия на конкретную FPGA? Уже есть, посмотрел. Есть пара задумок, пора уже свою на втором циклоне обновить…
      0

      В дальнейшем планирую порадовать отображением видео с камеры OVM7692 на дисплее.

      0
      А каким образом модуль SB_HFOSC генерирует синхросигнал? Что у него внутри?
        0
        Встроенный генератор на плате. Один сигнал подтягивает питание (CLKHFPU), а второй — разрешение на выходе генератора (CLKHFEN).
          0
          Не стало особо понятнее, как генератор на плате может быть использован внутри ПЛИС без указания в списке портов входа тактового сигнала, полез в гугл.
          Согласно документа «iCE40 Oscillator Usage Guide», в ПЛИС семейства iCE40 UltraPlus есть два внутренних (on-chip) генератора на 10кГц и 48МГц. Соответственно компонент SB_HFOSC описывает внутренний генератор на 48МГц. Сигнал CLKHFPU включает модуль, сигнал CLKHFEN — разрешение выхода.
            0
            Как я понял, этими самыми модулями можно обойти указание портов для тактового генератора. Как приятное «плюшка» от разработчиков, на мой взгляд. Модуль высокочастотного генератора с делителем и еще один низкочастотный.
        0
        Есть сомнения в лоупауэрности этой плис: выпускается с 2011 года, 40нм — старая планарная технология. За статью спасибо, но плис — древняя. Я бы такую не стал в новых разработках использовать

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

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