今回は普通と違う使い方をする。何が普通じゃないかというと、PWMを使う目的が普通じゃない。

 普通は単にPWMを設定し、信号を出す、ということなんだが、今回やりたいのは、PWMで送出される信号のタイミングや切り替えなど、細かく予定通りに制御することを目指している。要は1kHzの信号を出す、というような単純なものではなく、その1kHzをどのタイミングから送出し、その後、どのタイミングで信号を変化せるか、など細かく制御する。

 PWMの設定は他サイトでも色々出ているので詳細は割愛する。まずは、CubeMxでの設定。

 マイコンの源クロックは64MHzにしている。そのため、Perscalerで1/64にすれば1MHzのクロックになる。しかし、設定値は64ではなく、1少ない63にする。ここ、間違えないようにしたい。次に、Counter ModeはUp(Downでもよい)。これは、カウントアップするということ。0から1、1から2と増えていく、という意味。Counter Periodは周期。例えば、999にすれば1MHzで1000カウント(ここも1少ない値になる。0から開始し、999までの1000カウントということ)、要は1ms周期になる。最後に重要なのがMode。ここではPWM mode 1にしている。これは、仕様書に以下のように説明されている。

PWM モード 1 – カウントアップ時、チャネル 1 は、TIMx_CNT < TIMx_CCR1 の場合はアクティブに、そうでない場合は非アクティブになります。 カウントダウン時、チャネル 1 は、TIMx_CNT>TIMx_CCR1 の場合は非アクティブ(OC1REF=“0”)に、そうでない場合はアクティブ(OC1REF=“1”)になります。

PWM モード 2 – カウントアップ時、チャネル 1 は、TIMx_CNT < TIMx_CCR1 の場合は非アクティブに、そうでない場合はアクティブになります。 カウントダウン時、チャネル 1 は、TIMx_CNT>TIMx_CCR1 の場合はアクティブに、そうでない場合は非アクティブになります。

 今、カウントアップ、モード1にしたので、タイマーカウンターの値がCCR1値(比較値)より小さければ出力は1、Highになり、そうでない場合は0、Lowになる。

 で、私としてはまずは2つのことを実現したい。PWM設定したポートの出力を固定化するにはどうしたらいいか?(要はHigh固定、Low固定にする)。色々試した結果、比較値で制御できることが分かった。

  __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0);

これにしておくと、出力はLow固定。逆に、

  __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 周期最大=Counter Period);

としておくと出力はHigh固定が実現できる。

 比較値(わかりずらいが、この値の設定値は1少ない値にはしない)が0の場合、TIMx_CNT < TIMx_CCR1(=0)は絶対成り立たず、よって出力は非アクティブ(Low)固定となる。逆に、比較値が周期最大になっている場合、CNTは0からCouter Period-1しか取らないため、常にTIMx_CNT < TIMx_CCR1が成り立ち、アクティブ(High)固定となる。ということで、汎用タイマーでも簡単に出力固定が出せる。ちなみに、Dutyを変えるにもこの比較値で対応できる。比較値が小さくなるとDutyが下がり、比較値を大きくすればDutyが上がる。つまりは、比較値0はDuty0%、比較値が周期最大でDuty100%になっている、ということは容易に理解できよう。

 さて、お次は好きなタイミングで信号を出す、という手法について。色々やってみたのだが、STM32のPWMはレベルセンスではなく、エッジセンスというか、イベントドリブンになっているようだ。値を設定して出力させても、イベントが起きないと出力が更新されない、という現象が発生していた。ここで最初に書いたコードを紹介する。

HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);

htim2.Init.Period = 周期カウント値-1;

HAL_TIM_Base_Init(&htim2);

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 周期カウント値/2); // duty 50%

__HAL_TIM_SET_COUNTER(&htim2, 0);

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

 例えば、64MHzを64分周したタイマーで、カウント値1000、1msの周期のパルスを出すことを考えたとする。上記ではPeriodに999を設定する。そして、COMPAREで比較値500を設定するとDutyが50%になる。これでタイマーをスタートさせると、果たしてその通りになるか?2波目以降はそうなるのだが、スタートは違う!0~500us間はHighではなく、Lowになっているのだ。比較値に対して、0(=1000)、500を通過しないと出力が更新されない。従って、最初の1ms間はずっとLowで、以降Duty50%、1ms周期のパルスになってしまい、最初だけ所望の出力が出ない。

 これを解決するのにいいものを見つけた。

htim2.Instance->EGR |= TIM_EGR_UG;

 これは何をしているかというと、イベント生成レジスタ(event generation register)に対して、更新生成(Update generation)を行う、というものらしい。仕様書によると、

ビット 0 UG:更新生成
このビットは、ソフトウェアによってセットでき、ハードウェアによって自動的にクリアされます。
0:影響なし。
1:カウンタを再初期化し、レジスタの更新を生成します。プリスケーラカウンタもクリアされます(プ
リスケーラ比は変化しません)。センターアラインモードが選択されている場合、または、DIR=0(カ
ウントアップ)の場合、カウンタはクリアされます。そうでない場合、DIR=1(カウントダウン)であ
れば、自動再ロード値(TIMx_ARR)をとります。

→ プリスケーラ―カウンタ及びカウンタがゼロクリアされるので、開始状態に戻るんですね

となっている。そこで、次のように書き直した。

HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);

htim2.Init.Period = 周期カウント値-1;

HAL_TIM_Base_Init(&htim2);

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 周期カウント値/2); // duty 50%

htim2.Instance->EGR |= TIM_EGR_UG;

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

 これにより、きちんとHigh、Lowと開始できるようになった。ちなみに、更新生成命令はStart命令文の一つ前ではなく、後の方がいいのではないかと思われるが、この順でもちゃんと動くのと、この方がタイミングが切替が早いようだ。

 さて、大分目標に近づいてきた。やろうとしているのは、例えば最初1kHzの波形を3波出し、その後100Hzの波形を1波出す、という動作。最初の命令は以下。

HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);

htim2.Init.Period = 999;

HAL_TIM_Base_Init(&htim2);

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 500); // duty 50%

htim2.Instance->EGR |= TIM_EGR_UG;

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

 問題は1kHzの3波の終了をどう判断し、その後100Hz信号をどのようにつなげるか、に掛かっている。モタモタしていると、1kHzの4波目のHighが出てきてしまうので、この間をどのようにスムースにつなげるか、考えないといけない。

 まずは、3波終了間際をカウント値で見極める。

__HAL_TIM_GET_COUNTER(&htim2)

この関数は、Timer2のカウント値がいくつになっているか読める関数。切り替え処理にある程度時間がかかることを想定し(これは計測でタイミングを調べる)、例えば980から981(この値はCPUやクロック、環境によって異なる)になる数を数え、3回目になった時点で次の動作を開始する。

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0); // force output Low

htim2.Init.Period = 9999;

HAL_TIM_Base_Init(&htim2);

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 5000); // duty 50%

__HAL_TIM_SET_COUNTER(&htim2, 9990);

 最初の1文は比較値を0にして出力をLowにしているが、ここ、要らないかもしれない。現時点で出力はLowであり、オーバーフローと比較値との比較結果の変化がないので値はLowのままと思われる。第2文は100Hzへの切り替え、第4文はDuty50%設定、最後はカウンタ値の設定となっている。これで、のこり10us後にHighに移行することになる(要は、1kHzのPWMを20us前で終了し、この5つの命令を実行するのに10us程度想定し、100Hzを10us前、言い換えると1周期終了10us前に設定)。カウンタ値は、処理速度やタイミングで値を調整必要である。綺麗に3波出なくてもいいのであれば、この辺りはミスをしないよう、値を調整可能である。

 これで、何とか所望の波形を送出することができるようになった!