Очередной диспетчер. AVR. Си

Edit. Ну прямо азарт у меня появился. Очередная версия диспетчера. Возможности:
однократное выполнение,
отложенное однократное выполнение.

многократное выполнение без задержек и периодичности,
отложенное многократное выполнение, без задержек и периодичности.
отложенное многократное выполнение, с периодичностью.

Сразу же попробовал перенести на диспетчер свой один проект для проверки.

Примечание, у меня диспетчер работает только в основном цикле. Установка, удаление задач тоже в основном цикле. Обработчики прерываний должны быть максимально быстрыми. Нужно добавить\удалить задачу, ставьте флаги, в основном цикле по флагам делайте, что нужно.


Архив скомилированного проекта (IAR) и проверенного на железе по ссылке.

Структура очереди задач:

//========================================================================
typedef struct task
{
   u08 status;
   u16 timer;
   u16 period;
   void (*func) (void);
} task;

#define RUN_TASK        4
#define RUN_TASK_FLG    1<<RUN_TASK

extern struct task Task_Queue [];
extern struct task tmp_task;

// Варианты установки задач расписаны в set_task.
enum
{
   SINGLE_EXECUTION = 0, // Однократное исполнение.
   MULTIPLE_EXECUTIONS, // Многократное исполнение.
};
//========================================================================


Добавление задачи:

//------------------------------------------------------------------------
u08 set_task (u08 task_status, u16 task_pause, u16 task_period, void (*task_func) (void)) // Добавление задачи в очередь.
{
   if (Tail_Task_Queue < MAX_TASKS)
   {
      if (!(task_pause))
         Task_Queue [Tail_Task_Queue]. status = (task_status | RUN_TASK_FLG); // Если 0, то установка флага на немедленное исполнение.
      else
         Task_Queue [Tail_Task_Queue]. status = task_status;

      Task_Queue [Tail_Task_Queue]. timer = task_pause;
      Task_Queue [Tail_Task_Queue]. period = task_period;
      Task_Queue [Tail_Task_Queue]. func = task_func;
      Tail_Task_Queue++;
      return 1;
   }

   return 0; // Очередь задач заполнена, критическая ошибка.
}
//------------------------------------------------------------------------


Удаление задачи:

//------------------------------------------------------------------------
void delete_task (void (*task_func)(void)) // Удаление задачи из очереди.
{
   if (Tail_Task_Queue > 0)
   {
      u08 cnt = 0;

      while (cnt < Tail_Task_Queue)
      {
         if (Task_Queue [cnt]. func == task_func) // Если находится заданная задача, то эта задача удаляется из очереди.
         {
            replace_task (cnt);

            return;
         }
         cnt++;
      }
   }
}
//------------------------------------------------------------------------
void replace_task (u08 a) // Замена удаляемой задачи задачей из хвоста очереди.
{
   if (Tail_Task_Queue) Tail_Task_Queue--;

   Task_Queue [a] = Task_Queue [Tail_Task_Queue];

   Task_Queue [Tail_Task_Queue] = tmp_task;
}
//========================================================================


Диспетчер:

//========================================================================
void dispatcher_task (void)
{
   if (Tail_Task_Queue > 0)
   {
      void (*ptr_func) (void);

      ptr_func = EMPTY_FUNC;

      u08 cnt = 0;

      if (sys_tick & (1<<DISPATCHER_SYS_TICK_FLG)) // Если флаг в прерывании был установлен
      {
         clr_bit (sys_tick, DISPATCHER_SYS_TICK_FLG); // Сброс флага.

         while (cnt < Tail_Task_Queue) // Перебор таймеров задач.
         {
            if (Task_Queue [cnt]. timer)
            {
               if(!(--Task_Queue [cnt]. timer))
                  set_bit (Task_Queue [cnt]. status, RUN_TASK); // Помечаем задачу на исполнение.
            }
            cnt++;
         }
      }

      while (Cnt_Task_Queue < Tail_Task_Queue) // Выборка задач.
      {
         if (Task_Queue [Cnt_Task_Queue]. status & RUN_TASK_FLG) // Если этот флаг установлен, помещаем задачу в указатель.
         {
            ptr_func = Task_Queue [Cnt_Task_Queue]. func;

            switch (Task_Queue [Cnt_Task_Queue]. status & 0x0F)
            {
               case SINGLE_EXECUTION:
                  replace_task (Cnt_Task_Queue); // Удаляем задачу из очереди.
                  break;

               case MULTIPLE_EXECUTIONS:
                  if (!(Task_Queue [Cnt_Task_Queue]. timer) && (Task_Queue [Cnt_Task_Queue]. period))
                  {
                     Task_Queue [Cnt_Task_Queue]. timer = Task_Queue [Cnt_Task_Queue]. period;
                     clr_bit (Task_Queue [Cnt_Task_Queue]. status, RUN_TASK);
                  }
                  break;

               default:
                  break;
            }

            Cnt_Task_Queue++;
            if (Cnt_Task_Queue >= Tail_Task_Queue) Cnt_Task_Queue = 0;

            if ((ptr_func)) (*ptr_func) ();
            return;
         }

         Cnt_Task_Queue++;
         if (Cnt_Task_Queue >= Tail_Task_Queue)
         {
            Cnt_Task_Queue = 0;
            return;
         }
      }
   }
}
//========================================================================


Критика, обсуждение приветствуются.

Комментарии (67)

RSS свернуть / развернуть
Чето как то громоздко кмк
0
Позже на свежую голову обдумаю некоторые моменты. Пока на данный момент — все делается в основном цикле. И таймерная служба и диспетчер. Перенес один свой проект для пробы, все фурычет. Дисплей исправно показывает, клавиатура 4х4 работает. Меню работает. Основную работу нужно на оборудовании гонять. «Громоздко» — как я уже написал, позже, на свежую голову. Зато твоей «обезьяны с гранатой», «дамоклова меча» нет. Ну ты знаешь, о чем я.
0
Ну а щас? :)
0
Вы урезали главное достоинство диспетчера — регулирование временных интервалов. Без него это всего лишь упакованный в модуль КА.
0
Регулирование временных интервалов, уважаемый, вы должны делать сами. И это не достинство диспетчера, а определенные жесткие рамки диспетчера, вдобавок мина замедленного действия. Включить\выключить светодиодик на 500 мс — это не задача, а состояние модуля.
0
Будет это миной или нет, зависит от реализации. Вы оставляете это пользователю/программисту. Где гарантии того, что он реализует регулирование устойчивым к ошибкам? Дело ваше конечно, но на мой взгляд, эту функцию лучше внести в диспетчер, отладить ее и предоставлять пользователю готовые решения. Это вовсе не рамки, а доп. функционал, довольно часто используемый.
0
В функции delete_task вы перебираете все задачи. Может быть стоит в структуру task добавить переменную id, которая будет указывать на положение нужно задачи в массиве?
0
Для 16 задач будет достаточно даже 4х бит в переменной u08 status, а остальные 4 использовать для флагов.
0
ИМХО:
1.
void shift_task_queue (u08 cnt);
— в заголовочном файле ни к чему.

2. При добавлении/удалении задач атомарность не соблюдается. Соответственно, рулить тасками из прерываний нельзя — это плохо. Без этого программы, которые взаимодествуют с внешним миром и ждут определенных асинхронных событий, рискуют опять превратиться в флаговые спагетти.

3. Отсутствие таймерной службы гробит саму идею планирования на корню. Слишком высока цена (по накладным расходам) такой примитивной обертки суперлупа, почти не добавляющей удобства и функционала. Без нормального механизма временных интервалов и возможности, как минимум, однократного, циклического, отложенного запуска тасков оно годится только для самых примитивных проектов, которые и на суперлупе вполне бодренько взлетают.
+1
Опередил, покуда я код изучал)
Я бы еще добавил:
Очищать очередь задач полностью, если очередная не лезет — жестоко.
+1
Где вы видели очистку очереди, если очередь полная? Если очередь полная — это критическая ошибка.
0
Tail_Task_Queue++;
if (Tail_Task_Queue >= MAX_TASKS) Tail_Task_Queue = 0;

Правда, я не заметил if (Tail_Task_Queue < MAX_TASKS) в начале. Тем не менее, во первых, это делает код выше бесполезным а во вторых — при заполнении очереди (добавлении 16-й задачи) онадобавится и затем очередь будет очищена. Лучше или вообще убрать строку с обнулением Tail_Task_Queue, или хотя бы заменить условие на ">".
0
Епт, ну да, заснул в 5 утра, проснулся сразу попробовал несколько идей.
0
1 — Забыл сразу убрать. Убрал.

2 — Атомарность не нужна. Все делается в основном цикле. Если нужно добавить задачу, значит в прерывании ставьте флаг и по флагу в основном цикле добавляем, удаляем задачи.

3 — С самого начала, еще как у DI HALTA появился диспетчер, я увидел для себя несколько минусов.
а) Работа в прерывании таймерной службы. Я перенес ее в основной цикл. Потом, на этой основе сделал свою таймерную службу. Разброс в несколько десятков и даже сотен мкс погоды при десятках и сотнях мс абсолютно не делают. Ну вызовется, скажем, опрос клавиатуры не точно через 100 мс, а через 99,99 мс или 100,55 мс. Ну и фиг с ним.
б) Заложенная мина замедленного действия. Пример, скажем, кто-то считает рубануть гильотиной через 1 секунду. Ну и делает это самостоятельной задачей. Засовывает это в диспетчер. А тут у нас аварийная ситуация. Ну ладно, что-то там отработало. А в очереди болтается на исполнение через 1 с гильотина. И сработает она вполне себе. Что, придумывать kill_task? А откуда мы знаем, что удалять из диспетчера? Так что сработка гильотины — это должно быть состоянием КА. Точно так же, как периодичность запуска чего-либо. Делаем автомат, или по флагу, вот вам и периодичность.

4 — Как я уже написал, я сделал эксперимент. Люди утверждают, что на диспетчерах якобы проще программы писать. Не нужно задумываться, как реализовать запуск задачи, скажем, каждые 100 мс. А я утверждаю, что ничего трудного в этом нет. Когда я перенес один проект, я увидел только один плюс — Скажем, запуск процесса при автоматном программировании зависит от нескольких условий — определенные флаги, состояний других КА. При диспетчере же просто в определенный момент добавляем процесс в очередь.

Так что, гаспада, если у вас трудности запустить какую-либо задачу с определенными периодичностью, задержками, на определенное время — это ваше неумение писать программные таймеры и конечные автоматы…
0
2 — Атомарность не нужна.
Я бы сказал, что диспетчер такой не нужен.
+2
Если уж на то пошло, я прекрасно обхожусь вообще без диспетчеров. main без диспетчера выше. Опыт показал, что диспетчер дает только один плюс — избавляет от проверки нескольких условий, состояний одним КА состояний других автоматов. А так только трата лишних тактов на переключение задач.
0
Обойтись можно без чего угодно — но инструмент я предпочитаю облегчающий работу. А эта очередь задач практически ничего не дает — на неблокирующих задержек, ни управления тасками из прерываний — короче, ничего из того, для чего диспетчеры вообще делаются.
+1
Так что, гаспада, если у вас трудности запустить какую-либо задачу с определенными периодичностью, задержками, на определенное время — это ваше неумение писать программные таймеры и конечные автоматы…
Так если все-равно городить безобразные КА с кучей условий, лишних флагов, неочевидными ожиданиями, то тогда спрашивается «на кой хрен» такое счастье. В чем выигрыш?
Это Вы, кмк, не понимаете зачем нужны такие планировщики. Вы вот написали диспетчер ради диспетчера. Даже без намека на рациональность. А теперь пытаетесь доказать что они и вовсе не нужны? Ну да, такие, как ваш, не нужны точно :)
+1
О какой рациональности вы говорите. Пусть будет диспетчер с таймерной службой. Как уже выкладывали разные примеры. Какая проблема запустить процесс в основном цикле с определенной периодичностью скажем этот процесс:

//========================================================================
void proc_in_out (void)
{
   switch (_proc_in_out)
   {
      case PROC_IN_OUT_INIT:
         spi_init ();

         receive_send_in_out ();

         _oe_enable ();

         set_timer (ST_PROC_IN_OUT, DEC_NO_RERUN, 10);
         _proc_in_out = PROC_IN_OUT_RUN;
         break;

      case PROC_IN_OUT_RUN:
         if (wait (ST_PROC_IN_OUT))
         {
            receive_send_in_out ();

            set_timer (ST_PROC_IN_OUT, DEC_NO_RERUN, 10);
         }
         break;

      default:
         break;
   }

}
//========================================================================


в основном цикле:

   proc_in_out ();


И херачит он спокойненько каждые 10 мс…
0
ну на фриртосе эта задача будет выглядеть примерно так

void proc_in_out_Task( void *pvParameters )
{
    spi_init ();
    receive_send_in_out ();
    _oe_enable ();
    for( ;; )
    {
        receive_send_in_out ();	
        vTaskDelay(10/portTICK_PERIOD_MS);
    }
}

и херачит спокойненько каждые 10 мс и любой кто посмотрит исходники поймет что тут написано.
+1
О да, это как раз то, для чего и нужны вытесняющие планировщики) Ну и еще для ускорения реакции на высокоприоритетные события.
0
Отредактировал. Ждем-с новую порцию критики :)
0
Что изменилось? Ты правда думаешь, что кто-то захочет перечитывать портянки кода?
0
  • avatar
  • Vga
  • 01 сентября 2014, 16:04
Народ хотел таймерную службу. Вот что изменилось.
0
ОК, так понятнее. Обычно при обновлении поста принято ставить ту или иную пометку о том, что и где поменялось.
0
  • avatar
  • Vga
  • 01 сентября 2014, 16:23
Update
0

void replace_task (u08 a) // Замена удаляемой задачи задачей из хвоста очереди.
{
   if (Tail_Task_Queue) Tail_Task_Queue--;
   Task_Queue [a]. status = Task_Queue [Tail_Task_Queue]. status;
   Task_Queue [a]. timer = Task_Queue [Tail_Task_Queue]. timer;
   Task_Queue [a]. period = Task_Queue [Tail_Task_Queue]. period;
   Task_Queue [a]. func = Task_Queue [Tail_Task_Queue]. func;

   Task_Queue [Tail_Task_Queue]. status = 0;
   Task_Queue [Tail_Task_Queue]. timer = 0;
   Task_Queue [Tail_Task_Queue]. period = 0;
   Task_Queue [Tail_Task_Queue]. func = 0;
}

Можно заменить на

void replace_task (u08 a) // Замена удаляемой задачи задачей из хвоста очереди.
{
   if (Tail_Task_Queue) Tail_Task_Queue--;
   Task_Queue [a] = Task_Queue [Tail_Task_Queue];
}

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

void replace_task (u08 a)
{
   if (Tail_Task_Queue) Tail_Task_Queue--;
   Task_Queue [a] = Task_Queue [Tail_Task_Queue];

   Task_Queue [Tail_Task_Queue]. status = 0;
   Task_Queue [Tail_Task_Queue]. timer = 0;
   Task_Queue [Tail_Task_Queue]. period = 0;
   Task_Queue [Tail_Task_Queue]. func = 0;
}
0
Чтоб не искать в коде, зачем обнуление? При вызове set_task на место Tail_Task_Queue запишется новая задача, и без разницы, какие значения там записаны.
0
На случай каких-либо непредвиденных ситуаций. Конечно, если что-то произойдет за пределами буфера и структуры, тут ничего уже не сделаешь. Но в пределах вполне можно защититься, в коде вызов функции по указателю произойдет, если не 0.
0
Слушайте, а как бы вы решили следующий момент. В модуле могут быть запущены несколько таймеров. Когда в модуле на каждый момент времени один таймер, проблемы нет. Но как быть, если таймеров несколько, и задача должны запускаться при каждой итерации основного цикла, диспетчера. На КА это реализуется легко. Наверное этот вопрос нужно вынести на форум.
0
Честно признаться, я не понял, в чем вопрос. Зачем несколько таймеров? Работает один таймер, в его прерывании уменьшаются счетчики времени ВСЕХ задач, ну или ставится флаг, чтоб счетчики уменьшились в главном цикле. Чего еще надо? Ниже правильно отметили, что вы слишком усложнили простейший диспетчер. Кроме вас никто не захочет в этом разбираться.
0
Диспетчер ради диспетчера. В попытках упрощения только усложнение. Код получается неочевидным и плохочитаемым.
Главный минус симбиоза этого диспетчера и этих ваших автоматов — полная нечитаемость. Вы можете резонно возразить, но я скажу что код должен быть настолько очевидным, чтоб при его чтении испытывать наслаждение.
Вот тут я такого не испыытваю (взял из прикрепленного проекта).
switch (_drv_char_dsp)
{
	case 	DRV_CHAR_DSP_INIT_1:
		init_char_dsp ();

	 def_users_chars (table_users_chars);

		clr_dsp_buf ();

	case 	DRV_CHAR_DSP_INIT_2:
	 cnt_x = 0;
	 cnt_y = 0;

	 _drv_char_dsp = DRV_CHAR_DSP_SEND_ADDR;
	 set_task (MULTIPLE_EXECUTIONS, DRV_LCD_TIME, DRV_LCD_TIME, drv_char_dsp);
		break;

  case DRV_CHAR_DSP_SEND_ADDR:
	 lcd_send_com(lines[cnt_y]);
	 _drv_char_dsp = DRV_CHAR_DSP_SEND_CHAR;
	 set_task (MULTIPLE_EXECUTIONS, DRV_LCD_TIME, DRV_LCD_TIME, drv_char_dsp);
		break;

	case DRV_CHAR_DSP_SEND_CHAR:
	 data = dsp_buf [(cnt_y*MaxX) + cnt_x];

	 if (data >= 0xC0)
	 {
		data = table_rus_chars [data - 0xC0];
	 }

	 lcd_send_data (data);
	 cnt_x++;

	 if (cnt_x >= MaxX)
	 {
		cnt_x = 0;
		cnt_y++;
		if (cnt_y >= MaxY)
		{
		   cnt_y = 0;

		   init_char_dsp ();

		   def_users_chars (table_users_chars);
		}

		_drv_char_dsp = DRV_CHAR_DSP_SEND_ADDR;
	 }

	 set_task (MULTIPLE_EXECUTIONS, DRV_LCD_TIME, DRV_LCD_TIME, drv_char_dsp);
		break;

	default:
		_drv_char_dsp = DRV_CHAR_DSP_INIT_1;
		break;
}

Это не код. Это гавно. В одной функции намешаны вложенные кейсы, условия. Разные уровни абстракций, неочевидные манипуляции с битами и флагами. Состояние перетекают из одного в другое непонять в какой последовательности. Спрашивается, для чего диспетчер? Он призван упрощать, а тут только усложняется.
Если взглянуть на код диспетчера, то там тоже самое. В функции set_task вообще дублирование кода. Код сложен. А для ее снижения и придуманы эти наши гламурные оси и вообще эмпирические правила программирования, которым нужно следовать, и наконец уже забыть про эту погоню за байтами, которая была уместана лет 10 назад.
Остро проблема так не касается вас, пока вы делаете маленькие проекты в одиночку, пока никому другому не приходится поддерживать ваш код (по крайней мере тому, у кого квалификации хватит чтобы сказать что код некачественный). Нельзя просто так взять, написать подобие диспетчера, неправильно его применить и сделать выводы.
+1
Критикуя, предлагайте. По пунктам, пожалуйста. Чтобы вы правильно поняли, я воспринял ваши замечания адекватно. Культуру программирования я стараюсь повышать. На си не так давно пересел, так что имейте это в виду.
0
Хорошо. Напишу мысли и выложу. Но уже, наверно, не сегодня.
0
Сколько не смотрел, не вижу криминала. case никто не запрещает так использовать. Сделал я так, чтобы не плодить лишнее состояние и не дублировать код.
Постановка задачи: вывод из буфера на символьный дисплей. Хоть автомат, хоть флаги, чтобы был блок самоинициализации. Так как в начале программы очищается вся используемая ОЗУ. Вывод символов по 1 каждые 1 мс (тик). Если 1 символ, задается адрес строки. MaxY строк, MaxX символов в строке. В оригинале диспетчера нет, используются программные таймеры. Разница только в set_timer (status, time) вместо set_task. Покажите класс. Это я без ерничества, если что…
0
Я бы предложил МакКоннелла. Ну и культура программирования не зависит от языка, как говорит тот же МакКоннелл — «нужно программировать с использованием языка, а не на языке».
0
  • avatar
  • Vga
  • 03 сентября 2014, 22:36
Класс я, к сожалению, не покажу. Вчера накида код, сейчас описание сделал. К примеру возьмем вышеприведенный автомат обновления дисплея. В принципе и автомат не нужен. Выкинул всю низкоуровневую шелуху, получился такой код.
typedef struct {
	uint8_t x;
	uint8_t y;
} position_t;

void lcd_routine()
{
	while(1)
	{
		LCD_Init();
		position_t position = {0,0};
		int i;
		
		for (i=0; i<NUM_OF_LCD_DATA; i++)	{
			uint8_t data = getDataFromBuffer(position);
			lcdPutChar(position, data);
			next_position(&position);
		}
		OS_Delay(10);
	}
}

void next_position(position_t *position)
{
	position->x++;
	if (position->x >= POSITION_MAX_X)	{
		position->x = 0;
		position->y++;
		assert_param(y <= POSITION_MAX_X);
	}
}

Далее скармливаем функцию lcd_routine любой ОС. Делаем функции записи/чтения буфера памяти потокобезопасными (мьютекс втыкаем). Все тонкости работы с LCD (конвертация русских букв, рассчет адресов относительно координат, тонкости инициализации, загрузки своих символов) убираем в модуль LCD, они тут не нужны. В итоге разобраться с тем, что тут происходит в разы легче. Да, не обязательно ОС, но почемму бы и нет, если она так дешево дает абстракцию потоков и межпотоковое взаимодействие? Можно конечно и вернуться к конечным автоматам, просто данную функцию оставить как одно состояние, а второе состояние — пауза между обновлениями.
Когда я пытался сделать своей таймерный велосипед для КА, я копал в сторону уменьшения и упрощения кода в КА. Так и не закончил, но суть была такова: Есть некое ядро конечного автомата (по сути набор функций) Оно может работать со многими конечными автоматами, как с объектами. Создаем структуру конечного автомата (объект) для опроса кнопок, добавляем несколько состояний. Каждое состояние — это набор из трех функций: вход в состояние, работа в состоянии, выход из состояния. Любую функцию можно не объявлять, тогда она просто не будет вызываться. Так же ядро давала удобные, безопасные функции смены состояний. Причем смена сосояний поддерживалась с отсрочкой по времени. Допустим
FSM_SetState(&buttonScan, state_scan, 10);
Работа в текущем состоянии прекращается. Через 10 мс будет переход в состояние state_scan. Сначала выховется функция выхода из состояния текущего состояния, потом функция входа в состояние state_scan, а затем функция работы в состоянии, которая будет периодически вызваться, пока состояние не сменится.
Вообщем как-то интерес заглох, и до конца не довел идею и бросил. Да и код получался немного громоздким и сильно нелинейным. Тяжеловато было его отследить.

Теперь смотрим на код диспетчера, а именно на функцию set_task (приведу кусок)
while (cnt < Tail_Task_Queue)
  {
	 if (Task_Queue [cnt]. func == task_func) // Если задача уже добавлена, то обновление.
	 {
		if (!(task_pause)) // Если 0, то установка флага на немедленное исполнение.
		   Task_Queue [cnt]. status = (task_status | RUN_TASK_FLG);
		else
		   Task_Queue [cnt]. status = task_status;

		Task_Queue [cnt]. timer = task_pause;
		Task_Queue [cnt]. period = task_period;
		return 1;
	 }
	 cnt++;
  }

  if (!(task_pause))
	 Task_Queue [cnt]. status = (task_status | RUN_TASK_FLG); // Если 0, то установка флага на немедленное исполнение.
  else
	 Task_Queue [cnt]. status = task_status;

  Task_Queue [cnt]. timer = task_pause;
  Task_Queue [cnt]. period = task_period;
  Task_Queue [cnt]. func = task_func;
  Tail_Task_Queue++;
  return 1;

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

u08 set_task (u08 task_status, u16 task_pause, u16 task_period, void (*task_func) (void)) // Добавление задачи в очередь.
{
	if (queueIsFull() == true)	{
		return 0;
	}
	
	uint8_t taskIndex = findTaskOrFreeSpace(task_func);
	
	Task_Queue [taskIndex]. status = task_status;
	Task_Queue [taskIndex]. timer = task_pause;
	Task_Queue [taskIndex]. period = task_period;
	Task_Queue [taskIndex]. func = task_func;
	if (task_pause == 0)	{
		setFlagRun(taskIndex);
	}
	Tail_Task_Queue++;
	return 1;
}

uint8_t findTaskOrFreeSpace(void (*task_func) (void))
{
	uint8_t currentTaskIndex = 0;
	while (currentTaskIndex < QueueTail)
	{
		if (Queue[currentTaskIndex].func == task_func)	{
			return currentTaskIndex;
		}
		currentTaskIndex++;
	}
	return QueueTail;
}


Мне не нравится заполнение Task_Queue [taskIndex] в виде четырех комманд. Их нужно вынести в отдельную функцию. Но в нее нельзя передавать столько параметров, поэтому лучше создать тип структуры с этими параметрами и передавать указатель на структуру. Аналогично можно было бы проделать с функцией dispatcher_task, но смысла нет разбираться в том коде. Со большой вложенностью и разношертностью еще можно справиться, но флаги очень тяжело читаются и воспринимаются. Работу с ними нужно выносить в отдельные функции с понятными именами.
Как тут уже сказали, рекомендую хорошую книгу Совершенный код. Немного объемная, но там рассказывается как именно хорошо кодить. Так же для затравки сначала можно прочитать Чистый код. Меньше по объему, меньше материала, сжато и хорошо написана. Набрать азарт и втянуться в тему.
+2
Так же для затравки сначала можно прочитать Чистый код.
А есть линк на эту книгу в электронном виде? Тем паче что в продаже ее уже нет.
0
  • avatar
  • Vga
  • 04 сентября 2014, 10:05
Ссылка
Я ее, кстати, недавно в магазине видел. Недавно обнаружил, что книжки в хорошем качестве можно искать Вконтакте в документах. «Бесплатно и без смс». Причем легко найти именно электронную версию, а не сканы.
+1
Я покупал в Лабиринте и всё ещё там есть.
0
Их нужно вынести в отдельную функцию.
А какой смысл создавать функцию для заполнения структуры, в которую передаются параметры в виду структуры, которую придется заполнять тут же при вызове?
0
  • avatar
  • Vga
  • 04 сентября 2014, 10:07
А вот ту да, не подумал. Но функцию, записывающую таск надо бы, ибо это дублируется во многих местах. Можно, конечно, сделать функцию, принимающую 5 параметров. Или подумать, повертеть, покрутить и прийти к чему-нибудь еще. Например сократить число параметров.
0
Боюсь что здесь, по крайней мере с такой структурой данных, без километрового списка параметров не обойтись. Хотя тот же МакКоннелл, кажется, рекомендовал задумываться при 7+ параметрах, а не при более чем трех.
0
  • avatar
  • Vga
  • 04 сентября 2014, 10:39
Ну он, вроде, говорил это про количество переменных в классе или функции. Передавать 7 параметров в функцию, имхо очень много. Мартин писал, что идеальная функция имеет ноль параметров, а максимум — 3 параметра, но это уже повод принюхаться на предмет говнокода.
0
Хм, ну не помню.
Хотя помнится еще, что говнокод-детектор в ModelMaker MMX начинает ругаться с 6 параметров.
0
  • avatar
  • Vga
  • 04 сентября 2014, 10:49
Не получается по вашему примеру с обновлением. Мой код работает и с VFD и с ЖКИ. VFD насрать на времянки, поэтому можно вообще без задержек все написать и будет и код нормально выглядеть и работать будет. Но ЖКИ очень медленный. Если не опрашивать флаг занятости, нужно соблюдать времянки. Именно поэтому в начале программы я запускаю первым модуль, где вызывается модуль дисплея. Потому что в блоке инициализации программные задержки. И поэтому сначала в диспетчере одиночный вызов модуля дисплея, инициализация занимает примерно 60 мс. И потом ставлю задачу на периодический запуск каждые 1 мс. Когда дисплей инициализирован, задержки между отправкой даты\команды должны быть ~ 40-50 мкс. Поэтому, чтобы не тормозозить программу, я сделал обновление каждые 1 мс. И времянка соблюдена. И дисплей обновляется, программа не тормозит из-за дисплея.
0
Поправка. Каждые 1 мс символ и адреса строк. При 20х4 полное обновление = 84 мс
0
На первый взгляд можно сделать так:
void lcd_routine()
{
while(1)
{
LCD_Init();
position_t position = {0,0};
int i;
for (i=0; i<NUM_OF_LCD_DATA; i++) {
uint8_t data = getDataFromBuffer(position);
lcdPutChar(position, data);
next_position(&position);
OS_Delay(1);
}
}
}
Либо засунуть задержку в функцию lcdPutChar. Не очень красиво, потому что библиотека дисплея будет зависеть от реализации многопоточности, но хоть как-то. Можно реализовать периодическую проверку флага занятости, но тогда это будет тратить процессорное время. Поэтому как тут нарисовано оптимально: каждую миллисекунду шлем новый байт.
P.S. На всякий случай поясню: OS_Delay(1) тут не тупая задержка, а блокировка задачи на указанный отрезок времени. В этот момент будут крутиться другие задачи.
0
Правда, в этом диспетчере ее не так просто ввести. Эти «задачи» — не нормальные потоки, программировать с таким диспетчером приходится весьма своеобразно.
Хотя я хотел спарить свой аналогичный с protothreads и получить сорт оф нормальные потоки.
0
  • avatar
  • Vga
  • 04 сентября 2014, 12:50
Забыл процитировать, на что отвечаю:
OS_Delay(1) тут не тупая задержка, а блокировка задачи на указанный отрезок времени.
0
  • avatar
  • Vga
  • 04 сентября 2014, 12:50
а я freeRTOS какой-нибудь имел ввиду.
0
Извиняюсь что не поместил код в теги код. Жаль редактировать сообщения нельзя
0
Ну а так читабельно? 3 вечера убил. Больше вариантов не вижу.

//========================================================================
u08 _screen_refresh;

static const u08 lines [4] = {0x80, 0xC0, 0x94, 0xD4};

u08 position_x;
u08 position_y;

void screen_refresh (void)
{
   switch (_screen_refresh)
   {
      case  SCREEN_REFRESH_INIT:
         clr_dsp_buf ();

         position_x = POSITION_MAX_X;
         position_y = POSITION_MAX_Y;

         set_timer (ST_PROC_CHAR_DSP, NO_RERUN_TIMER, DRV_LCD_TIME);

         _screen_refresh = SCREEN_REFRESH_RUN;
         break;

      case SCREEN_REFRESH_RUN:
         if (wait (ST_PROC_CHAR_DSP))
         {
            next_position ();
            set_timer (ST_PROC_CHAR_DSP, NO_RERUN_TIMER, DRV_LCD_TIME);
         }
         break;

      default:
         _screen_refresh = SCREEN_REFRESH_INIT;
         break;
   }
}

void next_position (void)
{
   if (position_x >= POSITION_MAX_X && position_y >= POSITION_MAX_Y) // Это условие нужно намеренно создать в блоке инициализации КА.
   {
      position_x = 0;
      position_y = 0;

      char_dsp_init (); // Инициализация\переинициализация дисплея.
      lcd_send_com (lines [position_y]); // Отправка адреса строки.
      return;
   }

   if (position_x >= POSITION_MAX_X)
   {
      position_x = 0;
      position_y++;

      if (position_y >= POSITION_MAX_Y)
         position_y = 0;

      char_dsp_init (); // Инициализация\переинициализация дисплея.
      lcd_send_com (lines [position_y]); // Отправка адреса строки.
      return;
   }

   lcd_send_data (dsp_buf [(position_y * POSITION_MAX_X) + position_x]); // Отправка символа.
   position_x++;
}
//========================================================================
0
Повторю постановку задачи:
Обновление посимвольно, каждые 1 мс. Если начало строки, отправка адреса строки. Если первая строка, то сначала инициализация\переинициализация дисплея. Это при серьезных сбоях питания дисплея, если не переинициализировать дисплей, на экране будет мельтешня. При дисплее 20х4 получается 20х4+4 адреса строк = 84 мс.
0
А лучше уже намного. Но по функции void next_position (void) есть комментарии:
Сразу бросается в глаза дублирование:
char_dsp_init (); // Инициализация\переинициализация дисплея.
lcd_send_com (lines [position_y]); // Отправка адреса строки.

А это первый признак, что функция недоработана. Как я понял, эти две строки вызываются, когда осуществляется переход на новую строку. Плюс, эта функция делает много разных действия, поэтому ее можно разбить на несколько функций. Как насчет такого?
void LcdUpdateProcess (void)
{
	static bool needLineUpdate = true;
	if (needLineUpdate == true)	{
		LCD_Init();
		LCD_SetLine(y);
		needLineUpdate = false;
		return;
	}
	if ((isEndOfLine(x,y) )	{
		needLineUpdate = true;
	}
	uint8_t data = dspBuf_GetData(x,y);
	lcd_send_data(data);
	
	next_position(&x, &y);
}

void next_position(uint16_t *x, uint16_t *y)
{
	*x++;
	if (*x >= MAX_X)	{
		x = 0;
		*y++;
		if (*y >= MAX_Y)	{
			y = 0;
		}
	}
}

bool isEndOfLine(uint16_t x, uint16_t y)
{
	if (x >= MAX_X)	{
		return true;
	}
	return false;
}

void LCD_Init()
{
	char_dsp_init (); // Инициализация\переинициализация дисплея.
}

void LCD_SetLine(uint16_t y)
{
	lcd_send_com (lines [y]); // Отправка адреса строки.
}

uint8_t Buffer_GetData(uint16_t x, uint16_t y)
{
	return dsp_buf[(x * MAX_X) + y];
}

На истину не претендую.
0
Elisey, за наводку на книжки благодарю. Теперь по поводу этого нещастного модуля. Я и на флагах пробовал писать, и на условиях, и проще, чем на конечном автомате вариантов попросту нет. Все остальные варианты приводят к раздуванию условий и кода. И все только ради гламурности кода.

При анализе задачи я выявил явных 3 состояния модуля. Инициализация, отправка адреса, отправка строки. Я не везде употребляю КА. Но если явных состояний больше 2, скорее всего применю автомат. Вы думаете, зря продвигают автоматное программирование? В вашем же случае накладные расходы (это мое потраченное личное свободное время) для меня уже превысили все мыслимые пределы. Гламурность кода, обворачивание каждого чиха в функцию в данный момент превратилось в чрезмерное стремление и привело к значительному расходу времени, потраченного на этот кусок кода. За книжки благодарю. За советы тоже. Мой окончательный проверенный рабочий вариант ниже. Вполне читабельно, код вполне себе линейный и понятный, больше я к этому куску кода пальцем не прикоснусь. case, на который вы указали я убрал, для этого я изменил одну функцию (из-за которой я тогда и добавил еще один case). Если вы готовы потратить время и выложить готовый рабочий пример, выполняющий все условия моей задачи, я не против. Посмотрим, обсудим. Если продолжение будет, тогда предлагаю создать новую тему в форуме. Дайте знать, я сделаю новый топик. В комментариях сложно ковыряться и нет возможности редактировать свои сообщения.

//========================================================================
// Переинициализация модуля вывода информации из буфера на дисплей.
// Чтобы обновление дисплея началось заново.

void init_dsp_buf (void)
{
   position_x = POSITION_MAX_X;
   position_y = POSITION_MAX_Y;
}
//========================================================================



u08 _screen_refresh;

static const u08 lines [4] = {0x80, 0xC0, 0x94, 0xD4};

static u08 position_x;
static u08 position_y;

void screen_refresh (void)
{
   switch (_screen_refresh)
   {
      case SCREEN_REFRESH_INIT:
         char_dsp_init ();

         clr_dsp_buf ();

         position_x = 0;
         position_y = 0;

         set_timer (ST_SCREEN_REFRESH, NO_RERUN_TIMER, SCREEN_REFRESH_TIME);
         _screen_refresh = SCREEN_REFRESH_SEND_ADDR;
         break;

      case SCREEN_REFRESH_SEND_ADDR:
         if (wait (ST_SCREEN_REFRESH))
         {
            lcd_send_com (lines [position_y]);
            set_timer (ST_SCREEN_REFRESH, NO_RERUN_TIMER, SCREEN_REFRESH_TIME);
            _screen_refresh = SCREEN_REFRESH_SEND_CHAR;
         }
         break;

      case SCREEN_REFRESH_SEND_CHAR:
         if (wait (ST_SCREEN_REFRESH))
         {
            lcd_send_data (dsp_buf [(position_y * POSITION_MAX_X) + position_x]);
            position_x++;
            if (position_x >= POSITION_MAX_X)
            {
               position_x = 0;
               position_y++;
               if (position_y >= POSITION_MAX_Y)
               {
                  position_y = 0;

                  char_dsp_init ();
               }
               _screen_refresh = SCREEN_REFRESH_SEND_ADDR;
            }
            set_timer (ST_SCREEN_REFRESH, NO_RERUN_TIMER, SCREEN_REFRESH_TIME);
         }
         break;

      default:
         _screen_refresh = SCREEN_REFRESH_INIT;
         break;
   }
}
//========================================================================
0
void delete_task (void (*task_func)(void)) // Удаление задачи из очереди.
ну кто так делает удаление? а если есть несколько задач с одинаковым task_func но вызываемых с разными периодами для разного «контекста»? не дублировать же код в самом деле.
и насчет «контекста» задачи. будет удобно, если с каждой задачей будет связывается т.н. «контекст» — переменная типа void* которая передается параметром в функцию задачи. переменная может хранить указатель на «контекст» задачи, может каститься в int, или быть просто NULL — это на усмотрение. Данная переменная с одной стороны идентифицирует конкретную задачу, с другой — позволяет использовать один и тот же код задачи для операций над разными «контекстами».
пример: есть две задачи — одна мигает синим led-ом с периодом 10мс, другая — красным с периодом 500мс. Для обеих можно использовать одну и ту же функцию void blink(void * param), вся разница в выводах порта на которых «сидят» led-ы. номер вывода порта можно передавать как «контекст» внутрь задачи.
+2
  • avatar
  • 6502
  • 03 сентября 2014, 18:09
Вполне нормальный вариант. Использовать одну функцию для разного «контекста» не стоит. Можно запутаться в логике программы. ИМХО. К тому же в абсолютном большинстве программ это не нужно.
Что касается этого диспетчера, и других похожих на него, то все они работают с указателями функций. Реализовать параметры конечно можно, с помощью глобальных переменных, но это уже вряд ли назовешь красивым решением.
0
Не соглашусь. ИМХО проще запутаться когда имеется куча функций выполняющих схожие задачи. Больше кода -> больше вероятность ошибки. Очень часто такой подход с выделением контекста облегчал жизнь, плюс имеем очевидный выигрыш от экономии flash. Хранить контекст можно не только в области глобальных/статических переменных. Конкретная структура контекста зависит от самой задачи, что в неё входит определяется создателем задачи. В структуре задачи достаточно хранить только указатель на контекст, который внутри функции будет каститься к чему-то конкретному, будь то структура входящего пакета данных подлежащего обработке, состояния светодиодов…
Сам контекст можно хранить на куче или в пуле и передавать указатель на него в задачу при её создании. По завершении работы таск сам освободит свой контекст (внутри функции задачи). В случае если не требуется «тяжелый» контекст и можно обойтись одной переменной — можно просто передавать ее как параметр задачи приводя значение к void*.
+1
А, кстати, наоборот, хорошо и широко используется. Функция, которая является повторно-входимой является реентабельной (не использует глобалные переменные и ресурсы). Ее можно использовать для выполнения схожих действий, передавая ей параметр действия. Можно даже рекурсию применять. Главное помнить о стеке. Реентабельность устраняет дублирование и делает программу более выразительной. При правильном подходе, разумеется.
0
Вообще, переменная для хранения пользовательского контекста обычно используется везде, где есть callback'и — например, переменные user во всех коллбэках WINAPI.
+1
  • avatar
  • Vga
  • 03 сентября 2014, 22:41
Поддерживаю Ваш подход с передачей указателя.

Единственный момент:

 Данная переменная с одной стороны идентифицирует конкретную задачу, с другой

Если допускается, что в качестве указателя на контекст можно передавать значение, которое потом «каститься» в что-то,

переменная может хранить указатель на «контекст» задачи, может каститься в int, или быть просто NULL — это на усмотрение.

то такая переменная не может однозначно идентифицировать задачу (например, две задачи, с разными «обработчиками» и с NULL  в качестве параметра и т. д.). Но этот вопрос легко решается.
0
Я на объект поехал, вызвали. Предлагаю создать в форуме топик. Не только касаемо именно это диспетчера, но и другие вопросы относящиеся к этой теме. Если отмашку дадите, вечером создам новый топик в форуме.
0
Почему бы не обсудить прямо тут? Я не хочу еще и вкладку с форумом открывать.
0
  • avatar
  • Vga
  • 04 сентября 2014, 11:50
Потому что сообщения нельзя отредактировать, и на форуме удобнее читать. Можно процитировать собеседнику и сразу понятно о чем речь. В комментариях сообщения могут быть разбросаны по всей странице, а на форуме сообщения идут линейно.
0
Цитаты и тут есть, а древовидные комментарии напротив, удобнее для обсуждения. Что редактирования нет — с этим согласен.
0
  • avatar
  • Vga
  • 04 сентября 2014, 15:48
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.