目次
前
次
PWM(Pulse Width Modulation)WaveGenerator
CPLD/FPGAを利用して、PWM(Pulse Width Modulation)WaveGeneratorを作成してみます。
PWMが、どういう用途で利用されているかをリストします。
- DCモータのスピード制御
- DCサーボモータ制御
- D/Aコンバータ
- AC電源制御(調光)
- DC−DCコンバータ
この他にもありますが、自分が試した内容は、リストの通りです。
自分で最初に作った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の出力値の条件をまとめてみます。
- 0〜99カウントまでは、出力は1
- 100〜199カウントまでは、コンペアマッチレジスタの値による
- 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個程度は簡単に制御できます。
目次
前
次