スイッチ制御は簡単じゃなかった|PICで短押し・長押し実装の失敗と気づき

今回は前回の
『PICでPWM調光を改善|下限オフセットでチラつき問題を解決する方法』につづき、
オフセットに使用したスイッチでの応用編となります。

スイッチ入力は簡単に見えて、実はかなりハマりやすいポイントでした。

押しボタンスイッチの短押し、長押しで異なる動きを再現してみました。

短押し : EEPROMへの下限オフセット位置の記憶

長押し : 下限オフセット~MAXまでの調光範囲のUI表示

今回もしっかりとハマってしまいましたので、
余すことなく紹介していこうと思います。

今回の回路

相も変わらず前回(『PICでPWM調光を改善|下限オフセットでチラつき問題を解決する方法』)と同じこの回路です。

今回の主役はSW1が “memory SW” の機能以外の使い方を実装します。
そして今回も前回と同様、
ハードPWMのみ
の実装となります。

ソフトPWMはしばらくお休みです。

コードと動き

前回との違いを中心に
紹介していきます。

PINの初期設定、割り込み設定

c


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

    TRISBbits.TRISB4 = 1;
    ANSELBbits.ANSB4 = 0;

    OPTION_REGbits.TMR0CS = 0;
    OPTION_REGbits.PSA = 0;
    OPTION_REGbits.PS = 0b000;

    TMR0 = 235;
    INTCONbits.TMR0IF = 0;
    INTCONbits.TMR0IE = 1;

    //    IOCBPbits.IOCBP4 = 0;
    //    IOCBNbits.IOCBN4 = 1;
    //    IOCBFbits.IOCBF4 = 0;
    //    INTCONbits.IOCIF = 0;
    //    INTCONbits.IOCIE = 1;

    INTCONbits.GIE = 1;
}

void __interrupt() isr(void) {
    if (INTCONbits.TMR0IF == 1) {
        TMR0 = 235;
        count++;
        if (count >= 34) count = 0;
        if (count < soft_duty) {
            LATBbits.LATB1 = 1;
        } else {
            LATBbits.LATB1 = 0;
        }
        INTCONbits.TMR0IF = 0;
    }   
}

今回はRB4の長押しも使用するので、
PINの初期設定の立上り、立下りの部分は不使用です。
さらに、割り込みもソフトPWM用の割り込みだけ残しました。

main()関数と実機の動き

Part.1

c

void main(void) {
    unsigned int adc_hard_raw;         //ハードPWMの現在のADC値用変数
    unsigned int adc_soft_raw;         //ソフトPWMの現在のADC値用変数
    unsigned int hard_offset;          //ハードの下限オフセット用変数
    unsigned int soft_offset;          //ソフトの下限オフセット用変数
    unsigned int ui_level = 0;         //長押し時の出力レベル用変数
    unsigned int ui_start = 0;         //長押し時のハードの下限オフセット格納変数
    signed char ui_dir = 1;            //明暗フラグ(1:明るくなる, -1:暗くなる)
    unsigned char long_press;          //長押しか短押しの判定フラグ
    PWM_Init();
    ADC_Init();
    SOFT_Pin_Init();

    unsigned char val_hard = EEPROM_Read(0);
    unsigned char val_soft = EEPROM_Read(1);

/***************************************************/
//前回は while(1)ループの中に入れていましたが、
//今回は実行の一度だけの読み込みとしました。

    if (val_hard == 0xFF) {
        hard_offset = 0;
    } else {
        hard_offset = ((unsigned int) val_hard) << 2;
    }

    if (val_soft == 0xFF) {
        soft_offset = 0;
    } else {
        soft_offset = ((unsigned int) val_soft) << 2;
    }
/***************************************************/

    while (1) {
        unsigned char sw_now = PORTBbits.RB4;   //スイッチの状態確認フラグ
        static unsigned char sw_prev = 1;       //チャタリング防止用 (※1)

        if ((sw_prev == 1) && (sw_now == 0)) {   //ボタンが押された瞬間
            press_time = 0;
            long_press = 0;
            ui_level = hard_offset;
            ui_start = hard_offset;
            ui_dir = 1;
        }
  
        if (sw_now == 0) {
            __delay_ms(10);

            if (press_time < 1000)  //スイッチの押している時間の判定
            {
                press_time++;       //押し時間のカウンタ
            }
            if (press_time > 50)    //長押しかどうか
            {
                long_press = 1;     //長押し確定
            }
         
            if (long_press) {
                if ((press_time % 3) == 0) {         //カウンタの3回に1回処理される
                    /**明るくする処理**/
                    if (ui_dir > 0) {
                        if (ui_level < 1023 - 4) { 
                            ui_level += 2;
                        } else {
                            ui_level = 1023;
                            ui_dir = -1;
                        }
                    /**暗くする処理**/
                    } else {
                        if (ui_level > ui_start + 4) {
                            ui_level -= 2;
                        } else {
                            ui_level = ui_start;
                            ui_dir = 1;
                        }
                    }
               }
            }
        }





        if ((sw_prev == 0) && (sw_now == 1)) {   //ボタンが離れた瞬間
            
            /***短押しの場合の処理***/
            if (long_press == 0) {
                hard_offset = Read_ADC(ch[0]);
                soft_offset = Read_ADC(ch[1]);
                EEPROM_Write(0, (unsigned char) (hard_offset >> 2));
                EEPROM_Write(1, (unsigned char) (soft_offset >> 2));
            }
            press_time = 0;
            long_press = 0;
         }
        sw_prev = sw_now;

        adc_hard_raw = Read_ADC(ch[0]);
        adc_soft_raw = Read_ADC(ch[1]);

        write_adc[0] = Apply_Offset(adc_hard_raw, hard_offset);
        write_adc[1] = Apply_Offset(adc_soft_raw, soft_offset);

        if (long_press) 
        {
            HARD_PWM_APPLY(ui_level);      //長押しの場合はui_levelを出力
        } else {
            HARD_PWM_APPLY(write_adc[0]);  //短押し又は押されていない時
        }
        SOFT_PWM_APPLY(write_adc[1]);
    }
}

今回はmain()のボリュームが大きめです。
長押しか短押しの判定をして、
長押しの場合は “ui_level” を出力させ、
短押しの場合は、adc値をEEPROMへ書き込み、
スイッチの処理が無い時は、”adc[0]”(ハード用vol1)の値を出力させます。

実際にこのコードで動かしてみました。

下限オフセットの設定はちゃんとできていることは分かります。
それは前回もできていたので、当然ですが。

とてもゆっくりすすんで調光しているかどうかは、
オシロスコープで見ていてなんとか分かるレベル。

そして途中で止まってしまっている…
修正が必要なようです。

Part.2

長押しのコード部分だけの修正でよさそうなので下のように変更しました。

c

        if (sw_now == 0) {
            __delay_ms(10);

            if (press_time < 1000)  //スイッチの押している時間の判定
            {
                press_time++;       //押し時間のカウンタ
            }
            if (press_time > 50)    //長押しかどうか
            {
                long_press = 1;     //長押し確定
            }
         
            if (long_press) {
                if ((press_time % 3) == 0) {         //カウンタの3回に1回処理される
                    /**明るくする処理**/
                    if (ui_dir > 0) {
                        if (ui_level < 1023 - 4) { 
                            ui_level += 4;           //←ここを変更
                        } else {
                            ui_level = 1023;
                            ui_dir = -1;
                        }
                    /**暗くする処理**/
                    } else {
                        if (ui_level > ui_start + 4) {
                            ui_level -= 4;           //←ここを変更
                        } else {
                            ui_level = ui_start;
                            ui_dir = 1;
                        }
                    }
               }
            }
        }

Part.1では3周に1回ui_levelを2ずつプラスしていたのを、
4ずつプラスするようにしたので、
倍の速度になったはずです!

動かしてみます!

下限オフセットはしっかりと効いています。
しかし早くなったような気はしますが、
全く実感できません。
今回は「下限オフセット → MAX」は、
途中で止まることなく進みました。
しかし「MAX →下限オフセット」で
途中で止まってしまいました。

止まってしまうのは、
根本的に別の問題がありそうです。
速度を早くしたことで、止まる位置が変わっていることも気になります。

Part.3

Part.3としていますが、実際にはかなりの試行錯誤しました。
今回行きついたベストは次のコードでした。

c

        if (sw_now == 0) {
//            __delay_ms(10);  //コメントアウト

            if (press_time < 1000)  //スイッチの押している時間の判定
            {
                press_time++;       //押し時間のカウンタ
            }
            if (press_time > 50)    //長押しかどうか
            {
                long_press = 1;     //長押し確定
            }
         
            if (long_press) {
//                if ((press_time % 3) == 0) {         //コメントアウト
                    /**明るくする処理**/
                    if (ui_dir > 0) {
                        if (ui_level < 1023 - 4) { 
                            ui_level += 4;           //←ここを変更
                        } else {
                            ui_level = 1023;
                            ui_dir = -1;
                        }
                    /**暗くする処理**/
                    } else {
                        if (ui_level > ui_start + 4) {
                            ui_level -= 4;           //←ここを変更
                        } else {
                            ui_level = ui_start;
                            ui_dir = 1;
                        }
                    }
//               }                                     //コメントアウト
            }
        }

動きはこんな感じです。

速度もいい感じで、
「下限オフセット → MAX → 下限オフセット → …」
と止まることなくスムーズに動いています。

速度が遅かった原因の一番は、
__delay_ms(10)でした。
そらそうですね。

さらに3周に1回にしていた
if ((press_time % 3) == 0)
が止まる原因のようでした。

これに関しては、なぜ止まってしまうのかは、
分かりません。

今回の学び

今回は、スイッチ入力によるUI実装に挑戦してみました。

正直、もっと簡単にできると思っていたのですが、
実際にやってみると、チャタリングや処理の止まりなど、
想像以上に難しいポイントが多くありました。

特に、「時間を止める処理(delay)」を使ってしまうと、
他の処理が動かなくなるという点は大きな学びでした。

スイッチ入力は単純なようでいて、
しっかりと設計しないと安定して動作しないことを実感しました。

次回は、この問題を解決するために、
ノンブロッキング処理や状態管理を取り入れた実装に挑戦していきます。

コメント

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