Планировщик для микроконтроллера

14/06/2014 - 10:00 Владимир Шибанов

Введение

Вопреки расхожему мнению, применение планировщиков/диспетчеров позволяет значительно ускорить разработку приложений, затратив при этом совсем немного памяти. А, как известно, время разработчика дороже последней. Средний диспетчер занимает около 1 кБ flash. Это совсем немного, учитывая те возможности, которые он предоставляет.

Сделать свои программы более организованными мне, простому любителю, хотелось достаточно давно. Я опробовал на реальных устройствах событийную систему на таблице, диспетчер ДиХальта и планировщик. Последний меньше всего мне понравился. При использовании его "как есть" он, на мой взгляд, мало пригоден для создания приложений посложнее мигания светодиодами. Но у него есть один плюс, который меня покорил. Он очень, очень простой. А значит быстрый и надежный. Поэтому его я и взял за основу.

Итак, что же, как минимум, должно быть у диспетчера для полноценного его использования? На мой взгляд необходимо следующее:

1) Возможность однократного запуска задачи;
2) Возможность циклического запуска задачи с заданным периодом;
3) Возможность однократного или циклического отложенного запуска с заданной паузой;
4) Удаление задачи.

Перечисленного вполне достаточно для реализации самых разнообразных алгоритмов. При этом, как видим, ничего лишнего нет.

Доработка планировщика

Старый планировщик умеет запускать циклические задачи отложенно (или сразу). Это уже немало. Но чтобы реализовать весь желаемый функционал, пришлось изменить практически все его функции. Теперь по порядку.

Структура задачи и очередь задач остались неизменными:


typedef struct task
{
   void (*pFunc) (void); // указатель на функцию
   u16 delay; // задержка перед первым запуском задачи
   u16 period; // период запуска задачи
   u08 run; // флаг готовности задачи к запуску
}task;

volatile static task TaskArray[MAX_TASKS]; // очередь задач


Начиная с инициализации начинаются отличия. В исходном варианте очередь очищается путем обнуления параметров задач во всей очереди. Я же ввел дополнительную переменную «хвоста» очереди и просто обнуляю ее.


inline void RTOS_Init()
{
   TCCR0 |= (1<<CS01)|(1<<CS00); // прескалер - 64
   TIFR = (1<<TOV0); // очищаем флаг прерывания таймера Т0
   TIMSK |= (1<<TOIE0); // разрешаем прерывание по переполнению
   TIMER_COUNTER = 130; // загружаем начальное зн. в счетный регистр

   arrayTail = 0; // "хвост" в 0
}


Постановщик в очередь. В старом планировщике это функция AddTask. При наличии свободного места в очереди она добавляет задачу, даже если такая уже есть в списке. Мне показалось это неудобным, поэтому задача добавляется только в том случае, если ее в списке нет. В противном случае обновляются ее параметры: пауза до выполнения и период. Поэтому функция теперь называется SetTask. Это логичней.


u08 i;

if(!taskFunc) return;

for(i = 0; i < arrayTail; i++) // поиск задачи в текущем списке
{
   if(TaskArray[i].pFunc == taskFunc) // если нашли, то обновляем переменные
   {
      DISABLE_INTERRUPT;

      TaskArray[i].delay = taskDelay;
      TaskArray[i].period = taskPeriod;
      TaskArray[i].run = 0;

      RESTORE_INTERRUPT;
      return; // обновив, выходим
   }
}

if (arrayTail < MAX_TASKS) // если такой задачи в списке нет
{ // и есть место,то добавляем
   DISABLE_INTERRUPT;

   TaskArray[arrayTail].pFunc = taskFunc;
   TaskArray[arrayTail].delay = taskDelay;
   TaskArray[arrayTail].period = taskPeriod;
   TaskArray[arrayTail].run = 0;

   arrayTail++; // увеличиваем "хвост"
   RESTORE_INTERRUPT;
}
}


Следующий шаг - удаление задачи. В старом планировщике такая функция есть, но по индексу в очереди. На практике неудобно, поэтому она была изменена. Теперь задачи удаляются по имени, причем удаление происходит не смещением всех последующих задач на шаг назад, а переносом последней задачи на место удаляемой. Это гораздо быстрее.


void RTOS_DeleteTask (void (*taskFunc)(void))
{
   u08 i,j;

   for (i=0; i<arrayTail; i++) // проходим по списку задач
   {
      if(TaskArray[i].pFunc == taskFunc) // если задача в списке найдена
      {

         DISABLE_INTERRUPT;
         if(i != (arrayTail - 1)) // переносим последнюю задачу
         { // на место удаляемой
            TaskArray[i] = TaskArray[arrayTail - 1];
         }
         arrayTail--; // уменьшаем указатель "хвоста"
         RESTORE_INTERRUPT;
         return;
      }
   }
}


Для реализации однократного запуска была изменена функция извлечения задачи из очереди. Если подошло время выполнения (флаг Run установлен), проверяется значение периода, если он равен 0, задача просто удаляется из очереди.


void RTOS_DispatchTask()
{
   u08 i;
   void (*function) (void);
   
   for (i=0; i<arrayTail; i++) // проходим по списку задач
   {
      if (TaskArray[i].run == 1) // если флаг на выполнение взведен,
      { // запоминаем задачу, т.к. во
         function = TaskArray[i].pFunc; // время выполнения может
         // измениться индекс
        if(TaskArray[i].period == 0)
        { // если период равен 0
           RTOS_DeleteTask(TaskArray[i].pFunc); // удаляем задачу из списка,
        }
        else
        {
           TaskArray[i].run = 0; // иначе снимаем флаг запуска
           if(!TaskArray[i].delay) // если задача не изменила задержку
           { // задаем ее
               TaskArray[i].delay = TaskArray[i].period-1;
           } // задача для себя может сделать паузу
        }
       
        (*function)(); // выполняем задачу
   }
}


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


ISR(RTOS_ISR)
{
   u08 i;

   TIMER_COUNTER = 130; // задаем начальное значение таймера

   for (i=0; i<arrayTail; i++) // проходим по списку задач
   {
      if (TaskArray[i].delay == 0) // если время до выполнения истекло
      TaskArray[i].run = 1; // взводим флаг запуска,
      else TaskArray[i].delay--; // иначе уменьшаем время
   }
}


Вот все, что касается доработок старого планировщика. Теперь использовать его уже не просто удобно, а приятно. Программа становится логичной, читабельной, простой.
Теперь несколько примеров использования.

Примеры использования планировщика

1) Запуск сканирования клавиатуры 100 раз в секунду


RTOS_SetTask(KeyScan, 0, 10); // период 10 мс, неотложенный запуск


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


// где-то в программе

// вывод на экран текущей температуры каждые 300 мс
RTOS_SetTask(FEN_ViewCurrentTemp, 0, 300); 
...
...

FEN_TempUp()
{
...
// пауза до вывода текущей температуры 1000 мс
RTOS_SetTask(FEN_ViewCurrentTemp, 1000, 300);

// отображение заданной температуры (1 раз) 
RTOS_SetTask(FEN_ViewTargetTemp, 0, 0); 
...
}


3) Зажигаем светодиод на 5 секунд:


RTOS_SetTask(LED_ON, 0, 0); // зажигаем
RTOS_SetTask(LED_OFF, 5000, 0); // гасим через 5 секунд


4) Смена цвета моргания светодиода при смене режима работы:


// где-то в программе
mode = GREEN; // установили режим
RTOS_SetTask(Blink_green, 0, 500); // задали цвет моргания

// изменение режима
mode = RED;
RTOS_DeleteTask(Blink_green); // выключили зеленый цвет моргания
RTOS_SetTask(Blink_red, 0, 500); // задали красный цвет моргания

Файлы

scheduler.rar 


Автор статьи: Владимир Шибанов. 

Comments   

# Peter 2014-06-15 06:51
Гм... А в так называемом СтаромПланировщ ике регистр TIFR очищался правильно. Как видим, новое не есть улучшенное)
# demiurg 2014-06-15 07:06
Все правильно. Скобки ставим, чтобы наверняка. Потому как, если скобки не ставить, нужно помнить приоритет операций. Я, например, не желаю забивать голову всякой херней, сидеть, вспоминать, а какой там порядок приоритета? Проще скобки поставить.
# Peter 2014-06-15 07:15
Да не в скобках дело. TIFR у многих AVR не любит операций |=
# demiurg 2014-06-15 07:21
Раз вы так сейчас сказали, значит, вы плохо знаете архитектуру AVR... Подумайте, прежде чем ответить.
# demiurg 2014-06-15 07:26
Если вы не любите кошек, значит вы не умеете их готовить...
# Peter 2014-06-15 07:27
Ууууу. Тогда советую Вам повнимательнее пояитать даташит хотя бы на весьма распрочтраненну ю мегу128. А именно об недопустимости применять инструкцию SBI для сброса регистров флагов, очищающихся записью 1. Удачи!
# demiurg 2014-06-15 07:33
Обоснуйте. Конкретные примеры в студию.
# Peter 2014-06-15 07:43
Подозреваю, что мои примеры Вас не убедят. Вместо благодарности за науку будете ругать. Поэтому я дал Вам точную наводку где и по каким словам искать. RTFM, уважаемый. Там все описано с объяснениями.
# demiurg 2014-06-15 07:47
Э-э-э, нет! Вы ничего не подозревайте. Сказали, отвечайте. А то, получается, слились вы, батенька... Если я увижу что-то новое, то только поблагодарю вас. За что же тут ругать?
# Peter 2014-06-15 07:55
Вы сначала почитайте в даташЫте на мегу128 все по ключевому слову SBI. Ладно? А потом пишите сюда)).
# demiurg 2014-06-15 08:04
Так что вы этим хотели сказать, что sbi, cbi не работает для этого регистра? Так нормальный компилер си будет использовать команды соответствующие этому адресному пространству. И? Так что вы хотели сказать-то, уважаемый?
# Peter 2014-06-15 08:23
Она очень даже работает)) Так хорошо работает эта SBI, что может сбросить уже стоЯщие там чужие флаги. Простой пример Налюбовавшись Вашим обращением к TIFR, пользователь так же будет применять операцию |= где-нибудь в критической секции. И тем самым сбросит появившийся только что флаг таймера 0 от вашего шедулера. Вы этого хотите? Уверен что нет.
# demiurg 2014-06-15 08:27
Вот это уже речь не мальчика но мужа... :) Так и пишите, как сейчас написали.
А то любит, не любит. Мы что тут, на ромашке гадаем?
# Peter 2014-06-15 08:45
Рад что Вы наконец поняли. А дело свое надо именно ЛЮБИТЬ. Тогда и предмет работы будет казаться живым. Удачи!
# demiurg 2014-06-15 08:53
Как бы вы не одушевляли МК, камнем он и остается. И нужно знать, как заставить этот камень правильно шевелиться. Согласен, я не сразу вкупился в чем дело. Бегло глянул, вроде отличия только в скобках. Ну и успокоился. А то что вы рассказали, я и так знал.
Ну и удивился формулировке "не любит". И наличие команд sbi cbi. Делать новый проект, чтобы убедиться, какие команды будут подставлены мне лениво. Все равно я для этих регистров делаю прямую запись. Нули-то все равно бесполезно пихать, не так ли? В крайнем случаю лишний раз гляну в даташит, чтобы уточнить как запустить периферию.
# Peter 2014-06-15 08:06
Почитали? А теперь я готов ответить на Ваши вопросы, если они остались.
# demiurg 2014-06-15 08:11
1 - Что вы нашли противозаконног о? Отсутствие скобок в предыдущей статье? Где команды sbi, cbi у автора?
2 - Сама ваша формулировка "МК не любит, видите ли |=" А с чего вы взяли, что будут обязательно команды sbi, cbi ?!
# demiurg 2014-06-15 08:14
Тьфу, блин, кажись сообразил. Вы имели в виду, чтение модификация запись? Это что ли? Так сразу бы и сказали.
А то МК не любит... Не "не любит"!!! Это формулировка начинающего любителя, который не знает чего ждать от МК и программы...
# Peter 2014-06-15 08:37
Я пейсал что TIFR не любит... Да, новые МК от этого эффекта защищены.
# Peter 2014-06-15 08:32
Да что с Вами? Операция |= по установке одного бита регистра в допустимом адресном пространстве пояти наверняка заставит компилятор поставить инструкцию SBI
# Владимир 2014-06-18 14:09
Скобки я ставлю только для того, чтобы стоящие рядом команды установки и снятия (тут то скобки обязательны) бита были красиво выровнены. Люблю аккуратно оформленный код)
# Владимир 2014-06-18 14:21
По большому счету, диспетчер и есть флаговый автомат, просто более структурированн ый
# Владимир 2014-06-18 14:22
Не туда отправил, ну да ладно
# Peter 2014-06-16 07:03
Ну что ж, с очисткой флагов регистра TIFR разобрались. Теперь, перед анализом багов собственно планировщика, надо научиться правильно запускать таймер. Пользователь, поверив "продвинутому разработчику планировщика", возьмет за основу этот метод, и так и будет запускать свои таймеры в своих задачах. И может огрести кучу траблов. Для исключения этого я просто перечислю шаги запуска таймера - на примере AVR, но, наверно, и независимо от МК.
1. Остановить таймер.
2. Задать в регистрах режим работы таймера (для AVR это TCCRA, TCCRC, если они есть. А если нет - то пропустить этот шаг). Причем делать это явным присваиванием (а не пресловутой операцией |= ). Регистр, где запускается таймер - не трогать.
3. Инициализовать счетный регистр TCNTn.
4. Инициализовать регистр(ы) сравнения OCCRn
5. В регистре TIFR cбросить флаги относящиеся к таймеру-пОциенту.
6. Разрешить нужные прерывания таймера, запрещая ненужные.
7. Запустить таймер, одновременно до-задавая режим его работы - для AVR это TCCRnB(если он есть) или TCCRn. Причем тоже операцией простого присваивания, а не |=.

После такой процедуры таймер не подведет.
Готов обсудить.
# demiurg 2014-06-16 07:58
Трабла есть, если используется системный тик. Скажем, 1 мс. У меня сейчас сделано так:
Массив из нужного кол-ва таймеров. Структура массива: статусный байт, 16-битный счетчик. Прерывание настроено на 1 мс. В обработчике прерывания только перезапуск таймера, для точного отбивания по 1 мс. И установка флага. В основной программе вызывается Service_Timers. Зашли, проверили флаг, не установлен, вышли. Установлен, пробежались по массиву, если в статусном байте установлен флаг TMR_UNLOCK_FLG, декремент 16-битного счетчика. Обработка следующего таймера. Если флаг не установлен, обработка следующего таймера. Время вышло (0), установка флага TIME_OUT_FLG.
# demiurg 2014-06-16 07:59
Этой таймерной службы мне хватает практически для всех проектов. Точность - +- 1-10 мкс.

Но есть но: представим ситуацию. Нам нужно запустить задачу на 10 мс. И установка таймера задача произошла перед прерыванием. В итоге получится не 10 мс, а 9 с небольшим. Но так как я эти таймеры использую в задачах, где 10, даже 100 мкс погоды не играют. Абсолютно. Ну будет разброс в мкс обработки матричной клавиатуры, или обновление символьного ЖКИ. Ну и хрен на него, на это расброс.

Зато я получил простую в использовании таймерную службу. Максимальное время 65535 мс. Если мне требуются большие отрезки времени, в модулях использую дополнительные счетчики. Скажем, счет каждые 100 мс.

Дальше планирую допилить, чтобы был флаг инкремент-декре мент. И пауза таймеров. Например, это потребуется в следующих ситуациях. Есть оборудование с режимом "Пауза". Вот в этих случаях и требуется наличие паузы в таймерах.
# demiurg 2014-06-16 08:00
http://pastebin.com/4Vz96ASs
# demiurg 2014-06-16 08:00
delete
# demiurg 2014-06-16 08:04
delete
# Peter 2014-06-17 06:42
Вот мы дошли и до анализа потенциальной уязвимости "улучшенного планировщика". Кто мне объяснит, что будет, если вызываемая в фоновом режиме функция DispatchTask() прервётся от таймера во время доступа к двухбайтовой переменной TaskArray.delay?
Да, я о проблеме атомарности, если че))
# demiurg 2014-06-17 07:28
В таких планировщиках не только проблема атомарности. Взять, к примеру диспетчер DI HALT-а. В его диспетчере, в начальной версии, на асме (в сишной я еще не копался, да и нет желания) два момента. 1. Таймерная служба работает в прерывании. 2. Дамоклов меч. Представим ситуацию, мы на таком диспетчере написали программу для станка. В очереди таймеров болтается задача на исполнение. И тут бац, аварийная ситуация. Ну ладно, встали в соответствующий режим, а из прерывания таймерная служба выстрелила задачу на исполнение. Потому и назвал дамоклов меч. И вот как теперь шерстить очередь на предмет удаления задач? Если мы даже не знаем, что можно удалять, а что нельзя. Мало ли какие моменты, пусть и аварийная ситуация.
На данный момент мой опыт: пока всякие диспетчеры мне ни разу не понадобились.
void main (void)
{
func_1 ();
func_2 ();
func_3 ();
}
Все! Главные принципы: модули, автоматное программировани е. Каждая задача разбивается так, чтобы не было долгих циклов. К примеру, системный тик 1 мс. И каждый пробег main должен с запасом уложиться в 1 мс.
Ну как-то так...
# САБ 2014-06-17 11:17
Quoting demiurg:
На данный момент мой опыт: пока всякие диспетчеры мне ни разу не понадобились.
На данный момент мне ни разу не понадобились: FPGA, многослойные платы, долбежный станок, картофелеубороч ный комбайн, женские прокладки и много чего еще. Предлагаете доказывать всем их ненужность?
# demiurg 2014-06-17 11:28
Нет. Всему свое применение. Только прежде чем лепить что-то свое, нужно прогнать как следует. Выявить недостатки.
И не диспетчеры нужно лепить, а научиться взаимодействию модулей.
Потому что эти материалы читают начинающие. Которые вроде как нашли решение, как же, ляпаешь как попало задачи, и они блин работают. А если таким начинающим придет в голову применить эти диспетчеры и потом вляпываются по самое не балуй...
# Kodex 2014-06-17 14:12
Я вот сегодня доделал для себя версию этого "старого планировщика". Добавил два уровня приоритетов. Массив задачь такой же как здесь. Задачи готовые для выполнения суются в один из двух массивов, в зависимости от приоритета. После нек. количества запуска высокоприоритет ных задачь обязятельно запускается одна низкоприоритетн ая. Одно не пойму. Нахрена мне все это...
# JoJo 2014-06-17 14:19
Спортивный интерес .. только и всего
# Владимир 2014-06-18 12:21
Нда, не ожидал такой дискуссии) Постараюсь ответить на все вопросы:
1) по поводу |=. Установка флага в регистре TIFR у меня происходит именно так, архив чуть устаревший отправил Паше. Уже попросил его поправить статью. Я не силен в ассемблере, но команда TIFR |= (1
# Владимир 2014-06-18 12:23
плин...
1) по поводу |=. Установка флага в регистре TIFR у меня происходит именно так, архив чуть устаревший отправил Паше. Уже попросил его поправить статью. Я не силен в ассемблере, но команда TIFR |= (1 shl TOV0); транслируется в 3 асмкоманды: IN, ORI, OUT. Ни разу у меня не было конфликтов, связанных с установкой флага с помощью |=. Возможно потому, что в инициализациях я не разрешаю глобальные прерывания. Делаю это уже перед основным циклом.
# _Артём_ 2014-06-18 13:57
Quoting Владимир:
транслируется в 3 асмкоманды: IN, ORI, OUT.

Транслируется правильно, но сам алгоритм неправильный.

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

Видимо для ваших программ это не было важно.
Проблема может возникнуть в старых мегах - регистр TIFR зашарен между некоторыми таймерами. Поэтому в результате операции |= могут быть сброшены все флаги, а не один. В результате можно потерять прерывания или не отреагировать на нужный флаг.
И для сброса бита в TIFR достаточно записать в нужную позицию 1 - на асме это две команды:
Code:
ldi reg, 1<<BIT_POS
out TIFR, reg
# Владимир 2014-06-18 14:29
Я использовал только меги8, 16, 32. Проблем не было. Как тогда правильно будет установить этот бит на С? Или только вставки делать?
# JoJo 2014-06-18 14:41
Вот так
Code:TIFR = (1<<TOV0);
Без ИЛИ.
# _Артём_ 2014-06-18 14:52
Quoting Владимир:
Я использовал только меги8, 16, 32. Проблем не было.

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

Quoting Владимир:
Как тогда правильно будет установить этот бит на С? Или только вставки делать?

Code:
TIFR=1<<TOV0;
# Владимир 2014-06-18 15:02
Ну так в статье тогда все правильно, и в архиве тоже! Свою рабочую версию я исправил. Но все равно, учту на будущее. Спасибо!
# Владимир 2014-06-18 12:23
2) Применение подобных планировщиков никогда не позволит соблюсти абсолютную временную точность выполнения задач. Погрешность может составлять до одного системного тика. И в этом нет ничего страшного, по крайней мере в большинстве приложений.
# Владимир 2014-06-18 12:24
3) Атомарный доступ к переменной delay тут не обязателен. Уже говорилось в статье про исходный планировщик, что все готовые к запуску задачи (я называю это сценарий) должны уложиться в системный тик. Это довольно важный момент, который надо учитывать, если задачи требуют больших вычислительных мощностей.
# Peter 2014-06-18 15:55
Гм.... ну уложились все задачив один тик. И... что, будут стоять на месте и ждать прерывания? Нет, DispatchTask() так и будет вызываться в фоновой программе(см. пример работы старого планировщика) . И таким образом опасность прерывания внутри чтения/записи двухбайтового delay сохраняется. Надо делать критическую секцию.
# Владимир 2014-06-18 17:05
Да, будут стоять и ждать. К моменту, когда ожидается прерывание, все задачи с установленным флагом Run должны выполниться. В цикле только прочесывается очередь и проверяется этот флаг, который у всех (!) задач должен быть равен 0. Прерывание никак не может прервать обращение к переменной delay.
# Peter 2014-06-19 07:56
Тогда зачем критические секции в SetTask() и DeleteTask(), если прерывание не пересекается с работой ни одной из задач?
# Владимир 2014-06-19 13:06
Вы правы, незачем. Это следы моих экспериментов с минимальным временем системного тика. Можно убрать. Но еще раз подчеркну, очень важно, чтобы все активные задачи успевали выполняться за один тик!
# Peter 2014-06-21 05:33
Но тогда зачем вообще здесь прерырвания? Будет проще после прлхода всех задач опрашивать флаг таймера - тем более точность тайминга при этом не ухудшится по сравнению с данной реализацией. Зато будет короче, понятнее и безо всяких флагов состояния задач.
# Владимир 2014-06-21 06:18
Совершенствоват ь можно до бесконечности, главное вовремя остановиться. Меня устроила текущая реализация, поэтому я и остановился на ней.
Согласен с тем, о чем Вы говорите. Про опрос флага таймера в основном цикле я не думал. Возможно кто-нибудь захочет доработать этот диспетчер, добавить функциональност и. Например было бы неплохо добавить удаление задачи из списка через заданное время. Возможно сам доработаю, но уже не для avr, а для stm. Следующий проект будет на нем и планирую переводить туда этот диспетчер.
# Владимир 2014-06-18 12:24
4) Применение диспетчеров не панацея от всего. И везде их совать ни к чему. Я это прекрасно понимаю. Но, уже в сотый раз, да ладно, приведу пример моей горемычной паяльной станции. Там применен этот планировщик. И мне бы очень хотелось бы сравнить код, выполняющий те же функции, что у меня реализованы, только без диспетчера, так же легко читаемый, структурированн ый, логичный. Удобство сопровождения я теперь ценю гораздо выше, чем года 3-4 назад. Станция уже почти готова, скоро выложу.
# demiurg 2014-06-18 13:30
Не сочтите, что я вообще против диспетчера. Приведу пример. Не так давно я делал один проект. Блок управления, термоформовочны й станок. Типа СТА-150. Это: несколько датчиков, пульт управления (пульт оператора), символьный VFD, матричная клавиатура 4х4, выходы. В итоге получилось 3 порта дискретных входов, 1 порт клавиатура, 1 порт VFD, 2 порта выходы. Я взвесил и принял решение применить сдвиговые регистры для расширения ввода-вывода. 74HC165 и 74HC595. То есть, используется SPI. МК в DIP40 корпусе. Если что, вытащил из панельки, заменил.
Так вот никакого диспетчера у меня в помине нет. Main и список вызываемых функций. Все. А вы диспетчер в паяльную тсанцию засунули. Что у вас там? Кнопки, дисплей, неважно какой. Термодатчик, нагреватель паяльника. Ну управление питанием еще. Спрашивается, зачем в паяльной станции диспетчер? Лучше автоматным программировани ем бы занялись. И диспетчеры тогда вам еще не скоро понадобятся...
# Владимир 2014-06-18 13:57
Паяльник, фен, корпус с 5-ю кнопками, два 3-символьных 7-сегментника, несколько светодиодов. Дело не в том, сколько чего вешается на порты, а в том, как разрулить все необходимые режимы работы системы. Причем так, чтобы было удобно сопровождать. Я ни в коем случае не заставляю всех использовать диспетчеры! Можно реализовать это на них, можно на автоматах или полноценных ОСРВ. Дело вкуса. Но я сделал так, мне понравилось, и решил поделиться.
Еще могу добавить по автоматному программировани ю. 2 года назад я делал контроллер управления гальванической линией металлизации печатных плат, а это: 5 ванн с терморегулирова нием и контролем уровня, 3 струйных промывки, включение источников тока и барботажа, система перемещения заготовки (2 шаговых дигателя с концевиками), дисплей. Делал на автоматах. Сейчас я даже не хочу в тот код залазить. Так вот, к чему я это? А к тому, что для каждой задачи надо выбирать оптимальное решение!
# demiurg 2014-06-18 15:46
А писать надо нормально, чтобы можно было и потом в код заглядывать....

pastebin.com/Qdhdh370
# Владимир 2014-06-18 17:07
Это приходит с опытом.
# demiurg 2014-06-18 17:46
Вот именно. Я споткнулся на диспетчере DI HALT-а. Понимаете на тот момент у меня была проблема, взаимодействие модулей. Казалось бы, вот оно решение. Но облегчения не наступило. Но стало все наоборот. Вдобавок, я вовремя поймал засаду с дамокловым мечом. Примерно в то время на одном форуме мне указали на автоматное программировани е. Да и на асме я тогда застрял. Все слазить с него не хотел.
Когда у меня началось получаться с си, тогда я начал понимать, что мне говорили спецы на форуме.
Поэтому я и говорю, не стоит тратить время на диспетчеры, если еще не научился модульному программировани ю, автоматному программировани ю. И тогда тебе, возможно вообще не потребуются диспетчеры. RTOS - это уже иная тема...
# Владимир 2014-06-19 13:20
Мне тот диспетчер тоже не понравился, но по другим причинам. Там не получится так прозрачно запустить такую простую задачу, как однократное включение нагрузки, скажем, на 1 минуту (примеры 2 и 3 в статье). Ну и куча лишних переборов очередей мне не нравится.
По поводу автоматов, когда задач становится слишком много, сложно понять структуру программы. Я ничуть не умаляю достоинств такой организации, тем более диспетчер по своей сути автоматом и является. Точнее его более структурированн ым вариантом.
Quoting demiurg:
не стоит тратить время на диспетчеры, если еще не научился модульному программированию, автоматному программированию

Вот с этим абсолютно согласен.
# Владимир 2014-06-18 12:25
5) Создавать нужно то, что тебе нужно. А не делать что-то, а потом думать, а зачем?! Я лично писал диспетчер для конкретной задачи, формируя только то, что нужно мне. Результат мне понравился. Настолько, что решил опубликовать. Этот планировщик был первой моей публикацией в жизни.
6) По поводу тестирования. Я очень долго прогонял его и в симуляторе, и в железе. И он претерпел немало доработок. Но если кому-то захочется целенаправленно его потестировать на баги - я только за!)
# Peter 2014-06-18 16:04
Насчет атоиарности доступа к "длинным" переменным, исрользуемым в прерывании - необходимо выполнять простые правила:
а) если переменная лишь читается в прерывании, то достаточно в фоновой функции обеспечить атомарность при ее изменении.
б) если переменная изменяется в прерывании - то в фоновой функции нужно обеспечивать атомарность и при ее чтении и при ее изменении
# Kodex 2014-06-19 07:10
Владимир, можешь рассказать/пока зать что конкретно менял в коде при отладке?
# Владимир 2014-06-19 13:55
Всего так сразу и не вспомнишь...
Из значительного: функция DeleteTask сначала сдвигала очередь после удаляемой задачи на одну позицию. Потом один человек подсказал идею с перестановкой только последней задачи. На тот момент функция Dispatch выглядела так:
Code:
void RTOS_DispatchTask()
{
u08 i;
for (i=0; i<arrayTail; i++)
{
if (TaskArray.run == 1)
{
(TaskArray.pFunc)();
if(TaskArray.period == 0)
{
RTOS_DeleteTask(TaskArray.pFunc);
}
else
{
TaskArray.run = 0;
if(!TaskArray.delay)
{
TaskArray.delay = TaskArray.period-1;
}
}
}
}
}

Довольно быстро проявился глюк, когда задача удаляет саму себя, установка delay происходила для другой задачи, которая вставала на место удаляемой. Поэтому функция Dispatch обрела такой вид, как в статье.
# Владимир 2014-06-19 14:00
блин, форматирование не получается, все смещается на начало строки
# demiurg 2014-06-19 13:33
В строках RTOS_Set_Task и тд RTOS_ я бы убрал. В данном случае у нас простенький диспетчер. Set_Task Del_Task будет вполне достаточно и верно.
# Владимир 2014-06-19 14:10
Дело вкуса. Просто у меня так сложилось, что если модуль представляет собой самостоятельную структуру, я добавляю префикс. Только для того, чтобы видеть, из какого именно модуля вызывается функция: RTOS_, WH1602_, WG12864_, UART_ и т.д. Согласен, бросается в глаза. Но когда подключено много модулей, как-то понятнее выглядит программа.
# demiurg 2014-06-21 06:30
Quoting Peter:
Но тогда зачем вообще здесь прерырвания? Будет проще после прлхода всех задач опрашивать флаг таймера - тем более точность тайминга при этом не ухудшится по сравнению с данной реализацией.

Плюсую. Диспетчер DI HALT-а сразу же так и переделал. У него в прерывании было, я вынес в основной цикл. В прерывании взводится флаг. В основном цикле проверяется флаг, если установлен, сброс флага, прочесывание очереди. Точность в единицы, десятки мкс не критична.
Я решил все-таки собрать диспетчер. Позже выложу результат.
# Peter 2014-06-21 09:10
Да. А для точности тайминга и упрощения диспетчера лучше настроить таймер в режим СТС. Более того,раз это не прерывание, то там же в шедулере и сбрасывать вачдог.
# demiurg 2014-06-21 10:12
Нахер. На AVR никогда больше не вернусь к диспетчеру. Диспетчеры и RTOS хороши в системах, где код загружается с какого-либо носителя. На AVR код программы во флеш. И хоть через диспетчер, хоть в main крути функции, обвешав флагами или автоматом, суть не меняется. Только через диспетчер функции дольше переключаются.
Если использовать диспетчер, то нужно четко определить, что считать задачей? Почему-то в диспетчерах задачей считают моргалку светодиодом. Воткнули моргалку, она исправно моргает, вау! Диспетчер, задача, епт! Хорошо, а если много светодиодов? И разные световые эффекты. Как тогда быть? На автоматах легко это делается. Через диспетчер замаешься разруливать.
Вот если вытеснение, тогда уже другое дело. Простенький переключатель задач уже имеет право на существование. Но опять же, нужно четкое определение, что такое задача. Это не какая-то функция, которая запускается через определенное время.
Задача диспетчеров - распараллеливан ие задач.
Но опять же, если следовать некоторым правилам, то выясняется, что диспетчер опять не требуется.
Правила простые. Модульность. Большие функции разбиваются. Никаких долгих циклов в функциях. Зашли, выполнили код, вышли. Проверили условие, вышли. Все это делается на флагах, автоматах, какие-либо условия.
# Владимир 2014-06-21 17:38
Задача диспетчеров (планировщиков) - как раз таки запуск функции через заданное время. А распараллеливан ие задач - прерогатива ОСРВ. Не надо валить все в одну кучу. Для разных задач оптимальная организация программы определяется индивидуально. Тем более, кто Вам мешает в одну из задач диспетчера включить конечный автомат, реализующий эти самые световые эффекты. А потом задать период запуска этой задачи. Конечно, если вся программа состоит только из смены эффектов, то тогда да, смысла использования диспетчера нет. Но бывают ведь случаи и посложнее.
И при чем тут avr? Какая разница какая архитектура контроллера? Дело ведь не в ней, а в структурном представлении программы. Причем модульности последней никто не отменял!
На форумах иногда попадались личности, нежелающие использовать диспетчеры на avr, основываясь только на малом объеме памяти и быстродействии. Зато переходя на эти же stm первым делом изучали например freeRTOS. Только потому, что "так все делают". Появилась какая-то тенценция применения ОСРВ на ARM'ах, особенно, если присутствует мало-мальский графический интерфейс. На мой взгляд, совершенно необоснованная. Возможности диспетчеров позволяют в большинстве случаев обойтись без ОСРВ, а разница в быстродействии с автоматами измеряется микросекундами, не думаю, что сильно критично. Более того, могут быть ситуации, когда диспетчер окажется быстрее. Зависит от количества задач и частоты их запуска.
# demiurg 2014-06-21 18:04
Диспетчер не может быть быстрее по определению. В обычном цикле нет переключения. На данный момент я не вижу для себя смысла в диспетчере. Если мне требуется что-то запустить с какой-либо периодичностью, достаточно флагов или конечного автомата. Если мне потребуется запустить моргалку светодиодом, мне не нужно превращать это в отдельную задачу. На автоматах можно запустить что угодно как угодно, в диспетчере же замаешься разруливать.
Вы недавно правильно сказали, дело в опыте.
Я мучался с диспетчером, пока меня не ткнули в автоматное программировани е. Поверьте, как только я увидел преимущество КА, сразу же выкинул диспетчер.
# Владимир 2014-06-22 05:56
Это бесконечный спор.
Quoting demiurg:

Я мучался с диспетчером, пока меня не ткнули в автоматное программирование

Что не устраивало Вас в том диспетчере? Когда я сформировал свои требования и реализовал их, использование диспетчера приносит только удовольствие, никаких мучений!

Quoting demiurg:
На автоматах можно запустить что угодно как угодно, в диспетчере же замаешься разруливать.

Вобще не согласен, на своем диспетчере я делал подсветку для коньков с более чем 10-ю эффектами, в большинстве из них используются переливы. При этом есть ИК-синхронизаци я между ними + пара кнопок. Очень прозрачно все написано, даже после длительного перерыва можно быстро во всем разобраться. Я не спорю, можно сделать и на автоматах. В этом случае будет даже эффективней в плане производительно сти. Но прога и так простаивает большую часть времени. А удобство восприятия программы на диспетчере с функциями SetTask(func, delay, period) мне нравится гораздо больше, чем просто список функций, непонятно как и когда вызываемых. ИМХО.
# Владимир 2014-06-22 05:58
Quoting Владимир:
При этом есть ИК-синхронизация между ними

Между коньками естесственно
# Владимир 2014-06-22 06:01
del
# Владимир 2014-06-22 06:02
Вы наверно видели реализацию, описанную в статье. Там же я принавал, что это криво и что исправленный вариант (не привязанный к диспетчеру, можно и с автоматами использовать) в комментариях.
# Владимир 2014-06-22 06:03
ну что за... Про реализацию - это мой ответ на пост ниже
# demiurg 2014-06-21 18:08
Кстати, я увидел вашу реализацию с короткими и длинными нажатиями. Вы диспетчер натягиваете, как ту сову, там где надо и не надо... Дайте ваш прямой контакт. Я отправлю вам пример, как можно спокойненько сделать на КА. И удержание (длинное нажатие по вашему), и автоповтор, и много чего еще можно сделать. И нафиг диспетчер не нужен.
# demiurg 2014-06-21 18:19
И кстати, автор, посмотрите в сторону micro menu. В инете найдете. Только за основу возьмите оригинал, а не как там товарищ сделал. И вы тем более поймете, что диспетчер во многих случаях не нужен.
# Владимир 2014-06-22 06:07
Микроменю я использовал в контроллере управления гальваникой, у меня там было ЕМНИП 168 пунктов меню - параметры системы для 3-х технологических процессов. Диспетчера тогда еще не было.
# Владимир 2014-06-22 07:01
Quoting demiurg:
И вы тем более поймете, что диспетчер во многих случаях не нужен.

Так я с этим и не спорил. Даже наоборот, говорил что для каждой задачи организация программы выбирается индивидуально! Я не имею ничего против автоматов!
# demiurg 2014-06-22 08:47
Вот вам ссылка is.ifmo.ru/works/ Цикл статей Татарчевского. Для поиска. Татарчевский В. Применение SWITCH-технолог ии при разработке прикладного программного обеспечения для микроконтроллер ов. Часть 1
Ну и там много еще чего интересного.
К примеру у Татарчевского рассказано, как можно реализовать опрос клавиатуры.
Вкратце: перед вызовом автомата сканируются кнопки. В зависимости от того, какая клавиатура, пишется соответствующая функция. Матричная клавиатура, на одном порту, на нескольких портах. Дальше работает конечный автомат. Главная идея: сколько бы кнопок не было, они обрабатываются, как одна. И вам не нужно извращаться с диспетчером. Лично у меня опрос клавиатуры идет в модуле вывода информации на дисплей. Ну а если уж так приспичило использовать диспетчер, смотрите по программе, как вам запускать. Или как самостоятельную задачу, или в составе модуля.
pastebin.com/URMd767u
Идем дальше. Как дальше использовать. Отсканировали, распознали кнопки. В буфере у нас код кнопки.
У меня это сделано так:
В конечном автомате:
Code:
void Tui_Service_Main_Init (void)
{
set_menu (L_OUT_MODE);
}

void Tui_Service_Main_Out_Mode (void) { if (proc_menu_keys ()) return; // Если была обработка кнопок - выход.
# Владимир 2014-06-22 12:56
На контроллере гальваники я примерно так и делал, только железно, почти без дефайнов. Опыта маловато было. Была матрица 3х4.
Code:
key_code = 0;
SetPortKeys = 0x08;
PortKeys = 0x77;

if ((PinPortKeys&0x01)==0x00) key_code=HEAT_key;
if ((PinPortKeys&0x02)==0x00) key_code=PREV_key;
if ((PinPortKeys&0x04)==0x00) key_code=START_key;

SetPortKeys = 0x10;
PortKeys = 0x6F;

if ((PinPortKeys&0x01)==0x00) key_code=ESC_key;
if ((PinPortKeys&0x02)==0x00) key_code=SET_key;
if ((PinPortKeys&0x04)==0x00) key_code=ENTER_key;

SetPortKeys = 0x20;
PortKeys = 0x5F;

if ((PinPortKeys&0x01)==0x00) key_code=PAUSE_key;
if ((PinPortKeys&0x02)==0x00) key_code=NEXT_key;
if ((PinPortKeys&0x04)==0x00) key_code=STOP_key;

SetPortKeys = 0;
PortKeys = 0x40;

Потом в основном цикле проверял код кнопки.
В паяльной станции я сделал по другому, т.к. всего 5 кнопок.
Code:
void BUT_Scan()
{
// define | counter | short press | long press
TestButton (BUT_FEN_SET, &fenSetPressed, FEN_SetMode, FEN_Off);
TestButton (BUT_FEN_DOWN, &fenDownPressed, FEN_TempDown, NULL);
TestButton (BUT_FEN_UP, &fenUpPressed, FEN_TempUp, FEN_SaveTemp);
TestButton (BUT_SOLD_DOWN, &soldDownPressed, SOLD_TempDown, SOLD_Off);
TestButton (BUT_SOLD_UP, &soldUpPressed, SOLD_TempUp, SOLD_SaveTemp);
}

Согласитесь, приведенный фрагмент (если его выровнять, получится таблица) раскрывает весь функционал программы. К этому я и стремился.
# Владимир 2014-06-22 13:12
Да, и какой это у меня изврат с опросом кнопок? Вызов его (опроса) каждые 10 мс осуществляется одной строчкой:
Code:
RTOS_SetTask(BUT_Scan, 0, 10);

Остальное разрулит фрагмент, приведенный выше. А он к диспетчеру можно сказать не имеет отношения.
# Casper 2014-06-30 09:56
День добрый. А под ардуино такой планировщик сложно будет переписать? Его очень не хватает мне для проекта)) Постепенно ухожу конечно я с ардуино, но самый большой проект всетаки на нем остался...
# Владимир 2014-06-30 13:44
Никогда не интересовался ардуиной, даже не знаю, какой там контроллер используется. Переделать несложно: поправить инициализацию аппаратного таймера в функции RTOS_Init, не забыть обновить счетчик в прерывании таймера (у меня это ISR(RTOS_ISR)). Разрешить глобальные прерывания. Все.
# Casper 2014-06-30 17:30
Там проц atmega328 используется, но он сишный язык как я понял не понимает, надо под с++ переписывать..
# _Артём_ 2014-07-01 08:37
Quoting Casper:
Там проц atmega328 используется, но он сишный язык как я понял не понимает, надо под с++ переписывать..

Как так? Что там переписывать нужно?
Если плюсы понимает, то си наверняка поймёт.
# Владимир 2014-07-01 13:37
скорей всего компилятор ругается на функции авр студии. уберите из функций диспетчера DISABLE_INTERRU PT и RESTORE_INTERRU PT (они не обязательны). Затем уберите инклуды из файла commons.h. Там же удалите ENABLE_, DISABLE_ и RESTORE_INTERRU PT. После этого оформите программу по правилам вашей среды разработки.
# Владимир 2014-07-01 13:39
Еще возможно потребуется изменить в главном файле .с строку
Code:
#include <rtos.h>

на
Code:
#include "rtos.h"
# Casper 2014-07-01 16:34
Жалуется на это
Code:rtos.c: In function 'RTOS_Init':
rtos.c:15: error: 'TCCR0' undeclared (first use in this function)
rtos.c:15: error: (Each undeclared identifier is reported only once
rtos.c:15: error: for each function it appears in.)
rtos.c:16: error: 'TIFR' undeclared (first use in this function)
rtos.c:17: error: 'TIMSK' undeclared (first use in this function)
# _Артём_ 2014-07-02 14:22
Quoting Casper:
Жалуется на это
Code:rtos.c: In function 'RTOS_Init':
rtos.c:15: error: 'TCCR0' undeclared (first use in this function)
rtos.c:15: error: (Each undeclared identifier is reported only once
rtos.c:15: error: for each function it appears in.)
rtos.c:16: error: 'TIFR' undeclared (first use in this function)
rtos.c:17: error: 'TIMSK' undeclared (first use in this function)

В mega328 нет таких регистров, вот и выдаёт ошибку. Там TIMSK соответствует TIMSK0, TIFR - TIFR0 и так далее.
# Vlad 2014-07-21 06:57
Успешно прикрутил к STM8L.
Кому интересно -- пишите.
# megannnn98 2014-08-01 09:44
Спасибо огромное за планировщика. :D реально нужная штука
# megannnn98 2014-08-08 12:26
Единственное, что мне не хватило функции очистки очереди

void RTOS_Reset()
{
arrayTail = 0; // "хвост" в 0
}
# Владимир 2014-08-16 09:03
Пожалуйста! А в каких случаях используете эту функцию?
# demiurg 2014-09-02 10:47
Владимир. Что вы скажете насчет такого варианта? ссылка
# Владимир 2014-09-02 12:33
Ответил в сообществе
# Chugun 2014-09-17 20:35
Можно ли передавать параметры в задачу при добавлении её в очередь, как при работе с функциями ???
# Владимир 2014-09-19 11:15
Нет, нельзя. Как вариант - использовать глобальные переменные.
# Анатолий 2016-01-05 00:28
Спасибо за планировщик. Все работает четко.
Запустил на LED матрице 8х8. Параметр ПЕРИОД очень пригодился, позволил визуально проверить последовательно сть зажигания светодиодов и работу кода.
Маленькая поправка. В коде заголовочные файлы в угловых скобках (а не кавычках), поэтому компилятор ругается. Или это только у меня?!.
И еще было бы не плохо написать процедуру запуска с одной тестовой задачей (TskTest). Легче сразу разобраться.
# Владимир 2016-01-09 08:12
Пожалуйста.
В свойствах проекта надо прописать корневой каталог проекта, тогда угловые скобки будут работать. Ну или переделать на кавычки.
Как пример можно посмотреть код моей станции. Там много файлов, но для начала надо смотреть ssf.c и buttons.c
# Анатолий 2016-01-10 13:40
Quoting Владимир:
В свойствах проекта надо прописать корневой каталог проекта, тогда угловые скобки будут работать.


Спасибо буду знать
# *Dim 2016-07-23 10:15
А нет ли полного проекта с этим планировщиком? Нужен пример "правильного" построения программы.
# *Dim 2016-07-23 10:18
:-) Увидел ссыль на паяльную станцию, буду разбираться, спасибо.
# Владимир_ 2016-07-26 09:46
Будут вопросы - спрашивайте. Я сюда периодически заглядываю.
# arhiv6 2016-10-01 11:46
А зачем в функции void RTOS_DispatchTa sk() в строчке TaskArray.delay = TaskArray.perio d-1; отнимается единица?
# Владимир_ 2016-10-12 04:34
Чтобы заданный период соответствовал действительност и. А иначе задачи будут выполняться через количество системных тиков period+1.
# demiurg 2016-10-01 12:01
Мы с Владимиром уже обсуждали это творение. Остались при своих.
Этот планировщик и ему подобные не имеют никакого смысла. Потому что начинающие программисты считают, что подобные планировщики и диспетчеры якобы решают их проблемы. Но на самом деле приобретают лишний геморрой.
Решение главной проблемы (взаимодействие модулей и своевременное выполнение программного кода) следующее:
Исключение долгих циклов программных модулей, дробление выполнения модулями программного кода. Программные таймеры. Автоматное программировани е. Конечные автоматы.
# ujin 2016-10-03 15:32
под разные задачи разные реализации...вы ше уже это говорили...поэт ому планировщику в этом мире есть свое место....с небольшой адаптацией вполне себе взлетел под CVAVrом
вполне нормально так работает...нагр евалки-мигалки аля подобное ак раз вполне это для него...
автору спасибо что делитесь своими знаниями ,и подробные объяснения по коду.
# arhiv6 2016-10-24 04:14
Автор, я перенес планировщик на ARM и немного его доработал. Используются встроенные в ядро системный таймер SysTick и прерывание PendSV, что позволяет использовать планировщик на любом Cortex микроконтроллер е. Т.к. вся обработка сделана в прерываниях, в main вместо бесконечного вызова DispatchTask() можно оставить пустой цикл или заменить его на for(;;){idle()} ; - вызов какой-нибудь фоновой задачи, которую допускается в любой момент прервать остальными. Ещё добавил приоритеты задачам - если в один момент времени пришло время выполнить несколько задач одновременно - они выполняются в порядке, определённом их приоритетами. Собственно вопрос - у Вас в статье не указана лицензия проекта. Если я выложу код на github, достаточно того, что я укажу ссылку на эту статью и Вас в качестве автора изначального кода, на основе которого сделаны правки?
# Владимир_ 2016-10-24 05:44
Более чем достаточно. Это 100%-ный OpenSource. Если не затруднит, оставьте здесь ссылку на гит. Интересно будет посмотреть.
# arhiv6 2016-10-24 06:09
Вот, выложил: https://github.com/arhiv6/arm-task-sheduler
# Владимир_ 2016-10-25 06:24
На практике уже пробовали его или просто ради удовольствия сделали?
# arhiv6 2016-10-25 06:29
Ваш на практике пробовал в одном проекте. Свой - пока только пару тестов запускал. В ближайшее время буду небольшой проект делать - там попробую. Если какие-то правки/исправле ния вносить буду - внесу исправления в репозиторий и тут в комментариях напишу.
# Антон_Ш 2016-11-20 05:05
Представляю вашему вниманию утилиту которая визуализирует выполнение задач данным планировщиком.

https://drive.google.com/file/d/0B6yun0FRTB-DalJhUmpFSF80Y28/view
# Владимир_ 2016-12-13 04:15
С точки зрения программиста программа написана на высоком уровне. Она полностью самодостаточка. Приятно полазить, понажимать кнопки... Оценил график (делал нечто подобное), наверняка пришлось порядком повозиться.
С прикладной точки зрения все не так однозначно. Главная проблема - необходимость определять время выполнения каждой функции (знаю, в симуляторе AVR это легко). Для небольших проектов реально, но в ряде случаев это может стать проблемой.
В целом интересная утилитка. Мне понравилась.
# mikhail 2017-10-11 16:02
Спасибо за интересную публикацию!
Читал с удовольствием как саму статью так и разгоревшуюся дискуссию в комментариях
# Jonah 2017-10-12 12:02
Create Trackers for Twitter, Instagram and Facebook.


My web-site: best time to post
on social media: https://www.4shared.com/web/preview/pdf/H9p8vvU7ca?
# Rashad 2017-10-15 09:53
Patriot Care is happy to provide the qualified sufferers of Massachusetts with a wide-variety of
medicine-grade, naturally produced marijuana products for
medical use with the best quality customer support
available. Some states have processes for certifying
and registering eligible folks. Some have additionally designated dispensaries, or medical marijuana facilities, the place
individuals can get the product and advice on dosing and what type
to make use of for his or her condition. Research
report that marijuana has doable profit for several conditions.
State legal guidelines range in which situations qualify individuals for treatment with medical marijuana.

If you happen to're contemplating marijuana for medical use, verify your state's regulations.
Nausea, vomiting or extreme wasting related to most cancers remedy.

If you're experiencing uncomfortable symptoms or unintended effects of medical remedy, particularly ache and nausea, speak with your physician about all your choices before attempting marijuana.


The state had nearly 10,800 registered patients as of the top of
last yr. Craig West, the dispensary operations supervisor, stated Thursday
the most important cause is that Compassionate Sciences is the
only one in the state to offer cannabis extract s to its
patients. In all probability between 20 and 30 p.c of our patients buy extracts,” he said, including that
a whole lot of sufferers journey two hours from the tippy-prime of the state” to obtain the extracts.
The primary cannabis-infuse d lozenges have been accredited for sale within the
state final October. At that time, the dispensary was selling a bottle of 30 grape- or
cherry-flavor lozenges for $75, or $60 if sufferers qualify for financial help, in response
to a discover despatched out to sufferers.

Which means the Egg Harbor-based mostly dispensary in Atlantic County is the second to the final in the state in terms
of patients and gross sales. According to the report,
about 4,seven hundred new sufferers registered for the program
final year, nearly doubling the number from the previous year.
WebMD describes spasticity as a muscle control dysfunction that
is characterised by tight or stiff muscle tissue and an incapability to manage
these muscle groups.” About 39 percent of patients who are
eligible for marijuana are recognized with this
situation. The second most common ailment was listed as severe or power pain,” based
on the Health Division report.

The brand new heart had some hiccups early Friday in the form of some laptop issues associated to the state's new customer monitoring system for ensuring patients don't exceed a
limit of 3 ounces in a 14-day period. But those hold-ups were nothing compared with the gradual
progress of Delaware's foray into medical marijuana , a point of frustration for many sufferers.
Marijuana is still unlawful below federal legislation, even for medicinal use.
But under state regulation, Delawareans can apply for a medical marijuana card to purchase cannabis at a dispensary so long as a physician has licensed that they
have sure qualifying situations, together with cancer, Alzheimer's illness, a number of sclerosis, HIV and illnesses that cause severe ache and nausea.



Legal points and construction delays pushed First State's opening in Wilmington back to mid-2015 - around the same time lawmakers
voted to decriminaliz e marijuana, downgrading possession of
1 ounce from a felony offense to a civil violation, like a parking ticket.
By then, greater than 300 Delawareans have been licensed to make use
of medical marijuana. At present, more than three,000 patients statewide are licensed to buy cannabis.
First State Compassion Middle, a new medical marijuana dispensary near Lewes.
As a substitute, the New Castle County dispensary has nearly doubled its crop production to accommodate
the Sussex County operation and growing demand close to Wilmington.

Columbia's facility is slated to open this fall, although
an exact location has not yet been disclosed. Shari Mosley, a
fifty seven-year-old Dover resident who makes use of medical marijuana to help
treat her multiple sclerosis, said she's trying
forward to with the ability to purchase cannabis nearer to residence.
The within of the Columbia Care medical marijuana dispensary is seen in New York
January 7, 2016.Shannon Stapleton. THE BIG APPLE (Reuters)
- New York state opened its first medical marijuana dispensaries on Thursday, launching one of the most conservative and tightly
monitored applications of its form within the United States.



Not like all the opposite states that permit medical marijuana except Minnesota, New
York prohibits marijuana for smoking. It permits the drug to be offered solely in liquid or oil form to be used in vaporizers and inhalers, or
capsules taken orally. In addition, New York requires a 4-hour mandatory training course for physicians before they can certify sufferers
and for pharmacists earlier than they will dispense the drug,
a requirement not mandated by different state applications, based on the National Convention of State Legislatures.
By allowing solely refined marijuana extracts, not smokable
crude plant or so-referred to as edibles, the state is
permitting for larger dosing standardization and purity,
Bednarczyk mentioned.

We're additionally proud to perpetuate the Cannabis
motion by way of our efforts in local and nationwide business and
affected person outreach programs. We strive to present the widest variety of regionally sourced and in home strains for our sufferers.
Signal up to obtain the most recent news and deals from The Clinic Nevada!

Nonetheless, we're too busy swooning over the plush interiors of America's rising dispensary architecture to care about any of those things.
The rising authorized marijuana industry is the quickest-growin g trade in the United States.
Indeed, there are even structure and design corporations that
specialize in the design of dispensaries and develop rooms.


Jensen stated the durability and security
of a financial institution added to the constructing's enchantment as a house for the
potential dispensary. Jensen, an lawyer turned entrepreneur who has worked as a mentor with the Maryland Middle for Entrepreneurshi p, Howard County's business incubator, mentioned his experience as a most
cancers survivor was one thing that led him to the medical cannabis
trade, which is simply getting its begin within the state.
The business' team also contains Melanie Galloway, the previous director of Crownsville's
Serenity Acres drug rehabilitation clinic. Evolution Wellness was certainly one of 12 businesses awarded
a dispensary license in December to function in Anne Arundel.


Rising Medical Marijuana Card Doctor and the Studio City Easy
Clinic in Valley Village additionally moved across the road to the easily accessible Starbucks shopping middle.

The Studio Metropolis Simple clinic is now at the nook of Laurel Canyon and Riverside
Dr at 12045 Riverside Dr Los Angeles CA, 91607. Open since 2009, Hollywood Easy Clinic and Medical Marijuana Card
Physician concentrate on medical hashish evaluations, recommendations , renewals, playing cards
and cultivation licenses for growing marijuana.
Cannabis in Hollywood Los Angeles has its biggest advocate with Medical Marijuana Card Physician of the Hollywood Simple Clinic.


California marijuana patients of Medical Marijuana
Card Physician on the Hollywood Simple Clinic are the best advocates for
how you can get a medical marijuana card.
You should use this advice alone to get access
to a cannabis club. However, a medical marijuana card is
offered via our clinic for $20. Solely after you might have a valid and verified medical hashish suggestion are you able to receive a
California State Verified medical marijuana identification card administered by your native
county Division of Public Well being. If you are at the moment ready over an hour at
one other docs workplace that looks just like the DMV, name and ask our cannabis clinic workers how simple
it's to get a medical cannabis card at one in every of our clinics.


These articles are thought of the California authority on medical
hashish.Medical Marijuana Card Doctor and his consulting marijuana physicians
comparable to Rising Medical Marijuana Doctor also work
at the Studio Metropolis Simple Clinic. Studio Metropolis Simple Clinic, open since 2011, serves the sufferers of Sherman Oaks, North
Hollywood, Van Nuys, Reseda. Our medical cannabis medical doctors train our
patients about the advantages of organic meals, train and pure medicine as
a complement to present medical care. Medical hashish patients as far west as Ventura County, Santa Barbara and Pismo Beach
come to see if cannabis could be a protected part of their therapy plan.

Renewals $forty five (renewal worth only counts if your suggestion hasn't expired prior to now 6 months) Full Bundle $eighty five.

Often called the California Compassionate Use Act,” Proposition 215 was
enacted by California voters and took effect on November sixth, 1996.

Marijuana Dispensaries are fashioned by these collectives or
teams of medical marijuana patients. Medical hashish in California has given energy again to the patient and made medical marijuana medical doctors objective observers to help patients in managing the advantages
of marijuana with the negative effects of marijuana.Medic al
marijuana card necessities have turn out to be higher defined because the
legalization of medical marijuana.

Alternative suggestions are $30 and includes a miniature advice so you're much less more likely to lose it
again. Medical Marijuana Card San Francisco Doctor is a local Californian with a family history of farmers from Sonoma County
in Northern California. He has worked with many different doctor's workplaces that specialize in medical marijuana and was dissatisfied
with the level of disregard for affected person nicely being.
However, he has utilized all of the greatest rules from each medical marijuana docs
office into his personal apply with the Hollywood Easy Clinic.
Hollywood Easy Clinic is a medical doctor's office that is situated in Los Angeles County, California specializing in medical
marijuana cards, evaluations, suggestions and renewals.


Our staff supplies steerage based on the affected person's needs and
most sufferers uncover sure strains that provide the very best aid for his or her individual symptoms.
Medical marijuana dispensaries are open in Nevada.

Anyone with a medical marijuana card (even from out-of-state) is allowed to possess as
much as 2.5 oz of hashish. Leisure and medical marijuana dispensaries are up and running in Nevada.
Nevada recognizes reciprocity with different medical marijuana states,
which means your medical card from out-of-state
is valid at any Nevada dispensary. The Easiest Method to Get a Medical Marijuana
Card from Medical doctors Who Care.

You'll even be issued a medical marijuana card and literature containing California's medical marijuana
legal guidelines. Your medical marijuana card
permits you access to holistic drugs all through the state, whether you
live in Downtown Los Angeles, Long Beach,
CA, Culver Metropolis, CA, South Gate, CA, Inglewood, CA, Huntington Park, CA or anywhere else.
Patients who have accomplished our medical examination with
a qualifying condition will receive a durable, plastic affected person ID card containing your
photograph, name, patient ID number, and your birthdate.
This card could also be presented to regulation enforcement and incorporates necessary information about California's medical marijuana
legal guidelines.

Also visit my blog ... marijuana clinic - www.smartmylife.fr: https://www.smartmylife.fr/smartnews/groups/california-is-the-first-state-to-cross-marijuana-legal-guidelines-to-permit-california-sufferers-to-satisfy-with-medical-doctors-in-clinic-or-online-by-way-of-tele-medicine-to-visit-with-420-docs-for/members/ -
# Keisha 2017-10-15 15:00
I have noticed that your page needs some fresh
content. Writing manually takes a lot of time, but there is tool for this boring
task, search for: Wrastain's tools for content

Feel free to surf to my homepage PatX: https://Roy2009.blogspot.com

У вас недостаточно прав для комментирования.