Показаны сообщения с ярлыком Samples. Показать все сообщения
Показаны сообщения с ярлыком Samples. Показать все сообщения

суббота, 14 сентября 2013 г.

Мигаем без delay() с комфортом

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

к сожалению обсуждение написание макросов выходит за рамки данной статьи. фактически этот макрос - изложение прошлой статьи на языке понятном компилятору. что-бы "черновую работу" - он делал за нас 

Размещаем вверху скетча что-то такое:

#define DO_EVERY_V(varname,interval,action) \
static unsigned long __lastDoTime_##varname = 0; \
if( (millis()-__lastDoTime_##varname>interval )){ \
  {action;}\
  __lastDoTime_##varname=millis();\
}\
#define DO_EVERY_V_1(varname,interval,action)  DO_EVERY_V(varname,interval,action)
#define DO_EVERY(interval,action) DO_EVERY_V_1(__LINE__,interval,action)

После  этого мы можем писать в нашем скетче периодически действия в таком виде:

DO_EVERY(ИНТЕРВАЛ_ДЕЙСТВИЯ_В_МИЛЛИСЕКУНДАХ, ЧТО_НУЖНО_СДЕЛАТЬ);
или, если нам нужно выполнить несколько действий в таком
  DO_EVERY(ИНТЕРВАЛ_ДЕЙСТВИЯ_В_МИЛЛИСЕКУНДАХ,{
       ЧТО_НУЖНО_СДЕЛАТЬ1;
       ЧТО_НУЖНО_СДЕЛАТЬ2;
       ....
  });

Все. Дальше компилятор сам "развернет" DO_EVERY в объявление переменной, проверку "наступило-ли время" и т.п.


Наш пример из прошлой статьи  можно переписать в таком виде:

/* Blink without Delay
 2013
 by alxarduino@gmail.com
 http://alxarduino.blogspot.com/2013/09/ComfortablyBlinkWithoutDelay.html
 */
 
#define LED_PIN  13      // номер выхода,подключенного к светодиоду
#define  BLINK_INTERVAL  5000UL  // интервал между включение/выключением светодиода (5 секунд)
#define PRINT_INTERVAL 1000UL  // периодичность вывода времени в Serial (1 cекунда)
#define SERIAL_SPEED 9600 // скорость работы Serial

#define DO_EVERY_V(varname,interval,action) \
static unsigned long __lastDoTime_##varname = 0; \
if( (millis()-__lastDoTime_##varname>interval )){ \
  {action;}\
  __lastDoTime_##varname=millis();\
}\

#define DO_EVERY_V_1(varname,interval,action)  DO_EVERY_V(varname,interval,action) 
#define DO_EVERY(interval,action) DO_EVERY_V_1(__LINE__,interval,action)


 
void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(LED_PIN, OUTPUT);      
  
  // задаем скорость работы ком-порта
  Serial.begin(SERIAL_SPEED);
  
  
}
 
void loop()
{
  // мигаем диодом   (периодически переключаем его состоянии)
  DO_EVERY(BLINK_INTERVAL,digitalWrite(LED_PIN,!digitalRead(LED_PIN))); 
  
  // периодически выводим время в Serial
  DO_EVERY(PRINT_INTERVAL,{
    Serial.print("Current time:");
    Serial.println(millis());
  });

}

Все хорошо, но... что-то меня пугает

loop() у нас стал гораздо более лаконичным. Фактически в нем осталось только "что нам нужно делать", без "технической кухни", но все-таки, вот эта "магия в начале скетча" выглядит несколько "ошарашивающе". Захламляет саму суть скетча. Кухня осталась, просто переехала в другое место. 
А можно ли как-то обойтись без вставки этого макроса?  К сожалению - нет. Должен же компилятор как-то узнать "как обеспечивать переодичность". Но, хорошая новость, мы можем "спрятать" все это "за кулисы". Что-бы оно было, но не мозолило глаза. 
Я оформил этот макрос в виде библиотеки (хотя, конечно "библиотека" - это громко сказано :)

Cкачиваем библиотеку  отсюда https://bitbucket.org/alxarduino/leshakutils



  1. Скачиваем "свежую версию"
  2. Распаковываем в папку [путь к вашей ArduinoIDE]\libraries\Распаковываем вместе с именем папки. 
  3. Переименовываем распакованную папку из имени вида "alxarduino-leshakutils-3ef5efa1498a"  в "LeshakUtils"
  4. В результате все файлы должны попасть в папку [путь к вашей ArduinoIDE]\libraries\LeshakUtils\
  5. Перезапускаем ArduinoIDE (!!! Важно, без этого на найдет новую библиотеку)
Теперь , что-бы наша магия заработала, нам, вверху скетча нужно добавить всего одну строку (вместо того "страшного прицепа"):

#define <TimeHelpers.h>

 Теперь наш пример выглядит так:

/* Blink without Delay
 2013
 by alxarduino@gmail.com
 http://alxarduino.blogspot.com/2013/09/ComfortablyBlinkWithoutDelay.html
 */
 
#include <TimeHelpers.h>  
 
#define LED_PIN  13      // номер выхода,подключенного к светодиоду
#define  BLINK_INTERVAL  _SEC_(5)  // интервал между включение/выключением светодиода (5 секунд)
#define PRINT_INTERVAL _SEC_(1)  // переодичность вывода времени в Serial (1 cекунда)
#define SERIAL_SPEED 9600 // скорость работы Serial


 
void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(LED_PIN, OUTPUT);      
  
  // задаем скорость работы ком-порта
  Serial.begin(SERIAL_SPEED);
}
 
void loop()
{
  // мигаем диодом   (переодически переключаем его состоние)
  DO_EVERY(BLINK_INTERVAL,digitalWrite(LED_PIN,!digitalRead(LED_PIN))); 
  
  // переодически выводим время в Serial
  DO_EVERY(PRINT_INTERVAL,{
    Serial.print("Current time:");
    Serial.println(millis());
  });
  
}

Все. Теперь у нас все чисто и опрятно.

А что это за _SEC_()?

Если вы внимательно смотрели на прошлый скетч, то заметили что у нас произошло еще одно изменение: интервалы я теперь задал в секундах, а не в миллисекундах.
Это стало возможно, потому что в библиотеку я добавил еще "макросы помогалки" переводящие секунды/минуты/часы/дни в миллисекунды (ну лениво мне каждый раз высчитывать сколько миллисекунд в часе : и считать количество нулей в длинной цифре типа 60000):

#define _SEC_(sec) 1000UL*sec
#define _MIN_(mn)  _SEC_(60*mn)
#define _HR_(h)  _MIN_(60*h)
#define _DAY_(d) _HR(24*d)  

Так что теперь, если мне нужно, скажем, что-бы диод мигал раз 10-ть минут, а время выводилось в serial раз в два часа - я могу прямо так и написать:

#define  BLINK_INTERVAL  _MIN_(10) 
#define PRINT_INTERVAL _HR_(2) 

Но это - по желанию. Если привыкли все писать в миллисекундах - это возможность сохранилась. Просто "пишем цифру" как и раньше

#define PRINT_INTEVAL 7200000UL // 2 часа

А в чем преимущество перед другими библиотеками?

Действительно, для выполнения переодических для ардуины до меня написана далеко не одна библиотека (который используют прерывания таймеров, или как и наша работает через millis())
Скажем библиотека Arduino Playground - SimpleTimer Library ("идеологически" - она очень близка к нашему подходу. внутри у нее -тоже millis() используется).

В первую очередь  - мне просто хотелось написать "свою" :)  Во вторых - я не буду отговаривать вас использовать SimpleTimer. Она умеет больше чем моя "библиотека" (моя, пока умеет только периодическое действие выполнять). Но... за счет того что моя библиотека сделана с помощью макросов, а не объектов - само ее подключение не приводит к дополнительному расходу памяти. Она более "легковестная". У меня расход памяти будет в точности таким же как если бы мы "все писали руками".
Вообщем под разные ситуации - разные библиотеки будут более удобными. В каких-то случаях SimpleTimer предпочтительней, в каких-то - моя.

UPD1:

Раз в коментариях возник вопрос, попробую объяснить "почему тут не будет переполнения".
Оно будет. Но будет "два раза". После того как millis() перепрыгнет границу и начнет опять отсчет с нуля можно заподозрить что выражение if(millis()-lastDoTime>inteval) больше никогда не сработает. Так как разница (millis()-lastDoTime) - будет отрицательной (пока millis() опять не станет боьлше lastDoTime). И соответственно всегда меньше любого интервала.

Но.... это в математике. А тут нам нужно еще помнить про типы. И то что время мы храним в unsigned long. Следовательно отрицательных чисел - у нас просто не бывает. Попытка сохранить отрицательное числов в беззнаковую переменную, опять вызовет переполнение-переход. Только "в обратную сторону". И интервал (millis()-lastDoTime) - все-таки будет правильным. Несмотря на переполнение.

Что-бы это все "легче представить" (и не путать мозг "большими числами"). Представим что у нас время у нас вообще хранится в byte ( тоже безнаковый). Он "переполнится" уже на числах больше 255.

void setup(){
  Serial.begin(9600);
  byte interval=10;
  byte lastDoTime=250;

  // две "эмуляции millis()" после переполнения. Обе - меньше lastDoTime
  byte millis1=2; // не прошло еще время выполнение
  byte millis2=20; // прошло

  byte diff1=millis1-lastDoTime;
  byte diff2=millis2-lastDoTime;

  Serial.println(diff1); // нет, тут не -253, а 8-мь, отрицательное число тоже "переполнит", 
  Serial.println(diff2); // а тут у нас 26
  
  Serial.println(diff1>interval); // false - еще не прошло достаточно времени
  Serial.println(diff2>interval); // true - а вот теперь прошло.
}
void loop(){}


Если все равно, не понятно как происходит "переполнение в обратную сторону" и откуда там взялись 8 и 26, то можно рассмотреть такой пример.

Предположим нам нужно "прикинуть", что сохранится в переменную в таком случае
byte diff3=5-10;

Давайте эту -10, разобьем на слагаемые.

diff3=5-(5+1+4)
Откроем скобки
diff3=5-5-1-4 = (0 - 1) -4

Вот в это 0-1, в случае byte, дает нам в результате не -1, максимальное число 255.
У нас числовая ось, как-бы "закольцована" (в обе стороны).
diff3=(0-1)-4= 255 -4 = 251;

Поэтому сделав println(diff3), мы увидим выводе 251, а не -5




воскресенье, 8 сентября 2013 г.

Делаем несколько дел одновременно-периодически

продолжение статьи МИГАЕМ СВЕТОДИОДОМ БЕЗ DELAY() или всегда ли официальный примеру учат "хорошему".

Сразу оговорюсь, в рамках 8-ми битных контроллеров настоящей одновременности добится невозможно (только если какое-то действие будет выполняется независимым аппаратным блоком, например - генерация PWM). Но для подавляющего большинства задач - хватает "псевдо-одновременности". Когда контроллер по очереди "хватается" то за одно дело, то за другое. Если на каждом "деле" он не задерживается слишком на долго, то с точки зрения человека это выглядит как "одновременно".
Типичный пример чем может одновременно заниматься контроллер:

  • мигать диодом (периодически переключать его состояние)
  • читать датчик температуры
  • слушать приходящие команды из Serial
  • сообщать о текущем состоянии переменных в Serial
Так как каждое из этих действий выполняется контроллером за микросекунды (миллисекунды в худшем случае), то пробегая раз за разом по этому списку - мы создадим иллюзию одновременности. Главное - не задерживаться на одном месте не долго. Скажем при мигании диодом - не использовать функцию delay()  (она полностью останавливает выполнение скетча на какое-то время).

В принципе, как обходится без delay() - мы уже умеем благодаря примеру [src="blink..."]. Так же в прошлой статье [src....] мы детально разобрали стилистические ошибки этого примера и постарались его улучшить. Так что сделать, по образу и подобию этого примера два одновременных периодических действия - не должно составить проблемы. Просто заводим для каждого действия заводим свою переменную previousTime  (естественно имя чуть-чуть меняем) и оборачиваем действий в if(millis()-previousTime)

Два дела сразу

Скажем мы хотим мигать диодом раз в пол-секунды и, при этом выводить, раз в секунду, в Serial текущие значение millis()  (что-бы даже не видя ардуины физически иметь уверенность что скетч не завис).

Полностью по аналогии со скетчем из предыдущей статьи


/* Blink And Print Without Delay
 2013
 by alxarduino@gmail.com
 http://alxarduino.blogspot.com/2013/09/BlinkAndPrintWithoutDelay.html
 */
 
#define LED_PIN  13      // номер выхода,подключенного к светодиоду
#define  BLINK_INTERVAL  5000UL  // интервал между включение/выключением светодиода (5 секунд)
#define PRINT_INTERVAL 1000UL  // периодичность вывода времени в Serial (1 cсекунда)
#define SERIAL_SPEED 9600 // скорость работы Serial
 
void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(LED_PIN, OUTPUT);      
  
  // задаем скорость работы ком-порта
  Serial.begin(SERIAL_SPEED);
  
  
}
 
void loop()
{
  // мигаем диодом   (периодически переключаем его состояние)
  static unsigned long prevBlinkTime = 0; // время когда последний раз переключали диод
  if(millis() - prevBlinkTime > BLINK_INTERVAL) {
    prevBlinkTime = millis();  // 
    digitalWrite(LED_PIN,!digitalRead(LED_PIN)); 
  }
  
  // периодически выводим millis() в Serial
  static unsigned long prevPrintTime=0;
  if(millis()-prevPrintTime>PRINT_INTERVAL){
    prevPrintTime=millis();
    
    Serial.print("Current time:");
    Serial.println(millis());

  }
  
}

Причесываем код

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

/* Blink And Print Without Delay
 2013
 by alxarduino@gmail.com
 http://alxarduino.blogspot.com/2013/09/BlinkAndPrintWithoutDelay.html
 */
 
#define LED_PIN  13      // номер выхода,подключенного к светодиоду
#define  BLINK_INTERVAL  5000UL  // интервал между включение/выключением светодиода (5 секунд)
#define PRINT_INTERVAL 1000UL  // периодичность вывода времени в Serial (1 cекунда)
#define SERIAL_SPEED 9600 // скорость работы Serial
 
void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(LED_PIN, OUTPUT);      
  
  // задаем скорость работы ком-порта
  Serial.begin(SERIAL_SPEED);
  
  
}
 
void loop()
{
  blinkLed(BLINK_INTERVAL);  // мигаем
  printTime(PRINT_INTERVAL); // выводим время
}

// мигает диодом с периодичностью interval
void blinkLed(unsigned long interval ){
  static unsigned long prevTime = 0; // время когда последний раз переключали диод
  if(millis() - prevTime > interval) {
    prevTime = millis();  // 
    digitalWrite(LED_PIN,!digitalRead(LED_PIN)); 
  }
}

// выводит в Serial время с периодичностью interval
void printTime(unsigned long interval){
  static unsigned long prevTime=0;
  if(millis()-prevTime>interval){
    prevTime=millis();
    
    Serial.print("Current time:");
    Serial.println(millis());

  }
}

Шаблон/Заготовка

Как видите, благодаря тому что мы, с помощью ключевого слово static, "спрятали" объявление prevTime внутрь функции (но при этом сохранили ее способность сохранять значение при выходе из функции подобно глобальной переменной) мы смогли внутри обеих функций (blinkLed и printTime) использовать одно и тоже имя переменной prevTime. Нам теперь не нужно хмурить мозг что-бы каждый раз придумывать новое уникальное имя переменное. Более того, у нас получилась как-бы "универсальная заготовка периодической функции".

// "заготовка/шаблон функции" которая периодически выполняет КАКОЕ-ТО-ДЕЙСТВИЕ
void somePeriodical(unsigned long interval){
  static unsigned long prevTime=0;
  if(millis()-prevTime>interval){
    prevTime=millis();
    
    КАКОЕ-ТО-ДЕЙСТВИЕ;

  }
}

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

А зачем нужен параметр функции?

Так же, если вы заметили, этот шаблон не содержит жестко прописанного интервала времени. Мы его "подаем снаружи" с помощью параметра функции. Это дает нам возможность, примеру, сам интервал мигания положить в какую-то переменную и менять его по мере надобности (ну скажем в зависимости от температуры). 
Или, мигать с разной частотой в зависимости от того нажата кнопка или нет. Примерно так:

void loop(){
  
  if(digitalRead(PIN_BUTTON)) blinkLed(BLINK_INTERVAL)
                        else blinkLed(BLINK_INTERVAL*2); // в два раза медленее
  .....
}

Тут мы мигаем либо с нормальной, либо с половинной частотой в зависимости от того HIGH или LOW уровень сейчас на пине PIN_BUTTON (нажата или нет кнопка)

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


P.S. Задать вопросы и почитать обсуждение вы можете либо в комментариях ниже, либо на сайте arduino.ru в ветке Еще раз мигаем светодиодом без Delay | Аппаратная платформа Arduino

UPD: Оформил этот прием в виде библиотеки. Про это следующая статья:  Мигаем без delay() с комфортом

пятница, 30 августа 2013 г.

Мигаем светодиодом без 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. По крайней мере до тех пор, пока светодиод у нас может принимать два состояния.

4. И вновь не верный тип. Теперь уже критично

Наверное уже надоел :)  Но объявление следующей переменной мне опять не нравится. Почему previousMillis как long? В нее  мы сохраняем время  возвращаемое функцией millis(). В документации на нее говорится что она возвращает unsigned long  (кстати currentMillis объявили правильно). Значит и все переменные где хранится время должны быть unsigned long.
Эта ошибка с типом - гораздо более опасная. В отличает от предыдущих где тип был выбран "избыточно", в этом случае он выбран "недостаточно". Функция millis() возвращает unsigned long которая может принимать максимальное значение 4,294,967,295 . А в long у нас может поместится 2,147,483,647 . В два раза меньше.  Примерно на 24-тый день в переменной previousMillis из-за этого начнут образовыватся отрицательный значения. И все логика  (currentMillis - previousMillis) - может поломатся.

Конечно мигать диодом 24-дня непрерывно никто не будет. Но если научившись на этом примере, по его образу и подобию сделают, скажем систему полива сада?  Которая через месяц устроит потоп?

Кстати недавно на форуме, как раз и ловили подобную ошибку. Там вместо "unsigned long" подобная переменная была объявлена вообще как int. Повезло в том, что работали не с миллисекундами, на более мелких интервалах. Вместо millis() использовали micros() . И скетч "зависал" через 32-секунды (это число и натолкнуло на необходимость поиска ошибочного int). А если бы был millis() - все бы висло раз в несколько дней. И догадатся "что не так" - было бы гораздо сложней. Грешили бы на "питание/помехи" и т.п.
Вообщем я думаю вы уже поняли, что небрежное обращения с типами, использование "первого попавшегося с которым заработало" - в итоге выливается в часы жизни проведенные за поиском бага.


5. Мелочный придирки

Ну и парочка придирок которые "не ошибки", но можно было-бы компактней написать. Хотя возможно тут автор примера просто старался написать "попроще для новичков". Но раз мы уже "смотрим скетч под лупой.." :)
Переменную previousMillis можно, с помощью ключевого слово static объявить внутри loop(). Не засорять простраство имен глобальных переменных. Полезная привычка.

От переменное ledState - вообще можно отказаться. Текущие состояние пина можно узнать с помощью digitalRead().  Вообщем-то этот "совет" довольно спорен. С переменой ledState - код более читабелен... просто покажу что можно и без нее.

Да и без переменной currentMillis можно обойтись. Просто два раза вызвать функцию millis() . 

6. Наш ответ Чемберлену

Ну вот, а теперь попытаемся переписать этот пример, учтя все "замечания".

 /* Blink without Delay
 2013
 by alxarduino@gmail.com
 http://alxarduino.blogspot.com/2013/08/delay.html
 */

#define LED_PIN  13      // номер выхода,подключенного к светодиоду
#define  INTERVAL  1000UL           // интервал между включение/выключением светодиода (1 секунда)

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

void loop()
{
  // здесь будет код, который будет работать постоянно
  // и который не должен останавливаться на время между переключениями свето
  
  static unsigned long previousMillis = 0;        // храним время последнего переключения светодиода
 
  //проверяем не прошел ли нужный интервал, если прошел то
  if(millis() - previousMillis > INTERVAL) {
    // сохраняем время последнего переключения
    previousMillis = millis();  
    
    // меняем состояние выхода светодиода на противоположное текущему. 
    // если горит - тушим, не горит - зажигаем.
    digitalWrite(LED_PIN,!digitalRead(LED_PIN));
  }
}

UPD: Больше примеров вы найдете в следующей статье Делаем несколько дел одновременно-переодически

P.S. Задать вопросы и почитать обсуждение вы можете либо в комментариях ниже, либо на сайте arduino.ru в ветке Еще раз мигаем светодиодом без Delay | Аппаратная платформа Arduino