VHDL で PWM コントローラーを作成する方法
パルス幅変調 (PWM) は、純粋なデジタル FPGA ピンからアナログ電子機器を制御する効率的な方法です。アナログ電圧を安定化しようとする代わりに、PWM はアナログ デバイスへのフル パワーで供給電流をすばやくオン/オフします。この方法により、消費者向けデバイスに提供されるエネルギーの移動平均を正確に制御できます。
PWM の適切な候補となるユース ケースの例としては、オーディオ変調 (スピーカー)、光強度制御 (ランプまたは LED)、および誘導モーターがあります。後者には、サーボ モーター、コンピューター ファン、ポンプ、電気自動車用のブラシレス DC モーターなどが含まれます。
こちらもご覧ください:
FPGA ピンから PWM を使用する RC サーボ コントローラー
PWM の仕組み
デバイスへの電源供給を高周波でオン/オフすることにより、デバイスに流れる平均電流を正確に制御できます。下の図は、PWM の仕組みの基本を示しています。 PWM 出力は、電力を 100% または 0% に設定できるバイナリ スイッチを制御します。 2 つの極値をすばやく切り替えることで、スライディング ウィンドウの平均は、各状態で費やされた時間の関数になります。
デューティサイクル
PWM でアナログ デバイスに供給される電力を制御するには、デューティ サイクルが重要です。 デューティ サイクルという用語 PWM 出力が ON 位置にある時間を意味します。下の図に示すように、デューティ サイクルをパーセンテージで表すのが一般的です。ただし、VHDL の例では、この記事の後半で符号なしの 2 進数を使用します。 VHDL 実装でのデューティ サイクルの完全な解像度を表すことができる 2 進数を使用する方が理にかなっています。
デューティ サイクルが 0 の場合、PWM 出力は継続的にオフの位置に留まり、100% の場合はオンの位置で停止しません。 PWM コントローラーがペイロード効果に及ぼすことができる精度の程度は、PWM カウンターの長さに直接関係します。この記事の後半で PWM コントローラーを実装するときに、VHDL コードでこれがどのように機能するかを確認します。
デューティ サイクルのバイナリ表現をパーセンテージに変換する式を以下に示します。
\mathit{duty\_cycle\_percentage} =\frac{\mathit{commanded\_duty\_cycle} * 100}{2^\mathit{pwm\_bits} - 1}PWM 周波数
PWM スイッチング周波数とは、PWM 出力がオンとオフの状態を切り替える頻度、つまり PWM カウンターがラップするのにかかる時間を意味します。いつものように、周波数は完全な PWM 周期の逆数です:
\mathit{pwm\_freq} =\frac{1}{\mathit{pwm\_period}}理想的な PWM 周波数は、制御しているデバイスの種類によって異なります。消費者が LED の場合、数百ヘルツを超える数値は肉眼では安定した光源のように見えます。ブラシレス DC モーターの場合、スイート スポットは数十キロヘルツの範囲にあります。周波数を低く設定しすぎると、物理的な振動が発生する場合があります。振動が速すぎると、電力が無駄になります。
注意すべき問題は、アナログ パワー エレクトロニクスはデジタル FPGA ピンほど高速ではないということです。典型的な PWM セットアップでは、パワー MOSFET をスイッチとして使用して、アナログ デバイスを流れる電流を制御します。
画像に示されている回路図を検討してください。これは、高度なドット マトリックス VHDL コースで使用される LED ドライバー回路の一部です。 FPGA ピンは MOSFET のゲートを制御し、直列 LED へのサーキット ブレーカーとして機能します。スイッチング周波数が高くなると、トランジスタが完全に開いたり閉じたりしない時間が長くなります。これは、電力の浪費と MOSFET での過剰な熱生成につながります。
PWM ジェネレータ モジュール
VHDL で PWM コントローラーの標準的な汎用実装を作成しましょう。 標準とは これは、経験豊富な VHDL 設計者が VHDL で PWM コントローラーを作成するように依頼した場合に作成するものに近いということです。 汎用です ほとんどのアプリケーションに適合するように PWM 周波数をカスタマイズできるという意味で。
実際の FPGA で PWM ジェネレーターをテストするには、PWM コントローラーに加えてさらにいくつかのモジュールが必要になります。 PWM モジュールを使用して Lattice iCEstick FPGA 開発ボード上の LED の照明を制御する際に、それらを後で紹介します。しかし、最初に、PWM ジェネレーター モジュールについて話しましょう。
PWM モジュール エンティティ
モジュールをカスタマイズ可能にするために、インスタンス化時に 2 つの定数を指定できる汎用ポートを追加しました。
pwm_bits という名前の最初のもの 、内部 PWM カウンターの長さを決定します。この定数は、最大カウンター値ではなく、ビット長を設定します。 PWM 周波数を特定のクロック周期数として指定することはできません。しかし通常、100% の精度で PWM 周波数を設定する必要はありません。理想的な PWM 周波数は、1 つの正確な数値ではなく、適切に機能する範囲です。
もう 1 つの汎用定数の名前は clk_cnt_len です .これは、PWM 周波数を効果的に下げる秒カウンターの長さを指定します。これはクロック分周器として機能しますが、派生クロック信号を実際に作成することはありません。デフォルト値の 1 が割り当てられていることに注意してください。この定数を 1 に設定すると、クロック分周器が無効になり、それを処理する余分なロジックも削除されます。
これについて説明し、記事の後半で正確な PWM 周波数を計算する式を提示します。
entity pwm is generic ( pwm_bits : integer; clk_cnt_len : positive := 1 ); port ( clk : in std_logic; rst : in std_logic; duty_cycle : in unsigned(pwm_bits - 1 downto 0); pwm_out : out std_logic ); end pwm;
これは完全同期モジュールであるため、最初の 2 つの信号はクロックとリセットです。
ポート宣言リストの 3 番目の入力は、デューティ サイクルです。上記の VHDL コードからわかるように、duty_cycle の長さは 信号は pwm_bits に従います ジェネリック定数。これは、pwm_bits 定数は、アナログ デバイスへの電力をどれだけ正確に調整できるかを制御します。
エンティティの最終的なシグナルは pwm_out です .これが PWM 変調制御信号で、FPGA ピンにルーティングして MOSFET のゲートに接続します。
PWM モジュールの内部信号
PWM モジュールには 2 つの内部信号しか含まれていません。 1 つ目は PWM カウンターで、duty_cycle と同じです。 入力。後者と同様に、pwm_bits 定数は、この信号の長さも決定します。
signal pwm_cnt : unsigned(pwm_bits - 1 downto 0); signal clk_cnt : integer range 0 to clk_cnt_len - 1;
2 番目の内部信号の名前は clk_cnt です であり、その名前が示すように、クロック サイクルをカウントするためのものです。これは整数型で、clk_cnt_len を設定すると から 1 まで、カウント範囲は (0 から 0) に評価されます —数字の 0 だけ。
PWM クロック サイクル カウンター プロセス
クロック カウンターを実装するプロセスは簡単です。モジュールがリセットされていない場合、ロジックは連続してクロック サイクルをカウントし、clk_cnt の最大値でゼロに戻ります。 整数を保持できます。
CLK_CNT_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then clk_cnt <= 0; else if clk_cnt < clk_cnt_len - 1 then clk_cnt <= clk_cnt + 1; else clk_cnt <= 0; end if; end if; end if; end process;
clk_cnt_len にデフォルト値の 1 を使用した場合は、 ジェネリック、このプロセスは合成中に蒸発するはずです。 0 < 1 - 1
のため、内部の if ステートメントは常に false になります。 は偽です。 clk_cnt の値 は常に 0 です。ほとんどの合成ツールはこれを認識し、プロセス全体を最適化します。
PWM出力プロセス
PWM 出力信号を設定するプロセスは、PWM カウンターも制御します。クロック サイクル カウンターが 0 のときに、PWM カウンターをインクリメントします。これが、PWM 周波数制限メカニズムのしくみです。
最初は if clk_cnt = 0 then
だけ書くつもりでした しかし、デフォルトの clk_cnt_len を使用すると、合成ツールがクロック カウンターに関連するすべてのロジックを削除しないことがわかりました。 1 の値。ただし、clk_cnt_len を含む ifステートメントでトリックを行いました。 clk_cnt_len であるため、合成に悪影響を与えるべきではありません。 定数です。合成ツールは、コンパイル時にその値を把握し、プロセスの内容が冗長かどうかを判断できます。
PWM_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pwm_cnt <= (others => '0'); pwm_out <= '0'; else if clk_cnt_len = 1 or clk_cnt = 0 then pwm_cnt <= pwm_cnt + 1; pwm_out <= '0'; if pwm_cnt = unsigned(to_signed(-2, pwm_cnt'length)) then pwm_cnt <= (others => '0'); end if; if pwm_cnt < duty_cycle then pwm_out <= '1'; end if; end if; end if; end if; end process;
clk_cnt_len の場合 pwm_cnt が 1 より大きい 信号はフリーランニング カウンターのように動作し、clk_cnt のときにインクリメントします。 は 0 です。符号なしの型で、オーバーフローすると自動的に 0 に戻ります。ただし、ゼロにラップする前に最大値をスキップするようにする必要があります。
上記のコードの 14 行目では、カウンターが 2 番目に高い値にあるかどうかを確認しています。そうであれば、この時点で 0 に設定します。 pwm_cnt がどれほど長くても機能するトリックを使用しています 信号は。 to_signed を使用する 関数、pwm_cnt と同じ長さの新しい符号付き定数を作成しています 、ただし値は -2 です。
VHDL の符号付き数値 -2、および一般的なコンピューターは、常に一連の 1 であり、右端の位置に 0 があります。それは、符号拡張がどのように機能するかによるものです。詳細については、以前のチュートリアルをご覧ください:
VHDL で署名付きおよび署名なしを使用する方法
最後に、signed 型を unsigned 型にキャストすることで、2 番目に高い値 pwm_cnt を取得します。
18 行目では、フリーランニング PWM カウンターがデューティ サイクル入力よりも大きいかどうかを確認しています。そうであれば、デューティ サイクルのオン期間にあるため、PWM 出力を「1」に設定します。
そのため、PWM カウンターを 2 番目に高い値で 0 に戻す必要がありました。 PWM カウンターがデューティ サイクルの可能な最大値に達する可能性がある場合、デューティ サイクルを 100% に設定することはできません。 pwm_cnt < duty_cycle
pwm_cnt の場合、行は常に false になります。 は最大値でした。
中間のデューティ サイクル ステップに加えて、完全なオフ状態とオン状態を表す必要があるため、これは理にかなっています。 pwm_bits を想像してみてください は 2 に設定されており、何を意味するかを理解するために頭の体操としてカウント シーケンス全体を実行してください!
\mathit{pwm\_hz} =\frac{\mathit{clk\_hz}}{(2^\mathit{pwm\_bits} - 1) * \mathit{clk\_cnt\_len}}これらの事実を考慮に入れることで、正確な PWM 周波数を計算するための上記の式を導き出すことができます。 clk_hz の間 は FPGA システム クロックの周波数、他の 2 つの変数は汎用入力定数です。
トップ モジュール
実際のハードウェアで PWM モジュールをテストするために、Lattice iCEstick の電源オン LED の照明を調整する実装を作成しました。この手頃な価格の FPGA 開発ボードは、VHDL Fast-Track 初心者コースと上級ドット マトリックス FPGA コースの両方で使用しています。
上の画像は、電源オン LED が矢印で示されている USB プラグ可能な iCEstick の前面を示しています。 iCEstick には星型に配置された 5 つの LED があります。電源オン LED は緑色で、その他の LED は赤色の光を発します。 iCEstick には、各 LED に専用の FPGA ピンがあります。それらを制御するための正確なピン番号を確認するには、iCEstick ユーザー マニュアルを参照してください。
下の図は、最上位モジュールのサブモジュールがどのように接続されているかを示しています。 PWM モジュールについては既に説明しました。カウンター モジュールとリセット モジュールについては、この記事の後半で簡単に説明します。
トップ モジュール エンティティ
トップ モジュール内で定数をハードコーディングする代わりに、トップレベル エンティティでジェネリックとして宣言しています。次に、iCEstick に適したデフォルト値を割り当てます。このアプローチの利点の 1 つは、テストベンチでこれらの値をオーバーライドして、シミュレーションを高速化できることです。デザインを合成するとき、ジェネリックには何も割り当てません。したがって、正しいデフォルト値が最終的にルーティングされたデザインになります。
pwm_bits を渡します および clk_cnt_len 同じ名前の PWM モジュール エンティティのジェネリックに。 iCEstick オシレーターのクロック周波数は 12 Mhz です。前に示した式を使用して、これらの値を代入して PWM 周波数を計算できます:\frac{12e6}{(2^8 - 1) * 47} \approx 1 \mathit{kHz}
entity pwm_led is generic ( pwm_bits : integer := 8; cnt_bits : integer := 25; clk_cnt_len : positive := 47 ); port ( clk : in std_logic; rst_n : in std_logic; -- Pullup led_1 : out std_logic; led_2 : out std_logic; led_3 : out std_logic; led_4 : out std_logic; led_5 : out std_logic ); end pwm_led;
3 番目の定数 cnt_bits があることに気付いたかもしれません 、上記のコードのジェネリック宣言で。自己ラッピングのこぎり歯カウンターの長さを制御します。この追加のカウンターを使用して、パワーオン LED の段階的な照明を作成し、PWM モジュールの動作をリアルタイムで観察できるようにします。
この新しいカウンタの上位ビットを PWM モジュールのデューティ サイクル入力に接続します。このカウンターはクロック サイクルをカウントするため、cnt_bits generic は、電源オン LED のパルス周波数を決定します。クロック周波数とカウンター長の関数である式を以下に示します。
\frac{2^{\mathit{cnt\_bits}}}{\mathit{clk\_hz}} =\frac{2^{25}}{12e6} \approx 2.8 \mathit{Hz}ポート宣言では、リセット入力の後に _n を付けました 、外部リセットが負極性であることを示します。このピンで内部プルアップ抵抗を使用するように Lattice FPGA を構成します。
最後に、ポート宣言で iCEstick に存在するすべての LED をリストしたことがわかります。 LED 番号 5 のみを使用しますが、他の LED をアクティブに駆動する必要があります。接続しないままにしておくと、かすかな赤色で点灯します。
VHDL コードと制約ファイルの詳細を確認したい場合は、以下のフォームに電子メールを入力してください。 ModelSim および Lattice iCEcube2 プロジェクトの完全なコードを含む Zip ファイルを受け取ります。
トップ モジュールの内部信号
最上位モジュールには RTL ロジックを含めないようにしています。 構造モジュールと呼ばれる概念です .私の経験では、RTL ロジックと相互接続を分離すると、構造化された VHDL プロジェクトを維持しやすくなります。以下のコードは、最上位モジュールのシグナル宣言と同時シグナル割り当てを示しています。
architecture str of pwm_led is signal rst : std_logic; signal cnt : unsigned(cnt_bits - 1 downto 0); signal pwm_out : std_logic; alias duty_cycle is cnt(cnt'high downto cnt'length - pwm_bits); begin led_1 <= '0'; led_2 <= '0'; led_3 <= '0'; led_4 <= '0'; led_5 <= pwm_out;
まず、外部リセットの非反転同期バージョンとなるリセット信号を宣言します。
cnt という名前の 2 番目に宣言されたシグナル 、無限にラッピングするクロック サイクル カウンターです。これは、任意の時点で LED 強度のノコギリ波の状態を保持する符号なしの型です。
次は pwm_out です 信号。 pwm_out を接続できたはずです PWM モジュールから led_5 に直接信号を送る 出力ですが、pwm_out を観察したかったのです シミュレーターで。合成ツールは、2 つの信号が同じネットに属していることを認識します。追加のリソースはかかりません。
最後に duty_cycle の宣言です ベクトル - 今回は alias を使用しました 新しいシグナルを作成する代わりに、キーワードを使用します。 VHDL エイリアスは、C のマクロのように機能します。duty_cycle を使用する場合 これ以降、名前を指定すると、コンパイラはそれを cnt の上位ビットに置き換えます。 ベクトル。
開始の後 キーワード、pwm_out を割り当てます led_5 への合図 出力。他のすべての LED は、赤色の光を点灯させないように「0」に配線されています。
インスタンス化
FPGA 内で外部信号を使用する前に、それらを常に内部システム クロックに同期させる必要があります。そうしないと、準安定性の問題、つまりデバッグが困難な問題が発生する可能性があります。
RESET : entity work.reset(rtl) port map ( clk => clk, rst_n => rst_n, rst => rst );
外部リセットも例外ではありませんが、最上位の構造モジュールでは RTL ロジックを使用できないため、リセット シンクロナイザーをスタンドアロン モジュールとして実装します。
次のインスタンス化は、以下のコード スニペットに示すように、PWM モジュールです。 PWM モジュールのインスタンス化では、duty_cycle を使用しています。 cnt の最上位ビットを割り当てるためのエイリアス duty_cycle へのベクトル 入力。これにより、カウンターが最大値に達するまで LED の明るさが増します。 いつ ゼロに戻り、LED が一時的にオフになり、サイクルが繰り返されます。
PWM : entity work.pwm(rtl) generic map ( pwm_bits => pwm_bits, clk_cnt_len => clk_cnt_len ) port map ( clk => clk, rst => rst, duty_cycle => duty_cycle, pwm_out => pwm_out );
最上位モジュールの 3 番目で最後のインスタンスは、次に示すようにクロック サイクル カウンターです。このモジュールをより汎用的にするために、count_enable を含めました。 信号。ただし、この設計では、すべてのクロック サイクルをカウントしたいので、定数「1」に設定します。
COUNTER : entity work.counter(rtl) generic map ( counter_bits => cnt'length ) port map ( clk => clk, rst => rst, count_enable => '1', counter => cnt );
このプロジェクトの完全な VHDL コードが必要な場合は、以下のフォームにメール アドレスを残してください。
PWM LED パルスのシミュレーション
ジェネリックを使用してカウンターの長さをカスタマイズ可能にすることの大きな利点は、シミュレーションを高速化できることです。ほとんどの場合、ロジック内の遷移とイベントをテストすることに関心があります。設計で他に何も起こらない間は、超長いカウンターを実行することにそれほど熱心ではありません。
ジェネリックを使用すると、テストベンチから非侵襲的な方法でこれらを変更できます。以下のコードは、テストベンチで PWM モジュールをインスタンス化するときにジェネリック マップに割り当てた値を示しています。
DUT : entity work.pwm_led(str) generic map ( pwm_bits => 8, cnt_bits => 16, clk_cnt_len => 1 )
ModelSim でこれらの定数を使用してシミュレートすると、100 MHz で 1400 マイクロ秒実行するだけで 2 つの完全な PWM サイクルが明らかになります。実際の値を使用した場合、6 秒近くシミュレートする必要があります。これは 3,200 万クロック サイクルであり、数えることしかできません。 ModelSim では永遠にかかります。
下の画像は、ModelSim での PWM シミュレーションの波形を示しています。 duty_cycle の形式を変更しました デフォルトの数値タイプからアナログ波形プレゼンテーションへの信号。 ModelSim でこれを行うには、波形の信号を右クリックし、Format->Analog (custom)... を選択します。 、信号の最小値と最大値に一致するようにピクセルの高さとデータ範囲を設定します。
波形を見ると、ノコギリ信号と呼ばれる理由がわかります。フリーランニングのラッピング カウンターは、鋸刃の歯に似ています。
PWM 出力 (led_5 ) は、デューティ サイクルが大きくなるにつれて増加します。 led_5 も確認できます のこぎり歯の先端で非常に短時間連続する「1」です。負荷サイクルが最大値の 255 のときです。
PWM モジュールに追加の if ステートメントを追加しなかった場合、pwm_cnt をラップするもの 信号が 2 番目に高い値でゼロに戻る場合、これは表示されません。最大出力に到達することはできません。これは、PWM ジェネレーターを実装する際によくあるエラーです。私も一度か二度やったことがあります。
FPGA の実装
Lattice の設計ソフトウェアである iCEcube2 を使用して、Lattice iCEstick に設計を実装しました。以下のリストは、配置配線後に報告されたリソース使用量を示しています。 iCE40 FPGA は小さいですが、PWM とサポート モジュールは利用可能な LUT の 5% しか使用しません。
Resource Usage Report for pwm_led Mapping to part: ice40hx1ktq144 Cell usage: GND 3 uses SB_CARRY 31 uses SB_DFF 5 uses SB_DFFSR 39 uses SB_GB 1 use VCC 3 uses SB_LUT4 64 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%) Total load per clock: pwm_led|clk: 1 @S |Mapping Summary: Total LUTs: 64 (5%)
iCEcube2 でプログラミング ビットストリームを生成した後、Lattice Diamond スタンドアロン プログラマを使用して USB 経由で FPGA を構成しました。
以下の Gif アニメーションは、のこぎり波のデューティ サイクル信号によって iCEstick の電源オン LED がどのように動作するかを示しています。 cnt まで強度を上げながら照らします。 カウンターラップ。その後、デューティ サイクルがすべてゼロになり、LED が一時的にオフになります。その後、サイクルは無期限に繰り返されます。
iCEstick は、安価で用途の広い FPGA 開発ボードです。初心者にも適していますが、高度な組み込みプロジェクトにも適しています。さらに、ラティスのソフトウェアは単純で使いやすいです。そのため、初心者向け VHDL コースと上級 FPGA コースの両方で iCEstick を使用しています。
すでに iCEstick をお持ちの場合は、以下のフォームを使用して iCEcube2 プロジェクトをダウンロードできます。
LED 呼吸効果の正弦波デューティ サイクル
これで、PWM を使用して LED の照明を制御する方法がわかりました。
のこぎり波パターンで脈動する LED は、単純なオン/オフの点滅アプリケーションよりも間違いなくクールです。これは、VHDL の学生にとって典型的な最初のタスクであり、ある時点で LED を点滅させたことがあると思います。
ただし、正弦波を使用してデューティ サイクルを制御すると、LED の点滅がさらに印象的になります。下の Gif アニメーションは、PWM モジュールが時間の経過とともに正弦波の強度変化で LED をパルスする様子を示しています。
LED でこの種の「呼吸」効果を見たことがあると思います。それが私の携帯電話の通知 LED の動作であり、光量の急激な変化がないので自然に見えると思います。
次回のブログ投稿では、FPGA のブロック RAM を使用して正弦波ジェネレーターを作成する方法を紹介します。 pwm_led を変更します 正弦波強度で iCEstick の LED を脈動させるモジュール。
ここをクリックして次のブログ記事にアクセスしてください:
ブロック RAM に保存された正弦波を使用してブリージング LED エフェクトを作成する方法
こちらもご覧ください:
FPGA ピンから PWM を使用する RC サーボ コントローラー
VHDL