PSoC 4200 Prototyping Kit (2)

多くのマイコンの PWM 回路ではコンペア・レジスタは「ダブル・バッファ」構成になっています。
もちろん、その理由はダブル・バッファ構成でないと問題が生じるからです。
簡単のため、カウンタがアップ・カウント・モードでの PWM 回路の原理的な図を下に示します。
レジスタの名称は PSoC4 の TCPWM コンポーネントでの呼び方に従っています。

実際にはピリオド・レジスタもダブル・バッファ構成になっている場合が多いのですが、簡単のため、タイマ動作中はピリオド・レジスタが変更されることはないとして、ピリオド・レジスタについてはダブル・バッファ構成ではないものとします。
まず、図の上半分の PWM 波形の周期を決めている部分の説明をします。
タイマ・カウンタはアップ・カウント・モードで動作しているので、カウントは 0 から始まります。
一方、カウンタの最大カウント値をピリオド・レジスタに設定しておきます。
ピリオド・レジスタに設定された最大カウント値は、一致検出回路により常時タイマ・カウント値と比較されており、一致すると TC (Terminal Count) 信号が発生します。
ここが「現」PWM 周期の最終カウントであり、この TC 信号により次のクロック・タイミングでタイマ・カウンタをリセット (図では一般性を持たせて「Reload」と表現) し、カウント値をゼロに戻して次の PWM 周期を開始させます。
図の下半分が PWM 出力生成部で、コンペア・レジスタに設定された値と、タイマ・カウンタの値を常時比較し、一致するとコンペア・マッチ信号が発生します。
PWM 出力コントロール部で、TC 信号とコンペア・マッチ信号とから PWM 出力波形を作りだします。
コンペア・レジスタの値は常時比較されているので、ひとつの PWM 周期の間では一定に保っておく必要があります。
もし、任意のタイミングでコンペア・レジスタの値を変更すると、正しくない PWM 波形が出力される可能性があります。
たとえば、PWM 周期が 500 で、コンペア・レジスタの値が 300 とします。
タイマ・カウンタ値が 200 まで進んだ時点で、コンペア・レジスタを 100 に書き換えたとします。
その場合に本来 PWM 波形が変化すべきカウント値 100 の時刻はすでに過ぎてしまっていますから、その時点で正しくない波形であることが確定します。
また、それ以降のどのタイマ・カウント値とも一致しませんから、現 PWM 周期ではコンペア・マッチ信号が全く発生せず、PWM 波形としては意図していないものになります。
このような問題が生じないためには、ひとつの PWM 周期の始め (カウント 0) から周期の終わり (最大カウント) までの間、コンペア・レジスタの値を一定に保つ必要があります。
コンペア・レジスタの値を変更するタイミングとして最適なのが、タイマ・カウンタが最大カウントから 0 にリセット (リロード) されるタイミングを表す TC 信号を利用することです。
マイコン側からアクセスする「コンペア・バッファ・レジスタ」を設け、マイコン側からは任意のタイミングでコンペア値を書き込めるようにします。
「コンペア・バッファ・レジスタ」から「コンペア・レジスタ」への転送を、上の図では破線の矢印で示してあります。
このダブル・バッファの構成がデフォルトになっているマイコンが多く、Atmel ATmega のカウンタ/タイマ・モジュールでは「アウトプット・コンペア・レジスタ」として一体化されており、タイマ・カウントと常時比較される側のレジスタにはアクセスできないようになっています。 また、ダブル・バッファ構成を解除することもできません。
STmicro の STM32F シリーズのタイマでも、同様にダブル・バッファは解除できず、常時比較されるレジスタは「シャドウ・レジスタ」と呼ばれてマイコンからはアクセスできず、マイコンがアクセスできるバッファ・レジスタは「プリロード・レジスタ」と呼ばれています。
NXP の LPC シリーズでは、汎用タイマの PWM 機能についてはハード的にシングル・バッファ構成となっていて、ダブル・バッファにすることはできません。
独立した PWM モジュールや、SCT (State Configurable Timer) ではダブル・バッファ構成になっています。
SCT では、常時比較される方が MATCH レジスタと呼ばれ、バッファ側が MATCHREL (Match Reload の意) レジスタと呼ばれています。 そして、タイマ動作中はマイコンからの MATCH レジスタへの書き込みは禁止されます。
ルネサスの H8 シリーズ、SH シリーズ、RX シリーズなどの MTU (マルチファンクションタイマパルスユニット) では、ダブル・バッファ、シングル・バッファどちらの構成も可能になっています。 ただし、ダブル・バッファ構成にすると PWM 出力の本数が減ってしまいます。
PSoC4 の TCPWM コンポーネントの構成を下に示します。

「PSoC4 Architecture TRM (Technical Reference Manual)」 の TCPWM の項を読むと、コントロールレジスタの「AUTO_RELOAD_CC」ビットフィールドに「1」を立てると、上で説明したようなダブル・バッファ・モードになると解釈できるような記述がありますが、そのビットフィールド位置については記述がありません。
レジスタ単位で記述してある「PSoC4 Registers」 TRM (Technical Reference Manual)」 の TCPWM の項には、4 つのインスタンスに同時にスタート/ストップ等を指示する共通のコントロールレジスタ「TCPWM_CTRL」については記述がありますが、個々のインスタンスに対する「TCPWM_CNTx_CTRL」レジスタ類に対しては、全く記述がありません。
PSoC Creator でも、他のマイコンのようなダブル・バッファ・モードとして機能させるような設定は見当たりません。
その代わりに、PSoC4 の TCPWM コンポーネントではコンペア・レジスタとコンペア・バッフア・レジスタとの間で、お互いの内容を交換する「スワップ」(Swap) 機能があります。
スワップ」が実行されると、もとのコンペア・レジスタの内容がコンペア・バッファ・レジスタに入り、もとのコンペア・バッファ・レジスタの内容がコンペア・レジスタに入ります。
お互いの内容が交換されるだけなので、もともと 2 種あった内容はどちらも失われることはありません。
上の図で破線の矢印で表現しているのがスワップです。
このスワップの実行には次のような条件が必要です。

  • コントロールレジスタ内のスワップを指定するビットが「1」
  • ハードウェアあるいはソフトウェアの「スイッチ」(switch) イベントが発生

現 PWM 周期内でのスイッチ・イベントの発生は記憶され、次の TC (Terminal Count) まで保持されます。 そして、TC のタイミングでスワップの実行が開始されます。
スワップ実行後は、記憶されていたスイッチ・イベントはクリアされ、新たなスイッチ・イベントが入らなければ、その次の TC ではスワップは実行されません。
つまり、毎回スワップさせるためには、毎回 PWM 周期のどこかでハードウェア/ソフトウェア・スイッチ・イベントを発生させる必要があります。
上の図では省略していますが、実際にはピリオド・レジスタとピリオド・バッファ・レジスタとの間のスワップ機能もあります。
これを利用すれば、PWM 周期および PWM デューティーについて、あらかじめ 2 つの値を設定しておくことができ、それらを外部からはハードウェア・スイッチ・イベントで、内部からはソフトウェア・スイッチ・イベントで切り替えることができます。
ダブル・バッファ・モードとして使うには、TC の割り込みをトリガとして PWM デューティー値を変更する際に、その値をコンペア・バッファ・レジスタの方に書き込み、(ソフトウェアのみで対応する場合には) さらにソフトウェア・スイッチ・イベントを発生させます。
つぎの TC でふたつのレジスタの内容が入れ替わりますが、コンペア・バッファ・レジスタに入ってくる前のコンペア・レジスタの値には関心がなく、TC 割り込みハンドラで次のサンプルの値を書き込んでしまうので、もとの値は捨てられてしまいます。
ソフトウェアで対応する場合の main() 関数の記述を下に示します。

int main()
{
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    
    PWM0_Start();
    PWM0_SetCompareSwap(1);
    PWM0_ISR_Start(); 
    Opa0_Start();
    CyGlobalIntEnable;  /* Uncomment this line to enable global interrupts. */
    for(;;)
    {
        /* Place your application code here. */
    }
}

何々_Start() 関数は、配置したコンポーネントインスタンスを初期化/起動するための API 関数で、

です。
PWM0_SetCompareSwap(1) はコンペア・レジスタスワップを有効にするための設定です。
PWM の TC 割り込みハンドラを下に示します。
interrupt コンポーネントインスタンス名を「PWM0_ISR」としたので、自動生成されるファイル名は「PWM0_ISR.c」となります。 そのファイル中に次のような記述を書き込みます。

/*******************************************************************************
*  Place your includes, defines and code here 
********************************************************************************/
/* `#START PWM0_ISR_intc` */
    
#include <PWM0.h>  

/* `#END` */

 . . . . . <中略> . . . . .

CY_ISR(PWM0_ISR_Interrupt)
{
    /*  Place your Interrupt code here. */
    /* `#START PWM0_ISR_Interrupt` */

  static volatile int32_t sin_acc = 0;
  static volatile int32_t cos_acc = 0x7c000000L;
  #define ACC_SHIFT (5)
    
  if (PWM0_INTR_MASK_TC & PWM0_GetInterruptSource()) {
// Terminal Count interrupt occurred, clear source    
    PWM0_ClearInterrupt(PWM0_INTR_MASK_TC);
// DDA calculation    
    sin_acc += (cos_acc >> ACC_SHIFT);
    cos_acc -= (sin_acc >> ACC_SHIFT);
// output to Compare Buffer register        
    PWM0_WriteCompareBuf((0x80000000U ^ (uint32_t)sin_acc) >> 23);
// issue software switch event
    PWM0_TriggerCommand(PWM0_MASK, PWM0_CMD_CAPTURE);
  } // if (PWM0_INTR_MASK_TC & ...
    /* `#END` */
}

(外部) interrupt コンポーネントは接続するコンポーネントとは直接の関係がないので、PWM0 の API 関数を使うために「PWM0.h」のヘッダファイルをインクルードしておきます。 「PWM0_ISR.c」ファイルは自動生成なので、指定の場所に書いておかないと回路の構成を変更して再ビルドした場合に追加した記述が消されてしまいます。
通常、信号発生はメイン・ループで行い、割り込みハンドラには FIFO (リング・バッファ) を介してデータを渡す構成にしますが、DDA (Digital Differential Analyzer) によるサイン/コサイン波形発生は処理が軽いので、割り込みハンドラ内で波形発生を行っています。

    PWM0_TriggerCommand(PWM0_MASK, PWM0_CMD_CAPTURE);

がソフトウェア・スイッチ・イベントのトリガです。
キャプチャ・モードでのキャプチャ・イベント/キャプチャ入力がコンペア・モードでのスイッチ・イベント/スイッチ入力になります。
API 関数名や #define のシンボル名としては「スイッチ」は登場しないので、「キャプチャ」が名称に付くシンボルを使用します。
以上のような対策を施して得られた「正常」な波形の写真を下に示します。