こんにちは、ENGかぴです。
PICマイコンにはタイマー機能があり8ビットタイマーとしてTMR0があります。TMR0の初期値を調整することでベースタイマを生成しオーバーフローした回数を管理することでソフトウェアタイマを作ることができます。
本記事はPICマイコンのTMR0を使用していますが、タイマーであれば同様の考え方でソフトウェアタイマを構成することができます。TMR0の設定などはMCCを使って実装しています。
PIC16F1827で動作確認したことについてリンクをまとめています。
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
タイマー管理の考え方
PIC16F1827の設定はMCCを使っています。MCCの使い方については下記記事にまとめています。
MPLAB Code Configurator(MCC)の追加と使い方
TMR0と割り込みの設定
最初に共通事項であるSystem Moduleの設定を行います。
内部クロックを使用するためINTOSCを選択しています。Intemal Clockは4MHz(任意でも良い)を選択しています。クロックを高速にするほど消費電流が増えてしまいます。
TMR0はDevice Resources内のPeripherals欄のTimerを選択しTMR0をクリックして追加します。PIC16F1827においてTMR0を割り込みを使用して1msでオーバーフローさせます。
TMR0のオーバーフロー割り込みを使用する場合はEnable Timer Interruptをチェックします。TMR0を1ms毎にオーバーフローさせるためTimer Periodに1msを入力しています。1msでオーバーフローすることが目的なのでプリスケーラは1:4以上にする必要があります。
Software Settingsは1msの割り込みが入力した値になった場合にコールバック関数を呼び出す設定です。例えばSoftware Settingsで10(0x0A)を入力すると1msの割り込みが10回後にコールバック関数が呼び出されます。
TMR1・TMR2・TRM4・TMR6についても同様に1msのタイマを生成してベースタイマにすることができます。TMR1は16ビットタイマなので長い時間の管理に向くためベースタイマの用途よりも他の用途に使用した方がよさそうです。
タイマー管理のイメージ
メイン関数でタイマ管理を行いますがタイマ管理に遷移するための条件である変数cnt10msは割り込み内でカウントを更新するため割り込みが10回以上発生した場合にタイマ管理処理を行います。
TMR0割り込みが1ms毎に発生させた場合はcnt10msが10以上になったときif文の条件を満たしTimerLedなどを更新するため10msを最低時限としたソフトウェアタイマを構成することができます。
例)変数TimerLedを10をセットしてタイマを動作する
タイマ管理処理は10ms毎に発生するためTimerLedのカウントを更新頻度が10ms毎になるため10回更新したとすると100ms経過のタイマとなる。
タイマー管理に必要な定義と変数
タイマー管理を行う際に準備しておきたい定義と変数について説明します。定義は#defineで行います。TIME_OFFとTIME_UPについて準備を行い、必要に応じて任意のタイマーの時限を決めるために値を定義しておきます。
//--------------定数定義----------------------------
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define TIME_LED_CNT_MAX 20 //200ms経過
#define TIME_LED_CNT_MAX2 10 //100ms経過
#define TIME_LED_CNT_MAX3 5 //50ms経過
#define TIME_BASE_MAX 10 //ベースタイマカウント値
//--------------変数定義----------------------------
int16_t cnt10ms;
int16_t TimerLed1;
int16_t TimerLed2;
次に変数を宣言します。定義をみると-1を扱っていることから符号なしではなく符号付で定義する必要があります。
タイマーの時限を長くしたい場合は状況によってint8_tやint16_tを使い分けます。cnt10msは10msが経過した時のカウントなので符号なしで宣言します。
初期化をするときにTIME_OFFをセットするとタイマーを使用しない設定になります。セットしない場合は0となるのでタイムアップがセットされている状態になります。
タイマー管理でメリットとデメリット
タイマーの考え方としては、__delay_ms(数値)や__delay_us(数値)によって管理してもよいのですが、これらはソフトウェアウェイトであるためメイン関数を素早く周回させたい場合においては適した方法ではありません。
タイマ管理の方法ではソフトウェアウェイトを使用しないためメイン関数の周回に殆ど影響されずに任意のタイミングでタイムアップするタイマが構築できることがメリットです。
今回はTMR0のオーバーフロー割り込みを1ms毎に発生させてベースタイマを構築していますが、分解能を細かくするほど割り込みの回数が増えてしまうことがデメリットとなります。
例えばメイン処理の周回が2ms以上であった場合はメイン処理が周回するまでの間に2回TMR0がオーバーフローしてベースタイマが更新されるためベースタイマのカウント数(cnt10ms)が累積していき徐々に精度が落ちてしまいます。
TMR0を使ってタイマー管理する方法
MCCが作成した関数をmain.cに組み込んでTMR0オーバーフロー割り込みを発生させタイマ管理を行います。
動作確認用の回路
タイマ管理によってLED1とLED2の点灯タイミングを変化させます。SW1を押している期間にLED3を点灯させLED1の点灯/消灯のタイミングを変更します。RB0はウィークプルアップしているため信号レベルが浮くことはありません。ピンの配置は以下の通りです。
PB0はINPUTでウィークプルアップ(WPU)設定しています。WPUを有効にするためにはRegistersのnWPUENをenabledにする必要があります。
Easy SetupでWPUにチェックするのみでは有効にならないので注意が必要です。Notificationsにワーニングで通知されます。
広告
変数の準備と各種初期化
割り込みの許可とTMR0割り込み時にコールバックする関数をセットします。
SYSTEM_Initialize(); //MCCによるレジスタ設定
INTERRUPT_GlobalInterruptEnable(); //GIEをセット
INTERRUPT_PeripheralInterruptEnable(); //PEIEをセット
TMR0_SetInterruptHandler(TMR0_INT); //TMR0割り込み時にコールする関数を指定
/* TMR0オーバーフロー割り込みでの処理 */
void TMR0_INT(void){
++cnt10ms;
}
main.cのwhile(1)より前には初期化処理を追加します。割り込みの許可に関するものをコメントアウトして割り込みが有効になるようにします。TMR0がオーバーフローしたときにTMR0_INT()関数がコールバックされるように設定します。
TMR0_INT()では割り込みが発生した回数を更新しています。
タイマー管理を実装する
TMR0オーバーフロー割り込みでcnt10msを更新しますが、メイン関数内では更新した回数を条件としてタイマ管理を行うmainTimer()関数を追加します。
/* タイマ管理関数 */
void mainTimer(void){
if( cnt10ms >= TIME_BASE_MAX){ //規定回数オーバーフローしたか
cnt10ms -= TIME_BASE_MAX;
if( TimerLed1 > TIME_UP ){
--TimerLed1; //タイマを更新
}
if( TimerLed2 > TIME_UP ){
--TimerLed2;
}
}
}
タイマー管理はTMR0オーバーフロー割り込みで更新しているcnt10msの回数が規定回数以上になったときにタイマーを更新する処理を行います。
タイマ管理の分解能はTMR0のオーバーフロー時間に対する規定回数までの時間になります。TMR0オーバーフロー割り込みが1msであり規定回数としてTIME_BASE_MAXを10設定しているのでタイマ管理の分解能は10msになります。
TimerLed1やTimerLed2をカウントスタートしたいときは変数に値をセットするとタイマー管理によってタイマー値が更新されていきタイムアップします。
タイムアップした時に任意の処理を実装することでタイムアップ後の動作を作ることができます。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include "mcc_generated_files/mcc.h"
//--------------定数定義----------------------------
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define TIME_LED_CNT_MAX 20 //200ms経過
#define TIME_LED_CNT_MAX2 10 //100ms経過
#define TIME_LED_CNT_MAX3 5 //50ms経過
#define TIME_BASE_MAX 10 //ベースタイマカウント値
//--------------変数定義----------------------------
int16_t cnt10ms;
int16_t TimerLed1;
int16_t TimerLed2;
uint8_t ledmode;
//--------------関数プロトタイプ宣言----------------------------
void mainApp(void); //メイン関数内での処理
void TMR0_INT(void); //タイマー0割り込みでの処理
void mainTimer(void);
/* Main application */
void main(void)
{
SYSTEM_Initialize();
INTERRUPT_GlobalInterruptEnable();
INTERRUPT_PeripheralInterruptEnable();
TMR0_SetInterruptHandler(TMR0_INT);
while (1)
{
mainApp();
mainTimer();
}
}
/* TMR0オーバーフロー割り込みでの処理 */
void TMR0_INT(void){
++cnt10ms;
}
/* タイマ管理関数 */
void mainTimer(void){
if( cnt10ms >= TIME_BASE_MAX){ //規定回数オーバーフローしたか
cnt10ms -= TIME_BASE_MAX;
if( TimerLed1 > TIME_UP ){
--TimerLed1; //タイマを更新
}
if( TimerLed2 > TIME_UP ){
--TimerLed2;
}
}
}
/* main処理 */
void mainApp(void){
if( TimerLed1 == TIME_UP ){ //タイムアップしたか
if( IO_RB0_GetValue()){
TimerLed1 = TIME_LED_CNT_MAX; //タイムアップしたのでOFFにする
IO_RA3_SetLow();
}else{
TimerLed1 = TIME_LED_CNT_MAX3; //タイムアップしたのでOFFにする
IO_RA3_SetHigh();
}
IO_RA7_Toggle(); //RA7の出力を反転
}
if( TimerLed2 == TIME_UP){
TimerLed2 = TIME_LED_CNT_MAX2;
switch( ledmode ){ //点灯消灯タイミングを切り替える
case 0:
IO_RB6_SetHigh(); //点灯
break;
case 1:
IO_RB6_SetLow(); //消灯
break;
case 2:
IO_RB6_SetHigh(); //点灯
break;
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
IO_RB6_SetLow(); //消灯
break;
}
if( ++ledmode >= 10 ){
ledmode = 0;
}
}
}
/* End of File */
本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていることが前提となります。MCCをインストールする方法は下記記事を参考にしてください。
MPLAB Code Configurator(MCC)の追加と使い方
関連リンク
関連リンクではPIC16F1827を使ってマイコンの基本動作を確認するために動作確認したことについてまとめています。
PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ
最後まで、読んでいただきありがとうございました。
ソフト構成に寄りますが最近のマイコンは高速動作できるものが多くメイン処理の周回時間が1ms以内に収まることが多いことから問題になることは少ないと思います。