ハードウエアによるPWM


 これまで見てきたとおり、PWMの信号(パルス)を、IOポートをソフトウエアで制御することで作成することができた。
 でも、これって面倒じゃないか?

 PWMの出力をソフトでやろうとすると、他の処理に手が回らなくもなる。
 こういった処理は専用のハードウエアにやらせたほうが効率的である... ということでAVRマイコンにはタイマ機能の一部として、PWMの生成機能が実装されている。(タイマ機能の全体については、こちら
 AVR マイコンのPWMには、高速PWMモードと位相基準PWMモードがある。ここでは、タイマ0を用いた高速PWMモード(8bitタイマ)について説明す る。(タイマ1、3は16bitタイマ。タイマ4は10bit。これらや、位相基準PWMモードなどのその他のモードについては、別に説明する。)

高速PWMモードは、更に2つのモードに分かれている。どちらのモードを使うかはTCCR0Bの第3ビットで選択する。

  1. モード3
    • OC0AとOC0Bの2チャンネルを出力。いずれも同一のTCNT(TCNT0)を参照
    • PWMの周期はプリスケーラのみで決定
      (システムクロックの1/1、1/8、1/64、1/256、1/1024が設定可能)
    • 比較・一致の値を保管するレジスタ(OCR0AOCR0B)はデューティを設定
       
  2. モード7
    • OC0Bの1チャンネル出力
    • OC0AはTCNTの上限値(PWMの周期)、OC0Bはデューティを設定
    • PWMの周期はプリスケーラとOCR0Aで設定
      (システムクロックの1/1、1/8、1/64、1/256、1/1024とTCNTの上限値が設定可能)

 PWMの設定にはTCNTOCR0AOCR0BTCCR0ATCCR0BTIMSK0(割り込みマスクレジスタ)、TIFR0(割り込みフラグレジスタ)のレジスタが関係する。また、TCNTは8bitの勝手に動作するカウンタで、システムクロック(16MHzもしくは1MHz)をプリスケーラで分周(周波数を何分の一かにすること)しカウントする。
 OCR0AOCR0Bレジスタも8bitのレジスタで、TCNTと値が一致したとき(比較・一致)に、PWM信号が切り替わる。(OCR0ATCNTが一致した時はOC0A、OCR0BTCNTが一致した時はOC0B)

 各レジスタの役割を整理すると以下のとおり。
 

レジスタ名 機    能
TCNT0 システムクロックをプリスケーラで分周した信号で自走するカウンタ
TCCR0A タイマ0の設定を行うレジスタ。
  タイマモード、比較・一致発生時の出力
TCCR0B タイマ0の設定を行うレジスタ。
  タイマモード、プリスケーラの分周比
OCR0A TCNT0との比較・一致を行う際に値を保管するレジスタ。
  OC0A出力時のデューティ可変(モード3)、TCNT0の上限値(モード7)
OCR0B TCNT0との比較・一致を行う際に値を保管するレジスタ。
  OC0B出力時のデューティ可変(モード3、7)
TIMSK0 割り込みマスクレジスタ。比較・一致、TCNT0のオーバフローによる割り込み
ハンドラの呼び出しを許可・禁止する。
TIFR0 割り込みフラグレジスタ。割り込みイベントの発生を記録する。


  具体的な動作について、以下に説明する。

モード3では、

  • TCNTはプリスケーラで分周されたシステムクロック(1MHzか16MHz)をカウントする。(つまりシステムクロックの信号が1/0を繰り返すたびに+1する)TCNTの値は255になると0に戻る。
     
  • OCR0AOCR0Bに設定された値と、TCNTの値を比較し、これが一致したとき(これをコンペア・マッチと呼ぶこともある)にPWM出力を変化させる。(変化のさせ方は、下表、「値の一致での動作」を参照)
    OCR0A、OCR0Bのいずれも、デューティを決める。
     
  • TCNTの値が0に戻るときに、PWM出力を変化させる。
    (a)下表(値の一致での動作)で「値の一致で0」に設定されている場合は、TCNT=255でPWM=1 (OC0AとOC0B)
    (b)下表(値の一致での動作)で「値の一致で1」に設定されている場合は、TCNT=255でPWM=0 (OC0AとOC0B)
    ※上の図の例は、(a)の「値の一致で0」に設定した場合の動作。(b)に設定した場合は、信号が反転する。
     

モード7では、

  • TCNTはプリスケーラで分周されたシステムクロック(1MHzか16MHz)をカウントする。(つまりシステムクロックの信号が1/0を繰り返すたびに+1する)TCNTの値はOCR0Aと同じ値になると0に戻る。
     
  • OCR0AOCR0Bに設定された値と、TCNTの値を比較し、これが一致したときにPWM出力を変化させる。(変化のさせ方は、下表、「値の一致での動作(TCCR0A)」を参照)
    OCR0Aは周期、OCR0Bはデューティを決める。
     
  • TCNTの値が0に戻るときに、PWM出力を変化させる。
    (a)下表(値の一致での動作)で「値の一致で0」に設定されている場合は、TCNT=OCR0AでPWM=1 (OC0Bのみ)
    (b)下表(値の一致での動作)で「値の一致で1」に設定されている場合は、TCNT=OCR0AでPWM=0 (OC0Bのみ)
    ※上の図の例は、(a)の「値の一致で0」に設定した場合の動作。(b)に設定した場合は、信号が反転する。

のように動作する。

 

TCCR0Aの各設定ビットの詳細は、以下のとおり。

 まず、タイマの動作を、高速PWMモードに設定する。このためには、TCCR0ATCCR0BのWGM0x(xは0~3)ビットを下表(タイマモードの設定)の様に設定する。

表:タイマモードの設定(TCCR0A、TCCR0B)

動作モード

レジスタbitの位置
(bitの名前)

TCCR0B:3
(WGM02)

TCCR0A:1
(WGM01)
TCCR0A:0
(WGM00)
TOP
(TCNTが0に戻る時の上限値)
TOV
割り込み
  モード0    (標準) 0 0 0 0xFF 0xFF
  モード1    (位相基準PWM) 0 0 1 0xFF 0
  モード2    (CTC) 0 1 0 OCR0Aの値 0xFF
  モード3  (高速PWM) 0 1 1 0xFF 0xFF
  モード4    (予約) 1 0 0 -  
  モード5    (位相基準PWM) 1 0 1 OCR0Aの値 0
  モード6    (予約) 1 1 0 -  
  モード7  (高速PWM) 1 1 1 OCR0Aの値 0xFF

  ここで、WGM02ビットを0に設定した場合は、TCNTの上限値は255、WGM02を1に設定した場合はTCNTの上限値はOCR0Aと同じ値になる。PWMの周期を様々に変えたい場合は、このモード(モード7)を使うと便利である。 また、WGM02を0とすると、OC0AとOC0Bの2本のPWM信号を出力可能だが、WGM02を1とすると、OC0Bのみとなる。

表:値の一致での動作(TCCR0A)

TCCR0Aのbitの位置
(ビットの名前)
7
(COM0A1)
6
(COM0A0)
5
(COM0B1)
4
(COM0B0)
  PWM出力
(OC0Aに出力)
PWM出力
(OC0Bに出力)
波形出力なし 0 0 0 0
TCNTOCR0AOCR0Bとの値の一致でPWM出力切替 0 1 0 1
TCNTOCR0AOCR0Bとの値の一致でPWM出力0 1 0 1 0
TCNTOCR0AOCR0Bとの値の一致でPWM出力1 1 1 1 1

※モード3の動作

表:プリスケーラの設定(TCCR0B)

TCCR0Bのbitの位置
(ビットの名前)
2
(CS02)
1
(CS01)
0
(CS00)
タイマ停止 0 0 0
1/1 0 0 1
1/8 0 1 0
1/64 0 1 1
1/256 1 0 0
1/1024 1 0 1
外部クロック(T0ピンの立下り) 1 1 0
外部クロック(T0ピンの立上がり) 1 1 1

 プリスケーラは、TCNTのカウント入力の周波数を決める。システムクロック(16MHz、変更可能)を何分の一(分周)かにして、TCNTに供給する。
 例えば、CS02:CS01:CS00を1:0:0と設定したとすると、システムクロックは1/256に分周される事になる。従って、TCNTは、

16000000 / 256 = 62500 Hz

でカウントされることになる。(つまり一秒間に62500回、カウントアップされる)
 この状態で、WGM02:WGM01:WGM00を0:1:1としたとすると、PWMの1周期は、

62500 / 255 = 245.098Hz

となる。
 また、WGM02:WGM01:WGM00を1:1:1として、OCR0Aを100とすると、

62500 / 100 = 625Hz

となる。


サンプルプログラム1
(モード3:OC0AとOC0Bの2チャンネル出力)

/*
 * タイマ0ハードウエアによるPWM出力サンプル
 *   PWMの出力は、モード3の場合
 *        OC0A ~ 基板の29ピン (PB7と共用)
 *        OC0B ~ 基板の4ピン (PD0と共用)
 *
 *   PWMの出力は、モード7の場合
 *    OC0B ~ 基板の4ピン (PD0と共用)
 */

... 略 ....

void hw_init()
{
// PWM
    DDRD |= 0b00000001; //PD0を出力に(OC0B)
    DDRB |= 0b10000000; //PB7を出力に(OC0A)
    TCCR0A |= _BV(WGM01) | _BV(WGM00);      // タイマ・カウンタ・コントロールレジスタ  モード3(高速PWMモード)
    TCCR0A |= _BV(COM0A1);          // TCNT==OCR0Aで、OC0Aが0(GND)、TCNT=255でOC0Aが1(+5V)
                                                   // _BV(a)は、aで指定した桁のbitが1の8bit幅の定数を返す。例えば、
                                                   // a=3のとき、_BV(a) の値は 0b00001000である。

    TCCR0A |= _BV(COM0B1);        // TCNT==OCR0Bで、OC0Bをlow(=0)、TCNT=255でOC0Bをhi(=1)

    TCCR0B |= _BV(CS01);             // 周波数設定(PWMの一周期) (TCNT:8bit)
                                                   // システムクロックが16MHzでTCNTがリセットされる値が255(上限値)であるため、
                                                   // 16000000 / 分周比 / 256がPWMの一周期となる。
                                                   // (分周比が1/8なら2000000Hz→0.5マイクロ秒)
}

int main()
{
    CLKPR = 0x80; CLKPR = 0;    // 16MHz動作のための設定①
    hw_init();

    // 周期は、16000000(Hz) / 8(CS01) / 256(TCNT) = 7812.5 Hz固定

    OCR0A = 200;  // OC0A(29ピン~PB7)にduty = 200/256で出力
    OCR0B = 100;  // OC0B(4ピン~PD0)にduty = 100/256で出力

    while(1) {
    }
}


サンプルプログラム2
(モード7:OC0A=周期、OC0B=デューティ、OC0Bの1チャンネル出力)

 

/*
 * タイマ0ハードウエアによるPWM出力サンプル
 *   PWMの出力は、モード7の場合
 *        OC0B ~ 基板の4ピン (PD0と共用)
 *
 */

... 略 ....

void hw_init()
{
// PWM
    DDRD |= 0b00000001; //PD0を出力に(OC0B)
    DDRB |= 0b10000000; //PB7を出力に(OC0A)
    TCCR0A |= _BV(WGM01) | _BV(WGM00);   // タイマ・カウンタ・コントロールレジスタの設定(TCCR0A、TCCR0B)
    TCCR0B |= (WGM02);               //    高速PWMモードのモード7

    TCCR0A |= _BV(COM0A1);        // TCNT==OCR0Aで、OC0Aが0(GND)、TCNT=OCR0AでOC0Aが1(+5V)
                                                   // _BV(a)は、aで指定した桁のbitが1の8bit幅の定数を返す。例えば、
                                                   // a=3のとき、_BV(a) の値は 0b00001000である。

    TCCR0A |= _BV(COM0B1);        // TCNT==OCR0Bで、OC0Bをlow(=0)、TCNT=255でOC0Bをhi(=1)

    TCCR0B |= _BV(CS01);             // 周波数設定(PWMの一周期) (TCNT:8bit)
                                                   // システムクロックが16MHzでTCNTがリセットされる値が255(上限値)であるため、
                                                   // 16000000 / 分周比 / 256がPWMの一周期となる。
                                                   // (分周比が1/8なら2000000Hz→0.5マイクロ秒)
}


int main()
{
    CLKPR = 0x80; CLKPR = 0;    // 16MHz動作のための設定①
    hw_init();

    OCR0A = 200;  // 周期は、16000000(Hz) / 8(CS01) / 200(OCR0A) = 10000 Hz
    OCR0B = 100;  // OC0B(4ピン~PD0)にduty = 100/256で出力

    while(1) {
    }
}

 


PWMと割り込み

タイマ0の高速PWMでは、TCNTとOCR0A・OCR0Bとの比較・一致が発生した時に、割り込み(イベント)を発生させる事が出来る。
発生可能な割り込みイベントは、以下の3つである。

  1. TCNTとOCR0Aとの比較・一致
     TCNTとOCR0Aの値が一致した時に発生する割り込みイベント。この割り込みイベントが発生すると、割り込みフラグOCF0Aが1にセットされ、更に、割り込みマスクビットOCIE0Aが1であれば、割り込みハンドラ(TIMER0_COMPA_vect)が起動される。
     
  2. TCNTとOCR0Bとの比較・一致
     TCNTとOCR0Bの値が一致した時に発生する割り込みイベント。この割り込みイベントが発生すると、割り込みフラグOCF0Bが1にセットされ、更に、割り込みマスクビットOCIE0Bが1であれば、割り込みハンドラ(TIMER0_COMPB_vect)が起動される。
     
  3. TCNTのオーバフロー
     TCNTが0xFF(255)から0になる時に発生。この割り込みイベントが発生すると、割り込みフラグTOV0が1にセットされ、更に、割り込みマスクビットTOIE0が1であれば、割り込みハンドラ(TIMER0_OVF_vect)が起動される。


割り込み関連レジスタとビットの配置は、以下の通り。

表:割り込みフラグレジスタ(TIFR0)

bitの位置
(ビットの名前)
7
(未使用)
6
(未使用)
5
(未使用)
4
(未使用)
3
(未使用)
2
(OCF0B)
1
(OCF0A)
0
(TOV0)
初期値 0 0 0 0 0 0 0 0

表:割り込みマスクレジスタ (TIMSK0)

bitの位置
(ビットの名前)
7
(未使用)
6
(未使用)
5
(未使用)
4
(未使用)
3
(未使用)
2
(OCIE0B)
1
(OCIE0A)
0
(TOIE0)
初期値 0 0 0 0 0 0 0 0


サンプルプログラム(TCNTオーバフロー時の割り込みを使う例)

/*
 * タイマ0ハードウエアによるPWM出力及び、TCNTオーバフロー時の割り込み発生サンプル
 *   PWMの出力は、モード3の場合
 *        OC0A ~ 基板の29ピン (PB7と共用)
 *        OC0B ~ 基板の4ピン (PD0と共用)
 *
 *   PWMの出力は、モード7の場合
 * OC0B ~ 基板の4ピン (PD0と共用)
 */

 .....

ISR(TIMER0_OVF_vect)   // タイマ0でオーバフローが発生すると呼び出される割り込みハンドラ
{
    static int cnt = 0;

    cnt++;
}


void hw_init()
{
// PWM
    DDRD |= 0b00000001; //PD0を出力に(OC0B)
    DDRB |= 0b10000000; //PB7を出力に(OC0A)
    PORTD = 0b00000001;
    PORTB = 0b10000000;

    TCCR0A = 3;                            // タイマ・カウンタ・コントロールレジスタ  モード3(高速PWMモード)
                                                   // もし、高速PWMモードのモード7を使いたい場合は TCCRB |= _BV(WGM02)が必要
                                                   // モード7の設定を行うと、PWM出力はOC0B (4ピン) の1チャンネルのみとなる。

    TCCR0A |= _BV(COM0A1);        // TCNT==OCR0Aで、OC0Aが0(GND)、TCNT=255でOC0Aが1(+5V)
                                                   // _BV(a)は、aで指定した桁のbitが1の8bit幅の定数を返す。例えば、
                                                   // a=3のとき、_BV(a) の値は 0b00001000である。

    TCCR0A |= _BV(COM0B1);        // TCNT==OCR0Bで、OC0Bをlow(=0)、TCNT=255でOC0Bをhi(=1)

    TCCR0B |= _BV(CS01);             // 周波数設定(PWMの一周期) (TCNT:8bit)
                                                   // システムクロックが16MHzでTCNTがリセットされる値が255(上限値)であるため、
                                                   // 16000000 / 分周比 / 256がPWMの一周期となる。
                                                   // (分周比が1/256なら62500Hz→16マイクロ秒)

   TIMSK0 |= _BV(TOIE0);    // TCNTが最大値を超えた時(0xFFもしくはOCR0Aの値)、割り込み発生
}


int main()
{
    CLKPR = 0x80; CLKPR = 0;    // 16MHz動作のための設定①
    hw_init();

    OCR0A = 200;  // OC0A(29ピン~PB7)にduty = 200/256で出力
    OCR0B = 100;  // OC0B(4ピン~PD0)にduty = 100/256で出力

   sei();   // 全ての割り込みを許可

    while(1) {
    }
}

 

最終更新:2016年03月22日 10:13