Manufaktur industri
Industri Internet of Things | bahan industri | Pemeliharaan dan Perbaikan Peralatan | Pemrograman industri |
home  MfgRobots >> Manufaktur industri >  >> Industrial programming >> VHDL

Pengontrol servo RC menggunakan PWM dari pin FPGA

Servo model yang dikendalikan radio (RC) adalah aktuator kecil yang biasanya digunakan dalam model pesawat, mobil, dan kapal penghobi. Mereka memungkinkan operator untuk mengontrol kendaraan melalui link radio dari jarak jauh. Karena model RC telah ada sejak lama, antarmuka standar de-facto adalah modulasi lebar-pulsa (PWM), bukan skema digital.

Untungnya, mudah untuk mengimplementasikan PWM dengan waktu yang tepat yang dapat digunakan FPGA pada pin outputnya. Dalam artikel ini, kami akan membuat pengontrol servo generik yang akan berfungsi untuk semua servo RC yang menggunakan PWM.

Cara kerja kontrol PWM untuk servo RC

Saya telah membahas PWM di posting blog sebelumnya, tetapi kami tidak dapat menggunakan modul itu untuk mengendalikan servo RC. Masalahnya adalah servo RC tidak mengharapkan pulsa PWM datang sesering itu. Itu tidak peduli dengan siklus tugas penuh, hanya durasi periode tinggi.

Ilustrasi di atas menunjukkan cara kerja sinyal PWM.

Interval ideal antara pulsa adalah 20 ms, meskipun durasinya kurang penting. 20 ms diterjemahkan menjadi frekuensi PWM 50 Hz. Ini berarti servo mendapat perintah posisi baru setiap 20 md.

Ketika pulsa tiba di servo RC, ia mengambil sampel durasi periode tinggi. Pengaturan waktu sangat penting karena interval ini diterjemahkan langsung ke posisi sudut pada servo. Kebanyakan servo mengharapkan untuk melihat lebar pulsa yang bervariasi antara 1 dan 2 md, tetapi tidak ada aturan yang ditetapkan.

Pengontrol servo VHDL

Kami akan membuat modul pengontrol servo VHDL generik yang dapat Anda konfigurasikan untuk bekerja dengan servo RC apa pun menggunakan PWM. Untuk melakukan itu, kita perlu melakukan beberapa perhitungan berdasarkan nilai input generik.

Frekuensi PWM yang digunakan oleh servo RC lambat dibandingkan dengan frekuensi switching megahertz dari FPGA. Penghitungan bilangan bulat dari siklus clock memberikan presisi yang cukup dari panjang pulsa PWM. Namun, akan ada kesalahan pembulatan kecil kecuali frekuensi clock cocok dengan periode pulsa dengan sempurna.

Kami akan melakukan perhitungan menggunakan nyata (floating-point), tetapi pada akhirnya, kita harus mengonversi hasilnya menjadi bilangan bulat. Tidak seperti kebanyakan bahasa pemrograman, putaran VHDL mengapung ke bilangan bulat terdekat, tetapi perilaku untuk setengah angka (0,5, 1,5, dll.) tidak ditentukan. Alat simulator atau sintesis dapat memilih untuk membulatkan keduanya.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.round;

Untuk memastikan konsistensi di seluruh platform, kami akan menggunakan round fungsi dari math_real library, yang selalu dibulatkan dari 0. Kode di atas menunjukkan impor dalam modul VHDL kami dengan math_real perpustakaan disorot.

Jika Anda membutuhkan kode lengkap untuk proyek ini, Anda dapat mengunduhnya dengan memasukkan alamat email Anda pada formulir di bawah ini. Dalam beberapa menit Anda akan menerima file Zip dengan kode VHDL, proyek ModelSim, dan proyek Lattice iCEcube2 untuk papan FPGA iCEstick.

Entitas modul servo dengan generik

Dengan menggunakan konstanta generik, kita dapat membuat modul yang akan berfungsi untuk semua RC servo yang diaktifkan PWM. Kode di bawah ini menunjukkan entitas modul servo.

Konstanta pertama adalah frekuensi clock FPGA yang diberikan sebagai tipe nyata, sedangkan pulse_hz menentukan seberapa sering output PWM harus berdenyut, dan dua konstanta berikut mengatur lebar pulsa dalam mikrodetik pada posisi minimum dan maksimum. Konstanta generik terakhir menentukan berapa banyak langkah yang ada antara posisi min dan maks, termasuk titik akhir.

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;

Selain clock dan reset, deklarasi port terdiri dari satu input dan satu sinyal output.

Posisi sinyal adalah input kontrol ke modul servo. Jika kita set ke nol, modul akan menghasilkan min_pulse_us pulsa PWM panjang mikrodetik. Kapan posisi berada pada nilai tertinggi, itu akan menghasilkan max_pulse_us pulsa panjang.

pwm output adalah antarmuka ke servo RC eksternal. Itu harus melalui pin FPGA dan terhubung ke input "Sinyal" pada servo, biasanya kabel kuning atau putih. Perhatikan bahwa Anda mungkin perlu menggunakan konverter level. Sebagian besar FPGA menggunakan level logika 3,3 V, sementara sebagian besar servo RC berjalan pada 5 V.

Wilayah deklaratif

Di bagian atas wilayah deklaratif modul servo, saya mendeklarasikan fungsi yang akan kita gunakan untuk menghitung beberapa konstanta. cycles_per_us fungsi, yang ditunjukkan di bawah, mengembalikan jumlah siklus clock terdekat yang perlu kita hitung untuk mengukur us_count mikrodetik.

function cycles_per_us (us_count : real) return integer is
begin
  return integer(round(clk_hz / 1.0e6 * us_count));
end function;

Tepat di bawah fungsi, kita mendeklarasikan konstanta pembantu, yang akan kita gunakan untuk membuat pengaturan waktu keluaran PWM sesuai dengan generiknya.

Pertama, kami menerjemahkan nilai mikrodetik min dan maks ke jumlah absolut siklus jam:min_count dan jumlah_maks . Kemudian, kami menghitung rentang dalam mikrodetik di antara keduanya, dari mana kami memperoleh step_us , perbedaan durasi antara setiap langkah posisi linier. Akhirnya, kami mengonversi mikrodetik nyata nilai ke sejumlah periode jam tetap: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);

Selanjutnya, kami mendeklarasikan penghitung PWM. Sinyal bilangan bulat ini adalah penghitung yang berjalan bebas yang membungkus pulse_hz kali setiap detik. Begitulah cara kami mencapai frekuensi PWM yang diberikan dalam obat generik. Kode di bawah ini menunjukkan bagaimana kita menghitung jumlah siklus clock yang harus kita hitung, dan bagaimana kita menggunakan konstanta untuk mendeklarasikan kisaran bilangan bulat.

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;

Akhirnya, kami mendeklarasikan salinan penghitung bernama duty_cycle . Sinyal ini akan menentukan lamanya periode tinggi pada keluaran PWM.

Menghitung siklus jam

Kode di bawah ini menunjukkan proses yang mengimplementasikan penghitung yang berjalan bebas.

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;

Tidak seperti ditandatangani dan tidak ditandatangani jenis yang membungkus diri, kita perlu secara eksplisit menetapkan nol ketika penghitung mencapai nilai maksimal. Karena kita sudah memiliki nilai maksimal yang ditentukan di counter_max konstan, mudah dicapai dengan konstruksi If-Else.

Proses keluaran PWM

Untuk menentukan apakah output PWM harus bernilai tinggi atau rendah, kami membandingkan penghitung dan duty_cycle sinyal. Jika penghitung kurang dari siklus kerja, outputnya bernilai tinggi. Jadi, nilai duty_cycle sinyal mengontrol durasi pulsa PWM.

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;

Menghitung siklus kerja

Siklus tugas tidak boleh kurang dari min_count siklus jam karena itulah nilai yang sesuai dengan min_pulse_us masukan umum. Oleh karena itu, kami menggunakan min_count sebagai nilai reset untuk duty_cycle sinyal, seperti yang ditunjukkan di bawah ini.

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;

Ketika modul tidak direset, kami menghitung siklus kerja sebagai fungsi dari posisi input. siklus_per_langkah konstanta adalah perkiraan, dibulatkan ke bilangan bulat terdekat. Oleh karena itu, kesalahan pada konstanta ini dapat mencapai 0,5. Ketika kita mengalikan dengan posisi yang diperintahkan, kesalahan akan meningkat. Namun, dengan jam FPGA yang jauh lebih cepat daripada frekuensi PWM, itu tidak akan terlihat.

Meja uji servo

Untuk menguji modul servo RC, saya telah membuat testbench pemeriksaan manual yang memungkinkan kita mengamati perilaku modul servo dalam bentuk gelombang. Jika Anda telah menginstal ModelSim di komputer Anda, Anda dapat mengunduh contoh proyek simulasi dengan memasukkan alamat email Anda pada formulir di bawah ini.

Konstanta simulasi

Untuk mempercepat waktu simulasi, kami akan menentukan frekuensi clock rendah 1 MHz di testbench. Saya juga telah menetapkan jumlah langkah hanya 5, yang seharusnya cukup bagi kami untuk melihat perangkat yang sedang diuji (DUT) beraksi.

Kode di bawah ini menunjukkan semua konstanta simulasi yang didefinisikan dalam testbench.

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;

sinyal DUT

Sinyal yang dideklarasikan di testbench cocok dengan input dan output DUT. Seperti yang dapat kita lihat dari kode di bawah, saya telah memberikan clk dan pertama menandakan nilai awal '1'. Ini berarti bahwa jam akan dimulai pada posisi tinggi dan modul akan direset pada awalnya.

signal clk : std_logic := '1';
signal rst : std_logic := '1';
signal position : integer range 0 to step_count - 1;
signal pwm : std_logic;

Untuk menghasilkan sinyal clock di testbench, saya menggunakan proses satu baris biasa yang ditunjukkan di bawah ini.

clk <= not clk after clk_period / 2;

Instansiasi DUT

Di bawah garis stimulus jam, kami melanjutkan untuk membuat instance DUT. Kami menetapkan konstanta testbench ke obat generik dengan nama yang cocok. Selanjutnya, kami memetakan sinyal port DUT ke sinyal lokal di testbench.

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
);

Sekuenser meja ujian

Untuk memberikan rangsangan untuk DUT, kami menggunakan proses sequencer yang ditunjukkan di bawah ini. Pertama, kita reset DUT. Kemudian, kami menggunakan loop For untuk mengulangi semua posisi input yang mungkin (5 dalam kasus kami). Terakhir, kami mencetak pesan ke konsol simulator dan mengakhiri testbench dengan memanggil VHDL-2008 selesai prosedur.

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;

Bentuk gelombang simulasi servo

Bentuk gelombang di bawah ini menunjukkan bagian dari bentuk gelombang yang dihasilkan testbench di ModelSim. Kita dapat melihat bahwa testbench secara berkala mengubah input posisi dan bahwa DUT merespons dengan menghasilkan pulsa PWM. Perhatikan bahwa output PWM tinggi hanya pada nilai counter terendah. Itulah pekerjaan proses PWM_PROC kami.

Jika Anda mengunduh file proyek, Anda seharusnya dapat mereproduksi simulasi dengan mengikuti petunjuk yang ada di file Zip.

Contoh penerapan FPGA

Hal berikutnya yang saya inginkan adalah menerapkan desain pada FPGA dan membiarkannya mengontrol servo RC kehidupan nyata, TowerPro SG90. Kami akan menggunakan papan pengembangan Lattice iCEstick FPGA untuk itu. Papan ini sama dengan yang saya gunakan dalam kursus VHDL pemula dan kursus FPGA lanjutan saya.

Jika Anda memiliki Lattice iCEstick, Anda dapat mengunduh proyek iCEcube2 dengan menggunakan formulir di bawah ini.

Namun, modul servo tidak dapat bekerja sendiri. Kita perlu memiliki beberapa modul pendukung agar ini dapat bekerja pada FPGA. Setidaknya, kita perlu sesuatu untuk mengubah posisi input, dan kita juga harus memiliki modul reset.

Untuk membuat gerakan servo lebih menarik, saya akan menggunakan modul Sine ROM yang telah saya bahas di artikel sebelumnya. Bersama dengan modul Penghitung dari artikel yang disebutkan sebelumnya, ROM Sine akan menghasilkan pola gerakan sisi-ke-sisi yang halus.

Baca tentang modul Sine ROM di sini:
Cara membuat efek LED pernapasan menggunakan gelombang sinus yang disimpan di blok RAM

Bagan aliran data di bawah ini menunjukkan submodul dan bagaimana mereka terhubung.

Entitas modul teratas

Entitas modul atas terdiri dari input clock dan reset dan output PWM, yang mengontrol servo RC. Saya telah merutekan pwm sinyal ke pin 119 pada Lattice iCE40 HX1K FPGA. Itu pin paling kiri di rak header paling kiri. Jam berasal dari osilator on-board iCEstick, dan saya telah menghubungkan pertama sinyal ke pin yang dikonfigurasi dengan resistor pull-up internal.

entity top is
  port (
    clk : in std_logic;
    rst_n : in std_logic; -- Pullup
    pwm : out std_logic
  );
end top; 

Sinyal dan konstanta

Di wilayah deklaratif modul atas, saya telah menetapkan konstanta yang cocok dengan Lattice iCEstick dan servo TowerPro SG90 saya.

Melalui percobaan, saya menemukan bahwa 0,5 ms hingga 2,5 ms memberi saya gerakan 180 derajat yang saya inginkan. Berbagai sumber di internet menyarankan nilai-nilai lain, tetapi inilah yang berhasil bagi saya. Saya tidak sepenuhnya yakin bahwa saya menggunakan servo TowerPro SG90 yang sah, mungkin itu palsu.

Kalau begitu, memang tidak disengaja karena saya membelinya dari penjual internet, tapi mungkin bisa menjelaskan perbedaan nilai periode pulsa. Saya telah memverifikasi durasi dengan osiloskop saya. Mereka adalah apa yang tertulis dalam kode yang ditunjukkan di bawah ini.

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;

Saya telah mengatur cnt sinyal untuk penghitung yang berjalan bebas dengan lebar 25 bit, yang berarti penghitung akan selesai dalam waktu sekitar 2,8 detik saat dijalankan pada jam 12 MHz iCEstick.

constant cnt_bits : integer := 25;
signal cnt : unsigned(cnt_bits - 1 downto 0);

Terakhir, kami mendeklarasikan sinyal yang akan menghubungkan modul tingkat atas sesuai dengan diagram alur data yang saya sajikan sebelumnya. Saya akan menunjukkan bagaimana sinyal di bawah ini berinteraksi nanti di artikel ini.

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);

Instansiasi modul servo

Instansiasi modul servo mirip dengan cara kami melakukannya di testbench:konstan ke generik, dan sinyal lokal ke sinyal port.

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
);

Instansiasi penghitung self-wrapping

Saya telah menggunakan modul penghitung self-wrapping di artikel sebelumnya. Ini adalah penghitung berjalan bebas yang diperhitungkan counter_bits , dan kemudian kembali ke nol. Tidak banyak yang bisa dikatakan tentang itu, tetapi jika Anda ingin memeriksanya, Anda dapat mengunduh proyek contoh.

COUNTER : entity work.counter(rtl)
generic map (
  counter_bits => cnt_bits
)
port map (
  clk => clk,
  rst => rst,
  count_enable => '1',
  counter => cnt
);

Instansiasi ROM sinus

Modul Sine ROM sudah saya jelaskan secara detail di artikel sebelumnya. Singkatnya, ini menerjemahkan nilai bilangan linier menjadi gelombang sinus penuh dengan amplitudo min/maks yang sama. Masukannya adalah addr sinyal, dan nilai sinus muncul di data keluaran.

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
);

Kami akan menggunakan tugas bersamaan yang ditunjukkan di bawah ini untuk menghubungkan modul Counter, modul Sine ROM, dan modul Servo.

position <= to_integer(rom_data);
rom_addr <= cnt(cnt'left downto cnt'left - step_bits + 1);

Input posisi modul Servo adalah salinan dari output Sine ROM, tetapi kita harus mengonversi nilai yang tidak ditandatangani menjadi bilangan bulat karena jenisnya berbeda. Untuk input alamat ROM, kami menggunakan bit teratas dari penghitung yang berjalan bebas. Dengan melakukan ini, siklus gerakan gelombang sinus akan selesai ketika cnt sinyal terbungkus, setelah 2,8 detik.

Pengujian pada iCEstick Lattice

Saya telah menghubungkan seluruh sirkuit pada papan tempat memotong roti, seperti yang ditunjukkan oleh sketsa di bawah ini. Karena FPGA menggunakan 3,3 V sementara servo berjalan pada 5 V, saya telah menggunakan catu daya 5 V eksternal dan pemindah level papan tempat memotong roti. Tanpa mempertimbangkan konverter level, output PWM dari pin FPGA langsung menuju kabel “Sinyal” pada servo TowerPro SG90.

Setelah menjentikkan sakelar daya, servo harus bergerak maju mundur dengan gerakan 180 derajat yang mulus, berhenti sedikit pada posisi ekstrem. Video di bawah ini menunjukkan pengaturan saya dengan sinyal PWM yang divisualisasikan pada osiloskop.

Pemikiran terakhir

Seperti biasa, ada banyak cara untuk mengimplementasikan modul VHDL. Tapi saya lebih suka pendekatan yang diuraikan dalam artikel ini, menggunakan tipe integer sebagai penghitung. Semua perhitungan berat terjadi pada waktu kompilasi, dan logika yang dihasilkan hanya penghitung, register, dan multiplexer.

Bahaya paling signifikan ketika berhadapan dengan bilangan bulat 32-bit di VHDL adalah bahwa mereka meluap secara diam-diam dalam perhitungan. Anda harus memeriksa bahwa tidak ada subekspresi yang akan meluap untuk nilai apa pun dalam rentang input yang diharapkan. Modul servo kami akan bekerja untuk frekuensi jam dan pengaturan servo yang realistis.

Perhatikan bahwa PWM jenis ini tidak cocok untuk sebagian besar aplikasi selain servo RC. Untuk kontrol daya analog, siklus kerja lebih penting daripada frekuensi switching.

Baca tentang kontrol daya analog menggunakan PWM di sini:
Cara membuat pengontrol PWM di VHDL

Jika Anda ingin mencoba contoh sendiri, Anda dapat memulai dengan cepat dengan mengunduh file Zip yang telah saya siapkan untuk Anda. Masukkan alamat email Anda dalam formulir di bawah ini, dan Anda akan menerima semua yang Anda butuhkan untuk memulai dalam beberapa menit! Paket tersebut berisi kode VHDL lengkap, proyek ModelSim dengan run-script, proyek Lattice iCEcube2, dan file konfigurasi programmer Lattice Diamond.

Beri tahu saya pendapat Anda di bagian komentar!


VHDL

  1. Pengontrol Daya PWM
  2. Cara membuat pengontrol PWM di VHDL
  3. Cara menginisialisasi RAM dari file menggunakan TEXTIO
  4. Streaming Data Sensor dari ppDAQC Pi Plate Menggunakan InitialState
  5. Pantau suhu rumah Anda menggunakan Raspberry Pi
  6. Langkah demi langkah:Bagaimana cara mendapatkan data dari PLC menggunakan IIoT?
  7. Contoh Mengamankan AI Dalam Kabin menggunakan TEE pada SoC FPGA Aman
  8. Apakah Kontroler Servo Anda Dapat Diperbaiki?
  9. Kehidupan Nyata Dari Pabrik:Drive sumbu C tidak berfungsi dengan baik Kesalahan pada Drive Servo
  10. 25 kHz 4 Pin Kontrol Kipas PWM dengan Arduino Uno