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

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

目次

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

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

PICマイコンでタッチセンサを使用する場合、次の設定や処理が必要です。

  • 内蔵されているタッチセンサモジュールの設定
  • タイマーモジュールの設定(割り込みを使用するため同時に割り込み設定も行う)
  • タッチセンサーの処理

タッチセンサーモジュールを使用するための設定が必要、というのは理解できますが、「❷ タイマーモジュールの設定」ってちょっと?ですよね。

これは、タッチセンサーで検知するときに「一定時間内にバケツに何回電荷を満杯にできるか」という計測をしますが、この計測のときにタイマーモジュールを使用するためです。


プログラムではこれらの設定や処理が必要ですので、タッチセンサープログラムの基本構造は次のようになっています。

Pic app 22 program template

それではこれから、タッチセンサの設定、タイマーモジュールの設定、タッチ検知処理の方法を説明します。(ピンの設定も必要ですので合わせて説明します)

ところで、タッチセンサモジュールの設定はちょっとわかりづらいところがあります。

そこで、その設定の概略について最初に説明します。

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

タッチセンサモジュールの設定は、どのピンをタッチセンサに使用するかなどの基本設定以外に、センサモジュールの動作設定があります。

この動作設定がちょっとわかりづらいので、最初にイメージをつかんでおきたいと思います。


ちょっとしつこいですが、もう一度タッチ検知の方法を確認しておきましょう。

タッチ検知は、人がセンサに触れていないときと触れたときで静電容量が異なる性質を利用します。

その静電容量を調べるために、一定時間内に「電荷を満杯にして空にする」という処理を何回繰り返すことができるかをカウントする、という処理を行います。

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

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

Pic app 22 bucket
  • バケツの満杯と空を判断する水位の設定
  • 水を供給するホースの太さの設定

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

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


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

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

例えば製作したセンサを動作させてみたところ、人が触れていない時はカウントが10000、触れた時はカウントが8000だとします。

この場合、せっかく65535までカウントできるのに、10000以下で、8000〜10000の小さな変化を測定するのは勿体無いですよね。

そこで、ホースの太さを太くして満杯になる速度を早めたり、満杯・空の判断基準を調整してカウント数が増えるようにします。

すると、人が触れていない時は30000、触れた時は25000、というように、触れたときと触れていないときの数値差が大きくなり、判定がより容易になります。

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

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

設定方法

それでは、「ピンの設定」「タッチセンサーモジュールの設定」「タイマーモジュールの設定」について順番に説明していきます。

ピンの設定

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

デジタルに設定するには、ANSELAレジスタの該当ピンのビットを0にします。

入力に設定するには、TRISAレジスタの該当ピンのビットを1にします。

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

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

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

CPSCON0レジスタ

最初はCPSCON0レジスタです。このレジスタは8ビットで次の構成になっています。

(Microchip Technology社「PIC12F1822データシート」より引用)

上位ビットから設定内容を確認しましょう!

CPSON
1にすると計測を開始、0に設定すると計測を停止します。センサ値の計測は、このビットを1にして計測開始、一定時間待って、このビットを0にして計測停止します。停止後、後ほど説明するタイマーレジスタにセンサ値(満杯と空を繰り返した回数)が入っています。

CPSRM
この設定は最初に説明した、バケツの満杯・空の判定基準の設定になります。
先ほど説明したバケツの例で、満杯状態、空の状態の検知水位レベルをどうするかの設定を行います。

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

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

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

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

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

CPSCON1レジスタ

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

(Microchip Technology社「PIC12F1822データシート」より引用)

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

CPSCH設定値使用する
センサーモジュール
ピン番号
0000CPS07
0001CPS16
0010CPS25
0011CPS33

なんだか眠くなってきちゃいますよね。

まだ続きますので、この辺りで一度お茶の時間にしましょう!

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

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

タイマー1をタッチセンサのカウンタとして使用する場合、設定はT1CON(Timer1 Controlの意味)レジスタで行います。

特に難しい設定なく、「Timer1をタッチセンサのカウンタとして使用します」という設定だけです。

(Microchip Technology社「PIC12F1822データシート」より引用)

TMR1CS
タイマー1でカウントする際のソース(元となる信号)を指定します。

2ビットで以下の意味です。

タッチセンサを使用する場合は、TMR1CSを0b11に指定することになります。

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

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

T1CKPS設定値設定内容
111:8
101:4
011:2
001:1

このタイマーは割込み処理により動作しますので、割込み許可する必要があります。

割込み許可は周辺モジュールの割込み許可をするPEIEレジスタと全体の割込み許可をするGIEレジスタの両方を1にします。

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

タッチセンサの設定に関するレジスタがわかりましたので、最初に説明したプログラム構造をもう少し具体的にしてみました。

実際のプログラムはこの構造に加えて、もう少し色々な処理が入りますので、まずはこの基本構造を頭に入れておいていただければと思います。

Pic app 22 program template 2

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

以上を踏まえて、タッチ検知を行う場合の典型的なプログラム例を作成してみました。

プログラム動作としては、PIC12F1822の7番ピンをタッチセンサ接続ピンとして、タッチが検知された場合にRA5ピンに接続されているLEDを点灯する、というものです。

基本的なプログラム構造としては、カウンタの値(バケツの満杯と空の繰り返し回数)を調べればよいのですが、実際にプログラムを作る際にはもう少し工夫が必要です。

というのは、タッチセンサのカウント値は、定常時、タッチ時ともに環境によって異なります

同じ場所でも季節によって異なったり、周りの環境によっても異なります。

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

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

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

新規プロジェクトを作成し、新規main.cファイルを作成後、以下のプログラムをコピペしてビルド、現在製作しているタイマー回路に書き込むと動作します。7番ピンにジャンパワイヤなどを接続し、そのワイヤを触ると反応すると思いますが、うまく反応しない場合はタッチした値の判定値(0.99倍)を変更してみてください。(LEDが点灯したままの時は0.99より小さい値(0.98など)、LEDが消灯したままの場合は0.99より大きい値(0.992など)にしてみてください)

/*
 * PICマイコン電子工作入門 応用編 第22回
 *   タッチセンサのプログラム構造理解のためのプログラム
 */

#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){

    uint16_t cap_value;       // タッチセンサ値格納用
    uint16_t cap_threshold;   // タッチ判定のしきい値格納用
    uint32_t cap_total;       // キャリブレーション計算用
    
    bool led_status = false;  // LED点灯状態格納

    // 内部クロックとピン属性設定
    OSCCON = 0b01011000;    // 内部クロックを1MHzに設定
    ANSELA = 0;             // PortAをデジタルI/Oに設定
    TRISA  = 0b00011111;    // 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(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;

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

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

        // タッチセンサモジュールの計測を停止して、センサ値を一旦cap_valueに格納
        CPSON = 0 ;        // タッチセンサモジュール計測停止(バケツの満杯と空の繰り返しを停止)
        cap_value = TMR1 ; // カウント値を読み込む(バケツの満杯と空の繰り返し回数をcap_valueに代入)
        
        // タッチ判定
        if( led_status == false) {
            // LEDがOFFだったらON判定をする
            // ONの場合はLEDを点灯する
            if( cap_value < cap_threshold) {
                LATA5 = 1;
                led_status = true;
            }
        } else {
            // LEDがONだったらOFF判定をする
            // OFFの場合はLEDを消灯する
            if( cap_value > cap_threshold) {
                LATA5 = 0;
                led_status = false;
            }
        }        
    }


}

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

更新履歴

日付内容
2017.11.13新規投稿
2025.5.29XC8 Version3.00で動作確認
タッチ判定の閾値を変更
説明内容整理
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
やまね
やまね
4 年 前

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

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

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

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

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

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

junjun
junjun
7 年 前

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

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

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

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

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

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
返信  管理者
7 年 前

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

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

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

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

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

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

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言語を自分のものにしていただければと思います。

目次