shTaskManager - легкий диспетчер задач для Arduino

shTaskManager - легкий диспетчер задач для Arduino, основанный на системном таймере millis().

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

Возможности библиотеки:

  • Простое создание списка задач;
  • Добавление новых и удаление существующих задач;
  • Запуск, перезапуск, остановка и получение статуса любой задачи по ее идентификатору;
  • Получение времени до срабатывания как ближайшей, так и конкретной задачи;
  • Получение количества задач в списке;

Ссылка на GitHiub - GitHub - VAleSh-Soft/shTaskManager: Легкий диспетчер задач для Ардуино, построенный на millis()

Ну, а как бы удобнее иметь диспетчер, вместо обычного перебора в лупе? Чем? Насколько?

Во-первых - не загроможденный кодом луп.
Во-вторых - каждая задача управляема, для каждой можно:

  • запускать и останавливать;
  • получать информацию о текущем статусе;
  • менять интервал срабатывания:

Ну а нужно ли это кому - каждый сам решает. Мне было нужно, потому и замутил :slightly_smiling_face:

“Во первых - это красиво.” Извини.)

  • Я ведь тоже могу всё оформить функциями и не загромождать кодом луп?
  • И также получать статус
  • Полезность изменения интервала срабатывания?

Один вызов tasks.tick() супротив вызова кучи функций.

Ну так тогда и для каждой задачи где-то этот статус сохранять нужно

Классический пример - блинк с неравномерным интервалом.
Или вот

пищалка для будильника
void runAlarmBuzzer()
{
  static byte n = 0;
  static byte k = 0;
  // "мелодия" пищалки: первая строка - частота, вторая строка - длительность
  static const PROGMEM uint32_t pick[2][8] = {
      {2000, 0, 2000, 0, 2000, 0, 2000, 0},
      {70, 70, 70, 70, 70, 70, 70, 510}};

  // проверка статуса задачи пищалки, если не запущена, запустить и обнулить счетчики
  if (!tasks.getTaskState(alarm_buzzer))
  {
    tasks.startTask(alarm_buzzer);
    n = 0;
    k = 0;
  }
  else if (alarm.getAlarmState() != ALARM_YES)
  { // остановка пищалки, если будильник отключен (нажали кнопку)
    tasks.stopTask(alarm_buzzer);
    return;
  }

  // собственно, писк согласно текущего момента ))
  tone(BUZZER_PIN, pgm_read_dword(&pick[0][n]), pgm_read_dword(&pick[1][n]));
  // изменение интервала для задачи
  tasks.setTaskInterval(alarm_buzzer, pgm_read_dword(&pick[1][n]), true);
  if (++n >= 8)
  {
    n = 0;
    if (++k >= 60)
    { // остановка пищалки через заданное число секунд
      k = 0;
      tasks.stopTask(alarm_buzzer);
    }
  }
}

В общем, основная идея диспетчера - все задачи со всеми своими опциями в одном месте с единым управлением

ОК. Согласен. Всё в одном месте и не нужно лишних телодвижений.
Другое дело, когда нельзя развернуться. Но это “проблемы индейцев”, естественно.)

Луп:

void loop()
{
    if (Messages.Available()) Dispatch(Messages.GetMessage());
}

Дед, супротив твоих списков, что таймеров, что сообщений я ничего не имею. Кроме ограниченного количества :slightly_smiling_face:

На AVR мне ни разу больше 6 таймеров не потребовалось, нинаю, как на RP2040 масть пайдёть. А очередь сообщений у меня теперь кольцевым буфером сделана, что на Тини, что на меге, 16 сообщений глубиной. Думаю и для других контроллеров этого будет достат.кол.

1 лайк

У меня уже не раз за десяток переваливало. Конечно, можно что-то упростить, но упрощенное по любому придется переводить на миллис, так какой смысл заводить два списка? :slightly_smiling_face:

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


Зато празник от кота спасает.
На худой конец, 1 константу поменять в *.h файле и таймеров будет скока хошь (до 256), но принесёт ли это радость?

Ну смотри, реальная штука - на работе будильник, что пищит каждые полтора часа ночью, типа, пора на обход :slightly_smiling_face:

shHandle rtc_guard;               // опрос микросхемы RTC по таймеру, чтобы не дергать ее откуда попало; можно и обойтись, но так упорядоченнее
shHandle blink_timer;             // блинк, используется всем, что мигает; можно тоже обойтись, но в каждом случае таки придется усложнять код;
shHandle return_to_default_mode;  // таймер автовозврата в режим показа времени из любого режима настройки; тут без вариантов
shHandle set_time_mode;           // режим настройки времени; тут тоже без вариантов
shHandle display_guard;           // вывод данных на экран; проверяет буфер экрана на изменения и выводит только если что изменилось; можно обойтись? можно, но тогда вывод придется дергать отовсюду, где информация выводится
shHandle alarm_guard;             // отслеживание будильника; без вариантов
shHandle alarm_buzzer;            // пищалка будильника; без вариантов
shHandle show_alarm_setting_mode; // режим показа настроек будильника; без вариантов 
#ifdef USE_TEMP_DATA
shHandle show_temp_mode; // режим показа температуры; ежели у нас есть вывод температуры, то без вариантов
#ifdef USE_DS18B20
shHandle ds18b20_guard; // опрос датчика DS18b20; ежели используется этот датчик, то тоже без вариантов
#endif
#endif
#ifdef USE_LIGHT_SENSOR
shHandle light_sensor_guard; // отслеживание показаний датчика света; возможно, можно обойтись, но так регулировать яркость экрана проще
#endif
#ifdef USE_SET_BRIGHTNESS_MODE
shHandle set_brightness_mode; // режим настройки яркости экрана; без вариантов
#endif

Итого до 12 задач.

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

Да, я сам отказался от выполнения привязанной к таймеру функции в контексте прерывания. Теперь у меня таймер при сработке просто кладет в очередь сообщение msg_TimerEnd и свой номер, а диспетчер потом сам разберется, что ему с этим делать. А сенсоры у меня в loop опрашиваются, без таймеров, каждый сенсор сам знает, когда его опрашивали в последний раз, и просто не читается реально, если интервал опроса меньше заданного. А когда пройдет заданное время, то сенсор реально читается, и если его значение изменилось, в очередь шлёцца сообщение об этом.

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

Маленький классик, прародитель всех сенсоров, короче всего, чьё измеряемое значение может измениться, даже RTC

Спойлер
class TCustomSensor : public TClass {
protected:
	
	uint32_t	FLastReadTime;		// время (millis()), когда сенсор читали последний раз 
	uint16_t	FReadInterval;		// минимальный интервал физического чтения сенсора

	virtual void init(void) = 0;    // если нужна инициализация
	
	virtual void internalRead(void)  = 0;  // функция реального чтения сенсора

	TCustomSensor(TCustomSensor&) = delete;
	TCustomSensor(TCustomSensor&&) = delete;

public:

	TCustomSensor() {
		FError = false;
		FInitNeed = true;
		SetReadInterval(1000);
		classname = F("CustomSensor");
	}

	void SetReadInterval(const uint16_t ANewReadInterval) {
		FReadInterval = ANewReadInterval;
		FLastReadTime = 0 - FReadInterval - 1;
	}


	void Read(void) {
		if (isNeedInit()) init();  // если нужна начальная инициализация, вместо всяких begin(), init() в setup()

		if (isError())  return;    // если сенсор в состоянии ошибки, читать не надо, мошт его спиздили

		uint32_t now = millis();   // берем текущее время
		if (now - FLastReadTime < FReadInterval) return; // если читали недавно, выходим

		FLastReadTime = now; // иначе, запоминаем время

		internalRead();      // и реально читаем

	}
};
1 лайк

Утащил в запасник :slightly_smiling_face:

Вот как это выглядит потом у Далласа

Спойлер
	void internalRead() override {

		if (ReadData()) {  // Здесь читаются 8 байт с CRC из Далласа (true, если CRC сошлась)

			int8_t temp = (FDallasMemory[1] << 4) | (FDallasMemory[0] >> 4); // берем тенпературу

			if (temp == FTemperature) return;  // если она равна предыдущей, уходим, ничо не поменялось

			FTemperature = temp; // запоминаем последнее прочитанное
			PostMessage(msg_TemperatureChanged, FTemperature); // шлём привет диспеччеру
		}
		else 	
			Error(err_OneWire_CRCError); // либо шлем ошибку для того, чтобы перечитать правильно

	}

Без толку, у него еще папаша есть, TClass, в нем Error реализован, InitNeed и прочая х-ня. Наследование, матьиё

Тут сам принцип важен, а с остальным разберемся

Не в тему, но коль уж проскочил Dallas, самому часто приходится разбивать операции для DS18b20 по шагам, с тем что бы не было блокировки в лупе. В принципе это не важно, когда опрашиваешь раз в…, но может быть важно для других процессов, типа индикации и т.п.