はじめに
この文書では,PIC16F1936に繋がっているLEDの点灯方法を説明します.今回は下図のように3個のLED(LD1,LD2,LD3)がPICの18番ピン,17番ピン,16番ピンに接続されているものとします.カソード側がPICとつながっているため,PICからLowを出力すればLEDは光ります.
下の図は今回使用するLEDボードです.DMD付き距離センサボードの代わりとしてメインマイコンボード搭載ボードに接続して使用できます.DMD付き距離センサボードとの違いはDMDと距離センサがないことです.まずはこのボードを使ってLEDの点灯を通じ,簡単なPICの使い方を学びましょう.
LEDは全部で3個あります.右からLD1,LD2,LD3となっており,マイコンの接続端子はそれぞれポートCビット7,ポートCビット6,ポートCビット5となっています.
LEDの名称 | マイコンの接続端子名 |
LD1 | ポートCビット7 |
LD2 | ポートCビット6 |
LD3 | ポートCビット5 |
では,MPLABのプロジェクトを作成しましょう.プロジェクトの作成はこちらで説明していますので参考にしてください.プロジェクト名はLedAndSwitchとしておきましょう.
-
動作周波数の設定
内部オシレータを動作周波数として使用するとき,PIC16F1936では周波数選択ビットIRCFにより,16[MHz]から31[kHz]までの周波数を選ぶことができます.今回,4[MHz]にするときには,IRCF=1101にすればよいです.従って,C言語では次のように書けばよいです.
/* 内部オシレータの速度を4[MHz]とする */ IRCF0 = 1; IRCF1 = 0; IRCF2 = 1; IRCF3 = 1;
-
LCD制御機能を停止
今回制御の対象となる16から18番ピンはLCDの制御を行う端子(SEGxx)として使用できる機能を有しています.そこで汎用ピンとして使用するため,LCDの制御を行う機能を停止させる必要があります.従って,C言語では次のように書けばよいです.今回のプログラムではすべてLCDを制御することはないため,毎回この記述を書いてください.
/* LCD を制御しないようにする */ LCDEN = 0;
-
出力端子の設定
出力端子にするには,TRISxy=1にすればよいです.ここで,xはポートのグループ名を表しており,PIC16F1936の場合にはA,B,C,Eのいずれかになります.また,yはポートの番号を表しており,0から7までの数字が入ります.各端子のポートグループおよび番号が何なのか調べるには,上記回路図を参照してください.例えば,18番ピンには下図のように「RC7」と書いてあります.つまり,18番ピンはグループ名C,番号7です.
このピンを出力にするには次のプログラムを追記してください.
/* RC7を出力端子とする */ TRISC7=0;
-
LD1を点灯させる
端子からLowを出力するにはxy=0,Highを出力するにはxy=1とします.このxとyは前章に示したものと同じで,xはグループ名,yは番号を表しています.今回,9番ピンつまりRC7からLowを出力するには,次のプログラムを追記してください.
/* RC7からLowを出力する */ RC7 = 0;
-
LEDに関するプログラムをファイルに分ける
上記ではメイン関数にLEDを制御するプログラムを直接書きました.しかしこれでは多くのデバイスを制御するとき分かりづらくなります.そこでLEDを制御するプログラムをLed.cファイルへ分けて書いてみましょう.加えて,今回は3つのLEDがありますので,それぞれを指定して点灯および消灯をさせるようにします.このため,LEDの種類を列挙型で定義し,列挙型の要素を指定することで操作対象のLEDを指定するようにします.では,関数のプロトタイプ宣言と列挙型の宣言を行うヘッダファイル,具体的にはLed.hを作成しましょう.下の図のようにHeader Filesを右クリックし,New→C Header Fileを選択してください.
次にヘッダファイル名を指定します.下の図のようにFile NameにLedと書き,Finishボタンを押してください.
初期状態ではC++言語のための記述がヘッダファイルに入っています.この記述は不要ですので,下図で囲った箇所を削除してください.
次にLEDの種類を列挙型で宣言します.加えてtypedefを使ってLedType型を宣言します.これにより,LedType型を新たに作成することができます.下の図のように追記してください.
typedef enum{ LED1, LED2, LED3 }LedType;
次にプロトタイプ宣言をします.今回作成しているLed.hをインクルードしたソースプログラムではLedに関する関数が外部(具体的には後で作成するLed.c)にあることを知らせる必要があります.従って,プロトタイプ宣言の最初にはexternをつけます.これにより,externの後に続く関数はインクルードしたソースプログラムにはないことを宣言できます.下の図のように追記してください.ここでは3つの関数を宣言します.Led_initialize関数はLedの初期化を行い,Led_turnOnでは点灯,Led_turnOffでは消灯させます.
次にLed.cを作ります.下の図のようにSource Filesを右クリックし,New→C Source Fileを選択してください.extern void Led_initialize(void); extern void Led_turnOn(LedType type); extern void Led_turnOff(LedType type);
新たに追加するファイル名はLed.cですので,下の図のようにLedを書き,Finishボタンを押してください.
ソースファイルの冒頭にはいくつかの定番のヘッダファイルと,先ほど書いたLed.hをインクルードします.定番のヘッダとしてはstdbool.h(←真偽を表すbool型が宣言されています),stdint.h(←uint8_tなどの整数型が宣言されています),stdio.h(←NULLなどの標準的な定数などが宣言されています),xc.h(←PICのレジスタが宣言されています)があります.使うか使わないかは別にして,これらはいずれのソースファイルでもこれらのヘッダはインクルードしておいてください.最後にLed.hをインクルードしておきましょう.
#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <xc.h> #include "Led.h"
次に関数を実装していきます.Led.hに書いたプロトタイプ宣言をコピーし,ソースファイルにペーストするとよいでしょう.下の図はプロトタイプ宣言をLed.cにコピーした様子です.こののち,externの箇所を削除すれば,プロトタイプ宣言と同じ関数名,仮引数,戻り値が記述できます.さらに,末尾にあるセミコロンを削除し,代わりに{ }を書いておきましょう.
void Led_initialize(void){ } void Led_turnOn(LedType type){ } void Led_turnOff(LedType type){ }
ではプログラムを実装しましょう.まずはLed_initialize関数です.ここではLEDとつながる3つの端子を出力端子にすることと,LEDを消灯することを設定します.前者はTRISxxレジスタ,後者はRxyレジスタで行えます.前に掲載した表を参考に初期化する関数をLed_initialize関数に書いてください.なお,消灯させるときには1を出力することとなっていたことに注意してください.
TRISC7=0; TRISC6=0; TRISC5=0; RC7=1; RC6=1; RC5=1;
では点灯させるLed_turnOn関数を実装しましょう.この関数では引数であるtypeでどのLEDかを特定し,そのLEDを点灯させることとします.特定の方法としてはswitch-case文がよさそうです.下の図のようにLed_turnOn関数に書きます.
switch(type){ case LED1: break; case LED2: break; case LED3: break; }
次にLED1を点灯させるプログラムを追記します.case LED1:からbreak;の間に追記すればよさそうです.行うこととしてはLED1が接続されているポートCビット7をLowにすればよいので,RC7=0;と追記します.同じ要領でLED2とLED3についても追記してください.
switch(type){ case LED1: RC7=0; break; case LED2: RC6=0; break; case LED3: RC5=0; break; }
演習
Led_turnOff関数を実装してください. -
メイン関数からLEDを操作する
LEDを操作する関数ができましたのでメイン関数でそれらの関数を呼び出してみましょう.下の図のようにすでにメイン関数にはオシレータを4MHzに設定するとともに,LCDを制御する周辺機能を停止していることを確認してください.
さて,今回はLEDを下図のように動かすことを目指します.
このプログラムでは「1000ms待つ」ということを行っています.時間を待つ方法は大きく分けて2つあります.1つはマイコンの周辺機能のタイマを利用する方法,もう一つはnop関数,つまり何もしない関数を待ちたい時間だけ呼び出して待つ方法です.周辺機能のタイマの使い方については説明していないため,ここではnop関数を呼び出す方法について説明します.今回は4MHzでマイコンが動作しており,PICでは1命令を4サイクルで動作させているため,1命令は1MHzで実行します.ということは,1命令1usの時間を費やせるわけです.よって,1,000回で1ms,1,000,000回で1000ms待つことができるわけです.これを書けばいいのですが毎回このようなことをプログラムするのは不便であるため,実はすでにウェイト用の組み込み関数(正確にはマクロ)が用意されています.それが__delay_ms関数です.この関数を使う準備として,動作しているマイコンがどの速度なのか,定数によりコンパイラに伝える必要があります.その定数は_XTAL_FREQです.これを#define で定義しておきます.下の図のように,メイン関数の前に追記してください.ついでにLed.hをインクルードしておきましょう.それから,定番のインクルードファイルが書かれていないようであれば追記しておいてください.
#include "Led.h" #define _XTAL_FREQ (4000000)
ではプログラムを書きましょう.まずはLEDの初期化をしてください.Led_initialize関数ですね.次は無限ループになっていますので,while(true){ }を書きます.その中にLED1点灯→1000ms待つ→LED1消灯→...と書いていきます.結果として下の図のようになります.
どうでしょうか.正しく動作したでしょうか.Led_initialize(); while(true){ Led_turnOn(LED1); __delay_ms(1000); Led_turnOff(LED1); __delay_ms(1000); Led_turnOn(LED2); __delay_ms(1000); Led_turnOff(LED2); __delay_ms(1000); Led_turnOn(LED3); __delay_ms(1000); Led_turnOff(LED3); __delay_ms(1000); }