PSoC 3 で、 PWM を作った [PSoC]
今日のテーマは、車輪の再発明、 PWM を作ります。
分周器のおさらい
前回作成した「分周器」は、決められた数のクロックごとに "tc" 出力をアサートするものでした。 内部動作は、以下のタイミング図のようになっています。
分周器の内部では、カウンタの値が "0" になったかを示す "zero" が周期的にアサートされています。 "tc" 出力のタイミングは、通常動作時には、 "zero" と同時になります。 カウンタは、与えられた周期 (n) よりも1小さい値 (n-1) からデクリメントされていきますので、 "tc" 出力の周期は、 n と等しくなります。 実際の回路では、 "tc" をクロックでサンプリングすることにより、 LED 出力を反転しています。
PWM への応用
今回作成しようとしている PWM 出力は、分周器を応用することで作成することができます。 PWM の動作は、以下のタイミング図で示されます。
分周器に加えて、カウンタの値とパルス幅の値を比較する回路を取り入れると、 PWM 出力が得られます。 このタイミング図では、パルス幅を "2" と "3" に設定した場合を示しています。 カウンタの値がパルス幅の値(2または3)よりも小さい場合、ディジタル比較器の出力 less0 または less1 がアサートされています。 この信号をクロックでサンプリングすることにより、 PWM の出力 pwm0 および pwm1 が得られます。
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 に変更して展開すると再現できます。
関連書籍
シリーズ最強!PSoC 3ボード+デバッグ・ボード: あのPSoCが生まれ変わった!アナログもディジタルも一新 (トライアルシリーズ)
- 作者: 古平 晃洋
- 出版社/メーカー: CQ出版
- 発売日: 2012/10/23
- メディア: 単行本
コメント 0