Комментарии 48
http://playground.arduino.cc/Code/Timer1 Все остальное — костыли
В прерывании же надо что-то сделать и быстро выйти. С моим подходом нет такого жёсткого отграничения. Понятно, что это не реальное время и не многозадачность, так, припарка. Для ардуининых задач пока хватает :)
Timer1 — сила, но…
Не совсем.
Во-первых, по таймеру ограничение по времени (не знаю точно про ардуино, я про общий случай) — успеть отработать до того, как он вызовется снова. В целом, этого обычно вполне достаточно, чтобы успеть переключить задачу.
Во-вторых, вообще есть два пути: либо конечный автомат (state machine), либо многопоточность. Первый реализуется без каких-либо таймеров, часто используется во всяких устройствах типа микроволновки. Когда же устройству пора становится умнее, например, обрабатывать нажатия на кнопки и рисовать что-то на экран, первое время костыль вроде вашего smartDelay подойдет, но чем раньше от него отказаться, тем лучше. Благо, многозадачность делается в несколько строк кода. И еще больше благо — проверенных временем и миллиардами различных устройств реализаций предостаточно, тот же rtos (да, он больше про реальное время, но выпотрошить и достать только диспетчер задач из него можно).
Ну и в третьих — обрабатывать нажатые кнопки тоже стоит по прерываниям. Но там все хитрее и в случае с ардуиной (уж не знаю, умеет ли она в прерывания на пинах, ATmega328, на чем она построена, вроде как умеет, но не на всех ножках) может быть достаточно проблематично и проще действительно влепить планировщик задач.
Ну и планировщик задач на две (или больше заранее известные) задачи делается в 5 строчек кода.
Конечно, если нужен шедулер, который будет выполнять функцию ровно раз в секунду — уже будет гораздо сложнее, ваш код тут даже больше подойдет, но я не представляю себе таких задач на практике.
У прерываний есть одна важная особенность — они позволяют уводить контроллер в режим ожидания, когда ничего не нужно считать. Опять же, маловероятно что в проекте на ардуино кто-то будет париться энергопотреблением, но перспективы роста — они такие.
на атмега328 всего два внешних прерывания, прочие можно обрабатывать, но это будет точно не переносимый код и сильно зависимый от камня. Прочие прерывания группируются по нескольку портов и их надо разделять ещё как-то в обработчике.
Я думаю перейти с микросекунд в более удобные миллисекунды, практика показала, что лишние нули лично мне не нужны, кстати.
Перспективы же роста — перейти на stm32 :)
И если уж изучать работу таймера, то лучше таки по гайду типа такого: Newbie's Guide to AVR Timers © Dean Camera ( http://www.github.com/abcminiuser/avr-tutorials/blob/master/Timers/Output/Timers.pdf?raw=true )
http://robotsbigdata.com/docs-arduino-timer.html
Есть в ардуиновском library manager, так что даже с гитхаба качать ничего не надо.
Ну, т.е. — main loop, и отдельные «задачи», вызываемые по таймеру — каждую секунду,
каждые 100мс, 10мс, etc.
Понятно, что это не настоящая многозадачность — «задачи» должны сами успевать завершаться до следующего цикла, но, тем не менее…
Но люди, зачем-то, изобретают велосипеды — я этот факт не обсуждал.
Боюсь, что «взять ртос» примерно равно «забыть про arduino ide».
https://www.hackster.io/feilipu/using-freertos-multi-tasking-in-arduino-ebc3cc
Для ардуино есть несколько портов free rtos. Да и чему там не влазить? Если не брать всякие аллокаторы и прочее.
Пример из практики: нужно сделать так, чтоб кнопка срабатывала при нажатии большем 3 сек, при этом основная программа не блокировалась в проверке уровня на кнопке.
//в основном цикле:
main()
{
//bla bla bla
PT_SCHEDULE(ScanKey(&ScanKey_pt)); //неблокирующее сканирование клавиатуры
//bla bla bla
}
//сама функция проверки, преобразуется из обычной функции в неблокирующую 3-мя строчками кода.
//void ScanKey(void)
PT_THREAD(ScanKey(struct pt *pt))
{
PT_BEGIN(pt);
if (keys.SW3)
{
timer_set(&timer_service, KEY_DELAY);
PT_WAIT_WHILE(pt, ((!SW3) && (timer_expired(&timer_service)==0))); // "ждем" пока кнопку удерживают и не истёк таймаут
if(timer_expired(&timer_service) && (!SW3))
{
//время вышло а кнопка нажата-всё ОК
Regim=1;
}
else Regim=0;
}
PT_END(pt);
}//eof keys
https://github.com/emelianov/Run
Использование прерываний это не отменяет: в прерывании выставил флаг, а в основном цикле выполнил ресурсоемкую часть.
Ээээ…
Ну, если я спрячу таймер1 внутри своего класса, а потом захочу в самом скетче им попользоваться (я же не знаю потроха класса SmartDelay), то код превратится в тыкву.
В задачах как раз стояло сделать нечно, что можно спрятать, подключить и забыть про код внутри. Ардуино-стайл некий.
Основная проблема здесь в том, что семантика его использования достаточно страшная и непонятная.
Если сделать нечто наподобие такого (код большой, прячу под спойлер и отбрасываю много чего)
#define TASK_CLASS(TypeName) TypeName
#define TASK_BEGIN(TypeName, Locals) class TASK_CLASS(TypeName) : public StatefullTaskBase { \
private: \
struct Locals; \
public: \
virtual bool Step() override { \
switch (this->state) { \
case -1: return true; \
case 0:
#define TASK_BODY_END ;} return true; }
#define TASK_CLASS_END };
#define TASK_END TASK_BODY_END TASK_CLASS_END
#define TASK_YIELD() this->state = __LINE__; return false; case __LINE__:
#define TASK_WAIT_FOR(Object) this->WaitFor(Object); this->state = __LINE__; return false; case __LINE__:
#define TASK_YIELD_WHILE(cond) this->state = __LINE__; case __LINE__: if ((cond)) return false;
#define SECOND *1000LL
#define SECONDS SECOND
#define MINUTE *(60LL*1000LL)
#define MINUTES MINUTE
#define HOUR *(3600LL*1000LL)
#define HOURS HOUR
#define TASK_SLEEP(timeout) this->sleep.Start(timeout); TASK_WAIT_FOR(&this->sleep);
#define TASK_PERIODICALLY(period, action) for (;;) {this->sleep.Start(period); action; TASK_WAIT_FOR(&this->sleep);}
#define TASK_POLL(action) for(;;) {action; TASK_YIELD();}
#define TASK_WAIT_CONDITION(callback) TASK_WAIT_FOR(callback)
#define TASK_WAIT_SIGNAL(hSignal) TASK_WAIT_FOR(hSignal)
#define TASK_SET_SIGNAL(hSignal) hSignal->Set()
#define TASK_WAIT_VALUE(hValueHolder, variable) TASK_WAIT_FOR(hValueHolder); variable = hValueHolder->Get();
#define TASK_SET_VALUE(hValueHolder, value) hValueHolder->Set(value);
DEFINE_TELEMETRY(PowerMonitorRecord)
{
u16 Value;
};
TASK_BEGIN(PowerMonitorTask, {})
TASK_PERIODICALLY(5 SECONDS,
telemetry << CreateRecord(GetState())
);
TASK_BODY_END
PowerMonitorRecord GetState()
{
return PowerMonitorRecord{ 0 };
}
TASK_CLASS_END
Да, макросы, но результат явно менее страшный, чем кодирование руками конечного автомата.
С другой стороны, так тоже ничего, понятно.
bool TaskFunc(int &state)
{
switch (state)
{
case 0: // начальное
//
state = __LINE__; return false; case __LINE__: // в одну строку.
default:
state = -1;
return true;
}
}
Там выше есть строчка:
#define TASK_WAIT_FOR(Object) this->WaitFor(Object); this->state = __LINE__; return false; case __LINE__:
Так вот, WaitFor указывает планировщику, что таска заблокирована на указанном объекте, после чего сохраняется состояние. Выполнение с точки case __LINE__: начнётся после того, как объект станет сигнальным. Это чем-то напоминает дескрипторы, на которых выполняются блокирующие вызовы в операционке.
Я, если и начну делать что-то дальше, сделаю на ООП плюсово и наследованием от класса SmartTask, который будет дёргать методы порождённого класса, а SmartOS :) будет по списку бегать таких тасков.
Вернее оно у Вас и реализовано, но поскольку это микроконтроллер, когда я писал для него, казалось целесообразнее делать наиболее понятно, чтобы все сразу на виду. Вот как-то так, или здесь.
Но, наверное, это дело вкуса.
кон
еще можно посмотреть вот сюда и вот сюда
void loop() { q=0;
while (q<80000) { // здесь можем увеличивать или уменьшать паузу, меняя значение q меньше какого-то численного значения
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
q++;
}
q=0;
while (q<80000) {
digitalWrite(LED_BUILTIN, LOW);
q++; } // turn the LED off by making the voltage LOW
}
2) float
p/s
Я думал что это банальное решение без delay через while, но что-то поисковик такое решение через while не дает мне. Я вроде читал про такое в первых мануалах про Ардуино.
Вот полный код:
float q=0;
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(9600);
}
void loop() { q=0;
while (q<80000) {// здесь можем увеличивать или уменьшать паузу, меняя значение q меньше какого-то численного значения
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
q++;
}
q=0;
while (q<80000) { // wait for a second
digitalWrite(LED_BUILTIN, LOW);
q++; } // turn the LED off by making the voltage LOW
}
Потом выражаете свое фи, хотя код мегапрост и работает с четким миганием и без delay, что мне и нужно было.
Во-первых, использование инкремента для вещественных типов не рекомендуется, поскольку возможны ситуации, когда «проинкрементированное» значение будет равно исходному.
Во-вторых, Ваш код чувствителен к частоте контроллера, к интенсивности возникновения прерываний, к реализации операции digitalWrite (теоретически constexpr-эквивалент может быть выполнен за 1 такт, но если его пока нет, это не значит что он не может появиться).
И да, глобальная переменная с неадекватным именем вырвала глаза.
www.kit-e.ru/articles/circuit/2007_3_180.php
Замена delay() для неблокирующих задержек в Arduino IDE