目次

PWM(Pulse Width Modulation)WaveGenerator

 CPLD/FPGAを利用して、PWM(Pulse Width Modulation)WaveGeneratorを作成してみます。

 PWMが、どういう用途で利用されているかをリストします。

 この他にもありますが、自分が試した内容は、リストの通りです。

 自分で最初に作ったPWMのWaveGeneratorの回路は、以下です。



 JICA研修で、DCモータのスピード制御を説明するために手持ちの
 部品を利用して作成しました。

 動作原理は非常に単純で、1周期の中にHが何%あるかを
 指定して、出力を決めればPWMの波形が得られます。

 カウンタを利用して、マルチプレクサの入力を切り替えます。
 入力を出力に伝達しますが、どの入力を使うかで制御します。

 8ビットのDIPスイッチで、HとLを決めています。

 DIPスイッチの状態を、HHHHLLLLとすると
 HとLの比率は、50%(=(4/8)*100)になります。

 DIPスイッチの状態を、HHLLLLLLとすると
 HとLの比率は、25%(=(2/8)*100)になります。

 上の回路では、マルチプレクサを利用し、HとLの比率(Duty比)
 を変えていますが、カウンタとコンパレータで実現するのが一般的です。




 カウンタで、0から100までカウントします。
 100になったら、0にカウンタの値を戻します。
 カウンタの値を監視して、レジスタの値を超えたならば
 1を出力しています。

 この動作を回路で実現すると、PWM WaveGeneratorになります。


ブロック図作成

 動作仕様から、ブロック図を作成します。  フリーランするカウンタ、カウンタ値と比較する値を格納する  レジスタが必要になります。  カウンタ、レジスタの値を比較するコンパレータがあれば、それ  でPWM WaveGeneratorが完成します。  カウンタは、フリーランでよいので、リセット信号とクロックを  入力します。  レジスタには、値を設定しますが、カウンタのクロックとは独立  にラッチ(記憶)用パルスを与えます。内部はシフトレジスタと  して、シリアルでデータを入力します。

entity定義

 ブロック図で外部とやりとりする信号が決まれば、entityを定義します。 entity tstpwm is Port ( -- system nRESET : in std_logic ; -- system reset CLOCK : in std_logic ; -- reference clock -- register signals SDAT : in std_logic ; -- serial data SCLK : in std_logic ; -- serial latch clock -- output PWMOUT : out std_logic --; ); end tstpwm;

フリーランカウンタ定義

 フリーランカウンタは、入力信号がnRESET、CLOCKです。  カウンタのビットサイズをどうするかを考えます。  0から100まで計数するので、バイナリカウンタを利用して  8ビットサイズとします。  8ビットサイズのバイナリカウンタを定義します。 process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCOUNTER <= (others => '0') ; elsif rising_edge( CLOCK ) then -- increment iCOUNTER <= iCOUNTER + '1' ; -- counter reset if ( conv_integer( iCOUNTER ) = 100 ) then iCOUNTER <= (others => '0') ; end if ; end if ; end process ;

コンペアマッチレジスタ定義

 フリーランカウンタの値と比較するためのレジスタを定義します。  外部から0〜99の値を設定しなければなりません。  パラレルで一気に8ビットデータを設定することもできますが  最近は、利用する信号線を減らす目的で、シリアルデータ転送を  するのが主流です。  8ビットのシフトレジスタを用意して、データを設定します。 process ( nRESET , SCLK ) begin if ( nRESET = '0' ) then iREG <= (others => '0'); elsif rising_edge( SCLK ) then -- set 1 bit data iREG <= iREG(6 downto 0) & SDAT ; end if ; end process ;

コンパレータ定義

 フリーランカウンタとコンペアマッチレジスタの値を比較する処理  と出力値を決定すればよいので、条件をwhenを利用して記述します。 iPWMOUT <= '1' when ( iCOUNTER < iREG ) else '0' ; PWMOUT <= iPWMOUT ;

全ソースコード

 ブロック図から3つのブロックを定義したので、まとめます。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity tstpwm is port ( -- system nRESET : in std_logic ; -- system reset CLOCK : in std_logic ; -- reference clock -- register signals SDAT : in std_logic ; -- serial data SCLK : in std_logic ; -- serial latch clock -- output PWMOUT : out std_logic --; ); end tstpwm; architecture Behavioral of tstpwm is signal iCOUNTER : std_logic_vector(7 downto 0) ; signal iREG : std_logic_vector(7 downto 0) ; signal iPWMOUT : std_logic ; begin -- free run counter process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCOUNTER <= (others => '0'); elsif rising_edge( CLOCK ) then -- increment iCOUNTER <= iCOUNTER + '1' ; -- counter reset if ( conv_integer( iCOUNTER ) = 100 ) then iCOUNTER <= (others => '0'); end if ; end if ; end process ; -- compare match register process ( nRESET , SCLK ) begin if ( nRESET = '0' ) then iREG <= (others => '0'); elsif rising_edge( SCLK ) then -- set 1 bit data iREG <= iREG(6 downto 0) & SDAT ; end if ; end process ; -- comparator iPWMOUT <= '1' when ( iCOUNTER < iREG ) else '0' ; PWMOUT <= iPWMOUT ; end Behavioral;  このソースコードで動作しますが、実用回路には少し難があります。  コンペアマッチレジスタの値を、フリーランカウンタが動作している  間に変更するので、そのときに出力値がおかしくなります。  DCモータであれば、慣性で回り続けるので、出力値が多少ふらついても  動作として観測されません。システムによっては、このままでも問題は  発生しないでしょう。  本質的な不具合修正は、ダブルバッファ構成を利用します。  フリーランカウンタの値が100から0にリセットされるときに  同時にコンペアマッチレジスタ値を更新します。  フリーランカウンタとコンペア処理の一部を変更します。 -- free run counter process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCOUNTER <= (others => '0'); iREGX <= (others => '0'); elsif rising_edge( CLOCK ) then -- increment iCOUNTER <= iCOUNTER + '1' ; -- counter reset if ( conv_integer( iCOUNTER ) = 100 ) then iCOUNTER <= (others => '0'); iREGX <= iREG ; end if ; end if ; end process ; -- comparator iPWMOUT <= '1' when ( iCOUNTER < iREGX ) else '0' ;  このPWM WaveGeneratorのソースコードは、XC9536で確認しました。  1回路だけであれば、XC9536で充分です。  DCモータに接続する場合は、CPLD/FPGAのドライブ能力では不足するので  パワートランジスタあるいはMOS-FETを利用します。

応用例1

 ルネサステクノロジー社が後援するMCR(Micom Car Rally)のマシン用に作った  2つのPWM WaveGeneratorを紹介します。  DCモータを2個利用して、左右のモータの回転数を変えて動かすようにしました。  左右のモータの回転数が同じであれば、直進します。  左右のモータの回転数に差があれば、右か左に方向を変えます。  フリーランカウンタを1個として、コンペアマッチレジスタを左右に各1個  用意して対応します。さらに、PWM波形を出力しないように、制御ピンを用意  します。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity tstpwm is port ( -- system nRESET : in std_logic ; -- system reset CLOCK : in std_logic ; -- reference clock -- register signals SDATL : in std_logic ; -- serial data left SDATR : in std_logic ; -- serial data right SCLK : in std_logic ; -- serial latch clock -- output PCON : in std_logic ; LEFT : out std_logic ; RIGHT : out std_logic --; ); end tstpwm; architecture Behavioral of tstpwm is signal iCOUNTER : std_logic_vector(7 downto 0) ; signal iREGL : std_logic_vector(7 downto 0) ; signal iREGR : std_logic_vector(7 downto 0) ; signal iREGLX : std_logic_vector(7 downto 0) ; signal iREGRX : std_logic_vector(7 downto 0) ; signal iLEFT : std_logic ; signal iRIGHT : std_logic ; begin -- free running counter process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCOUNTER <= (others => '0'); iREGLX <= (others => '0'); iREGRX <= (others => '0'); elsif rising_edge( CLOCK ) then -- increment iCOUNTER <= iCOUNTER + '1' ; -- counter reset if ( conv_integer( iCOUNTER ) = 100 ) then iCOUNTER <= (others => '0'); iREGLX <= iREGL ; iREGRX <= iREGR ; end if ; end if ; end process ; -- compare match register process ( nRESET , SCLK ) begin if ( nRESET = '0' ) then iREGL <= (others => '0'); iREGR <= (others => '0'); elsif rising_edge( SCLK ) then -- set 1 bit data iREGL <= iREGL(6 downto 0) & SDATL ; iREGR <= iREGR(6 downto 0) & SDATR ; end if ; end process ; -- comparator iLEFT <= '1' when ( iCOUNTER < iREGLX ) else '0' ; iRIGHT <= '1' when ( iCOUNTER < iREGRX ) else '0' ; LEFT <= iLEFT when ( PCON = '1' ) else '0' ; RIGHT <= iRIGHT when ( PCON = '1' ) else '0' ; end Behavioral;  マイコンから左右のDUTY比を同時に設定するときは、次のように  コードを記述します。 int i ; unsigned char left ; unsigned char right ; left = 35 ; right = 65 ; for ( i = 0 ; i < 8 ; i++ ) { /* set bit data */ SDATL = 0 ; SDATR = 0 ; if ( left & 0x80 ) { SDATL = 1 ; } if ( right & 0x80 ) { SDATR = 1 ; } /* 1 -> SCLK */ SCLK = 1 ; /* shift */ left <<= 1 ; right <<= 1 ; /* 0 -> SCLK */ SCLK = 0 ; } SDATL = 0 ; SDATR = 0 ;

応用例2

 サーボモータの制御は、PWMの応用になります。  DCサーボモータは、20msに一度1.0ms〜2.0msの時間長をもったパルス  を出力することで、軸を回転させて位置を保持します。  1.0msのパルス幅は必ず必要なので、残りの1.1msから2.0msを  0.01ms単位で出力してみます。  0.01msを最小分解能とすると、20msは2000カウントになります。  この最大カウント値から、フリーランカウンタのサイズは11ビット必要  になります。さらにクロックは、0.01ms=10usになるので100kHzになります。  PWMの出力値の条件をまとめてみます。
  1. 0〜99カウントまでは、出力は1
  2. 100〜199カウントまでは、コンペアマッチレジスタの値による
  3. 200カウント以上では、出力は0
 条件を記述してみます。 iPWMOUT <= '1' when ( conv_integer( iCOUNTER ) < conv_integer( iREG ) + 100 ) else '0' ; PWMOUT <= '1' when ( conv_integer( iCOUNTER ) < 100 ) else '0' when ( conv_integer( iCOUNTER ) > 199 ) else iPWMOUT ;  条件が確定したので、カウンタのフリーランとコンペアマッチレジスタの設定を  考えます。  フリーランカウンタは、2000カウント目で0に再設定すると同時に  コンペアマッチレジスタの値をコピーします。 process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCOUNT <= (others => '0') ; iREGX <= (others => '0') ; elsif rising_edge( CLOCK ) then -- increment iCOUNT <= iCOUNT + '1' ; -- reset counter if ( conv_integer( iCOUNT ) = 2000 ) then iCOUNT <= (others => '0') ; -- set compare match register iREGX <= iREG ; end if ; end if ; end process ;  コンペアマッチレジスタは、シフトレジスタを利用して実現します。 process ( nRESET , SCLK ) begin if ( nRESET = '0' ) then iREG <= (others => '0') ; elsif rising_edge( SCLK ) then -- set iREG <= iREG(6 downto 0) & SDAT ; end if ; end process ;  すべてのブロックの定義ができたので、ソースコードにまとめます。 --------------------------------------------------------------------- library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity tstservo is Port ( -- system nRESET : in std_logic ; -- system reset CLOCK : in std_logic ; -- reference clock -- set register value SDAT : in std_logic ; SCLK : in std_logic ; -- output PWMOUT : out std_logic --; ); end tstservo; architecture Behavioral of tstservo is signal iCOUNT : std_logic_vector(10 downto 0) ; signal iREG : std_logic_vector( 7 downto 0) ; signal iREGX : std_logic_vector( 7 downto 0) ; signal iPWMOUT : std_logic ; begin -- free run counter process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCOUNT <= (others => '0') ; iREGX <= (others => '0') ; elsif rising_edge( CLOCK ) then -- increment iCOUNT <= iCOUNT + '1' ; -- reset counter if ( conv_integer( iCOUNT ) = 2000 ) then iCOUNT <= (others => '0') ; -- set compare match register iREGX <= iREG ; end if ; end if ; end process ; -- compare match register process ( nRESET , SCLK ) begin if ( nRESET = '0' ) then iREG <= (others => '0') ; elsif rising_edge( SCLK ) then -- set iREG <= iREG(6 downto 0) & SDAT ; end if ; end process ; -- iPWMOUT <= '1' when ( conv_integer( iCOUNT ) < conv_integer( iREGX ) + 100 ) else '0' ; PWMOUT <= '1' when ( conv_integer( iCOUNT ) < 100 ) else '0' when ( conv_integer( iCOUNT ) > 199 ) else iPWMOUT ; end Behavioral; ---------------------------------------------------------------------  1個のサーボモータの制御にしていますが、複数のサーボモータを  制御したい場合には、PWM波形出力ピンとDUTY比を設定する入力ピン  を増やし、内部レジスタを必要なだけ用意します。  内部レジスタを増やすので、CPLDでは対応できるサーボモータは 4個程度です。 FPGAであれば、30個程度は簡単に制御できます。


目次 inserted by FC2 system