04_06 | 77,17 | |
Использование константы с номером контакта вместо переменной | 04_07 | 77,92 |
Перенос содержимого loop в setup | 04_08 | 86,39 |
Байты и биты
Прежде чем переходить к непосредственному управлению портами ввода/вывода, нужно сначала разобраться с двоичным представлением, битами, байтами и целыми числами.
На рис. 4.2 показано, как связаны биты и байты.
Рис. 4.2. Биты и байты
Бит (в английском языке bit, происходит от binary digit — двоичная цифра) может иметь одно из двух значений — 0 или 1. Байт — это коллекция из 8 битов. Так как каждый из битов в байте может иметь значение 1 или 0, всего возможно 256 разных комбинаций битов в байте. Байт можно использовать для представления любых чисел в диапазоне от 0 до 255.
Каждый бит можно использовать также для обозначения состояния «включено» или «выключено». То есть, чтобы включить или выключить подачу напряжения на какой-то контакт, нужно установить или сбросить некоторый бит.
Порты в ATmega328
На рис. 4.3 изображены порты в микроконтроллере ATmega328 и то, как они связаны с контактами на плате Arduino Uno.
Рис. 4.3. Порты в ATmega328
Каждый порт не случайно имеет по 8 бит (байт), хотя в портах B и C используется только по 6 бит. Каждый порт управляется тремя регистрами. Регистр можно считать специальной переменной, позволяющей присваивать ей значения и читать значение из нее. На рис. 4.4 изображены регистры для порта D.
Рис. 4.4. Регистры для порта D
Регистр DDRD (Data Direction Register D — регистр D направления передачи данных) имеет 8 бит, каждый из которых определяет режим работы соответствующего контакта — вход или выход. Если бит установлен в значение 1, контакт работает как выход, в противном случае — как вход. Этим регистром управляет функция pinMode. Регистр PORTD используется для установки выходного напряжения на выходе, то есть digitalWrite устанавливает соответствующий бит, 1 или 0, чтобы установить на указанном контакте уровень напряжения HIGH или LOW.
Последний регистр называется PIND (Port Input D — вход порта D). Читая содержимое этого регистра, можно определить, на какие контакты подано напряжение HIGH, а на какие — LOW.
Каждый из трех портов имеет свои три регистра, для порта B они называются DDRB, PORTB и PINB, а для порта C — DDRC, PORTC и PINC.
Очень быстрый вывод цифровых сигналов
Следующий скетч обращается к портам напрямую, без применения pinMode и digitalWrite:
// sketch_04_09_square_ports
byte state = 0;
void setup()
{
DDRB = B00000100;
while (true)
{
PORTB = B00000100;
PORTB = B00000000;
}
}
void loop()
{
}
Скетч должен переключать контакт D10, который связан с портом B, поэтому вначале контакт настраивается на работу в режиме выхода, для чего третий бит справа в регистре DDRB устанавливается в 1. Обратите внимание на то, что B00000100 — это двоичная константа. В главном цикле мы сначала устанавливаем тот же бит в 1, а затем сбрасываем его в 0. Установка бита производится как простое присваивание значения регистру PORTB, как если бы это была обычная переменная.
Этот скетч способен генерировать сигнал с частотой 3,97 МГц (рис. 4.5) — почти 4 млн импульсов в секунду, что почти в 46 раз быстрее, чем с использованием digitalWrite.
Рис. 4.5. Сигнал с частотой 4 МГц, сгенерированный платой Arduino
Сигнал далек от прямоугольной формы из-за переходных процессов, которые вполне ожидаемы на такой частоте.
Еще одно преимущество непосредственного использования регистров порта — возможность вывода сигналов сразу на восемь контактов, что может пригодиться для вывода данных в параллельную шину данных.
Быстрый ввод цифровых сигналов
Тот же прием непосредственного доступа к регистрам можно использовать для увеличения скорости ввода цифровых сигналов. Хотя, если вы предполагаете таким способом определять моменты появления очень коротких импульсов, подумайте о возможности использования прерываний, они являются лучшим решением этой задачи (см. главу 3).
Прямой доступ к порту может пригодиться, например, когда требуется прочитать состояние сразу нескольких контактов. Следующий скетч читает все контакты, связанные с портом B (с D8 по D13), и выводит результат в монитор последовательного порта в виде двоичного числа (рис. 4.6).
Рис. 4.6. Чтение состояния сразу восьми контактов
// sketch_04_010_direct_read
byte state = 0;
void setup()
{
DDRB = B00000000; // все контакты на ввод
Serial.begin(9600);
}
void loop()
{
Serial.println(PINB, 2);
delay(1000);
}
Сбросом всех битов в регистре DDRB в 0 соответствующие контакты на плате настраиваются на работу в режиме входов. В цикле вызывается функция Serial.println, которая посылает число в монитор последовательного порта. Чтобы число посылалось в двоичной форме, а не в десятичной, как обычно, передается дополнительный аргумент 2.
Увеличение скорости ввода аналоговых сигналов
Давайте изменим скетч, который выполняет хронометраж, чтобы узнать, как долго работает analogRead, а потом попробуем ее ускорить:
// sketch 04_11_analog
void setup()
{
Serial.begin(9600);
while (! Serial) {};
Serial.println("Starting Test");
long startTime = millis();
// Далее следует код тестирования
long i = 0;
for (i = 0; i < 1000000; i ++)
{
analogRead(A0);
}
// конец кода, выполняющего тестирование
long endTime = millis();
Serial.println("Finished Test");
Serial.print("Seconds taken: ");
Serial.println((endTime — startTime) / 1000l);
}
void loop()
{
}
На плате Arduino Uno этот скетч выполняется 112 с. То есть Uno выполняет в секунду около 9000 операций чтения аналоговых сигналов.
Функция analogRead использует АЦП, имеющийся в микроконтроллере на плате Arduino. В Arduino используется тип АЦП, который называют АЦП с последовательной аппроксимацией. Он действует методом постепенного приближения, сравнивая аналоговый сигнал с опорным напряжением. АЦП управляется таймером, поэтому есть возможность ускорить преобразование, увеличив частоту.
Следующий скетч увеличивает частоту АЦП со 128 кГц до 1 МГц, что должно увеличить скорость чтения в восемь раз:
// sketch 04_11_analog_fast
const