PICでPWM調光を改善|下限オフセットでチラつき問題を解決する方法

前回はソフトPWMとハードPWMの両方を
ボリュームで調光させるところまで行いました。
実際に様々な照明器具を調光していると、
0-100%の調光させると、
10%までの暗いところでは、チラつくことが多くあります。

今回はこのチラつき(フリッカー)の対策として、
下限オフセットを作りました。
つまり、設定したところが10%の場合、
ボリュームを回しても10%~100%範囲でしか調光せず、
0~10%の間は切り捨てられます。
この場合はボリュームが0でも10%の明かりが点いてしまうため、
ボリュームが0の時はOFF、少しでも回すと10%から始まり、
ボリュームを上げるとLEDは100%調光するという風にしました。

今回もなかなかうまく動いてくれなくて、
難儀しました。
そのあたりも紹介していきます。

 

今回の回路

前回と全く同じです。

ただし、今回ハードPWM、ソフトPWM共に
下限オフセットをかけるソフトにしていますが、
ソフトに関しては、全く確認してません。
ハードPWMの下限オフセットに全振りしています。
前回のソフトPWMは34段階でしか調光できていないので、
下限を設定してもあまり面白くないと思ったためです。
あと、ハードの方が実用性は高いと判断したためという
理由もあります。

下限オフセットの設定方法としては、

①ボリュームを回して明るさを作る
②SW1を押す
③下限値の決定

という具合にします。

 

今回のコード

前回と違う部分を紹介します。

変数の名前が少し変わっていたりはしますが
大きく違うのは以下の通りです。

PINの初期設定

c

void SOFT_Pin_Init(void){
    TRISBbits.TRISB1 = 0;
    LATBbits.LATB1 = 0;

    TRISBbits.TRISB4 = 1;          //RB4を入力モード(スイッチ入力)
    ANSELBbits.ANSB4 = 0;          //RB4をデジタルモード

    OPTION_REGbits.TMR0CS = 0;     //Timer0のクロック源を内部サイクルに設定
    OPTION_REGbits.PSA = 0;        //プリスケーラをTimer0に割り当てる
    OPTION_REGbits.PS = 0b000;     //プリスケーラを1:2に設定
    
    TMR0 = 235;                    //Timer0カウントを235からスタート
    INTCONbits.TMR0IF = 0;         //Timer0オーバーフロー割り込みフラグ
    INTCONbits.TMR0IE = 1;         //Timer0オーバーフロー割り込み有効

    IOCBPbits.IOCBP4 = 0;          //RB4の立ち上がり割り込みの無効
    IOCBNbits.IOCBN4 = 1;          //RB4の立ち下がり割り込みの有効
    IOCBFbits.IOCBF4 = 0;          //RB4の状態変化割り込みフラグをクリア
    INTCONbits.IOCIF = 0;          //状態変化割り込みフラグをクリア
    INTCONbits.IOCIE = 1;          //状態変化割り込みをイネーブル

    INTCONbits.GIE = 1;            //グローバル割り込みをイネーブル

}

PIN割り込みの設定

c

volatile unsigned char save_request = 0;

void __interrupt() isr(void){
    if(INTCONbits.IOCIF)
    {
        save_request = 1;          //SW1が押されEEPROMへの書き込みのリクエストフラグ
        IOCBFbits.IOCBF4 = 0;      //RB4の状態変化フラグのクリア
                
        INTCONbits.IOCIF = 0;      //PIN割り込みの状態変化フラグのクリア
    }
}

ここは前回とほぼ同じです。
後半のPORTBレジスタの状態変化の割り込みは、
初登場となりました。
このPICでは、PORTAやCでは使えず、
Bのみ使用できるとのことです。
たまたまスイッチ入力をBにしていたのが
功を奏した形になります。

EEPROMへの書き込み、読み込み

c

void EEPROM_Write(unsigned char addr, unsigned char data)
{
    EEADRL = addr;          //アドレスの設定
    EEDATL = data;          //データの設定

 /*************以下EEPROMへの書き込みに必要なシーケンス***************/ 
    EECON1bits.EEPGD = 0;
    EECON1bits.CFGS = 0;
    EECON1bits.WREN = 1;
    INTCONbits.GIE = 0; 
    
    EECON2 = 0x55;
    EECON2 = 0xAA;
    EECON1bits.WR = 1;
    
    INTCONbits.GIE = 1;
    
    while(EECON1bits.WR);
    
    EECON1bits.WREN = 0;
}
/*********************************************************************/


unsigned char EEPROM_Read(unsigned char addr)
{
    EEADRL = addr;
 
/**************以下EEPROMからの読み込みに必要なシーケンス*************/   
    EECON1bits.EEPGD = 0;
    EECON1bits.CFGS = 0;
    
    EECON1bits.RD = 1;
 /*********************************************************************/
   
    return EEDATL;
}

こちらも初登場。
このシーケンスを書くだけでさまざまなデータを
保存できる訳ですね。
そこまでややこしくない!

下限オフセットの設定

c

unsigned int Apply_Offset(unsigned int adc_val, unsigned int offset)
{
    //読み込んだADC値が2以下の時は0に設定
    if(adc_val <= 2){          
        return 0; 
    }
    //オフセット値が大きすぎる数値だった場合は、1023に設定
    if(offset >= 1023){
        return 1023;
    }

    //オフセットに設定するための仕掛け
    return offset + ((unsigned long)(adc_val - 2) * (1023UL - offset)) / (1023UL - 2);
    
}

今回の記事のキモはここです!

ADCの値とEEPROMのオフセットの値を比較して、
ADCがEEPROMより大きくなると、
100%まで調光します。

0〜100%をそのまま使うのではなく、“使える範囲に再マッピングする”という考え方になります。

ここはかなり試行錯誤したので、次の章で解説します。

main関数

c

    while(1)
    {
        if(save_request){
            //EEPROMへの書き込みのリクエストフラグのクリア
            save_request = 0;              
            for(int i = 0; i < 2; i++){

                //VOL1とVOL2のADC値をEEPROMへ書き込む
                EEPROM_Write(i, (unsigned char)(Read_ADC(ch[i]) >> 2);

            }
        }
        
        //ハードオフセット値とソフトオフセット値のEEPROMからの読み込み
        hard_offset = ((unsigned int)EEPROM_Read(0)) << 2;
        soft_offset = ((unsigned int)EEPROM_Read(1)) << 2;

        //現在のボリュームのADC値の読み込み
        adc_hard_raw = Read_ADC(ch[0]);
        adc_soft_raw = Read_ADC(ch[1]);

        //上で読み込んだEEPROMのADC値と現在のADC値、2つの数値の比較
        write_adc[0] = Apply_Offset(adc_hard_raw, hard_offset);
        write_adc[1] = Apply_Offset(adc_soft_raw, soft_offset);

        //比較結果の出力
        HARD_PWM_APPLY(write_adc[0]);
        SOFT_PWM_APPLY(write_adc[1]);
     
    }

 

<h2>今日の学び</h2>

今回は、EEPROMへの書き込みに苦戦するのでは?と
少し期待(?)していました。
案の定間違えてました。
pull-upでのRB4の割り込みなのに、下のようにしていました。

c

void SOFT_Pin_Init(void){
         …
   IOCBPbits.IOCBP4 = 1;          //RB4の立ち上がり割り込みの有効
   IOCBNbits.IOCBN4 = 0;          //RB4の立ち下がり
}

pull-downであればこれでよかったんですけど、
今回、SW1は、pull-upなので、
常時RB4pinには5Vが印加されていて、
スイッチが押されたときだけ、
0Vになるので、逆でした。

なんと言っても今回一番悩まされたのは、
上の下限オフセットの設定のところの式です。

Apply_Offset()関数の最後の行で、
何を"return"させるべきかかなり悩みました。

c


adc_val; //①

((unsigned long)(adc_val - offset) * 1023UL) / (1023UL - offset); //②

offset + ((unsigned long)(adc_val - 2) * (1023UL - offset)) / (1023UL - 2); //③

上のコード①~③の動きを図示したのが下の図です。
40%の位置で下限カットした場合の図となります。

①の式では、確かに下限カットはできています。
しかし、ボリュームの遊びの範囲が広すぎるためNG。

②の式では、0~40%が遊びの範囲になり、
ボリュームの40%位置から0-100%の調光になります。
0~40%の値で照明がチラつく場合は、意味がありません。

③の式では、0~40%がカットされています。
また、ボリュームの遊びの部分もなく、
少し回すと40%から調光が始まっています。

今回は③の動きを再現したかったので、
③を採用しました。

今回は、PWMの下限オフセットについて色々試してみました。

正直、ここまで悩むとは思っていなかったのですが、
実際にやってみると「0付近ってこんなに扱いにくいんだな」と実感しました。

ただ、その分かなり理解は深まった気がします。

まだ完全に思い通りという訳ではないですが、
一歩ずつ“使えるPWM”に近づいてきたかなと思います。

次は、この値をスイッチで操作できるようにしていきたいので、
短押し・長押しやチャタリング対策に挑戦していきます。

コメント

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