Изучаем Arduino: инструметы и методы технического волшебства

Блум Джереми

Часть IV. Дополнительные темы и проекты

 

В этой части

Глава 12. Аппаратные прерывания и прерывания по таймеру

Глава 13. Обмен данными с картами памяти SD

Глава 14. Подключение Arduino к Интернету

 

Глава 12. Аппаратные прерывания и прерывания по таймеру

Список деталей

Для повторения примеров главы вам понадобятся следующие детали:

• плата Arduino (рекомендуется Uno );

• USB-кабель для программирования платы Arduino;

• кнопка;

• пьезозуммер;

• RGB-светодиод с общим катодом;

• 1 резистор номиналом 10 кОм;

• 1 резистор номиналом 100 Ом;

• 1 резистор номиналом 150 Ом;

• 3 резистора номиналом 220 Ом;

• электролитический конденсатор 10 мкФ;

• микросхема 74НС14 (шесть инверторов с триггерами Шмитта);

• набор перемычек;

• 2 макетные платы.

Электронные ресурсы к главе

На странице http://www.exploringarduino.com/content/ch12 можно загрузить программный код, видеоуроки и другие материалы для данной главы. Кроме того, листинги примеров можно скачать со страницы www.wiley.com/go/exploringarduino в разделе Downloads.

Что вы узнаете в этой главе

Все предыдущие программы работали в синхронном режиме. В связи с этим возникали проблемы, например, выполнение команды delay() останавливает программу на некоторое время и не дает возможности Arduino осуществлять другие действия.

В главе 1 мы создали программный таймер, использующий функцию millis(), что

- 254 -

позволило избежать временного блокирования платы Arduino функцией delay().

Продолжим эту тему, добавив два таймера и аппаратные прерывания. Прерывания позволяют выполнять программу асинхронно, при наступлении определенного события (истечение временного интервала, изменение состояния входов и т. д.). Прерывания, как и следует из их названия, дают возможность остановить ход текущей программы Arduino, выполнить код прерывания, а затем вернуться к прерванной задаче. Далее мы узнаем, как осуществить прерывания по времени и при изменении состояния контактов. На основе этих знаний мы построим систему аппаратных прерываний и напишем программу, использующую прерывания таймера.

ПРИМЕЧАНИЕ

Видеоурок по прерываниям и аппаратному устранению дребезга можно посмотреть на странице

http://www.jeremyblum.com/2011103/07/arduino-tutorial-10-interrupts-and1hardware-debouncing. Найти данный видеофайл можно и на странице издательства Wiley.

 

12.1. Использование аппаратных прерываний

Аппаратные прерывания происходят при наступлении (или изменении) заданного состояния на входах-выходах. Аппаратное прерывание полезно, например, когда нужно изменить значение переменной, не проверяя непрерывно состояние кнопки.

Ранее мы устраняли дребезг путем опроса состояния кнопки в цикле. Этот прием отлично работает, если время выполнения остальной части программы невелико.

Но предположим, вы устраняете дребезг в цикле, выполнение которого занимает значительное время. Например, в основном цикле программы изменяется яркость

Рис. 12.1. Влияние внешнего прерывания на ход выполнения программы

- 255 -

светодиода или скорость двигателя с помощью оператора for() с некоторой задержкой delay(). Возникает опасность пропустить нажатие кнопки, которое происходит в момент выполнения главной программы. Вот здесь и приходят на помощь прерывания. Определенные контакты на плате Arduino могут вызывать внешние аппаратные прерывания. Вы выполняете главную программу, и при возникновении внешнего прерывания запускается специальная процедура его обработки (рис. 12.1), причем прерывание может наступить в любом месте программы.

 

12.2. Что выбрать: опрос состояния в цикле или прерывания?

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

12.2.1. Программная реализация

Благодаря встроенному языку программирования Arduino программировать внешние прерывания сравнительно просто. Однако организовать опрос контактов в цикле еще проще, все, что требуется - это вызов команды digitalRead(). Если нет безусловной необходимости в аппаратных прерываниях, то лучше их не применять, т. к. код программы усложнится.

12.2.2. Аппаратная реализация

С точки зрения аппаратной реализации между опросом контакта в цикле и прерыванием нет разницы, т. к. в обоих случаях считывается состояние входа. Тем не менее, при наличии дребезга ( см. главу 2) возникает серьезная проблема: процедура обработки прерывания может быть вызвана несколько раз. Самое неприятное, что в процедуре обработки прерывания нельзя задействовать функцию программного устранения дребезга, потому что невозможен вызов функции ctelay(). Поэтому, если вы хотите использовать прерывание для входа с дребезгом, необходимо устранить дребезг аппаратно.

12.2.3. Многозадачность

Одна из причин использования прерываний - предоставление псевдомногозадачности. С помощью Arduino никогда нельзя обеспечить реальную многозадачность, т. к. на плате только один микроконтроллер, который за один такт может выполнить только одну команду. Однако, поскольку микроконтроллер выполняет команды очень быстро, можно прибегнугь к прерываниям, чтобы выполнять разные задачи почти одновременно. Например, с помощью прерывания можно в про-

- 256 -

цессе уменьшения яркости светодиодов реагировать на нажатие кнопки, которая регулирует скорость или цвет. В то время как при опросе контакта в цикле можно прочитать значение входа кнопки командой dig i talRead() только один раз и имеющиеся в цикле loop() "медленные" операции снижают эффективность контроля входа кнопки.

12.2.4. Точность сбора данных

Для некоторых задач, где требуется быстрый сбор данных, прерывания являются необходимостью. Один из примеров - энкодер, смонтированный на двигателе постоянного тока, отправляющий в микроконтроллер импульс при определенном числе оборотов вала. Так осуществляется следящая обратная связь за скоростью вращения двигателя постоянного тока. Это позволяет динамически регулировать скорость в зависимости от нагрузки или отслеживать поворот вала двигателя. Но в такой системе исключительно важно быть уверенным, что плата Arduino получает каждый импульс. Поскольку импульсы очень короткие (намного короче импульса при нажатии кнопки), то при опросе в цикле loop() их можно пропустить. Если энкодер посылает импульс два раза за оборот вала двигателя, то из-за пропуска одного импульса измеренная частота вращения окажется вдвое меньше истинной. Поэтому в данном случае без аппаратного прерывания не обойтись.

12.2.5. Реализация аппаратного прерывания в Arduino

В большинстве плат Arduino для аппаратных прерываний выделены специальные контакты. Прерывания обозначаются с помощью идентификационного номера, которому сопоставлен определенный контакт (табл. 12.1). Исключение составляет плата Due, у которой для прерываний можно задействовать любой контакт, и номер прерывания совпадает с номером контакта.

Таблица 12.1. Аппаратные прерывания на различных платах Arduino

Плата INTO INT1 INT2 INT3 INT4 INT5
UNO, Ethernet Pin2 Pin3 - - - -
Mega2560 Pin2 Pin3 Pin21 Pin20 Pin19 Pin18
Leonardo Pin3 Pin2 Pin0 Pin1 - -

Для вызова прерывания существует встроенная функция attachInterrupt(). Ее первый аргумент - это идентификатор прерывания (для плат из табл. 12.1) или номер контакта Arduino (для платы Due). Если на плате Arduino Uno вы хотите назначить прерывание физическому контакту 2, то первый аргумент функции attachInterrupt() будет 0. Arduino Uno (и другие платы на базе ATmega328) поддерживает всего два внешних прерывания, платы Mega и Leonardo больше (см. табл. 12.1).

- 257 -

Аппаратные прерывания осуществляют переход к выполненшо определенных функций. Второй аргумент функции attach_interrupt() - имя выполняемой функции. Если вы хотите переключать состояние логической переменной при каждом прерывании, то функция обработки прерывания будет выглядеть так:

void toggleLed()

{

var = !var;

}

При вызове этой функции переменная var переключается в состояние, противоположное предыдущему, и происходит возврат в основную программу. Последний аргумент функции attachInterrupt() - режим запуска прерывания. Существуют режимы LOW, CHANGE, RISING и FALLING (для платы Due также есть режим HIGH).

Режимы CHANGE, RISING и FALLING наиболее популярны, потому что прерывание выполняется только один раз при изменении состояния входа. Режимы HIGH и LOW встречаются реже, потому что будут вызывать процедуру обработки прерывания непрерывно, блокируя остальную часть программы.

 

12.3. Разработка и тестирование системы противодребезговой защиты для кнопки

Чтобы применить полученные знания на практике, построим схему управления RGB-светодиодом с помощью кнопки с аппаратной противодребезговой защитой.

Пусть яркость одного из компонентов цвета RGB-светодиода меняется от максимума к минимуму и наоборот. При нажатии кнопки цвет меняется на другой, в то время как при изменении яркости используется функция задержки delay().

12.3.1. Создание схемы аппаратного устранения дребезга

Как вы узнали в главе 2, при нажатии кнопок уровень напряжения на входе многократно меняется от максимального до минимального значения. При наличии аппаратных прерываний это представляет большую проблему, потому что программа обработки прерывания будет вызвана несколько раз. К счастью, дребезг можно устранить аппаратно, получая на входе неискаженный сигнал.

Обычная кнопка подключена к цифровому выводу с помощью подтягивающего резистора. Наличие подтягивающего резистора (pull-up) дает следующий эффект: по умолчанию на входе высокий уровень, при нажатии на кнопку контакт Arduino соединяется с землей, и на вход платы поступает низкий уровень.

На рис. 12.2 изображена осциллограмма сигнала на входе при нажатии кнопки.

Видно, что напряжение скачет несколько раз вверх и вниз, прежде чем устанавливается в низком состоянии.

При таком сигнале функция обработки прерывания будет вызвана три раза подряд.

Чтобы предотвратить это, добавим в схему RC-цепочку. Подключим параллельно

- 258 -

кнопке конденсатор, а последовательно подсоединим резистор (рис. 12.3). Когда кнопка не нажата, конденсатор заряжается через резисторы R1 и R2. При нажатии на кнопку конденсатор начинает разряжаться и через некоторое время на выходе напряжение упадет до нуля. Если есть дребезг, сигнал "подпрыгивает" вверх и вниз в течение нескольких миллисекунд, но конденсатор подзаряжается через резистор и на выходе поддерживается высокий уровень напряжения. Благодаря этому уровень сигнала меняется с высокого на низкий только один раз в течение интервала времени, определяемого значениями резистора и конденсатора.

Рис. 12.2. Дребезг при нажатии обычной кнопки

Рис. 12.3. Схема устранения дребезга с помощью RC-цепочки

- 259 -

Резистор R2, включенный последовательно с кнопкой, уменьшает ток заряда и разряда конденсатора. Слишком большой ток может повредить кнопку. Добавление резистора 100 Ом увеличивает время разряда и обеспечивает безопасность компонентов. Но в результате спад импульса приобретает вид, изображенный на рис. 12.4.

Погашение дребезга с помощью RC-контура

Рис. 12.4. Сигнал на выходе RC-цепочки, устраняющей дребезг

Благодаря RC-цепочке дребезг исчез, но перепад напряжения на выводе Arduino приобрел экспоненциальную форму. При опросе контакта прерывание происходит при переходе от низкого уровня к высокому и от высокого к низкому с определеой скоростью. Из-за сглаживания, вызванного конденсатором, момент прерывания определяется неточно. Крутизну спада сигнала можно увеличить с помощью триггера Шмитта. Интегральные схемы с триггером Шмитта обеспечивают резкий перепад сигнала на выходе, при превышении входным сигналом определенного порога. Выходной сигнал с триггера Шмитта можно непосредственно подавать на контакт Arduino. В этой главе мы используем инвертирующий триггер Шмитта на микросхеме 74НС14. В корпусе этой микросхемы находятся шесть отдельных инвертирующих триггеров Шмитта, в наших примерах понадобится только один.

Цоколевка микросхемы приведена на рис. 12.5.

Подсоединим выход RC-цепочки к входу триггера Шмитта, а сигнал с его выхода подадим на контакт платы Arduino (рис.-12.6).

Поскольку выход триггера инверсный, то и сигнал будет перевернут. При нажатии кнопки уровень на входе Arduino будет высокий. При написании программы необходимо помнить об этом. Окончательный вид выходного сигнала изображен на рис. 12.7, как видим, сигнал чистый, без дребезга. Такой сигнал вполне подходит для формирования аппаратного прерывания.

- 260 -

Рис. 12.5. Цоколевка микросхемы 74НС14

Рис. 12.6. Окончательный вариант схемы подавления дребезга

12.3.2. Монтаж схемы

Мы разобрались, как работает аппаратное подавление дребезга для кнопки. Для экспериментов возьмем RGB-светодиод и одну кнопку. Соедините элементы согласно схеме, изображенной на рис. 12.8.

- 261 -

Погашение дребезга с помощью RC-контура и триггера Шмитта

Рис. 12.7. Сигнал на выходе схемы подавления дребезга с триггером Шмипа

Рис. 12.8. Схема устройства подавления дребезга

- 262 -

12.3.3. Программа обработки аппаратного прерывания

Пришло время написать простую программу для проверки работы устройства устранения дребезга и уяснения возможностей аппаратных прерываний Arduino. Наиболее очевидное и полезное применение аппаратных прерываний - опрос внешних входов во время работы программы, в которой присутствуют задержки delay().

Есть много сценариев, в которых это необходимо, мы будем управлять яркостью светодиода, формируя ШИМ-сигнал с помощью функции analogWrite(). Устройство подает на один из трех выводов RGB-светодиода сигнал, варьирующий от 0 до 255 и обратно. Каждый раз при нажатии на кнопку меняется цвет свечения. Это невозможно сделать с помощью опроса состояния вывода в цикле, т. к. момент нажатия кнопки почти наверняка будет пропущен.

Сначала разберемся, что такое переменные volatile. Если переменная будет меняться при выполнении прерывания, ее следует объявить как volatile (нестабильная). Это необходимо для корректной обработки переменной компилятором. Пример объявления переменной как volatile:

volatile int selectedLED = 9;

Для вызова прерывания в Arduino предусмотрена функция attachInterrupt(), находящаяся в setup(). Параметры функции: идентификатор прерывания (или номер контакта для платы Due ), имя функции обработки прерывания и режим запуска прерывания (LOW, CHANGE, RISING или FALLING). В нашей программе объявлено прерывание о (вывод 2 на плате Uno), которое при срабатывании RISING (по фронту) запускает функцию swap()

attachInterrupt(0, swap, RISING);

Осталось написать код функции обработки прерывания swap() и добавить его к основной программе. После подключения прерывания и написания кода функции обработки прерывания вы можете писать остальной текст программы. Каждый раз при вызове прерывания основная программа приостанавливается, выполняется функция обработки прерывания, затем ход основной программы продолжается с того места, где она была прервана. Поскольку прерывание останавливает основную программу, его обработка должна быть очень короткой и не содержать задержки delay(). Теперь все готово для написания программы управления яркостью RGB-светодиода и переключения цвета по нажатию кнопки. Полный текст программы приведен в листинге 12.1.

Листинг 12.1. Аппаратные прерывания, реализующие многозадачность - hw_multitask.ino

// кнопка с аппаратной противодребезговой защитой,

// управляемая прерыванием

// Контакт кнопки

const int BUTTON_INT =0; // Прерывание 0 (вывод 2 для Uno)

const int RED=11;

// Красный вывод RGB-светодиода контакт 11

const int GREEN=10;

// Зеленый вывод RGB-светодиода контакт 10

const int BLUE=9;

// Синий вывод RGB-светодиода контакт 9

- 263 -

// Переменные volatile можно изменять внутри функции обработки прерывания

volatile int selectedLED = RED;

void setup()

{

pinMode (RED, OUTPUT);

pinMode (GREEN, OUTPUT);

pinMode (BLUE, OUTPUT);

// Режим прерывания RISING (переход с LOW на HIGH)

attachInterrupt(BUTTON_INT, swap, RISING);

}

void swap()

{

// Выключить текущий цвет

analogWrite(selectedLED, 0);

// Новое значение для переменной selectedLED

if (selectedLED == GREEN)

selectedLED = RED;

else if (selectedLED == RED)

selectedLED = BLUE;

else if (selectedLED == BLUE)

selectedLED = GREEN;

}

void loop()

{

for (int i = 0; i<256; i++)

{

analogWrite(selectedLED, i);

delay(10);

}

for (int i = 255; i>= 0; i--)

{

analogWrite(selectedLED, i);

delay(10);

}

}

Загрузите программу на плату Arduino, вы увидите изменение яркости одного из цветов (R, G или В) RGB-светодиода от нуля до максимума и обратно. Каждый раз при нажатии на кнопку выбирается следующий цвет с той же яркостью, что и предыдущий.

ПРИМЕЧАНИЕ

Посмотреть видеоурок, демонстрирующий пример аппаратного прерывания Arduino для кнопки без дребезга, можно на странице http://www.exploringarduino.com/content/ch12. Этот видеофайл доступен также на сайте издательства Wiley.

- 264 -

 

12.4. Прерывания по таймеру

Аппаратные прерывания - не единственный вид прерываний, который возможен для Arduino. Существуют также прерывания по таймеру. В контроллере ATmega328 (установленном на Arduino Uno) есть три аппаратных таймера. На самом деле Arduino по умолчанию использует эти таймеры для функции millis(), работы с delay() и для включения ШИМ при вызове analogWrite(). Хотя в языке программирования Arduino отсутствуют специальные конструкции для работы с таймерами, управляя таймерами вручную, можно генерировать произвольные ШИМ-сигналы на любом контакте и делать многое другое. Далее мы расскажем, как с помощью сторонних библиотек (библиотека TimerOne) управлять 16-разрядным таймером Timer1 в ATmega328. Подобные библиотеки есть и для плат Leonardo, но здесь опишем только работу с Arduino Uno.

ПРИМЕЧАНИЕ

Таймер Timer1 служит для включения ШИМ-сигнала на выводах 9 и 10, поэтому при работе с библиотекой TimerOne выполнить функцию analogWrite() для этих контактов не удастся.

12.4.1. Общие сведения о прерываниях по таймеру

Так же, как секундомер в ваших часах, таймеры в Arduino начинают отсчет с нуля, увеличивая значение с каждым тактом кварцевого резонатора. Timer1 16-разрядный таймер, следовательно, значения в нем меняются от 0 до 216 - 1 (или 65 535). Как только это число будет достигнуто, значения сбрасываются в 0 и отсчет начинается снова. Время, через которое таймер достигает максимального значения, зависит от делителя частоты. Поскольку тактовая частота равна 16 МГц, то при отсутствии делителя переполнение и сброс Timer1 произойдет много раз в секунду. Библиотека TimerOne берет на себя все нюансы работы с таймером и позволяет установить любой интервал времени в микросекундах для срабатывания прерывания по таймеру.

12.4.2. Установка библиотеки

Для начала загрузите библиотеку TimerOne либо со страницы сайта Wiley для этой главы, либо непосредственно со страницы https://code.google.com/p/arduinotimerone/downloads. Распакуйте архив в папку с названием TimerOne и скопируйте в папку библиотек Arduino. Размещение этой папки по умолчанию отличается для разных операционных систем:

• Windows - Documents/ Arduino/libraries;

• Mac - Documents/ Arduino/libraries;

• Linux - /homeNOUR USER NAME/sketchbook/libraries.

Если в момент распаковки архива и копирования папки TimerOne среда Arduino IDE была открыта, перезапустите ее и убедитесь, что библиотека загрузилась.

Теперь все готово для работы с Timer1 на Arduino.

- 265 -

12.4.3. Одновременное выполнение двух задач

Важно понимать, что с помощью Arduino нельзя реализовать "истинную" многозадачность. Прерывания создают лишь видимость одновременного выполнения нескольких операций, позволяя переключаться между несколькими задачами чрезвычайно быстро. Использование библиотеки TimerOne позволит заставить мигать светодиод по таймеру, пока в цикле loop() выполняются другие задачи. В конце главы мы опишем проект, где в основном цикле loop() данные выводятся в последовательный порт с задержкой delay(), а по прерываниям таймера осуществляется управление светодиодом и пьезозуммером. Чтобы убедиться, что библиотека работает правильно, можно загрузить программу, приведенную в листинге 12.2, на плату Arduino Uno. Светодиод, подключенный к контакту 13 платы Arduino, будет мигать раз в секунду и управляться с помощью таймера. Если вставить какой-либо фрагмент кода в цикл loop(), то он будет выполняться одновременно.

// Использование прерывания по таймеру

Листинг 12.2. Прерывания по таймеру для проврки blink-timer1.ino

#include

const int LED=13;

void setup()

{

pinMode(LED, OUTPUT);

Timer1.initialize(1000000); // Значение для переполнения 1000000 мкс

// (1 секунда)

Timer1.attachInterrupt(blinky); // Выполнять blinky()при переполнении

}

void loop()

{

// Вставьте сюда любой код

}

// Обработка прерываний по таймеру

void blinky()

{

digitalWrite(LED, !digitalRead(LED)); // Переключить статус светодиода

}

В функции Timer1.initialize() задается временной интервал таймера в микросекундах. В этом примере установлено значение 1 с (1000000 мкс). Аргумент команды Timer1.attachInterrupt() - имя функции, которая будет выполняться по истечении заданного интервала времени. Очевидно, что функция обработки займет меньше времени, чем интервал между прерываниями.

- 266 -

 

12.5. Музыкальный инструмент на прерываниях

Чтобы закрепить знание аппаратных прерываний и прерываний по таймеру, создадим электронный музыкальный инструмент, который воспроизводит ноты последовательно в нескольких октавах. Ноту выбирают с помощью кнопки с аппаратной противодребезговой защитой. По прерываниям таймера одна и та же нота по порядку воспроизводится сначала в октаве 2, затем 3 и так до октавы 6, пока нажатием кнопки не будет выбрана следующая нота. В цикле loop() для отладки можно предусмотреть вывод текущей ноты и октавы через последовательный интерфейс на экран компьютера.

Если известна начальная частота, несложно вычислить частоты нот для каждой октавы. Например, рассмотрим ноту (До). Нота До большой октавы (С2) имеет частоту около 65 Гц. Чтобы получить частоту для До малой октавы СЗ (130 Гц) умножим частоту С2 на 2. Чтобы получить частоту для С4 (260 Гц), умножим частоту для СЗ на 2. Таким образом, частоту ноты для каждой октавы можно вычислить, умножая исходную частоту на степень числа 2. Зная это правило, будем увеличивать коэффициент в два раза при прерываниях по таймеру.

Переключать ноты будем так же, как в предьщущем примере с цветным светодиодом, - по нажатию кнопки. Задаем базовые частоты для каждой ноты и переключаем их каждый раз при нажатии кнопки.

12.5.1. Схема музыкального инструмента

Аппаратная часть проекта очень простая. К схеме предыдущего примера с RGB-светодиодом добавляем динамик, подключенный к контакту 12 Arduino через резистор 150 Ом. Я взял пьезоизлучатель, вы можете выбрать другой динамик. Монтажная схема показана на рис. 12.9.

12.5.2. Программа для музыкального инструмента

Программа для музыкального инструмента использует аппаратные прерывания и прерывания по таймеру, а также функцию tone() для управления динамиком и осуществляет передачу данных в последовательный порт. Загрузите код из листинга 12.3 на плату Arduino и нажимайте кнопку для перебора базовых частот. Посмотреть частоту, воспроизводимую в текущий момент, можно в мониторе последовательного порта.

Листинг 12.3. Код для музыкального инструмента - fun_with_sound.ino

// Использование аппаратных прерываний и прерываний по таймеру

// Подключение библиотеки TimerOne

#include

//

const int BUTTON INT =0; // Прерывание 0 (вывод 2 для Uno)

const int SPEAKER=12;

// Вывод 12 для подключения динамика

- 267 -

Рис. 12.9. Графическая схема музыкального аппарата

// Базовые частоты для нот

#define NOTE_C 65

#define NOTE_D 73

#define NOTE_E 82

#define NOTE_F 87

#define NOTE_G 98

#define NOTE_A 110

#define NOTE_B 123

// Переменные volatile для изменения при обработке прерываний

volatile int key = NOTE_C;

volatile int octave_multiplier = 1;

void setup()

{

// Запуск последовательного порта

Serial.begin(9600);

pinMode (SPEAKER, OUTPUT);

// Запуск для кнопки аппаратного прерывания по RISING

attachInterrupt(BUTTON_INT, changeKey, RISING);

//Set up timer interrupt

Timer1.initialize(500000);

// Период 0,5 секунды

- 268 -

Timer1.attachInterrupt(changePitch); // Выполнять changePitch()

// при прерываниях по таймеру

void changeKey()

{

octave_multiplier=1;

if (key == NOTE_C)

key = NOTE_D;

else if (key == NOTE_D)

key = NOTE_E;

else if (key == NOTE_E)

key = NOTE_F;

else if (key == NOTE_F)

key = NOTE G;

else if (key == NOTE_G)

key = NOTE_A;

else if (key == NOTE_A)

key = NOTE_B;

else if (key == NOTE_B)

key = NOTE_C;

}

// Обработка прерывания по таймеру

void changePitch()

{

octave_multiplier = octave_multiplier * 2;

if (octave_multiplier > 16) octave_multiplier

tone(SPEAKER,key*octave_multiplier);

}

void loop()

{

Serial.print ("Кеу: ");

Serial.print(key);

Serial.print(" Multiplier: ");

Serial.print(octave_multiplier);

Serial.print(" Frequency: ");

Serial.println(key*octave_multiplier);

delay(100);

}

Значения частот для нот можно найти в Интернете. Базовые частоты определены для нот второй октавы. Обратите внимание, что переменные key и octave_multiplier должны быть объявлены как volatile, потому что их значения меняются во время обработки прерываний. Функция changeKey() вызывается каждый раз при срабатывании прерывания по нажатию кнопки и изменяет базовое значение пере

- 269 -

менной key. Функция changePitch() вызывает tone() для установки частоты сигнала для динамика. Она запускается каждые полсекунды по прерыванию таймера.

При каждом вызове она удваивает базовую частоту, пока не будет достигнуто ее 16-кратное увеличение. Затем процесс повторяется с начальной базовой частоты.

В цикле loop() в последовательный монитор каждые 0, 1 с выводятся значения переменных key, octave_multiplier и частоты.

ПРИМЕЧАНИЕ

Посмотреть видеоклип, демонстрирующий работу музыкального инструмента, можно на странице www.exploringarduino.com/content/ch12. Этот видеофайл доступен и на странице издательства Wiley.

Резюме

В этой главе вы узнали следующее:

• Как выбрать альтернативу: опрос входов в цикле или использование прерываний.

• Что реализация прерываний на различных платах Arduino неодинакова. На плате Due прерывания можно задать для любого контакта, на других платах для прерываний доступны только определенные контакты.

• Как создать противодребезговую защиту кнопок на аппаратном уровне с помощью RC-цепочки и триггера Шмитта.

• Что с помощью функций обработки прерываний можно асинхронно опрашивать входы Arduino.

• Как с помощью сторонней библиотеки TimerOne организовать прерывания по таймеру.

• Как комбинировать прерывания таймера, аппаратные прерывания и опрос в одной программе, чтобы параллельно выполнять несколько задач.

 

Глава 13. Обмен данными с картами памяти SD

Список деталей

Для повторения примеров главы вам понадобятся следующие детали:

• плата Arduino (рекомендуется Uno );

• USB-кабель для программирования платы Arduino;

• источник питания для платы Arduino (блок питания или аккумулятор);

• инфракрасный датчик расстояния;

• плата часов реального времени;

• плата расширения SD card shield;

• карта памяти SD;

• набор перемычек;

• макетная плата;

• компьютер с картридером.

Электронные ресурсы к главе

На странице http://www.exploringarduino.com/content/ch13 можно загрузить программный код, видеоуроки и другие материалы для данной главы. Кроме того, листинги примеров можно скачать со страницы www.wiley.com/go/exploringarduino в разделе Downloads.

Что вы узнаете в этой главе

Есть множество примеров устройств на основе плат Arduino для сбора данных о состоянии атмосферы с метеодатчиков, зондов, датчиков систем жизнеобеспечения зданий и т. п. Учитывая небольшой размер, минимальное энергопотребление и простоту взаимодействия с датчиками, платы Arduino удобны для построения регистраторов данных (устройств для записи и хранения информации в течение определенного периода времени). Такие регистраторы часто присутствуют в сетях сбора данных и для хранения накопленной информации требуется энергонезависимая память, например SD-карта. В этой главе вы узнаете, как связать Arduino с SD-картой.

- 271 -

Мы расскажем, как записывать данные в файл и как читать информацию с SD-карты. С помощью часов реального времени к данным мы добавим точные временные метки. Вы также узнаете о том, как отобразить эти данные на вашем компьютере.

ПРИМЕЧАНИЕ

Видеоурок по записи и хранению данных можно посмотреть на странице

http://www.jeremyblum.com/2011 /04/05/tutorial-11-for-arduino-sd-cards-anddatalogging/ .

ПРИМЕЧАНИЕ

Видеоурок о регистрации данных местоположения с помощью GPS-приемника можно посмотреть на странице http://www.jeremyblum.com/2012/07/16/tutorial-15-for-arduinogps-tracking/. Эти видеофайлы доступны и на странице издательства Wiley.

 

13.1. Подготовка к регистрации данных

Системы регистрации данных, как правило, предназначены для сбора информации, поступающей, например, с аналоговых датчиков. Неотьемлемая часть подобных систем - устройства памяти для хранения данных в течение длительного времени.

Далее рассмотрим несколько способов использования SD-карты для записи данных с платы Arduino. Кратко перечислим возможные применения регистраторов:

• метеостанция для мониторинга освещенности, температуры и влажности в течение длительного времени;

• GPS-трекер для отслеживания местоположения;

• система контроля за температурой компонентов персонального компьютера;

• регистратор для управления электрическим освещением в доме или офисе.

В конце главы мы разработаем систему протоколирования данных с инфракрасным датчиком расстояния, которая позволит создать журнал входа и выхода людей из помещения.

13.1.1. Форматирование данных с помощью CSV-файлов

Хранить данные на SD-карте будем в CSV-файлах. Файлы этого формата легко создать, считать и проанализировать в различных приложениях, что хорошо подходит для задач регистрации информации. Стандартный CSV-файл выглядит примерно так:

Date,Time,Valuel,Value2

2013-05-15,12:00,125,255

2013-05-15,12:30,100,200

2013-05-15,13:00,110,215

- 272 -

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

Таблица 13.1. Просмотр CSV-файла в программе Excel

Поскольку CSV-файлы являются простыми текстовыми файлами, записывать в них данные можно с помощью знакомых нам команд print() и println(). С помощью Arduino можно легко проанализировать CSV -файлы, считывая их по строкам и отделяя нужную информацию от разделителей.

13.1.2. Подготовка SD-карты для регистрации данных

Перед тем как начать запись данных с Arduino, необходимо определиться с SD-картой. Вид SD-карты зависит от SD-адаптера, который вы будете использовать.

Существуют полноразмерные SD-карты и microSD. Большинство microSD-карт поставляется с адаптером, который позволяет подключить их к считывателю стандартных SD-карт. Для выполнения упражнений из этой главы вам потребуется картридер для компьютера (внешний или встроенный).

Большинство новых SD-карт заранее отформатировано и готово к использованию с Arduino. Если на вашей карте была записана какая-нибудь информация, необходимо ее отформатировать в FAT16 или FAT32. Карты объемом до 2 Гбайт следует отформатировать в FAT16, карты с большим объемом памяти- в FAT32. В примерах этой главы мы применяем microSD-карту, отформатированную в FАТ16. Обратите внимание, что при форматировании карты удаляется вся имеющаяся на ней информация, но при этом гарантируется, что карта готова для взаимодействия с Arduino. Если у вас новая карта, следующие шаги можно пропустить и вернуться к ним только при возникновении проблем с доступом к карте из Arduino.

Форматировать SD-карту в Windows легко:

1. Вставьте SD-карту в картридер. Откройте окно Computer (Мой компьютер) (рис. 13.1 ).

2. Щелкните правой кнопкой мыши по значку SD-карты ( она может иметь другое имя) и выберите опцию Format (рис. 13.2). Появится окно с вариантами форматирования карты (рис. 13.3).

3. Выберите тип файловой системы (FAT для карт 2 Гбайт и меньше, FAT32 для карт большего объема), оставьте размер кластера по умолчанию и задайте метку

- 273 -

Рис. 13.1. SD-карта в окне Computer

тома (я выбрал LOG, но вы можете выбрать любую). На рис. 13.3 изображена конфигурация форматирования для карты 2 Гбайт.

4. Нажмите кнопку Start, чтобы отформатировать карту памяти.

На компьютерах с ОС Mac процесс также прост:

1. Используйте команду Finder, чтобы найти и открыть приложение Disk Utility.

2. Нажмите на вкладку SDcard в левой панели и откройте вкладку Erase. Выберите пункт MS-DOS (FAT) для формата.

3. Нажмите кнопку Erase. Система отформатирует карту как FАТ16, независимо от ее емкости (в Mac нельзя отформатировать карту как FAT32).

В Linux можно отформатировать SD-карту из терминала. Большинство дистрибутивов Linux будет монтировать карту автоматически при ее вставке:

1. Вставьте карту, откроется всплывающее окно, отображающее карту.

2. Откройте терминал и введите команду df, чтобы получить список смонтироваых устройств. Результат должен выглядеть так, как на рис. 13.4.

Последняя запись должна относиться к SD-карте. На моей системе она была смонтирована как /ctev /mmcblkOp1, но у вас может отличаться.

- 274 -

Рис. 13.2. Выбор опции форматирования SD-карты

Рис. 13.3. Окно опций форматирования

- 275 -

Рис. 13.4. Список смонтированных устройств

3. Прежде чем форматировать SD-карту, необходимо ее отмонтировать с помощью команды umount. Аргументом команды будет название вашей SD-карты (рис. 13.5).

Рис. 13.5. Отмонтирование SD-карты в Linux

4. Форматируем SD-карту с помощью команды mkdosfs. Возможно, вам придется выполнить команду с правами привилегированного пользователя ( с помощью команды sudo). Используем флаг -F, чтобы указать файловую систему FAT. Вы можете включить опции 16 или 32, чтобы выбрать FAT16 или FAT32. Отформа-

- 276 -

тировать карту, которая была смонтирована как /dev/mmcblkOp1, можно командой sudo mkdosfs -F 16 /dev/mmcblkOp1 (рис. 13.6).

Ваша SD-карта отформатирована и готова к работе! Приступаем к взаимодействию с SD-картой через плату SD card shield.

Рис. 13.6. Форматирование SD-карты в Linux

 

13.2. Взаимодействие Arduino с SD-картой

Для SD-карт, как и для радиомодулей ХВее, которые мы рассмотрели в главе 11, требуется питание 3,3 В. Поэтому подключать SD-карту необходимо через переходник, который преобразует логические уровни и напряжение питания для SD-карты. Кроме того, связь с SD-картой возможна по интерфейсу SPI, описанному в главе 9. Arduino IDE поставляется с удобной библиотекой SD, реализующей функции низкого уровня и позволяющей легко читать и записывать файлы на SD-карту.

13.2.1. Платы расширения для SD-карт

Есть множество плат расширения (переходников) для подключения SD-карт к Arduino. Все рассмотреть в книге невозможно, опишем некоторые из популярных, указав их достоинства и недостатки.

Общие черты всех переходников SD-карт:

• подключаются к плате Arduino по интерфейсу SPI, через 6 контактов ICSP платы Arduino или к контактам цифровых выводов (11, 12 и 13 на Uno или 50, 51 и 52 на Mega);

- 277 -

• имеют вход выбора (CS), который может быть контактом по умолчанию (53 на Mega, 10 на других платах Arduino );

• подают питание 3,3 В на SD-карту и согласуют логические уровни.

Вот список наиболее распространенных плат расширения для SD-карт:

• Cooking Hacks Micro SD shield (http://www.exploringarduino.com/parts/cooking-hacks-SD-shield) (рис. 13. 7) используется для иллюстрации примеров этой главы. Это самый маленький переходник из рассматриваемых нами, и его можно подключить либо к цифровым контактам (8-13 на Uno ), либо к 6-контактному ICSP на Arduino. При подключении к контактам 8-13 вход выбора CS соединен с контактом 10. При подключении к разъему ICSP вход CS можно соединить с любым контактом Arduino. Это полезно, если контакт 10 платы Arduino занят. Переходник поставляется с SD-картой 2 Гбайт.

Рис. 13.7. Плата расширения Cooking Hacks Micro SD shield

• Official Arduino Wireless SD shield (http://www.exploringarduino.com/parts/arduino-wireless-shield) (рис. 13.8)- это первая из нескольких "официальных" плат расширения Arduino с поддержкой SD-карт. Переходник содержит схему для подключения радиомодуля ХВее и SD-карты памяти, что позволяет легко объединить примеры из настоящий главы и из главы 11. На этой плате расширения вход выбора SD-карты (CS) подключается к контакту 4 Arduino. Вы должны назначить контакт 10 как выход, а также указать в библиотеке, что контакт 4 подключен к CS.

• Official Arduino Ethernet SD shield (http://www.exploringarduino.com/parts/arduino-ethernet-shield) (рис. 13.9) позволяет подключить плату Arduino к проводной сети. Переходник также реализует интерфейс SD-карты, хотя в основном он служит для хранения файлов, которые будут доступны через сеть. Оба контроллера (Ethernet и SD-карты) на этом переходнике являются SPI-устрой

- 278 -

Рис. 13.8. Плата расширения Arduino Wireless SD shield

Рис. 13.9. Плата расширения Arduino Ethernet SD shield

ствами; вывод CS Ethemet подключается к контакту 10 платы Arduino, а вывод CS SD-карты - к контакту 4.

• Official Arduino Wi-Fi SD shield (http://www.exploringarduino.com/parts/arduino-wifi-shield) (рис. 13.10) также реализует подключение к сети, но через Wi-Fi. SD-карта, как и в Ethemet SD shield, служит для хранения файлов, доступных через сеть. Как и в Ethemet SD shield, Wi-Fi-контроллер для CS исполь

- 279 -

зует контакт 10, а SD-карта - контакт 4. Необходимо следить, чтобы оба устройства не были включены одновременно; активной может быть только одна линия CS (низкий логический уровень).

Adafruit data logging shield (http://www.exploringarduino.com/parts/ adafruitdata-logging-shield) (рис. 13. 11) особенно хорошо подходит для экспериментов,

Рис. 13.10. Плата расширения Arduino Wi-Fi SD shield

Рис. 13.11. Плата расширения Adafruit data logging shield

- 280 -

которые мы рассмотрим далее в этой главе, потому что включает в себя часы реального времени (RTC) и интерфейс SD-карты. Вывод CS SD-карты на этом переходнике назначен по умолчанию (53 на Mega, 10- на других платах Arduino ), а чип часов реального времени подключен к шине I2C.

На плате расширения SparkFun MicroSD shield (http://www.exploringarduino.com/parts/spark-fun-microSD-shield) (рис. 13.12) установлен только слот SD-карты. Тем не менее, предусмотрено монтажное поле для припаивания дополнительных компонентов. Вывод CS SD-карты подключен к контакту 8, его необходимо указать при использовании библиотеки SD с этим переходником.

Рис. 13.12. Плата расширения SparkFun MicroSD shield

13.2.2. SPI-интерфейс SD-карты

Как упоминалось ранее, связь платы Arduino с SD-картой осуществляется через SPI-интерфейс: контакты MOSI, MISO, SCLK (тактовые импульсы) и CS (выбор чипа). Для выполнения примеров этой главы мы будем использовать библиотеку SD. Это предполагает, что задействованы аппаратные SPI-контакты на плате Arduino, а в качестве CS назначен или контакт по умолчанию или определенный пользователем. Библиотека SD для правильной работы требует установки контакта для CS по умолчанию в качестве выхода, даже если для CS задан другой вывод.

В случае Uno это вывод 10, для платы Mega это вывод 53. Описанные далее примеры собраны на плате Arduino Uno с выводом CS, заданным по умолчанию.

13.2.3. Запись на SD-карту

Библиотека SD позволит создать несложный пример записи данных на SD-карту.

Будем получать данные с датчиков и записывать на карту. Данные хранятся в фай

- 281 -

ле под названием log.csv, в дальнейшем его можно открыть на компьютере. Важно отметить, что при форматировании карты в F АТ 16 имена файлов должны быть записаны в формате "8.3", т. е. расширение должно состоять из трех символов, а имя файла - не более чем из восьми символов.

Убедитесь, что переходник SD правильно подключен к плате Arduino и в него установлена SD-карта. Для Cooking Hacks Micro SD shield это будет выглядеть так, как на рис. 13.13 (используются контакты 8-13 Arduino и перемычка находится на правой стороне).

Рис. 13.13. Подключение платы расширения Cooking Hacks Micro SD shield

Для отладки программы будем контролировать статус нескольких функций библиотеки SD. Например, для установки связи с SD-картой необходимо вызвать следующую функцию (листинг 13.1 ):

Листинг 13.1. Функция инициализации SD-карты

if (!SD.begin(CS_pin))

{

Serial.println("Card Failure");

return;

}

Serial.println("Card Ready");

- 282 -

Обратите внимание, что мы не просто инициализируем обмен с картой с помощью функции so.begin (CS_pin), а получаем статус выполнения этой функции. При успешной инициализации программа выдает в последовательный порт сообщение об этом, в противном случае выводится сообщение о неуспехе и команда возврата останавливает дальнейшее выполнение программы.

При записи строки в файл подход аналогичный. Например, если вы хотите записать новую строку "hello" в файл, код будет выглядеть так, как в листинге 13.2.

Листинг 13.2. Функция записи информации на SD-карту

File dataFile = SD.open("log.csv", FILE_WRITE);

if (dataFile)

{

dataFile.println("hello");

dataFile.close();

// Данные не записываются,

// пока соединение не будет закрыто

}

else

{

Serial.println("Couldn't open log file");

}

В первой строке расположена команда создания нового файла (или открытие, если он уже существует) с названием log. csv. Если файл создан/открыт успешно, переменная dataFile получит значение true, и начнем процесс записи данных в файл.

В противном случае сообщаем об ошибке в последовательный порт. Запись строки данных в файл осуществляет функция da taFile.println(); чтобы предотвратить добавление символа новой строки, вызывайте функцию dataFile.print(). Все данные направляются в буфер и записываются в файл только после выполнения команды dataFile.close().

Теперь напишем простую программу, которая создает на SD-карте файл log.csv и каждые 5 секунд записывает в него через запятую метки и какое-либо сообщение (листинг 13.3). В каждой строке файла CSV будет записана временная метка (текущее значение функции millis()) и некоторый текст. Программа может показаться вам бесполезной, но на самом деле это важный пример, подготавливающий взаимодействие с реальными датчиками, чем мы займемся в дальнейших проектах.

Листинг 13.3. Тест записи данных на SD-карту- write_to_sd.ino

// Запись данных на SD-карту

#include

// Подключение контактов

// MOSI = pin 11

// MISO = pin 12

// SCLK = pin 13

- 283 -

// Подключение контакта выбора CS

const int CS_PIN = 10;

// Контакт для питания SD-карты

const int POW_PIN =8;

void setup()

{

Serial.begin(9600);

Serial.println ("Initializing Card");

// Установить CS как выход

pinMode(CS_PIN, OUTPUT);

// Для питания карты используется вывод 8, установить HIGH

pinMode(POW_PIN, OUTPUT);

digitalWrite(POW_PIN, HIGH);

if ( !SD.begin(CS_PIN))

{

Serial.println("Card Failure");

return;

}

Serial.println("Card Ready");

}

void loop()

{

long timeStamp = millis();

String dataString = "Hello There!";

// Открыть файл и записать в него

File dataFile = SD.open("log.csv", FILE_WRITE);

if (dataFile)

{

dataFile.print(timeStamp);

dataFile.print(",");

dataFile.println(dataString);

dataFile.close(); // Данные не записаны,

// пока соединение не закрыто!

// Вывод в монитор для отладки

Serial.print(timeStamp);

Serial.print(",");

Serial.println(dataString);

}

else

{

Serial.println("Couldn't open log file");

}

delay(5000);

}

- 284 -

Обратите внимание на несколько моментов, особенно если у вас такой же переходник, как у меня (Cooking Hacks Micro SD shield):

• CS можно установить на любом контакте; если это не контакт по умолчанию (10), то необходимо в setup() предусмотреть команду pinMode(10, OUTPUT), иначе библиотека SD не будет работать;

• питание на переходник подается через контакт 8, поэтому POW_PIN должен быть установлен в качестве выхода и в setup() необходимо определить его значение как HIGH;

• при каждом проходе цикла loop() временная метка обновляется значением текущего времени, прошедшего с начала выполнения программы в миллисекундах. Переменная должна иметь тип long, потому что millis() возвращает число больше, чем 16 бит.

Файл открывается для записи и в него добавляются данные, разделенные запятыми.

Мы также выводим эти данные в последовательный порт для отладочных целей.

Если вы откроете терминал последовательного порта, то увидите вывод данных как на рис. 13.14.

Рис. 13.14. и Рис. 13.15.

При возникновении ошибок проверьте правильность подключения переходника, убедитесь, что SD-карта отформатирована и должным образом вставлена. Для проверки корректности записи данных вставьте SD-карту в компьютер и откройте файл в программе просмотра электронных таблиц (рис. 13.15). Обратите внимание,

- 285 -

что данные располагаются в таблице с учетом разделяющих запятых и символов перехода на новую строку.

13.2.4. Чтение с SD-карты

Теперь рассмотрим чтение информации с SD-карты. При регистрации данных это не нужно, но может оказаться полезным для установки параметров программы. Например, можно указать частоту регистрации данных.

Вставьте SD-карту в компьютер и создайте на ней новый текстовый файл с именем speed.txt. В этом файле просто введите время обновления в миллисекундах. На рис. 13.16 показано, что я задал время, равное 1000 мс ( 1 с).

После записи желаемой скорости обновления сохраните файл на SD-карте и вставьте карту в переходник, подключенный к Arduino. Теперь изменим програм