Регистры портов (Arduino)

Использование регистров портов позволяют выполнять низкоуровневые высокоскоростные манипуляции с выводами микроконтроллера. Плата 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 — затраченное время в мкс

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

Войти с помощью: