В чипе ATmega328, который стоит в Arduino, есть три аппаратных таймера, которые работают независимо друг от друга и от ядра. Они называются Timer/Counter0, Timer/Counter1, Timer/Counter2. Среди них нулевой и второй имеют разрядность 8 бит, а первый - 16 бит. Функция таймеров - считать и сбрасываться. Пользователю это дает возможность генерировать ШИМ-сигнал, генерировать волновой сигнал, выполнять действия с заданным периодом и т.д.

Любой таймер можно настроить так, чтобы его период стал таким, какой нам нужно (в некоторых пределах, конечно). После того, как таймер досчитал от 0 до значения периода, он автоматически сбрасывается и генерирует прерывание по переполнению (TIMx_OVF). Программа автоматически заваливается в обработчик прерывания, который мы должны написать, выполняет его код и возвращается обратно. 

Для упрощения работы с таймером воспользуемся замечательной библиотекой TimerOne от Paul Stoffregen. Скачаем ее, установим  и сразу подключим к нашему скетчу:

1
#include <TimerOne.h>

Озаботимся пином светодиода, которым будем блинкать:

1
2
3
4
5
#define LED_PIN 13
 
void setup(){
	pinMode(LED_PIN,OUTPUT);
}

Теперь внутри сетапа инициализируем таймер и задаем его период:

1
Timer1.initialize(500000);

Активируем прерывание:

1
Timer1.attachInterrupt(toggleLed);

здесь в аргументе указываем, как называется функция обработчика прерывания.

Пишем пустой луп:

1
void loop(){}

Осталось написать сам обработчик прерывания:

1
2
3
void toggleLed(){
	digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}

Его содержимое читает состояния пина, инвертирует и записывает обратно.

Получается вот такой симпатичный скетч:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <TimerOne.h>
 
#define LED_PIN 13
 
void setup(){
	pinMode(LED_PIN,OUTPUT);
	Timer1.initialize(500000);
	Timer1.attachInterrupt(toggleLed);
}
 
void loop(){}
 
void toggleLed(){
	digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}

 Его можно было бы так и оставить, если бы не одно но... Давайте посмотрим на ассемблерный код нашего обработчика прерывания.

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
0000015e <__vector_13>:
 15e:	1f 92       	push	r1
 160:	0f 92       	push	r0
 162:	0f b6       	in	r0, 0x3f	; 63
 164:	0f 92       	push	r0
 166:	11 24       	eor	r1, r1
 168:	2f 93       	push	r18
 16a:	3f 93       	push	r19
 16c:	4f 93       	push	r20
 16e:	5f 93       	push	r21
 170:	6f 93       	push	r22
 172:	7f 93       	push	r23
 174:	8f 93       	push	r24
 176:	9f 93       	push	r25
 178:	af 93       	push	r26
 17a:	bf 93       	push	r27
 17c:	ef 93       	push	r30
 17e:	ff 93       	push	r31
 180:	e0 91 00 01 	lds	r30, 0x0100
 184:	f0 91 01 01 	lds	r31, 0x0101
 188:	09 95       	icall
 18a:	ff 91       	pop	r31
 18c:	ef 91       	pop	r30
 18e:	bf 91       	pop	r27
 190:	af 91       	pop	r26
 192:	9f 91       	pop	r25
 194:	8f 91       	pop	r24
 196:	7f 91       	pop	r23
 198:	6f 91       	pop	r22
 19a:	5f 91       	pop	r21
 19c:	4f 91       	pop	r20
 19e:	3f 91       	pop	r19
 1a0:	2f 91       	pop	r18
 1a2:	0f 90       	pop	r0
 1a4:	0f be       	out	0x3f, r0	; 63
 1a6:	0f 90       	pop	r0
 1a8:	1f 90       	pop	r1
 1aa:	18 95       	reti

Самое главное здесь - это строки 19-21, которые говорят нам, что вызывается подпрограмма по адресу 0x0100, это как раз та функция, которую мы написали:

1
2
3
4
5
6
7
8
9
00000100 <_Z9toggleLedv>:
 100:	8d e0       	ldi	r24, 0x0D	; 13
 102:	0e 94 6e 01 	call	0x2dc	; 0x2dc <digitalRead>
 106:	61 e0       	ldi	r22, 0x01	; 1
 108:	89 2b       	or	r24, r25
 10a:	09 f0       	breq	.+2      	; 0x10e <_Z9toggleLedv+0xe>
 10c:	60 e0       	ldi	r22, 0x00	; 0
 10e:	8d e0       	ldi	r24, 0x0D	; 13
 110:	0c 94 38 01 	jmp	0x270	; 0x270 <digitalWrite>

Здесь в строках 3 и 9 вызываются функции digitalRead и digitalWrite,

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
000002dc <digitalRead>:
 2dc:	cf 93       	push	r28
 2de:	df 93       	push	r29
 2e0:	28 2f       	mov	r18, r24
 2e2:	30 e0       	ldi	r19, 0x00	; 0
 2e4:	f9 01       	movw	r30, r18
 2e6:	e8 59       	subi	r30, 0x98	; 152
 2e8:	ff 4f       	sbci	r31, 0xFF	; 255
 2ea:	84 91       	lpm	r24, Z
 2ec:	f9 01       	movw	r30, r18
 2ee:	e4 58       	subi	r30, 0x84	; 132
 2f0:	ff 4f       	sbci	r31, 0xFF	; 255
 2f2:	d4 91       	lpm	r29, Z
 2f4:	f9 01       	movw	r30, r18
 2f6:	e0 57       	subi	r30, 0x70	; 112
 2f8:	ff 4f       	sbci	r31, 0xFF	; 255
 2fa:	c4 91       	lpm	r28, Z
 2fc:	cc 23       	and	r28, r28
 2fe:	91 f0       	breq	.+36     	; 0x324 <digitalRead+0x48>
 300:	81 11       	cpse	r24, r1
 302:	0e 94 d6 00 	call	0x1ac	; 0x1ac <turnOffPWM>
 306:	ec 2f       	mov	r30, r28
 308:	f0 e0       	ldi	r31, 0x00	; 0
 30a:	ee 0f       	add	r30, r30
 30c:	ff 1f       	adc	r31, r31
 30e:	ec 55       	subi	r30, 0x5C	; 92
 310:	ff 4f       	sbci	r31, 0xFF	; 255
 312:	a5 91       	lpm	r26, Z+
 314:	b4 91       	lpm	r27, Z
 316:	2c 91       	ld	r18, X
 318:	2d 23       	and	r18, r29
 31a:	81 e0       	ldi	r24, 0x01	; 1
 31c:	90 e0       	ldi	r25, 0x00	; 0
 31e:	21 f4       	brne	.+8      	; 0x328 <digitalRead+0x4c>
 320:	80 e0       	ldi	r24, 0x00	; 0
 322:	02 c0       	rjmp	.+4      	; 0x328 <digitalRead+0x4c>
 324:	80 e0       	ldi	r24, 0x00	; 0
 326:	90 e0       	ldi	r25, 0x00	; 0
 328:	df 91       	pop	r29
 32a:	cf 91       	pop	r28
 32c:	08 95       	ret

и

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
00000270 <digitalWrite>:
 270:	0f 93       	push	r16
 272:	1f 93       	push	r17
 274:	cf 93       	push	r28
 276:	df 93       	push	r29
 278:	1f 92       	push	r1
 27a:	cd b7       	in	r28, 0x3d	; 61
 27c:	de b7       	in	r29, 0x3e	; 62
 27e:	28 2f       	mov	r18, r24
 280:	30 e0       	ldi	r19, 0x00	; 0
 282:	f9 01       	movw	r30, r18
 284:	e8 59       	subi	r30, 0x98	; 152
 286:	ff 4f       	sbci	r31, 0xFF	; 255
 288:	84 91       	lpm	r24, Z
 28a:	f9 01       	movw	r30, r18
 28c:	e4 58       	subi	r30, 0x84	; 132
 28e:	ff 4f       	sbci	r31, 0xFF	; 255
 290:	14 91       	lpm	r17, Z
 292:	f9 01       	movw	r30, r18
 294:	e0 57       	subi	r30, 0x70	; 112
 296:	ff 4f       	sbci	r31, 0xFF	; 255
 298:	04 91       	lpm	r16, Z
 29a:	00 23       	and	r16, r16
 29c:	c9 f0       	breq	.+50     	; 0x2d0 <digitalWrite+0x60>
 29e:	88 23       	and	r24, r24
 2a0:	21 f0       	breq	.+8      	; 0x2aa <digitalWrite+0x3a>
 2a2:	69 83       	std	Y+1, r22	; 0x01
 2a4:	0e 94 d6 00 	call	0x1ac	; 0x1ac <turnOffPWM>
 2a8:	69 81       	ldd	r22, Y+1	; 0x01
 2aa:	e0 2f       	mov	r30, r16
 2ac:	f0 e0       	ldi	r31, 0x00	; 0
 2ae:	ee 0f       	add	r30, r30
 2b0:	ff 1f       	adc	r31, r31
 2b2:	e2 55       	subi	r30, 0x52	; 82
 2b4:	ff 4f       	sbci	r31, 0xFF	; 255
 2b6:	a5 91       	lpm	r26, Z+
 2b8:	b4 91       	lpm	r27, Z
 2ba:	9f b7       	in	r25, 0x3f	; 63
 2bc:	f8 94       	cli
 2be:	8c 91       	ld	r24, X
 2c0:	61 11       	cpse	r22, r1
 2c2:	03 c0       	rjmp	.+6      	; 0x2ca <digitalWrite+0x5a>
 2c4:	10 95       	com	r17
 2c6:	81 23       	and	r24, r17
 2c8:	01 c0       	rjmp	.+2      	; 0x2cc <digitalWrite+0x5c>
 2ca:	81 2b       	or	r24, r17
 2cc:	8c 93       	st	X, r24
 2ce:	9f bf       	out	0x3f, r25	; 63
 2d0:	0f 90       	pop	r0
 2d2:	df 91       	pop	r29
 2d4:	cf 91       	pop	r28
 2d6:	1f 91       	pop	r17
 2d8:	0f 91       	pop	r16
 2da:	08 95       	ret

 Эти ребята вместе еще тянут два раза turnOffPWM:

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
000001ac <turnOffPWM>:
 1ac:	83 30       	cpi	r24, 0x03	; 3
 1ae:	81 f0       	breq	.+32     	; 0x1d0 <turnOffPWM+0x24>
 1b0:	28 f4       	brcc	.+10     	; 0x1bc <turnOffPWM+0x10>
 1b2:	81 30       	cpi	r24, 0x01	; 1
 1b4:	99 f0       	breq	.+38     	; 0x1dc <turnOffPWM+0x30>
 1b6:	82 30       	cpi	r24, 0x02	; 2
 1b8:	a1 f0       	breq	.+40     	; 0x1e2 <turnOffPWM+0x36>
 1ba:	08 95       	ret
 1bc:	86 30       	cpi	r24, 0x06	; 6
 1be:	a9 f0       	breq	.+42     	; 0x1ea <turnOffPWM+0x3e>
 1c0:	87 30       	cpi	r24, 0x07	; 7
 1c2:	b9 f0       	breq	.+46     	; 0x1f2 <turnOffPWM+0x46>
 1c4:	84 30       	cpi	r24, 0x04	; 4
 1c6:	d1 f4       	brne	.+52     	; 0x1fc <turnOffPWM+0x50>
 1c8:	80 91 80 00 	lds	r24, 0x0080
 1cc:	8f 7d       	andi	r24, 0xDF	; 223
 1ce:	03 c0       	rjmp	.+6      	; 0x1d6 <turnOffPWM+0x2a>
 1d0:	80 91 80 00 	lds	r24, 0x0080
 1d4:	8f 77       	andi	r24, 0x7F	; 127
 1d6:	80 93 80 00 	sts	0x0080, r24
 1da:	08 95       	ret
 1dc:	84 b5       	in	r24, 0x24	; 36
 1de:	8f 77       	andi	r24, 0x7F	; 127
 1e0:	02 c0       	rjmp	.+4      	; 0x1e6 <turnOffPWM+0x3a>
 1e2:	84 b5       	in	r24, 0x24	; 36
 1e4:	8f 7d       	andi	r24, 0xDF	; 223
 1e6:	84 bd       	out	0x24, r24	; 36
 1e8:	08 95       	ret
 1ea:	80 91 b0 00 	lds	r24, 0x00B0
 1ee:	8f 77       	andi	r24, 0x7F	; 127
 1f0:	03 c0       	rjmp	.+6      	; 0x1f8 <turnOffPWM+0x4c>
 1f2:	80 91 b0 00 	lds	r24, 0x00B0
 1f6:	8f 7d       	andi	r24, 0xDF	; 223
 1f8:	80 93 b0 00 	sts	0x00B0, r24
 1fc:	08 95       	ret

Итого получается две сотни инструкций! Полный трэш XD

Правильный обработчик прерывания - короткий обработчик прерывания. Желательно, чтобы он менял какие-то переменные или выставлял флаги, которые проверяются в основном цикле.

Давайте сделаем так, чтобы обработчик прерывания менял переменную, которая хранит состояние пина. А записывать переменную в пин будет уже основная программа. Напишем обработчик вот таким образом:

1
2
3
void toggleLed(){
	ledState = 1 - ledState;
}

Ясно, что переменная ledState должна быть объявлено глобально. Сделаем это следующим волшебным способом:

1
volatile boolean ledState = LOW;

Слово volatile говорит компилятору загружать эту переменную из RAM, а не из регистра. Такое стоит делать, когда переменная меняет свое значение в обработчике прерывания.

Окей. Теперь осталось в основном цикле записывать переменную состояния в пин. Чтобы не делать это бессмысленно все время, давайте будем проверять, поменялось ли состояние. Для этого заведем переменную, в которой хранится предыдущее состояние:

1
boolean ledStatePrev = LOW;

Теперь в лупе будем проверять изменилось ли состояние пина (в обработчике прерывания). Если сменилось, переключаем пин и запоминаем состояние. Если нет - не делаем ничего:

1
2
3
4
if (ledState != ledStatePrev){
	digitalWrite(LED_PIN, ledState);
	ledStatePrev = ledState;
}

Так получаем крутой скетч:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <TimerOne.h>
 
#define LED_PIN 13
 
volatile boolean ledState = LOW;
boolean ledStatePrev = LOW;
 
void setup(){
	pinMode(LED_PIN,OUTPUT);
	Timer1.initialize(500000);
	Timer1.attachInterrupt(toggleLed);
}
 
void loop(){
	if (ledState != ledStatePrev){
		digitalWrite(LED_PIN, ledState);
		ledStatePrev = ledState;
	}
}
 
void toggleLed(){
	ledState = 1 - ledState;
}