Playing with Arduino
A page to record my playing with Arduino
analogWrite()

Abstract

The analogWrite() outputs PWM(Pulse Width Modulation) wave.

The ATmega328P has three timer/counter for PWM wave output. Each timer/counter has two comparators, six pins can output PWM wave. The Arduino Uno can also output PWM wave from six pins.

The two of three timers(timer/count 0 and timer/counter 2) are 8bit, and the rest is 16 bit.

The output frequency of the PWM wave is set in init(). The analogWrite() sets the duty cycle and begins to output the wave. The analogWrite() is implemented using timer related registers of ATmega328P.

Timer/Counter 0

The timer/counter 0 is an 8bit counter. It is controlled by registers named TCCR0A, TCCR0B, TCNT0, OCR0A, OCR0B, TIMSK0 and TIFR0.

The timer/counter 0 controls the PWM output of digital pins 6(related to OC0A) and 5(related to OC0B).

TCCR0A、TCCR0B

The TCCR0A(timer/counter 0 control register A) and TCCR0B(timer/counter 0 control register B) control the PWM output.

TCCR0A

Bit 7 6 5 4 3 2 1 0
Name COM0A1 COM0A0 COM0B1 COM0B0 - - WGM01 WGM00

TCCR0B

Bit 7 6 5 4 3 2 1 0
Name FOC0A FOC0B - - WGM02 CS02 CS01 CS00

The WGM02, WGM01 and WGM00 controls the wave form. The wave is determined by the combination of the three bits.

Mode WGM02 WGM01 WGM00 意味
0 0 0 0 normal
1 0 0 1 PWM、Phase Correct
2 0 1 0 CTC
3 0 1 1 Fast PWM
4 1 0 0 reserved
5 1 0 1 PWM、Phase Correct
6 1 1 0 reserved
7 1 1 1 Fast PWM

In case of the Arduino Uno, the init() function sets WMG01 and WGM00 to 1. The type of timer/counter 0 is mode 3, the Fast PWM. At the fast PWM of mode 3, the counter is incremented from 0 to 255, then it goes back to 0. At the fast PWM of mode 7 , the counter is incremented from 0 to the value stored in OCR0A, then it becomes 0.

The COM0A1 and COM0A0 control the PWM of digital pin 6.

COM0A1 COM0A0 Meanings
0 0 Normal port operation, OC0A disconnected.
0 1 WGM02 = 0: Normal Port Operation, OC0A Disconnected. WGM02 = 1: Toggle OC0A on Compare Match.
1 0 Clear OC0A on Compare Match, set OC0A at BOTTOM,(non-inverting mode).
1 1 Set OC0A on Compare Match, clear OC0A at BOTTOM,(inverting mode).

The COM0A0 is set to 0, and the analogWrite() sets the COM0A1 1.

The COM0B1 and COM0B0 control the PWM of digital pin5.

COM0B1 COM0B0 Meanings
0 0 Normal port operation, OC0B disconnected. 
0 1 Reserved.
1 0 Clear OC0B on Compare Match, set OC0B at BOTTOM,(non-inverting mode)
1 1 Set OC0B on Compare Match, clear OC0B at BOTTOM,(inverting mode).

The COM0B0 is set to 0, and the analogWrite() sets the COM0B1 to 1.

The Arduino Uno dose not use FOC0A and FOC0B.

The CS02, CS01 and CS00 select clock.

CS02 CS01 CS00 Meaningsg
0 0 0 No clock source (Timer/Counter stopped)
0 0 1 Division ratio 1
0 1 0 Division ratio 8
0 1 1 Division ratio 64
1 0 0 Division ratio 256
1 0 1 Division ratio 1024
1 1 0 External clock source on T0 pin. Clock on falling edge.
1 1 1 External clock source on T0 pin. Clock on rising edge.

In case of the Arduino Uno, the init() sets the CS01 and CS00 to 1. So the division ratio is 64.

The frequency of the Fast PWM is calculated as follows.

$$f = clock frequency / (division ratio * 256)$$

The clock frequency is 16MHz and the division ratio is 64, the frequency of wave output to the digital pin 5 and 6 is 16000000 / (64 * 256) = 976.5625 Hz.

TCNT0

The TCNT0 is an eight bit register and timer/counter. The value is incremented each 1 clock. If it gose to 255 then back to 0.

OCR0A、OCR0B

The OCR0A(for pin 6) and OCR0B(for pin 5)are 8bit compare registers. They hold duty ratio specified by analogWrite(). When using fast PWM, comparing TCNT0 with OCR0A or OCR0B, if they are the same value, the output becomes LOW. If TCNT0 becomes 0, the output becomes HIGH. The figure below depicts how fast PWM works.

TIMSK0

The TIMSK0 contols the interrupt mask.

Bit 7 6 5 4 3 2 1 0
Name - - - - - OCIE0B OCIE0A TOIE0

The OCIE0B and OCIE0A controls the interrupt mask of the timer/counter0. The analogWrite() dose not use them.

The TOIE0 is an overflow interrupt enable flag. The init() function sets the flag. It is not for analogWrite() but for millis() and micros().

TIFR0

The TIFR0 is a interurpt flag register of the timer/counter 0. The Arduino dose not set it.

Timer/Counter1

The timer/counter1 is a 16bit counter. It is controlled by registers named TCCR1A, TCCR1B, TCCR1C, TCNT1H/TCNT1L, OCR1AH/ORC1AL, OCR1BH/OCR1BL, ICR1H/ICR1L, TIMSK1 and TIFR1.

The timer/counter 1 controls the PWM output of digital pins 9(related to OC1A) and 10(related to OC1B).

TCCR1A、TCCR1B、TCCR1C

The TCCR1A(Timer/Counter1 Control Register A) , TCCR1B(Timer/Counter1 Control Register B) and TCCR1C(Timer/Counter1 Control Register C) control the PWM output.

TCCR1A

Bit 7 6 5 4 3 2 1 0
Name COM1A1 COM1A0 COM1B1 COM1B0 - - WGM11 WGM10

TCCR1B

Bit 7 6 5 4 3 2 1 0
Name ICNC1 ICES1 - WGM13 WGM12 CS12 CS11 CS10

TCCR1C

Bit 7 6 5 4 3 2 1 0
Name FOC1A FOC1B - - - - - -

The WGM13, WGM12, WGM11 and WGM10 controls the wave form. The wave is determined by the combination of the four bits.

Mode WGM13 WGM12 WGM11 WGM10 Meanings
0 0 0 0 0 Normal
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 Reserved
14 1 1 1 0 Fast PWM
15 1 1 1 1 Fast PWM

In case of the Arduino Uno, the init() function sets WMG10 to 1. The type of timer/counter 1 is mode 1, the Phase Correct PWM. At the Phase Correct PWM of mode 1, the counter is incremented from 0 to 255, then the timer is decremented from 255 to 0.

The COM1A1 and COM1A0 control the PWM of the digital pin 9. The COM1B1 and COM1B0 control the PWM of the digital pin 10. The meanings of the combination of the registers are shown below.

COM1A1/COM1B1 COM1A0/COM1B0 Meanings
0 0 Normal port operation, OC1A/OC1B disconnected.
0 1 Normal port operation, OC1A/OC1B disconnected.
1 0 Clear OC1A/OC1B on Compare Match when upcounting. Set OC1A/OC1B on Compare Match when downcounting.
1 1 Set OC1A/OC1B on Compare Match when upcounting. Clear OC1A/OC1B on Compare Match when downcounting.

In case of Arduino Uno, COM1A0/COM1B0 are 0, the analogWrite() sets the COM1A1/COM1B1 to 1.

ICNC1 and ICES1 is not touched.

The CS12, CS11 and CS10 select the clock.

CS12 CS11 CS10 Meanings
0 0 0 No clock
0 0 1 Division ratio 1
0 1 0 Division ratio 8
0 1 1 Division ratio 64
1 0 0 Division ratio 256
1 0 1 Division ratio 1024
1 1 0 External clock source on T0 pin. Clock on falling edge.
1 1 1 External clock source on T0 pin. Clock on rising edge.

In case of the Arduino UNO, the init() function sets CS11 and CS10 to 1. So the division rate is 64.

The frequency of the Phase Correct PWM is calculated as follows.

$$ f = clock frequency / (division ratio * TOP * 2)$$

The value of TOP is 255 when using phase correct PWM 8bit. As the clock frequency is 16 MHz and the division ratio is 64, the PWM frequency of the PWM output to digital pin 9 and pin 10 is, 16000000 / (64 * 255 * 2) = 490.1961 Hz.

The FOC1A and FOC1B is not used.

TCNT1H・TCNT1L

The TCNT1H and TCNT1L are 8bit counters and the combination of the two is used as a 16 bit counter. In case of the Arduino Uno, It is used as a 8bit counter because it increments from 0 to 255 then decrement from255 to 0.

OCR1AH・OCR1AL、OCR1BH・OCR1BL

The combination of OCR1A(for pin 9) and OCR1B(for pin 10) is a 16bit compare registers. They hold duty ratio specified by analogWrite(). When using Phase Correct PWM, comparing TCNT1H/TCNT1L with OCR1A or OCR1B, if they are the same value, the output becomes LOW. If TCNT1 becomes 0, the output becomes HIGH. The figure below depicts how Phase Correct PWM works.

ICR1H・ICR1L、TIMSK1、TIFR1

The analogWrite() dose not use ICR1H/ICR1L, TIMSK1, and TIFR1.

Timer/Counter 2

The timer/counter 2 is a 8bit counter. It is controlled by register named TCCR2A, TCCR2B, TCNT2, OCR2A, OCR2B, TIMSK2 , TIFR2 ASSR and GTCCR.

The timer/counter 2 controls the PWM output of digital pins 3(related to OC2B) and 11(related to OC2A).

TCCR2A、TCCR2B

The TCCR2A(timer/counter 2 control register A) and TCCR2B(timer/counter 2 control register B) control the PWM output.

TCCR2A

Bit 7 6 5 4 3 2 1 0
Name COM2A1 COM2A0 COM2B1 COM2B0 - - WGM21 WGM20

TCCR2B

Bit 7 6 5 4 3 2 1 0
Name FOC2A FOC2B - - WGM22 CS22 CS21 CS20

The WGM22, WGM21 and WGM20 controls the wave form. The wave is determined by the combination of the three bits.

Mode WGM22 WGM21 WGM20 Meanings
0 0 0 0 Normal
1 0 0 1 PWM、Phase Correct
2 0 1 0 CTC
3 0 1 1 Fast PWM
4 1 0 0 Reserved
5 1 0 1 PWM、Phase Correct
6 1 1 0 Reserved
7 1 1 1 Fast PWM

In case of the Arduino Uno, the init() function sets WMG20 to 1. The type of timer/counter 2 is mode 1, the Phase Correct PWM. At the Phase Correct PWM of mode 1, the counter is incremented from 0 to 255, then the timer is decremented from 255 to 0.

The COM2A1 and COM2A0 control the PWM of the digital pin 11. The COM2B1 and COM2B0 control the PWM of the digital pin 3. The meanings of the combination of the registers are shown below.

COM2A1 COM2A0 Meanings
0 0 Normal port operation, OC2A disconnected.
0 1 Normal port operation, OC2A disconnected.
1 0 Clear OC2A on Compare Match when up-counting. Set OC2A on Compare Match when down-counting.
1 1 Set OC2A on Compare Match when up-counting. Clear OC2A on Compare Match when down-counting.
COM2B1 COM2B0 Meanings
0 0 Normal port operation, OC2B disconnected.
0 1 Reserved
1 0 Clear OC2B on Compare Match when up-counting. Set OC2B on Compare Match when down-counting.
1 1 Set OC2B on Compare Match when up-counting. Clear OC2B on Compare Match when down-counting.

In case of Arduino Uno, the COM2A0 and COM2B0 are 0. The analogWrite() sets the COM2A1 and COM2B1to 1.

The analogWrite() dose not use FOC2A and FOC2B.

The CS22, C211 and CS20 select the clock.

CS22 CS21 CS20 Meanings
0 0 0 No clock
0 0 1 Division Ratio 1
0 1 0 Division Ratio 8
0 1 1 Division Ratio 32
1 0 0 Division Ratio 64
1 0 1 Division Ratio 128
1 1 0 Division Ratio 256
1 1 1 Division Ratio 1024

In case of the Arduino UNO, the init() function sets CS22 to 1. So the division rate is 64.

The frequency of the Phase Correct PWM is calculated as follows.

$$f = clock frequency / (division ratio \times 255 \times 2)$$

As the clock frequency is 16 MHz and the division ratio is 64, the PWM frequency of the PWM output to digital pin 11 and pin 3 is, 16000000 / (64 * 255 * 2) = 490.1961 Hz.

TCNT2

The TCNT2 is an eight bit register and timer/counter. The value is incremented each 1 clock. If it goes to 255 then it decrements the counter to 0.

OCR2A、OCR2B

The OCR2A(for pin 11) and OCR2B(for pin 3)are 8bit compare registers. They hold duty ratio specified by analogWrite(). When using Phase Correct PWM, comparing TCNT2 with OCR1A or OCR1B, if they are the same value, the output becomes LOW. If TCNT2 becomes 0, the output becomes HIGH.

TIMSK2

The TIMSK2 controls the interrupt mask. The analogWrite() dose not set it but the tone() uses it.

TIFR2

The TIFR2 is an interrupt flag register. The analogWrite() dose not set it.

Source Code

The analogWrite() is defined in hardware/arduino/avr/cores/arduino/wiring_analog.c as below. Only the source code for Arduino UNO is quoted. The original source code supports many tips using #if’s.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 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);
				}
		}
	}
}

The inputs are pin and val. The type of the inputs are uint8_t and int respectively. The function dose not return any value.

First set the pin to OUTPUT using pinMode().

12
	pinMode(pin, OUTPUT);

Next if the val is equal to 0, which means no output, or 255, which means no PWM, set the output to LOW and HIGH using digitalWrite().

13
14
15
16
17
18
19
20
	if (val == 0)
	{
		digitalWrite(pin, LOW);
	}
	else if (val == 255)
	{
		digitalWrite(pin, HIGH);
	}

If the val is neither 0 nor 255, the operation branches according to the result of the digitalPinToTimer() which returns the name of the timer.

21
22
23
24
	else
	{
		switch(digitalPinToTimer(pin))
		{

If the result of the digitalPinToTimer() is TIMER0A, the function set the COM0A1 bit of the TCCR0A register to 1 using sbi(). The sbi() is a macro to set the bit(the second argument) of the address(the first argument) to 1. Then the function substitute OCR0A for val to set the duty ratio. After this the function set the TCCRnx and OCRnx according to the result of the digitalPinToTimer().

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
			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;

If the pin can not output the PWM, it outputs LOW if the val is less than 128 or HIGH.

61
62
63
64
65
66
67
68
69
70
			case NOT_ON_TIMER:
			default:
				if (val < 128) {
					digitalWrite(pin, LOW);
				} else {
					digitalWrite(pin, HIGH);
				}
		}
	}
}

Version

Arduino 1.8.13

Last Update

June 19, 2020

inserted by FC2 system