Использование регистров портов позволяют выполнять низкоуровневые высокоскоростные манипуляции с выводами микроконтроллера. Плата Arduino Nano (Uno) имеет три порта:
- B цифровые выводы от D8 до D13
- C аналоговые входы от A0 до А5
- D цифровые выводы от D0 до D7
Управление портами осуществляется при помощи трех регистров:
- DDR — определяет какой из выводов указанного порта будет выходом, а какой входом
- PORT — переводит выводы порта в состояние HIGH или LOW
- PIN — считывает состояние порта
Каждый бит в регистре отвечает за соответствующий вывод порта, например за вывод PD0 отвечает самый младший байт, за PD7 самый старший.
Пример:
void setup(){ DDRB = 0b00100000; // 5 бит соответствует PB5 или D13 // 1 - вывод сконфигурирован как выход (0 - вход) PORTB = 0b00100000; // 5 бит соответствует PB5 или D13 // установить на PB5 HIGH } void loop(){}
В данном примере регистр DDR обращается к порту B, лог. 1 в 5 байте переводит вывод в режим выхода, регистр PORT так же обращается к порту B, лог. 1 в 5 байте переводит вывод в HIGH. После загрузки скетча должен засветится светодиод который расположен на плате Arduino.
Показанный выше пример позволяет всего двумя строчками кода сконфигурировать все выходы порта и установить необходимые логические состояния выводов.
Если необходимо установить для примера на выходе D13 лог. 1 и не менять состояние других выходов, то можно воспользоваться следующим примером:
void setup(){ DDRB = 0B00100000; PORTB |= (1 << 5); // см. пример http://rcl-radio.ru/?p=80784 } void loop(){}
или установить лог. ноль:
void setup(){ DDRB = 0B00100000; PORTB &= ~(1 << 5); // см. пример http://rcl-radio.ru/?p=80784 } void loop(){}
Для чтения состояния порта используется регистр PIN:
void setup(){ Serial.begin(9600); DDRB = 0b00111111; // назначить выводы PB0...PB5 как выходы (D8...D13) PORTB = 0b00111011; // PB0...PB1 и PB3...PB5 = HIGH, PB2 = LOW } void loop(){ Serial.println(PINB,BIN); // чтение порта и вывод в монитор порта delay(1000); }
Информация о состоянии порты выводится в монитор порта:
Для чтения одного вывода порта используйте следующий пример:
void setup(){ Serial.begin(9600); DDRB = 0b00111111; PORTB = 0b00111011; } void loop(){ Serial.println(((PINB >> 5) & 1),BIN); // чтение состояния PB5 delay(1000); }
Далее переведем все выводы порта в режим чтения:
void setup(){ Serial.begin(9600); DDRB = 0b00000000; // все выводы порта в режиме чтения } void loop(){ Serial.println(((PINB >> 0) & 1),BIN); // чтение состояния PB0 (D8) delay(1000); }
На вход D8 подавать уровни LOW или HIGH, в мониторе порта Вы увидите 0 или 1.
Так же в цифровом входе можно использовать подтягивающий резистор, при этом на входе будет HIGH. Этот режим удобно использовать для подключения кнопки (коммутация кнопки на GND).
void setup(){ Serial.begin(9600); DDRB = 0b00000000;// все выводы порта в режиме чтения PORTB = 0b00000001;// подключить подтягивающий резистор к выводу PB0 (D8) } void loop(){ Serial.println(((PINB >> 0) & 1),BIN); delay(1000); }
У Вас может возникнуть вопрос: — «Зачем все это? Ведь существуют такие функции как pinMode(), digitalWrite() и digitalRead(). Эти функции понятны и просты в применении.»
Как говорилось в начале статьи, что использование регистров портов позволяют выполнять низкоуровневые высокоскоростные манипуляции с выводами микроконтроллера. Помимо увеличения скорости чтения и переключения цифровых выводов, использование памяти контроллера значительно уменьшается. Если у Вас простой скетч не требовательный к размеру памяти и ресурсам микроконтроллера, то проще использовать стандартные функции. Но если скетч большой и требовательный к скорости переключения цифровых выводов, то использование регистров будет самым оптимальным для Вас решением.
Далее приведу несколько примеров использования регистров портов, в которых будет сравнение с использованием стандартных для Arduino функций.
Мигание светодиодом D13 (Blink)
void setup() { pinMode(13,OUTPUT); } void loop() { digitalWrite(13,HIGH); delay(1000); digitalWrite(13,LOW); delay(1000); }
Скетч использует 924 байт (2%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 9 байт (0%) динамической памяти, оставляя 2039 байт для локальных переменных. Максимум: 2048 байт.
void setup(){ DDRB = 0B00100000; } void loop(){ PORTB |= (1 << 5); delay(1000); PORTB &= ~(1 << 5); delay(1000); }
Скетч использует 642 байт (1%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 9 байт (0%) динамической памяти, оставляя 2039 байт для локальных переменных. Максимум: 2048 байт.
Оба скетча выполняют одно и тоже действие, второй скетч занимает меньше памяти. А если Вам не важна точность задержки функции delay(), то можно применить _delay_ms() и использование памяти микроконтроллера станет еще меньше:
void setup(){ DDRB = 0b00100000; } void loop(){ PORTB |= (1 << 5); _delay_ms(1000); PORTB &= ~(1 << 5); _delay_ms(1000); }
Скетч использует 488 байт (1%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 9 байт (0%) динамической памяти, оставляя 2039 байт для локальных переменных. Максимум: 2048 байт.
Скорость переключения выхода D13
void setup() { Serial.begin(9600); pinMode(13,OUTPUT); } void loop() { unsigned long times = micros(); for(int i=0;i<100;i++){ digitalWrite(13,HIGH); digitalWrite(13,LOW); } Serial.println(micros()-times); delay(1000); }
Скетч использует 2104 байт (6%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 188 байт (9%) динамической памяти, оставляя 1860 байт для локальных переменных. Максимум: 2048 байт.
void setup(){ Serial.begin(9600); DDRB = 0B00100000; } void loop(){ unsigned long times = micros(); for(int i=0;i<100;i++){ PORTB |= (1 << 5); PORTB &= ~(1 << 5); } Serial.println(micros()-times); delay(1000); }
Скетч использует 1820 байт (5%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 188 байт (9%) динамической памяти, оставляя 1860 байт для локальных переменных. Максимум: 2048 байт.
Как видно на скриншотах, переключение цифрового выхода D13 100 раз занимает в среднем 680 мкс, тот же процесс во втором скетче занимает в среднем 54 мкс.
Аналогичная ситуация и при чтении выхода:
void setup() { Serial.begin(9600); pinMode(13, OUTPUT); digitalWrite(13, HIGH); } void loop() { unsigned long times = micros(); int x = 0; for (int i = 0; i < 1000; i++) { if (digitalRead(13) == 1) { x++; }; } Serial.println(micros() - times); Serial.println(x); delay(1000); }
Скетч использует 2242 байт (6%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 188 байт (9%) динамической памяти, оставляя 1860 байт для локальных переменных. Максимум: 2048 байт.1000 — кол-во измерений, 2576 — затраченное время в мкс
void setup() { Serial.begin(9600); DDRB = 0B00111111; PORTB |= (1 << 5); } void loop() { unsigned long times = micros(); int x = 0; for (int i = 0; i < 1000; i++) { if (((PINB >> 5) & 1) == 1) { x++; } } Serial.println(micros() - times); Serial.println(x); delay(1000); }
Скетч использует 1906 байт (5%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 188 байт (9%) динамической памяти, оставляя 1860 байт для локальных переменных. Максимум: 2048 байт.1000 — кол-во измерений, 444 — затраченное время в мкс