Андрей Смирнов
Время чтения: ~13 мин.
Просмотров: 0

Мигание светодиодом на ардуино. мигалка и маячок

Функция _delay_ms() и мигающий светодиод

При написании кода на Си в Atmel Studio имеется очень удобная функция _delay_ms(). Для работы данной функции ее необходимо предварительно подключить директивой препроцессора <util/delay.h>.

В круглых скобках данной функции можно задавать время в миллисекундах, тогда перед скобками нужно записать ms, или в микросекундах – us:

При использовании данной функции для того, чтобы при компиляции Atmel Studio не выдавала никаких предупреждений, следует объявить частоту с помощью оператора #define. Так как по умолчанию для ATmega8 она равна 1 000 000 Гц, то это значение мы и объявим. Это делается следующей строкой:

#define F_CPU 1000000UL

В дальнейшем, когда мы будем подключать к МК кварцевый резонатор, без данной строки уже не обойтись. Структура ее останется прежней, только вместо 1 000 000 нужно будет записать частоту кварцевого резонатора.

Давайте улучшим нашу программу, так, чтобы сначала загорался один светодиод, затем через полсекунды он гаснул и еще через полсекунды загорался второй и снова через 0,5 с гаснул.

Загрузка примера “Blink” (мигание) в Arduino IDE

При подключении новой платы к персональному компьютеру, обратите внимание, что светодиод начинает мигать, так как все платы от производителей поступают с уже “залитым” скетчем “Blink”. На этом уроке мы перепрограммируем нашу плату, изменив частоту мигания светодиода. Не забудьте настроить оболочку Arduino IDE и выбрать нужный серийный порт, по которому Вы подключили Вашу плату

На этом уроке мы перепрограммируем нашу плату, изменив частоту мигания светодиода. Не забудьте настроить оболочку Arduino IDE и выбрать нужный серийный порт, по которому Вы подключили Вашу плату.

Пришло время проверить Ваше подключение и запрограммировать плату.

В оболочке Arduino IDE существует большая коллекция скетчей, которые уже готовы к использованию. Среди них находится и пример, который заставляет мигать “L” светодиод.

Откройте пример “Blink”, который находится в пункте меню File – Examples – 01.Basics

После открытия, расширьте окно оболочки Arduino IDE, чтобы Вы могли весь скетч в одно окне.

Скетчи из примеров, включенные в Arduino IDE предусматривают режим “только чтение” (“read only”). То есть, загрузить их на плату Вы сможете, но после изменения кода, Вы не сможете их сохранить в том же файле.

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

Из меню “File” выберите опцию “Сохранить как” (“Save As..”) и сохраните скетч под подходящим Вам названием, например, “MyBlink”.

Вы сохранили копию скетча “Blink” в Вашей библиотеке. Теперь открыть этот файл Вы можете в любой момент, перейдя по вкладке File – Scetchbook.

Код

Данный код использует millis() – функцию, которая возвращает программе (т.е. «оповещает программу о…») количество миллисекунд, прошедших с момента ее запуска, тем самым заставляя светодиод моргать.

 1 /* Моргание без команды Delay
 2 
 3 Включает/выключает светодиод, подсоединенный к цифровому контакту на Arduino, не прибегая к использованию функции delay(). Это значит, что в то же самое время может быть запущен другой код, не мешающий выполнению кода с морганием светодиода. 
 4 
 5  Цепь
 6  * Светодиод подсоединен к 13-му контакту и «земле».
 7  * Примечание: большинство плат Arduino уже имеют светодиод, подключенный к 13-му контакту – в таком случае этот пример можно выполнить, не пользуясь никаким дополнительным оборудованием, кроме самой Arduino.
 8 
 9  Создан в 2005 году Дэвидом А. Мэллисом (David A. Mellis),
10  модифицирован 8 февраля 2010 Полом Стоффрегеном (Paul Stoffregen).
11 
12  Данный пример кода не защищен авторским правом.
13 
14 
15  http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
16  */
17  
18 // Тут вписываем константы. Это постоянные (т.е. не меняющиеся) значения. 
19 // Используйте эту секцию, чтобы задать номер контакта:
20 const int ledPin =  13;      // номер для контакта светодиода
21 
22 // Тут вписываем переменные. Это значения, которые изменятся:
23 int ledState = LOW;             // в переменной ledState задаем режим работы светодиода (LOW значит «выкл»)
24 long previousMillis = ;        // в переменной previousMillis будет информация о времени, прошедшем с тех пор, как светодиод последний раз включался или выключался;
25 
26 // тип нижеследующих переменных будет long, поскольку время
27 // измеряется в миллисекундах и потому будет быстро превращаться в 
28 // слишком большую цифру, из-за чего ее нельзя будет хранить в int. 
29 long interval = 1000;           // интервал, после которого светодиод должен поменять режим работы (с «вкл» на «выкл» или наоборот; измеряется в миллисекундах)
30 
31 void setup() {
32   // устанавливаем цифровой контакт как выходной контакт:
33   pinMode(ledPin, OUTPUT);  
34 }
35 
36 void loop()
37 {
38   // здесь пишем код, который будет работать постоянно.
39 
40   // проверяем, не настало ли время менять режим светодиода 
41   // (с «вкл» на «выкл» или наоборот));
42   // то есть, проверяем, какова разница между текущим временем и временем, когда светодиод в последний раз перешел в положение «вкл»/«выкл», и не превышает ли она желаемый интервал.
43   unsigned long currentMillis = millis();
44  
45   if(currentMillis - previousMillis > interval) {
46     // сохраняем время, когда светодиод в последний раз включался/выключался 
47     previousMillis = currentMillis;   
48 
49     // если светодиод выключен, включаем его (и наоборот):
50     if (ledState == LOW)
51       ledState = HIGH;
52     else
53       ledState = LOW;
54 
55     // передаем светодиоду значение переменной ledState:
56     digitalWrite(ledPin, ledState);
57   }
58 }

Мигаем светодиодом без delay()

или всегда ли официальный примеру учат «хорошему».

Обычно это одна из первых проблем с которой сталкивается навичок в микроконтроллерх. Помигал диодом, запустил стандартный скетч blink(), но как только он него возникает желание что-бы контроллер «занимался чем-то еще» его ждет неприятный сюрприз — тут нет многопоточности. Как только он написали где-то что-то типа delay(1000) — обнаруживается что на это строчке «контроллер остановился» и ничего больше не делает (кнопки не опрашивает, датчики не читает, вторым диодом «помигать» не может).

Новичок лезет с этим вопросом на форум и тут же получает ушат поучений: «отказывайтесь от delay()», учитесь работать с millis() и в прочитайте, в конце концов базовые примеры. В частности Мигаем светодиодом без delay()

Приведу его код:

/* Blink without Delay
 2005
 by David A. Mellis
 modified 8 Feb 2010
 by Paul Stoffregen
 */

const int ledPin =  13;      // номер выхода, подключенного к светодиоду
// Variables will change:
int ledState = LOW;             // этой переменной устанавливаем состояние светодиода 
long previousMillis = 0;        // храним время последнего переключения светодиода

long interval = 1000;           // интервал между включение/выключением светодиода (1 секунда)

void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(ledPin, OUTPUT);      
}

void loop()
{
  // здесь будет код, который будет работать постоянно
  // и который не должен останавливаться на время между переключениями свето
  unsigned long currentMillis = millis();
 
  //проверяем не прошел ли нужный интервал, если прошел то
  if(currentMillis - previousMillis > interval) {
    // сохраняем время последнего переключения
    previousMillis = currentMillis;  

    // если светодиод не горит, то зажигаем, и наоборот
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // устанавливаем состояния выхода, чтобы включить или выключить светодиод
    digitalWrite(ledPin, ledState);
  }
}
  

В принципе отправка к этому примеру — вполне правильна. В нем действительно виден стандартный паттерн как нужно выполнять какое-то переодическое действие (или отложенное):

1. Сохраняем время в какую-то переменную
2. В loop() все время смотрим на разницу «текущие-время — сохраненное»
3. Когда эта разница превысила какое-то значение (нужный нам «интервал переодичности»)

4. Выполняем какое-то действие (меняем состояние диода, заново запоминаем «стартовое время и т.п.»)
С задачей «объяснить идею» — пример справляется. Но, с моей точки зрения, в нем есть несколько методологических ошибок. Написание скетчек в подобно стиле — рано или поздно приведет к проблемам.
Итак, что же тут не так?

1. Не экономно выбираем тип переменных

Переменная ledPin у нас объявлена как тип int. Зачем?  Разве номер пина может быть очень большим числом? Или может быть отрицательным числом? Зачем под его хранение выделять два байта, когда вполне хватит одного. Тип byte может хранить числа от 0 до 255. Для номера пина — этого вполне хватит.

  const byte ledPIN = 13; //  номер выхода, подключенного к светодиоду

Этого будет вполне достаточно.

2. А зачем нам переменная для малых чисел?

А зачем нам тут вообще переменая? (пусть и объявленная как const). Зачем тратить такты процессора на чтение переменной? И расходовать еще один байт?  Воспользуемся директивой препроцессора #define

#define LED_PIN  13 //  номер выхода, подключенного к светодиоду

Тогда еще на этапе компиляции компилятор просто подставить 13-ть везде где в коде используется LED_PIN и не будет выделять отдельных переменных.

3. Тип int опять выбрали как «первое что в голову пришло»?

И опять спотыкаемся на объявлении следующей же переменной. Почему ledState опять int? Кроме того что снова «два байта там где можно один использовать», так еще и «смысловая нагрузка» теряется. Что у нас хранится в переменной? Состояние светодиода. Включен/выключен. Горит/Не горит. Явно же напрашивается тип boolean. По крайней мере до тех пор, пока светодиод у нас может принимать два состояния.

Резюме

Платформа Arduino предоставляет нам несколько способов выполнения задержки в своем проекте. С помощью delay вы можете быстро поставить на паузу выполнение скетча, но при этом заблокируете работу микроконтроллера. Использование команды millis позволяет обойтись в ардуино без delay, но для этого потребуется чуть больше программировать. Выбирайте лучший способ в зависимости от сложности вашего проекта. Как правило, в простых скетчах и при задержке меньше 10 секунд используют delay. Если логика работы сложнее и требуется большая задержка, то вместо delay лучше использовать millis.

Используем в Ардуино millis вместо delay

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

// пример замены delay на millis в программе
unsigned long time;   // переменная времени
boolean ledState = 0;  // переменная состояния светодиода

void setup() {
   pinMode(13, OUTPUT);
   Serial.begin(9600);  // запускаем монитор порта
   time = millis();          // запускаем отсчет времени
}
 
void loop() {
    if (millis() - currentTime > 500)   // проверяем сколько прошло миллисекунд
    {
       currentTime = millis();
       ledState=!ledState;                   // меняем состояние светодиода на противоположное
       digitalWrite(10, ledState);
    }

   // выводим количество миллисекунд прошедших с момента начала программы
   Serial.print("Time: ");
   Serial.println(time);
}

Загружаем код на ESP8266

Вариант А – Загрузка кода на ESP-12E

Загрузка кода на ESP-12E NodeMCU Kit выполняется очень просто, т.к. эта плата оснащена встроенным программатором. Вам не придется делать никаких лишних соединений – достаточно лишь подключить плату к ПК.

Кликните в IDE Arduino на «Инструменты» > «Плата» (Tools > Board) и выберите плату NodeMCU 1.0 (ESP-12E Module). Все ее настройки по умолчанию должны выглядеть вот так:

Важно: Ваш COM-порт будет называться, скорее всего, не «COM11» (как на скриншоте выше), а как-то по-другому. И это нормально – так он не будет мешать никаким другим процессам

Но все остальные настройки должны быть как на скриншоте выше.

Проверив настройки, нажмите на кнопку «Загрузка» (Upload) в IDE Arduino

и подождите несколько секунд, пока не увидите сообщение «Загрузка завершена» (Done uploading) в нижнем левом углу программы.

Как должна выглядеть цепь

Подключите к контакту D4 (GPIO2) ESP8266-модуля светодиод и резистор на 220 Ом.

Перезапустите ESP8266. Все готово, поздравляем! Светодиод должен начать мигать раз в секунду!

Вариант Б – Загрузка кода на ESP-01

Для загрузки кода на ESP-01 между ESP8266 и FTDI-программатором должна быть установлена последовательная коммуникация.

Важно: У большинства FTDI-программаторов есть перемычка для преобразования 5 вольт в 3.3 вольта. Убедитесь, что у вашего программатора она стоит на 3.3 вольтах.. Далее подключите ESP8266 к FTDI-программатору согласно схеме ниже, чтобы настроить последовательную коммуникацию между ними.

Далее подключите ESP8266 к FTDI-программатору согласно схеме ниже, чтобы настроить последовательную коммуникацию между ними.

Их контакты должны быть подключены друг к другу следующим образом (слева – ESP8266, справа – FTDI-программатор):

  • RX –> TX
  • TX –> RX
  • CH_PD –> 3.3V
  • GPIO0 –> GND
  • VCC –> 3.3V
  • GND –> GND

Примечание: На схеме выше контакт GPIO0 подключен к GND, т.к. наша задача – загрузить код. Когда мы загружаем на ESP8266 новый скетч, это требует от ESP8266 записать новую прошивку. При нормальном использовании (если нам не нужно записать на ESP8266 новую прошивку) контакт GPIO0 должен быть подключен к VCC.

Подготовка IDE Arduino

Подключив к ПК связку из ESP8266 и FTDI-программатора, запустите IDE Arduino.

Кликните на «Инструменты» > «Плата» (Tools > Board) и выберите плату «Generic ESP8266 Module». По умолчанию все настройки для нее должны выглядеть примерно вот так:

Важно: В вашем случае, скорее всего, будет какой-то другой COM-порт, а не тот, что на скриншоте выше («COM8»). И это нормально, потому что мы не будем мешать другим процессам

Но все прочие настройки должны выглядеть как на скриншоте выше.

Проверив настройки, нажмите на кнопку «Загрузка» (Upload) в IDE Arduino

и подождите несколько секунд, пока не увидите сообщение «Загрузка завершена» (Done uploading) в нижнем левом углу программы.

Как должна выглядеть цепь

Загрузив код в модуль, отключите его от компьютера. Затем подсоедините компоненты друг к другу как на схеме ниже.

Запитайте ESP8266 от 3.3-вольтового источника питания. Все готово, поздравляем! Светодиод должен начать мигать раз в секунду.

Функция delayMicroseconds

Данная функция является полным аналогом delay за исключением того, что единицы измерения у нее не миллисекунды, а микросекунды (в 1 секунде – 1000000 микросекунд). Максимальное значение будет 16383, что равно 16 миллисекундам. Разрешение равно 4, то есть число будет всегда кратно четырем. Кусочек примера будет выглядеть следующим образом:

 digitalWrite(2, HIGH);    // подаем высокий сигнал на 2 пин
 delayMicroseconds(16383); // пауза 16383мкс
 digitalWrite(2, LOW);     // подаем низкий сигнал на 2 пин
 delayMicroseconds(16383); // пауза 16383мкс

Проблема с delayMicroseconds точно такая же, как у delay – эти функции полностью «вешают» программу и она на некоторое время буквально замирает. В это время невозможна работа с портами, считывание информации с датчиков и произведение математических операций.  Для мигалок данный вариант подходит, но опытные пользователи не используют её для больших проектов, так как там не нужны такие сбои. Поэтому, гораздо лучше использовать функции, описанные ниже.

#define и регистры

Также оператор #define хорош тем, что с помощью него можно задавать имена регистрам. Например, если мы подключаем к порту D светодиоды, то вместо PORTD мы можем записать, например VD:

#define VD PORTD

.

.

.

VD = 0b00000001;

Давайте перепишем программу, применяю директиву #define:

#define F_CPU 1000000UL

#include <avr/io.h>

#include <util/delay.h>

#define MIG 300

#define VD PORTD

int main(void)

{

    DDRD = 0b000000011;

    while (1)

    {

             VD = 0b000000001; // Включаем 1-й светодиод

             _delay_ms(MIG);     // Ждем 0,5 секунды

             VD = 0b000000000; // Выключаем 1-й

             _delay_ms(MIG);     // Ждем 0,5 секунды

             VD = 0b000000010; // Включаем 2-й

             _delay_ms(MIG);     // Ждем 0,5 секунды

             VD = 0b000000000; // Выключаем 2-й

             _delay_ms(MIG);     // Ждем 0,5 секунды

    }

}

Таким способом можно сделать простейшую гирлянду. Однако применение функции _delay не всегда будет оправдано, поскольку во время задержки MK не выполняет никаких полезных действий. Более эффективный способ формирования временных интервалов является применение встроенных таймеров-счетчиков. О них будет подробно рассказано в последующих статьях.

Скачать  2 Atmel Studio

Скачать  2 Proteus

Рейтинг автора
5
Материал подготовил
Максим Иванов
Наш эксперт
Написано статей
129
Ссылка на основную публикацию
Похожие публикации