Как создать эффект дышащего светодиода, используя синусоиду, хранящуюся в оперативной памяти блока
Я заметил, что многие гаджеты, которые я купил за последние пару лет, перешли от мигания светодиодов к дыханию светодиодов. Большинство электронных приспособлений содержат светодиодный индикатор состояния, поведение которого указывает на то, что происходит внутри устройства.
Моя электрическая зубная щетка мигает светодиодом, когда пришло время ее зарядить, а мой мобильный телефон использует светодиод, чтобы привлечь мое внимание по целому ряду причин. Но светодиоды не мигают, как в старые времена. Это больше похоже на аналоговый пульсирующий эффект с непрерывно меняющейся интенсивностью.
На приведенной ниже Gif-анимации моя мышь Logitech использует этот эффект, чтобы указать, что она заряжает аккумулятор. Я называю этот эффект дышащий светодиод потому что картина интенсивности света аналогична по скорости и ускорению человеческому дыхательному циклу. Это выглядит так естественно, потому что цикл освещения повторяет синусоидальный узор.
Эта статья является продолжением статьи о широтно-импульсной модуляции (ШИМ), опубликованной на прошлой неделе. Сегодня мы будем использовать модуль ШИМ, пилообразный счетчик и модуль сброса, которые мы создали в предыдущем уроке. Нажмите на ссылку ниже, чтобы прочитать статью о ШИМ!
Как создать ШИМ-контроллер в VHDL
Сохранение значений синусоиды в блочной ОЗУ
Хотя можно генерировать синусоиду с помощью примитивов цифровой обработки сигналов (DSP) в FPGA, более простым способом является сохранение точек выборки в блочной ОЗУ. Вместо того, чтобы вычислять значения во время выполнения, мы вычисляем ряд значений синуса во время синтеза и создаем постоянную память (ПЗУ) для их хранения.
Рассмотрим минимальный пример ниже, который показывает, как сохранить полную синусоиду в ПЗУ размером 3×3 бита. Как адресный ввод, так и вывод данных имеют ширину три бита, что означает, что они могут представлять целочисленное значение в диапазоне от 0 до 7. Мы можем хранить восемь значений синуса в ПЗУ, и разрешение данных также составляет от 0 до 7. .
Функция тригонометрического синуса выдает число в диапазоне [-1, 1] для любого введенного угла, x . Также цикл повторяется, когда x ≥ 2π. Поэтому достаточно хранить только значения синуса от нуля и до, но не включая 2π. Значение синуса для 2π такое же, как и для нуля. На иллюстрации выше показана эта концепция. Мы храним значения синуса от нуля до \frac{7\pi}{4}, что является последним шагом на равных интервалах перед полным кругом 2π.
Цифровая логика не может представлять реальные значения, такие как значения угла или синуса, с бесконечной точностью. Так в любой компьютерной системе. Даже при использовании чисел с плавающей запятой двойной точности это всего лишь приближение. Так работают двоичные числа, и наша синусоидальная память ничем не отличается.
Чтобы получить максимальную отдачу от доступных битов данных, мы добавляем смещение и масштаб к значениям синуса, когда мы вычисляем содержимое ПЗУ. Наименьшее возможное значение синуса -1 соответствует значению данных 0, а максимально возможное значение синуса 1 преобразуется в 2^{\mathit{data\_bits}-1}, как показано в приведенной ниже формуле.
\mathit{data} =\mathit{Round}\left(\frac{(1 + \sin \mathit{addr}) * (2^\mathit{data\_bits} - 1)}{2}\right)Чтобы преобразовать адрес ПЗУ в угол x, мы можем использовать следующую формулу:
x =\ frac {\ mathit {addr} * 2 \ pi} {2 ^ \ mathit {addr \ _bits}}Конечно, этот метод не дает универсального преобразователя значения угла в синус. Если это то, что вы хотите, вам придется использовать дополнительную логику или примитивы DSP для масштабирования ввода адреса и вывода данных. Но для многих приложений синусоидальная волна, представленная целым числом без знака, достаточно хороша. И, как мы увидим в следующем разделе, это именно то, что нам нужно для нашего примера проекта пульсирующего светодиода.
Модуль синусоидального ПЗУ
Модуль синусоидального ПЗУ, представленный в этой статье, выводит блочное ОЗУ на большинстве архитектур ПЛИС. Рассмотрите возможность сопоставления универсальных шаблонов ширины и глубины с примитивами памяти вашей целевой FPGA. Это даст вам лучшее использование ресурсов. Вы всегда можете обратиться к примеру проекта Lattice, если не знаете, как использовать синусоидальное ПЗУ для реального проекта FPGA.
Оставьте свой адрес электронной почты в форме ниже, чтобы загрузить файлы VHDL и проекты ModelSim / iCEcube2!
Объект
В приведенном ниже коде показан объект синусоидального модуля ПЗУ. Он содержит два общих входа, которые позволяют указать ширину и глубину ОЗУ предполагаемого блока. Я использую спецификаторы диапазона для констант, чтобы предотвратить непреднамеренное целочисленное переполнение. Подробнее об этом далее в этой статье.
entity sine_rom is generic ( addr_bits : integer range 1 to 30; data_bits : integer range 1 to 31 ); port ( clk : in std_logic; addr : in unsigned(addr_bits - 1 downto 0); data : out unsigned(data_bits - 1 downto 0) ); end sine_rom;
В объявлении порта есть ввод часов, но нет сброса, потому что примитивы ОЗУ не могут сбрасываться. адрес input — это место, где мы назначаем масштабированный угол (x ) и данные output — это место, где будет отображаться масштабированное значение синуса.
Объявления типов
В верхней части декларативной области файла VHDL мы объявляем тип и подтип для нашего объекта хранения ROM. диапазон_адресов подтип — это целочисленный диапазон, равный количеству слотов в нашей оперативной памяти, и rom_type описывает двумерный массив, в котором будут храниться все значения синуса.
subtype addr_range is integer range 0 to 2**addr_bits - 1; type rom_type is array (addr_range) of unsigned(data_bits - 1 downto 0);
Однако мы пока не собираемся объявлять сигнал хранения. Во-первых, нам нужно определить функцию, которая будет производить значения синуса, которые мы можем использовать для преобразования ОЗУ в ПЗУ. Мы должны объявить его над объявлением сигнала, чтобы мы могли использовать функцию для присвоения начального значения сигналу памяти ПЗУ.
Обратите внимание, что мы используем addr_bits общий в качестве основы для определения addr_range . Вот почему мне пришлось указать максимальное значение 30 для addr_bits. . Поскольку для больших значений 2**addr_bits - 1
расчет переполнится. Целые числа VHDL имеют длину 32 бита, хотя это скоро изменится с VHDL-2019, в котором использовались 64-битные целые числа. Но пока нам приходится мириться с этим ограничением при использовании целых чисел в VHDL, пока инструменты не начнут поддерживать VHDL-2019.
Функция для генерации значений синуса
Код ниже показывает init_rom функция, которая генерирует значения синуса. Он возвращает rom_type объект, поэтому мы должны сначала объявить тип, затем функцию и, наконец, константу ROM. Они зависят друг от друга именно в таком порядке.
function init_rom return rom_type is variable rom_v : rom_type; variable angle : real; variable sin_scaled : real; begin for i in addr_range loop angle := real(i) * ((2.0 * MATH_PI) / 2.0**addr_bits); sin_scaled := (1.0 + sin(angle)) * (2.0**data_bits - 1.0) / 2.0; rom_v(i) := to_unsigned(integer(round(sin_scaled)), data_bits); end loop; return rom_v; end init_rom;
Функция использует несколько удобных переменных, в том числе rom_v , локальная копия массива, который мы заполняем синусоидальными значениями. Внутри подпрограммы мы используем цикл for для перебора диапазона адресов, и для каждого слота ПЗУ мы вычисляем значение синуса, используя формулы, которые я описал ранее. И в конце мы возвращаем rom_v переменная, которая уже содержит все сэмплы синуса.
Целочисленное преобразование в последней строке цикла for — причина, по которой мне пришлось ограничить data_bits. универсальный до 31 бита. Он будет переполняться для большей длины в битах.
constant rom : rom_type := init_rom;
Под init_rom определение функции, мы переходим к объявлению rom объект как константа. ПЗУ — это просто ОЗУ, в которое вы никогда не записываете, так что это совершенно нормально. А теперь пришло время использовать нашу функцию. Мы вызываем init_rom для создания начальных значений, как показано в приведенном выше коде.
Процесс ПЗУ
Единственная логика в синус-файле ROM — это довольно простой процесс, описанный ниже. Он описывает блочное ОЗУ с одним портом чтения.
ROM_PROC : process(clk) begin if rising_edge(clk) then data <= rom(to_integer(addr)); end if; end process;
Верхний модуль
Этот дизайн является продолжением проекта PWM, который я представил в своем предыдущем сообщении в блоге. Он имеет модуль сброса, модуль генератора ШИМ и модуль автономного счетчика тактовых импульсов (пилообразный счетчик). Обратитесь к статье прошлой недели, чтобы узнать, как работают эти модули.
На приведенной ниже диаграмме показаны соединения между подмодулями в верхнем модуле.
В приведенном ниже коде показана декларативная область верхнего файла VHDL. В дизайне ШИМ прошлой недели duty_cycle объект был псевдонимом для старших битов cnt прилавок. Но сейчас это не сработает, потому что выход синусоидального ПЗУ будет управлять рабочим циклом, поэтому я заменил его реальным сигналом. Кроме того, я создал новый псевдоним с именем addr. то есть MSB счетчика. Мы будем использовать его в качестве ввода адреса для ПЗУ.
signal rst : std_logic; signal cnt : unsigned(cnt_bits - 1 downto 0); signal pwm_out : std_logic; signal duty_cycle : unsigned(pwm_bits - 1 downto 0); -- Use MSBs of counter for sine ROM address input alias addr is cnt(cnt'high downto cnt'length - pwm_bits);
Вы можете увидеть, как создать экземпляр нашего нового синусоидального ПЗУ в верхнем модуле в листинге ниже. Мы устанавливаем ширину и глубину ОЗУ в соответствии с длиной внутреннего счетчика модуля ШИМ. данные вывод из ПЗУ управляет duty_cycle вход в модуль ШИМ. Значение в duty_cycle сигнал будет изображать синусоидальную форму, когда мы будем считывать слоты ОЗУ один за другим.
SINE_ROM : entity work.sine_rom(rtl) generic map ( data_bits => pwm_bits, addr_bits => pwm_bits ) port map ( clk => clk, addr => addr, data => duty_cycle );
Моделирование синусоидального ПЗУ
На изображении ниже показана форма волны симуляции верхнего модуля в ModelSim. Я изменил представление неподписанного duty_cycle сигнал в аналоговый формат, чтобы мы могли наблюдать синусоиду.
Это светодиод_5 выход верхнего уровня, несущий ШИМ-сигнал, управляющий внешним светодиодом. Мы можем видеть, что выход быстро меняется, когда рабочий цикл увеличивается или уменьшается. Но когда рабочий цикл находится на вершине синусоиды, led_5 является устойчивой «1». Когда волна находится в нижней части склона, выходной сигнал ненадолго остается равным «0».
Хотите попробовать на домашнем компьютере? Введите свой адрес электронной почты в форму ниже, чтобы получить файлы VHDL и проекты ModelSim и iCEcube2!
Реализация дыхания светодиодов на ПЛИС
Я использовал программное обеспечение Lattice iCEcube2 для реализации проекта на плате iCEstick FPGA. Используйте форму выше, чтобы загрузить проект и попробовать его на своей доске, если у вас есть iCEstick!
В приведенном ниже списке показано использование ресурсов, согласно отчету программного обеспечения Synplify Pro, которое поставляется с iCEcube2. Это показывает, что в проекте используется примитив ОЗУ с одним блоком. Это наша синусоидальная память.
Resource Usage Report for led_breathing Mapping to part: ice40hx1ktq144 Cell usage: GND 4 uses SB_CARRY 31 uses SB_DFF 5 uses SB_DFFSR 39 uses SB_GB 1 use SB_RAM256x16 1 use VCC 4 uses SB_LUT4 65 uses I/O ports: 7 I/O primitives: 7 SB_GB_IO 1 use SB_IO 6 uses I/O Register bits: 0 Register bits not including I/Os: 44 (3%) RAM/ROM usage summary Block Rams : 1 of 16 (6%) Total load per clock: led_breathing|clk: 1 @S |Mapping Summary: Total LUTs: 65 (5%)
После маршрутизации дизайна в iCEcube2 вы найдете файл .bin файл в led_breathing_Implmnt\sbt\outputs\bitmap папка внутри Lattice_iCEcube2_proj каталог проекта.
Вы можете использовать автономное программное обеспечение Lattice Diamond Programmer для программирования FPGA, как показано в руководстве пользователя iCEstick. Это то, что я сделал, и анимация Gif ниже показывает результат. Интенсивность света светодиода колеблется с синусоидальным рисунком. Выглядит очень натурально, а светодиод как бы «дышит», если приложить немного фантазии.
Заключительные мысли
Использовать блочное ОЗУ для хранения предварительно вычисленных значений синуса довольно просто. Но есть несколько ограничений, из-за которых этот метод подходит только для синусоидальных сигналов с ограниченным разрешением по осям X и Y.
Первая причина, которая приходит на ум, — это 32-битное ограничение для целочисленных значений, о котором я говорил ранее. Я уверен, что вы можете решить эту проблему, более разумно вычислив значение синуса, но это еще не все.
Для каждого бита, который вы расширяете адрес ПЗУ, вы удваиваете использование ОЗУ. Если вам нужна высокая точность по оси X, может не хватить оперативной памяти даже на более крупной FPGA.
Если количество битов, используемых для синусоидальных значений по оси Y, превышает внутреннюю глубину ОЗУ, инструмент синтеза будет использовать дополнительные ОЗУ или LUT для реализации ПЗУ. Это съест больше вашего бюджета ресурсов по мере увеличения точности Y.
Теоретически нам нужно сохранить только один квадрант синусоиды. Следовательно, вы могли бы обойтись четвертью используемой ОЗУ, если бы использовали конечный автомат (FSM) для управления чтением ПЗУ. Это должно было бы инвертировать квадрант синуса для всех четырех перестановок осей X и Y. Затем вы можете построить полную синусоиду из одного квадранта, хранящегося в блочной ОЗУ.
К сожалению, сложно добиться плавного соединения всех четырех сегментов. Два одинаковых образца в стыках вверху и внизу синусоиды искажают данные, создавая плоские пятна на синусоиде. Введение шума противоречит цели сохранения только квадранта для повышения точности синусоиды.
VHDL
- Как создать шаблон CloudFormation с помощью AWS
- Как создать UX без трения
- Как создать список строк в VHDL
- Как создать управляемый Tcl тестовый стенд для модуля кодовой блокировки VHDL
- Как инициализировать RAM из файла с помощью TEXTIO
- Как создать самопроверяющийся тестовый стенд
- Как создать связанный список в VHDL
- Как создать массив объектов в Java
- Как медицинские работники используют цифровое производство для создания анатомических моделей нового покол…
- Как вызвать функциональный блок из клиента OPC UA с помощью информационной модели