第22回 タッチセンサ(3) 〜タッチセンサ基本プログラム〜

今回は、PICマイコンでタッチ検知をするためのプログラムの基本構造を説明します。

目次

タッチ検知プログラムの基本構造

タッチ検知プログラムの詳細を説明する前に、プログラムの基本構造について説明します。

PICマイコンでタッチセンサを使用する場合、内蔵されているタッチセンサモジュールそのものの設定や処理をプログラムする必要がありますが、もう一つセンサ値をカウントするカウンタが必要です。このカウンタとして、タイマーモジュールを使用しています。

ということで、タッチセンサを使用する場合、タッチセンサモジュールとタイマーモジュールの設定や処理をプログラムします。

プログラムではこれらを以下のように使用します。

Pic app 22 program template

それではこれから、タッチセンサの設定、タイマーモジュールの設定、タッチ検知プログラムの基本構造を説明します。また、ピンの設定も必要ですので合わせて説明します。

ただ、タッチセンサモジュールの設定はちょっとわかりづらいところがありますので、概略について最初に説明しておくことにします。

タッチセンサモジュール設定の概略

タッチセンサモジュールの設定は、どのピンをタッチセンサに使用するかなどの基本設定以外に、センサモジュールの動作設定があります。この動作設定がちょっとわかりづらいので、最初に概略を説明しておきます。

繰り返しになりますが、センサに触れていないときと触れたときで静電容量が異なりますので、その静電容量を調べるために、電荷を満杯にして空にして、という動作を一定時間内に何回繰り返すことができるかをカウントする、ということを説明しました。

これをバケツに置き換えると、バケツに水を満杯にして→空にして→満杯にして、という動作を一定時間内に何回繰り返すことができるかを調べることに相当します。

PICマイコンのタッチセンサモジュールの設定では、バケツに例えた場合、以下の設定があります。

Pic app 22 bucket

  1. バケツの満杯と空を判断する水位の設定
  2. 水を供給するホースの太さの設定

(1)の設定では、通常は「100%が満杯・で0%が空」という判断ですが、例えば「80%溜まったら満杯・20%まで減ったら空」と判断する、というような設定ができます。このように満杯・空を80%・20%と判断するようにすると、実質的なバケツの容量は少なくなりますので、一定時間のカウント値は増えることになります。

同様に(2)の設定では、ホースの太さを調整、つまり水の供給量を変えることができます。例えば水の供給量を増やすと、一定時間のカウントは増えることになります。

ところで、なぜこのようにカウント値を増やしたり減らしたりする設定があるのでしょうか。

今回カウンタとして使用するTIMER1レジスタは16ビット、つまり65535までカウントできます。

例えば製作したセンサを動作させてみたところ、人が触れていない時はカウントが10000、触れた時はカウントが8000だとします。この場合、せっかく65535までカウントできるのに、10000以下で、8000〜10000の小さな変化を測定するのは勿体無いですよね。

そこで、ホースの太さを太くして満杯になる速度を早めたり、満杯・空の判断基準を調整してカウント数が増えるようにします。すると、人が触れていない時は30000、触れた時は25000、というように、触れたときと触れていないときの数値差が大きくなり、判定がより容易になります。

かなり細かい設定ですが、PICマイコンのタッチセンサはこのような設定を行うことも可能です。

これを踏まえて、各モジュールの設定について説明します。

ピンの設定

タッチセンサに使用するピンは、「デジタル」の「入力」に設定します。

ANSELAレジスタで該当ピンのビットを0にすることによりデジタルに設定します。

TRISAレジスタで該当ピンのビットを1にすることにより入力に設定します。

タッチセンサモジュールの設定

タッチセンサモジュールは、「CPSCON0」と「CPSCON1」レジスタで設定を行います。「CPSCON」は「Capacitive Sensing Control」(=静電容量センサ制御)の意味です。

以下にそれぞれのレジスタについて説明します。

まずCPSCON0レジスタです。このレジスタは8ビットで以下の構成です。

Pic app 22 cpscon0
(Microchip社 PIC12F1822データシートより引用)

CPSON
0に設定するとセンサ値計測停止、1にすると計測を開始します。センサ値の計測は、このビットを1にして計測開始、一定時間待って、このビットを0にして計測停止します。停止後、後ほど説明するタイマー変数にセンサ値が入っています。

CPSRM
ここでは、コンデンサに電気を貯める・空にする際のレベルを設定します。先ほど説明しました、バケツの例では満杯状態、空の状態の検知水位レベルをどうするかの設定を行います。

1に設定すると、コンデンサに電荷を満杯にした状態を「満杯」、空の状態を「空」と判定します。0に設定すると、満杯と空の判定基準を別途設定します。(設定の詳細は省略します)

この設定は最初に説明した、バケツの満杯・空の判定基準の設定になります。

CPSRNG
センサ値を計測する際の電流値を指定します。この設定は最初に説明した、バケツに水を供給するためのホースの太さの設定になります。

CPSRNG 電流の大きさ
00 OFF
01
10
11

CPSOUT
このビットは読み取り専用です。1の時はコンデンサに電荷を貯めるために電流を供給している状態、0の時はコンデンサから電荷を放出している状態を意味します。

T0XCS
センサ値をカウントするためにTIMER0レジスタを使用しているときの設定です。今回はTIMER1を使用するため説明は省略させていただきます。

次に、CPSCON1レジスタです。このレジスタは8ビットで以下の構成です。

Pic app 22 cpscon1
(Microchip社 PIC12F1822データシートより引用)

このレジスタでは、タッチセンサに使用するピンを選択します。CPSCHは4ビットで以下の意味になります。

CPSCH センサ PIC12F1822ピン番号
0000 CPS0 7番ピン
0001 CPS1 6番ピン
0010 CPS2 5番ピン
0011 CPS3 3番ピン

タイマー1モジュールの設定

ここでは、タイマーモジュールの設定レジスタのうち、タッチセンサに関わるレジスタのみ説明します。

タイマー1をタッチセンサのカウンタとして使用する場合、設定はT1CON(Timer1 Control)レジスタで行います。特に難しい設定なく、「Timer1をタッチセンサのカウンタとして使用します」という設定だけです。

Pic app 22 t1con

TMR1CS
Timer1でカウントする際のソース(元となる信号)を指定します。2ビットで以下の意味です。タッチセンサを使用する場合は、TMR1CSを0b11に指定することになります。

TMR1CS 意味
11 タッチセンサ信号をソースとする
10 外部からピンに供給される信号をソースとする
01 システムクロックをソースとする
00 実行クロック(システムクロックの4分の一)をソースとする

T1CKPS
入力信号の分周比を指定します。タッチセンサの場合は特に問題なければ1:1の指定で問題ありません。「分周」という言葉はこのシリーズでは出てきていませんが、簡単に説明すると「元の信号を、何回分を1回として数えるか」という意味です。タッチセンサモジュールからの信号は満杯が1、空が0という信号の繰り返しです。1回の繰り返しを1回と数えるか、2回の繰り返しを1回と数えるか、など、計測値を減らしたいときに使用します。

T1CKPS 意味
11 1:8
10 1:4
01 1:2
00 1:1

また、タイマーは割込み処理により動作しますので、割込み許可をします。割込み許可は周辺モジュールの割込み許可をする「PEIEレジスタ」と全体の割込み許可をする「GIEレジスタ」の両方を1にします。

もう少し具体的なプログラム構造

タッチセンサの設定に関するレジスタがわかりましたので、最初に説明したプログラム構造をもう少し具体的にすると以下のようになります。実際のプログラムはこの構造に加えて、もう少し色々な処理が入りますので、まずはこの基本構造を頭に入れておきましょう。

Pic app 22 program template 2

タッチ検知プログラムのサンプル

以上を踏まえて、タッチ検知を行う場合の典型的なプログラム例を以下に示します。プログラム動作としては、PIC12F1822の7番ピンをタッチセンサ接続ピンとして、タッチが検知された場合RA5ピンに接続されているLEDを点灯する、というものです。

基本的なプログラム構造としては、単にタッチセンサのカウントを開始、一定時間待ってカウントを停止してカウント値に応じた処理をすればいいですが、実際にプログラムを作る際にはもう少し工夫が必要です。

というのは、タッチセンサのカウント値は、定常時、タッチ時ともに環境によって異なります。同じ場所でも周りの環境によって定常時の数値が異なったり、タッチセンサに触れる人によってタッチ時の数値が異なります。

そのため、タッチ判定のやり方として、以下の方法があります。

  1. プログラム起動時など、タッチしていない状態でタッチセンサ値を何回か測定し、その平均値を定常時の数値とする(キャリブレーション)
  2. 次に、キャリブレーションした値の0.9倍の値をタッチした値とする
  3. タッチセンサ値を読み取り、タッチ判定を行う

このような動作を実装した基本的なタッチ検知プログラムは以下になります。

新規プロジェクトを作成し、新規main.cファイルを作成後、以下のプログラムをコピペしてビルド、現在製作しているタイマー回路に書き込むと動作します。7番ピンにジャンパワイヤなどを接続し、そのワイヤを触ると反応するはずです。

/*
 * File:   main.c
 * プログラム内容
 *    タッチセンサを使用して、タッチされている間LEDを点灯する
 *    タッチセンサピンは7番ピンを使用
 */

#include <xc.h>
#include <stdbool.h>
#define _XTAL_FREQ 1000000 

// 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 = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will 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)


// main
//
void main(void){

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

    unsigned char  count;   // for文カウントに使用
    
    bool sw_status = false; // スイッチ状態格納

    // 内部クロックとピン属性設定
    OSCCON = 0b01011000;    // 内部クロックを1MHzに設定
    ANSELA = 0;             // PortAをデジタルI/Oに設定
    TRISA  = 0b00001111;    // RA4, RA5ピン以外入力ピンに設定
    
    // タッチセンサ(Capacitive Sensor)の設定
    CPSCON0 = 0b00001000 ;   // 固定基準電圧使用、オシレータ(電流)は中に設定
    CPSCON1 = 0b00000000 ;   // センサピンにCPS0(7番ピン)を使用

    // タイマー1の設定
    T1CON   = 0b11000001 ;   // 容量検知オシレータでTIMER1をカウント、プリスケーラ1:1
    TMR1    = 0 ;            // タイマー1の初期化
    
    // 割込み設定
    PEIE    = 1 ;          // 周辺装置割り込みを許可する
    GIE     = 1 ;          // 全割り込み処理を許可する 

    //
    // キャリブレーション
    //   最初に10回測定してスイッチタッチのしきい値を決める
    //

    // キャリブレーション中はLED点灯
    LATA5 = 1;

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

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

        // 検知モジュールの値を読み込む
        CPSON = 0 ;                 // タッチセンサモジュール計測停止
        cap_total += TMR1 ; // カウント値を積算
    }
    
    // しきい値を10回の平均値の90%にする
    cap_threshold = (cap_total / 10) * 0.9;

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

    //
    // タッチ検知処理
    //
    while(1){
        // 検知開始
        TMR1  = 0 ;  // タイマー1の初期化
        CPSON = 1 ;  // タッチセンサモジュール計測開始

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

        // タッチセンサモジュールの計測を停止して、センサ値を一旦cap_valueに格納
        CPSON = 0 ;        // タッチセンサモジュール計測停止
        cap_value = TMR1 ; // カウント値を読み込む
        
        // タッチ判定
        if( sw_status == false) {
            // 現在がOFFだったらON判定をする
            // ONの場合はLEDを点灯する
            if( cap_value < cap_threshold) {
                LATA5 = 1;
                sw_status = true;
            }
        } else {
            // 現在がONだったらOFF判定をする
            // OFFの場合はLEDを消灯する
            if( cap_value > cap_threshold) {
                LATA5 = 0;
                sw_status = false;
            }
        }        
    }


}

次回は、タイマープログラムのスタートボタンをタッチセンサに変更したプログラムを作成します。

更新履歴

日付 内容
2017.11.13 新規投稿
通知の設定
通知タイミング
guest
0 コメント
本文中にフィードバック
全てのコメントを見る
やまね
やまね
2 年 前

こんにちは.
どのサイトより親切に解説されていて,いつも参考にさせて頂いております.

現在こちらの記事を参考に,静電センサを使う工作をしています.チャタリング防止のプログラムを実装したい都合でセンサ閾値をPC上で取得したいと考え,シリアル通信を試しているのですが,そこで躓いています.
I2C通信とSPI通信についてはサイト内で解説してくださっているのですが,もし新しく記事を作成される機会があれば,12F1822を用いてシリアル通信を行う記事があれば大変ありがたいです.
ご検討のほどお願い致します。

管理者
管理者
返信  やまね
2 年 前

このシリーズの実践編で16F18857を使用してI2C通信やSPI通信を解説しています。使用しているPICマイコンは16F18857ですが、12F1822でも設定内容はほとんど同じです。

あまり慣れていないと12F1822で動作させるのは難しいかもしれませんが、特に実践編のSPI通信はPICマイコンの機能を使用せずに、プログラムで動作させていますので、参考にされてみてください。

このサイトは2022年よりコンテンツを拡充していきますので、引き続きよろしくお願いいたします。

junjun
junjun
5 年 前

毎回詳細な解説と迅速かつ丁寧な回答を ありがとうございます。

いつものように また迷路に迷い込んで もがいておりますが、
今回は入り口で幾つか質問が ございます。

・#include が必要なのでは?
・37行目: int main(int argc, char** argv) {  かと?
・50行目: TRISA = 0b00001111;  ← RA5を出力にするためには
 ちなみに本文中 “4番ピンに接続されているLED” という記述になっている
 部分があります。

C言語にも疎いので、初お目見えの bool にも少々戸惑っております。

管理者
管理者
返信  junjun
5 年 前

junjunさま、
ご指摘、ご質問どうもありがとうございます。

・#includeについて
 stdio.hの関数は使用していませんので書く必要はありません(書いても害はないですが…)。また、stdlib.hも使用していませんのでサンプルコードから削除しました。以前のMPLABX IDEで新規ファイルでmain.cを作成すると、stdio.hとstdlib.hの#include文が自動的に書かれていたのですが、それを消していませんでした。使用していないヘッダファイルを書いておくと紛らわしいので、サンプルコードから削除しました。

・mainについて
 PICマイコンのmain関数は誰からも呼ばれず、誰かに何か返すこともありませんので、int main(int argc, char** argv)にする必要はありません。MPLABX IDEのVersion4.15で確認しましたが、新規ファイルでmain.cを作成すると、void main(void)で作成されます。おそらくPICマイコンのmain関数は誰からも呼ばれることはないので、int main(int argc, char** argv)と書くのはおかしいと判断されているのだと思います。なお、Mac/Linux/WindowsのCプログラムでは、コマンドラインなどからmain関数が呼ばれるため、必ずint main(int argc, char **argv)にする必要があります。

・TRISA
 設定が間違っていました。0b00001111が正解です。ご指摘どうもありがとうございました! 助かります!

junjun
junjun
返信  管理者
5 年 前

早速のご回答 ありがとうございます。

見よう見まねで ここまでやって来たために、
何が分かっていないのか? が分かっていない状態です。

自分なりに解釈してみると、
xc.h を include しておけば stdio.h および stdlib.h に関しては
不要ということでしょうか?

また基礎編に書かれていた コードなども基本構造としては
 #include
 Configuration 設定
 void main(void) {
 return;
 }
と考えればよろしいでしょうか?

ご回答に対する再質問で申し訳ありません。
よろしくお願い致します。

管理者
管理者
返信  junjun
5 年 前

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

stdio.hとstdlibは応用編のプログラムでは使用しませんのでxc.hのみのインクルードで問題ありません。また基本構造としては、書いていただいた内容で問題ありません。(実践編ではprintf文を使用するためstdio.hをイクルードします)

このような疑問を持たれるとより深く理解できますので、今後ともわからないことをそのままにせずに自分のものにしながら進めていただければと思います。

なお、この入門シリーズは電子工作については全く知識がなくても大丈夫なように書いていますが、C言語については第1回の記事でもリストしておりますが、以下の知識を前提としています。

コメント文の書き方
#define、#include文
関数
変数型宣言、変数代入、演算子
構造体
繰り返し構文(while文、for文)
条件判断構文(if文、switch文)
2進数、16進数

もしこれらの知識に自信がないようでしたら、もう一度C言語の入門書を読まれることをお勧めします。実践編ではプログラムがさらに複雑になりますし、今後、自分の作りたいものを作るようになるにはどうしてもこれらの知識は避けて通ることはできません。

PICマイコンを始め、Arduino、Raspberry Piを使う場合、C言語では上記知識程度があれば自分の作りたいものが十分作れますので、ぜひC言語を自分のものにしていただければと思います。

目次