今日はPICでLEDのPWM調光を作る勉強をしました。
そしてついに、PIC16F1938で
ボリューム → ADC → PWM → LEDの明るさ
という一連の流れを理解し、
ボリュームでLEDの明るさを変えることができました。
この記事では
その作り方のすべてを公開します。
最初はただLEDが光るだけでしたが、
オシロスコープで波形を見ながら理解すると
かなりおもしろい世界が見えてきました。
今回はその整理です。
①PWMとは何か
PWMは
Pulse Width Modulation(パルス幅変調)
です。
簡単に言うと
電圧を変えるのではなく
ONの時間を変える
ことで明るさを制御します。
たとえば、
ON 50%
OFF 50%
なら平均電圧は
5V × 0.5 = 2.5V
になります。
つまり
高速にON/OFFを繰り返すことで明るさを変える
という仕組みです。
②PWMの周波数は何で決まるか
PICのPWM周波数は次の式で決まります。
この式はPICのPWMモジュール(Timer2)で決まる仕様です。
今回の実験では
Fosc = 16MHz
Prescaler = 64
PR2 = 124
なので上の式に代入すると
となります。
③PWMの分解能
PWMの細かさ(分解能)は
で決まります。
つまりPR2を大きくするとPWMは細かくなります。
今回の場合は、
つまり
約9bit(29=512)の分解能
になります。
④ADCでボリュームを読む
ボリュームの電圧は
0V~5V
です。
PICのADCは10bitなので、
0~1023
の値になります。つまり
0V → 0
5V → 1023
です。
⑤ADC → PWMに変換
PWMの最大値は
499
なので
ADCの値をPWMの範囲に変換します。
コードはこうしました。
c
duty = (adc * 499UL) / 1023UL;
これで
adc = 0 → duty = 0
adc = 1023 → duty = 499
になります。
ADCの範囲(0~1023)を
PWMの範囲(0~499)に
スケーリングしています。
⑥回路図

KiCADにPIC16F1938が無く
CADで自力で描いたので、
少し見づらいかもしれませんが…
⑦実際のコード
なかなか動かない…
・書き込みエラー(またかよ!!)
・ボリューム操作でのLEDは動かない
・LED点きっぱなし
・LED全く点かない
・ボリュームのGNDが浮いていた…
等ありました。
しかし次のコードでちゃんと動いてくれました。
c
#include <xc.h>
#pragma config FOSC = INTOSC
#pragma config PWRTE = OFF
#pragma config WDTE = OFF
#pragma config MCLRE = ON
#pragma config CP = OFF
#pragma config BOREN = ON
#pragma config CLKOUTEN = OFF
#pragma config IESO = OFF
#pragma config FCMEN = OFF
#define _XTAL_FREQ 8000000UL
void pwm1_init_1kHzish(void){
OSCCON = 0b01111010; //Foscを16MHzに設定
TRISCbits.TRISC2 = 0; //RC2bitの出力設定
T2CONbits.T2CKPS = 0b11; //Prescaler 1/64に設定
PR2 = 124; //PR2の設定(解像度の選択)
T2CONbits.TMR2ON = 1; //TMR2をON
CCP1CON = 0b00001100; //PWMモード
}
void ADC_Init(void){
TRISAbits.TRISA0 = 1; //RA0の入力設定
ANSELAbits.ANSA0 = 1; //RA0のアナログ設定
ADCON1bits.ADPREF = 0; //VDDを正の参照電圧に設定
ADCON1bits.ADNREF = 0; //VSSを負の参照電圧に設定
ADCON1bits.ADCS = 0b101; //Fosc/16に設定(ADCのクロック周期の設定)
ADCON1bits.ADFM = 1; //A/D変換結果を右詰で読み込む
ADCON0bits.CHS = 0; //アナログチャンネル選択ビット AN0を使用
ADCON0bits.ADON = 1; //ADCを有効にする
}
unsigned int ADC_Read(void){
__delay_ms(20);
ADCON0bits.GO_nDONE = 1; //A/D変換サイクルのON設定
while(ADCON0bits.GO_nDONE);
return ((unsigned int)ADRESH << 8) | ADRESL;
}
void main(void){
pwm1_init_1kHzish();
ADC_Init();
while(1){
unsigned int adc;
unsigned int duty;
adc = ADC_Read();
duty = (adc * 499UL) / 1023UL;
CCPR1L = duty >> 2;
CCP1CONbits.DC1B = duty & 0x03;
}
}
今回の重要な部分はここ↓
c
unsigned int adc = ADC_READ(0);
unsigned int duty;
duty = (adc * 499UL) / 1023UL;
CCPR1L = duty >> 2; //10bitのうちの上位8bit分を代入
CCP1CONbits.DC1B = duty & 0x03 //10bitのうちの下位2bit分を代入
//PWMのデューティは10bitなので、
//最後の2行で 8bit + 2bit = 10bit
//に分けてレジスタに書き込む
⑧オシロスコープで確認
実際にオシロスコープで波形を確認すると


・周波数
約500Hz
(②の計算と一致)
・ボリュームを回すと
パルス幅の変化
がみられました。
やったーーーー!!!!
つまり
ボリューム
↓
ADC
↓
PWM
↓
LEDの明るさ
という流れがちゃんと動きました。
やはり、よく見かけるのは1kHzなので、
そこへも挑戦してみました。
計算上は
Fosc : 8MHz
Prescaler : 16
で行けそうです。
元のコードの2か所を下の様に書き換えました。
c
OSCCON = 0b01110010; //Foscを8MHzに設定
T2CONbits.T2CKPS = 0b10; //Prescaler 1/16に設定
オシロスコープで波形を確認。

よし!!計算通り!!
今日の学び
今日の勉強で理解できたことは
・PWMはON時間を変えて平均電圧を作る
・PWMの周波数はTimer2で決まる
・PR2はPWM周期を決める
・ACDは0~1023
・ADC値をPWMのDutyに変換する必要がある
そして何より
オシロスコープで見ると理解が進み
達成感が味わえる
ということでした。
次にやりたいこと
・Timer設定をもっと理解する
・DMX信号とPWMを繋ぐ
このあたりを勉強していこうと思います。
ハーフブリッジ?
フルブリッジ?
なども
”ちんぷんかんぷん”
なので、このあたりも。

コメント