タクテク2
昭和歌謡好き大学院生の雑記

ARMマイコン内蔵タイマでPWM出力

1:まずはデータシート+ユーザーマニュアル

前回、マイコンの開発環境及びOLEDの基本的な使い方を確認したエアロバイク型発電機 制作企画の第3回。今回は、制御部で必要となるマイコンによるゲート駆動のためのPWM出力の使い方を、ステップバイステップで確認します。

LPC1114 データシート

 とは言っても、正直なところ中身の見えないICチップを前に、何をすればいいのか分からないというのが本音です。そんなときはまずマイコンのデータシートとユーザーマニュアルを見ます。データシートはマイコンの機能や性能、ピンアサインの説明で、ページ数は少なめ(といっても127ページありますが)。対して、ユーザーマニュアルはマイコンの具体的な使い方を説明しており、ページ数は500ページを超えています。というわけでまずはデータシートを見てどんな機能があるのかを簡単に確認しましょう

LPC1114 ブロックダイアグラム

 データシートには、マイコンの機能をブロックで表したブロックダイアグラムがあり、マイコンの中に何が入っているのかを図で確認できます。制御回路では電圧測定のためのADCとPWM出力を使いたいわけですが、ダイアグラムを見ると10-bit ADCが8チャンネル入っていることがわかるので、これを使えば電圧が測定できそうです。しかし、PWMの文字がありません。

LPC1114 ユーザーマニュアル

 というわけで、次はユーザーマニュアルを見ます。500ページ以上あるので流石に端から読み進めるのは非現実的ですから、「PWM」とか「Pulse Width」とかで検索をかけます。

ユーザーマニュアル PWM説明

 すると、内蔵タイマの項目に「Pulse Width Modulator via match outputs」の文字を発見。どうやら内蔵タイマでPWM出力ができるそうです。筆者はPWMといえばArduino以外で使ったことがないので知らなかったのですが、専用PWMコントローラを使うことなく、タイマ出力との比較で簡単にPWM波形が生成できるみたいです。詳細は「PWM タイマ」とかでググってください。いずれにせよ、これで必要な機能はいずれも内蔵されていることが分かりましたので、コーディングに移っていきます。

2:「構造体へのポインタ」を理解しようとする

CT16B0_MAT1 ピンアサイン

 というわけで、PWM出力の方法を確認していきます。まず初めにやることは出力ポートの決定です。今回は目的がFETのゲート駆動である点を考慮して16ビットタイマB0のマッチ出力1(CT16B0_MAT1)を使用することに決定。

IOCONレジスタ

 このピンはPIO0_9なので、ユーザーマニュアルのPIOポート設定用レジスタである「IOCON register」の項目を確認します。すると、表が示されているので目的のポート(PIO0_9)を探し、リンクをクリックします。

PIO0_9 設定用レジスタ

 すると、遂にPIO0_9ポートの設定用レジスタの説明が現れました。というわけで、この表に従ってPIO0_9レジスタを設定していきます。とは言っても1から書いていくのは面倒ですし、冗長なコードになりそうなのでサンプルプログラムを参照します。

 そして、サンプルプログラムの該当箇所がこちら。

LPC_IOCON->R_PIO1_1 = 0xc3;

 要はLPC_IOCONレジスタの中のR_PIO1_1レジスタを0xc3に設定するよ、といっているわけなので、これを書き換えればOK…なのですが、筆者は思いました。

いや、「->」って何?

 プログラミング初心者である筆者にとって、こんな演算子は見たことありません。「これはマイナスと大なり、2つの演算子なのか、それとも一つなのか」などと考えながら調べると、これはどうやら構造体を指し示すポインタのようです。「構造体」と「ポインタ」、明らかに手強そうな単語が並んでいて挫折しそうになりますが、こんな時はCQ出版です。残念ながら重版未定となっていますが、こちらの「やさしい組み込みCプログラミング」で確認します。「組み込みC」という手強そうな内容をやさしく教えてくれる、なかなか便利な一冊です。

インターフェースZERO No.01: やさしい組み込みCプログラミング

新品価格
¥3,480から
(2021/6/20 11:15時点)

 詳しいことは専門書に任せるとして、「x->a」はどうやら「(*x).a」の省略形のようで、「構造体xへのポインタで、構造体中の要素aを指し示す」ということのようです。構造体は単に複数のデータをひとまとめにする入れ物のようなものです。なんか分かったような分からないような…
 まあ、サンプルプログラムをパクればいいでしょう(諦め)。

 というわけで、コーディングしていきます。まずはポート設定用レジスタに代入する値を決定しましょう。これは、先程確認したユーザーマニュアルに各項目の意味が説明されているのでそれに従って決定していきます。

 まずは下位0~2bitはピンの機能を決定するFUNC。今回は16bitカウンタとして使いたいのでCT16B0_MAT1(0x02,バイナリで10)を選択。

 次に3~4bitはピンのプルアップモードを決定するMODE。プルアップ、プルダウン、リピータ(何これ?)とかがありますが今回は単純な出力端子として使うのでInactive(0x0)を選択。

 5bit目はヒステリシス。スイッチのチャタリング防止機能でしょうか、よくわかりませんが必要ないのでDisable(0)を選択。

 これより上位のビットは予約済みとGPIO関連のみで使用しないのですべて0としておきます。そして、以上のビットをすべて下から順に並べると今回IOCON_PIO0_9に代入すべき値となります。

00010=2

結局FUNC以外はすべて0でした。というわけで、

`LPC_IOCON->R_PIO0_9 = 0x02;`

これでピンの設定は終了。次に16bitタイマーの設定に移ります。

3:ステップバイステップでレジスタを設定する

ahbclock 設定

 PWM出力への道のみはまだまだです。というわけで次はPWM出力の基となる16bitタイマの設定を行っていきます。まずは初期状態ではタイマにクロックが供給されていないので、今回使うタイマ(CT16B0)にクロックを供給するように設定します。設定はAHBCLKCTRLレジスタで行います。AHBというのは内部バスの名前です。先程のピン設定と同様にユーザーマニュアルを参照し、CT16B0に該当するレジスタを見つけます。CT16B0にクロックを供給するにはAHBCLKCTRLレジスタの7bit目を1にすればよいのですが、7bit目以外は変えたくないのでOR演算を用いて

LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);

と記述します。これでクロックがタイマに供給されるようになりました。ちなみに、クロックは初期設定で48MHzです。クロックを供給したら次はタイマのカウント関連の設定を行うわけですが、ここからカウントをリセットするタイミング及びマッチ出力を行うタイミング、更には割り込みハンドラの設定を行う必要があります。気が遠くなる…

 まずはタイマをリセットするタイミングを指定します。これは同時にPWMのキャリア周波数にもなります。タイマのリセット方法は、マニュアルやサンプルプログラムを眺めるとどうやらMatch Control Register(MCR)で設定したマッチレジスタが1になったらリセットされるという仕組みのようです。つまり、MCRでリセット用マッチレジスタを指定し、マッチ用レジスタでマッチを1にするカウント数を指定すれば一定間隔でタイマがリセットされ、PWMのキャリア周波数が生成されるという仕組みです。マッチ出力は0~3までありますが、ユーザーマニュアルによれば 「MAT3は外部ピンに出力されていないので、タイマリセット用のマッチ出力は3にするのがおススメだよ」 とのことなので、今回もこれに従って MAT3をタイマリセット用に、MAT0をPWMのデューティー比決定用に設定しましょう。設定方法は今までのものと同様なので、最終的なコードを以下に示します。割り込みハンドラは「まだいいや」ということで実装していません。

#ifdef __USE_CMSIS
#include "LPC11xx.h"
#endif
int <b>main</b>(void)
{
    LPC_IOCON-&gt;PIO0_9 = 0x02;
    LPC_SYSCON-&gt;SYSAHBCLKCTRL |= (1&lt;&lt;7); <span>//48MHz</span>
    LPC_TMR16B0-&gt;PR = 0;   <span>//Prescaler</span>
    LPC_TMR16B0-&gt;MR3 = 999;  <span>//Career</span>
    LPC_TMR16B0-&gt;MR1 = 500;  <span>//-Duty(50%)</span>
    LPC_TMR16B0-&gt;MCR = (0x02 &lt;&lt; 9); <span>//Reset at MR3</span>
    LPC_TMR16B0-&gt;PWMC = 0x02; <span>//Set MAT1 as PWM Mode</span> 

    LPC_TMR16B0-&gt;TCR = 1;  <span>//Start 16-bit Timer 0</span>
    <b>while</b>(1)
    {

    }
    return 0;
}
                    

 int内3行目のPRレジスタはプリスケーラー(分周器)の設定で、今回は0にしていますが、これを設定することでカウンタ用クロックの周波数を1/PRに落とすことができます

 MR3,MR1レジスタはそれぞれマッチ出力3,1のマッチ値で、要はタイマーの設定時間です。MR3はリセット用ですが999、即ち(0から数えて)1000クロック目でリセットするということです。クロックは48MHz、分周器はなしですからキャリア周波数は48MHz/1000=48kHzとなります。

 MCRレジスタは"Match Control Register"の略で、どのマッチレジスタにマッチしたときにタイマーをリセットしたり割り込みを発生させたりするかを設定します。実際は割り込みも有効化させて使用しますが、今回はとりあえずMR3とのマッチ時にタイマーをリセットする、とだけ設定しています。

 PWMCはPWMモードにするマッチ出力を設定するレジスタです。今回はMAT1をPWMモードに設定。

 そして、TCR(タイマコントロールレジスタ)のタイマスタート用ビットを1にすることでタイマーをスタートさせます。最後にwhile無限ループを入れておきます。これでコーディングは終了。

4:ユーザーマニュアルは裏切らない

プロービング

 コーディングが終わりましたので、いよいよ動作確認に移っていきます。筆者がプログラミングをすると大抵間違いが多発して素直には動かないのですが、今回はどうなるのか。まずはオシロスコープをPIO0_9に接続します。PIO0_9はちょうど右隅なので接続しやすいです。

PWM オシロスコープによる測定

 そしてプログラムを書き込むと…見事方形波が出力されました。デューティー比は約50%、周波数48.1kHzと設定通りです(正負に振れているのは間違えてACカップリングにしてたから)。やっぱりマイコンプログラミングの近道は嫌がらずにデータシートとユーザーマニュアルを読むこと、そしてサンプルプログラムから学ぶことですね(なお、プログラムの間違いは1か所ありました)。

PWM出力波形

 このように、デューティー比を変更できることも確認しました。ここで、デューティー比決定用MR1レジスタの値は出力がオフになる時間に比例するので注意が必要です。この例ではデューティー比80%なので MR1は200(1000*0.2) に設定してあります。というわけで、これでPWMの基本的な使い方の学習は終了。次回以降は電子回路からいったん離れ、発電機本体の物理的な製作(金属加工)に移っていこうと思います。

Tags: