【演習用】LED点灯プログラム例

LED点灯プログラム例

main.cの書き方として、LED点灯プログラム例を考えていただきました。
色んなプログラム例が想定されるのですが、一例としては以下の様なプログラムが有り得ます。

/***********************************************************************************
 *                                                                                 *
 *  File:        main.c                                                            *
 *  version      1.00   2026/03/04     (PIC16F1614)                                *
 *  Description:                                                                   *
 *                                                                                 *
 *  Date         By              Description                                       *
 *  2026/03/04   Yoshiki Eto     新規作成                                          *
 *  Copyright (C) XXXXXXXX.                                                        *
 *  XXXXXXX  All rights reserved.                                                  *
 *                                                                                 *
 ***********************************************************************************/
/***********************************************************************************
 *                                                                                 *
 *  Configuration                                                                  *
 *                                                                                 *
 ***********************************************************************************/
#pragma config CLKOUTEN = OFF
#pragma config BOREN = ON
#pragma config CP = ON
#pragma config MCLRE = ON
#pragma config PWRTE = ON
#pragma config FOSC = INTOSC
#pragma config LVP = OFF
/* #pragma config DEBUG  -> これはデバッガで制御されるのでここでは設定しない */
#pragma config LPBOR = OFF
#pragma config BORV = ON
#pragma config STVREN = ON
#pragma config PLLEN = ON
#pragma config ZCD = OFF
#pragma config PPS1WAY = OFF
#pragma config WRT = OFF
#pragma config WDTCCS = SC
#pragma config WDTCWS = WDTCWS_7
#pragma config WDTE = OFF
#pragma config WDTCPS = WDTCPS_31



void main(void)
{
  /*************/
    /* 初期化処理 */
    /*************/
    OSCCON = 0x7A;   /* 内部発振周波数 16MHz 設定 0b01111010 */
    
    /*************/
    /* PORTA設定  */
    /*************/
    /* 出力初期値 */
    LATA = 0x00;
    
    /* 入出力方向設定 */
    /* 1 = 入力ポート */
    /* 0 = 出力ポート */
    TRISA = 0xFE;   /* TRISA0を出力ポート設定 */
    
    /* LED点灯 */
    LATAbits.LATA0 = 1;  /* LATA0をHIGH出力 */
}

一つずつ解説していきたいと思います。
まず、この状態でコンパイルしてみましょう。

main.cに自分なりのプログラムを書いた後、MPLABXのメニューバーのビルドアイコンをクリックします。金槌のマークがあるアイコンです。

コンパイルエラーの解決方法

ご自身のプログラム次第では、メッセージの出方が異なると思いますが、
皆さんmain関数の{ }の中身以外は同じコードをコピーしていただいていると、以下の様なメッセージが出ると思います。

赤字で表示されている箇所はエラーになります。
青字の下線行はエラーとなっている行になり、エラーの内容が表示されています。

慣れない内はエラー内容が分からないと思いますので、青色行を1行コピーしてGoogle検索してみてください。
先人の玄人がどの様なエラー内容で、何をどうすればよいのかを指南してくれています。

ちなみに、ここでエラーとなっている内容は以下になります。
それぞれ、「コンフィギュレーションの設定値 or レジスタ名が分からない(見つからない)」と言われています。

#pragma config BORV = ON
#pragma config WDTCCS = SC
#pragma config WDTCWS = WDTCWS_7
#pragma config WDTCPS = WDTCPS_31

PIC16F1614の仕様書を見ると、BORV、WDTCCS、WDTCWS、WDTCPSというレジスタ名は存在しています。
そうすると、設定値が正しくないことになります。

まず、BORVはON or OFFではなく、HI or LOになります。
#pragma config BORV = LO と書くことでエラーが一つ減ると思います。

次に、WDTCCS は、SWCと書くことでエラーが一つ減ると思います。
ソフトウェアでクロック源を設定するという意味になります。

次に、WDTCWS は、WDTCWS100と書くことでエラーが一つ減ると思います。

最後にWDTCPS は、WDTCPSAと書くことでエラーが全てなくなると思います。
これで以下の通り、「BUILD SUCCESFUL」と表示され、エラーが全てなくなりました。

この状態が「コンパイルが通った」とか「ビルドエラーがなくなった」と言われる状態です。
必ずこの状態にまで到達しないと、マイコンへ書き込みができません。

但し、エラーがなくなったから正しい動作をするわけではありません
C言語のルール、PICマイコンのルール上は問題ない。というだけです。

ちなみに、コンフィグレーションビットの設定値は、MPLABのメニューの「Window」->「Target Memory Views」->「Configuration Bits」を選択することで、Output欄に表示されて確認することが可能です。

MPLABのOUTPUT欄は最下部の表示箇所です。
赤枠が設定値である定義文字列になりますので、Fieldに書かれているレジスタビット名に対して、設定したい定義文字列をそのままソースファイル側に書きます。
プルダウンがある項目は、複数設定値が存在することを意味しています。

ONやOFFなどの定義はどこにあるの?

windowからConfiguration Bitsの表示を出して、定義値が存在していることはわかりましたが、プログラムとしてはどこに定義されているのでしょうか?

参考者の座学で学んだ知識としては、#defineで定義された箇所がないとエラーになるはずです。
PICマイコンの開発環境であるMPLABでは、この辺りを意識しなくて良いようにXC8コンパイラが吸収してくれています。

#include <xc.h> がONやOFFといった定義ファイルを読み込みしている大元になります。
このファイルから最終的に #include pic16F1614 というデバイス毎のヘッダーファイルを読み込むことになっています。

以下の様なイメージです。
#include <xc.h>

xc.h

pic16f1614.h

この#include pic16F1614の中に、仕様書に書かれているレジスタなどが定義されています。
全てを確認すると膨大なので、LED点灯に使用したLATAレジスタを見てみましょう。

// Register: LATA
#define LATA LATA
extern volatile unsigned char           LATA                __at(0x10C);
#ifndef _LIB_BUILD
asm("LATA equ 010Ch");
#endif
// bitfield definitions
typedef union {
    struct {
        unsigned LATA0                  :1;
        unsigned LATA1                  :1;
        unsigned LATA2                  :1;
        unsigned LATA3                  :1;
        unsigned LATA4                  :1;
        unsigned LATA5                  :1;
    };
} LATAbits_t;
extern volatile LATAbits_t LATAbits __at(0x10C);
// bitfield macros
#define _LATA_LATA0_POSN                                    0x0
#define _LATA_LATA0_POSITION                                0x0
#define _LATA_LATA0_SIZE                                    0x1
#define _LATA_LATA0_LENGTH                                  0x1
#define _LATA_LATA0_MASK                                    0x1
#define _LATA_LATA1_POSN                                    0x1
#define _LATA_LATA1_POSITION                                0x1
#define _LATA_LATA1_SIZE                                    0x1
#define _LATA_LATA1_LENGTH                                  0x1
#define _LATA_LATA1_MASK                                    0x2
#define _LATA_LATA2_POSN                                    0x2
#define _LATA_LATA2_POSITION                                0x2
#define _LATA_LATA2_SIZE                                    0x1
#define _LATA_LATA2_LENGTH                                  0x1
#define _LATA_LATA2_MASK                                    0x4
#define _LATA_LATA3_POSN                                    0x3
#define _LATA_LATA3_POSITION                                0x3
#define _LATA_LATA3_SIZE                                    0x1
#define _LATA_LATA3_LENGTH                                  0x1
#define _LATA_LATA3_MASK                                    0x8
#define _LATA_LATA4_POSN                                    0x4
#define _LATA_LATA4_POSITION                                0x4
#define _LATA_LATA4_SIZE                                    0x1
#define _LATA_LATA4_LENGTH                                  0x1
#define _LATA_LATA4_MASK                                    0x10
#define _LATA_LATA5_POSN                                    0x5
#define _LATA_LATA5_POSITION                                0x5
#define _LATA_LATA5_SIZE                                    0x1
#define _LATA_LATA5_LENGTH                                  0x1
#define _LATA_LATA5_MASK     

まず、#define LATA LATA と定義され、プログラム上で LATA という記述ができるようになっています。

  extern volatile unsigned char LATA __at(0x10C);

と書かれていますが、これはLATAのレジスタアドレスを割り当てしています。
つまり、LATAという定義に関わる情報は、仕様書に記載のあるLATAレジスタのアドレスに紐づけされるようになっています。

 typedef union {
 struct {
 unsigned LATA0 :1;
  unsigned LATA1 :1;
 unsigned LATA2 :1;
 unsigned LATA3 :1;
 unsigned LATA4 :1;
 unsigned LATA5 :1;
 };
 } LATAbits_t;

これは構造体に似ていますが、共用体と呼ばれるものです。
参考書には出てこなかったですが、LATAbits_tという共用体名で宣言され、
メンバー変数には、LATA0~LATA5が用意されています。

構造体はそれぞれが独立したメモリに配置されていましたが、共用体は一つのメモリーにLATA0~LATA5が共同使用されるものとなっています。

図で見た方が分かりやすいかもしれません。

構造体は青色、緑色、オレンジ色でそれぞれ独立してメモリが配置されています。
共用体は3つのメンバ変数が一つのメモリを共用しています。

その為、共用体は、一つのメンバ変数しか管理できません。
num1の値をU_DATAの実体に保存するとnum2が保存された時点でnum1の値は上書きされてなくなります。

現段階では少し難しいかもしれませんので、概念としてその様なものが存在するのだな。と思っておいてください。

LED点灯コードと想定回路接続


LATAに話を戻しますが、LATA0は、LATAレジスタのbit0を示しています。
LATA1はbit1、LATA2はbit2・・・。といった感じです。

PORTAのbit0を出力設定とした場合、LATA0に1をセットすると、PORTAのbit0のポートがHIGH状態になります。
つまり、1bitずつ出力設定をする際に利用できるように、ヘッダーファイルに共用体という形で宣言がされています。

概念的は部分は初学者としては、理解が難しいと思いますので、
以下の様な記述をすると、該当するポートの出力状態が反映されるのだ。と覚えておいてください。

 /* LED点灯 */
LATAbits.LATA0 = 1; /* LATA0をHIGH出力 */

LATA0の部分を、LATA1、LATA2、LATA3とすれば、該当ポートの出力値が反映されます。

今回のサンプルコードで想定している接続回路は以下になります。

LATA0はRA0に紐づいています。
LATA0に1を設定するとHIGH出力されますので、LED1のアノードに5Vが印可され、
制限抵抗の100Ωを経て、2~3mAほど流れて点灯します。
LED1の品種にもよりますが、VF=2~2.5V程度を想定しています。

次回は、このプログラムをどのようにマイコンへ書き込みするのかを解説していきます。

マイコンとPickitの接続

「pickitの接続」で掲載の通り、ハード的には同じ信号名同士を接続するだけです。
ピンヘッダ―やコネクタなどで接続できるようにしておけば、必要な時に接続して書き込みができます。

ハード接続がされた状態として、LED回路にPickitを追加しました。

CN1をコネクタとしていますが、ここにPickitの信号ラインをケーブルを介して接続していただき、接続された状態で5.0Vを印可することで、デバッグおよび書き込みできる環境となります。

プログラムの書き込み

PickitのUSBをMPLABを起動しているパソコンに接続します。
MPLABのプロジェクトツリーのプロジェクト名を右クリックして「Properties」を選択します。

そうすると、以下の様なダイアログが出てきます。
中央の「Connected Hardware Tool」のプルダウンをクリックすると、接続されているPickit4が項目として出てきますので選択して、ダイアログの右下の「Apply」をクリックして「OK」ボタンで閉じます。

これで、デバッグに使用するToolはPickit4を選択指定したことになります。
正常に選択できていれば、プロジェクトツリーWindowの下にあるDashboard内にある、
「Debug Tool」がPickit4の表示となって出ますので、この状態になればOKです。

マイコンとPickit4へ5V電源を供給できている状態で、以下の赤丸アイコンを押下するとmakeされてプログラム書き込みし、書き込み完了後に実行されます。

一度書き込みすると、電源OFFにしてPickitの接続を外しても、ROMに書かれていますのでプログラムは消えません。
その為、Pickitを外した状態で5V電源を印可すれば書いたプログラムが実行されて、マイコンと周辺回路が動作します。

アイコン押下時にエラーが出る場合

原因によって表示されるエラー内容が違うので、一概には語れませんが以下のどちらかになります。
①Pickitが正しく接続できていない
  ⇒配線間違い。回路上の問題(電源供給できていないなど)

②プログラムがmakeエラー
  ⇒C言語の文法ルール通りにコーディングできていない

例えば、Pickitが基板に接続されていなかったり、正しい配線ではない場合の一例として以下の様なエラーが出ます。

赤字部分がエラー内容ですが、「ターゲットボードが見当たらない・・・」という内容になっています。
わざと基板に接続せずにアイコン押下したので、当然書き込み対象が何もないのでエラーになった。ということです。

エラーが出た場合、エラー内容をネット検索することで随分と情報が集まります。
断片的にも内容を確認することで、何となくでもどこが問題なのかがわかることが多いので、慣れるまでは検索しながらエラー内容を熟考してみてください。

オンチップデバッグをする場合

オンチップデバッグとは、書いたプログラムを1行ずつ実行したり、任意の場所まで実行したり、どこまで意図通りの動作をしているのか、ターゲットボード上で段階的にプログラム実行するデバッグ手法です。

先ほどのLED回路 + pickit接続の基板を「マイコンとPickitの接続」で書いた内容を実施します。
つまり、PickitのUSBをパソコンへ接続し、Pickitは基板のマイコンと接続状態にして、5V電源を印可します。

「プログラムの書き込み」でPickitを選択しましたので、1度設定をすればPickitの接続を外しても、再接続時に自動認識されると思います。
もし、認識されていなければ「プログラムの書き込み」の内容をもう一度実施してください。

ここで一つ問題があります。。。
わざと解説の為にLEDをRA0に接続したのですが、、、

RA0はLED1のアノードに電源供給されるポートです。
加えて、PickitのICSPDATの信号線もRA0に接続されています。

PickitがICSPDAT信号にマイコンへデータ送信していますので、
先ほどの書き込み時、LED1は点灯⇔消灯を繰り返したのではないでしょうか。

オンチップデバッグ時も同様にマイコンとPickitが通信するのに利用されるため、
書いたプログラムとは異なる意図しない点灯制御が見受けられてしまいます。

これでは、デバッグするにあたり、自身が書いたプログラムが間違っていたのか判断がつきません。。。

この場合、回避方法は一つしかありません。
Pickitが利用するポートは使わないことです。つまりは、以下の様な回路にすることです。

RA0 ⇒ RA2にLED1の接続を変更します。
そうすると、RA0はPickitの信号接続。RA2はLED1のアノード制御。と切り分けされます。

更にプログラムもRA0⇒RA2へ制御プログラムを変更しなければなりません。

/***********************************************************************************
 *                                                                                 *
 *  File:        main.c                                                            *
 *  version      1.00   2026/03/04     (PIC16F1614)                                *
 *  Description:                                                                   *
 *                                                                                 *
 *  Date         By              Description                                       *
 *  2026/03/04   Yoshiki Eto     新規作成                                          *
 *  Copyright (C) XXXXXXXX.                                                        *
 *  XXXXXXX  All rights reserved.                                                  *
 *                                                                                 *
 ***********************************************************************************/
/***********************************************************************************
 *                                                                                 *
 *  Configuration                                                                  *
 *                                                                                 *
 ***********************************************************************************/
#pragma config CLKOUTEN = OFF
#pragma config BOREN = ON
#pragma config CP = ON
#pragma config MCLRE = ON
#pragma config PWRTE = ON
#pragma config FOSC = INTOSC
#pragma config LVP = OFF
/* #pragma config DEBUG  -> これはデバッガで制御されるのでここでは設定しない */
#pragma config LPBOR = OFF
#pragma config BORV = ON
#pragma config STVREN = ON
#pragma config PLLEN = ON
#pragma config ZCD = OFF
#pragma config PPS1WAY = OFF
#pragma config WRT = OFF
#pragma config WDTCCS = SC
#pragma config WDTCWS = WDTCWS_7
#pragma config WDTE = OFF
#pragma config WDTCPS = WDTCPS_31



void main(void)
{
  /*************/
    /* 初期化処理 */
    /*************/
    OSCCON = 0x7A;   /* 内部発振周波数 16MHz 設定 0b01111010 */
    
    /*************/
    /* PORTA設定  */
    /*************/
    /* 出力初期値 */
    LATA = 0x00;
    
    /* 入出力方向設定 */
    /* 1 = 入力ポート */
    /* 0 = 出力ポート */
    TRISA = 0xFB;   /* TRISA2を出力ポート設定 */
    
    /* LED点灯 */
    LATAbits.LATA2 = 1;  /* LATA2をHIGH出力 */
}

main関数内で出力設定をしている以下の箇所をRA2が出力として使用できるように設定。

 TRISA = 0xFB; /* TRISA2を出力ポート設定 */

出力値を設定するレジスタビットをLATA0 ⇒ LATA2へ変更

 LATAbits.LATA2 = 1; /* LATA2をHIGH出力 */

これでRA2が出力ポートとして機能し、LED1のアノードへHIGH出力(=5V印可)となり点灯になります。

マイコンポートに余裕がある場合は、この様な設定変更をかけることでPickitと機能ブロックを明確に分けることが可能です。
逆にポートに余裕がない場合は、冒頭の様にPickitの信号線と兼用としておき、オンチップデバッグはあきらめて、書き込みした後、スタンドアローン動作で確認する他にありません。

今回の様にLED点灯する単純な制御であれば、スタンドアローン動作でも確認できますが、複雑な制御となった場合は、オンチップデバッグ無くして動作検証はできないことになりますので、ポートのピンアサインと使用する機能との兼ね合いは、基本設計時に慎重に検討する必要があります。

オンチップデバッグの際、MPLABの操作についてはマニュアルをご確認ください。