Организация программ. Конечные автоматы. Программные таймеры.

Сначала я хотел написать статью об очередной реализации программных таймеров. Попутно хотел поднять одну тему и понял, что программные таймеры — составная часть это темы. Тема касается организации программ, взаимодействия программных модулей и затрагивает автоматное программирование, то есть конечные автоматы.

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

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

Этот вариант реализации программных таймеров я использую в своих проектах уже несколько лет. До этого я перебрал кучу вариантов. Все они чем-нибудь да не устраивали. В конце концов я составил подробное ТЗ. Требования были следующие:

Системный тик 1 мс. 10 мс.
Однократный запуск.
Периодический запуск.
Отложенное исполнение.
Немедленное исполнение.

В итоге получилось следующее:

Определения:

//========================================================================
#ifndef SOFT_TIMERS_H

#define SOFT_TIMERS_H

#include "soft_timers.h"

#include "main_def_func.h"
//========================================================================

//========================================================================
#define __ST_NO_INTERRUPT__ // __ST_NO_INTERRUPT__ // __ST_INTERRUPT__
#define __ST_NO_PERIOD__ // __ST_PERIOD__ // __ST_NO_PERIOD__
//========================================================================

//========================================================================
#define ST_TCNT         TCNT2
#define ST_TIMSK        TIMSK
#define ST_OCIE         OCIE2
#define ST_OCR          OCR2
#define ST_TCCR         TCCR2
#define CS0             CS20
#define CS1             CS21
#define CS2             CS22
//========================================================================

//========================================================================
typedef struct soft_timer
{
   bool status;
   u08 sys_tick_prev;
   u16 cnt;       // Counter.

#ifdef __ST_PERIOD__
   u16 period;    // Period.
#endif
} soft_timer;
//========================================================================

//========================================================================
void init_soft_timers (void);

#define handle_soft_timer(a) _handle_soft_timer (&a)
bool _handle_soft_timer (soft_timer *ptr_timer);

#ifdef __ST_PERIOD__
#define set_soft_timer(a,b,c) _set_soft_timer (&a, b, c)
void _set_soft_timer (soft_timer *ptr_timer, u16 time, u16 period);
#endif

#ifdef __ST_NO_PERIOD__
#define set_soft_timer(a,b) _set_soft_timer (&a, b)
void _set_soft_timer (soft_timer *ptr_timer, u16 time);
#endif

#define reset_soft_timer(a) _reset_soft_timer (&a)
void _reset_soft_timer (soft_timer *ptr_timer);

void proc_sys_tick (void);
//========================================================================

#endif


Функции:

//========================================================================
#include "soft_timers.h"
//========================================================================

//========================================================================
static u08 sys_tick;
//========================================================================

//========================================================================
#ifdef __ST_INTERRUPT__
#pragma vector = TIMER2_COMP_vect
__interrupt void SysTimerComp (void)
{
   ST_OCR += 250;
   sys_tick++;
}
#endif
//========================================================================

//========================================================================
void init_soft_timers (void)
{
   sys_tick = 0;
   ST_TCNT = 0;
   ST_OCR = 250;
   ST_TCCR |= (1<<CS2);

#ifdef __ST_INTERRUPT__
   set_bit (ST_TIMSK, ST_OCIE);
#endif
}
//------------------------------------------------------------------------
#ifdef __ST_PERIOD__
void _set_soft_timer (soft_timer *ptr_timer, u16 time, u16 period)
#else
void _set_soft_timer (soft_timer *ptr_timer, u16 time)
#endif
{
   ptr_timer -> status = true;

   if (time == 0)
      ptr_timer -> sys_tick_prev = ~sys_tick;
   else
      ptr_timer -> sys_tick_prev = sys_tick;

   ptr_timer -> cnt = time;

#ifdef __ST_PERIOD__
   ptr_timer -> period = period;
#endif
}
//------------------------------------------------------------------------
bool _handle_soft_timer (soft_timer *ptr_timer)
{
   if (ptr_timer -> status)
   {
      if (ptr_timer -> sys_tick_prev != sys_tick)
      {
         ptr_timer -> sys_tick_prev = sys_tick;

         if (ptr_timer -> cnt == 0)
         {
            #ifdef __ST_PERIOD__
            if (ptr_timer -> period != 0)
               ptr_timer -> cnt = ptr_timer -> period;
            #endif

            return true;
         }
         else
         {
            ptr_timer -> cnt--;

            if (ptr_timer -> cnt == 0)
            {
               #ifdef __ST_PERIOD__
               if (ptr_timer -> period == 0)
                  ptr_timer -> status = false;
               else
                  ptr_timer -> cnt = ptr_timer -> period;
               #else
               ptr_timer -> status = false;
               #endif

               return true;
            }
         }
      }
   }

   return false;
}
//------------------------------------------------------------------------
void _reset_soft_timer (soft_timer *ptr_timer)
{
   ptr_timer -> status = false;
   ptr_timer -> sys_tick_prev = 0;
   ptr_timer -> cnt = 0;

#ifdef __ST_PERIOD__
   ptr_timer -> period = 0;
#endif
}
//------------------------------------------------------------------------
#ifdef __ST_NO_INTERRUPT__
void proc_sys_tick (void)
{
   static u08 _proc_sys_tick;

   switch (_proc_sys_tick)
   {
      case 0:
         init_soft_timers ();
         _proc_sys_tick = 1;
         break;

      case 1:
         if (TIFR & (1<<OCF2))
         {
            TIFR = (1<<OCF2);
            ST_OCR += 250;
            sys_tick++;
         }
         break;
   }
}
#endif
//========================================================================


main.c:

//========================================================================
__C_task main (void)
{
   wdt_enable (WDTO_15_MS); // Инициализаця сторожевого таймера. 16 мс.

// Иницилизация программных таймеров. Прерывание аппаратного таймера включено.
#ifdef __ST_INTERRUPT__
   init_soft_timers (); // Иницилизация программных таймеров
#endif

   __enable_interrupt (); // Глобальное разрешение прерываний.

   while (1)
   {
      __watchdog_reset (); // Сброс сторожевого таймера.

// Обработчик счетчика системных тиков. Прерывание аппаратного таймера отключено.
#ifdef __ST_NO_INTERRUPT__
      proc_sys_tick ();
#endif

      proc_device (); // Главный алгоритм программы.
   }
}
//========================================================================


Макросы:

//========================================================================
#define set_bit(reg, bit)  reg |= (1<<(bit))    // Установка бита.
#define clr_bit(reg, bit)	reg &= (~(1<<(bit))) // Сброс бита.
#define switch_bit(reg, bit)  reg ^= (1<<(bit)) // Переключение бита.
//------------------------------------------------------------------------
#define check_bit(reg, bit) (reg & (1<<bit))    // Проверка бита.
//========================================================================

//========================================================================
#define __LED_PORT_METHOD__ // __LED_PORT_METHOD__ // __LED_DDR_METHOD__
//========================================================================

//========================================================================
#ifdef __LED_DDR_METHOD__
#define led_1_on()     set_bit(LED_1_DDR,LED_1)
#define led_1_off()    clr_bit(LED_1_DDR,LED_1)
#define led_1_switch() switch_bit(LED_1_DDR,LED_1)
#else
#define led_1_on()     set_bit(LED_1_PORT,LED_1)
#define led_1_off()    clr_bit(LED_1_PORT,LED_1)
#define led_1_switch() switch_bit(LED_1_PORT,LED_1)
#endif
//------------------------------------------------------------------------
void set_proc_led_1_run (void);
void set_proc_led_1_off (void);
void proc_led_1 (void);
//------------------------------------------------------------------------

//========================================================================
void led_1_blink (void);
//========================================================================


Функции:

//========================================================================
static u08 _proc_led_1;

static soft_timer ST_PROC_LED_1;
static soft_timer ST_PROC_LED_2;
static soft_timer ST_PROC_LED_3;
static soft_timer ST_PROC_LED_4;
static soft_timer ST_PROC_LED_5;
static soft_timer ST_PROC_LED_6;
static soft_timer ST_PROC_LED_7;
static soft_timer ST_PROC_LED_8;

void set_proc_led_1_run (void)
{
   led_1_off ();
   _proc_led_1 = 1;
}

void set_proc_led_1_off (void)
{
   led_1_off ();
   _proc_led_1 = 0;
}

void proc_led_1 (void)
{
   switch (_proc_led_1)
   {
      case 0:
         break; // Idle.

      case 1:
         #ifdef __ST_PERIOD__
         set_soft_timer (ST_PROC_LED_1, 0, 10);
         #else
         set_soft_timer (ST_PROC_LED_1, 0); // !!!!!!!!!!!!!!!!!!!!!!!!!!!
         #endif
         _proc_led_1 = 2;
         break;

      case 2:
         if (handle_soft_timer (ST_PROC_LED_1))
         {
            if (!(LED_1_DDR & (1<<LED_1)))
               led_1_on ();
            else
               led_1_off ();
         }
         break;
   }
}
//========================================================================

//========================================================================
// Здесь самое интересное. Умение обходиться теми инструментами,
// что имеются в наличии. Конечный автомат на имеющихся инструментах.
// Первый пример работает на способе управления пином МК - открытый сток.
// Управление регистром DDRx

// Этот пример уже работает на пине настроенном как выход.
// Управление регистром PORTx

void led_1_blink (void)
{
   if (!(LED_1_DDR & (1<<LED_1)))
   {
      set_bit (LED_1_DDR, LED_1);

      #ifdef __ST_PERIOD__
      set_soft_timer (ST_PROC_LED_1, 0, 10);
      #else
      set_soft_timer (ST_PROC_LED_1, 0);
      #endif
   }

   if (handle_soft_timer (ST_PROC_LED_1))
   {
      if (!(LED_1_PORT & (1<<LED_1)))
         led_1_on ();
      else
         led_1_off ();

      #ifdef __ST_NO_PERIOD__
      set_soft_timer (ST_PROC_LED_1, 10);
      #endif
   }
}
//------------------------------------------------------------------------


Главный алгоритм программы:

//========================================================================
void proc_device (void)
{
   static u08 _proc_device;

   switch (_proc_device)
   {
      case 0: // Блок инициализации.
         #ifdef __ST_PERIOD__
         set_proc_led_1_run (); // Запуск автомата свтодиода.
         set_proc_led_2_run ();
         set_proc_led_3_run ();
         set_proc_led_4_run ();
         set_proc_led_5_run ();
         set_proc_led_6_run ();
         set_proc_led_7_run ();
         set_proc_led_8_run ();
         #endif

         _proc_device = 1;
         break;

      case 1:
         #ifdef __ST_PERIOD__
         proc_led_1 (); // 
         proc_led_2 ();
         proc_led_3 ();
         proc_led_4 ();
         proc_led_5 ();
         proc_led_6 ();
         proc_led_7 ();
         proc_led_8 ();
         #else
         led_1_blink ();
         led_2_blink ();
         led_3_blink ();
         led_4_blink ();
         led_5_blink ();
         led_6_blink ();
         led_7_blink ();
         led_8_blink ();
         #endif
         break;
   }
}
//========================================================================


Продолжение следует.

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

RSS свернуть / развернуть
Странноват код местами.
#ifndef SOFT_TIMERS_H
#define SOFT_TIMERS_H
#include "soft_timers.h"

Зачем из хедера включать самого себя? Это бессмысленно.
#define handle_soft_timer(a) _handle_soft_timer (&a)
bool _handle_soft_timer (void *ptr_timer);

Дефайн, по-видимому, обусловлен нежеланием писать каждый раз &. Ну, ОК. Но чем не угодили типизированные указатели? И некоторая защита от передачи левого значения, и не требуются struct soft_timer *ptr = ptr_timer в каждой функции.

Имея в структуре prev_sys_tick — можно этим воспользоваться для определения, сколько тиков прошло между вызовами handle. Вместо этого оно используется только для проверки, не вызвали ли handle несколько раз за тик.

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

Ну и немного странно, что в bool пишется то true, то 0. Из разряда «вы или крестик снимите, или трусы наденьте».

По функциональности сказать нечего. Она вполне типична. Хотя я бы, вероятно, постарался собрать таймеры в единую структуру и обрабатывать скопом.
0
  • avatar
  • Vga
  • 03 июня 2018, 15:16
1 — Защита от перекомпилирования, на тот случай, если хедер где-то еще добавлен.
2 — Да, дефайн написан для облегчения написания.
3 — Нет смысла выполнять какие-либо арифметические действия над счетчиком тиков. Именно поэтому он u08. Выполняется только сравнение предыдущего и текущего состояния. Если изменилось, значит тик произошел, декремент заданного значения времени.

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

#ifndef SOFT_TIMERS_H
#define SOFT_TIMERS_H

А вот писать
#include "soft_timers.h"

в самом файле soft_timers.h бессмысленно.
P.S. Иногда встречал в чужом коде и такое :)
#include "file.c"
0
P.S. Иногда встречал в чужом коде и такое :)
#include «file.c»
Ну, это двоякий момент. Бывает, когда человек это делает намеренно и с полным пониманием того, что он делает. Пример — Оберхеймеровские сырки, скажем LZO или UCL.
Бывает, конечно, и даже чаще, что это просто чайник…
0
1 — Защита от перекомпилирования, на тот случай, если хедер где-то еще добавлен.
Ты невнимательно читаешь. Впрочем, это уже разжевали комментом выше.
2 — Да, дефайн написан для облегчения написания.
Ты невнимательно читаешь. Вопрос был «чем тебе не угодил типизированный указатель».
Но я вижу, что это ты уже пофиксил. Но пофиксил неправильно. Во-первых, не soft_timer *ptr_timer, а struct soft_timer *ptr_timer. Во-вторых, «soft_timer *ptr = ptr_timer;» из всех функций нужно выкинуть, а все ptr в коде функций заменить на ptr_timer. И напоследок — скормить компилятору и убедиться, что у него нет претензий — которые обычно намекают, что ты что-то забыл или сделал не так.
3 — Нет смысла выполнять какие-либо арифметические действия над счетчиком тиков.
Напротив, есть. Можно вычесть из текущего предыдущий и узнать, что с момента прошлого вызова функции прошло 10 тиков. После чего вычесть из cnt 10 тиков (здесь надо учитывать тот случай, когда в cnt менее 10 тиков и даже случай, когда период менее 10 тиков и надо щелкнуть таймер несколько раз).
0
Благодарю за замечания. Пофиксил. Остальное позже. Занят.
0
Автор топика запретил добавлять комментарии