はじめに

この文書では,PIC16F1936に繋がっているLEDの点灯方法を説明します.今回は下図のように3個のLED(LD1,LD2,LD3)がPICの18番ピン,17番ピン,16番ピンに接続されているものとします.カソード側がPICとつながっているため,PICからLowを出力すればLEDは光ります.

PICLED01

下の図は今回使用するLEDボードです.DMD付き距離センサボードの代わりとしてメインマイコンボード搭載ボードに接続して使用できます.DMD付き距離センサボードとの違いはDMDと距離センサがないことです.まずはこのボードを使ってLEDの点灯を通じ,簡単なPICの使い方を学びましょう.

PICLED03

LEDは全部で3個あります.右からLD1,LD2,LD3となっており,マイコンの接続端子はそれぞれポートCビット7,ポートCビット6,ポートCビット5となっています.

LEDの名称 マイコンの接続端子名
LD1 ポートCビット7
LD2 ポートCビット6
LD3 ポートCビット5


では,MPLABのプロジェクトを作成しましょう.プロジェクトの作成はこちらで説明していますので参考にしてください.プロジェクト名はLedAndSwitchとしておきましょう.

  1. 動作周波数の設定

    内部オシレータを動作周波数として使用するとき,PIC16F1936では周波数選択ビットIRCFにより,16[MHz]から31[kHz]までの周波数を選ぶことができます.今回,4[MHz]にするときには,IRCF=1101にすればよいです.従って,C言語では次のように書けばよいです.
    /* 内部オシレータの速度を4[MHz]とする */
    IRCF0 = 1;
    IRCF1 = 0;
    IRCF2 = 1;
    IRCF3 = 1;

  2. LCD制御機能を停止

    今回制御の対象となる16から18番ピンはLCDの制御を行う端子(SEGxx)として使用できる機能を有しています.そこで汎用ピンとして使用するため,LCDの制御を行う機能を停止させる必要があります.従って,C言語では次のように書けばよいです.今回のプログラムではすべてLCDを制御することはないため,毎回この記述を書いてください.
    /* LCD を制御しないようにする */
    LCDEN = 0;

  3. 出力端子の設定

    出力端子にするには,TRISxy=1にすればよいです.ここで,xはポートのグループ名を表しており,PIC16F1936の場合にはA,B,C,Eのいずれかになります.また,yはポートの番号を表しており,0から7までの数字が入ります.各端子のポートグループおよび番号が何なのか調べるには,上記回路図を参照してください.例えば,18番ピンには下図のように「RC7」と書いてあります.つまり,18番ピンはグループ名C,番号7です.
    PICLED02
    このピンを出力にするには次のプログラムを追記してください.
    /* RC7を出力端子とする */
    TRISC7=0;

  4. LD1を点灯させる

    端子からLowを出力するにはxy=0,Highを出力するにはxy=1とします.このxyは前章に示したものと同じで,xはグループ名,yは番号を表しています.今回,9番ピンつまりRC7からLowを出力するには,次のプログラムを追記してください.
    /* RC7からLowを出力する */
    RC7 = 0;
  5. 演習

    今度はLD2のみ点灯させるプログラムを作成してください.
  6. LEDに関するプログラムをファイルに分ける

    上記ではメイン関数にLEDを制御するプログラムを直接書きました.しかしこれでは多くのデバイスを制御するとき分かりづらくなります.そこでLEDを制御するプログラムをLed.cファイルへ分けて書いてみましょう.加えて,今回は3つのLEDがありますので,それぞれを指定して点灯および消灯をさせるようにします.このため,LEDの種類を列挙型で定義し,列挙型の要素を指定することで操作対象のLEDを指定するようにします.では,関数のプロトタイプ宣言と列挙型の宣言を行うヘッダファイル,具体的にはLed.hを作成しましょう.下の図のようにHeader Filesを右クリックし,New→C Header Fileを選択してください.
    PICLED04

    次にヘッダファイル名を指定します.下の図のようにFile NameにLedと書き,Finishボタンを押してください.
    PICLED05

    初期状態ではC++言語のための記述がヘッダファイルに入っています.この記述は不要ですので,下図で囲った箇所を削除してください.
    PICLED06

    次にLEDの種類を列挙型で宣言します.加えてtypedefを使ってLedType型を宣言します.これにより,LedType型を新たに作成することができます.下の図のように追記してください.
    PICLED07
    typedef enum{
        LED1,
        LED2,
        LED3
    }LedType;

    次にプロトタイプ宣言をします.今回作成しているLed.hをインクルードしたソースプログラムではLedに関する関数が外部(具体的には後で作成するLed.c)にあることを知らせる必要があります.従って,プロトタイプ宣言の最初にはexternをつけます.これにより,externの後に続く関数はインクルードしたソースプログラムにはないことを宣言できます.下の図のように追記してください.ここでは3つの関数を宣言します.Led_initialize関数はLedの初期化を行い,Led_turnOnでは点灯,Led_turnOffでは消灯させます.
    PICLED08
    extern void Led_initialize(void);
    extern void Led_turnOn(LedType type);
    extern void Led_turnOff(LedType type);
    次にLed.cを作ります.下の図のようにSource Filesを右クリックし,New→C Source Fileを選択してください.

    PICLED09

    新たに追加するファイル名はLed.cですので,下の図のようにLedを書き,Finishボタンを押してください.
    PICLED10

    ソースファイルの冒頭にはいくつかの定番のヘッダファイルと,先ほど書いたLed.hをインクルードします.定番のヘッダとしてはstdbool.h(←真偽を表すbool型が宣言されています),stdint.h(←uint8_tなどの整数型が宣言されています),stdio.h(←NULLなどの標準的な定数などが宣言されています),xc.h(←PICのレジスタが宣言されています)があります.使うか使わないかは別にして,これらはいずれのソースファイルでもこれらのヘッダはインクルードしておいてください.最後にLed.hをインクルードしておきましょう.
    PICLED11
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <xc.h>
    
    #include "Led.h"

    次に関数を実装していきます.Led.hに書いたプロトタイプ宣言をコピーし,ソースファイルにペーストするとよいでしょう.下の図はプロトタイプ宣言をLed.cにコピーした様子です.こののち,externの箇所を削除すれば,プロトタイプ宣言と同じ関数名,仮引数,戻り値が記述できます.さらに,末尾にあるセミコロンを削除し,代わりに{ }を書いておきましょう.
    PICLED12
    void Led_initialize(void){
    }
    void Led_turnOn(LedType type){
    }
    void Led_turnOff(LedType type){
    }

    ではプログラムを実装しましょう.まずはLed_initialize関数です.ここではLEDとつながる3つの端子を出力端子にすることと,LEDを消灯することを設定します.前者はTRISxxレジスタ,後者はRxyレジスタで行えます.前に掲載した表を参考に初期化する関数をLed_initialize関数に書いてください.なお,消灯させるときには1を出力することとなっていたことに注意してください.

    PICLED13
        TRISC7=0;
        TRISC6=0;
        TRISC5=0;
        RC7=1;
        RC6=1;
        RC5=1;

    では点灯させるLed_turnOn関数を実装しましょう.この関数では引数であるtypeでどのLEDかを特定し,そのLEDを点灯させることとします.特定の方法としてはswitch-case文がよさそうです.下の図のようにLed_turnOn関数に書きます.
    PICLED14
        switch(type){
            case LED1:
                break;
            case LED2:
                break;
            case LED3:
                break;
        }

    次にLED1を点灯させるプログラムを追記します.case LED1:からbreak;の間に追記すればよさそうです.行うこととしてはLED1が接続されているポートCビット7をLowにすればよいので,RC7=0;と追記します.同じ要領でLED2とLED3についても追記してください.
    PICLED15
        switch(type){
            case LED1:
                RC7=0;
                break;
            case LED2:
                RC6=0;
                break;
            case LED3:
                RC5=0;
                break;
        }
    

    演習

    Led_turnOff関数を実装してください.

  7. メイン関数からLEDを操作する

    LEDを操作する関数ができましたのでメイン関数でそれらの関数を呼び出してみましょう.下の図のようにすでにメイン関数にはオシレータを4MHzに設定するとともに,LCDを制御する周辺機能を停止していることを確認してください.
    PICLED16

    さて,今回はLEDを下図のように動かすことを目指します.
    PICLED17

    このプログラムでは「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をインクルードしておきましょう.それから,定番のインクルードファイルが書かれていないようであれば追記しておいてください.
    PICLED18
    #include "Led.h"
    
    #define _XTAL_FREQ (4000000)
    

    ではプログラムを書きましょう.まずはLEDの初期化をしてください.Led_initialize関数ですね.次は無限ループになっていますので,while(true){ }を書きます.その中にLED1点灯→1000ms待つ→LED1消灯→...と書いていきます.結果として下の図のようになります.
    PICLED19
        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);
        }
    
    
    どうでしょうか.正しく動作したでしょうか.