PIC16F1938 3ch ADC → 3ch PWMの作り方~AN8でハマった話とPWM100%にならない話~

PICの基礎

今回も前回と同様、
PIC16F1938を使い
3chのADC入力から、3chのLEDのPWM調光を作りました。

今回も、回路図、コード、オシロの波形
ハマったポイントをしっかり載せます。

①今回の回路図

前回の10進ロータリスイッチは残したままにしています。
今回はこれは使いません。

回路のポイントは、

入力
①ボリューム1:RA5 (AN4)
②ボリューム2:RB2 (AN8)
③ボリューム3:RB3 (AN9)

出力
①LED1 : RC2 (CCP1)
②LED2 : RC1 (CCP2)
③LED3 : RB5 (CCP3)

使うのはこれだけです。

ボリューム1を回してLED1の調光のデューティーを変える。
という感じです。

回路はとてもシンプルです。

②今回のコード

c

#include <xc.h>
#include <pic16f1938.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 PWM_Init(void)
{
    OSCCON = 0b01110010;    //8MHz
    
    TRISCbits.TRISC2 = 0;   //RC2 OUT
    TRISCbits.TRISC1 = 0;   //RC1 OUT
    TRISBbits.TRISB5 = 0;   //RB5 OUT
    
    ANSELB = 0x00;  //RBxbits digital
    
    APFCONbits.CCP2SEL = 0;     //0:RC1, 1:RB3
    APFCONbits.CCP3SEL = 1;     //0:RC6, 1:RB5
    
    T2CONbits.T2CKPS = 0b10;    //prescaler 1:16
    T2CONbits.TMR2ON = 1;       //Timer2 ON
    PR2 = 124;                  // 1kHz
    
    CCP1CON = 0b00001100;       //CCP1 PWM mode
    CCP2CON = 0b00001100;       //CCP2 PWM mode
    CCP3CON = 0b00001100;       //CCP3 PWM mode
}

void ADC_Init(void){

//RA5, RB2, RB3を出力ピンに設定
    TRISAbits.TRISA5 = 1;
    TRISBbits.TRISB2 = 1;
    TRISBbits.TRISB3 = 1;

//RA5, RB2, RB3をアナログ有効に設定
    ANSELAbits.ANSA5 = 1;
    ANSELBbits.ANSB2 = 1;
    ANSELBbits.ANSB3 = 1;
    
    ADCON0bits.ADON = 1;          //アナログ変換をONに設定
    
//リファレンス電源をVSSとVDDに設定
    ADCON1bits.ADNREF = 0;
    ADCON1bits.ADPREF = 0;

    ADCON1bits.ADCS = 0b101;       //プリスケーラ設定 FOSC/16
    ADCON1bits.ADFM = 1;           //A/D変換結果の右詰保存
}

void pwm1_set(void){
    ADCON0bits.CHS = 0b00100;      //AN4(RA5)選択中
    __delay_us(10);                //コンデンサの充電待ち
    ADCON0bits.GO_nDONE = 1;       //A/D変換変換中
    while(ADCON0bits.GO_nDONE);
    
    unsigned int adc = (unsigned int) ADRESH << 8 | ADRESL;

//adcが4未満は0、1015より大きいとき1023に設定
    if(adc < 4) adc = 0;
    if(adc > 1015) adc = 1023;
    
    unsigned int duty = (adc * 499UL) / 1023UL;
    
        
    CCPR1L = duty >> 2;
    CCP1CONbits.DC1B = duty & 0x03;
}

void pwm2_set(void){
    ADCON0bits.CHS = 0b01000;
    __delay_us(10);
    ADCON0bits.GO_nDONE = 1;
    while(ADCON0bits.GO_nDONE);
    
    unsigned int adc = (unsigned int) ADRESH << 8 | ADRESL;
    if(adc < 4) adc = 0;
    if(adc > 1015) adc = 1023;
    
    unsigned int duty = (adc * 499UL) / 1023UL;
    
    CCPR2L = duty >> 2;
    CCP2CONbits.DC2B = duty & 0x03;
}

void pwm3_set(void){
    ADCON0bits.CHS = 0b01001;
    __delay_us(10);
    ADCON0bits.GO_nDONE = 1;
    while(ADCON0bits.GO_nDONE);
    
    unsigned int adc = (unsigned int) ADRESH << 8 | ADRESL;
    if(adc < 4) adc = 0;
    if(adc > 1015) adc = 1023;
    
    unsigned int duty = (adc * 499UL) / 1023UL;
    
    CCPR3L = duty >> 2;
    CCP3CONbits.DC3B = duty & 0x03;
}

void main(void)
{
    PWM_Init();
    ADC_Init();
   
    while(1)
    {
        pwm1_set();
        pwm2_set();
        pwm3_set();
    }
}

こんな感じのコードになりました。

 

③オシロスコープで確認

LED1 : 黄色の波形
LED2 : 青色の波形
LED3 : ピンクの波形

それぞれのボリュームで独自のPWM調光ができました。

全てのボリュームをMAXにし、拡大すると

少し波形が残っていることが確認できました。
dutyを485以上を499にするように
ソフトで書いても波形は変わりませんでした。

 

④今日の学びと考察

<h3>1. AN8の罠</h3>

ピン名 ≠ レジスタ名

AN8をアナログ設定しようと

c

ANSELBbits.ANSB8 = 1;

と書いていました。

でもコンパイルエラーになる…
今回はここでめっちゃ迷いました。

AN8 は RB2 にあるので、

c

ANSELBbits.ANSB2 = 1;

と書かないとダメでした。

つまり、

PICのアナログ設定は「AN番号」ではなく
「ポート番号(RB2)」に紐づいている

からです。

 

<h3>2. ADCは"チャンネル切替して1個ずつ読む"</h3>

CCPxビットは5つあるので、

c

ADCON0bits.CHS1 = 0b00100;
ADCON0bits.CHS2 = 0b01000;
ADCON0bits.CHS3 = 0b01001;

みたいに、
同時に5つ設定して同時進行でいけるのかなと、
思っていました。

でも "CHS" しかないので、
読むビットをその都度切替ながら、
読み取って、PWM出力をするのが正解でした。

<h3>3. ADCは"10bit"でもレジスタは"8bit ×2"</h3>

c

    unsigned int adc = (unsigned int) ADRESH << 8 | ADRESL;
    if(adc < 4) adc = 0;
    if(adc > 1015) adc = 1023;
    

今回は上の様に書いていました。
しかし、下の if文2行が無くてもdutyは動きます。
その時は

c

   (unsigned int) ADRESH << 8 | ADRESL;
  

でもしっかりと動きます。
型をしっかりとキャストして、16bit保存されているので問題ありません。

 

<h3>4. delay()関数の目的</h3>

pwm_set()関数内にdelay_us(10)を入れているのは、

・スイッチの場合 → チャタリング除去
・ADCの場合   → サンプル保持(コンデンサ充電)

似ているけど違う。
安定するのを待つって意味では一緒かな?

 

<h3>5. PWM最大でも完全100%ではない?!?!</h3>

ソフト的にはボリュームがMAXの時は、
dutyもMAXになると思っていました。

しかしオシロスコープでは、少し波形が残っています。

かなり拡大して見ることができました。

さらに、同じ回路にも関わらず、波形の形が違って見えました。

考えられる原因:
・PWMの仕様(完全100%にならない)
・分解能の問題
・回路のRC特性

でも確実に波形は違っていました。

完全な100%にするためには、
ボリュームが100%とのときは、
条件分岐をさせて
LATを使って出力させるのがいいと思います。 

  

<h2>まとめ</h2>

・AN番号ではなくポートで設定する
・ADCは1chずつ読む
・PWMは完全100%にはならない

 

<h2>次回予告</h2>

3ch PWM出力に成功したので、
次は下限のオフセットの設定をしたいと思います。
EEPROMに記憶をさせて~
楽しみです

コメント

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