LECTURE 7 タイマ/カウンタ0を使う

○ 関連レジスタ

タイマ/カウンタ0関連のレジスタを以下に紹介します。マクロについては、avr-libc で使用できる定義名を掲載しています。また、日本語のレジスタ名とビット名は、英語の定義名称を適当に訳してます。

  • タイマ/カウンタ0制御レジスタ(TCCR0)

    ビット番号 7 6 5 4 3 2 1 0
    マクロ FOC0 WGM00 COM01 COM00 WGM01 CS02 CS01 CS00
    初期値 0 0 0 0 0 0 0 0

    各ビットの説明を以下に書きます。

    ビット名 内容
    FOC0(OC0強制出力) 動作種別が標準動作および比較一致タイマ/カウンタクリアの場合に、1 を設定することで強制的に比較一致を発生させます。割り込みやタイマ/カウンタのクリアは行われず、OC0 ピンの出力のみ変更になる様です。自分は使い所がよく分りませんが…
    WGM00(波形生成モード) 後述の WGM01 と併せて動作種別(LECTURE 6 タイマ/カウンタについてを参照)を設定します。

    WGM01 WGM00 動作種別
    0 0 標準動作
    0 1 位相基準PWM
    1 0 比較一致タイマ/カウンタクリア
    1 1 高速PWM

    COM01/COM00(比較0出力モード) OC0 ピン(PDIP パッケージでは 4 ピン)の出力方式を設定します。

    • WGM00 = 0 の場合(標準動作、CTC動作)
      COM01 COM00 内容
      0 0 OC0 には出力されない
      0 1 比較一致において反転
      1 0 比較一致において 0(Low)
      1 1 比較一致において 1(High)

    • WGM00 = 1、WGM01 = 1 の場合(高速PWM)
      COM01 COM00 内容
      0 0 OC0 には出力されない
      0 1 予約
      1 0 比較一致で 0(Low)、TCNT0 = 0 で 1(High)
      1 1 比較一致で 1(High)、TCNT0 = 0 で 0(Low)

    • WGM00 = 1、WGM01 = 0 の場合(位相基準PWM)
      COM01 COM00 内容
      0 0 OC0 には出力されない
      0 1 予約
      1 0 TCNT0 上昇時の比較一致で 0(Low)、下降時の比較一致で 1(High)
      1 1 TCNT0 上昇時の比較一致で 1(High)、下降時の比較一致で 0(Low)

    WGM01(波形生成モード) WGM00 の説明を参照
    CS02/CS01/CS00(クロック選択) タイマ/カウンタ0で使用するクロックのシステムクロックに対する分周比、あるいは外部クロックの使用を以下の組み合わせで設定します。

    CS02 CS01 CS00 内容
    0 0 0 タイマ/カウンタ0停止
    0 0 1 分周比無し
    0 1 0 CK/8
    0 1 1 CK/64
    1 0 0 CK/256
    1 0 1 CK/1024
    1 1 0 外部クロック。T0 ピン(PDIP パッケージでは 1 ピン)の立ち下がりエッジ
    1 1 1 外部クロック。T0 ピン(PDIP パッケージでは 1 ピン)の立ち上がりエッジ


  • タイマ/カウンタ0カウンタ(TCNT0)

    8ビットのカウンタ値を格納するレジスタです。

    ビット番号 7 6 5 4 3 2 1 0
    マクロ - - - - - - - -
    初期値 0 0 0 0 0 0 0 0


  • タイマ/カウンタ0比較レジスタ(OCR0)

    TCNT0 の値との比較に使われる 8 ビットレジスタです。

    ビット番号 7 6 5 4 3 2 1 0
    マクロ - - - - - - - -
    初期値 0 0 0 0 0 0 0 0


  • タイマ/カウンタ割り込みマスクレジスタ(TIMSK)(タイマ/カウンタ間共通)
    ※タイマ/カウンタ0に関係有るのは OCIE0 / TOIE0 です。

    ビット番号 7 6 5 4 3 2 1 0
    マクロ OCIE2 TOIE2 TICIE1 OCIE1A OCIE1B TOIE1 OCIE0 TOIE0
    初期値 0 0 0 0 0 0 0 0

    各ビットの説明を以下に書きます。

    ビット名 内容
    OCIE0(比較一致フラグ割り込み許可) 1 を設定すると、比較一致割り込みが有効になります。
    TOIE0(オーバーフロー割り込み許可) 1 を設定すると、オーバーフロー割り込みが有効になります。


  • タイマ/カウンタ割り込み要求フラグレジスタ(TIFR)(タイマ/カウンタ間共通)
    ※タイマ/カウンタ0に関係有るのは OCF0 / TOV0 です。

    ビット番号 7 6 5 4 3 2 1 0
    マクロ OCF2 TOV2 ICF1 OCF1A OCF1B TOV1 OCF0 TOV0
    初期値 0 0 0 0 0 0 0 0

    各ビットの説明を以下に書きます。

    ビット名 内容
    OCF0(比較一致フラグ) TCNT0 と OCR0 の値が一致した場合に 1 が設定されます。割り込み処理時に自動的に 0 となる他、1 を書き込むことで 0 となります。
    TOV0(オーバーフローフラグ) TCNT0 でのオーバーフロー発生時に 1 が設定されます。割り込み処理時に自動的に 0 となる他、1 を書き込むことで 0 となります。

○ 回路図

LECTURE 1 ATmega8535 および使用環境について に掲載してある全体回路図に対して、点滅させる LED 部分を赤枠で囲んだ図をこちらに置いておきます。…とは言っても、LECTURE 2 LED を点滅させる と全く同じなのでそちらの図へのリンクです。

○ サンプルプログラムと説明

タイマ/カウンタ0を使って LED を点滅させるサンプルプログラムを 07timer0.tar.gz からダウンロードできます。はじめに のページから、全てのサンプルプログラムを 固めた tar ファイルをダウンロードすることもできます。

中には

  • Makefile
  • util.h
  • util.c
  • timer0.h
  • timer0.c
  • main-ov.c
  • main-comp.c

が格納されています。util.h/util.c については LECTURE 4 A/D 変換でボリューム(可変抵抗)値を読む の内容と同じで Makefile もほとんど変わらないので、それ以外について以下に載せます。なお、以下に掲載しているソースコードには修正 BSD ライセンス条項を含めていませんが、ソースコード配布は修正 BSD ライセンスに従います(はじめに のページの免責事項を参照してください)。

これまでにヒューズビットの設定を行ったことが一度も無い場合は、まず最初に make fuse を実行してください。fuse ターゲットについては、LECTURE 2 LED を点滅させる のサンプルプログラムの項を参照してください。

make 実行後、オーバーフロー割り込みを使用したプログラムをアップロードする場合は make uisp-ov を実行してください。また、比較一致割り込みを使用したプログラムをアップロードする場合は make uisp-comp を実行してください。実装方法が異なるだけで動作内容は同じです。

LED が約 500ms 毎に(1 秒間に 2 回程)ON/OFF を繰り返します。

こちらは LED 点滅の動画です(10秒。YouTube に接続します)。

  • timer0.h
        1    #ifndef ___TIMER0_H_INCLUDED_
        2    #define ___TIMER0_H_INCLUDED_
        3    
        4    #include <avr/io.h>
        5    #include <inttypes.h>
        6    
        7    
        8    /* タイマークロック選択用マクロ */
        9    #define TIMER0_STOP     0x00
       10    #define TIMER0_CK       (_BV(CS00))
       11    #define TIMER0_CK_8     (_BV(CS01))
       12    #define TIMER0_CK_64    (_BV(CS01) | _BV(CS00))
       13    #define TIMER0_CK_256   (_BV(CS02))
       14    #define TIMER0_CK_1024  (_BV(CS02) | _BV(CS00))
       15    
       16    /* 比較出力モード選択用マクロ */
       17    #define COM0_NONE       0x00
       18    #define COM0_TOGGLE     (_BV(COM00))
       19    #define COM0_HIGH_FIRST (_BV(COM01))
       20    #define COM0_LOW_FIRST  (_BV(COM01) | _BV(COM00))
       21    
       22    /* 波形生成種別選択用マクロ */
       23    #define WGM0_NORMAL     0x00
       24    #define WGM0_PWM        (_BV(WGM00))
       25    #define WGM0_CTC        (_BV(WGM01))
       26    #define WGM0_FAST_PWM   (_BV(WGM01) | _BV(WGM00))
       27    
       28    #define timer0_ov_enable()       { TIMSK |= _BV(TOIE0); }
       29    #define timer0_ov_disable()      { TIMSK &= ~_BV(TOIE0); }
       30    #define timer0_comp_enable()     { TIMSK |= _BV(OCIE0); }
       31    #define timer0_comp_disable()    { TIMSK &= ~_BV(OCIE0); }
       32    #define timer0_clear_com_mode()  { TCCR0 &= ~(_BV(COM01) | _BV(COM00)); }
       33    #define timer0_clear_wave_mode() { TCCR0 &= ~(_BV(WGM01) | _BV(WGM00)); }
       34
       35    #define timer0_stop()\
       36            { TCCR0 &= ~(_BV(CS02) | _BV(CS01) | _BV(CS00)); }
       37    
       38    #define timer0_set_com_mode(mode)  {\
       39            timer0_clear_com_mode();\
       40            TCCR0 |= (mode) & (_BV(COM01) | _BV(COM00));\
       41    }
       42    
       43    #define timer0_set_wave_mode(mode) {\
       44            timer0_clear_wave_mode();\
       45            TCCR0 |= (mode) & (_BV(WGM01) | _BV(WGM00));\
       46    }
       47    
       48    #define timer0_start(clock) {\
       49            timer0_stop();\
       50            TCCR0 |= (clock) & (_BV(CS02) | _BV(CS01) | _BV(CS00));\
       51    }
       52    
       53    /*
       54     * TCNT0 に設定するカウンタ値を指定する。
       55     */
       56    static inline void
       57    timer0_set_count(uint8_t count)
       58    {
       59            TCNT0 = count;
       60    }
       61    
       62    
       63    /*
       64     * OCR0 に設定するカウンタ値を指定する。
       65     */
       66    static inline void
       67    timer0_set_ocr0(uint8_t ocr0)
       68    {
       69            OCR0 = ocr0;
       70    }
       71    
       72    
       73    void timer0_ov_set_interrupt(void (*func)(void));
       74    void timer0_comp_set_interrupt(void (*func)(void));
       75    
       76    
       77    #endif
       78    
       79    /* End of timer0.h */
    
    
    • 8 〜 14 行目

      タイマ/カウンタ0 用のクロック指定を楽にできる様にマクロを定義しています。TCCR0 レジスタの CS02-00 ビットに対応するビットを _BV() マクロで設定しています。本マクロは 後述の timer0_start() マクロ実行時に指定します。

    • 16 〜 20 行目

      比較出力モードを楽に指定できる様にマクロを定義しています。TCCR0 レジスタの COM01-00 ビットに対応するビットを _BV() マクロで設定しています。本マクロは後述の timer0_set_com_mode() マクロ実行時に指定します。

    • 22 〜 26 行目

      波形生成種別を楽に指定できる様にマクロを定義しています。TCCR0 レジスタの WGM01-00 ビットに対応するビットを _BV() マクロで設定しています。本マクロは後述の timer0_set_wave_mode() マクロ実行時に指定します。

    • 28 〜 51 行目

      記述が楽になる様に、あるいは分りやすくなる様にいくつかのマクロを定義しています。

      • timer0_ov_enable()/timer0_ov_disable() マクロは、TIMSK レジスタの TOIE0 ビットを操作することでオーバーフロー割り込みの有効化/無効化を行います。

      • timer0_comp_enable()/timer0_comp_disable() マクロは、TIMSK レジスタの OCIE0 ビットを操作することで比較一致割り込みの有効化/無効化を行います。

      • timer0_clear_com_mode() マクロは、TCCR0 レジスタの COM01-00 ビットをクリアします。

      • timer0_clear_wave_mode() マクロは、TCCR0 レジスタの WGM01-00 ビットをクリアします。

      • timer0_stop() マクロは、TCCR0 レジスタの CS02-00 ビットをクリア(クロック無し) することでタイマ/カウンタ0 を停止します。

      • timer0_set_com_mode() マクロは、timer0_clear_com_mode() で TCCR0 レジスタの COM01-00 ビットをクリアした後に、指定されたクロック設定を COM01-00 ビットに設定します。

      • timer0_set_wave_mode() マクロは、timer0_clear_wave_mode() で TCCR0 レジスタの WGM01-00 ビットをクリアした後に、指定された波形生成種別を WGM01-00 ビットに設定します。

      • timer0_start() マクロは、timer0_stop() でタイマ/カウンタ0 を停止した後に、 指定されたクロック設定を TCCR0 レジスタの CS02-00 ビットに設定することでタイマ/カウンタ0 を開始します。

    • 53 〜 70 行目

      こちらは型チェックを有効にしたかったので、マクロではなく inline 関数としました。

      timer0_set_count() 関数は、指定された 8 ビット値を TCNT0 に書き込みます。 TCNT0 の初期値を設定したり、割り込み等のタイミングでカウンタ値を変えたい場合に使います。 また、timer0_set_ocr0() 関数は、指定された 8 ビット値を OCR0 に書き込みます。 この値は比較一致に使用されます。

  • timer0.c
        1    #include <avr/interrupt.h>
        2    #include "timer0.h"
        3    
        4    
        5    static void (*timer0_ov_interrupt_handler)(void);
        6    static void (*timer0_comp_interrupt_handler)(void);
        7    
        8    
        9    /*
       10     * オーバーフロー割り込みハンドラ
       11     */
       12    ISR(TIMER0_OVF_vect)
       13    {
       14            timer0_ov_interrupt_handler();
       15    }
       16    
       17    
       18    /*
       19     * オーバーフロー時に割り込みハンドラ内で実行するユーザ関数を設定する。
       20     */
       21    void 
       22    timer0_ov_set_interrupt(void (*func)(void))
       23    {
       24            timer0_ov_interrupt_handler = func;
       25    }
       26    
       27    
       28    /*
       29     * 比較一致割り込みハンドラ
       30     */
       31    ISR(TIMER0_COMP_vect)
       32    {
       33            timer0_comp_interrupt_handler();
       34    }
       35    
       36    
       37    /*
       38     * 比較一致時に割り込みハンドラ内で実行するユーザ関数を設定する。
       39     */
       40    void
       41    timer0_comp_set_interrupt(void (*func)(void))
       42    {
       43            timer0_comp_interrupt_handler = func;
       44    }
       45    
       46    /* End of timer0.c */
    
    
    • 9 〜 15 行目

      オーバーフロー割り込みを有効にした場合に使用する、割り込みハンドラを記述しています。 ATmega8535 のタイマ/カウンタ0 オーバーフロー割り込みを表すマクロは TIMER0_OVF_vect です。 実際には、後述する timer0_ov_set_interrupt() で登録されたユーザ関数を実行する様に実装しています。

    • 18 〜 25 行目 (timer0_ov_set_interrupt())

      前述のオーバーフロー割り込みハンドラ内で実際に実行する、ユーザ関数を登録します。

    • 28 〜 34 行目

      比較一致割り込みを有効にした場合に使用する、割り込みハンドラを記述しています。 ATmega8535 のタイマ/カウンタ0 比較一致割り込みを表すマクロは TIMER0_COMP_vect です。 実際には、後述する timer0_comp_set_interrupt() で登録されたユーザ関数を実行する様に実装しています。

    • 37 〜 44 行目 (timer0_comp_set_interrupt())

      前述の比較一致割り込みハンドラ内で実際に実行する、ユーザ関数を登録します。

  • main-ov.c(オーバーフロー割り込み使用時の main)
        1    #include <avr/interrupt.h>
        2    #include <avr/io.h>
        3    #include <avr/sleep.h>
        4    
        5    #include "timer0.h"
        6    #include "util.h"
        7    
        8    
        9    static void timer0_ov_interrupt(void);
       10    
       11    
       12    int
       13    main(void)
       14    {
       15            /* PB0 ピンのみ出力設定 */
       16            DDRB |= _BV(DDB0);
       17            pullup();
       18    
       19            timer0_ov_enable();
       20            timer0_ov_set_interrupt(timer0_ov_interrupt);
       21            timer0_set_count(0);
       22    
       23            sei();
       24    
       25            /* (8MHz/256)/256 = 約122Hz */
       26            timer0_start(TIMER0_CK_256);
       27        
       28            while (1){
       29                    sleep_mode();
       30            }
       31    
       32            return 0; /* 警告を避けるため */
       33    }
       34    
       35    
       36    static void 
       37    timer0_ov_interrupt(void)
       38    {
       39            static int count = 0;
       40    
       41            /*
       42             * LED の状態変更周波数を約 2Hz (500ms) とする。
       43             * 割り込み周期が約 122Hz なので 61回毎に状態を変える。
       44             *
       45             * ON-----OFF-----ON-----...
       46             *   500ms   500ms  
       47             */
       48            if (++ count != 61) {
       49                    return;
       50            }
       51    
       52            count = 0;
       53            PORTB ^= _BV(PB0);
       54    }
       55    
       56    /* End of main-ov.c */
    
    

    本ファイルは、オーバーフロー割り込みを使う場合の main() を記述しています。

    • 12 〜 33 行目 (main())

      • pullup() 前に PORTB0 のみ出力方向に設定
      • pullup() (pullup.h、PORTB0 以外の全入力ポートの内部プルアップを有効化)
      • timer0_ov_enable() (timer0.h、オーバーフロー割り込み有効化)
      • timer0_ov_set_interrupt() (timer0.h、オーバーフロー割り込み時に実行するユーザ関数の登録)
      • timer0_set_count() (timer0.h、TCNT0 の初期値を設定)

      を行った後、sei() (avr/interrupt.h) を実行してシステムの割り込みを有効化しています。

      その後に timer0_start() を実行してタイマ/カウンタ0 の動作を開始します。 timer0_start() に TIMER0_CK_256 を指定しているため、オーバーフロー割り込みの周期は約 122Hz ((8MHz/256)/256)となります。

      オーバーフロー割り込みタイミング

      以後 sleep_mode() にてアイドル動作へ移行し、LED の点滅処理はオーバーフロー割り込みハンドラに任せます。

    • 36 〜 54 行目 (timer0_ov_interrupt())

      オーバーフロー割り込み時に実行されるユーザ関数です。

      static 変数である count で割り込み回数を数え、61 回に一度 PORTB0 の値を反転します。 割り込み周期が約 122Hz であるため、反転周期は約 122/61 = 2 Hz となります。

  • main-comp.c(比較一致割り込み使用時の main)
        1    #include <avr/interrupt.h>
        2    #include <avr/io.h>
        3    #include <avr/sleep.h>
        4    
        5    #include "timer0.h"
        6    #include "util.h"
        7    
        8    
        9    static void timer0_comp_interrupt(void);
       10    
       11    
       12    int
       13    main(void)
       14    {
       15            /* PB0 ピンのみ出力設定 */
       16            DDRB |= _BV(DDB0);
       17            pullup();
       18    
       19            timer0_comp_enable();
       20            timer0_comp_set_interrupt(timer0_comp_interrupt);
       21            timer0_set_wave_mode(WGM0_CTC);
       22            timer0_set_count(0);
       23            timer0_set_ocr0(127);
       24    
       25            sei();
       26    
       27            /* (8MHz/256)/128 = 約244Hz */
       28            timer0_start(TIMER0_CK_256);
       29        
       30            while (1){
       31                    sleep_mode();
       32            }
       33    
       34            return 0; /* 警告を避けるため */
       35    }
       36    
       37    
       38    static void 
       39    timer0_comp_interrupt(void)
       40    {    
       41            static int count = 0;
       42    
       43            /*
       44             * LED の状態変更周波数を約 2Hz (500ms) とする。
       45             * 割り込み周期が約 244Hz なので 122回毎に状態を変える。
       46             *
       47             * ON-----OFF-----ON-----...
       48             *   500ms   500ms  
       49             */
       50            if (++ count != 122) {
       51                    return;
       52            }
       53    
       54            count = 0;
       55            PORTB ^= _BV(PB0);
       56    }
       57    
       58    /* End of main-comp.c */
    
    

    本ファイルは、比較一致割り込みを使う場合の main() を記述しています。

    • 12 〜 35 行目 (main())

      • pullup() 前に PORTB0 のみ出力方向に設定
      • pullup() (pullup.h、PORTB0 以外の全入力ポートの内部プルアップを有効化)
      • timer0_comp_enable() (timer0.h、比較一致割り込み有効化)
      • timer0_comp_set_interrupt() (timer0.h、比較一致割り込み時に実行するユーザ関数の登録)
      • timer0_set_wave_mode() (timer0.h、波形生成種別を設定)
      • timer0_set_count() (timer0.h、TCNT0 の初期値を設定)
      • timer0_set_ocr0() (timer0.h、OCR0 の値を設定)

      を行った後、sei() (avr/interrupt.h) を実行してシステムの割り込みを有効化しています。

      この初期化フェーズで、timer0_set_wave_mode(WGM0_CTC) によって波形生成種別を "比較一致タイマ/カウンタクリア(CTC)"に設定しています。これにより TCNT0 と OCR0 が一致した時点で TCNT0 をクリアし、0 からカウントを再開する動作を選択しています。

      その後に timer0_start() を実行してタイマ/カウンタ0 の動作を開始します。 timer0_start() に TIMER0_CK_256 を指定しており、かつ OCR0 の値によりカウンタ最大値は 127 となるため、比較一致割り込みの周期は約 244Hz ((8MHz/256)/128)となります。

      比較一致割り込みタイミング

      以後 sleep_mode() にてアイドル動作へ移行し、LED の点滅処理は比較一致割り込みハンドラに任せます。

    • 38 〜 56 行目 (timer0_comp_interrupt())

      比較一致割り込み時に実行されるユーザ関数です。

      static 変数である count で割り込み回数を数え、122 回に一度 PORTB0 の値を反転します。 割り込み周期が約 244Hz であるため、反転周期は約 244/122 = 2 Hz となります。

以上で解説は終わりです。次回はタイマ/カウンタ1を使って同じことをやってみたいと思います。