第23回 タッチセンサ(4)〜タイマープログラム実装〜

今回は、タイマープログラムのスタートボタンをタッチスイッチに変更します。

目次

変更内容

前回の記事で、タッチセンサの基本プログラムを作りました。

今回は、タイマープログラムのスタートスイッチをタッチスイッチに変更します。


現在、RA3ピンに接続しているスイッチの状態を確認して、スイッチが押されたらタイマーカウントを開始しています。この部分をタッチセンサの判定に置き換えます。

プログラムをあまり変更しないように、while(RA3)でスイッチ判定している部分を以下のように変更することにします。

Pic app 23 program structure

タッチスイッチに変更したプログラム

タッチセンサー検知のためのプログラムは前回の記事で作成しました。

そのプログラムをタイマースタート部分に差し替えてみました。


次回はタッチセンサの補足説明を行います。

/*
 * PICマイコン電子工作入門 応用編 第23回
 *   スタートボタンをタッチセンサーに変更
 */

#include <xc.h>
#include <stdbool.h>

// PIC12F1822 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF     // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)


// クロック周波数指定
// __delay_ms()関数が使用する
#define _XTAL_FREQ 1000000

// 割り込み関数のプロトタイプ宣言
void __interrupt() isr(void);

// EEPROM初期データ
// アドレス0はタイマー設定値の初期値、残りはダミーデータ
__EEPROM_DATA (0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

// タイマー関連グローバル変数
uint8_t timerValue; // タイマー設定時間 (main関数の最初でEEPROMから設定値を読み出し、この変数に入れておく)
uint8_t timerCount; // 現在のタイマー残り時間 (main関数ではこの値を減らしながらタイマーカウント。割込み処理関数でtimerValueに戻す)

void main(void) {

    uint16_t cap_value;       // タッチセンサ値格納用
    uint16_t cap_threshold;   // タッチ判定のしきい値格納用
    uint32_t cap_total;       // キャリブレーション計算用

    // PICマイコン設定
    OSCCON = 0b01011010;  // 内部クロック周波数を1MHzに設定
    ANSELA = 0b00000100;  // RA2ピンをアナログ、それ以外のピンはデジタルに設定
    TRISA  = 0b00001111;  // RA4、RA5ピン以外入力ピンに設定

    // ADコンバータ設定
    ADCON0 = 0b00001001;  // RA2(AN2)をADコンバータピンに設定し、ADコンバータ機能をEbableにする
    ADCON1 = 0b10000000;  // 結果数値は右寄せ、ADコンバータクロックはFOSC/2、基準電圧はVDD

    // タッチセンサ(Capacitive Sensor)の設定
    CPSCON0 = 0b00001000 ;   // 固定基準電圧使用、オシレータ(電流)は中に設定
    CPSCON1 = 0b00000000 ;   // センサピンにCPS0(7番ピン)を使用

    // タイマー1の設定
    T1CON   = 0b11000001 ;   // 容量検知オシレータでTIMER1をカウント、プリスケーラ1:1
    TMR1    = 0 ;            // タイマー1の初期化

    // ブザーをOFFにする
    LATA4 = 0;
    
    // 少し時間待ち
    __delay_ms(1000);

    // 割込み設定
    PEIE    = 1 ;          // 周辺装置割り込みを許可する
    GIE     = 1 ;          // 全割り込み処理を許可する 

    //
    // キャリブレーション
    //   最初に10回測定してスイッチタッチのしきい値を決める
    //
    // キャリブレーション中はLED点灯
    LATA5 = 1;

    // 10回測定 
    cap_total = 0;
    for(uint8_t i=0; i<10; i++) {
        // 検知開始
        TMR1  = 0 ;                 // タイマー1の初期化
        CPSON = 1 ;                 // タッチセンサモジュール計測開始

        // 一定時間待つ
        __delay_ms(100) ;

        // 検知モジュールの値を読み込む
        CPSON = 0 ;                 // タッチセンサモジュール計測停止
        cap_total += TMR1 ; // カウント値を積算
    }

    // しきい値を10回の平均値の90%にする
    cap_threshold = cap_total / 10 * 0.99;

    // キャリブレーション終了後はLED消灯
    LATA5 = 0;

    // タイマー設定時間をEEPROMから読み出す
    //   timerValueは1バイトにしています
    timerValue = eeprom_read(0);

    // タイマー時間をLEDの点滅回数で表現する
    //
    // 動作開始後少し待つ
    __delay_ms(500);

    // 設定時間分LEDを点滅する
    for(uint8_t i=0; i<timerValue; i++) {
        LATA5 = 0;
        __delay_ms(200);
        LATA5 = 1;
        __delay_ms(200);
    }

    // 少しの間LEDをOFFにする少しの間LEDをOFFにする
    LATA5 = 0;
    __delay_ms(800);

    // LEDを点灯する
    LATA5 = 1;            
    
    // タッチが検知されるまで、タイマー時間増減スイッチ処理を行う
    bool touch_status = false;
    
    while( touch_status == false ) {

        // タッチセンサー検知開始
        TMR1  = 0 ;  // タイマー1の初期化
        CPSON = 1 ;  // タッチセンサモジュール計測開始(バケツの満杯と空の繰り返しを開始)

        // 一定時間待つ
        __delay_ms(100) ;

        // タッチセンサモジュールの計測を停止して、センサ値を一旦cap_valueに格納
        CPSON = 0 ;        // タッチセンサモジュール計測停止(バケツの満杯と空の繰り返しを停止)
        cap_value = TMR1 ; // カウント値を読み込む(バケツの満杯と空の繰り返し回数をcap_valueに代入)
        
        // タッチ判定
        if( TMR1 < cap_threshold ) {
            touch_status = true;
        }

        // ADコンバータ読み取り
        GO = 1;     // 読み取り依頼
        while(GO) { // 読み取り完了待ち
        }
        
        // ADRESの数値に応じてタイマー時間の増減を行う
        //
        // タイマー時間を減らすスイッチの処理(ADRESが250未満の場合)
        if( ADRES < 250 ) {
            // タイマー時間を減らす(マイナスにならないようにする)
            if( timerValue > 0 ) {
                timerValue--;
            }

            // タイマー時間をLEDの点滅回数で表現する
            //
            // 少しの間LEDをOFFにする
            LATA5 = 0;
            __delay_ms(500);
            
            // 設定時間分LEDを点滅する
            for(uint8_t i=0; i<timerValue; i++) {
                LATA5 = 0;
                __delay_ms(200);
                LATA5 = 1;
                __delay_ms(200);
            }

            // 少しの間LEDをOFFにする少しの間LEDをOFFにする
            LATA5 = 0;
            __delay_ms(800);

            // LEDを点灯する
            LATA5 = 1;            
        } else {
            // タイマー時間を増やすスイッチの処理(ADRESが250以上750未満)
            if( ADRES < 750 ) {
                // タイマー時間を増やす(上限チェックはしない)
                timerValue++;
                
                // タイマー時間をLEDの点滅回数で表現する
                LATA5 = 0;
                __delay_ms(500);
                
                for(uint8_t i=0; i < timerValue; i++) {
                    LATA5 = 0;
                    __delay_ms(200);
                    LATA5 = 1;
                    __delay_ms(200);
                }
                
                LATA5 = 0;
                __delay_ms(800);
                
                LATA5 = 1;
            }
        }
    }

    // タイマー設定値をEEPROMに書き込む
    eeprom_write(0, timerValue);

    // RA3割り込み設定
    IOCAF3 = 0;  // RA3割り込みフラグをクリアしておく
    IOCIE  = 1;
    IOCAN3 = 1;
    // GIE    = 1;  // タッチセンサーの時に設定済み

    // timerCountをタイマー設定時間(timerValue)に設定
    timerCount = timerValue;

    // LEDをtimerValue分点滅する (timerCountが1以上であれば継続、0であればカウント終了)
    while( timerCount ) {
        timerCount--;
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);            
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);
    }

    // 割り込み禁止 (割り込みはRA3ピンのみなので、GIEのみで禁止する)
    GIE = 0;

    // アラーム音を3回鳴らす
    for(uint8_t i=0; i<3; i++) {
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピーッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        // 1回分の音パターンの間を少し空ける
        __delay_ms(800);
    }

    // LEDをPWM制御してスムーズな点滅制御をする
    //
    // PWM機能のピン割り当て設定
    APFCONbits.CCP1SEL = 1;     // PWM機能をRA5ピンに設定
    CCP1CONbits.CCP1M = 0b1100; // PWM機能を有効、active-highに設定
    CCP1CONbits.P1M = 0b00;     // RA2ピンはGPIOに設定

    // 周期とデューティーサイクルの設定
    //   周期は1ms
    //   デューティーサイクルは100%から開始するので1msに設定
    T2CONbits.T2CKPS = 0b00;    // プリスケーラ値を1に設定
    PR2 = 249;                  // 周期を1msに設定 (249 + 1) x 4 x 1us = 1000us = 1ms
    CCPR1L = 1000/4;            // デューティーサイクルを0.5msに設定
    CCP1CONbits.DC1B = 1000 & 0b11;

    // PWM制御スタート
    T2CONbits.TMR2ON = 1;

    // LEDをPWM制御
    // デューテー比は10%〜100%の制御にする
    while(1) {
        // 100% -> 5%の制御
        for(uint16_t duty=1000; duty>=50; duty--) {
            CCPR1L = (uint8_t) (duty / 4);    // 上位8ビット
            CCP1CONbits.DC1B = duty & 0b11; // 下位2ビット
            __delay_ms(1);
        }
        // 5% -> 100%の制御
        for(uint16_t duty=50; duty<=1000; duty++) {
            CCPR1L = (uint8_t) (duty / 4);    // 上位8ビット
            CCP1CONbits.DC1B = duty & 0b11; // 下位2ビット
            __delay_ms(1);
        }
    }

    // 以下の命令は実行されない
    return;
}

// 割り込み処理関数
void __interrupt() isr(void)
{
    // タイマー測定時間リセット
    timerCount = timerValue;

    // LEDが点灯中に割り込みが発生することもあるので、
    // 少しの間LEDをOFFにする
    // ただし、割り込み処理内で時間がかかる処理を入れるのは本来は良くないです
    LATA5 = 0;
    __delay_ms(300);
    
    // 短く3回点滅
    for( uint8_t i=0; i<3; i++) {
        LATA5 = 0;
        __delay_ms(100);
        LATA5 = 1;
        __delay_ms(100);
    }

    // 少しの間LEDをOFFにする
    // この処理も時間がかかるので本来は避けるべきです
    LATA5 = 0;
    __delay_ms(300);

    // 割り込み処理フラグクリア
    IOCAF3 = 0; 

}

更新履歴

日付内容
2017.11.16新規投稿
2018.12.2プログラムテンプレートをMPLABX IDE v5.10のものに変更
2024.2.1割り込み関数名を__interruptに変更
2025.5.30プログラム変更
MPLABX IDE6.25・XC8 Version3.00で動作確認
通知の設定
通知タイミング
guest
7 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
AKIO
AKIO
1 年 前

お世話になります。初心者です。
次の2か所で4件のエラーがでます。すみませんが教えていただけますでしょうか。
(1か所目)
// 割り込み関数のプロトタイプ宣言
void interrupt isr(void);
(1か所目のエラー)
main001.c:45:6: error: variable has incomplete type ‘void’
void interrupt isr(void);
   ^
main001.c:45:15: error: expected ‘;’ after top level declarator
void interrupt isr(void);
(2か所目)
// 割り込み関数
void interrupt isr(void)
(2か所目のエラー)
main001.c:296:6: error: variable has incomplete type ‘void’
void interrupt isr(void)
   ^
main001.c:296:15: error: expected ‘;’ after top level declarator
void interrupt isr(void)

AKIO
AKIO
返信  管理者
1 年 前

コンパイルできました。お礼申し上げます。

大日向幹夫
大日向幹夫
4 年 前

お世話になります。まったくの初心者です。
基礎編、応用編と兎に角やらなければならないと思い第23回まで見よう見まできましたが、
第23回まできましたが、ビルドでエラーが4個でて困っています。

82 bool touch_status = false;

エラー内容
error: use of underclared indentifier ‘ bool ‘

145 while( touch_status == false) {

エラー内容
error use of underclared identier ‘ touch_status ‘

error use of underclared idetifier ‘ fales ‘

159 touch_status = true ;

エラー内容
error use of underclared idetifier ‘ touch_status ‘

263 T2CONbits . TMR2ON = 500

warning from ‘ int ‘ to bit-field change value from 500 to 0[-wbitfield-constant-conversion]

以上です。よろしくお願いいたします。

管理者
管理者
返信  大日向幹夫
4 年 前

ご質問どうもありがとうございます。

最新版のXC8を確認したところ、stdint.hでbool型の定義がなくなっていました。そのため、ビルド時に「bool」型の解釈ができずにエラーになっています。また、「fasle」の定義もなくなっているようです。

最新版では、bool型の定義は「stdbool.h」に移されていました。お手数ですが、stdbool.hもインクルードしていただけますでしょうか。このページのソースコードは後ほど更新いたします。

大日向幹夫
大日向幹夫
返信  管理者
4 年 前

ご返信ありがとうございます。返信が遅くなり申し訳ありません。
ご指摘の通り「stdbool.h」をインクルードしましたら、ビルドは成功しました。
ありがとうございました。取り敢えず応用編を理解するために、行ったり来たりし
て苦闘しています。基礎編からプログラムに追加する方法でタッチセンサまで来ましたが、一旦整理するために、タッチセンサまでのプログラムをコピペしてビルドしたところ、タッチセンサのプログラムでエラーがでました。
41行と294行の void interrupt isr(void) → void __intterrupt( ) isr(void)でビルドできましたのでご報告いたします。このサイトは懇切丁寧で私のようなまったくの初心者でも何とか付いていけそうです。今後とも宜しくお願い致します。

管理者
管理者
返信  大日向幹夫
4 年 前

無事ビルドできたとのことでよかったです!

また、さらなるご指摘をいただき感謝いたします。stdbool.hの件も含めて、早めにブログ記事を更新するようにいたします。

それにしても、ソフトウエアの世界は人間が仕様を変えられるので、変化に追いつくのが大変です。

それではまた分からないことなどございましたらコメントいただければと思います。

目次