Перейти к содержанию
  • запись
    21
  • комментариев
    136
  • просмотра
    3 082

Минималистическая RTOS


ARV

1 009 просмотров

В моём гнезде прибавление.

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

Преамбула.

Что мы понимаем под понятием "таймер"? Ну, не в смысле задатчика времени варки яиц всмятку, а в программировании? Это некая функция, которая "сама по себе" выполняется через заданные интервалы времени. Или же чуть иначе: функция выполнится через заданный интервал времени однократно. Наконец, и третья интерпретация тоже имеет место быть: таймер - это некий счетчик, который сам по себе считает, а мы можем время от времени поглядывать на его значение и принимать какие-то решения.

Амбула.

Как обычно реализуются таймеры в микроконтроллерном программировании? Безусловно, наиболее удобно - с задействованием аппаратных таймеров-счетчиков и прерываний от них. Существует вариант реализации и без этого, но сие удовольствие надо оставить пациентам более строгого режима лечения. С прерыванием от аппаратного таймера все понятно, но их количество (аппаратных счетчиков я имею ввиду) ограничено. И поэтому в общем случае используется модель "программных" таймеров на основе одного прерывания от аппаратного. Вот как, например, выглядит один из простейших вариантов:

#define TIMER_CNT	5
static volatile uint8_t timer[TIMER_CNT];

// обработчик прерывания от таймера, вызывается каждую миллисекунду
ISR(TIMER_vect){
  for(uint8_t i=0; i<TIMER_CNT; i++){
    if(timer[i]) timer[i]--;
  }
}

// вот так можно ограничить длительность цикла интервалом времени в 100 мс
timer[0] = 100;
while(timer[0]){
  // что-то длительное
  if(какое-то-условие-неизвестно-когда-возникающее) break;
}
if(!timer[0]){
  // из цикла вышли по таймауту
} else {
  // из цикла вышли по условию
}

Вроде бы, все просто и понятно. И даже удобно. Я сам 100 раз так делал!

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

Путем нехитрых манипуляций можно заметно улучшить ситуацию. Хотя и несколько усложнив код:

#include <util/atomic.h>
  
#define TIMER_CNT	5

typedef uint8_t (*tmr_func)(void);  
typedef struct{
  uint16_t	counter;
  uint16_t	duration;
  tmr_func	func;
} timer_t;

static volatile timer_t timer[TIMER_CNT];

ISR(TIMER_vect){
  for(uint8_t i=0; i<TIMER_CNT; i++){
    if(timer[i].counter){
      timer[i].counter--;
      if((timer[i].counter == 0) && (timer[i].func != NULL))
        if(timer[i].func()) timer[i].counter = timer[i].duration;
    }
  }
}

void timer_start(uint8_t t, uint16_t duration, tmr_func f){
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
    timer[t].counter = duration;
    timer[t].duration = duration;
    timer[t].func = f;
  }
}

uint8_t timer_out(uint8_t t){
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
    return timer[t].counter == 0;
  }
}

// вот так можно заставить светодиоды мигать с разной частотой
static uint8_t blink_led1(void){
  PORTB ^= 1<<0; // светодиод на нулевой линии порта
  return 1; // для перезапуска функции
}

static uint8_t blink_led2(void){
  PORTB ^= 1<<1; // светодиод на первой линии порта
  return 1; // для перезапуска функции
}

timer_start(0, 500, blink_led1);
timer_start(0, 300, blink_led2);

while(1){
  // тут что-то делаем, а светодиоды тем временем мигают каждый по-своему
}

Разумеется, здесь уже и атомарность доступа к значению счетчика реализована (ценой вызова отдельной функции), и все вариаты из преамбулы тоже. Надеюсь, очевидно, что если переданная в таймер функция вернет 0, она больше не будет вызываться после того, как таймер истечет? Чем вам не RTOS в минимальном виде? Главное условие в применимости такого подхода - предельно быстрое исполнение таймерной функции. Но при использовании автоматов состояний этим способом можно решать большой спектр практических задач.

Но проблема с "учетом" таймеров осталась. Да и если вы вдруг станете нуждаться в бОльшем количестве таймеров, чем TIMER_CNT, вам придется эту константу менять. И в случае, если вы модифицируете старый проект, и старое количество таймеров вам не нужно, то тоже надо это вручную менять. Мелочь, а неприятно.

Хорошо было бы, если бы в любом месте кода описал свой отдельный static (т.е. невидимый другим) таймер, и пользуешься им. Не нужен -удалил его описание, и не пользуешься. А "система" сама заботится о том, чтобы таймер "тикал" или не "тикал", если не нужен.

И обычно для этого используют возможности RTOS.

Хотя... Хотя максимальное количество выделяемых RTOS таймеров позапросу пользователя тоже ограничено значением какой-то константы... Но и из этого исхода есть выход! Только об этом в следующий раз. Т.е. о самом главном я и не сказал...

2 Комментария


Рекомендуемые комментарии

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

Ссылка на комментарий
2 часа назад, Геннадий сказал:

Удачное решение

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

Хотел написать о том, как можно вообще забыть про количество таймеров при написании кода, а вышло, что об этом ни слова... Ничего, передохну немного и напишу.

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы публикуете как гость. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Добавить комментарий...

×   Вставлено с форматированием.   Восстановить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

Загрузка...
×
×
  • Создать...