目次
今回の必要素材
- PIC16F1938
- ブレッドボード
- I2C接続LCD
- LED
- ジャンパケーブル
- 抵抗(100Ω)
必要な前提知識
- PICの基礎(動けばいい人のためのPICマイコン入門編)
- I2C接続LCDの使いかた
今回作る工作(動画)
PICのピンに繋がった抵抗の足を指で触れると、LEDが点灯します(タッチスイッチ)。LCDの値が指の触れる前後で変動していることが分かりますね。
タッチセンサ
今回はタッチセンサを扱っていきます。タッチセンサとはその名の通り、タッチされたことを感知するセンサです。このサイトで扱っているPIC16F1938にはもともとタッチセンサ機能が付いています。しかも、拡張パーツなしでこのセンサが利用できます。この技術をMicrochip社はmTouch
mTouch とは
簡単に言えば、mTouchの機能が割り当てられているピンのコンデンサとしての容量を数値化するという機能です。コンデンサというのは、そのピンと空気中との間に電気が溜まることを指します。この電気が溜まるまでの時間を勝手に測ってくれると思えばOKです。
例えば、このピンに巨大な電極(ただの四角い鉄板でもなんでもいい)がつながっていれば、充電には時間がかかるのでこの数値は変化します。つまり、何かがピンに触れたことを感知出来るのです。人間も導電性がありますから、巨大な電極として働き、この値がかなり大きく変動します。よって、例えば「mTouchの値が100以下ならLEDをオンにする」というプログラムを書けば、人間がそのピンを触った時にLEDをオンにするように動かせるということです。タッチスイッチですね。
もう少し詳しい説明(飛ばしてもOK)
実際のmTouchの動きは若干直感と異なり、指が触れた時に値が小さくなります。この原理に関しては、いつもお世話になっているこちらのサイトの開いてすぐ下に書いてあります。つまり、ある時間(例えば1秒間など)に何回充放電ができたかをカウントして、その値を返すということをしてくれます。当然指を触れた方が電荷が多く溜まって充放電に時間がかかりますから、1秒間にできる充放電の回数は少なくなります。よって、指が触れた方がmTouchの値は小さくなるわけです。
実際にプログラムで使うときは、指が触れたらどのくらいまで値が下がるのかを確かめる必要があります。人によっても値は違うかもしれませんね。
センサの値を表示する
今回使う回路図
では早速mTouchの技術を使ってみましょう。これから各関数を紹介していきますが、内容がある程度理解できればいいので、実際にプログラムを書くときは目次最後の完成プログラムからコピペして、自分のやりたいように関数の中身を変えたりするのが早いかと思います。
PICinit()
ほぼこのサイトの他の記事のコピペですが、一部変わっているところがあります。
void PICinit(){
OSCCON = 0b01110000;
ANSELA = 0b00010000; //AN4をCPS
ANSELB = 0b00000000;
TRISA = 0b00100000;
TRISB = 0b00000000;
TRISC = 0b00011000;
PORTA = 0b00000000; //2進数で書いた場合
PORTB = 0x00; //16進数で書いた場合
}
ANSELAのAN4を1にしました。今回mTouchを使えるピンにはCPSという表記がされています。今回私はピン番号7のCPS7を使いたいのですが、この時充放電をするのでアナログ入力にしなければなりません。そのため、ANSELの5bit目はAN4,つまり7番ピンに当たるので1を割り当て、アナログ入力ができるようにしました。
TRISAに関しても同様に7番ピンであるRA5を入力に割り当てています。これで入力する準備はできました。
CPSinit()
今度は容量検知モジュール(CPS)の初期設定をする関数です。
void CPSinit(){
CPSCON0 = 0b00001000; //発振子は中速を利用
CPSCON1bits.CPSCH = 0b0111; //CPSチャネルはCPS7(7ピン)を利用
T1CON = 0b11000001; //クロックソースは容量検知オシレータ、Timer1ON
TMR1H = 0; //Timer1の値をリセット
TMR1L = 0;
PEIE = 1; //割り込みを許可
GIE = 1; //グローバル割り込みを許可
CPSON = 1; //容量検知モジュール(mTouch)ON
}
コメントを見ていただければ、そのまま分かるかとは思います。これらのレジスタの意味が分からなくなったら、PIC16F1938のデータシートを開き、ctrl+Fでレジスタ名を入れれば出てきますので参考にしてください。
Timer1はある発振子を基準にカウントします。デフォルトではPICを駆動している内部発振子なのですが、今回はクロックソースを容量検知オシレータ(CPSモジュールのことです)にしました。よって、容量が変わるとクロック周波数も変わり、Timer1の時間の刻みが変化します。
getCap()
実際にこの容量を検知する関数です。
int getCap(){ //容量検知関数(充放電回数のカウントを返す)
int cap;
TMR1H = 0; //Timer1の値を0に
TMR1L = 0;
__delay_ms(9);
CPSON = 0; //容量検知モジュールをオフ
cap = (TMR1H*256) + TMR1L; //Timer1の値をcapへ
CPSON = 1; //容量検知モジュールをオン
return cap; //capの値(カウント値)を返す
}
CPSinit()関数の後では常にCPS(mTouch)がオンになっています。よって、TMR1の値がどんどん増えて行っているわけです。これでは計算が面倒なので、計測前に0にしています。
その後9ms待っていますね。この間も充放電を繰り返して、その回数がTMR1にカウントされていきます。この秒数は適当に決めて構いません。多ければカウント数が多くなります。
ある程度待ったらCPSONを再度オンにします。そしてTMR1の値をcapに代入し、それをreturnで返しています。
showCap()
getCap()で取得した値をLCDの使い方を参考にLCDに表示します。LCDへの接続関数はこのサイトのままです。
void showCap(){
char Cap[16];
sprintf(Cap,"Cap:%d",getCap());
LCD_str(Cap);
writeCommand(0x02);
}
文字列用の配列Capを用意して、sprintfで「Cap:getCap()」をCapに入れます。これをLCD_strでLCDに表示します。
main関数
int main(void){
PICinit(); //PICを初期化
LCD_Init(); //LCDを初期化
CPSinit();
writeCommand(0x01); //画面をクリア
__delay_ms(20); //LCD処理待ち
writeCommand(0x02); //ホームへカーソル移動
__delay_ms(100); // LCD側の処理待ち
RB7 = 1;
__delay_ms(1000);
while(1){
showCap();
if(getCap() < 800){
RB7 = 1;
}else{
RB7 = 0;
}
__delay_ms(10); //0.01秒おきに更新
}
return 0;
}
始めの方にあるinitラッシュはそのまま初期化しているだけです。そこに先程作ったCPSinitがありますね。次にLCDのセットアップ待ちを入れ、RB7をオンにしています。RB7には今回の動画では青色LEDを繋いでいるので、これが点灯します。
while文の中ではまずshowCap()で容量の値をLCDに表示しています。次にif文内でgetCap()が800以下の時にLEDをオンにしています。私の環境ではmTouchのピンに抵抗を繋いでピンを拡張しているようになっていて、通常時は900ぐらいの値を示します。指で触れた時に300程度まで落ち込むので、その判定基準を800にしました。
if文の後の__delay_msですが、別にこの値である必要は全くありません。応答性を良くしたい場合は、これを無くしてもいいかもしれません。
完成プログラム
// PIC16F1938 Configuration Bit Settings
// 'C' source line config statements
// 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 = OFF // Brown-out Reset Enable (Brown-out Reset disabled)
#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 VCAPEN = OFF // Voltage Regulator Capacitor Enable (All VCAP pin functionality is disabled)
#pragma config PLLEN = OFF// PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF // 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 = ON // Low-Voltage Programming Enable (Low-voltage programming enabled)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include <xc.h>
#include <pic16f1938.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define _XTAL_FREQ 8000000
#define LCD_ADD 0x7C
void I2C_Master_Init(const unsigned long c)
{
SSPCON1 = 0b00101000;
SSPCON2 = 0;
SSPADD = (_XTAL_FREQ/(4*c))-1;
SSPSTAT = 0b00000000 ; // 標準速度モードに設定する(100kHz)
}
void I2C_Master_Wait()
{
while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}
void I2C_Master_Start()
{
I2C_Master_Wait();
SEN = 1;
}
void I2C_Master_RepeatedStart()
{
I2C_Master_Wait();
RSEN = 1;
}
void I2C_Master_Stop()
{
I2C_Master_Wait();
PEN = 1;
}
void I2C_Master_Write(unsigned d)
{
I2C_Master_Wait();
SSPBUF = d;
}
void writeData(char t_data){
I2C_Master_Start();
I2C_Master_Write(LCD_ADD);
I2C_Master_Write(0x40);
I2C_Master_Write(t_data);
I2C_Master_Stop();
__delay_ms(10);
}
void writeCommand(char t_command){
I2C_Master_Start();
I2C_Master_Write(LCD_ADD);
I2C_Master_Write(0x00);
I2C_Master_Write(t_command);
I2C_Master_Stop();
__delay_ms(10);
}
void PICinit(){
OSCCON = 0b01110000;
ANSELA = 0b00010000; //AN4をCPS
ANSELB = 0b00000000;
TRISA = 0b00100000;
TRISB = 0b00000000;
TRISC = 0b00011000;
PORTA = 0b00000000; //2進数で書いた場合
PORTB = 0x00; //16進数で書いた場合
}
void CPSinit(){
CPSCON0 = 0b00001000; //発振子は中速を利用
CPSCON1bits.CPSCH = 0b0111; //CPSチャネルはCPS7(7ピン)を利用
T1CON = 0b11000001; //クロックソースは容量検知オシレータ、Timer1ON
TMR1H = 0; //Timer1の値をリセット
TMR1L = 0;
PEIE = 1; //割り込みを許可
GIE = 1; //グローバル割り込みを許可
CPSON = 1; //容量検知モジュール(mTouch)ON
}
int getCap(){ //容量検知関数(充放電回数のカウントを返す)
int cap;
TMR1H = 0; //Timer1の値を0に
TMR1L = 0;
__delay_ms(9);
CPSON = 0; //容量検知モジュールをオフ
cap = (TMR1H*256) + TMR1L; //Timer1の値をcapへ
CPSON = 1; //容量検知モジュールをオン
return cap; //capの値(カウント値)を返す
}
void LCD_Init(){ //LCDの初期化
I2C_Master_Init(100000);
__delay_ms(400);
writeCommand(0x38);
__delay_ms(20);
writeCommand(0x39);
__delay_ms(20);
writeCommand(0x14);
__delay_ms(20);
writeCommand(0x73);
__delay_ms(20);
writeCommand(0x52);
__delay_ms(20);
writeCommand(0x6C);
__delay_ms(250);
writeCommand(0x38);
__delay_ms(20);
writeCommand(0x01);
__delay_ms(20);
writeCommand(0x0C);
__delay_ms(20);
}
void LCD_str(char *c) { //LCDに配列の文字を表示
unsigned char i,wk;
for (i=0 ; ; i++) {
wk = c[i];
if (wk == 0x00) {break;}
writeData(wk);
}
}
void showCap(){
char Cap[16];
sprintf(Cap,"Cap:%d",getCap());
LCD_str(Cap);
writeCommand(0x02);
}
int main(void){
PICinit(); //PICを初期化
LCD_Init(); //LCDを初期化
CPSinit();
writeCommand(0x01); //画面をクリア
__delay_ms(20); //LCD処理待ち
writeCommand(0x02); //ホームへカーソル移動
__delay_ms(100); // LCD側の処理待ち
RB7 = 1;
__delay_ms(1000);
while(1){
showCap();
if(getCap() < 800){
RB7 = 1;
}else{
RB7 = 0;
}
__delay_ms(10); //0.01秒おきに更新
}
return 0;
}
上記のコードをコピペして貼り付ければ、同じ回路であれば動作するでしょう。