Прерывания на всех пинах Arduino

При обработке нажатий кнопок лучше всего использовать прерывания. Если сделать опрос пинов, к которым подключены кнопки, через digitaRead() в программе, есть риск того, что контроллер будет занят не пойми чем и пропустит нажатие.

Все знают, что у Arduino на пинах 2 и 3 есть внешние прерывания INT0 и INT1, которые включаются функцией attachInterrupt(interrupt, function, mode).
У них можно настраивать режим срабатывания - по логическому уровню, по смене состояния, по нарастающему фронту, по спадающему фронту. Это все очень круто, но пинов с внешним прирыванием только два.

Кроме INT0 и INT1 на каждом (почти) пине Atmega328 есть прерывания, которые называются PCINT (Pin Change Interrupts). Эти прерывания герерируются, когда изменяется состояния пина. Все такие прерывания делятся на три группы, поскольку их много, а регистры для их управления восьмибитные. Прерывание PCI0 генерируется, когда изменяется состояние любого пина из PCINT0...PCINT7, при условии  что прерывание на этом пине разрешено. Аналогично PCI1 генерируется при смене состояния пинов PCINT8...PCINT14, а прерывание PCINT2 генерируется при смене состояния пинов PCINT16...PCINT23. Для того, чтобы разрешить прерывание на пине, требуется записать единицу в соответствующий бит регистра PCMSK0, PCMSK1 или PCMSK2, в зависимости от того, к какой группе относится пин. 

Приведу самодельную таблицу, в которой содержится вся информация, необходимая нам для работы с внешними прерываниями.

Пин Arduino Порт ATmega Пин порта Линия PCINT Группа PCINT
0 PD 0 16 2
1 PD 1 17 2
2 PD 2 18 2
3 PD 3 19 2
4 PD 4 20 2
5 PD 5 21 2
6 PD 6 22 2
7 PD 7 23 2
8 PB 0 0 0
9 PB 1 1 0
10 PB 2 2 0
11 PB 3 3 0
12 PB 4 4 0
13 PB 5 5 0
A0 PC 0 8 1
A1 PC 1 9 1
A2 PC 2 10 1
A3 PC 3 11 1
A4 PC 4 12 1
A5 PC 5 13 1

Итого можно сделать двадцать входов с прерываниями. Сделаем ULTIMATE версию, в которой все доступные нам пины будут работать источниками прерываний.

Сначала переведем все пины в режим вход с подтяжкой (то, что в Arduino называется INPUT_PULLUP). Для этого в регистрах DDRB, DDRC, DDRD должны быть записаны 0, а в регистрах PORTB, PORTC, PORTD должны быть записаны 1 для всех пинов, которые мы используем. Поскольку значения по умолчанию в регистрах DDRx и так 0, ничего с ними делать не будем. А вот в регистры PORTx попишем. Смотрим в таблице какие пины в каких портах надо активировать, и для простоты пишем в двоичном виде значения для регистров. Справа младший бит (нулевой пин порта), слева старший (седьмой пин порта):

1
2
3
PORTB = 0b00111111;
PORTC = 0b00111111;
PORTD = 0b11111111;

Теперь разрешим прерывание на каждой линии, сделав запись в регистры PCMSK0...PCMSK2:

1
2
3
PCMSK2 = 0b11111111;
PCMSK1 = 0b00111111;
PCMSK0 = 0b00111111;

Теперь разрешим уже сами прерывания в общем:

1
PCICR = 0b00000111;

Бит 0 в регистре PCICR отвечает за группу 0, бит 1 за группу 1, бит 2 за группу 2. Разрешили все три группы.

Теперь напишем обработчики прерываний. Трерываний у нас три, и их векторы называются PCINT0_vect, PCINT1_vect и PCINT2_vect. Поскольку одно и то же прерывание вызывается при смене состояния сразу нескольких линий, нужно самостоятельно написать механихм определения, с какой линии пришло прерывание. Мы будем по очереди опрашивать все пины, которые вызывают прерывание, и если на пине 0, делаем то, что должны.
Для опроса состояния пина нужно прочитать регистр PINx и выделить в нем бит, соответствующий нашему пину.
Например, выражение

1
PIND & (1 << PD0)

будет равно единице, только если пин PD0 в высоком состоянии.

Для пинов ардуино с 0 по 7 обработчик будет выглядеть вот так:

1
2
3
4
5
6
7
8
9
10
ISR(PCINT2_vect) {
    if (!(PIND & (1 << PD0))) {/* Arduino pin 0 interrupt*/}
    if (!(PIND & (1 << PD1))) {/* Arduino pin 1 interrupt*/}
    if (!(PIND & (1 << PD2))) {/* Arduino pin 2 interrupt*/}
    if (!(PIND & (1 << PD3))) {/* Arduino pin 3 interrupt*/}
    if (!(PIND & (1 << PD4))) {/* Arduino pin 4 interrupt*/}
    if (!(PIND & (1 << PD5))) {/* Arduino pin 5 interrupt*/}
    if (!(PIND & (1 << PD6))) {/* Arduino pin 6 interrupt*/}
    if (!(PIND & (1 << PD7))) {/* Arduino pin 7 interrupt*/}
} 

в фигурных скобках нужно написать то, что мы хотим делать при срабатытывания прерывания на соответствующем пине. Желательно делать как можно меньше. Лучше всего поменять какую-нибудь переменную и свалить.

Аналогично напишем обработчики для двух других прерываний.

Теперь код целиком:

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
void setup(){
    PORTB = 0b00111111;
    PORTC = 0b00111111;
    PORTD = 0b11111111;
    PCICR = 0b00000111;
    PCMSK2 = 0b11111111;
    PCMSK1 = 0b00111111;
    PCMSK0 = 0b00111111;
}
 
void loop() {} 
 
ISR(PCINT2_vect) {
    if (!(PIND & (1 << PD0))) {/* Arduino pin 0 interrupt*/}
    if (!(PIND & (1 << PD1))) {/* Arduino pin 1 interrupt*/}
    if (!(PIND & (1 << PD2))) {/* Arduino pin 2 interrupt*/}
    if (!(PIND & (1 << PD3))) {/* Arduino pin 3 interrupt*/}
    if (!(PIND & (1 << PD4))) {/* Arduino pin 4 interrupt*/}
    if (!(PIND & (1 << PD5))) {/* Arduino pin 5 interrupt*/}
    if (!(PIND & (1 << PD6))) {/* Arduino pin 6 interrupt*/}
    if (!(PIND & (1 << PD7))) {/* Arduino pin 7 interrupt*/}
}
 
ISR(PCINT1_vect) {
    if (!(PINC & (1 << PC0))) {/* Arduino pin A0 interrupt*/}
    if (!(PINC & (1 << PC1))) {/* Arduino pin A1 interrupt*/}
    if (!(PINC & (1 << PC2))) {/* Arduino pin A2 interrupt*/}
    if (!(PINC & (1 << PC3))) {/* Arduino pin A3 interrupt*/}
    if (!(PINC & (1 << PC4))) {/* Arduino pin A4 interrupt*/}
    if (!(PINC & (1 << PC5))) {/* Arduino pin A5 interrupt*/}
}
 
ISR(PCINT0_vect) {
    if (!(PINB & (1 << PB0))) {/* Arduino pin 8 interrupt*/}
    if (!(PINB & (1 << PB1))) {/* Arduino pin 9 interrupt*/}
    if (!(PINB & (1 << PB2))) {/* Arduino pin 10 interrupt*/}
    if (!(PINB & (1 << PB3))) {/* Arduino pin 12 interrupt*/}
    if (!(PINB & (1 << PB4))) {/* Arduino pin 11 interrupt*/}
    if (!(PINB & (1 << PB5))) {/* Arduino pin 13 interrupt*/}
}

Всем удачи и безбажного кода!

Метки: программирование, Arduino, hack, код, хардкор, прерывание

Комментарии   

+9 #1 Алексей Козлов 03.11.2016 17:14
удивительно, но вы первый сайт где адекватно описано как правильно использовать PCINT на ардуино, без заморочек с библиотекой)
Цитировать

Добавить комментарий


Защитный код
Обновить