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() Arduino Software версии 1.8.2

digitalWrite()

1.6.8

94.61 кГц

Меандр функции digitalWrite() Arduino Software версии 1.6.8

digitalWrite()

2:1.0.5 (Ubuntu 17.04)

105.19 кГц

Меандр функции digitalWrite() Arduino Software версии 2:1.0.5 (Ubuntu 17.04)

GPIO::setHigh()/setLow()

1.8.2

2 МГц

Меандр функции GPIO::setHigh()/setLow() библиотеки VEduino

Для работы с портами ввода/вывода в библиотеке 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>