工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Industrial programming >> VHDL

FPGA ピンからの PWM を使用する RC サーボ コントローラー

ラジコン (RC) モデルのサーボは、一般的に愛好家の模型飛行機、車、およびボートで使用される小さなアクチュエータです。これらにより、オペレーターは無線リンクを介してリモートで車両を制御できます。 RC モデルは長い間存在しているため、事実上の標準インターフェイスはデジタル方式ではなくパルス幅変調 (PWM) です。

幸いなことに、FPGA が出力ピンに適用できる正確なタイミングで PWM を実装するのは簡単です。この記事では、PWM を使用するすべての RC サーボで動作する汎用サーボ コントローラーを作成します。

RC サーボの PWM 制御のしくみ

PWM については以前のブログ記事で既に説明しましたが、このモジュールを使用して RC サーボを制御することはできません。問題は、RC サーボが PWM パルスがそれほど頻繁に到着することを想定していないことです。フル デューティ サイクルは考慮せず、ハイ期間のみを考慮します。

上の図は、PWM 信号のタイミングがどのように機能するかを示しています。

パルス間の理想的な間隔は 20 ミリ秒ですが、持続時間はそれほど重要ではありません。 20 ms は、50 Hz の PWM 周波数に変換されます。これは、サーボが 20 ミリ秒ごとに新しい位置コマンドを取得することを意味します。

パルスが RC サーボに到着すると、ハイ期間の期間をサンプリングします。この間隔はサーボの角度位置に直接変換されるため、タイミングは非常に重要です。ほとんどのサーボは、パルス幅が 1 ~ 2 ミリ秒の間で変化することを期待していますが、決まったルールはありません。

VHDL サーボ コントローラ

PWM を使用して任意の RC サーボで動作するように構成できる汎用 VHDL サーボ コントローラー モジュールを作成します。そのためには、一般的な入力の値に基づいていくつかの計算を実行する必要があります。

RC サーボで使用される PWM 周波数は、FPGA のメガヘルツ スイッチング周波数に比べて低速です。クロック サイクルの整数カウントにより、PWM パルス長の十分な精度が得られます。ただし、クロック周波数がパルス周期と完全に一致しない限り、小さな丸め誤差が発生します。

実数を使用して計算を実行します (浮動小数点) 数値ですが、最終的には結果を整数に変換する必要があります。ほとんどのプログラミング言語とは異なり、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 から遠ざかります。上記のコードは、math_real を使用した VHDL モジュールのインポートを示しています。 ライブラリが強調表示されています。

このプロジェクトの完全なコードが必要な場合は、以下のフォームにメール アドレスを入力してダウンロードできます。数分以内に、VHDL コード、ModelSim プロジェクト、および iCEstick FPGA ボード用の Lattice iCEcube2 プロジェクトを含む Zip ファイルを受け取ります。

ジェネリックを持つサーボ モジュール エンティティ

汎用定数を使用することで、PWM 対応の RC サーボで動作するモジュールを作成できます。以下のコードは、サーボ モジュールのエンティティを示しています。

最初の定数は実数型として与えられる FPGA のクロック周波数で、pulse_hz PWM 出力がパルス化される頻度を指定し、次の 2 つの定数は最小位置と最大位置でパルス幅をマイクロ秒単位で設定します。最後のジェネリック定数は、エンドポイントを含め、最小位置と最大位置の間のステップ数を定義します。

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 を生成します マイクロ秒の長さの PWM パルス。 位置の場合 が最大値の場合、max_pulse_us が生成されます 長いパルス。

PWM 出力は、外部 RC サーボへのインターフェースです。これは FPGA ピンを経由し、サーボの「信号」入力 (通常は黄色または白色のワイヤ) に接続する必要があります。レベルコンバーターを使用する必要がある可能性が高いことに注意してください。ほとんどの FPGA は 3.3 V ロジック レベルを使用しますが、ほとんどの RC サーボは 5 V で動作します。

宣言領域

サーボ モジュールの宣言領域の上部で、いくつかの定数を計算するために使用する関数を宣言しています。 cycles_per_us 以下に示す関数は、us_count を測定するためにカウントする必要がある最も近いクロック サイクル数を返します。

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

関数のすぐ下で、ジェネリックに従って出力 PWM のタイミングを作成するために使用するヘルパー定数を宣言します。

まず、マイクロ秒の最小値と最大値を絶対クロック サイクル数に変換します:min_count および max_count .次に、2 つの間の範囲をマイクロ秒単位で計算し、そこから 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 をラップするフリーランニング カウンターです。 毎秒回。これが、ジェネリックで指定された PWM 周波数を実現する方法です。以下のコードは、カウントする必要があるクロック サイクル数を計算する方法と、定数を使用して整数の範囲を宣言する方法を示しています。

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 という名前のカウンターのコピーを宣言します .この信号は、PWM 出力のハイ期間の長さを決定します。

クロック サイクルのカウント

以下のコードは、フリーランニング カウンターを実装するプロセスを示しています。

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 コンストラクトを使用して簡単に達成できます。

PWM出力プロセス

PWM 出力が高値か低値かを判断するには、カウンターを比較します および duty_cycle 信号。カウンタがデューティ サイクルよりも小さい場合、出力は高い値になります。したがって、duty_cycle の値は 信号は 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;

デューティサイクルの計算

デューティ サイクルは、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;

モジュールがリセットされていない場合、入力位置の関数としてデューティ サイクルを計算します。 cycles_per_step 定数は近似値であり、最も近い整数に丸められます。したがって、この定数の誤差は最大 0.5 になる場合があります。命令された位置で乗算すると、誤差が拡大します。ただし、FPGA クロックは PWM 周波数よりもはるかに高速であるため、気にすることはありません。

サーボ テストベンチ

RC サーボ モジュールをテストするために、サーボ モジュールの動作を波形で観察できる手動チェック テストベンチを作成しました。コンピュータに ModelSim がインストールされている場合は、以下のフォームに電子メール アドレスを入力して、サンプル シミュレーション プロジェクトをダウンロードできます。

シミュレーション定数

シミュレーション時間を短縮するために、テストベンチで 1 MHz の低いクロック周波数を指定します。また、ステップ カウントを 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;

DUT 信号

テストベンチで宣言された信号は、DUT の入力と出力と一致します。以下のコードからわかるように、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 のポート信号をテストベンチのローカル信号にマッピングします。

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 でテストベンチが生成する波形の一部を示しています。テストベンチが位置入力を定期的に変更し、DUT が PWM パルスを生成して応答していることがわかります。 PWM 出力は、最も低いカウンター値でのみハイになることに注意してください。それが PWM_PROC プロセスの作業です。

プロジェクト ファイルをダウンロードすると、Zip ファイルにある指示に従ってシミュレーションを再現できるはずです。

FPGA の実装例

次に必要なのは、設計を FPGA に実装し、実際の RC サーボである TowerPro SG90 を制御できるようにすることです。そのために、Lattice iCEstick FPGA 開発ボードを使用します。初心者の VHDL コースと上級 FPGA コースで使用しているものと同じボードです。

Lattice iCEstick をお持ちの場合は、以下のフォームを使用して iCEcube2 プロジェクトをダウンロードできます。

ただし、サーボモジュールは単独では機能しません。これを FPGA で動作させるには、いくつかのサポート モジュールが必要です。少なくとも、入力位置を変更するための何かが必要であり、リセット モジュールも必要です。

サーボ モーションをより興味深いものにするために、以前の記事で説明した Sine ROM モジュールを使用します。前述の記事の Counter モジュールと共に、Sine ROM は滑らかな左右の動きパターンを生成します。

Sine ROM モジュールについてはこちらをご覧ください:
ブロック RAM に格納された正弦波を使用してブリージング LED エフェクトを作成する方法

以下のデータ フロー チャートは、サブモジュールとそれらの接続方法を示しています。

トップ モジュール エンティティ

最上位モジュールのエンティティは、クロックとリセット入力、および RC サーボを制御する PWM 出力で構成されます。 pwm をルーティングしました Lattice iCE40 HX1K FPGA のピン 119 への信号。それは、一番左のヘッダー ラックの一番左のピンです。クロックは 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 を設定しました これは、iCEstick の 12 MHz クロックで実行する場合、約 2.8 秒でラップすることを意味します。

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

正弦 ROM のインスタンス化

以前の記事で Sine ROM モジュールについて詳しく説明しました。簡単に言えば、線形数値を同じ最小/最大振幅を持つ完全な正弦波に変換します。入力は addr です 信号、および正弦値が data に表示されます 出力。

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

以下に示す同時割り当てを使用して、カウンター モジュール、サイン ROM モジュール、およびサーボ モジュールを接続します。

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

サーボ モジュールの位置入力は Sine ROM 出力のコピーですが、型が異なるため、符号なしの値を整数に変換する必要があります。 ROM アドレスの入力には、フリーランニング カウンタの上位ビットを使用します。これにより、正弦波モーション サイクルは cnt のときに完了します。 信号は 2.8 秒後に折り返します。

Lattice iCEstick でのテスト

下のスケッチに示すように、回路全体をブレッドボードに接続しました。 FPGA は 3.3 V を使用し、サーボは 5 V で動作するため、外部 5 V 電源とブレッドボード可能なレベル シフターを使用しました。レベル コンバーターを考慮しないと、FPGA ピンからの PWM 出力は、TowerPro SG90 サーボの「信号」ワイヤに直接送られます。

電源スイッチをフリックした後、サーボは滑らかな 180 度の動きで前後に動き、極端な位置でわずかに停止します。下のビデオは、オシロスコープで視覚化された PWM 信号を使用したセットアップを示しています。

最終的な考え

いつものように、VHDL モジュールを実装するには多くの方法があります。しかし、整数型をカウンターとして使用する、この記事で概説されているアプローチを私は好みます。重い計算はすべてコンパイル時に行われ、結果として得られるロジックはカウンター、レジスター、およびマルチプレクサーのみです。

VHDL で 32 ビット整数を処理する際の最も重大な問題は、計算時にサイレントにオーバーフローすることです。予想される入力範囲内の値に対して部分式がオーバーフローしないことを確認する必要があります。当社のサーボ モジュールは、現実的なクロック周波数とサーボ設定で動作します。

この種の PWM は、RC サーボ以外のほとんどのアプリケーションには適していないことに注意してください。アナログ電力制御では、スイッチング周波数よりもデューティ サイクルが重要です。

PWM を使用したアナログ電源制御については、こちらをご覧ください:
VHDL で PWM コントローラを作成する方法

例を自分で試してみたい場合は、私が用意した Zip ファイルをダウンロードしてすぐに始めることができます。以下のフォームにメールアドレスを入力すると、数分以内に開始するために必要なものがすべて届きます!このパッケージには、完全な VHDL コード、実行スクリプトを含む ModelSim プロジェクト、Lattice iCEcube2 プロジェクト、および Lattice Diamond プログラマ構成ファイルが含まれています。

コメント セクションでご意見をお聞かせください。


VHDL

  1. PWMパワーコントローラー
  2. VHDL で PWM コントローラーを作成する方法
  3. TEXTIO を使用してファイルから RAM を初期化する方法
  4. InitialStateを使用したppDAQCPiプレートからのセンサーデータのストリーミング
  5. RaspberryPiを使用して自宅の温度を監視します
  6. ステップバイステップ:IIoTを使用してPLCからデータを取得する方法は?
  7. セキュアFPGASoCでTEEを使用してキャビン内AIを保護する例
  8. 機械の脚にノックピンを使用する代わりの方法
  9. サーボコントローラは修理可能ですか?
  10. プラントからの実際の生活:C軸ドライブがOKではないサーボドライブのエラー
  11. ArduinoUnoを使用した25kHz4ピンPWMファン制御