タイマ機能とタイマ割り込み
LED点灯プログラムに対して、ON/OFFを周期的に処理される点滅プログラムを作成してみたいと思います。
タイマ機能の使い方を習得いただき、最終的に自社製品のプログラム化に活用ください。
PIC16F1614では、8bitタイマが4ch、16bitタイマが3ch用意されています。
今回、8bitタイマを1ch使用してLED点滅処理を実現させたいと思います。

Timer0の設定
PIC16F1614 仕様書 「21.0 TIMER0 MODULE」を見ていただくと、設定必要な各レジスタへの言及がされています。
ただ、これだけでは設定方法やどの様な順番でプログラムを書いてよいのかわからないので、解説していきたいと思います。
まず、タイマを何秒間隔で計測するか?を決めないといけません。
例えば、LEDの点滅周期を1Hzにする場合、500ms ON、500ms OFFとなります。
タイマの使い方として全ては書きませんがいくつか方法があります。
方法1:タイマを500ms間隔で割り込みさせて、
1回目の割り込みでON、2回目の割り込みでOFFと割り込み毎にLED制御をトグルする。
方法2:タイマを100ms間隔で割り込みさせて、
5回割り込み発生でON、更に5回割り込み発生でOFFと、割り込み回数を数えて、
if文で5回の割り込み数をチェックして、割り込み5回毎にLED制御をトグルする。
方法3:方法2を更に細かくカウントする。
タイマを10ms間隔で割り込みさせて、
50回割り込み発生でON、更に50回割り込み発生でOFFと、割り込み回数を数えて、
if文で5回の割り込み数をチェックして、割り込み50回毎にLED制御をトグルする。
3通りを記載しましたが、何れもLEDを1Hzで点滅させることが可能です。
故に、LEDの1Hz点滅だけがゴールなのであれば、どの方法でもOKです。
汎用的なタイマ機能
LED点滅1Hzが開発したい機能なのであれば、上記の通り何れの方法でも問題ありません。
しかし、種々の機能を搭載し、それぞれの機能で計測したい時間が異なる場合はどうでしょうか?
機能の数だけタイマの数を用意しますか? 7chありますが7パターン以上の時間を測定したい場合は足りないです。さてどうしましょう。。。
先ほどのLED点滅時の3つの方法がヒントになるのですが、最も測定分解能が高い設定にすると汎用的に利用できます。
つまり上記方法1~方法3であれば、方法3が3つの中では分解能が最も高いです。
それでは、方法3を設定してみましょう。
前提計算
・クロックは内部発振設定・・・プログラム内のConfigにて FOSC = INTOSC としている。
・発振周波数・・・プログラムにて OSCCON = 0x7A として16MHzとしている。
1命令のカウント時間:Fosc / 4 ⇒ 16MHz / 4 = 4MHz ⇒ 1カウントは0.25us
Fosc / 4 の定義は、仕様書「FIGURE 21-1: TIMER0 BLOCK DIAGRAM」の赤枠部にある。

10msに必要なカウント数は、10ms / 0.25μs = 40,000カウント
Timer0は8bit(最大256)タイマなので、プリスケーラを使用して1byte(256)以内の数値に抑えます。
プリスケーラ選定
仕様書「21.2 Register Definitions: Option Register」を参照し、
OPTION_REG = 0xD7 とする。内訳は以下に設定し、bit6,bit7はデフォルト値とする。
bit5 :TMR0CSを0に設定(FOSC/4)
bit3 :PSAを0に設定(Timer0にプリスケーラ―をアサイン)
bit2-0:PSを111に設定(1:256のプリスケーラ―設定)
この状態で10msに必要なカウント数を再度計算してみます。
1カウント = 0.25μs × 256 = 64μs
必要カウント数:10ms / 64μs ≒ 156.25 = 156カウント
1byteの256以内に収まるので、156カウントしたら10ms時間経過した状態となり、定期的に156カウントを繰り返すようにTimer0を設定すれば、10msが定周期計測できるようになります。
Timer0の設定値を計算する
156という値をどこに設定するか?ですが、
TMR0というレジスタが存在します。
このレジスタは、設定されたカウント周期でインクリメントされます。
つまり、8bitのレジスタなので0から256までカウントアップします。
カウントアップする時間間隔が設定周期(=64us)になります。
TMR0 = 156; としたいところですが、この様にしてしまうと、
設定直後から、157、158、159・・・255とカウントアップします。
つまり、256 – 156 = 100カウントしたところでオーバーフローとなります。
この後で設定しますが、通常オーバーフローで割り込み発生するので、
100カウント×64us = 6.4msで割り込みとなります。
設定したいのは10ms周期で割り込み発生するタイマなので、これでは実現できません。
正しくは、256 – 156 = 100 の100をTMR0に設定することです。
よって、TMR0 = 100;
計算してみましょう。
156カウント×64us = 9.984ms
凡そ10msになりましたので、正しい設定になります。
ちょうど10msにならないのは、必要カウント数が156.25で端数が出ていた為です。
ここで設定している周期タイマの時間は、厳密な精度は無くても構わない設計思想です。
処理時間を計測しなければならない場合は、タイマ機能を割り当てして計測することが求められます。
アバウトでも差支えのない時間計測として、LED点滅周期は数十usの誤差は視覚に影響はありませんので、大勢に影響のない機能で時間測定が必要なものに使用します。
ここまでをプログラムに反映してみましょう。
/***********************************************************************************
* *
* 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
OPTION_REGbits.T0CS = 0; // 内部クロック(Fosc/4)
OPTION_REGbits.PSA = 0; // プリスケーラをTimer0へ
OPTION_REGbits.PS = 0b111; // 1:256
/* 10msを計測する為の初期値を設定 */
/* この時点からカウントが始まる */
TMR0 = 100;
/* ここにタイマ0割り込みの設定を書く */
/*************/
/* PORTA設定 */
/*************/
/* 出力初期値 */
LATA = 0x00;
/* 入出力方向設定 */
/* 1 = 入力ポート */
/* 0 = 出力ポート */
TRISA = 0xFB; /* TRISA2を出力ポート設定 */
}
46行目の OPTION_REGbits.PS = 0b111; ですが、0bはその後に続く数値が2進数で表記する記号になります。
ここでは、0~2bitまでの3bitを全て1で設定することを指示しています。
この後、解説をしますが、Timer0の割り込み設定がまだ書かれていません。
一度、割り込み処理を調べて反映してみてください。
現在は、52行目の TMR0 = 100; から、10msカウントを継続し続ける設定になっています。
割り込み処理
Timerに限らず割り込み機能が使えるものは、割り込み処理が必要になります。
割り込みの概念と、割り込みベクタと呼ばれる割り込み発生時に遷移する関数の書き方などが、以下の公式サイトURLにあります。
PIC割り込み処理
その上で、要点を解説していきたいと思います。
Timer0割り込み有効設定
割り込みを使う場合、使いたい機能に対して有効設定をしなければなりません。
今回であればTimer0に対して、指定カウント数を経てTMR0レジスタのカウントがオーバーフローしたい際に割り込み発生して欲しいのでTimer0の機能に割り込み有効を設定します。

割り込み設定はINTCONレジスタの設定が関係します。
上記赤枠部のbitがTimer0に関わる設定です。
GIE・・・1:Enable設定すると、全体に対して割り込み機能を有効にします。
各機能毎に割り込み有効し、且つGIEをEnableにして初めて割り込み発生します。
TMR0IE・・・1:Timer0のカウンタがオーバーフローしたことで割り込み有効とする
今回、Timer0の割り込みを使いたいので、有効設定にする。
TMR0IF・・・0:Timer0のカウンタがオーバーフローした際に 1 がセットされるフラグbit。
このbitが1にだと割り込み発生となり、割り込み処理ベクタへ遷移する。
そのため、割り込み有効bit(=TMR0IE)を設定する前に 0 クリアしておく。
※TMR0IEを設定する際にTMR0IFが1になっていると、
設定と同時に割り込み発生してしまいます。
Timer0の割り込み処理順序
Timer0の割り込み設定をするには手順があります。
①INTCONレジスタ TMR0IFビットを 0 クリアする
②INTCONレジスタ TMR0IEビットに 1 設定する
③INTCONレジスタ GIEビットに 1 設定する
①でTimer0の割り込み発生状態をクリアしておきます。
②でTimer0のオーバーフローで割り込みが発生するように設定します。
③でマイコン全体の割り込み機能自体を有効に設定します。
これで、Timer0は設定したカウント周期ごとに割り込みが発生するようになります。
割り込み設定までをプログラムに追加してみましょう。
// 割り込み設定
INTCONbits.TMR0IF = 0;
INTCONbits.TMR0IE = 1;
INTCONbits.GIE = 1;
コメントを含めた4行をmain関数へ追加しました。
/***********************************************************************************
* *
* 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
OPTION_REGbits.T0CS = 0; // 内部クロック(Fosc/4)
OPTION_REGbits.PSA = 0; // プリスケーラをTimer0へ
OPTION_REGbits.PS = 0b111; // 1:256
/* 10msを計測する為の初期値を設定 */
/* この時点からカウントが始まる */
TMR0 = 100;
/* ここにタイマ0割り込みの設定を書く */
// 割り込み設定
INTCONbits.TMR0IF = 0;
INTCONbits.TMR0IE = 1;
INTCONbits.GIE = 1;
/*************/
/* PORTA設定 */
/*************/
/* 出力初期値 */
LATA = 0x00;
/* 入出力方向設定 */
/* 1 = 入力ポート */
/* 0 = 出力ポート */
TRISA = 0xFB; /* TRISA2を出力ポート設定 */
}
これで割り込み設定は反映されたのですが、実処理としては割り込みが発生した際にプログラム処理が遷移する先がありません。
割り込みが発生するとISR(割り込みサービスルーチン)と呼ばれる処理をプログラムに追加する必要があります。
ISRは、割り込み発生時に遷移する関数の様なものです。
実際の記述ルールも関数に似ており、PICマイコンでは以下の様に記述します。
void __interrupt() ISR(void)
{
//割り込み時の処理
}
関数の戻り値の役割に相当するものは存在しませんので、先頭は void と記述します。
void は何もない。という意味でしたね。
続いて、__interrupt() と記述します。
PICのXC8コンパイラでは、この記述のある関数を割り込みのISRだと認識するようになっています。
最後に、ISR(void) と記述します。
ISRは関数名です。
ISRでなくても、TEST_INT とか SAMPLE とか、自由に付けて構いません。
引数はないので (void) としておきます。
これで、割り込み発生時に void __interrupt() ISR(void) 関数へ処理が飛んできます。
「飛んでくる」というのは、main関数の処理をしている最中に割り込み発生すると、main関数の処理を途中で止めて、この関数に遷移してきます。
そして、この関数内の処理を一通り済ませて、遷移前のmain関数の処理へ戻ります。
PIC16シリーズでは割り込みベクタは1つ
マイコンによっては、機能毎に割り込み関数を用意する必要がある品種もあります。
PIC16シリーズでは、割り込みベクタは1つとなり、ISR関数は全体で1つだけ用意することになります。
つまり、Timer0以外にA/D、UART通信などの他機能で割り込みを使いたい場合、
各機能の割り込み設定を有効にすると、全てこの関数へ集約されます。
ISR関数内の処理記述にはいくつかルールがあります。
①関数冒頭で割り込みフラグをクリアすること
⇒割り込み発生機能のレジスタフラグをクリアしないと、永遠に割り込みが入り続きます。
②どの機能が割り込み発生したかは、プログラムで判定すること
⇒上述の通り、機能毎に関数が分かれていないので一つの割り込み関数内で、
割り込み発生となった機能がどれなのか?を判定しなければなりません。
③割り込み関数内の処理は、助長にならないようにする
⇒極論、割り込みフラグをクリアして、割り込み発生したことを検出するぐらいにしたい。
検出した後、main関数内に戻った後、重たい処理をする様なイメージ。
これは、割り込み関数内で更に割り込みが発生する(=割り込みのネスト)と、
処理ができない為、できるだけ処理を短くして関数を抜けることです。
それでは、割り込み関数を含めた①~③をコードに反映してみましょう。
/***********************************************************************************
* *
* 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
uint8_t timer_flag = 0; //タイマー割り込み発生のフラグ
void main(void)
{
/**************/
/* 初期化処理 */
/**************/
OSCCON = 0x7A; // 内部発振周波数 16MHz 設定 0b01111010
OPTION_REGbits.T0CS = 0; // 内部クロック(Fosc/4)
OPTION_REGbits.PSA = 0; // プリスケーラをTimer0へ
OPTION_REGbits.PS = 0b111; // 1:256
/* 10msを計測する為の初期値を設定 */
/* この時点からカウントが始まる */
TMR0 = 100;
/* ここにタイマ0割り込みの設定を書く */
// 割り込み設定
INTCONbits.TMR0IF = 0;
INTCONbits.TMR0IE = 1;
INTCONbits.GIE = 1;
/*************/
/* PORTA設定 */
/*************/
/* 出力初期値 */
LATA = 0x00;
/* 入出力方向設定 */
/* 1 = 入力ポート */
/* 0 = 出力ポート */
TRISA = 0xFB; /* TRISA2を出力ポート設定 */
//Timer0割り込みが発生したかフラグのチェック
if (timer_flag == 1)
{
timer_flag = 0;
// Timer0割り込み発生した時にしたい処理
}
}
void __interrupt() ISR(void)
{
if (INTCONbits.TMR0IF) { //①、②Timer0割り込み発生したかフラグをチェック
INTCONbits.TMR0IF = 0; // 発生していたらフラグビットをクリアする
timer_flag = 1; //③割り込み関数内は短処理とする為、フラグを用意して、
} //割り込み発生したことを記して抜ける
}
void __interrupt() ISR(void)内の処理は、①~③のコメントの通りです。
main関数内は、timer_flagとしてTimer0の割り込み発生有無を示すフラグを用意したので、フラグの状態を確認し、割り込み発生時は 1 が格納されているので、1であれば割り込み発生したと判定し、その時にさせたい処理を書きます。
ここでLEDの点灯から反映してこなかったmain処理が一つあります。
これまでのプログラムや上記のプログラムは、main関数を一通り処理すれば関数を抜けて終了してしまいます。
何度も繰り返し処理をしてもらいたいので、実際の記述は以下の様に書かないと繰り返し処理になりません。
/***********************************************************************************
* *
* 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
uint8_t timer_flag = 0; //タイマー割り込み発生のフラグ
void main(void)
{
/**************/
/* 初期化処理 */
/**************/
OSCCON = 0x7A; // 内部発振周波数 16MHz 設定 0b01111010
OPTION_REGbits.T0CS = 0; // 内部クロック(Fosc/4)
OPTION_REGbits.PSA = 0; // プリスケーラをTimer0へ
OPTION_REGbits.PS = 0b111; // 1:256
/* 10msを計測する為の初期値を設定 */
/* この時点からカウントが始まる */
TMR0 = 100;
/* ここにタイマ0割り込みの設定を書く */
// 割り込み設定
INTCONbits.TMR0IF = 0;
INTCONbits.TMR0IE = 1;
INTCONbits.GIE = 1;
/*************/
/* PORTA設定 */
/*************/
/* 出力初期値 */
LATA = 0x00;
/* 入出力方向設定 */
/* 1 = 入力ポート */
/* 0 = 出力ポート */
TRISA = 0xFB; /* TRISA2を出力ポート設定 */
while(1)
{
//Timer0割り込みが発生したかフラグのチェック
if (timer_flag == 1)
{
timer_flag = 0;
// Timer0割り込み発生した時にしたい処理
}
}
}
void __interrupt() ISR(void)
{
if (INTCONbits.TMR0IF) { //①、②Timer0割り込み発生したかフラグをチェック
INTCONbits.TMR0IF = 0; // 発生していたらフラグビットをクリアする
timer_flag = 1; //③割り込み関数内は短処理とする為、フラグを用意して、
} //割り込み発生したことを記して抜ける
}
main関数の最後の方に while(1){ } を追加しました。
これで、Timer0割り込みが発生したかフラグのチェックを繰り返し処理を行い、割り込み発生するのを処理しながら待ちます。
後は、「// Timer0割り込み発生した時にしたい処理」の部分に、LEDの点滅処理を書くことで、
点滅処理を実現させることができます。
さて、500ms点灯/500ms消灯とする点滅処理の場合、どのようにプログラムを書きますか?
ヒントは以下になります。
1.Timer0の割り込みは10msごとに発生する。
2.500msをカウントする変数を用意し、割り込みの回数を数える処理を記述する
3.点灯状態/消灯状態の状態保持するための変数を用意して、状態判定をしながら、
LED点灯/消灯をポート制御する
一度、考えてみてください。
500ms ON 500ms OFFの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
int timer_flag = 0; //タイマー割り込み発生のフラグ
int led_on = 0; //on時間のカウンタ
int led_off = 0; //off時間のカウンタ
int led_state = 0; //ON状態:1 OFF状態:0
void main(void)
{
/**************/
/* 初期化処理 */
/**************/
OSCCON = 0x7A; // 内部発振周波数 16MHz 設定 0b01111010
OPTION_REGbits.T0CS = 0; // 内部クロック(Fosc/4)
OPTION_REGbits.PSA = 0; // プリスケーラをTimer0へ
OPTION_REGbits.PS = 0b111; // 1:256
/* 10msを計測する為の初期値を設定 */
/* この時点からカウントが始まる */
TMR0 = 100;
/* ここにタイマ0割り込みの設定を書く */
// 割り込み設定
INTCONbits.TMR0IF = 0;
INTCONbits.TMR0IE = 1;
INTCONbits.GIE = 1;
/*************/
/* PORTA設定 */
/*************/
/* 出力初期値 */
LATA = 0x00;
/* 入出力方向設定 */
/* 1 = 入力ポート */
/* 0 = 出力ポート */
TRISA = 0xFB; /* TRISA2を出力ポート設定 */
while(1)
{
//Timer0割り込みが発生したかフラグのチェック
if (timer_flag == 1)
{
/* LEDがOFF状態の処理 */
if(led_state == 0)
{
if(led_off >= 50)
{
/* LED点灯 */
LATAbits.LATA0 = 1; // LATA0をHIGH出力
led_state = 1; //LEDをON状態に変更
led_off = 0; //OFF状態のカウンタをクリア
}else
{
/* LED消灯 */
LATAbits.LATA0 = 0; // LATA0をLOW出力
led_off++; //10ms毎の割り込みをカウント
}
}else
{
/* LEDがON状態の処理 */
if(led_on >= 50)
{
/* LED消灯 */
LATAbits.LATA0 = 0; // LATA0をLOW出力
led_state = 0; //LEDをOFF状態に変更
led_on = 0; //ON状態のカウンタをクリア
}else
{
/* LED点灯 */
LATAbits.LATA0 = 1; // LATA0をHIGH出力
led_on++; //10ms毎の割り込みをカウント
}
}
timer_flag = 0;
}
}
}
void __interrupt() ISR(void)
{
if (INTCONbits.TMR0IF) { //①、②Timer0割り込み発生したかフラグをチェック
INTCONbits.TMR0IF = 0; // 発生していたらフラグビットをクリアする
timer_flag = 1; //③割り込み関数内は短処理とする為、フラグを用意して、
} //割り込み発生したことを記して抜ける
}
追加したのは以下になります。
int led_on = 0; //on時間のカウンタ
int led_off = 0; //off時間のカウンタ
int led_state = 0; //ON状態:1 OFF状態:0
グローバル変数を3つ用意しました。
led_onはONの時間を数える変数です。
led_offはOFFの時間を数える変数です。
どちらも50カウント以上になるまでカウントをしています。
割り込み10ms × 50回 = 500ms ということです。
led_stateは、今現在がON状態なのか、OFF状態なのか、現在状態を0と1で設定する変数です。
実際の点滅処理は以下です。
/* LEDがOFF状態の処理 */
if(led_state == 0)
{
if(led_off >= 50)
{
/* LED点灯 */
LATAbits.LATA0 = 1; // LATA0をHIGH出力
led_state = 1; //LEDをON状態に変更
led_off = 0; //OFF状態のカウンタをクリア
}else
{
/* LED消灯 */
LATAbits.LATA0 = 0; // LATA0をLOW出力
led_off++; //10ms毎の割り込みをカウント
}
}else
{
/* LEDがON状態の処理 */
if(led_on >= 50)
{
/* LED消灯 */
LATAbits.LATA0 = 0; // LATA0をLOW出力
led_state = 0; //LEDをOFF状態に変更
led_on = 0; //ON状態のカウンタをクリア
}else
{
/* LED点灯 */
LATAbits.LATA0 = 1; // LATA0をHIGH出力
led_on++; //10ms毎の割り込みをカウント
}
}
if(led_state == 0)の{ }内が、消灯状態の時に処理する内容です。
消灯状態なのでled_off変数で消灯時間をカウントします。
if(led_off >= 50)で、50回以上に到達している場合は、ON状態に切り替えてLEDもONにして、ON状態の処理へ遷移させます。この時、忘れずに消灯時間をカウントしていた変数を0にクリアすることです。
50回以下の場合は、LEDを消灯させて消灯時間を割り込み毎に1カウントずつ増加させています。
f(led_state == 1)の{ }内が、点灯状態の時に処理する内容です。
点灯状態なのでled_on変数で点灯時間をカウントします。
if(led_on >= 50)で、50回以上に到達している場合は、OFF状態に切り替えてLEDもOFFにして、OFF状態の処理へ遷移させます。この時、忘れずに点灯時間をカウントしていた変数を0にクリアすることです。
50回以下の場合は、LEDを点灯させて点灯時間を割り込み毎に1カウントずつ増加させています。
上記の消灯と点灯の処理を交互に繰り返すことで、LEDの点滅処理が実現されます。
点滅の周期やON/OFFの時間を変えたい場合は、led_on変数やled_off変数のカウントを50から変更すれば、好きな点滅状態に変更することができます。
最後にもう少し参考書で学習した内容を反映してみましょう。
50という数値は定義にします。
/***********************************************************************************
* *
* 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
#define ON_TIME 50
#define OFF_TIME 50
#define LED_HIGH 1
#define LED_LOW 0
#define LED_ON_MODE 1
#define LED_OFF_MODE 0
int timer_flag = 0; //タイマー割り込み発生のフラグ
int led_on = 0; //on時間のカウンタ
int led_off = 0; //off時間のカウンタ
int led_state = 0; //ON状態:1 OFF状態:0
void main(void)
{
/**************/
/* 初期化処理 */
/**************/
OSCCON = 0x7A; // 内部発振周波数 16MHz 設定 0b01111010
OPTION_REGbits.T0CS = 0; // 内部クロック(Fosc/4)
OPTION_REGbits.PSA = 0; // プリスケーラをTimer0へ
OPTION_REGbits.PS = 0b111; // 1:256
/* 10msを計測する為の初期値を設定 */
/* この時点からカウントが始まる */
TMR0 = 100;
/* ここにタイマ0割り込みの設定を書く */
// 割り込み設定
INTCONbits.TMR0IF = 0;
INTCONbits.TMR0IE = 1;
INTCONbits.GIE = 1;
/*************/
/* PORTA設定 */
/*************/
/* 出力初期値 */
LATA = 0x00;
/* 入出力方向設定 */
/* 1 = 入力ポート */
/* 0 = 出力ポート */
TRISA = 0xFB; /* TRISA2を出力ポート設定 */
while(1)
{
//Timer0割り込みが発生したかフラグのチェック
if (timer_flag == 1)
{
/* LEDがOFF状態の処理 */
if(led_state == LED_OFF_MODE)
{
if(led_off >= OFF_TIME)
{
/* LED点灯 */
LATAbits.LATA0 = LED_HIGH; // LATA0をHIGH出力
led_state = LED_ON_MODE; //LEDをON状態に変更
led_off = 0; //OFF状態のカウンタをクリア
}else
{
/* LED消灯 */
LATAbits.LATA0 = LED_OFF; // LATA0をLOW出力
led_off++; //10ms毎の割り込みをカウント
}
}else
{
/* LEDがON状態の処理 */
if(led_on >= ON_TIME)
{
/* LED消灯 */
LATAbits.LATA0 = LED_OFF; // LATA0をLOW出力
led_state = LED_OFF_MODE; //LEDをOFF状態に変更
led_on = 0; //ON状態のカウンタをクリア
}else
{
/* LED点灯 */
LATAbits.LATA0 = LED_ON; // LATA0をHIGH出力
led_on++; //10ms毎の割り込みをカウント
}
}
timer_flag = 0;
}
}
}
void __interrupt() ISR(void)
{
if (INTCONbits.TMR0IF) { //①、②Timer0割り込み発生したかフラグをチェック
INTCONbits.TMR0IF = 0; // 発生していたらフラグビットをクリアする
timer_flag = 1; //③割り込み関数内は短処理とする為、フラグを用意して、
} //割り込み発生したことを記して抜ける
}
プログラムの冒頭部分に続いて、以下の様にdefine定義を追加しました。
#define ON_TIME 50
#define OFF_TIME 50
#define LED_HIGH 1
#define LED_LOW 0
#define LED_ON_MODE 1
#define LED_OFF_MODE 0
ON_TIME、OFF_TIMEは、if文でカウント判定していた割り込み回数です。
50と数値記載するとどの様な意味の値なのかがわからないので、ON/OFFの時間であることを示唆させるとイメージがしやすいです。
続いて、LEDをHIGH/LOW出力させていた箇所も1 or 0を記載していましたが、
LED_HIGH or LED_LOWと書くことで、出力状態が分かりやすくなります。
最後にLEDの状態を表す変数に対して1 or 0を記載していましたが、
LED_ON_MODE/LED_OFF_MODEと書くことで、LED状態制御状態が分かりやすくなります。
この様な感じで、制御のイメージが見るとイメージしやすいようにカスタマイズしていくことが、プログラミングの上達の一歩となります。
もう少し言及するならば、main関数内の初期化処理部分を初期化用の関数を用意して、main関数内からcallする方が整理されて可読しやすくなります。
例えば以下の様な感じです。
/***********************************************************************************
* *
* 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
#define ON_TIME 50
#define OFF_TIME 50
#define LED_HIGH 1
#define LED_LOW 0
#define LED_ON_MODE 1
#define LED_OFF_MODE 0
void Init(void);
int timer_flag = 0; //タイマー割り込み発生のフラグ
int led_on = 0; //on時間のカウンタ
int led_off = 0; //off時間のカウンタ
int led_state = 0; //ON状態:1 OFF状態:0
void Init(void)
{
/**************/
/* 初期化処理 */
/**************/
OSCCON = 0x7A; // 内部発振周波数 16MHz 設定 0b01111010
OPTION_REGbits.T0CS = 0; // 内部クロック(Fosc/4)
OPTION_REGbits.PSA = 0; // プリスケーラをTimer0へ
OPTION_REGbits.PS = 0b111; // 1:256
/* 10msを計測する為の初期値を設定 */
/* この時点からカウントが始まる */
TMR0 = 100;
/* ここにタイマ0割り込みの設定を書く */
// 割り込み設定
INTCONbits.TMR0IF = 0;
INTCONbits.TMR0IE = 1;
INTCONbits.GIE = 1;
/*************/
/* PORTA設定 */
/*************/
/* 出力初期値 */
LATA = 0x00;
/* 入出力方向設定 */
/* 1 = 入力ポート */
/* 0 = 出力ポート */
TRISA = 0xFB; /* TRISA2を出力ポート設定 */
}
void main(void)
{
//初期化処理
Init();
while(1)
{
//Timer0割り込みが発生したかフラグのチェック
if (timer_flag == 1)
{
/* LEDがOFF状態の処理 */
if(led_state == LED_OFF_MODE)
{
if(led_off >= OFF_TIME)
{
/* LED点灯 */
LATAbits.LATA0 = LED_HIGH; // LATA0をHIGH出力
led_state = LED_ON_MODE; //LEDをON状態に変更
led_off = 0; //OFF状態のカウンタをクリア
}else
{
/* LED消灯 */
LATAbits.LATA0 = LED_OFF; // LATA0をLOW出力
led_off++; //10ms毎の割り込みをカウント
}
}else
{
/* LEDがON状態の処理 */
if(led_on >= ON_TIME)
{
/* LED消灯 */
LATAbits.LATA0 = LED_OFF; // LATA0をLOW出力
led_state = LED_OFF_MODE; //LEDをOFF状態に変更
led_on = 0; //ON状態のカウンタをクリア
}else
{
/* LED点灯 */
LATAbits.LATA0 = LED_ON; // LATA0をHIGH出力
led_on++; //10ms毎の割り込みをカウント
}
}
timer_flag = 0;
}
}
}
void __interrupt() ISR(void)
{
if (INTCONbits.TMR0IF) { //①、②Timer0割り込み発生したかフラグをチェック
INTCONbits.TMR0IF = 0; // 発生していたらフラグビットをクリアする
timer_flag = 1; //③割り込み関数内は短処理とする為、フラグを用意して、
} //割り込み発生したことを記して抜ける
}
void Init(void)という初期化関数を用意し、ファイルの冒頭部でvoid Init(void);としてプリプロセッサ定義を書きます。
main関数内で書かれていた初期化処理を、void Init(void)内に記述し、main関数は Init(); とすることで、main関数内が整理されます。
機能毎に関数へ纏める癖をつけておくと、プログラムの可読もしやすくなります。
上記は一例の点滅処理ですが、他にもいろんなプログラムの設計方法があります。
自分なりの流儀を確立していけるようにしていきましょう。