2014/03/30

タイマ割込みでLED点滅(AVR)

 前回の記事はLED点滅タイミングに_delay_ms()関数を使いましたが、今回はAVR内蔵タイマを使ってみたいと思います。ついでに外部クロックも使ってみたいと思います。

ゴール


     
  • LEDの点滅周期は1秒(点灯0.1秒、消灯0.9秒)
  • 点灯、および消灯のタイミングをタイマ割込みで行う  
  • 外部クロック(20MHz X'tal)を使う

タイマ割込みについて


 ATmega168は8bitタイマと16bitタイマを持っていますが、秒単位の長い時間を測るには8bitでは足りないので16bitタイマを使うことにします。タイマをピンに出力し直接LEDを制御しても良いのですが、ここではタイマ割込みを使います。
 タイマ/カウンタのブロック図が参考文献1のFigure16-1(16-bit Timer/Counter block Diagram)にあります。眺めてみると、タイマ値TCNTnと比較レジスタOCRnAが一致した時に割込みを発生させることができるようです。同様にOCRnBとの一致でも割込みを発生できるようです。タイマのクリアは"Control Logic"で行うようですが、この"Control Logic"に"TOP"という入力があり、OCRnAと一致した時に発生できるようです。
 TOPになるとタイマ値をクリアできれば指定した時間周期での割込みが発生できそうです。このような使い方はタイマをMode=4(CTC/OCR1A)にするとできるようです。このモードはOCR1Aレジスタに設定した値になるとカウンタが0に戻ります。(詳細は参考文献1の16.9.2章を参照のこと)
 これで2種類のタイマ割込みが用意できました。OCR1Aを1秒にOCR1Bを0.1秒に設定すればそれぞれの割込みでLED点灯、LED消灯ができます。
 ではOCR1A/OCR1Bにどんな値を設定すれば良いでしょうか? それにはタイマの1カウントがどのくらいの時間かを知る必要があります。タイマの1カウントは1クロック分なのでタイマに入力するクロックを調べる必要があります。

タイマに入力するクロックについて


 前述のタイマ/カウンタのブロック図を引き続き眺めてみます。"Control Logic"の入力に"clkTn"があります。ここがタイマに入力するクロックのようです。外部ピン入力とプリスケーラ入力から選択できるようです。ここではプリスケーラ入力を選びます。プリスケーラは参考文献1のTable 16-5(Clock Select Bit Description)によるとclkI/Oを1/1024まで分周できるようです。clkI/Oは参考文献1のFigure9-1(Clock Distribution)によると"AVR Clock Control Unit"の出力です。
 "AVR Clock Control Unit"の入力であるSystem ClockはFuseビットで設定します。Fuseビットの設定はAVRISPmkIIで書き込みをする時に設定します。Fuse設定は以下の3点を変更しました。
  • BODLVEL=4V3
  • SUT_CKSEL=EXTFSXTAL_1KCK_14CK_0MS
  • CKDIV8にチェックを入れる
 ここだけはAtmel Studioを使って書き込みをしています。この設定により外部クロック(20MHz X'tal)を使い、内部で1/8(つまり2.5MHz)にしてからタイマにclkI/Oが供給されます。
 プリスケーラの設定(参考文献1のTable16-5)を幾つにするかですが1/64にしました。これでタイマのクロックは39.0625KHz(=2.5MHz/64)になります。1秒タイマにするにはOCR1A=0x9897(=39,063)にします。

LEDについて


 LEDは前回と同じくPORT Bのbit5に接続されているLEDを使います。

プログラムについて


 例によってdebian環境(avr-gcc+avr-libc)で作成しています。

リスト1 main.c

/*
 * LED点滅プログラム(タイマ割込み使用)
 *
 * For AE-ATmega board
 *
 * Ver. 1.0 2014/03/30
 */
#include <avr/io.h>
#include <avr/interrupt.h>

void init_timer1(void) {

  /*
   * タイマ設定
   * 外部クロック   : 20MHz (X'tal)
   * CKDIV8=1       : 20MHz / 8 = 2.5MHz
   * プリスケーラ   : 2.5MHz / 64 = 39,062.5 Hz
   * 比較レジスタ1A : 39,063 ( 39,063/ 39,062.5 = 1.000013 Hz)
   * 比較レジスタ1B :  3,906
   * 波形発生モード : CTC(OCR1A)
   */

  /*
   * 比較レジスタ1Aで1秒毎に割込みを発生させる。→LED ON
   * 比較レジスタ1Bは1A割込み発生の0.1秒後に割込みが発生。→LED OFF
   */

  TCCR1A = 0x00;
  TCCR1B = _BV(WGM12) | _BV(CS11) | _BV(CS10);
  TCCR1C = 0x00;
  OCR1AH = 0x98;        // 16bitレジスタなので上位バイトを先にライトする
  OCR1AL = 0x97;        // OCR1A=39,063=0x9897
  OCR1BH = 0x0F;        // OCR1B=3,906=-0x0F42
  OCR1BL = 0x42;
  TIMSK1 = ( _BV(OCIE1B) | _BV(OCIE1A) );
  sei();                // Enable all interrupts
}

ISR(TIMER1_COMPA_vect) {
  PORTB |= _BV(PB5);    // LED ON
}

ISR(TIMER1_COMPB_vect) {
  PORTB &= !(_BV(PB5)); // LED OFF
}

int main(void) {

  DDRB  = _BV(DDB5);    // PORTB bit5を出力に設定

  init_timer1();        // タイマ初期化
  while(1);             // 無限ループ(割込み待ち)
}

参考文献


  1. Atmel ATmega48P/V 88P/V 168P/V datasheet
  2. avr libcのinterrupt.h解説

0 件のコメント:

コメントを投稿

【備忘録】時系列データの編集方法(R言語, tidyverse)

TimeSeries.knit 1 サンプルデータ作成 2 日付単位に集計する 2.1 月毎集計 2.2 四半期毎集計 ...