MC9S08SH4 のギャング出力で作るPWM [HCS08]
MC9S08SH4 のギャング出力でPWMは使えるか?で判明したように、ギャング出力にハードウェアPWMを接続することはできませんでした。 しかたないので、ソフトウェアでPWMを実現します。
PWMの周波数をどのくらい高くすればいいか
今回のPWMの最終使用目的は、LEDではなく、DC-DCコンバータです。 一般にDC-DCコンバータの駆動周波数は可聴周波数よりも十分に大きくする必要があります。 これは、もし可聴周波数領域で駆動するとコイルなどの振動が耳障りな音として聞こえてくるからです。
また、周波数が低くなるとDC-DCコンバータのコイルのインダクタンスを大きくする必要があります。 インダクタンスを大きくすると、コイルが大きくなり、コイルの巻き数が増加して抵抗成分も増加し、いいことがありません。
そのため、できる限り周波数を高くしたPWMを設計する必要があります。 目安としては、インバータ式蛍光灯で使われている20kHzから50kHzというところでしょう。
MCUの駆動周波数
今回使用する MCU MC9S08SH4 は、最大バスクロックが 20MHz とされています。 これまで、良く使ってきた MC9S08QG8 の最大バスクロックが 10MHz だったので、二倍高速に動作することがわかります。 そのため、ソフトウェア PWM の周波数も従来の MCU に比べて二倍にすることができます。 今回は、この最大バスクロック20MHzで動作させることにします。
PWMの周波数を 20kHz から 50kHz と決めました。 そのため、 PWM 波形一周期に入るバスクロックは、 400サイクル から 1000サイクルということになります。 ここでは、「キリのいい」 512サイクルとします。 時間にして 25.6µ秒、周波数にして 約39kHzです。
成功の鍵は、割り込み処理にあり
ソフトウェアは、512サイクルごとにパルスを発生させなくてはなりません。 このイベントをハードウェア・タイマを使用して発行できたとしても、それをソフトウェアがきちんと受け止めなくてはタイミングが合わなくなります。 また、PWMパルスを発生させる裏では、電流や電圧を監視するA/Dコンバータ、PWMのデューティーを決定するための制御プログラムなどが動作します。 そのため、PWM出力をメイン・ルーチンに組み込むわけにもいきません。
このような事情から、PWMの処理は、割り込みによって行い、割り込み処理ルーチンの所要時間をできる限り削減することで対応することにしました。
初代割り込み処理ルーチン
最初に作成した割り込み処理ルーチンは、こんなものです。
word duty; // Duty value. ISR(pwm_overflow_isr) { TPM2C0V = duty; // Set next duty ratio. TPM2SC_TOF = 0; // Clear overflow flag. GPort_SetVal(); // Assert GPORT } ISR(pwm_channel_isr) { GPort_ClrVal(); // Negate GPORT TPM2C0SC_CH0F = 0; // Clear channel flag. }
タイマのオーバフロー周期をPWM周期の 25.6µ秒に設定し、オーバフロー割り込みの発生ごとにギャング出力をセットします。 このとき、同時に16ビットのアウトプット・コンペアにデューティ比を設定します。
アウトプット・コンペア割り込みが発生したら、ギャング出力をクリアします。
たった、これだけの処理なのですが、コンパイルの結果をみると意外に時間がかかっていることがわかります。
36: ISR(pwm_overflow_isr) { 0000 8b [2] PSHH 39: TPM2C0V = duty; // Set next duty ratio. 0001 320000 [5] LDHX duty 0004 3500 [5] STHX _TPM2C0V 41: TPM2SC_TOF = 0; // Clear overflow flag. 0006 1f00 [5] BCLR 7,_TPM2SC 42: GPort_SetVal(); // Assert GPORT 0008 1000 [5] BSET 0,_PTCD 43: } 000a 8a [3] PULH 000b 80 [9] RTI 44: 46: ISR(pwm_channel_isr) 47: { 0000 8b [2] PSHH 48: GPort_ClrVal(); // Negate GPORT 0001 1100 [5] BCLR 0,_PTCD 49: TPM2C0SC_CH0F = 0; // Clear channel flag. 0003 1f00 [5] BCLR 7,_TPM2C0SC 50: } 0005 8a [3] PULH 0006 80 [9] RTI
行番号がバラバラですが、お気になさらず。 "pwm_overflow_isr"では、割り込み受付を含めておよそ54サイクル必要で、"pwm_channel_isr"では、割り込み受付を含めておよそ44サイクル必要です。 割り込みが連続して発生したとしても、ギャング出力に出力される最小パルス幅は、およそ34サイクルなので、理論上の最小デューティー比は6.6%ということになります。
変数はゼロページに置くべし
HCS08には、ゼロページという概念があります。 ゼロページ、すなわち$0000から$00FFまでの範囲のメモリ・アクセスは高速に実行されます。 そこで、変数"duty"をゼロページに配置します。
#pragma DATA_SEG __SHORT_SEG MY_ZEROPAGE word duty; // Duty value. #pragma DATA_SEG DEFAULT
これだけで、オーバフロー処理が1サイクル高速になります。
36: ISR(pwm_overflow_isr) { 0000 8b [2] PSHH 39: TPM2C0V = duty; // Set next duty ratio. 0001 5500 [4] LDHX duty 0003 3500 [5] STHX _TPM2C0V 41: TPM2SC_TOF = 0; // Clear overflow flag. 0005 1f00 [5] BCLR 7,_TPM2SC 42: GPort_SetVal(); // Assert GPORT 0007 1000 [5] BSET 0,_PTCD 43: } 0009 8a [3] PULH 000a 80 [9] RTI
まあ、わずかに1サイクルではありますが。
Hレジスタの退避は、必要か?
"pwm_channel_isr"では、割り込み処理ルーチン内でHレジスタが使われないにも係わらずスタックに退避されています。 これはムダなので退避させないようにします。
#pragma TRAP_PROC SAVE_NO_REGS ISR(pwm_channel_isr) { GPort_ClrVal(); // Negate GPORT TPM2C0SC_CH0F = 0; // Clear channel flag. }
関数の前に"#pragma"宣言を置くと、Hレジスタの退避が省略されます。
45: #pragma TRAP_PROC SAVE_NO_REGS 46: ISR(pwm_channel_isr) 47: { 48: GPort_ClrVal(); // Negate GPORT 0000 1100 [5] BCLR 0,_PTCD 49: TPM2C0SC_CH0F = 0; // Clear channel flag. 0002 1f00 [5] BCLR 7,_TPM2C0SC 50: } 0004 80 [9] RTI
同様に"pwm_overflow_isr"でもHレジスタの退避を省略したいところですが、こちらの処理ルーチンでは、16ビットレジスタへのアクセスのためにHレジスタが使用されています。 そこで、タイマのプリスケーラを2倍に設定することでデューティー比変数"duty"を8ビットとし、上位8ビットには、常に0を書き込むことにします。 これで、Hレジスタを退避する必要がなくなりました。
#pragma DATA_SEG __SHORT_SEG MY_ZEROPAGE byte duty; // Duty value. #pragma DATA_SEG DEFAULT #pragma TRAP_PROC SAVE_NO_REGS ISR(pwm_overflow_isr) { TPM2C0VH = 0; TPM2C0VL = duty; // Set next duty ratio. TPM2SC_TOF = 0; // Clear overflow flag. GPort_SetVal(); // Assert GPORT }
この部分、デューティ比設定レジスタは、下位8ビットのデータだけを設定するだけでいいんじゃないかと思っていました。 ところが、 MC9S08SH4 のタイマでは16ビット分設定しないとレジスタが変更されませんでした。 したがって、ムダではありますが、 "TPM2C0VH = 0;" という行を入れてあります。
31: #pragma DATA_SEG __SHORT_SEG MY_ZEROPAGE 32: byte duty; // Duty value. 33: #pragma DATA_SEG DEFAULT 34: 35: #pragma TRAP_PROC SAVE_NO_REGS 36: ISR(pwm_overflow_isr) { 37: TPM2C0VH = 0; 0000 3f00 [5] CLR _TPM2C0V 38: TPM2C0VL = duty; // Set next duty ratio. 0002 4e0001 [6] MOV duty,_TPM2C0V:1 41: TPM2SC_TOF = 0; // Clear overflow flag. 0005 1f00 [5] BCLR 7,_TPM2SC 42: GPort_SetVal(); // Assert GPORT 0007 1000 [5] BSET 0,_PTCD 43: } 0009 80 [9] RTI
デューティー比レジスタを書き込む部分で多少てこずりましたが、3サイクル削減しました。 最初は34サイクルあった処理が、30サイクルまで減りました。 もうちょっと、がんばりたいけど、今のところはここまで。
設定値とデューティ比の確認
プログラムができたので、変数 "duty" に設定した値と実際のデューティー比を確認します。 デューティー比は、 2.2kΩ の抵抗と 0.1µF のマイラ・コンデンサで作った一次ローパスフィルタの出力電圧で代用しています。 電源電圧は、 5.1V でした。
duty | VOUT (V) | ratio (%) |
---|---|---|
0 | 4.65 | 91.2 |
1 | 1.763 | 34.6 |
2 | 0.2502 | 4.9 |
: | : | : |
20 | 0.2502 | 4.9 |
21 | 0.2701 | 5.3 |
22 | 0.2900 | 5.7 |
23 | 0.3100 | 6.1 |
: | : | : |
134 | 2.520 | 49.4 |
135 | 2.540 | 49.8 |
136 | 2.560 | 50.2 |
137 | 2.580 | 50.6 |
: | : | : |
237 | 4.57 | 89.6 |
238 | 4.59 | 90.0 |
239 | 4.61 | 90.4 |
240 | 4.63 | 90.8 |
241 | 4.65 | 91.2 |
: | : | : |
255 | 4.65 | 91.2 |
この結果から、このPWMが正しくパルスを出すことができるのは、 "duty" の値が 20 から 241 までであり、そのときのデューティ比は、 4.9% から 91.2% の範囲になることがわかりました。
次は、コイルを作ります。
2009-03-19 追記変数をゼロページに配置するための #pragma の記述方法が間違っておりましたので、訂正しました。
参考文献
HCS08 Unleashed: Designer's Guide to the HCS08 Microcontrollers
- 作者: Fabio Pereira
- 出版社/メーカー: Booksurge Llc
- 発売日: 2007/11/13
- メディア: ペーパーバック
コメント 0