はじめに
いつもはMPLAB X IDEのMCCを使ってなんとなくタイマーを構築し使っていたため、ちゃんと理解しようと、MCCの手を借りずにデータシートを読み解きながら理解したことをアウトプットしてみる。そんな投稿。
概要
今回利用するPICはPIC12F1840
とする。
秋月へのリンクはこちら。
データシートはこちら。
種類にもよるが、1つのPIC内にいくつかタイマーが用意されている。
今回利用するPIC12F1840については3つ用意されている。
- Timer0
- Timer1
- Timer2
タイマーがいくつあるか、どんなタイマーがあるかはデータシートのここをチェックする。
(データシート P2)
該当する行に2/1
とあるが、表のタイトルにもある通りそれぞれ8bitタイマー、16bitタイマーの数となる。
8bitは256カウント、16bitは65,536カウントできる。
1カウントでどれぐらいの時間を持たせるかは設定次第。
用途によって選択するタイマーが異なってくるが、今回は一番シンプルなTimer0(8bitタイマー)について理解する。
Timer0の仕組み
データシートを確認しながら進めていく。
説明箇所は重要な記載に絞る。
Timer0概要
Timer0の説明はデータシートP143から始まる。
20.0 TIMER0 MODULE
The Timer0 module is an 8-bit timer/counter with the following features:
・8-bit timer/counter register (TMR0)
・8-bit prescaler (independent of Watchdog Timer)
・Programmable internal or external clock source
・Programmable external clock edge selection
・Interrupt on overflow
・TMR0 can be used to gate Timer1
20.1 Timer0 Operation
The Timer0 module can be used as either an 8-bit timer or an 8-bit counter.
ここではTimer0に関する主な機能が羅列されている。
- Timer0は8bitタイマーで、8bitのプリスケーラ(後述)を持っている
- Timer0をカウントするタイミングを決めるクロックソースは内部、または、外部を設定できる
- Timer0がオーバーフローした際に割り込みが発生する
- Timer0のオーバーフローをTimer1のゲート機能に入力できる
Timer1にはゲート機能と呼ばれる特定の入力と同時にカウントを始めることが可能な機能を持っている。例えばセンサーの入力がONからOFFまでの時間を計測したいときに利用する。
超音波距離計測センサー等。
タイマーモード
20.1.1 8-BIT TIMER MODE
The Timer0 module will increment every instruction cycle, if used without a prescaler. 8-bit Timer mode is selected by clearing the TMR0CS bit of the OPTION_REG register.
プリスケーラを利用しない場合、Timer0はPICの命令サイクルごとでカウンタがインクリメントされる。
OPTION_REG
レジスタのTMR0CS
ビットを0にすることでこのような動きとなる。
1にすると外部クロックによってカウンタがインクリメントする動きとなる。
プリスケーラ
20.1.3 SOFTWARE PROGRAMMABLE PRESCALER
A software programmable prescaler is available for exclusive use with Timer0. The prescaler is enabled by clearing the PSA bit of the OPTION_REG register.
Timer0用に独立したプリスケーラが用意されている。
OPTION_REG
レジスタのPSA
ビットを0にすることでプリスケーラが有効となる。
There are eight prescaler options for the Timer0 module ranging from 1:2 to 1:256. The prescale values are selectable via the PS<2:0> bits of the OPTION_REG register. In order to have a 1:1 prescaler value for the Timer0 module, the prescaler must be disabled by setting the PSA bit of the OPTION_REG register.
プリスケーラには1:2から1:256までの8つの分周設定がある。
OPTION_REG
レジスタのPS
ビットに値を設定することで選択できる。
PS ビット値 |
プリスケーラ設定 |
---|---|
000 | 1 : 2 |
001 | 1 : 4 |
010 | 1 : 8 |
011 | 1 : 16 |
100 | 1 : 32 |
101 | 1 : 64 |
110 | 1 : 128 |
111 | 1 : 256 |
設定値はデータシートP145に掲載されている。
The prescaler is not readable or writable. All instructions writing to the TMR0 register will clear the prescaler.
プリスケーラの値(カウント値)は読み書きができない。
TMR0
レジスタの設定時にクリアされる。
割り込み
20.1.4 TIMER0 INTERRUPT
Timer0 will generate an interrupt when the TMR0 register overflows from FFh to 00h. The TMR0IF interrupt flag bit of the INTCON register is set every time the TMR0 register overflows, regardless of whether or not the Timer0 interrupt is enabled. The TMR0IF bit can only be cleared in software. The Timer0 interrupt enable is the TMR0IE bit of the INTCON register.
Timer0はTMR0
レジスタオーバーフロー時に割り込みを発生させ、INTCON
レジスタのTMR0IF
ビットに1
をセットする。
割り込みを有効にするにはINTCON
レジスタのTMR0IE
ビットに1
をセットする必要がある。
尚、TMR0IF
ビットのクリアは利用側のソフトウェア側で行う必要がある。
Timer0のタイマー値設計
今回は内部クロックとプリスケーラを利用し、大体50ms間隔でLEDの点灯を切り替えるようなタイマー設計を行う。
PICの周波数は4MHzとする。
今回想定する入力ルートがわかりやすいようにダイアグラム上で薄く赤線を引いておいた。
尚、タイマー値を設計する上で必要な箇所は①と④となる。
図で◯がついた番号は後述に掲載するソース内の設定箇所とリンクするようにしてある。
まずは①のFOSC/4
について、今回はPICの周波数を4MHzとしたためFOSCは4MHzとなる。
1MHz=4MHz/4
次に④のプリスケーラについて、一旦1:128
ぐらいで試算してみる。
7,812.5Hz=1,000,000Hz/128
上記7,812.5Hz
は1秒間の周波数なので、1周波がどれぐらいの時間なのかを計算し、Timer0のカウント値の最大で掛ける。
0.000128秒/周波=1秒/7,812.5周波数
0.032768秒=0.000128秒*256カウント
32msとなった。
今回50ms欲しいので、もう少し多めに分周してみる。
プリスケーラを1:256
で再計算する。
3,906.25Hz=1,000,000Hz/256
0.000256秒/周波=1秒/3,906.25周波数
タイマーオーバーフローまでの時間
0.065536秒=0.000256秒*256カウント
65msとなった。いい感じ。
TMR0
レジスタの初期値に多少ゲタを履かせれば大体50msになる。
0.050176秒=0.000256秒*(256-60)カウント
60カウントをTMR0
レジスタに初期設定してあげればよさそう。
ソースコード
#include <xc.h>
#pragma config FCMEN = OFF
#pragma config IESO = OFF
#pragma config CLKOUTEN = OFF
#pragma config BOREN = ON
#pragma config CPD = OFF
#pragma config CP = OFF
#pragma config MCLRE = ON
#pragma config PWRTE = OFF
#pragma config WDTE = OFF
#pragma config FOSC = INTOSC
#pragma config LVP = OFF
#pragma config DEBUG = OFF
#pragma config BORV = LO
#pragma config STVREN = ON
#pragma config PLLEN = OFF
#pragma config WRT = OFF
#define _XTAL_FREQ 4000000
#define LED LATA2
void TMR0Setup() {
TMR0IE = 1;
OPTION_REG = 0b00000111; // ③1:256で分周
TMR0CS = 0; // ②内部クロック利用
PSA = 0; // ④プリスケーラ利用
}
void __interrupt() isr (void) {
if(TMR0IF) {
// 大体50ms間隔で点滅
TMR0IF = 0;
TMR0 = 60;
LED = ~LED;
}
}
void Setup() {
OSCCON = 0b01101010; // ①4MHz
PORTA = 0x00; // 0で初期化
TRISA = 0b11111011; // ra2を出力ピンに設定
ANSELA = 0x00; // 全てデジタルIO
nWPUEN = 0;
INTCONbits.GIE = 1; // グローバル割り込み許可
TMR0Setup();
}
void main() {
Setup();
while(1) {
__delay_ms(100);
}
}
おわりに
今までMCCでいい感じに設定してきたが、データシートをしっかり読み解き仕組みを理解することができたのは大きな収穫。
ちなみに生成AIも活用したので理解が早かった。