Очередь сообщений. Упрощаем себе loop()

Тема в разделе "Arduino & Shields", создана пользователем DetSimen, 22 июн 2017.

  1. DetSimen

    DetSimen Guest

  2. mcureenab

    mcureenab Гуру

    Зачем динамическое выделение памяти?
    Почему бы не сделать очередь на кольцевом буфере?
     
  3. DetSimen

    DetSimen Guest

    привычка. :)
     
  4. DetSimen

    DetSimen Guest

    если определить DDEBUG в проекте, можно посмотреть нет ли утечки прямо в Serial-e
     
  5. AlexU

    AlexU Гуру

    Идея не новая (хотя в контексте AVR может и новая), а реализация хромает и ещё как.
    Например, функция 'void TMessageList:: DeleteFirst(void)' сначала идёт запрет прерываний, а в конце прерывания разрешаются. С чего бы вдруг? Перед получением очередного сообщения я сознательно запретил прерывания и разрешать их собираюсь в нужный мне момент. И тут вдруг "на ... получи ...".
    Например2, функция 'bool TMessageList::Add(PMessage msg)' в случае нехватки места в очереди освобождает память по указателю 'msg'. Я создал переменную типа TMessage, передал указатель на неё в функцию, места в очереди нет, "на ... получи второй раз ..." попытка освобождения памяти, на которую я рассчитываю в будущем или которая была выделена статически на этапе компиляции. То же относится к освобождению памяти в функции 'TMessage TMessageList::GetMessage()'. Раз Вы разрешили добавлять сообщения по указателю, с какого вдруг Вы высвобождаете память по этим указателям? Вы же не можете знать того как и для чего выделялась память под сообщения.

    В контексте AVR надо бы отвыкать. Памяти мало и она распределяется заранее на этапе проектирования устройства. А все эти 'malloc', 'free', 'new', 'delete' при не аккуратном обращении приведут либо к утечкам памяти, либо к её дефрагментации, что ещё хуже (утечки легче отловить).
     
  6. Tomasina

    Tomasina Сушитель лампочек Модератор

    интересно. Жду обещанного продолжения, с примерами.

    Вот еще нарыл у себя в закладках:

    чтение 2-х датчиков в фоновом режиме, на базе прерывания от WDT:
    http://arduino.ru/forum/programmirovanie/pochistil-sketch-primera-raboty-s-ds18b20-iz-bibly-oneware
    Многозадачность:
    http://robotosha.ru/arduino/multitasking-and-interrupts-arduino.html
    http://forum.amperka.ru/threads/Потоки-в-avr.9856
    https://www.hackster.io/feilipu/using-freertos-multi-tasking-in-arduino-ebc3cc
    Вытесняющая многозадачность для Arduino
    http://robocraft.ru/blog/985.html
    leOS — многозадачное ядро для Arduino
    https://geektimes.ru/post/255770/
    http://www.leonardomiliani.com/en/2012/leos-un-semplice-so-per-arduino/
     
    Последнее редактирование: 22 июн 2017
  7. DetSimen

    DetSimen Guest

    просто, если прерывание придет в DeleteFirst() существует большая вероятность, что Items сдвинется неправильно, ибо в функциях-обработчиках никто не утруждает себя сохранением регистров, и по возвращении адреса уплывут. Но Вы, конечно правы, в DeleteFirst надо просто запоминать и, на выходе, восстанавливать SREG.
    Думаю, TMessage вручную создавать не надо, есть функции Add у очереди. Но здесь можно и есть над чем подумать.
     
  8. sslobodyan

    sslobodyan Гик

    Что-то я не сильно понял нужность сего механизма. Получилась сильно обрезанная кооперативная ОС. С самым главным недостатком - любая задача, требующая сколь долгого выполнения, вешает всю систему. Каким образом вы сможете разбить долгоиграющую функцию на несколько мелких с передачей управления обработчикам следующих сообщений и возвратом к текущей функции? А если нет taskYield, то смысл городить очередь? Не проще ли тупой конечный автомат?
     
  9. DetSimen

    DetSimen Guest

    Это не претендует даже на сильно обрезанную кооперативную ОС. Это просто не сильно быстрые датчики и их обработчики
     
  10. Tomasina

    Tomasina Сушитель лампочек Модератор

    Ещё есть небольшой хук, в функции delay() предусмотрена возможность "быстрой" передачи управления, если время ожидания ещё не вышло. Что бы воспользоваться этим хуком нужно декларировать функцию:
    Код (C++):
    void yield(void)
    {
    // говорим библиотеке, что нужно отдать процессорное время другому потоку
    // так как текущему потоку пока делать нечего -- он ждёт пока пройдёт
    // время заданное при вызове функции delay()
      avr_thread_yield();
    }
    Хук этот не обязателен, но позволит более эффективно использовать процессорное время, т.к. "ждущие" потоки не будут его понапрасну использовать.

    Цитата отсюда.
     
  11. AlexU

    AlexU Гуру

    В моём случае нужность подобного механизма проявилась тогда, когда нужно было создать интерактивное устройство с кнопочками и дисплеем (текстовым типа 16x2). Реализовать что-то типа меню с применением анимации (бегущие строки, моргающий текст и т.п.) оказалось на много проще с применением сообщений и очереди с "ядром", которое эту очередь обслуживало. Конечно можно сделать и без сообщений, но с сообщениями получается гораздо проще и понятнее.
    Этот механизм может быть востребован в многопоточных решениях. Организация взаимодействия между потоками. Причём многопоточным решение может быть и без применения специальных реализаций типа 'avr-threads'. Например, опрос кнопок производится через прерывания -- первый поток, при помощи таймеров запускается некоторый функционал -- второй поток, и основной цикл, который занимается только отображением информации на дисплее -- третий поток. При нажатии кнопок обработчик прерывания кладёт в очередь сообщения о нажатых кнопках, основной цикл считывает сообщения из очереди и реагирует на них путём отображения на экране соответствующей информации. Как-то так...
    Автомат ни куда не девается. Просто сообщения его прекрасно дополняют.

    В общем вещь полезная...
     
  12. mcureenab

    mcureenab Гуру

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

    Не берусь утверждать в общем случае. У меня сложилось представление, что очередь длиннее одного слота для МК не практична. Либо МК обрабатывает события по мере их поступления (и тогда достаточно очереди из одного слота, или даже только флага, что очередное событие прилетело), либо есть вероятность переполнения даже длинной очереди.

    В ряде случаев блок МК взводит флаг прерывания. И если прерывания не используются, можно просто читать этот флаг и значения регистров с ним связанные. По сути те же сообщения. И экономия оперативной памяти.
     
  13. sslobodyan

    sslobodyan Гик

    О чем я и говорю. На маленьких проектах проще циклически перебирать все обработчики подряд. А внутри каждый обработчик сначала проверяет состояние флагов, которые устанавливаются другими обработчиками либо прерываниями и работает либо завершается. А на больших проектах нужна настоящая ОС. А здесь у вас идет передача одного параметра, причем для всех обработчиков одинаково длинного. Перерасход ресурсов. ИМХО, как прикладная задача - очень даже нормально, но как реально для мк - сомнительно.
     
  14. DetSimen

    DetSimen Guest

    А если события асинхронны? Клавиатуру надо, допустим, опрашивать 5 раз в секунду, а датчик влажности почвы - раз в полчаса, быстрее просто не нужно. зачем я буду в loop() циклически перебирать всё? Наерна, проще дождаться сообщения. IMHO
     
  15. mcureenab

    mcureenab Гуру

    Дождаться проще. Только как сообщение в очереди появится конкретно через 5 сек или 30 минут?

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

    DetSimen Guest

    Дак ведь а таймеры жеж на што? https://github.com/DetSimen/Arduino-

    Вот надо мне, чтоб двоеточие в часах мигало, делаю в сетап

    TTimerList.Add(tmrColon,500) - 2 раза в секунду (500 мс)

    void tmrColon(void)
    {
    MessageList->Add(msg_PrintColon,0,0);
    }

    в loop()

    case msg_PrintColon: DoPrintColon(); break;

    ну и сопсно

    void DoPrintColon(void)
    {
    bool showcolon = (sec & 1);
    char cln = showcolon ? ':' : ' ';
    lcd.setCursor(13,0);
    lcd.print(cln);
    }

    и ведь работает же
     
    mcureenab нравится это.
  17. sslobodyan

    sslobodyan Гик

    А я делаю проще.
    Код (C++):
    uint32_t tim_point, tim_button;
    #define TIM_POINT_MS 500
    #define TIM_BUTTON_MS 30

    void setup() {
        tim_button = millis();
        tim_point = millis();
    }

    void point_handler() {
        if (tim_point & (millis() > tim_point) ) {
            tim_point = millis() + TIM_POINT_MS;
            // переключаю мигалку или другая работа
        }
    }

    void button_handler() {
        if (tim_button & (millis() > tim_button) ) {
            tim_button = millis() + TIM_BUTTON_MS;
            // сканирую кнопки или другая работа
        }
    }

    loop() {
        point_handler();
        button_handler();
    }
    Накладных расходов минимум. Внутри обработчика делаете проверку любых своих флагов. Вызов обработчиков по очереди, но с индивидуальным интервалом.
     
  18. SergeiL

    SergeiL Оракул Модератор

    А зачем побитовое "И" между результатом сравнения и переменной?
    Я понимаю, что маска нужна для защиты от переполнения.
     
    Последнее редактирование: 22 июн 2017
  19. sslobodyan

    sslobodyan Гик

    Для того, чтобы вдучимо читали, а не тупо копировали :) Конечно же &&
     
  20. SergeiL

    SergeiL Оракул Модератор

    Понятно. :)

    Но в таком случае вижу два проблемных момента:

    1) При приближении к максимальному значению переменной (ну почти все единицы во всех разрядах) после добавления константы, происходит переполнение, и взводятся только младшие разряды.
    Сразу срабатывает условие.
    Потом все повторяется и опять сразу срабатывает условие.

    2) Это если в момент когда вычисляется tim_point = millis() + TIM_POINT_MS в tim_point будет занесен ноль. Вероятность низкая, но вполне может быть. Тогда в обработчик не попадем никогда.

    Как Вы боретесь с переполнением?