Arduinoで遊ぶページ

Arduinoで遊んだ結果を残すページです。
garretlab
analogWrite()

analogWrite()

概要

analogWrite()は、PWM出力を行う関数です。analogWrite()のリファレンスはこちらを参照してください。PWMの簡単な説明はこちらを参照してください。

ATmega328Pは、PWM出力に利用できるタイマ/カウンタを3個(タイマ/カウンタ0から2の3個)持っています。それぞれのタイマ/カウンタが2個の比較出力器を持っているため、6本のピンでPWM出力を行うことができます。このため、Arduinoでも6本のピンからPWM出力を行うことができます。

3個のタイマ/カウンタのうち2個(タイマ/カウンタ0とタイマ/カウンタ2)は8ビット、1個(タイマ/カウンタ1)は16ビットです。

PWMの出力周波数の設定はinit()で行っています。analogWrite()では、PWM出力の開始とデューティ比の設定を行います。これらの設定はタイマ関連のレジスタを操作することで実現しています。

タイマ/カウンタ0

タイマ/カウンタ0は8ビットのタイマ/カウンタで、TCCR0AとTCCR0B、TCNT0、OCR0A、OCR0B、TIMSK0、TIFR0という7個のレジスタで制御します。

タイマ/カウンタ0は、Arduino Unoのデジタルピンの6番(OC0A)とデジタルピンの5番(OC0B)のPWM出力を制御します。

TCCR0A、TCCR0B

TCCR0A(タイマ/カウンタ0制御レジスタA)とTCCR0B(タイマ/カウンタ0制御レジスタB)は、PWMの動作を制御します。

TCCR0A
ビット 7 6 5 4 3 2 1 0
名称 COM0A1 COM0A0 COM0B1 COM0B0 - - WGM01 WGM00
TCCR0B
ビット 7 6 5 4 3 2 1 0
名称 FOC0A FOC0B - - WGM02 CS02 CS01 CS00

WGM02とWGM01、WGM00は生成する波形を制御するビットです。3つのビットの組み合わせで意味が変わります。

Mode WGM02 WGM01 WGM00 意味
0 0 0 0 通常
1 0 0 1 PWM、Phase Correct
2 0 1 0 CTC
3 0 1 1 Fast PWM
4 1 0 0 予約
5 1 0 1 PWM、Phase Correct
6 1 1 0 予約
7 1 1 1 Fast PWM

Arduino Unoでは、init()の中で、WMG01とWGM00とを1に設定しています。このため、タイマ/カウンタ0は、モード3のFast PWMで動作します。モード3のFast PWMは、カウンタを0から255までインクリメントしていき、0に戻るまでが一周期です。モード7のFast PWMでは、カウンタを0からOCR0Aに設定した値までインクリメントし、0に戻るまでが1周期です。

COM0A1とCOM0A0は、デジタルピンの6番のPWMの制御を行います。Fast PWMモードの場合の動作は以下の通りです。

COM0A1 COM0A0 意味
0 0 標準操作(切断)
0 1 WGM02が0のときは、標準操作(切断)
WGM02が1のときは、比較一致で出力を反転
1 0 タイマとOCR0Aとの比較一致でLOW、タイマが0になるとHIGH。
1 1 タイマとOCR0Aとの比較一致でHIGH、タイマが0になるとLOW。

Arduino Unoでは、COM0A0は0です。analogWrite()で、COM0A1を1にします。

COM0B1とCOM0B0は、デジタルピンの5番のPWMの制御を行います。Fast PWMモードの場合の動作は以下の通りです。

COM0B1 COM0B0 意味
0 0 標準操作(切断)
0 1 予約
1 0 タイマとOCR0Bとの比較一致でLOW、タイマが0になるとHIGH。
1 1 タイマとOCR0Bとの比較一致でHIGH、タイマが0になるとLOW。

Arduino Unoでは、COM0B0は0です。analogWrite()で、COM0B1を1にします。

FOC0AとFOC0Bは、Arduino Unoでは利用していないようです。

CS02とCS01、CS00はクロックを選択します。

CS02 CS01 CS00 意味
0 0 0 クロックなし
0 0 1 分周比1
0 1 0 分周比8
0 1 1 分周比64
1 0 0 分周比256
1 0 1 分周比1024
1 1 0 外部クロック。立下りでオン。
1 1 1 外部クロック。立ち上がりでオン。

Arduino Unoでは、init()でCS01とCS00を1に設定しています。このため、分周比は64です。

Fast PWMの周波数fは、以下の式で求めます。

f = クロック周波数 / (分周比 * 256)

Arduino Unoでは、クロック周波数が16MHz、分周比は64なので、5番ピンと6番ピンに出力するPWMの周波数は、16000000 / (64 * 256) = 976.5625 Hz です。

256で割っているのは、カウンタが0から255までいって、その後0にリセットするので、全体で256クロックかかるからだと思います。

TCNT0

TCNT0は、8ビットのレジスタで、タイマ/カウンタです。この値が、1クロックごとに1インクリメントされます。255になると0に戻ります。

OCR0A、OCR0B

OCR0AとOCR0Bは8ビットの比較レジスタです。Fast PWMでは、TCNT0とOCR0A、OCR0Bの値を比較し、一致すると出力がLOWになります。TCNT0が0になると出力がHIGHになります。

OCR0Aが6番ピン用、OCR0Bが5番ピン用です。analogWrite()で指定したデューティ比を保持します。以下に動作の概念図を示します。

TIMSK0

TIMSK0は、割り込みマスクを制御します。

TIMSK0
ビット 7 6 5 4 3 2 1 0
名称 - - - - - OCIE0B OCIE0A TOIE0

OCIE0B、OCIE0Aはタイマ/カウンタ0の比較割り込みを制御します。analogWrite()では利用していません。

TOIE0は、タイマ/カウンタ0オーバーフロー割り込み許可です。init()でこのフラグを設定しています。analogWrite()ではなく、millis()やmicros()のために利用します。

TIFR0

TIFR0はタイマ/カウンタ0割り込みフラグレジスタです。Arduinoでは特に設定は行っていないようです。

タイマ/カウンタ1

タイマ/カウンタ1は、16ビットのタイマ/カウンタで、TCCR1AとTCCR1B、TCCR1C、TCNT1H・TCNT1L、OCR1AH・OCR1AL、OCR1BH・OCR1BL、ICR1H・ICR1L、TIMSK1、TIFR1というレジスタで制御します。

タイマ/カウンタ1は、Arduino Unoのデジタルピンの9番(OC1A)とデジタルピンの10番(OC1B)のPWM出力を制御します。

TCCR1A、TCCR1B、TCCR1C

TCCR1A(タイマ/カウンタ1制御レジスタA)とTCCR1B(タイマ/カウンタ1制御レジスタB)/TCCR1C(タイマ/カウンタ1制御レジスタC)は、PWMの動作を制御します。

TCCR1A
ビット 7 6 5 4 3 2 1 0
名称 COM1A1 COM1A0 COM1B1 COM1B0 - - WGM11 WGM10
TCCR1B
ビット 7 6 5 4 3 2 1 0
名称 ICNC1 ICES1 - WGM13 WGM12 CS12 CS11 CS10
TCCR1C
ビット 7 6 5 4 3 2 1 0
名称 FOC1A FOC1B - - - - - -

WGM13とWGM12とWGM11、WGM10は生成する波形を制御するビットです。4つのビットの組み合わせで意味が変わります。

Mode WGM13 WGM12 WGM11 WGM10 意味
0 0 0 0 0 通常
1 0 0 0 1 PWM、Phase Correct, 8bit
2 0 0 1 0 PWM、Phase Correct, 9bit
3 0 0 1 1 PWM、Phase Correct, 10bit
4 0 1 0 0 CTC
5 0 1 0 1 Fast PWM, 8bit
6 0 1 1 0 Fast PWM, 9bit
7 0 1 1 1 Fast PWM, 10bit
8 1 0 0 0 PWM, Phase and Frequency Correct
9 1 0 0 1 PWM, Phase and Frequency Correct
10 1 0 1 0 PWM, Phase Correct
11 1 0 1 1 PWM, Phase Correct
12 1 1 0 0 CTC
13 1 1 0 1 予約
14 1 1 1 0 Fast PWM
15 1 1 1 1 Fast PWM

Arduino Unoでは、init()の中で、WM10を1に設定しています。このため、タイマ/カウンタ1は、モード1のPWM、Phase Correct, 8bitで動作します。このモードでは、カウンタを0から255までインクリメントしていき、その後、0までデクリメントしていきます。

COM1A1とCOM1A0は、デジタルピンの9番のPWMの制御を行います。COM1B1とCOM1B0は、デジタルピンの10番のPWMの制御を行います。モード1のPhase Correct PWMモードの場合の動作は以下の通りです。

COM1A1/COM1B1 COM1A0/COM1B0 意味
0 0 標準操作(切断)
0 1 標準操作(切断)
1 0 タイマとOCR1A/OCR1Bとの比較一致で、加算しているときにLOW、減算しているときにHIGH。
1 1 タイマとOCR1A/OCR1Bとの比較一致でHIGH、タイマが0になるとLOW。

Arduino Unoでは、COM1A0/COM1B0は0です。analogWrite()で、COM1A1/COM1B1を1にします。

ICNC1とICES1はArduino Unoでは操作していません。

CS12とCS11、CS10はクロックを選択します。

CS12 CS11 CS10 意味
0 0 0 クロックなし
0 0 1 分周比1
0 1 0 分周比8
0 1 1 分周比64
1 0 0 分周比256
1 0 1 分周比1024
1 1 0 外部クロック。立下りでオン。
1 1 1 外部クロック。立ち上がりでオン。

Arduino Unoでは、init()でCS11とCS10を1に設定しています。このため、分周比は64です。

Phase Correct PWMの周波数fは、以下の式で求めます。

f = クロック周波数 / (分周比 * TOP * 2)

Phase Correct PWM 8bitの場合のTOPは255です。また、Arduino Unoでは、クロック周波数が16MHz、分周比は64なので、9番ピンと10番ピンに出力するPWMの周波数は、16000000 / (64 * 255 * 2) = 490.1961 Hz です。

カウンタを0から255まで加算し、その後、0まで減算していくため、255 * 2で割っていると思います。

FOC1AとFOC1Bは未使用です。

TCNT1H・TCNT1L

TCNT1HとTCNT1Lは、2つ合わせて、16ビットのタイマカウンタです。ただし、Arduino Unoで利用する8ビットのPhase Correct PWMでは0から255までインクリメントしたら、0までデクリメントしていきます。

OCR1AH・OCR1AL、OCR1BH・OCR1BL

OCR1AとOCR1Bは、16ビットの比較レジスタです。Phase Correct PWMでは、TCNT1とOCR1A、OCR1Bの値を比較し、TCNT1が加算中に一致すると出力がLOWになり、減算中に一致すると出力がHIGHになります。

OCR1Aが9番ピン用、OCR1Bが10番ピン用です。analogWrite()で指定したデューティ比を保持します。以下に動作の概念図を示します。

ICR1H・ICR1L、TIMSK1、TIFR1

ICR1H・ICR1L、TIMSK1、TIFR1はanalogWrite()では利用していないようです。

タイマ/カウンタ2

タイマ/カウンタ2は、8ビットのタイマ/カウンタで、TCCR2AとTCCR2B、TCNT2、OCR2A、OCR2B、TIMSK2、TIFR2、ASSR、GTCCRというレジスタで制御します。

タイマ/カウンタ2は、Arduino Unoのデジタルピンの3番(OC2B)とデジタルピンの11番(OC2A)のPWM出力を制御します。

TCCR2A、TCCR2B

TCCR2A(タイマ/カウンタ2制御レジスタA)とTCCR2B(タイマ/カウンタ2制御レジスタB)は、PWMの動作を制御します。

TCCR2A
ビット 7 6 5 4 3 2 1 0
名称 COM2A1 COM2A0 COM2B1 COM2B0 - - WGM21 WGM20
TCCR2B
ビット 7 6 5 4 3 2 1 0
名称 FOC2A FOC2B - - WGM22 CS22 CS21 CS20

WGM22とWGM21、WGM20は生成する波形を制御するビットです。3つのビットの組み合わせで意味が変わります。

Mode WGM22 WGM21 WGM020 意味
0 0 0 0 通常
1 0 0 1 PWM、Phase Correct
2 0 1 0 CTC
3 0 1 1 Fast PWM
4 1 0 0 予約
5 1 0 1 PWM、Phase Correct
6 1 1 0 予約
7 1 1 1 Fast PWM

Arduino Unoでは、init()の中で、WGM20を1に設定しています。このため、タイマ/カウンタ2は、モード1のPhase Correct PWMで動作します。モード1のPhase Correct PWMは、カウンタを0から255までインクリメントしていき、その後、0までデクリメントしていきます。

COM2A1とCOM2A0はデジタルピンの11番のPWMの制御を行ないます。Phase Correct PWMモードの場合の動作は以下の通りです

COM2A1 COM2A0 意味
0 0 標準操作(切断)
0 1 標準操作(切断)
1 0 タイマとOCR2Aとの比較一致で、加算しているときにLOW、減算しているときにHIGH。
1 1 タイマとOCR2Aとの比較一致でHIGH、タイマが0になるとLOW。

Arduino Unoでは、COM2A0は0です。analogWrite()で、COM2A1を1にします。

COM2B1とCOM2B0はデジタルピンの3番のPWMの制御を行います。

COM2B1 COM2B0 意味
0 0 標準操作(切断)
0 1 予約
1 0 タイマとOCR2Bとの比較一致で、加算しているときにLOW、減算しているときにHIGH。
1 1 タイマとOCR2Bとの比較一致でHIGH、タイマが0になるとLOW。

Arduino Unoでは、COM2B0は0です。analogWrite()で、COM2B1を1にします。

FOC2AとFOC2Bは、analogWrite()では利用していないようです。

CS22とCS21、CS20はクロックを選択します。

CS22 CS21 CS20 意味
0 0 0 クロックなし
0 0 1 分周比1
0 1 0 分周比8
0 1 1 分周比32
1 0 0 分周比64
1 0 1 分周比128
1 1 0 分周比256
1 1 1 分周比1024

Arduino Unoでは、init()でCS22を1に設定しています。このため、分周比は64です。

Phase PWMの周波数fは、以下の式で求めます。

f = クロック周波数 / (分周比 * 510)

Arduino Unoでは、クロック周波数が16MHz、分周比は64なので、11番ピンと3番ピンに出力するPWMの周波数は、16000000 / (64 * 512) = 490.1961 Hz です。

TCNT2

TCNT2は、8ビットのレジスタで、タイマ/カウンタです。この値が、1クロックごとにインクリメントされます。255になると0になるまで1クロックごとにデクリメントされます。

OCR2A、OCR2B

OCR0AとOCR0Bは8ビットの比較レジスタです。Phase Correct PWMでは、TCNT2とOCR2A、OCR2Bの値を比較し、TCNT2が加算中に一致すると出力がLOWになり、減算中に一致すると出力がHIGHになります。

OCR2Aが11番ピン用、OCR2Bが3番ピン用です。analogWrite()で指定したデューティ比を保持します。

TIMSK2

TIMSK2は、割り込みマスクを制御します。analogWrite()では特に設定していないようです。tone()で利用します。

TIFR2

TIFR2はタイマ/カウンタ0割り込みフラグレジスタです。analogWrite()では特に設定は行っていないようです。

ソースコード

analogWrite()は、hardware/arduino/avr/ cores/arduino/wiring_analog.c に定義されています。以下に全ソースコードを示します。Arduino Unoの場合に適用される部分だけを抜粋しています。実際には#if によりさまざまなチップに対応しています。

// Right now, PWM output only works on the pins with
// hardware support.  These are defined in the appropriate
// pins_*.c file.  For the rest of the pins, we default
// to digital output.
void analogWrite(uint8_t pin, int val)
{
	// We need to make sure the PWM output is enabled for those pins
	// that support it, as we turn it off when digitally reading or
	// writing with them.  Also, make sure the pin is in output mode
	// for consistenty with Wiring, which doesn't require a pinMode
	// call for the analog output pins.
	pinMode(pin, OUTPUT);
	if (val == 0)
	{
		digitalWrite(pin, LOW);
	}
	else if (val == 255)
	{
		digitalWrite(pin, HIGH);
	}
	else
	{
		switch(digitalPinToTimer(pin))
		{
			case TIMER0A:
				// connect pwm to pin on timer 0, channel A
				sbi(TCCR0A, COM0A1);
				OCR0A = val; // set pwm duty
				break;

			case TIMER0B:
				// connect pwm to pin on timer 0, channel B
				sbi(TCCR0A, COM0B1);
				OCR0B = val; // set pwm duty
				break;

			case TIMER1A:
				// connect pwm to pin on timer 1, channel A
				sbi(TCCR1A, COM1A1);
				OCR1A = val; // set pwm duty
				break;

			case TIMER1B:
				// connect pwm to pin on timer 1, channel B
				sbi(TCCR1A, COM1B1);
				OCR1B = val; // set pwm duty
				break;

			case TIMER2A:
				// connect pwm to pin on timer 2, channel A
				sbi(TCCR2A, COM2A1);
				OCR2A = val; // set pwm duty
				break;

			case TIMER2B:
				// connect pwm to pin on timer 2, channel B
				sbi(TCCR2A, COM2B1);
				OCR2B = val; // set pwm duty
				break;

			case NOT_ON_TIMER:
			default:
				if (val < 128) {
					digitalWrite(pin, LOW);
				} else {
					digitalWrite(pin, HIGH);
				}
		}
	}
}

入力はpinとvalで、それぞれuint8_t型とint型の変数です。出力はありません。

まず、pinMode()を使って、pinを出力モードにします。

	pinMode(pin, OUTPUT);

次に、valが0のとき(何も出力しない)と255(デューティ比100%=常に出力)のときは、digitalWrite()でそれぞれ、LOWとHIGHをpinに出力してこの関数は終了します。

	if (val == 0)
	{
		digitalWrite(pin, LOW);
	}
	else if (val == 255)
	{
		digitalWrite(pin, HIGH);
	}

valが0でも255でもない場合は、switch文を使って処理を分岐します。

	else
	{
		switch(digitalPinToTimer(pin))
		{

digitalPinToTimer()は、デジタルピンを入力すると対応するタイマ名を返す関数です。

TIMER0A(5番ピン)の場合は、sbi()を使って、TCCR0AのCOM0A1ビットを1にします。sbi()は、第1引数で指定したアドレスの内容の第2引数ビットを1に設定するマクロです。その後、OCR0Aにvalを代入し、デューティ比を設定します。以降、各ピンに応じたTCCRnxのビットの設定と、OCRnxへのデューティ比の設定を行います。

			case TIMER0A:
				// connect pwm to pin on timer 0, channel A
				sbi(TCCR0A, COM0A1);
				OCR0A = val; // set pwm duty
				break;

			case TIMER0B:
				// connect pwm to pin on timer 0, channel B
				sbi(TCCR0A, COM0B1);
				OCR0B = val; // set pwm duty
				break;

			case TIMER1A:
				// connect pwm to pin on timer 1, channel A
				sbi(TCCR1A, COM1A1);
				OCR1A = val; // set pwm duty
				break;

			case TIMER1B:
				// connect pwm to pin on timer 1, channel B
				sbi(TCCR1A, COM1B1);
				OCR1B = val; // set pwm duty
				break;

			case TIMER2A:
				// connect pwm to pin on timer 2, channel A
				sbi(TCCR2A, COM2A1);
				OCR2A = val; // set pwm duty
				break;

			case TIMER2B:
				// connect pwm to pin on timer 2, channel B
				sbi(TCCR2A, COM2B1);
				OCR2B = val; // set pwm duty
				break;

PWM出力ができないピンを指定した場合は、valが128未満の場合はLOWを、128以上の場合はHIGHを出力します。

			case NOT_ON_TIMER:
			default:
				if (val < 128) {
					digitalWrite(pin, LOW);
				} else {
					digitalWrite(pin, HIGH);
				}
		}
	}
}

バージョン

Arduino 1.8.3



メニューを表示するためにJavaScriptを有効にしてください。

Arduinoで遊ぶページ
Copyright © 2016 garretlab all rights reserved.
inserted by FC2 system