Выбрать главу

Поиск против вычисления

Как вы уже поняли, в скетчах вещественных чисел лучше избегать. Но как быть, если понадобится сгенерировать на аналоговом выходе сигнал синусоидальной формы, для чего, как можно догадаться, потребуется вычислять синус вызовом функции sin? Чтобы сформировать синусоидальный сигнал на аналоговом выходе, нужно обойти диапазон значений угла от 0 до 2 и вывести на аналоговый выход значение синуса этого угла. На самом деле все немного сложнее, потому что синусоиду нужно привести к диапазону значений, которые можно вывести на аналоговый выход.

Следующий пример генерирует синусоиду, разбивая каждый цикл на 64 шага, и выводит сигнал на аналоговый выход DAC0 платы Arduino Due. Имейте в виду, что для данного эксперимента годятся только платы Arduino с истинными аналоговыми выходами, такие как Due.

// sketch_-4_03_sin

void setup()

{

}

float angle = 0.0;

float angleStep = PI / 32.0;

void loop()

{

  int x = (int)(sin(angle) * 127) + 127;

  analogWrite(DAC0, x);

  angle += angleStep;

  if (angle > 2 * PI)

  {

    angle = 0.0;

  }

}

Измерение на выходе показывает, что данный скетч действительно производит сигнал замечательной синусоидальной формы, но с частотой всего 310 Гц. Процессор на плате Arduino Due работает с тактовой частотой 80 МГц, поэтому можно было бы ожидать увидеть сигнал с большей частотой. Проблема в том, что здесь скетч снова и снова повторяет одни и те же вычисления. Но поскольку каждый раз получаются одни и те же результаты, почему бы просто не рассчитать их все сразу и не сохранить в массиве?

Следующий пример также генерирует синусоиду, разбивая цикл на 64 шага, но использует прием поиска по таблице заранее подготовленных значений, которые выводит непосредственно в цифроаналоговый преобразователь (ЦАП).

byte sin64[] = {127, 139, 151, 163, 175, 186, 197,

207, 216, 225, 232, 239, 244, 248, 251, 253, 254,

253, 251, 248, 244, 239, 232, 225, 216, 207, 197, 186,

175, 163, 151, 139, 126, 114, 102, 90, 78, 67, 56, 46,

37, 28, 21, 14, 9, 5, 2, 0, 0, 0, 2, 5, 9, 14, 21, 28,

37, 46, 56, 67, 78, 90, 102, 114, 126};

void setup()

{

}

void loop()

{

  for (byte i = 0; i < 64; i++)

  {

    analogWrite(DAC0, sin64[i]);

  }

}

Этот пример генерирует точно такой же сигнал в форме синусоиды, но уже с частотой 4,38 кГц, то есть работает более чем в 14 раз быстрее.

Таблицу синусов можно рассчитать разными способами. Можно сгенерировать числа по обычной формуле в электронной таблице или написать скетч, который будет выводить числа в монитор последовательного порта, откуда их можно скопировать и вставить в другой скетч. Далее приводится версия скетча sketch_04_03_sin, которая выводит значения один раз в монитор последовательного порта:

// sketch_-4_05_sin_print

float angle = 0.0;

float angleStep = PI / 32.0;

void setup()

{

  Serial.begin(9600);

  Serial.print("byte sin64[] = {");

  while (angle < 2 * PI)

  {

    int x = (int)(sin(angle) * 127) + 127;

    Serial.print(x);

    angle += angleStep;

    if (angle < 2 * PI)

    {

      Serial.print(", ");

    }

  }

  Serial.println("};");

}

void loop()

{

}

Открыв окно монитора порта, вы увидите сгенерированную последовательность чисел (рис. 4.1).

Рис. 4.1. Использование скетча для получения массива чисел

Быстрый ввод/вывод

В этом разделе мы посмотрим, как увеличить скорость включения и выключения цифровых выходов. Мы увеличим максимальную частоту с 73 кГц почти до 4 МГц.

Простая оптимизация кода

Начнем с простого кода, включающего и выключающего цифровой выход с помощью digitalWrite:

// sketch_04_05_square

int outPin = 10;

int state = 0;

void setup()

{

  pinMode(outPin, OUTPUT);

}

void loop()

{

  digitalWrite(outPin, state);

  state = ! state;

}

Если запустить этот скетч и подключить осциллограф или частотомер к цифровому контакту 10, вы получите частоту чуть выше 73 кГц (мой осциллограф показал 73,26 кГц).

Прежде чем сделать большой шаг в направлении непосредственного управления портом, можно попробовать немного оптимизировать программный код скетча. Прежде всего, ни одна из переменных не обязана иметь тип int, их вполне можно объявить с типом byte. Это изменение увеличит частоту до 77,17 кГц. Далее переменную с номером контакта можно сделать константой, добавив слово const перед объявлением переменной. Это изменение увеличит частоту до 77,92 кГц.

В главе 2 вы узнали, что функция loop — это не просто цикл while, так как дополнительно проверяет наличие входящих данных в последовательном порте. То есть следующим шагом в направлении увеличения производительности может стать отказ от функции loop и перенос кода в setup. Скетч, в котором выполнены все описанные изменения, приводится ниже:

// sketch_04_08_no_loop

const byte outPin = 10;

byte state = 0;

void setup()

{

  pinMode(outPin, OUTPUT);

  while (true)

  {

    digitalWrite(outPin, state);

    state = ! state;

  }

}

void loop()

{

}

В результате всего этого мы получили увеличение максимальной частоты до 86,39 кГц.

В табл. 4.2 перечислены все улучшения, которые можно выполнить для увеличения производительности простого программного кода, прежде чем сделать последний шаг и заменить digitalWrite чем-нибудь более быстрым.

Таблица 4.2. Увеличение производительности простого программного кода

Действие

Скетч

Частота, кГц

Исходная версия

04_05

72,26

Объявление с типом byte вместо int

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.