今回はマイコンのプログラムで定期タスクを作る方法をまとめてみます。
プログラムを時間管理で動かす
マイコンのプログラムでは、プログラムカウンタが最後まで行ってしまうと処理を終了してしまうため、下記のように無限ループを配置して、起動後、ずっと処理が継続されるように実装するのが一般的です。
ただ、この処理の中で、時間を制御しながら処理を行おうと思うと、いろいろ工夫が必要です。
もっとも簡単なのは、delay()関数などを使用し、処理と処理の間を一定間隔開ける方法です。
ただしこの方法だと、delay()関数実行中はただただ待機、他の処理を何も実行できなくなるため、無駄な時間が増えてしまいます。
例えば、下記のような例を考えてみます。
・ポートA0を10ms間隔で出力反転させる
・ポートA1を50ms間隔で出力反転させる
この処理において、ポートA1の出力反転間隔を保つために、例えば「delay_ms(50)」など使用した場合、その時点でそれより短周期のポートA0の制御ができなくなります。
割り込みを使えばよいのでは?
→これはもっともなのですが、割り込みを発生させることができるタイマなどは、マイコンにより数が決まっているため、全部のポートを制御したい場合などは、実現できなくなります。(PIC16F1827の場合、GPIOポート16本)
今回はこのような処理を、多数の割り込みを使用せずに実現できるサンプルを紹介してみます。
今回タイトルで並列処理、と書きましたが、PICなどのシンプルなマイコンにはマルチタスク機能はありません。
ただし、コーディング次第ではそれっぽく処理を作ることができます。
今回作成したプログラム
今回作成したプログラムの動作内容は下記です。
★3つのポート出力を異なる間隔で更新するプログラム
・10ms毎にポートRA0の出力を反転させるプログラム
・30ms毎にポートRA1の出力を反転させるプログラム
・80ms毎にポートRA2の出力を反転させるプログラム
プログラム全体は下記になります。
※少々長いですが、ほとんどコメントです。
/* PIC16F1827 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 = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#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 = ON // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)
/* CONFIG2 */
#pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON // PLL Enable (4x PLL enabled)
#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. */
/*==========================================================================================================================================*/
/*
* File: main.c
* Author: ICE_MEGANE
*
* Created on 2020/04/2, 22:26
*/
#include <xc.h>
/* 共通マクロ設定 */
#define CLEAR (0U)
#define SET (1U)
#define U8_MAX (0xFFU)
/* 共通型名称 */
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
/* 関数プロトタイプ宣言 */
static void func_main_s_variable_init( void );
static void func_main_s_register_setup( void );
static void func_main_s_program_start( void );
static void func_main_s_loop( void );
static void func_main_s_timer4_match_int_task( void );
/* 変数宣言 */
static u8 u8_main_s_loop_go;
/* 10ms反転用 */
static u8 u8_main_s_10ms_invert_flag;
/* 30ms反転用 */
static u8 u8_main_s_30ms_invert_cnt;
static u8 u8_main_s_30ms_invert_flag;
/* 70ms反転用 */
static u8 u8_main_s_70ms_invert_cnt;
static u8 u8_main_s_70ms_invert_flag;
/**************************************************************/
/* Function: */
/* main task */
/* */
/**************************************************************/
void main(void)
{
func_main_s_register_setup(); /* レジスタ初期化 */
func_main_s_variable_init(); /* 変数初期化 */
func_main_s_program_start(); /* プログラム開始設定 */
while(1)
{ /* 20ms 周期タスク */
if( u8_main_s_loop_go == SET )
{
func_main_s_loop();
u8_main_s_loop_go = CLEAR;
}
}
}
/**************************************************************/
/* Function: */
/* 割り込み処理 */
/* 割り込み発生時にここに来る */
/**************************************************************/
void __interrupt() isr( void )
{
if( (PIR3 & 0x02U) != 0 )
{ /* TMR4 一致割り込み発生 */
func_main_s_timer4_match_int_task(); /* TMR4 一致割り込み処理 */
PIR3 &= (u8)~0x02U; /* 割り込みフラグクリア */
}
}
/**************************************************************/
/* Function: */
/* メインループ処理 */
/* */
/**************************************************************/
static void func_main_s_loop( void )
{
u8 u8_loop_cnt;
/*********************/
/* 10ms間隔で出力反転 */
/*********************/
if( u8_main_s_10ms_invert_flag == CLEAR )
{
u8_main_s_10ms_invert_flag = SET;
}
else
{
u8_main_s_10ms_invert_flag = CLEAR;
}
/*********************/
/* 30ms間隔で出力反転 */
/*********************/
if( u8_main_s_30ms_invert_cnt < U8_MAX )
{
u8_main_s_30ms_invert_cnt++;
}
if( u8_main_s_30ms_invert_cnt > (u8)3 )
{
u8_main_s_30ms_invert_cnt = (u8)0;
if( u8_main_s_30ms_invert_flag == CLEAR )
{
u8_main_s_30ms_invert_flag = SET;
}
else
{
u8_main_s_30ms_invert_flag = CLEAR;
}
}
/*********************/
/* 70ms間隔で出力反転 */
/*********************/
if( u8_main_s_70ms_invert_cnt < U8_MAX )
{
u8_main_s_70ms_invert_cnt++;
}
if( u8_main_s_70ms_invert_cnt > (u8)7 )
{
u8_main_s_70ms_invert_cnt = (u8)0;
if( u8_main_s_70ms_invert_flag == CLEAR )
{
u8_main_s_70ms_invert_flag = SET;
}
else
{
u8_main_s_70ms_invert_flag = CLEAR;
}
}
/* 各フラグからポート出力へ反映 */
/* !!!! PORTAだと出力が正しく更新されないので注意 !!!! */
if( u8_main_s_10ms_invert_flag == SET )
{
LATA |= 0x01U; /* RA0 : HI */
}
else
{
LATA &= ~0x01U; /* RA0 : LOW */
}
if( u8_main_s_30ms_invert_flag == SET )
{
LATA |= 0x02U; /* RA1 : HI */
}
else
{
LATA &= ~0x02U; /* RA1 : LOW */
}
if( u8_main_s_70ms_invert_flag == SET )
{
LATA |= 0x04U; /* RA2 : HI */
}
else
{
LATA &= ~0x04U; /* RA2 : LOW */
}
/* これ以降、自由に処理を記述可能 */
}
/**************************************************************/
/* Function: */
/* 変数初期設定 */
/* */
/**************************************************************/
static void func_main_s_variable_init( void )
{
u8_main_s_loop_go = CLEAR;
u8_main_s_10ms_invert_flag = CLEAR;
u8_main_s_30ms_invert_flag = CLEAR;
u8_main_s_30ms_invert_cnt = (u8)0;
u8_main_s_70ms_invert_flag = CLEAR;
u8_main_s_70ms_invert_cnt = (u8)0;
}
/**************************************************************/
/* Function: */
/* レジスタ初期設定 */
/* */
/**************************************************************/
static void func_main_s_register_setup( void )
{
/* クロック周波数設定 */
/*=================================================================*/
/* +------------------------- 7 : SPLLEN */ /* PLL 無効化 ※#pragmaセクションで設定したので、実際は常時許可状態(datasheet P.67) */
/* | +----------------------- 6 : IRCF<3> */ /* 1110 : 32MHz HFINTOSC に設定 */
/* | | +--------------------- 5 : IRCF<2> */
/* | | | +------------------- 4 : IRCF<1> */
/* | | | | */
/* | | | | +------------- 3 : IRCF<0> */
/* | | | | | +----------- 2 : - */
/* | | | | | | +--------- 1 : SCS<1> */ /* 00 : FOSC ※#pragmaセクションで設定している */
/* | | | | | | | + ------ 0 : SCS<0> */
OSCCON = 0x70U; /* 0 1 1 1 - 0 # 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* クロック周波数微調整 */
/*=================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : - */
/* | | +--------------------- 5 : TUN5 */
/* | | | +------------------- 4 : TUN4 */
/* | | | | */
/* | | | | +------------- 3 : TUN3 */
/* | | | | | +----------- 2 : TUN2 */
/* | | | | | | +--------- 1 : TUN1 */
/* | | | | | | | + ------ 0 : TUN0 */
OSCTUNE = 0x00U; /* 0 0 0 0 - 0 0 1 1 */
/*-----------------------------------------------------------------*/
/* memo */
/* 特に調整するつもりはないので初期設定 */
/*=================================================================*/
/* Option register setup */
/* 今回は#pragmaセクションで設定したので、設定しなくてもOK。 */
/* 割り込み設定 1 */
/*=================================================================*/
/* +------------------------- 7 : GIE */ /* 全体割り込み 許可 */
/* | +----------------------- 6 : PEIE */ /* ペリフェラル割り込み 許可 */
/* | | +--------------------- 5 : TMR0IE */ /* タイマ0割り込み 禁止 */
/* | | | +------------------- 4 : INTE */ /* 外部割込み 禁止 */
/* | | | | */
/* | | | | +------------- 3 : IOCIE */ /* 外部ピン変化割り込み 禁止 */
/* | | | | | +----------- 2 : TMR0IF */ /* タイマ0 オーバーフロー割り込み禁止 */
/* | | | | | | +--------- 1 : INTF */ /* 外部割込みフラグ クリア */
/* | | | | | | | + ------ 0 : IOCIF */ /* 外部ピン変化割り込みフラグ クリア */
INTCON = 0x40U; /* 0 1 0 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* 初期化時点では全体割り込みは無効化しておく */
/*=================================================================*/
/* 割り込み設定 2 */
/*=================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : - */
/* | | +--------------------- 5 : CCP4IE */ /* CCP4割り込み 禁止 */
/* | | | +------------------- 4 : CCP3IE */ /* CCP3割り込み 禁止 */
/* | | | | */
/* | | | | +------------- 3 : TMR6IE */ /* タイマ6 一致割り込み 禁止 */
/* | | | | | +----------- 2 : - */
/* | | | | | | +--------- 1 : TMR4IE */ /* タイマ4 一致割り込み 許可 */
/* | | | | | | | + ------ 0 : - */
PIE3 = 0x02U; /* 0 0 0 0 - 0 0 1 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* 割り込み設定 3 */
/*=================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : - */
/* | | +--------------------- 5 : CCP4IF */ /* CCP4割り込みフラグ クリア */
/* | | | +------------------- 4 : CCP3IF */ /* CCP3割り込みフラグ クリア */
/* | | | | */
/* | | | | +------------- 3 : TMR6IF */ /* タイマ6 一致割り込みフラグ クリア */
/* | | | | | +----------- 2 : - */
/* | | | | | | +--------- 1 : TMR4IF */ /* タイマ4 一致割り込みフラグ クリア */
/* | | | | | | | + ------ 0 : - */
PIR3 = 0x00U; /* 0 0 0 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* ポート入出力方向設定 */
/*=================================================================*/
/* +------------------------- 7 : TRISA7 */
/* | +----------------------- 6 : TRISA6 */
/* | | +--------------------- 5 : TRISA5 */ /* マイコンの設計上読み取りのみ */
/* | | | +------------------- 4 : TRISA4 */
/* | | | | */
/* | | | | +------------- 3 : TRISA3 */
/* | | | | | +----------- 2 : TRISA2 */
/* | | | | | | +--------- 1 : TRISA1 */
/* | | | | | | | + ------ 0 : TRISA0 */
TRISA = 0x20U; /* 0 0 1 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* 0:出力、1:入力 */
/* RA4は入力専用ポートなので注意 */
/*=================================================================*/
/* ポート出力データ設定 */
/*=================================================================*/
/* +------------------------- 7 : RA7 */
/* | +----------------------- 6 : RA6 */
/* | | +--------------------- 5 : RA5 */
/* | | | +------------------- 4 : RA4 */
/* | | | | */
/* | | | | +------------- 3 : RA3 */
/* | | | | | +----------- 2 : RA2 */
/* | | | | | | +--------- 1 : RA1 */
/* | | | | | | | + ------ 0 : RA0 */
PORTA = 0x00U; /* 0 0 0 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* ポート出力ラッチ設定 */
/*=================================================================*/
/* +------------------------- 7 : LATA7 */
/* | +----------------------- 6 : LATA6 */
/* | | +--------------------- 5 : LATA5 */
/* | | | +------------------- 4 : LATA4 */
/* | | | | */
/* | | | | +------------- 3 : LATA3 */
/* | | | | | +----------- 2 : LATA2 */
/* | | | | | | +--------- 1 : LATA1 */
/* | | | | | | | + ------ 0 : LATA0 */
LATA = 0x00U; /* 0 0 0 0 - 0 0 0 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* タイマ4の設定 */
/* ※一致割り込みを使用するだけなら、CCPの設定はいらない */
/* タイマレジスタ初期化 */
/*=================================================================*/
/* +------------------------- 7 : */
/* | +----------------------- 6 : */
/* | | +--------------------- 5 : */
/* | | | +------------------- 4 : */
/* | | | | */
/* | | | | +------------- 3 : */
/* | | | | | +----------- 2 : */
/* | | | | | | +--------- 1 : */
/* | | | | | | | + ------ 0 : */
TMR4 = 0xFFU; /* 0 0 0 0 - 0 0 1 1 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
/* タイマ4初期設定 */
/* 今回は100msの定期割り込みを発生させたい */
/*====================================================================*/
/* +------------------------- 7 : - */
/* | +----------------------- 6 : TxOUTPS<3> */ /* タイマ4 ポストスケーラ:1001 -> 1/10 */
/* | | +--------------------- 5 : TxOUTPS<2> */
/* | | | +------------------- 4 : TxOUTPS<1> */
/* | | | | */
/* | | | | +------------- 3 : TxOUTPS<0> */
/* | | | | | +----------- 2 : TMRxON */ /* タイマ4動作 停止 */
/* | | | | | | +--------- 1 : TxCKPS<1> */ /* タイマ4 プリスケーラ:11 -> 1/64 */
/* | | | | | | | + ------ 0 : TxCKPS<0> */
T4CON = 0x4BU; /* 0 1 0 0 - 1 0 1 1 */
/*--------------------------------------------------------------------*/
/* memo */
/* クロック:32MHz */
/* ペリフェラルクロック:Fosc/4 = 8MHz */
/* 割り込みポストスケーラ:1:10 -> 800kHz */
/* プリスケーラ:1/64 -> 12,500Hz */
/* 一致カウント数設定:125 -> 12,500/125 = 100Hz -> 1 0ms */
/*====================================================================*/
/* タイマ4 一致割り込み発生数値 */
/*=================================================================*/
/* +------------------------- 7 : */
/* | +----------------------- 6 : */
/* | | +--------------------- 5 : */
/* | | | +------------------- 4 : */
/* | | | | */
/* | | | | +------------- 3 : */
/* | | | | | +----------- 2 : */
/* | | | | | | +--------- 1 : */
/* | | | | | | | + ------ 0 : */
PR4 = 0x7DU; /* 1 1 1 1 - 1 0 1 0 */
/*-----------------------------------------------------------------*/
/* memo */
/* 125:10ms相当に設定 */
/*=================================================================*/
/* テンプレート */
/*=========================================================*/
/* +------------------------- 7 : */
/* | +----------------------- 6 : */
/* | | +--------------------- 5 : */
/* | | | +------------------- 4 : */
/* | | | | */
/* | | | | +------------- 3 : */
/* | | | | | +----------- 2 : */
/* | | | | | | +--------- 1 : */
/* | | | | | | | + ------ 0 : */
/* 0 0 0 0 - 0 0 1 1 */
/*-----------------------------------------------------------------*/
/* memo */
/* */
/*=================================================================*/
}
/**************************************************************/
/* Function: */
/* プログラム開始直前の設定 */
/* */
/**************************************************************/
static void func_main_s_program_start( void )
{
T4CON |= 0x04U; /* タイマ4 動作開始 */
INTCON |= 0x80U; /* 全体割り込み許可 */
}
/**************************************************************/
/* Function: */
/* タイマ4 一致割り込み処理 */
/* */
/**************************************************************/
static void func_main_s_timer4_match_int_task( void )
{
u8_main_s_loop_go = SET;
}
これを実行した場合、下記のような出力が得られます。

Delay関数を使わなくても、各ポートが、異なるタイミングで、意図した時間で規則的に出力反転できていることが確認できます。
詳細設定
詳細設定をざっと流して説明していきたいと思います。
PICマイコンの内蔵発振器の周波数設定
まずはPICマイコンで計算速度を決めるクロック周波数を設定します。
今回はマイコン内部の内蔵発信器を使用してパルスを発生させ、その周波数を32MHzになるよう設定しました。
マイコンにはクロック発生源を外部に用意し、外から矩形波上のパルスを入力して計算を行う場合と、マイコンに内蔵された周波数発振器を使用して計算を行うタイプがあります。
基本的にはクリスタルなどを使って外部からクロック入力したほうが精度が良いです。
今回はシビアな制御をしないこと、部品点数が少なくて済むことから、内蔵発振器を使用することにしました。
まずはブロック図を確認し、設定の全体像を把握します。
PIC16F1827のクロック選択は、データシートの55ページの下記の図で示されます。
赤色の矢印で示したルートが、今回行う設定のルートです。

次に文章を確認し、レジスタ設定の詳細について調べていきます。
データシートの59ページには下記のような記載があります。

この文章から、OSCCONレジスタのIRCFbit(3-0)を設定することで、HFINTOSC(16MHz)の高速内蔵発振クロックを使用できるとわかります。
また他に、FOSC(2-0)ビットを100にするorOSCCONレジスタのSCSビットを「1x」にする必要があると、記載があります。
また60ページには、下記のような記載があります。

この文章から、HFINTOSC(High Frequency INternal OSCillator:高速内蔵発振器)の設定が32MHzから31kHzの中で選択できるとわかります。
また今回設定する一番高速の32MHzを使用する場合、4倍のPLL機能を使用する必要があると、記載があります。
4倍のPLLについては、先ほどのブロック図中の下記の部分で、8MHzの部分から分岐して「4xPLL」へつながるルートに記載があります。
PIC16F1827では4倍のPLL機能を使うことにより、8MHzのクロックを4倍した32MHzの高速クロックへ変換することが可能です。
大元の発振器が16MHzでそれをPostscalerで分周して8MHzに、それを4倍して32MHzに、、、と、若干回りくどいです。。。
この辺はマイコン設計時の都合、もともとは16MHz~32kHzの11パターンの分周周波数を選ぶようにしか設計してなかった等、いろいろあるのかな~と思います。

まずは内蔵発振器に「HFINTOSC」を選択し、「4xPLL」を有効にする設定を行っていきます。
PICマイコンでは基本的なレジスタ設定は代入形式で実施しますが、Configuration Word などの一部の初期設定については、#pragmaセクションで実施することも可能です。
(#pragmaセクションでの設定が基本的に高優先度になるようです。)
MPLABの場合、#pragma関連の設定は、MPLABの「Set Configuration Bits」からコードを生成することが可能です。
下記のように、プロジェクトの「Production」から選択します。

次に表示される画面では、下記のように設定します。

設定が完了したら下記の「Generate Source Code to Output」ボタンを押し、表示されるコードをコピーします。

その後、main.cの#includeなどの定義より上の、一番上に張り付けます。
次にOSCCONレジスタの設定を行っていきます。
OSCCONレジスタは下記のように8bitのレジスタになっています。

bit:7 では、PLLの動作許可/禁止設定を行います。
今回は先ほど#pragmaセクションでPLLを有効にしているので、ここでの設定に寄らず4xPLLは常に動作許可になります。
(#pragmaセクションのほうが高優先度)

bit:6 では、内蔵発振器の16MHzを分周する、PostScallerの設定を行っていきます。
今回は先のブロック図より、8MHzの出力を4xPLLにつないで・・・という使い方をするので、「1110」を設定します。

bit:2 では特に設定用にビットが割り当てられていないため、0にしておきます。
bit:1、0 ではシステムクロックの設定もとを登録します。
今回は「Internal osscilator block」を使用するので、bit1を1に設定しておけばOKです。bit0はどっちでも可。
#pragmaセクションにて、FOSCはINTOSCにすると設定したので、「00:FOSCでの設定に依存」にしておいてもOKです。

次に、OSCTUNEレジスタを設定します。
OSCTUNEレジスタでは内蔵発振器の周波数に対し、若干の補正を行うことができます。
内蔵クロックを使用する時点で数%ずれるので、どこまで正確に設定できるかが微妙なところですが、工場出荷時の設定外でも、ユーザーが調節できるようになっています。
細かい修正が希望であれば、任意で値を設定します。

以上でPICマイコンの内蔵発振器周りの設定は完了です。
割込み許可の設定
次に、割込み許可の設定を行っていきます。
今回、複数ピンを任意のタイミングで制御するために、割り込みは使わない・・・と、最初に言いましたが、定期的な処理を作成するための時間基準として、最低1つ必要です。

今回は下記のように設定しました。
GIE:1
→割り込みを使用するために有効化
PEIE:1
→タイマなどの周辺機能の割り込みを使うために有効化
その他:初期値のまま

TMR4IE:1
→タイマ4のコンペア・マッチ割り込みを有効化

TMR4IF:0
→タイマ4の割り込みフラグをクリア
※以降、割り込みの監視にはこのフラグを使用する。
ポートの入出力設定
次はポートの入出力設定を行います。
PIC16F1827はRAポートとRBポートがあり、各ポートの入出力設定を「TRISA」「TRISB」レジスタで設定することが可能です。


PIC16F1827では、「1:入力 / 0:出力」となっています。

一部のポートは入力、出力が固定(0や1を設定しても切り替え不可能)なので注意が必要です。
(PIC16F1827の場合、PORT5は読み取り固定)
余談ですが、「TRIS-A」のTRISは、「Tri - state A port」の略です。
「Tri - state」の「Tri」は、ポートの状態が入力、出力、Hi-Zの3状態を持つため、このように命名されているっぽいです。
今回は「RA0、RA1、RA2」の3つのポートからGPIO出力を行って波形確認をするため、「TRISA<0:2>」を「0」に設定します。
出力ラッチの初期化、設定
次に出力ラッチです。
PICにはPORTAとLATAの2つがありますが、書き込み時に適しているのはLATAレジスタになります。
PORTAもほぼ同様の機能ですが、LATAと異なり実際のポートの電圧レベルに影響を受けます。
この辺は下記の記事でまとめています。
-
-
【PIC】PICマイコンのPORTAレジスタとLATAレジスタの違いについて紹介する -リード・モディファイ・ライトについて説明する-
備忘録も兼ねてメモしておきます PORTAとLATAは何が違うんや。 PICマイコンでデータ出力を決める際、PORTAの更新とLATAの更新の2つの方法があります。 この2つの方法ですが、基本的には結 ...
続きを見る


PICの場合、ポート出力をHIにしたければ1に、LOWにしたければ0するだけでOKです。
タイマの設定
次に、時間管理した処理を作るためのタイマを設定していきます。
今回はマイコンが持つハードウェアとしての機能(外部への出力)は使用せず、ソフトウェア内部の割り込みだけを使います。
今回は、あらかじめ設定したカウントに到達した際に発生するわりこみである「コンペア・マッチ」割り込みを使います。
PIC16F1827にはTimer0、Timer1、Timer2、Timer4、TImer6の計5つのタイマーが搭載されています。
各TImerはできることに制限があります。PIC16F1827の場合は下記のようになります。

ただし今回使うコンペアマッチ割り込みは、上記の3つの特殊機能とは異なり、どのタイマでも使用できる標準の機能です。
今回はCCPに関する設定は行いません。
タイマを設定するため、まずはタイマのブロック図から確認していきます。
TIMR2、4、6は、最大256までカウントできる8ビットタイマになります。

Timerは基本的に設定したクロック周波数で0、1、2、...といった具合にカウントアップしていく機能になります。
上記のブロック図を順を追って言葉にすると、
・Fosc/4 の周波数を、
・Prescalerで分周し、
・分周した周波数で0からカウントアップし、カウント中の値はTMRxレジスタに入る (≒TMRxレジスタでカウントアップ)
・PRxレジスタで設定した値とTMRxの現在のカウント値が一致(EQ)したら、「Reset」を発生させてTMRxレジスタをゼロクリアする
・一致した際にイベントを起こしたい場合は、割り込みフラグTMRxIFを使用する
・一致した際のイベントを何回かに1度にしたい場合、Postscalerを設定して分周、割り込みの発生回数を下げる。
となります。
このブロック図中の設定は、「TxCON」レジスタで行います。xにはTImer2,4,6のいずれかの数字をあてて読んでいけばOKです。
今回は下記のようなに設定しました。
・クロック:Fosc = 32MHz
・ペリフェラル(周辺タイマなど、CPU以外のクロック):Fosc/4 = 32/4 = 8MHz
・割り込みポストスケーラ:1:10 → 実質8MHz/10 = 800kHzで割り込み発生
・プリスケーラ:1/64 → 800kHz/64 = 12,5kHzで割り込み発生
・一致カウント数設定:125 → 12,500Hz/125 = 100 Hzで割り込み発生
➡10ms周期でコンペア・マッチ割り込み発生

bit:7は機能割り当てなしです。
bit:6, 5, 4, 3 では、TImerxの割込みをどのくらい分周するか、Postscaler を設定します。

※Postscalerはあくまで「割込みの発生回数を変える」だけであり、タイマの動作としては何度もオーバーフローとコンペア・マッチを繰り返している点は注意が必要です。
bit:2 では、Timerxの動作状態を設定します。

bit:1では、タイマのカウントアップ速度を決めるPrescalerを設定します、先ほどのPostscalerとは別物です。
Prescalerは4種類から選べます。

分周のイメージは下記のようになります。
矢印が付いたタイミングで0→1→2→・・・といった具合にカウントアップを行いますが、分周によってタイミングが間引きされ、カウントアップの速度が遅くなります。

ソフトの話
今回は割り込みをタイマを1つだけ使って発生させるだけなので、クロック設定とタイマの設定を行えば、マイコン上のハードウェア設定としては完了です。
ついでにソフトウェアの話も少しだけしてみます。
プログラムの時間管理について
今回作成したプログラムでは、無限ループの中に条件を一つ置き、タイマ割り込みでループ許可が出たら処理を実行しています。
このため、ループ内の処理の実行周期が、タイマ割り込みに10ms周期で動作しています。
このためこの中に置いた変数は、すべて10ms間隔で更新される変数になります。
1つの変数を更新した後、delayのように待つ必要がないため、空き時間があればほかの処理を実行可能です。
マイコンとしては1つの変数やフラグを更新し、次の処理に移っているだけなので、逐次処理には変わりありません。
ただ、ソフトウェアを作る側から見ると、時間管理はタイマ割り込みが実行してくれるので、変数更新後の空き時間に別の処理を入れることができ、見かけ上複数の処理を並列して実行することができます。
割り込みの記述
マイコンでは割り込みが発生した場合、それまで実行していたプログラムの処理を一時中断し、特定のアドレスにある特殊なプログラム(割り込み処理)に飛んできます。
この割り込み処理は、特定のアドレスに記述する必要があります。
今回の場合、データシートに記載があるように、0x0004hというアドレスが、割り込み発生時に飛んでくる場所になります。

ここに飛んできたときに実行する処理を作りたい場合は、下記のような記述をします。
void __interrupt() isr( void )
{
func_user(); /* 割り込み時に実行させたい処理を書く */
}PICの場合、割り込み要因によらず、1つのアドレスに戻ってくる特徴があります。
(コンペアマッチ以外にも、多数の割り込み機能があります)
割り込み発生時にどのようにCPU割り込みが入るかを表した図がデータシート上にありますが、下記のように最終的に1本に集約されています。


このため、割り込み発生時は「何が要因で発生した割り込みなのか」を切り分けるために、割り込みフラグを調べる処理を書く必要があります。
今回は割り込みは1つしか使っていませんが、PIR3の2bit目をチェックしてタイマ4のコンペア・マッチ割り込みであることを確認後、割り込み処理を実行するようにしています。
まとめ
今回はタイマ割り込みを1つ使用し、GPIOを規則的な時間で制御する例を作ってみました。
他にもタイマリソースを使わず時間管理する方法があれば、作ってみたいと思います。
それでは、また。