VE_AVR, функции digitalRead и digitalWrite
Самые первые функции, с которых начинается знакомство с Arduino - это pinMode(), digitalWrite() и digitalRead(). Эти функции хороши тем, что их удобно использовать, обращаясь к выводам микроконтроллера по мнемоническому названию. Например (скетч №1):
#define LED_PIN 13
void setup()
{
pinMode(LED_PIN, OUTPUT);
}
void loop()
{
digitalWrite(LED_PIN, HIGH);
digitalWrite(LED_PIN, LOW);
}
В коде скетча может довольно часто встречаться обращение к этому выводу, и если я захочу адаптировать скетч под другую электрическую схему, в которой светодиод будет подключен к другому выводу микроконтроллера, то мне достаточно будет внести эту правку в код скетча всего в одном месте.
Более того, этот же самый скетч я могу, ничего в нём не меняя, скомпилировать для платы Arduino MEGA или для платы Arduino DUE. Вывод Digital 13 и там, и там будет один и тот же, хотя если посмотреть схему, то выяснится, что во всех трёх случаях (UNO, MEGA, DUE) используются разные порты и разные номера выводов.
Согласитесь, что такая переносимость кода — вещь довольно удобная для прототипирования. Ведь без этих функций мне пришлось бы писать что-нибудь вроде этого (скетч №2):
#define LED_BIT 5
void setup()
{
DDRB |= _BV(LED_BIT); // pinMode(LED_PIN, OUTPUT);
}
void loop()
{
PORTB |= _BV(LED_BIT); // digitalWrite(LED_PIN, HIGH);
PORTB &= ~_BV(LED_BIT); // digitalWrite(LED_PIN, LOW);
}
Однако за это удобство приходится платить быстродействием.
Может показаться, что это достаточно простые функции, поскольку они выполняют довольно простое действие - устанавливают или сбрасывают бит в регистре ввода-вывода. Но данное впечатление ложно — после компиляции скетча код функции digitalWrite() занимает 144 байта (в Arduino Software версии 1.8.2).
Чуть лучше дело обстоит с функцией digitalRead(), но она вызывает функцию turnOffPWM(), код которой занимает 82 байта.
Если посмотреть меандр, генерируемый скетчем №1, на осциллографе, то мы увидим следующую картину:
Метод
Версия Arduino Software
Частота
Скриншот
digitalWrite()
1.8.2
145.35 кГц
digitalWrite()
1.6.8
94.61 кГц
digitalWrite()
2:1.0.5 (Ubuntu 17.04)
105.19 кГц
GPIO::setHigh()/setLow()
1.8.2
2 МГц
Для работы с портами ввода/вывода в библиотеке VEduino (VE_AVR) используется класс GPIO, реализующий следующие методы:
bool isHigh (GPIO &gpio, uint8_t pin)
Возвращает true, если на выводе высокий логический уровень.
bool isLow (GPIO &gpio, uint8_t pin)
Возвращает true, если на выводе низкий логический уровень.
void pullupOff (GPIO &gpio, uint8_t pin)
Отключить подтягивающий резистор на выводе.
void pullupOn (GPIO &gpio, uint8_t pin)
Включить подтягивающий резистор на выводе.
void setHigh (GPIO &gpio, uint8_t pin)
Установить высокий логический уровень на выводе.
void setLow (GPIO &gpio, uint8_t pin)
Установить низкий логический уровень на выводе.
void setModeInput (GPIO &gpio, uint8_t pin)
Перевести пин в режим ввода.
void setModeOutput (GPIO &gpio, uint8_t pin)
Перевести пин в режим вывода.
С использованием класса GPIO скетч №1 будет выглядеть так (скетч №3):
#include <ve_avr.h>
void setup()
{
setModeOutput(DEV_GPIOB, 5);
}
void loop()
{
setHigh(DEV_GPIOB, 5);
setLow(DEV_GPIOB, 5);
}
Либо так (скетч №4):
#include <ve_avr.h>
void setup()
{
DEV_GPIOB.setModeOutput(5);
}
void loop()
{
DEV_GPIOB.setHigh(5);
DEV_GPIOB.setLow(5);
}
Вспомогательные функции, используемые в скетче №3, позволяют определить для пина мнемоническое имя, например:
#include <ve_avr.h>
#define LED_PIN DEV_GPIOB, 5
void setup()
{
setModeOutput(LED_PIN);
}
void loop()
{
setHigh(LED_PIN);
setLow(LED_PIN);
}
Использование в обработчиках прерываний
Компилятор AVR-GCC использует соглашение о вызовах "вызывающий сохраняет регистры", поэтому если добавить в код обработчика прерываний вызов функции, то компилятор добавит в код функции-обработчика команды, сохраняющие и восстанавливающие значения всех использованных регистров микроконтроллера. Это требуется потому, что прерывание может произойти в любом месте программы, и при этом не должна быть нарушена целостность регистров - то есть программа ничего не знает о том, что она была прервана, да и не должна об этом знать.
Но в ядре AVR8 32 регистра, и их сохранение/восстановление оказывается заметным на скорости работы программы. Рассмотрим, как это выглядит на примере скетча, использующего вызов функции digitalWrite() внутри обработчика прерывания TIMER2_OVF_vect (переполнение счётчика таймера №2):
#include <ve_avr.h>
#define COUNTER_MAX 255
bool state = false;
uint8_t counter;
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
DEV_TIMER2.setClockSelect(Prescaler2::Prescaler_64); // Предделитель /64. 16 МГц / 64 / 256 = 976 Гц
DEV_TIMER2.setWaveGenMode(Timer2::Normal);
DEV_TICTRL2.overflowIntEnable(); // Разрешаем прерывание по переполнению т/с 2.
interrupts(); // Разрешаем прерывания
counter = COUNTER_MAX;
}
void loop()
{
// put your main code here, to run repeatedly:
}
/**
* Функция-обработчик прерывания по переполнению таймера-счётчика 2.
*/
ISR(TIMER2_OVF_vect)
{
--counter;
if (counter == 0) {
state = !state;
digitalWrite(LED_BUILTIN, state);
counter = COUNTER_MAX;
}
}
Код данной функции-обработчика после компиляции выглядит следующим образом:
00000174 <__vector_9>:
174: 1f 92 push r1
176: 0f 92 push r0
178: 0f b6 in r0, 0x3f ; 63
17a: 0f 92 push r0
17c: 11 24 eor r1, r1
17e: 2f 93 push r18
180: 8f 93 push r24
182: 9f 93 push r25
184: af 93 push r26
186: bf 93 push r27
188: ef 93 push r30
18a: ff 93 push r31
18c: 80 91 01 01 lds r24, 0x0101 ; 0x800101 <counter>
190: 81 50 subi r24, 0x01 ; 1
192: 80 93 01 01 sts 0x0101, r24 ; 0x800101 <counter>
196: 81 11 cpse r24, r1
198: 50 c0 rjmp .+160 ; 0x23a <__vector_9+0xc6>
19a: 20 91 00 01 lds r18, 0x0100 ; 0x800100 <_edata>
19e: 81 e0 ldi r24, 0x01 ; 1
1a0: 28 27 eor r18, r24
1a2: 20 93 00 01 sts 0x0100, r18 ; 0x800100 <_edata>
1a6: e5 e7 ldi r30, 0x75 ; 117
1a8: f0 e0 ldi r31, 0x00 ; 0
1aa: 94 91 lpm r25, Z
1ac: e1 eb ldi r30, 0xB1 ; 177
1ae: f0 e0 ldi r31, 0x00 ; 0
1b0: 84 91 lpm r24, Z
1b2: ed e9 ldi r30, 0x9D ; 157
1b4: f0 e0 ldi r31, 0x00 ; 0
1b6: e4 91 lpm r30, Z
1b8: ee 23 and r30, r30
1ba: 09 f4 brne .+2 ; 0x1be <__vector_9+0x4a>
1bc: 3b c0 rjmp .+118 ; 0x234 <__vector_9+0xc0>
1be: 99 23 and r25, r25
1c0: 39 f1 breq .+78 ; 0x210 <__vector_9+0x9c>
1c2: 93 30 cpi r25, 0x03 ; 3
1c4: 91 f0 breq .+36 ; 0x1ea <__vector_9+0x76>
1c6: 38 f4 brcc .+14 ; 0x1d6 <__vector_9+0x62>
1c8: 91 30 cpi r25, 0x01 ; 1
1ca: a9 f0 breq .+42 ; 0x1f6 <__vector_9+0x82>
1cc: 92 30 cpi r25, 0x02 ; 2
1ce: 01 f5 brne .+64 ; 0x210 <__vector_9+0x9c>
1d0: 94 b5 in r25, 0x24 ; 36
1d2: 9f 7d andi r25, 0xDF ; 223
1d4: 12 c0 rjmp .+36 ; 0x1fa <__vector_9+0x86>
1d6: 97 30 cpi r25, 0x07 ; 7
1d8: 91 f0 breq .+36 ; 0x1fe <__vector_9+0x8a>
1da: 98 30 cpi r25, 0x08 ; 8
1dc: a1 f0 breq .+40 ; 0x206 <__vector_9+0x92>
1de: 94 30 cpi r25, 0x04 ; 4
1e0: b9 f4 brne .+46 ; 0x210 <__vector_9+0x9c>
1e2: 90 91 80 00 lds r25, 0x0080 ; 0x800080 <__TEXT_REGION_LENGTH__+0x7e0080>
1e6: 9f 7d andi r25, 0xDF ; 223
1e8: 03 c0 rjmp .+6 ; 0x1f0 <__vector_9+0x7c>
1ea: 90 91 80 00 lds r25, 0x0080 ; 0x800080 <__TEXT_REGION_LENGTH__+0x7e0080>
1ee: 9f 77 andi r25, 0x7F ; 127
1f0: 90 93 80 00 sts 0x0080, r25 ; 0x800080 <__TEXT_REGION_LENGTH__+0x7e0080>
1f4: 0d c0 rjmp .+26 ; 0x210 <__vector_9+0x9c>
1f6: 94 b5 in r25, 0x24 ; 36
1f8: 9f 77 andi r25, 0x7F ; 127
1fa: 94 bd out 0x24, r25 ; 36
1fc: 09 c0 rjmp .+18 ; 0x210 <__vector_9+0x9c>
1fe: 90 91 b0 00 lds r25, 0x00B0 ; 0x8000b0 <__TEXT_REGION_LENGTH__+0x7e00b0>
202: 9f 77 andi r25, 0x7F ; 127
204: 03 c0 rjmp .+6 ; 0x20c <__vector_9+0x98>
206: 90 91 b0 00 lds r25, 0x00B0 ; 0x8000b0 <__TEXT_REGION_LENGTH__+0x7e00b0>
20a: 9f 7d andi r25, 0xDF ; 223
20c: 90 93 b0 00 sts 0x00B0, r25 ; 0x8000b0 <__TEXT_REGION_LENGTH__+0x7e00b0>
210: f0 e0 ldi r31, 0x00 ; 0
212: ee 0f add r30, r30
214: ff 1f adc r31, r31
216: e4 58 subi r30, 0x84 ; 132
218: ff 4f sbci r31, 0xFF ; 255
21a: a5 91 lpm r26, Z+
21c: b4 91 lpm r27, Z
21e: 9f b7 in r25, 0x3f ; 63
220: f8 94 cli
222: ec 91 ld r30, X
224: 21 11 cpse r18, r1
226: 03 c0 rjmp .+6 ; 0x22e <__vector_9+0xba>
228: 80 95 com r24
22a: 8e 23 and r24, r30
22c: 01 c0 rjmp .+2 ; 0x230 <__vector_9+0xbc>
22e: 8e 2b or r24, r30
230: 8c 93 st X, r24
232: 9f bf out 0x3f, r25 ; 63
234: 8f ef ldi r24, 0xFF ; 255
236: 80 93 01 01 sts 0x0101, r24 ; 0x800101 <counter>
23a: ff 91 pop r31
23c: ef 91 pop r30
23e: bf 91 pop r27
240: af 91 pop r26
242: 9f 91 pop r25
244: 8f 91 pop r24
246: 2f 91 pop r18
248: 0f 90 pop r0
24a: 0f be out 0x3f, r0 ; 63
24c: 0f 90 pop r0
24e: 1f 90 pop r1
250: 18 95 reti
В начале и в конце тела функции можно увидеть последовательности команд push и pop, сохраняющих и восстанавливающих регистры на стеке. Сама функция-обработчик занимает 222 байта.
Сравним полученный код с примером, использующим класс GPIO библиотеки VE_AVR.
/**
* Функция-обработчик прерывания по переполнению таймера-счётчика 2.
*/
ISR(TIMER2_OVF_vect)
{
--counter;
if (counter == 0) {
state = !state;
if (state)
DEV_GPIOB.setHigh(5);
else
DEV_GPIOB.setLow(5);
counter = COUNTER_MAX;
}
}
Для этой функции-обработчика компилятор создаст следующий код:
00000160 <__vector_9>:
160: 1f 92 push r1
162: 0f 92 push r0
164: 0f b6 in r0, 0x3f ; 63
166: 0f 92 push r0
168: 11 24 eor r1, r1
16a: 8f 93 push r24
16c: 9f 93 push r25
16e: 80 91 01 01 lds r24, 0x0101 ; 0x800101 <counter>
172: 81 50 subi r24, 0x01 ; 1
174: 80 93 01 01 sts 0x0101, r24 ; 0x800101 <counter>
178: 81 11 cpse r24, r1
17a: 0e c0 rjmp .+28 ; 0x198 <__vector_9+0x38>
17c: 90 91 00 01 lds r25, 0x0100 ; 0x800100 <_edata>
180: 81 e0 ldi r24, 0x01 ; 1
182: 89 27 eor r24, r25
184: 80 93 00 01 sts 0x0100, r24 ; 0x800100 <_edata>
188: 88 23 and r24, r24
18a: 11 f0 breq .+4 ; 0x190 <__vector_9+0x30>
18c: 2d 9a sbi 0x05, 5 ; 5
18e: 01 c0 rjmp .+2 ; 0x192 <__vector_9+0x32>
190: 2d 98 cbi 0x05, 5 ; 5
192: 8f ef ldi r24, 0xFF ; 255
194: 80 93 01 01 sts 0x0101, r24 ; 0x800101 <counter>
198: 9f 91 pop r25
19a: 8f 91 pop r24
19c: 0f 90 pop r0
19e: 0f be out 0x3f, r0 ; 63
1a0: 0f 90 pop r0
1a2: 1f 90 pop r1
1a4: 18 95 reti
Как видим, вместо 10 команд push и 10 команд pop, в коде остались 6 команд push и 6 команд pop. а сам код функции занимает всего 70 байт - в 3.17 раз меньше, чем при использовании digitalWrite().
Автор: Андрей Шаройко <vanyamboe@gmail.com>