CPLDによる多チャンネルパルスジェネレータ

はじめに

モータのPWM駆動やラジコンサーボの信号生成などでは 様々な周波数、デューティ比のパルスを生成する必要があります。 ところがPICやH8といったマイコンに内蔵されたパルスジェネレータでは すぐチャンネル数が足りなくなってしまいます。

CPLDを使用すれば、多チャンネルのパルスジェネレータを手軽かつ小さな回路規模で 作成することができます。 本稿では、XilinxのCPLD,XC95108を使用して Hブリッジ駆動用PWM信号3ch、サーボ用信号4chを生成する場合を例にとって説明します。

VHDLソースコード

要所だけ掲載・解説します。全ソースコードはこちらです。

入出力ピンと内部信号

ピン名説明
rstin std_logicリセット入力
clkin std_logicベースクロック
trigin std_logic読み込みトリガ
addr_datain std_logicアドレス/データ切替
datain std_logic_vector(7 downto 0)8ビットバス
srv_a〜srv_dout std_logicサーボ信号出力
pwm_a〜pwm_cout std_logic_vector(3 downto 0)Hブリッジ駆動出力

信号名説明
addrstd_logic_vector(2 downto 0)アドレス
cntstd_logic_vector(7 downto 0)8ビットカウンタ
phase_cntstd_logic_vector(2 downto 0)周期カウンタ
srv_cmp_a〜srv_cmp_dstd_logic_vector(7 downto 0)サーボ信号パルス幅
pwm_cmp_a〜pwm_cmp_cstd_logic_vector(3 downto 0)PWM信号パルス幅
pwm_drv_a〜pwm_drv_cstd_logicHブリッジOn/Off
pwm_dir_a〜pwm_dir_cstd_logicHブリッジ正逆転

PWM, サーボ信号生成プロセス

process(clk, rst) begin
    if(rst = '0') then
        -- リセット処理(省略)
    elsif(clk'event and clk = '1') then
        -- メインカウンタのインクリメント
        cnt <= cnt + 1;
        
        if(cnt = "11111111") then
            phase_cnt <= phase_cnt + 1;
        end if;

        if(cnt = "00000000") then
            -- サーボ信号の立ち上げ
            case phase_cnt is
                when "000" => if(srv_cmp_a /= "00000000") then srv_a <= '1'; end if;
                when "001" => if(srv_cmp_b /= "00000000") then srv_b <= '1'; end if;
                when "010" => if(srv_cmp_c /= "00000000") then srv_c <= '1'; end if;
                when "011" => if(srv_cmp_d /= "00000000") then srv_d <= '1'; end if;
                when others =>
            end case;

            -- PWM信号の立ち上げ
            if(pwm_cmp_a /= "0000") then pwm_drv_a <= '1'; end if;
            if(pwm_cmp_b /= "0000") then pwm_drv_b <= '1'; end if;
            if(pwm_cmp_c /= "0000") then pwm_drv_c <= '1'; end if;
        end if;

        -- サーボ信号の立ち下げ
        case phase_cnt is 
            when "000" => if(srv_cmp_a = cnt) then srv_a <= '0'; end if;
            when "001" => if(srv_cmp_b = cnt) then srv_b <= '0'; end if;
            when "010" => if(srv_cmp_c = cnt) then srv_c <= '0'; end if;
            when "011" => if(srv_cmp_d = cnt) then srv_d <= '0'; end if;
            when others =>
        end case;

        -- PWM信号の立ち下げ
        if(pwm_cmp_a = cnt(7 downto 4)) then pwm_drv_a <= '0'; end if;
        if(pwm_cmp_b = cnt(7 downto 4)) then pwm_drv_b <= '0'; end if;
        if(pwm_cmp_c = cnt(7 downto 4)) then pwm_drv_c <= '0'; end if;
    end if;
end process;
				
Fig.1 パルス生成の信号チャート

信号チャートをFig.1に示します。 ベースクロックclkには外部から100kHzを入力します。マイコンのクロック出力機能を使えばよいでしょう。 cntはclkによってカウントアップする8ビットアップカウンタです。

初めにHブリッジ信号から説明しましょう。 cntがオーバフローして0になった瞬間にpwm_xを立ち上げ、 cntがデュティ比指定信号pwm_cmp_xに一致した瞬間に対応するpwm_drv_xを立ち下げます。 clkが100[kHz]ですので、PWM信号の周波数は100[kHz] / 256 = 400[Hz]となります。

次にサーボ信号ですが、srv_xの各信号は一周期ずつずれてパルスを発生します
(これは、多数のサーボ信号を同時に立ち上げると、電圧降下によりサーボの制御回路が誤動作するという話を耳にしたからです。 アナログサーボは関係ないかも知れません)。 cntがオーバフローする度にphasecntがカウントアップします。 phasecntが0, 1, 2, 3の時に、それぞれsrv_a, srv_b, srv_c, srv_dがパルスを発生します。 phasecntが3ビットカウンタですので、サーボ信号の周期は100[kHz] / 256 / 8 = 50[Hz]となります。 また、生成されるパルスの幅は(srv_cmp_x / 100)[μs]となります。 srv_cmp_xは0〜255の値をとれますので、0.8[μs]や2.2[μs]といった「規格外の」パルス幅の信号を生成することも可能です。

PWM信号の論理変換

pwm_a(0) <= '1' when (pwm_drv_a = '1' and pwm_dir_a = '0') else '0';
pwm_a(1) <= '1' when (pwm_drv_a = '1' and pwm_dir_a = '1') else '0';
pwm_a(2) <= '1' when (pwm_drv_a = '1' and pwm_dir_a = '1') else '0';
pwm_a(3) <= '1' when (pwm_drv_a = '1' and pwm_dir_a = '0') else '0';        
				

従来はロジックICにさせていた論理変換もCPLDで行います。 これでCPLDの出力をHブリッジの各FETにつなぐだけでOK。 省スペース化にも貢献します。

マイコンとの通信プロセス

process(rst, trig) begin
    if(rst = '0') then
        -- リセット処理(省略)
    elsif(trig'event and trig = '1') then
        if(addr_data = '1') then
            -- アドレスを記憶
            addr <= data(2 downto 0);
        else
            case addr is
            when "000"  => srv_cmp_a <= data;
            when "001"  => srv_cmp_b <= data;
            when "010"  => srv_cmp_c <= data;
            when "011"  => srv_cmp_d <= data;
            when "100"  => pwm_dir_a <= data(4); pwm_cmp_a <= data(3 downto 0);
            when "101"  => pwm_dir_b <= data(4); pwm_cmp_b <= data(3 downto 0);
            when others => pwm_dir_c <= data(4); pwm_cmp_c <= data(3 downto 0);
            end case;
        end if;             
    end if;
end process;
				
Fig.2 通信プロセスの信号チャート

外部マイコンからの指令によってサーボ信号のパルス幅やHブリッジ信号のPWMデュティ比、正逆転を更新するためのプロセスです。 信号チャートをFig.2に示します。 まず8ビット単方向バス(命名がよくありませんね;;)に更新したい信号のアドレスが入力され、 トリガ信号trigの立ち上がりと共に内部信号addrに格納されます。 続いてdataに新しい値が入力されtrigの2度目の立ち上がりによってaddrに対応する信号が更新されます。 アドレスと対応する信号、データ構成を以下にまとめます。

アドレス信号データ構成
0サーボ信号Aパルス幅[μs] * 100
1サーボ信号B同上
2サーボ信号C同上
3サーボ信号D同上
4Hブリッジ信号A 第0〜3ビット→4ビット(16段階)PWMデュティ比
第4ビット→正逆転
5Hブリッジ信号B同上
6Hブリッジ信号C同上
おわりに

今回解説したVHDLソースコードをXC95108にインプリメントしたところ、使用マクロセル数は108中84となりました。 あと数チャンネル分追加することができそうです。 これより大幅にチャンネル数を増やしたい場合はより大規模なCPLDかFPGAを採用するべきでしょう。

今回XC95108を採用したのは、規模的に必要十分であったということに加えて、 PLCCパッケージのためソケットを使用することで容易にユニバーサル基板上に実装できたということもあります。 より大規模なCPLDやFPGAになると実装性や保守性においてリスクが増加してくることを気に留めておくべきでしょう。



トップへ戻る