今回は、ハードPWMとソフトPWMを作って比較してみたところ
想像以上に大きな違いが出ました。
特にソフトPWMでは、周波数が思った通りに出ず、
試行錯誤することになりました。
ソフトPWMでは、
タイマー設定や割り込みの影響を強く受けるため
思った通りの周波数にならないことがあります。
その原因と対策についてもまとめます。
今回の回路

前回使っていたPINとは違うPINにしました。
PINを変えるという面倒な作業を入れることで、
もう一度データシートを見返して、
レジスタの設定を見返すためです。
(コピペだと勉強にならないので)
回路のポイントは、
入力
①VOL1_ADC:RA1 (AN1)
②VOL2_ADC:RA2 (AN2)
③OFFSET_SAVE_SW:RB4 ←今回は未使用
出力
①LED1 : RC2 (CCP1) → ハードPWM用
②LED2 : RB1 → ソフトPWM用
使うのはこれだけです。
VOL1_ADCを回してハードPWMを使用しLED1を調光させる。
VOL2_ADCを回してソフトPWMを使用しLED2を調光させる。
という感じです。
今回のコード
今まではコード全部を載せていたのですが、
さすがに長いと思うので、ポイントだけを抑えたいと思います。
ハードPWM制御部は前回と同じです。
もちろんADCのPINが変わっているので、
そこだけ回路に合わせて変更しました。
c
void SOFT_PWM_Init(void){
TRISBbits.TRISB1 = 0; //RB1を出力に設定
LATBbits.LATB1 = 0; //RB1の出力をLOWにする
OPTION_REGbits.TMR0CS = 0; //Timer0のクロック源を内部クロックに設定
OPTION_REGbits.PSA = 0; //プリスケーラをTimer0モジュールに割り当てる
OPTION_REGbits.PS = 0b000; //Timer0のプリスケーラを1:2に設定
TMR0 = 246; //TMR0のカウントを246からスタート
INTCONbits.TMR0IF = 0; //Timer0割り込みフラグをクリア
INTCONbits.TMR0IE = 1; //TMR0を使用
INTCONbits.PEIE = 1; //周辺機器の割り込みをイネーブル(今回は不要)
INTCONbits.GIE = 1; //グローバルな割り込みをイネーブル
}
c
void SOFT_PWM_SET(void){
ADCON0bits.CHS = 0b00010; //AN2選択(VOL2_ADC 読み取り用)
__delay_us(10); //コンデンサの充電待ち(回路の安定)
ADCON0bits.GO_nDONE = 1; //A/D変換中
while(ADCON0bits.GO_nDONE); //A/D変換終了待ち
// A/D変換した値を16bitのadcに格納
unsigned int adc = (unsigned int) ADRESH << 8 | ADRESL;
// 100段階にスケーリング
soft_duty = (adc * 99UL) / 1023UL;
}
void __interrupt() isr(void){
if(INTCONbits.TMR0IF == 1){ //Timer0のオーバーフローフラグの確認
TMR0 = 246; //TMR0を246にセット(246から256まで数える)
count++; //グローバルで設定済みの変数countを+1する
if(count >= 100) count = 0; //countが100以上でクリア
//countがsoft_duty未満でRB1をHigh、以上でLowにする
//つまり、countの比率でデューティ比を作っています
if(count < soft_duty){
LATBbits.LATB1 = 1;
}else{
LATBbits.LATB1 = 0;
}
INTCONbits.TMR0IF = 0; //
}
}
今回のソフトPWMは、
Timer0のオーバーフローの回数を
変数countで数え、
countの値がADCの値になるまではHigh、
countの値がADCを超えたらLow
という仕組みです。
TMR0 = 246 にした根拠
Fosc = 8MHz
↓
Fosc / 4 = 2MHz ⇒ 1命令サイクル = 0.5μs
プリスケーラ 1:2なら
Timer0 1カウント = 1μs
TMR0 = 246から始めると、
8bit(28 = 256) の Timer0 がオーバーフローするまで、
256 – 246 = 10カウント = 10μs
分解能100 ステップであるので (count が Timer0 のオーバーフローを 100まで数える)
10μs × 100 = 1000μs = 1ms
周波数 1kHz = 1000Hz は、
1/1000 秒 = 1ms
である。
すなわち、TMR0 = 246とすることによって、
周波数を1kHzにすることを目的としている。
これで、ハードPWMもソフトPWMも共に
周波数1kHzで動いてくれるはず!
でもCPUを使ってPWMを生成するため、
割り込み処理の時間や処理内容によって
周波数がズレる可能性はありと思います。
オシロスコープの確認


いやズレすぎ…
36Hzて…
分かると思いますが、上の青がソフトPWM、下のピンクがハードPWMです。
下のピンクは約1kHzとなっていました。
ちなみに
36Hzでも、肉眼ではチラついているようには見えません。
ただし、カメラで見るとチラつきが確認できるため、
用途によっては問題になる可能性があります。
(舞台照明や撮影用途では特に注意が必要です。)
ここからできるだけ1kHzにできるように試行錯誤しました。
割り込み関数の中身を
c
void __interrupt() isr(void){
if(INTCONbits.TMR0IF == 1){
TMR0 = 246;
count++;
if(count >= 100) count = 0;
INTCONbits.TMR0IF = 0;
}
}
だけにして、下のコードはmainのなかに入れました。
c
if(count < soft_duty){
LATBbits.LATB1 = 1;
}else{
LATBbits.LATB1 = 0;
}
結果は、オシロスコープを見るまでもなくNG。
目で見て点滅していました。
また上の if 文を __interrupt()に戻し、以下のことを行いました。
TMR0 = 10 ⇒ 39Hz
TMR0 = 100 ⇒ 61Hz
TMR0 = 200 ⇒ 154Hz
TMR0 = 250 ⇒ 37Hz
一番最後だけ腑に落ちません。
でもこれはカウントが 6 や 10だと,
6 × 1μs = 6μs
10 × 1μs = 10μs
となります。
おそらく、割り込み処理そのものの実行時間や、
LATの書き換え処理などの
オーバーヘッドが加わることで、
理論値よりも周期が長くなっていると考えられます。
プリスケーラ設定を変更するにもこれ以上早くはできません。
そのため、分解能を下げることにしました。
つまり、count変数の値をいろいろと試し、
一番1kHzに近づけたのは次の条件でした。
c
TMR0 = 235;
if(count >= 34) count = 0;

黄色がソフトPWM、水色がハードPWMです。
でも、やはり分解能が小さいので、
LEDの付き始めがかなり明るかったです。
ハードPWMの分解能は500、
一報ソフトPWMの分解能は34。
この差が、LEDの立ち上がりの滑らかさの違いとして体感できました。
やはり、ハードPWMの方が設定は楽だし、
調光もきれい。
ただし、ソフトPWMはピンの自由度が高く
(すべてのI/Oピンが使える)、
専用モジュールを使わずに
PWMを生成できるというメリットもあります。
そのため、
・出力ピンに制約がある場合
・複数チャンネルを柔軟に扱いたい場合
などでは、ソフトPWMが有効になる場合もあります。
今日の学び
ソフトPWMは設定値の選定が大変。
ソフトPWMでは、理論計算通りに周波数が出ないことがある。
もしここにさらにmainの中にいろいろな処理を増やすとなると、
さらに数値の変更が必要になる可能性もあるでしょうし、
ハードPWMのありがたさを肌で感じました。
でも、今回頑張ったことで、
PWMの分解能と周波数がトレードオフであることは
よくわかりました。
ソフトPWMは、タイマー設定と割り込み処理の影響を強く受けます。
周波数と分解能のバランス設計が非常に重要だと分かりました。


コメント