RC-сервоконтроллер, использующий ШИМ от вывода FPGA
Сервоприводы моделей с радиоуправлением (RC) представляют собой крошечные приводы, обычно используемые в моделях самолетов, автомобилей и лодок для любителей. Они позволяют оператору дистанционно управлять транспортным средством по радиоканалу. Поскольку модели RC существуют уже давно, де-факто стандартным интерфейсом является широтно-импульсная модуляция (ШИМ), а не цифровая схема.
К счастью, легко реализовать ШИМ с точной синхронизацией, которую ПЛИС может воздействовать на свои выходные контакты. В этой статье мы создадим универсальный сервоконтроллер, который будет работать с любым радиоуправляемым сервоприводом, использующим ШИМ.
Как работает ШИМ-управление сервоприводом RC
Я уже рассказывал о ШИМ в предыдущем посте в блоге, но мы не можем использовать этот модуль для управления сервоприводом с дистанционным управлением. Проблема в том, что RC-сервопривод не ожидает, что импульсы ШИМ будут поступать так часто. Ему не важен полный рабочий цикл, а только продолжительность периода высокой нагрузки.
На приведенном выше рисунке показано, как работает синхронизация сигнала ШИМ.
Идеальный интервал между импульсами составляет 20 мс, хотя его продолжительность не так важна. 20 мс переводятся в частоту ШИМ 50 Гц. Это означает, что сервопривод получает новую команду положения каждые 20 мс.
Когда импульс поступает на сервопривод RC, он измеряет продолжительность периода высокого уровня. Время имеет решающее значение, потому что этот интервал напрямую преобразуется в угловое положение сервопривода. Большинство сервоприводов ожидают, что ширина импульса будет варьироваться от 1 до 2 мс, но нет установленного правила.
Сервоконтроллер VHDL
Мы создадим универсальный модуль сервоконтроллера VHDL, который можно настроить для работы с любым радиоуправляемым сервоприводом с использованием ШИМ. Для этого нам нужно выполнить некоторые вычисления на основе значений общих входных данных.
Частоты ШИМ, используемые сервоприводами RC, медленнее по сравнению с частотами переключения в мегагерцах FPGA. Целочисленный подсчет тактов дает достаточную точность длины импульса ШИМ. Однако будет небольшая ошибка округления, если тактовая частота не будет точно соответствовать периоду импульса.
Мы будем выполнять расчеты, используя real (с плавающей запятой), но в конечном итоге нам нужно преобразовать результаты в целые числа. В отличие от большинства языков программирования, округление VHDL округляется до ближайшего целого числа, но поведение половинных чисел (0,5, 1,5 и т. д.) не определено. Симулятор или инструмент синтеза могут выбрать округление в любую сторону.
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.round;
Чтобы обеспечить единообразие на разных платформах, мы будем использовать раунд. функция из math_real библиотеку, которая всегда округляется от 0. В приведенном выше коде показан импорт в нашем модуле VHDL с math_real библиотека выделена.
Если вам нужен полный код для этого проекта, вы можете загрузить его, введя свой адрес электронной почты в форму ниже. В течение нескольких минут вы получите Zip-файл с кодом VHDL, проектом ModelSim и проектом Lattice iCEcube2 для платы iCEstick FPGA.
Сущность сервомодуля с универсальными элементами
Используя общие константы, мы можем создать модуль, который будет работать с любым сервоприводом RC с поддержкой ШИМ. В приведенном ниже коде показана сущность сервомодуля.
Первая константа — это тактовая частота ПЛИС, заданная как реальный тип, а pulse_hz определяет, как часто выход PWM должен быть импульсным, а следующие две константы устанавливают ширину импульса в микросекундах в минимальном и максимальном положениях. Последняя общая константа определяет количество шагов между минимальной и максимальной позицией, включая конечные точки.
entity servo is generic ( clk_hz : real; pulse_hz : real; -- PWM pulse frequency min_pulse_us : real; -- uS pulse width at min position max_pulse_us : real; -- uS pulse width at max position step_count : positive -- Number of steps from min to max ); port ( clk : in std_logic; rst : in std_logic; position : in integer range 0 to step_count - 1; pwm : out std_logic ); end servo;
В дополнение к часам и сбросу объявление порта состоит из одного входного и одного выходного сигнала.
позиция сигнал является управляющим входом для сервомодуля. Если мы установим его равным нулю, модуль выдаст min_pulse_us микросекундные импульсы ШИМ. Когда позиция находится на самом высоком уровне, он выдаст max_pulse_us длинные импульсы.
ШИМ output — это интерфейс для внешнего сервопривода RC. Он должен проходить через контакт FPGA и подключаться к входу «Сигнал» на сервоприводе, обычно это желтый или белый провод. Обратите внимание, что вам, вероятно, потребуется использовать преобразователь уровней. Большинство FPGA используют логический уровень 3,3 В, в то время как большинство сервоприводов RC работают от 5 В.
Декларативная область
В верхней части декларативной области сервомодуля я объявляю функцию, которую мы будем использовать для вычисления нескольких констант. циклы_на_нас Функция, показанная ниже, возвращает ближайшее количество тактов, которое нам нужно подсчитать, чтобы измерить us_count микросекунд.
function cycles_per_us (us_count : real) return integer is begin return integer(round(clk_hz / 1.0e6 * us_count)); end function;
Непосредственно под функцией мы объявляем вспомогательные константы, которые будем использовать для синхронизации выходного ШИМ в соответствии с дженериками.
Во-первых, мы переводим минимальное и максимальное значения в микросекундах в абсолютное количество тактовых циклов:min_count и max_count . Затем мы вычисляем интервал между ними в микросекундах, из которого получаем step_us. , разница продолжительности между каждым шагом линейного положения. Наконец, мы конвертируем микросекунды в реальные значение на фиксированное количество тактов:cycles_per_step .
constant min_count : integer := cycles_per_us(min_pulse_us); constant max_count : integer := cycles_per_us(max_pulse_us); constant min_max_range_us : real := max_pulse_us - min_pulse_us; constant step_us : real := min_max_range_us / real(step_count - 1); constant cycles_per_step : positive := cycles_per_us(step_us);
Далее мы объявляем счетчик PWM. Этот целочисленный сигнал представляет собой автономный счетчик, охватывающий pulse_hz. раз каждую секунду. Вот как мы достигаем частоты ШИМ, указанной в дженериках. В приведенном ниже коде показано, как мы вычисляем количество тактовых циклов, которое нам нужно считать, и как мы используем константу для объявления диапазона целого числа.
constant counter_max : integer := integer(round(clk_hz / pulse_hz)) - 1; signal counter : integer range 0 to counter_max; signal duty_cycle : integer range 0 to max_count;
Наконец, мы объявляем копию счетчика с именем duty_cycle. . Этот сигнал определяет продолжительность периода высокого уровня на выходе ШИМ.
Подсчет тактов
В приведенном ниже коде показан процесс, реализующий автономный счетчик.
COUNTER_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then counter <= 0; else if counter < counter_max then counter <= counter + 1; else counter <= 0; end if; end if; end if; end process;
В отличие от подписанного и без подписи типы, которые являются самооборачивающимися, нам нужно явно присвоить ноль, когда счетчик достигает максимального значения. Поскольку у нас уже есть максимальное значение, определенное в counter_max константа, этого легко добиться с помощью конструкции If-Else.
Процесс вывода ШИМ
Чтобы определить, должно ли выходное значение ШИМ иметь высокое или низкое значение, мы сравниваем счетчик и рабочий_цикл сигналы. Если значение счетчика меньше рабочего цикла, на выходе высокое значение. Таким образом, значение duty_cycle сигнал управляет длительностью импульса ШИМ.
PWM_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pwm <= '0'; else pwm <= '0'; if counter < duty_cycle then pwm <= '1'; end if; end if; end if; end process;
Расчет рабочего цикла
Рабочий цикл никогда не должен быть меньше min_count. тактов, потому что это значение соответствует min_pulse_us общий ввод. Поэтому мы используем min_count в качестве значения сброса для duty_cycle сигнал, как показано ниже.
DUTY_CYCLE_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then duty_cycle <= min_count; else duty_cycle <= position * cycles_per_step + min_count; end if; end if; end process;
Когда модуль не находится в состоянии сброса, мы рассчитываем рабочий цикл как функцию входного положения. циклов_за_шаг константа является приближением, округленным до ближайшего целого числа. Поэтому погрешность по этой константе может достигать 0,5. Когда мы умножаем на заданную позицию, ошибка увеличивается. Однако, поскольку тактовая частота FPGA намного выше, чем частота ШИМ, это не будет заметно.
Тестовый стенд для сервоприводов
Чтобы протестировать модуль сервопривода RC, я создал испытательный стенд для ручной проверки, который позволит нам наблюдать за поведением модуля сервопривода на осциллограмме. Если на вашем компьютере установлен ModelSim, вы можете загрузить пример проекта моделирования, введя свой адрес электронной почты в форму ниже.
Константы моделирования
Чтобы ускорить время моделирования, мы укажем в тестовом стенде низкую тактовую частоту 1 МГц. Я также установил количество шагов только 5, чего должно быть достаточно, чтобы мы могли увидеть тестируемое устройство (DUT) в действии.
В приведенном ниже коде показаны все константы моделирования, определенные в тестовом стенде.
constant clk_hz : real := 1.0e6; constant clk_period : time := 1 sec / clk_hz; constant pulse_hz : real := 50.0; constant pulse_period : time := 1 sec / pulse_hz; constant min_pulse_us : real := 1000.0; constant max_pulse_us : real := 2000.0; constant step_count : positive := 5;
Сигналы ИУ
Сигналы, заявленные в испытательном стенде, соответствуют входам и выходам ИУ. Как видно из приведенного ниже кода, я указал clk и первое сигнализирует начальное значение «1». Это означает, что часы начнутся с высокого положения и что модуль изначально будет сброшен.
signal clk : std_logic := '1'; signal rst : std_logic := '1'; signal position : integer range 0 to step_count - 1; signal pwm : std_logic;
Чтобы сгенерировать тактовый сигнал в тестовом стенде, я использую обычный однострочный процесс, показанный ниже.
clk <= not clk after clk_period / 2;
Экземпляр DUT
Ниже линии тактового импульса мы приступаем к созданию экземпляра DUT. Мы назначаем константы тестового стенда дженерикам с соответствующими именами. Кроме того, мы сопоставляем сигналы порта ИУ с локальными сигналами в испытательном стенде.
DUT : entity work.servo(rtl) generic map ( clk_hz => clk_hz, pulse_hz => pulse_hz, min_pulse_us => min_pulse_us, max_pulse_us => max_pulse_us, step_count => step_count ) port map ( clk => clk, rst => rst, position => position, pwm => pwm );
Секвенсор тестового стенда
Чтобы обеспечить стимулы для DUT, мы используем процесс секвенсора, показанный ниже. Во-первых, мы сбрасываем DUT. Затем мы используем цикл For для перебора всех возможных входных позиций (5 в нашем случае). Наконец, мы выводим сообщение на консоль симулятора и завершаем тестовую среду, вызывая функцию VHDL-2008 finish. процедура.
SEQUENCER : process begin wait for 10 * clk_period; rst <= '0'; wait for pulse_period; for i in 0 to step_count - 1 loop position <= i; wait for pulse_period; end loop; report "Simulation done. Check waveform."; finish; end process;
Моделирование сигнала сервопривода
На приведенной ниже форме сигнала показана часть сигнала, созданного испытательным стендом в ModelSim. Мы можем видеть, что испытательный стенд периодически изменяет входное положение и что ИУ реагирует, создавая импульсы ШИМ. Обратите внимание, что на выходе ШИМ высокий уровень только при самых низких значениях счетчика. Это работа нашего процесса PWM_PROC.
Если вы загрузите файлы проекта, вы сможете воспроизвести симуляцию, следуя инструкциям в Zip-файле.
Пример реализации ПЛИС
Следующее, что я хочу, это реализовать проект на FPGA и позволить ему управлять реальным сервоприводом RC, TowerPro SG90. Для этого мы будем использовать макетную плату Lattice iCEstick FPGA. Это та же самая плата, которую я использую в моем начальном курсе VHDL и моем продвинутом курсе FPGA.
Если у вас есть Lattice iCEstick, вы можете загрузить проект iCEcube2, используя форму ниже.
Однако сервомодуль не может действовать в одиночку. Нам нужно иметь несколько вспомогательных модулей, чтобы это работало на FPGA. По крайней мере, нам нужно что-то для изменения позиции ввода, а также у нас должен быть модуль сброса.
Чтобы сделать движение сервопривода более интересным, я собираюсь использовать модуль Sine ROM, о котором я рассказывал в предыдущей статье. Вместе с модулем счетчика из ранее упомянутой статьи Sine ROM будет генерировать плавный шаблон движения из стороны в сторону.
Прочитайте о модуле Sine ROM здесь:
Как создать эффект дышащего светодиода, используя синусоиду, хранящуюся в блочной ОЗУ
На приведенной ниже диаграмме потока данных показаны подмодули и то, как они связаны.
Сущность верхнего модуля
Объект верхнего модуля состоит из входов часов и сброса, а также выхода ШИМ, который управляет сервоприводом RC. Я проложил pwm сигнал на контакт 119 на Lattice iCE40 HX1K FPGA. Это крайний левый контакт на крайней левой стойке. Часы поступают от встроенного генератора iCEstick, и я подключил первый сигнал на контакт, сконфигурированный с внутренним подтягивающим резистором.
entity top is port ( clk : in std_logic; rst_n : in std_logic; -- Pullup pwm : out std_logic ); end top;
Сигналы и константы
В декларативной области верхнего модуля я определил константы, соответствующие Lattice iCEstick и моему сервоприводу TowerPro SG90.
Экспериментируя, я обнаружил, что от 0,5 мс до 2,5 мс дает мне 180 градусов движения, которые я хотел. Различные источники в Интернете предлагают другие значения, но это те, которые сработали для меня. Я не совсем уверен, что использую настоящий сервопривод TowerPro SG90, возможно, это подделка.
Если это так, то это было непреднамеренно, так как я купил его у интернет-продавца, но это может объяснить разные значения периода пульса. Я проверил длительность с помощью моего осциллографа. Это то, что написано в коде, показанном ниже.
constant clk_hz : real := 12.0e6; -- Lattice iCEstick clock constant pulse_hz : real := 50.0; constant min_pulse_us : real := 500.0; -- TowerPro SG90 values constant max_pulse_us : real := 2500.0; -- TowerPro SG90 values constant step_bits : positive := 8; -- 0 to 255 constant step_count : positive := 2**step_bits;
Я установил cnt сигнал для автономного счетчика должен иметь ширину 25 бит, что означает, что он будет выполняться примерно через 2,8 секунды при работе на тактовой частоте iCEstick 12 МГц.
constant cnt_bits : integer := 25; signal cnt : unsigned(cnt_bits - 1 downto 0);
Наконец, мы объявляем сигналы, которые будут соединять модули верхнего уровня в соответствии с блок-схемой данных, которую я представил ранее. Позже в этой статье я покажу, как взаимодействуют указанные ниже сигналы.
signal rst : std_logic; signal position : integer range 0 to step_count - 1; signal rom_addr : unsigned(step_bits - 1 downto 0); signal rom_data : unsigned(step_bits - 1 downto 0);
Создание сервомодуля
Создание экземпляра модуля сервопривода похоже на то, как мы делали это в тестовом стенде:постоянный сигнал для универсального и локальный сигнал для сигнала порта.
SERVO : entity work.servo(rtl) generic map ( clk_hz => clk_hz, pulse_hz => pulse_hz, min_pulse_us => min_pulse_us, max_pulse_us => max_pulse_us, step_count => step_count ) port map ( clk => clk, rst => rst, position => position, pwm => pwm );
Создание самозакрывающегося счетчика
В предыдущих статьях я использовал модуль самооборачивающегося счетчика. Это автономный счетчик, который считает до counter_bits. , а затем снова стремится к нулю. Об этом особо нечего сказать, но если вы хотите ознакомиться с ним, вы можете скачать пример проекта.
COUNTER : entity work.counter(rtl) generic map ( counter_bits => cnt_bits ) port map ( clk => clk, rst => rst, count_enable => '1', counter => cnt );
Создание Sine ROM
Я подробно объяснил модуль Sine ROM в предыдущей статье. Короче говоря, он переводит значение линейного числа в полную синусоиду с той же минимальной/максимальной амплитудой. Ввод:адрес сигнала, а синусоидальные значения отображаются в данных вывод.
SINE_ROM : entity work.sine_rom(rtl) generic map ( data_bits => step_bits, addr_bits => step_bits ) port map ( clk => clk, addr => rom_addr, data => rom_data );
Мы будем использовать одновременные назначения, показанные ниже, для подключения модуля счетчика, модуля синусоидального ПЗУ и модуля сервопривода.
position <= to_integer(rom_data); rom_addr <= cnt(cnt'left downto cnt'left - step_bits + 1);
Вход положения модуля Servo является копией выхода Sine ROM, но мы должны преобразовать значение без знака в целое число, потому что они имеют разные типы. Для ввода адреса ПЗУ мы используем старшие биты автономного счетчика. При этом цикл движения синусоидальной волны завершится, когда cnt сигнал обрывается через 2,8 секунды.
Тестирование на Lattice iCEstick
Я подключил всю схему на макетной плате, как показано на рисунке ниже. Поскольку FPGA использует 3,3 В, а сервопривод работает от 5 В, я использовал внешний источник питания 5 В и макетный переключатель уровня. Не считая преобразователя уровней, выходной сигнал ШИМ с вывода ПЛИС поступает непосредственно на провод «Сигнал» на сервоприводе TowerPro SG90.
После щелчка выключателя питания сервопривод должен плавно перемещаться вперед и назад на 180 градусов, слегка останавливаясь в крайних положениях. На видео ниже показана моя установка с ШИМ-сигналом, отображаемым на осциллографе.
Заключительные мысли
Как всегда, есть много способов реализовать модуль VHDL. Но я предпочитаю подход, описанный в этой статье, с использованием целочисленных типов в качестве счетчиков. Все тяжелые вычисления происходят во время компиляции, и результирующая логика состоит только из счетчиков, регистров и мультиплексоров.
Наиболее серьезная опасность при работе с 32-битными целыми числами в VHDL заключается в том, что они незаметно переполняются при вычислениях. Вы должны убедиться, что ни одно подвыражение не переполнится для любых значений в ожидаемом диапазоне ввода. Наш модуль сервопривода будет работать для любой реалистичной тактовой частоты и настроек сервопривода.
Обратите внимание, что этот тип ШИМ не подходит для большинства приложений, кроме сервоприводов RC. Для аналогового управления мощностью рабочий цикл важнее частоты переключения.
Об аналоговом управлении питанием с помощью ШИМ читайте здесь:
Как создать ШИМ-контроллер в VHDL
Если вы хотите попробовать примеры самостоятельно, вы можете быстро приступить к работе, загрузив Zip-файл, который я подготовил для вас. Введите свой адрес электронной почты в форму ниже, и вы получите все необходимое для начала работы в течение нескольких минут! Пакет содержит полный код VHDL, проект ModelSim со скриптом запуска, проект Lattice iCEcube2 и файл конфигурации программатора Lattice Diamond.
Дайте мне знать, что вы думаете в разделе комментариев!
VHDL
- Контроллер мощности ШИМ
- Как создать ШИМ-контроллер на VHDL
- Как инициализировать RAM из файла с помощью TEXTIO
- Потоковая передача данных датчика с пластины PPDAQC Pi с использованием InitialState
- Следите за температурой в доме с помощью Raspberry Pi
- Пошаговое руководство:как получить данные от ПЛК с помощью IIoT?
- Пример защиты ИИ в салоне с помощью TEE на защищенной SoC FPGA
- Можно ли ремонтировать сервоконтроллеры?
- Реальная жизнь с завода:привод оси C не работает Ошибка сервопривода
- 4-контактный ШИМ-контроль вентилятора, 25 кГц, с Arduino Uno