FPGAベースのRCサーボ・コントローラ設計: 精密PWM実装
ラジコン(RC)サーボは、ホビー用航空機、車、ボートの心臓部です。リモート無線コマンドを精密な角度位置に変換します。RCシステムは数十年前から存在するため、業界標準インターフェースは純粋なデジタル・プロトコルではなくパルス幅変調(PWM)です。FPGAが提供する決定論的なタイミングを使えば、高精度PWMの実装は簡単です。この記事では、シミュレーション・テストベンチとLattice iCEstick FPGAボードでの実機実装を含む、汎用VHDLサーボ・コントローラを解説します。
RCサーボ用PWM制御の仕組み
PWMの基礎は別途説明されていますが、RCサーボには少し異なるアプローチが必要です。サーボはデューティ・サイクル全体を無視し、各パルスのハイ期間の長さのみをサンプリングします。パルス間の標準インターバルは20msで、これは50Hzの周波数に相当します。その20msウィンドウ内で、サーボは通常1ms(最小位置)から2ms(最大位置)のパルス幅を読み取りますが、正確な値はメーカーによって異なります。

図はタイミング関係を示しています: 20ms周期が繰り返され、各パルスのハイ時間がサーボの角度位置に直接マッピングされます。
VHDLサーボ・コントローラ
クロック周波数、PWMパルス周波数、最小/最大パルス幅(マイクロ秒)、極値間の離散位置数をジェネリック・パラメータで受け取る汎用VHDLモジュールを構築します。FPGAクロックはメガヘルツ帯で動作するため、整数カウンター演算で十分な分解能が得られます。丸め曖昧さを避けるため、ieee.math_realパッケージのround関数を使用します。これは常にゼロから離れる方向に丸めます。
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.round;
以下はサーボ・モジュールのエンティティ宣言です:
entity servo is generic ( clk_hz : real; pulse_hz : real; -- PWMパルス周波数 min_pulse_us : real; -- 最小位置でのマイクロ秒 max_pulse_us : real; -- 最大位置でのマイクロ秒 step_count : positive -- 離散位置の数 ); port ( clk : in std_logic; rst : in std_logic; position : in integer range 0 to step_count - 1; pwm : out std_logic ); end servo;
position入力でサーボを最小(0)から最大(step_count-1)まで駆動します。pwm出力はサーボの信号線(通常黄色または白)に接続します。ほとんどのFPGAは3.3Vロジックを出力するため、サーボの5V入力に合わせるにはレベル・シフターが必要です。
宣言領域
マイクロ秒単位の時間を最寄りの整数クロック・サイクル数に変換するヘルパー関数から始めます:
function cycles_per_us (us_count : real) return integer is begin return integer(round(clk_hz / 1.0e6 * us_count)); end function;
この関数を使って、コア・タイミング定数を計算します:
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カウンターを設定します:
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;
クロック・サイクルのカウント
以下のプロセスでフリー・ランニング・カウンターを実装します:
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;
PWM出力プロセス
PWM出力は、現在の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;
デューティ・サイクルの計算
リセット時、duty_cycleは最小パルス幅にデフォルト。それ以外ではposition入力から計算されます:
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は最寄りの整数に丸められますが、FPGAクロックが50Hzパルス・レートより桁違いに速いため、結果の誤差は無視できます。
サーボ・テストベンチ
設計を検証するため、すべての位置値を手動でステップ実行し波形を取得するシンプルなテストベンチを作成しました。1MHzのテスト・クロックでシミュレーションを高速化しつつ、現実的なタイミングを反映させています。
シミュレーション定数
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信号
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 : 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をリセットし、すべての位置値を順にイテレーションし、変更間で1パルス周期待機。シーケンス完了後、シミュレーションを終了します。
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;
サーボ・シミュレーション波形
以下のスナップショットはテストベンチ波形を示しています。PWM出力は、各位置値に対する期待パルス幅に一致する最も早いカウンター・サイクルでのみハイになります。

完全なプロジェクト・ファイルをダウンロードしてシミュレーションを再現してください。
FPGA実装例
次に、Lattice iCEstickボードから実機のTowerPro SG90サーボを駆動します。サーボには5Vロジック・レベルが必要なため、FPGAピンとサーボの信号線の間にレベル・シフターを挟みます。
トップ・モジュール エンティティ
entity top is port ( clk : in std_logic; rst_n : in std_logic; -- プルアップ pwm : out std_logic ); end top;
信号と定数
サーボのパルス幅を実験的に調整: 0.5ms〜2.5msで180°全スイープが可能。以下の定数はiCEstickのクロックとサーボ・パラメータに一致します。
constant clk_hz : real := 12.0e6; -- iCEstickクロック constant pulse_hz : real := 50.0; constant min_pulse_us : real := 500.0; -- 0.5ms constant max_pulse_us : real := 2500.0; -- 2.5ms constant step_bits : positive := 8; -- 0〜255 constant step_count : positive := 2**step_bits;
フリー・ランニング・カウンターは25ビット幅で、12MHzクロックでは約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 : entity work.counter(rtl) generic map ( counter_bits => cnt_bits ) port map ( clk => clk, rst => rst, count_enable => '1', counter => cnt );
サイン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 );
並列代入でカウンター、ROM、サーボを接続:
position <= to_integer(rom_data); rom_addr <= cnt(cnt'left downto cnt'left - step_bits + 1);
Lattice iCEstickでのテスト
回路全体をブレッドボード上に実装: FPGAの3.3V出力をレベル・シフターで5Vに変換してサーボに供給。PWM信号はiCEstickのピン119に接続。電源投入時、サーボは0°と180°の間を滑らかに振動し、極値で一瞬停止。オシロスコープでPWM波形を確認すると正しいタイミングが得られています。

まとめ
VHDLでPWMサーボ・コントローラを実装する方法はいくつもありますが、整数カウンターを使用すると、合成されるロジックがカウンター、レジスタ、マルチプレクサのみでシンプルに保たれます。最も一般的な落とし穴は、32ビット演算でのサイレント・オーバーフローです。選択したパラメータで中間計算が境界内に収まることを常に検証してください。
このPWMスタイルはRCサーボ向けに調整されています。周波数よりデューティ・サイクルが重要な用途(アナログ電力制御など)には、別のPWM戦略が適しています。
PWMを使ったアナログ電力制御についてはこちら:
VHDLでPWMコントローラを作成する方法
自分で試してみませんか? 完全なVHDLコード、ModelSimテストベンチ、iCEcube2プロジェクト、プログラマー設定を含むZIPアーカイブを、以下のフォームにメールアドレスを入力してダウンロードしてください。
コメントで感想をお聞かせください!
VHDL
- ダウェルピンは機械脚位置決めに最適でない理由——代替案と実践的解決策
- サーボコントローラーは修理可能?専門家の洞察とコスト効率的解決策
- Osai-Prima Electroサーボドライブ 97942050N「C軸ドライブNG」エラーの解決
- ppDAQC Pi-PlateからInitialStateへ温度データをストリーミング - ヘッドレスRaspberry Piで実現
- Raspberry Piダッシュボードで家庭の温度を監視する
- Arduino Unoで25kHz 4ピンPWMファンを制御 – 製作・コード・テスト
- PWM電力コントローラ:パルス幅変調ランプドライバの構築
- 車載キャビンAIをセキュアFPGA SoC上のTEEで保護する
- ステップバイステップガイド:IIoTでPLCデータを抽出しリアルタイムインサイトを得る
- VHDLでPWMコントローラを実装: 設計、シミュレーション、FPGAデモ
- VHDLのTEXTIOライブラリでテキスト・ファイルからFPGAブロックRAMを初期化する