SSブログ

PSoC 3 で、 PWM を作った [PSoC]このエントリーを含むはてなブックマーク#

今日のテーマは、車輪の再発明、 PWM を作ります。

分周器のおさらい

分周器

前回作成した「分周器」は、決められた数のクロックごとに "tc" 出力をアサートするものでした。 内部動作は、以下のタイミング図のようになっています。


分周器のタイミング図

分周器の内部では、カウンタの値が "0" になったかを示す "zero" が周期的にアサートされています。 "tc" 出力のタイミングは、通常動作時には、 "zero" と同時になります。 カウンタは、与えられた周期 (n) よりも1小さい値 (n-1) からデクリメントされていきますので、 "tc" 出力の周期は、 n と等しくなります。 実際の回路では、 "tc" をクロックでサンプリングすることにより、 LED 出力を反転しています。

PWM への応用

PWM出力

今回作成しようとしている PWM 出力は、分周器を応用することで作成することができます。 PWM の動作は、以下のタイミング図で示されます。


PWM のタイミング図

分周器に加えて、カウンタの値とパルス幅の値を比較する回路を取り入れると、 PWM 出力が得られます。 このタイミング図では、パルス幅を "2" と "3" に設定した場合を示しています。 カウンタの値がパルス幅の値(2または3)よりも小さい場合、ディジタル比較器の出力 less0 または less1 がアサートされています。 この信号をクロックでサンプリングすることにより、 PWM の出力 pwm0 および pwm1 が得られます。

Verilog での実装

Verilog での実装

最初は、 Verilog で記述します。 分周器の出力 "tc" の代わりに "pwm1" と "pwm2" が出力されています。

//`#start header` -- edit after this line, do not edit this line
// ========================================
//
// Copyright noritan.org, 2013
// All Rights Reserved
// UNPUBLISHED, LICENSED SOFTWARE.
//
// CONFIDENTIAL AND PROPRIETARY INFORMATION
// WHICH IS THE PROPERTY OF NORITAN.ORG.
//
// ========================================
`include "cypress.v"
//`#end` -- edit above this line, do not edit this line
// Generated on 04/29/2013 at 17:34
// Component: PwmUdb
module PwmUdb (
	output  pwm0,
	output  pwm1,
	input   clock,
	input   en,
	input   reset
);
	parameter DUTY0 = 30;
	parameter DUTY1 = 60;
	parameter PERIOD = 100;

//`#start body` -- edit after this line, do not edit this line

    wire[7:0]   count;      // counter value
    wire        zero;       // count == 0
    wire        less0;      // count < DUTY0
    wire        less1;      // count < DUTY1
    
    // Divide counter behavior
    reg[7:0]    count_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            count_reg <= (PERIOD - 1);
        end else if (!en) begin
            count_reg <= (PERIOD - 1);
        end else if (zero) begin
            count_reg <= (PERIOD - 1);
        end else begin
            count_reg <= count - 1;
        end
    end
    
    // PWM0 output
    reg     pwm0_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            pwm0_reg <= 1'b0;
        end else if (!en) begin
            pwm0_reg <= 1'b0;
        end else begin
            pwm0_reg <= less0;
        end
    end
    
    // PWM1 output
    reg     pwm1_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            pwm1_reg <= 1'b0;
        end else if (!en) begin
            pwm1_reg <= 1'b0;
        end else begin
            pwm1_reg <= less1;
        end
    end
    
    // Signal assignment
    assign      count = count_reg[7:0];
    assign      pwm0  = pwm0_reg;
    assign      pwm1  = pwm1_reg;
    assign      zero  = (count[7:0] == 8'd0);
    assign      less0 = (count[7:0] < DUTY0);
    assign      less1 = (count[7:0] < DUTY1);

//`#end` -- edit above this line, do not edit this line
endmodule
//`#start footer` -- edit after this line, do not edit this line
//`#end` -- edit above this line, do not edit this line

部品のシンボルが、窮屈になったので、イネーブル入力を "enable" から "en" に変更しましたが、機能は同じです。 "tc" 出力を生成する代わりに、 "pwm0" と "pwm1" を生成するフリップフロップが配置されています。

この動作記述をご覧になってお分かりのように、 PWM の周期もデューティも通常の PWM が可変であるのに対して固定となっています。 この設定では、 "pwm0" が 30%、 "pwm1" が 60% のデューティーを持っています。

テストプログラムは、以下のようになっています。

/* ========================================
 *
 * Copyright noritan.org, 2013
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF NORITAN.ORG.
 *
 * ========================================
*/
#include <device.h>

void main()
{
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    CyDelay(11);
    CR1_Write(1);

    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    for(;;)
    {
        /* Place your application code here. */
    }
}

/* [] END OF FILE */

初期化部分で、 "en" 出力をアサートしたら、おしまいです。 初期化の前にディレイが入っていますが、これは、 PwmUdb ブロックにクロックを与えて確実に初期化するためです。

PwmUdb ブロックは、非同期な "reset" 入力を持っていますが、慣例に従って、 "0" を接続されているので、使用されていません。 ところが、パワーオンリセット状態では、カウンタの値が "0" になっており、 "reset" を与えた時のリセット状態と異なっています。 そのため、リセット直後、一瞬だけ PWM 出力がアサートされてしまう現象が発生しました。

このブロックでは、 "!en" が同期リセットと同様の効果を持っているため、クロックの立ち上がりが入ったであろう時間を待ち合わせて、ブロックのイネーブルをしています。

データパスで書いた PWM

データパスによる実装

今回も、 Verilog 記述の部分をデータパスで書き換えます。

//`#start header` -- edit after this line, do not edit this line
// ========================================
//
// Copyright noritan.org, 2013
// All Rights Reserved
// UNPUBLISHED, LICENSED SOFTWARE.
//
// CONFIDENTIAL AND PROPRIETARY INFORMATION
// WHICH IS THE PROPERTY OF NORITAN.ORG.
//
// ========================================
`include "cypress.v"
//`#end` -- edit above this line, do not edit this line
// Generated on 04/29/2013 at 18:13
// Component: PwmDp
module PwmDp (
	output  pwm0,
	output  pwm1,
	input   clock,
	input   en,
	input   reset
);

//`#start body` -- edit after this line, do not edit this line

    localparam  CS_LOAD = 3'd0;
    localparam  CS_DECREMENT = 3'd1;
    
    wire[2:0]   addr;
    wire        zero;       // A0 == 0 (z0)
    wire        less0;      // A0 < D0 (cL0)
    wire        less1;      // A0 < D1 (cL1)

    // Specify DATAPATH behavior
    assign      addr = (zero|~en)?(CS_LOAD):(CS_DECREMENT);

    // PWM0 output
    reg     pwm0_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            pwm0_reg <= 1'b0;
        end else if (!en) begin
            pwm0_reg <= 1'b0;
        end else begin
            pwm0_reg <= less0;
        end
    end
    
    // PWM1 output
    reg     pwm1_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            pwm1_reg <= 1'b0;
        end else if (!en) begin
            pwm1_reg <= 1'b0;
        end else begin
            pwm1_reg <= less1;
        end
    end
    
    // Signal assignment
    assign      pwm0  = pwm0_reg;
    assign      pwm1  = pwm1_reg;

cy_psoc3_dp8 #(.cy_dpconfig_a(
{
    `CS_ALU_OP_PASS, `CS_SRCA_A1, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC__ALU, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM0: LOAD A1 into A0*/
    `CS_ALU_OP__DEC, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC__ALU, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM1: DECREMENT A0*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM2: */
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM3: */
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM4: */
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM5: */
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM6: */
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM7: */
    8'hFF, 8'h00,  /*CFG9: */
    8'hFF, 8'hFF,  /*CFG11-10: */
    `SC_CMPB_A1_D1, `SC_CMPA_A0_D1, `SC_CI_B_ARITH,
    `SC_CI_A_ARITH, `SC_C1_MASK_DSBL, `SC_C0_MASK_DSBL,
    `SC_A_MASK_DSBL, `SC_DEF_SI_0, `SC_SI_B_DEFSI,
    `SC_SI_A_DEFSI, /*CFG13-12: */
    `SC_A0_SRC_ACC, `SC_SHIFT_SL, 1'h0,
    1'h0, `SC_FIFO1_BUS, `SC_FIFO0_BUS,
    `SC_MSB_DSBL, `SC_MSB_BIT0, `SC_MSB_NOCHN,
    `SC_FB_NOCHN, `SC_CMP1_NOCHN,
    `SC_CMP0_NOCHN, /*CFG15-14: */
    10'h00, `SC_FIFO_CLK__DP,`SC_FIFO_CAP_AX,
    `SC_FIFO_LEVEL,`SC_FIFO__SYNC,`SC_EXTCRC_DSBL,
    `SC_WRK16CAT_DSBL /*CFG17-16: */
}
)) dp(
        /*  input                   */  .reset(reset),
        /*  input                   */  .clk(clock),
        /*  input   [02:00]         */  .cs_addr(addr),
        /*  input                   */  .route_si(1'b0),
        /*  input                   */  .route_ci(1'b0),
        /*  input                   */  .f0_load(1'b0),
        /*  input                   */  .f1_load(1'b0),
        /*  input                   */  .d0_load(1'b0),
        /*  input                   */  .d1_load(1'b0),
        /*  output                  */  .ce0(),
        /*  output                  */  .cl0(less0),
        /*  output                  */  .z0(zero),
        /*  output                  */  .ff0(),
        /*  output                  */  .ce1(),
        /*  output                  */  .cl1(less1),
        /*  output                  */  .z1(),
        /*  output                  */  .ff1(),
        /*  output                  */  .ov_msb(),
        /*  output                  */  .co_msb(),
        /*  output                  */  .cmsb(),
        /*  output                  */  .so(),
        /*  output                  */  .f0_bus_stat(),
        /*  output                  */  .f0_blk_stat(),
        /*  output                  */  .f1_bus_stat(),
        /*  output                  */  .f1_blk_stat()
);

//`#end` -- edit above this line, do not edit this line
endmodule
//`#start footer` -- edit after this line, do not edit this line
//`#end` -- edit above this line, do not edit this line

カウンタ部分の実装に、データパスを使っています。 "zero" でデクリメントの終端を検知するのは、これまでと同様ですが、今回は、ディジタル比較器の出力 "less0" と "less1" を取り出して、 PWM 出力の制御に利用しています。

データパスの設定

データパスは、これまでと同様に "LOAD" と "DECREMENT" の設定だけを定義しています。 ただし、レジスタの使い方が、これまでと異なっています。

これまでは、周期パラメータを "D0" に格納しておき、 "LOAD" 設定でカウンタ "A0" に値を直接入れていました。 ところが、今回は、2つの PWM 出力を得るために、カウンタ "A0" と "D0" および "D1" の値を比較する必要があります。 そのため、 "D0" を周期パラメータを格納するために使用することができません。 そこで、 "LOAD" 状態では、 "A1" に格納された周期パラメータを "ALU" を通して "A0" に入れています。


比較器の設定

データパスに内蔵されているディジタル比較器のうち、一つは、 "A0" と "D0" を比較する機能を持っています。 もう一方は、比較対象を選ぶことができます。 今回は、 "A0" と "D1" を比較したいので、データパスの設定で "CMP SEL" を "CFGA" に設定して、比較器の設定で "CMP SELA" を "A0_D1" としています。 これで、2つの比較器出力が個別に得られるようになりました。

テストプログラムは、以下の通りです。

/* ========================================
 *
 * Copyright noritan.org, 2013
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF NORITAN.ORG.
 *
 * ========================================
*/
#include <device.h>

void main()
{
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    CY_SET_REG8(Pwm_dp_u0__A0_REG, 99);
    CY_SET_REG8(Pwm_dp_u0__A1_REG, 99);
    CY_SET_REG8(Pwm_dp_u0__D0_REG, 30);
    CY_SET_REG8(Pwm_dp_u0__D1_REG, 60);
    CR1_Write(1);

    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    for(;;)
    {
        /* Place your application code here. */
    }
}

/* [] END OF FILE */

これまでと同様に、 API を作成せず、直接データパス内のレジスタを初期化してから、 "en" をアサートしています。 "A1" に格納されている周期と同時に、カウンタ "A0" の値も初期化されています。 これは、 Verilog で記述した時と同様に、初期状態のレジスタの値が "0" にリセットされてしまうため、一瞬だけパルスが発生してしまう事態を防ぐためです。

消費資源の比較

今回も Verilog を使用した場合とデータパスを使用した場合で、消費資源を比較してみました。

Verilog での実装
Resource Type                 : Used : Free :  Max :  % Used
============================================================
Digital domain clock dividers :    1 :    7 :    8 :  12.50%
Analog domain clock dividers  :    0 :    4 :    4 :   0.00%
Pins                          :    5 :   67 :   72 :   6.94%
UDB Macrocells                :   11 :  181 :  192 :   5.73%
UDB Unique Pterms             :   29 :  355 :  384 :   7.55%
UDB Total Pterms              :   30 :      :      : 
UDB Datapath Cells            :    0 :   24 :   24 :   0.00%
UDB Status Cells              :    0 :   24 :   24 :   0.00%
UDB Control Cells             :    1 :   23 :   24 :   4.17%
            Control Registers :    1 
DMA Channels                  :    0 :   24 :   24 :   0.00%
Interrupts                    :    0 :   32 :   32 :   0.00%
データパスでの実装
Resource Type                 : Used : Free :  Max :  % Used
============================================================
Digital domain clock dividers :    1 :    7 :    8 :  12.50%
Analog domain clock dividers  :    0 :    4 :    4 :   0.00%
Pins                          :    5 :   67 :   72 :   6.94%
UDB Macrocells                :    3 :  189 :  192 :   1.56%
UDB Unique Pterms             :    3 :  381 :  384 :   0.78%
UDB Total Pterms              :    3 :      :      : 
UDB Datapath Cells            :    1 :   23 :   24 :   4.17%
UDB Status Cells              :    0 :   24 :   24 :   0.00%
UDB Control Cells             :    1 :   23 :   24 :   4.17%
            Control Registers :    1 
DMA Channels                  :    0 :   24 :   24 :   0.00%
Interrupts                    :    0 :   32 :   32 :   0.00%

Verilog を使用した場合、ディジタル比較器が合成されているため、約10倍もの積項("Pterm")が使われているのがわかります。 この実装では、定数との比較しか行っていないにも関わらす、これだけの資源を消費していますから、周期とデューティーを可変にしたら、もっと多くの資源が消費される事が予想されます。

プロジェクトアーカイブ

この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。

関連書籍


nice!(0)  コメント(0)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。