PIC16F1938でADC入力からPWM調光を作る方法【LEDをボリューム制御】

今日は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周波数は次の式で決まります。

PWM=Fosc4×Prescaler×(PR+1)PWM = \frac{Fosc}{4 × Prescaler × (PR + 1)}

この式はPICのPWMモジュール(Timer2)で決まる仕様です。

今回の実験では

Fosc = 16MHz
Prescaler = 64
PR2 = 124

なので上の式に代入すると

PWM=16,000,000(4×64×125)=500HzPWM = \frac{16,000,000}{(4 × 64 × 125)} =500Hz

となります。

 

③PWMの分解能

PWMの細かさ(分解能)は

4×(PR2+1)4 × (PR2 + 1)

で決まります。

つまりPR2を大きくするとPWMは細かくなります。

今回の場合は、

4×125=5004 × 125 = 500段階

つまり
約9bit(29=512)の分解能
になります。

 

④ADCでボリュームを読む

ボリュームの電圧は
0V~5V
です。

PICのADCは10bitなので、
0~1023
の値になります。つまり

0V → 0

5V → 1023

です。

 

⑤ADC → PWMに変換

PWMの最大値は
499
なので

ADCの値をPWMの範囲に変換します。

duty=adc1023UL×499ULduty = \frac{adc}{1023UL} × 499UL

コードはこうしました。

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なので、
そこへも挑戦してみました。

PWM=8,000,000(4×16×125)=1,000HzPWM = \frac{8,000,000}{(4 × 16 × 125)} =1,000Hz

計算上は

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を繋ぐ

このあたりを勉強していこうと思います。

ハーフブリッジ?
フルブリッジ?

なども

”ちんぷんかんぷん”
なので、このあたりも。

コメント

タイトルとURLをコピーしました