Многозадачный программный таймер.

В мире микроконтроллеров, как и в мире вообще — существуют разного рода задачи. Какие-то требуют немедленного исполнения, какие-то могут подождать. Но есть и такие, которые возникают крайне редко, но их количество может быть внушительным. Вот о реализации таких задач (последних) мы и поговорим. Чтобы «убить» 7 мух(, а может быть и великанов) одним ударом — будем использовать программный таймер на базе одного аппаратного. Так как все задачи — «медленноприходящие» (интересно, есть такое слово в русском языке?) — настроим аппаратный таймер таким образом, чтобы тикал он несколько раз в секунду (например, 50 или даже 500). И создадим массив структур, в каждой из которых будет лежать вся информация по каждому программному таймеру отдельно. Итак, рассмотрим чего добился:

Удобной первичной настройки программного таймера благодаря «всемогущему» препроцессору.
Удобной инициализации вложенных таймеров.

Хочу поделиться с вами исходниками и, как всегда, прежде всего вкусностями препроцессора. Файл Slow_timer.h состряпан таким образом, что для настройки таймера требуется минимум настроек, таких как выбор аппаратного таймера, частота срабатывания и кол-во программных ячеек(таймеров).

#define SLOW_TIMER_COUNT 		10
#define SLOW_TIMER_NUMBER		5			//Timer == (1..15) or SYSTICK (==0)
#define SLOW_TIMER_FREQUENCY		500			// 5..1000

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

Итак, сам Slow_timer.h

#ifndef SLOW_TIMER_H
#define SLOW_TIMER_H

#define SLOW_TIMER_COUNT                20
#define SLOW_TIMER_NUMBER               0              //Timer 1..15 or SYSTICK (==0)
#define SLOW_TIMER_FREQUENCY            500            // 5..1000

typedef enum
{
 tm_Not_Change,
 tm_Stop,
 tm_Repeat,
 tm_Once
} Timer_Mode_t;

#include <stdint.h>

void Init_Slow_Timer(void);
int8_t Slow_Timer_Add(Timer_Mode_t mode, uint16_t compare, void (*handler)());
uint16_t Slow_Timer_Get_Counter(int8_t TimerId);
void Slow_Timer_Delete(int8_t TimerId);
void Slow_Timer_Modify(int8_t TimerId, Timer_Mode_t mode, uint16_t counter, uint16_t compare);
void Slow_Timer_Lock(void);
void Slow_Timer_Unlock(void);
#endif


и файл Slow_timer.c:

#include "Slow_timer.h"
#include "bitbanding.h"
#include "stm32f10x.h"

#if (SLOW_TIMER_NUMBER == 1)||(SLOW_TIMER_NUMBER==8)||(SLOW_TIMER_NUMBER==9)||(SLOW_TIMER_NUMBER==10)||(SLOW_TIMER_NUMBER==11)
#define SYS_FREQ_TIMER                          SystemFrequency_APB2Clk
#define TIMER_IRQ_SUFFIX                        _UP_
#define TIMER_SLOW_APBENR                       APB2ENR
#else
#define SYS_FREQ_TIMER                         (SystemFrequency_APB1Clk*2)
#define TIMER_IRQ_SUFFIX                        _
#define TIMER_SLOW_APBENR                       APB1ENR
#endif

#define __SLOW_TIMER(N)                         TIM##N
#define _SLOW_TIMER(N)                          __SLOW_TIMER(N)
#define SLOW_TIMER                              _SLOW_TIMER(SLOW_TIMER_NUMBER)

#define __SLOW_TIMER_IRQH(N,S)                  TIM##N##S##IRQHandler
#define _SLOW_TIMER_IRQH(N,S)                   __SLOW_TIMER_IRQH(N,S)

#if SLOW_TIMER_NUMBER>0
#define SLOW_TIMER_IRQHandler                   _SLOW_TIMER_IRQH(SLOW_TIMER_NUMBER, TIMER_IRQ_SUFFIX)
#else
#define SLOW_TIMER_IRQHandler                   SysTick_Handler
#endif

#define __SLOW_TIMER_IRQN(N,S)                  TIM##N##S##IRQn
#define _SLOW_TIMER_IRQN(N,S)                   __SLOW_TIMER_IRQN(N,S)
#define SLOW_TIMER_IRQn                         _SLOW_TIMER_IRQN(SLOW_TIMER_NUMBER, TIMER_IRQ_SUFFIX)

#define __SLOW_TIMER_RCC_EN(A, N)               RCC_##A##_TIM##N##EN
#define _SLOW_TIMER_RCC_EN(A, N)                __SLOW_TIMER_RCC_EN(A, N)
#define SLOW_TIMER_RCC_EN                       _SLOW_TIMER_RCC_EN(TIMER_SLOW_APBENR, SLOW_TIMER_NUMBER)

#define SLOW_TIMER_PRESCALER                    ((SYS_FREQ_TIMER/100000)-1)
#define SLOW_TIMER_PERIOD                       ((100000/SLOW_TIMER_FREQUENCY)-1)

typedef struct
{
   uint8_t timer_mode;
   uint16_t counter;
   uint16_t compare;
   void (*handler)();
} Slow_Timer_t;

volatile Slow_Timer_t Slow_Timers[SLOW_TIMER_COUNT];

void Slow_Timer_Lock(void)
{
#if SLOW_TIMER_NUMBER > 0
  BIT_BAND_PER(SLOW_TIMER->DIER,TIM_DIER_UIE)=RESET;
#else
  SysTick->CTRL&=~(SysTick_CTRL_TICKINT_Msk);
#endif
}

void Slow_Timer_Unlock(void)
{
#if SLOW_TIMER_NUMBER > 0
  BIT_BAND_PER(SLOW_TIMER->DIER,TIM_DIER_UIE)=SET;
#else
  SysTick->CTRL|=(SysTick_CTRL_TICKINT_Msk);
#endif
}

void Slow_Timer_Delete(int8_t TimerId)
{
  if (TimerId<SLOW_TIMER_COUNT)
  {
    Slow_Timers[TimerId].timer_mode=tm_Not_Change;
    Slow_Timers[TimerId].handler=0;
  }
}

void Init_Slow_Timer(void)
{
  uint8_t i;
  for(i=0;i<SLOW_TIMER_COUNT;i++)
    Slow_Timer_Delete(i);
  #if SLOW_TIMER_NUMBER > 0
    BIT_BAND_PER(RCC->TIMER_SLOW_APBENR,SLOW_TIMER_RCC_EN)=SET;
    SLOW_TIMER->PSC = SLOW_TIMER_PRESCALER;
    SLOW_TIMER->CNT = 0x0000;
    SLOW_TIMER->ARR = SLOW_TIMER_PERIOD;
    NVIC_SetPriority(SLOW_TIMER_IRQn,15);
    NVIC_EnableIRQ(SLOW_TIMER_IRQn);
    BIT_BAND_PER(SLOW_TIMER->EGR,TIM_EGR_UG)=SET; 
    BIT_BAND_PER(SLOW_TIMER->SR,TIM_SR_UIF)=SET;
    BIT_BAND_PER(SLOW_TIMER->DIER,TIM_DIER_UIE)=SET;
    BIT_BAND_PER(SLOW_TIMER->CR1,TIM_CR1_CEN)=SET;
  #else
    SysTick_Config(SystemFrequency/SLOW_TIMER_FREQUENCY);
  #endif
}

void Slow_Timer_Modify(int8_t TimerId, Timer_Mode_t mode, uint16_t counter, uint16_t compare)
{
  if ((TimerId<SLOW_TIMER_COUNT)&&(TimerId>=0))
  {  
    Slow_Timer_Lock();
    if (mode!=tm_Not_Change)
      Slow_Timers[TimerId].timer_mode = mode;
    if (counter)  
      Slow_Timers[TimerId].counter = counter;
    if (compare)  
      Slow_Timers[TimerId].compare = compare;
    Slow_Timer_Unlock();
  }
}

int8_t Slow_Timer_Add(Timer_Mode_t mode, uint16_t compare, void (*handler)())
{
  uint8_t i;
  if(mode!=tm_Not_Change)
  {
    for (i=0;i<SLOW_TIMER_COUNT;i++)
    {
      if (Slow_Timers[i].timer_mode==tm_Not_Change)
      {
        Slow_Timers[i].counter = 1;
        Slow_Timers[i].compare = compare;
        Slow_Timers[i].handler = handler;
        Slow_Timers[i].timer_mode = mode;
        return i;        
      }
    }
  }
  return -1;
}

uint16_t Slow_Timer_Get_Counter(int8_t TimerId)
{
  uint16_t counter=0;
  if ((TimerId>=0)&&(TimerId<SLOW_TIMER_COUNT))  
  {
    Slow_Timer_Lock();
    counter=Slow_Timers[TimerId].counter;
    Slow_Timer_Unlock();
  }
  return counter;
}

void SLOW_TIMER_IRQHandler(void)
{
  uint8_t i;
  #if SLOW_TIMER_NUMBER > 0
    BIT_BAND_PER(SLOW_TIMER->SR,TIM_SR_UIF)=RESET;
  #endif
  for(i=0;i<SLOW_TIMER_COUNT;i++)
  {
    if(Slow_Timers[i].timer_mode > tm_Stop)
    {
      if(Slow_Timers[i].counter >= Slow_Timers[i].compare)
      {
        Slow_Timers[i].counter = 1;
        if(Slow_Timers[i].timer_mode==tm_Once)
          Slow_Timers[i].timer_mode = tm_Stop;
        if (Slow_Timers[i].handler)
          Slow_Timers[i].handler();
      }
      else
        Slow_Timers[i].counter++;
    }
  }
}


Для инициализации программного таймера в системе запустим функцию:
Init_Slow_Timer();


Чтобы нам стартовать (инициализировать) вложенный программный таймер, нужно выполнить функцию:
uint8_t Slow_Timer_Add(Timer_Mode_t mode, uint16_t compare, void (*handler)())

где:
mode — режим таймера. Их три: выключен (tm_Stop), включен однократно (tm_Once) или постоянно (tm_Repeat)
compare — число сравнения. Если вы укажете здесь значение, равное 0 или 1 — таймер «тикнет» один раз. Если 2 — 2 раза и так далее…
handler — указатель на функцию, которая будет вызываться при срабатывании таймера.

Чтобы вычислить период задержки — нужно пользоваться несложной формулой:
VALUE = SLOW_TIMER_FREQUENCY * PERIOD, где:
VALUE — значение, записываемое в counter;
SLOW_TIMER_FREQUENCY — константа, определяющая частоту прерываний аппаратного таймера
PERIOD — требуемое время задержки в секундах.

Если мы хотим вычислить частоту вызова — тогда здесь другая формула:
VALUE = SLOW_TIMER_FREQUENCY / FREQUENCY, где:
VALUE, SLOW_TIMER_FREQUENCY аналогично
FREQUENCY — требуемая частота вызова функции в Гц. Учтите, что частота вызова не может быть больше частоты работы самого таймера и будет всегда кратна значению SLOW_TIMER_FREQUENCY.

Функция возвращает число: идентификатор программного таймера или -1, если таймер инициализировать не удалось. Таким образом, если потребуется в дальнейшем этот таймер конфигурировать — то это значение нужно сохранить, например в глобальной переменной Keyboard_Timer_Id (так мы обозвали переменную в модуле опроса клавиатуры):
Keyboard_Timer_Id = Slow_Timer_Add(tm_Repeat, SLOW_TIMER_FREQUENCY / 50 /*50Гц*/, KeyboardExe);


Конфигурация программного таймера доступна через функцию Slow_Timer_Modify. Вот ее описание:

void Slow_Timer_Modify(int8_t TimerId, Timer_Mode_t mode, uint16_t counter, uint16_t compare);

где:
TimerId — идентификатор таймера
mode — режим таймера (аналогично как в функции Slow_Timer_Add)
counter — программный счетчик. Если таймер активен(см. timer_mode), то этот параметр инкрементируется на 1 при каждом срабатывании прерывания таймера до тех пор, пока не это число не станет равным compare(см. ниже). После этого значение сбрасывается (присваивается 1), и происходит вызов функции handler (см. выше).
compare — число сравнения.
Если параметр mode изменять не требуется — пишем туда — tm_Not_Change. Если параметр compare или counter изменять не требуется — пишем 0 в соответствующие поля.

Чтобы узнать, сколько времени прошло с момента, когда мы таймер запустили — используйте функцию Slow_Timer_Get_Counter:
uint16_t counter = Slow_Timer_Get_Counter(int8_t TimerId);


Чтобы удалить таймер, используйте функцию Slow_Timer_Delete:
Slow_Timer_Delete(int8_t TimerId);

, где единственным параметром этой функции является идентификатор TimerId.

Пример: Нам нужно по какому-то событию включить светодиод и выключить его спустя 5 секунд. Задачу можно решить следующим образом:

#include "Slow_timer.h"
.....
int main (void)
{
.....
  Init_Slow_Timer();//инициализируем Slow Timer
  Init_Led(); //инициализируем светодиод
.....
  Led_On(); // Где-то в программе включаем светодиод. Через нужное кол-во сек он сам погаснет
}
// где-то в модуле, например в "led.c"
#define LED_LIGHT_PERIOD     5
int8_t Led_Timer_Id;

void Led_Off()
{
  LED_OFF;
}

//Инициализация таймера:
void Init_Led(void)
{
  Led_Timer_Id=Slow_Timer_Add(tm_Stop, SLOW_TIMER_FREQUENCY * LED_LIGHT_PERIOD, Led_Off);
  .....
}

//Включение светодиода:
void Led_On(void)
{
  LED_ON; // какой-то там макрос или команда, включающая светодиод
  Slow_Timer_Modify(Led_Timer_Id, tm_Once, 1, 0); //Запускаем таймер для выключения светодиода через 5 сек.
//                        ↑          ↑     ↑  ↑
// идентификатор таймера ─┘          │     │  │
// режим однократного срабатывания ──┘     │  │
// сбрасываем счетчик ─────────────────────┘  │
// период оставляем без изменений ────────────┘
}


Ну вот и все!

Ах, да… В этом модуле активно используется другой, мною написанный модуль bitbanding.h Его обсуждение тоже имеется на этом же интернет ресурсе здесь

PS: Отдельная благодарность teplofizik и Vga за активное участие в процессе становления сего продухта))

UPD:
Продолжение жизни данного таймера смотрите в следующей статье
  • +4
  • 06 августа 2013, 18:42
  • Mihail

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

RSS свернуть / развернуть
жду комментариев!)
0
Я делал сортировку задач при добавлении с тем, чтобы не проходить по всему списку в прерывании.
+1
  • avatar
  • dekar
  • 06 августа 2013, 19:47
типа своего рода приоритет?
0
Slow_Timers[i].handler();


Я бы добавил

if(Slow_Timers[i].handler) Slow_Timers[i].handler();


тогда вместо калбека можно задавать NULL. Иногда калбек не нужен, состояние TIMER_MODE_ONCE таймера можно проверять через timer_mode

Еще, стоит объявить поля структуры (или саму структуру) как volatile. У Вас заполнение структуры из основной программы, изменение из прерывания. Теоретически, компилятор может сделать оптимизацию, которая все угробит.
+2
  • avatar
  • e_mc2
  • 06 августа 2013, 19:53
Еще один момент

struct sSlow_Timer Slow_Timers[10];


наверное все-же

struct sSlow_Timer Slow_Timers[SLOW_TIMER_COUNT];
+2
это вы верно заметили. спасибо. исправлю
0
про компиляцию можно подробнее? как у него рука поднимется такое сделать?))
0
Здесь есть 2 потенциальные пробелы — первая связанна с эффективностью, вторая — с декомпозицией кода.
Доступ к timer_mode ароматен, я могу этим пользоваться, не вызывая  lock/unlock

Например как-то так:

Slow_Timer_Lock();
Slow_Timers[0].counter=0;
Slow_Timers[0].counter =100;
Slow_Timer_Unlock();

…

Slow_Timers[0].timer_mode=TIMER_MODE_ONCE; //Запускам
while(Slow_Timers[0].timer_mode == TIMER_MODE_ONCE); //Ждем срабатывания


Дык вот, последние 2 строки, с точки зрения компилятора лишены смысла. Мы делам присвоение

Slow_Timers[0].timer_mode=TIMER_MODE_ONCE;


а потом проверяем это значение

Slow_Timers[0].timer_mode == TIMER_MODE_ONCE


Компилятор (вернее оптимизатор) не знает о прерываниях (вызов которого НЕОЖИДАННО для него может изменить Slow_Timers[0].timer_mode), с его точки переменная Slow_Timers[0].timer_mode никогда не изменит свое значение, поэтому данная конструкция эквивалентна бесконечному циклу.

Ели каждую подобную конструкцию обрамлять в lock/unlock возникнет барьер для оптимизации, и код будет работать корректно. Но, это пока есть декомпозиция между модулями (и то не факт). Если изменить декомпозицию (всю программу реализовать в одном С файле), то оптимизатор может захотеть заклинить методы Slow_Timer_Lock(), Slow_Timer_Unlock() и барьер исчезнет.
+4
хотеть заклинить
Упс, «заклинить»  = «заинлайнить». Ну и, в данном случае, не метод а функцию.
+1
Доступ к timer_mode ароматен
:)
Извиняюсь, подразумевалось «Доступ к timer_mode атомарен»
+3
Оговорка по Фрейду :)
+2
аромат от атомарности хороший))
0
Вообще, вместо вывешивания наружу функций lock, unlock и массива таймеров лучше предоставить функции AddTimer (вместо StartTimer), DeleteTimer, ModifyTimer (здесь можно и более удачное название подобрать, пожалуй), которые будут при необходимости сами блокировать таймер, модифицировать массив и возвращать состояние таймера.
Ну и текущий вариант очень сильно завязан на STM32. Вполне можно вынести инициализацию аппаратного таймера и интерфейс к прерыванию в HAL-модуль.
+2
  • avatar
  • Vga
  • 06 августа 2013, 22:12
Вообще, вместо вывешивания наружу функций lock, unlock и массива таймеров лучше предоставить функции AddTimer (вместо StartTimer), DeleteTimer, ModifyTime

Поддерживаю. Во-первых такой подход в проектировании API изначально избавлен от некорректно использования (нельзя «забыть» вызвать lock() или unlock()). Во-вторых не всегда этот lock/unlock нужен. Например, доступ к полю структуры таймера timer_mode ароматен, можно к нему обращается без блокировки таймера. А вот для доступа к counter блокировка потенциально нужна.

В третьих — здесь можно удачно применить связанный список из «таймеров» вместо массива (имеется ввиду реализация без динамического выделения памяти). Это сделает код более гибким и позволит некоторые оптимизации (например удалять «неактивные» таймеры из списка и снизить нагрузку на обработчик прерывания). Ну и такой подход уменьшает связанность кода, нет необходимости в обмене данными через глобальный массив (extern struct sSlow_Timer Slow_Timers[SLOW_TIMER_COUNT];), доступ к которому неконтролируем.
+1
завязан по уши. Ну пока что у меня из ARM только стм-ки. Так что так и живем)
0
AddTimer (вместо StartTimer), DeleteTimer, ModifyTimer
это верная идея. в моем проекте не требуется удалять таймеры. Но для полноценного модуля нужно будет добавить. Про HAL модуль можно поподробнее?
0
Про HAL модуль можно поподробнее?

Реализация таймера и всего механизма выносится в отдельный c-файл таким образом, чтоб остальному коду было пофиг, как этот таймер реализован — работает и ладно. И никоим образом его интерфейс (что выносят в h-файл) не был привязан к STM32 =) Я чуть ниже пример привёл, правда, не знаю, насколько понятный. Но он придерживается идеологии hal.
А в том сишнике уж и битбенги, и макросы во все поля, прерывания и вообще что угодно.
0
Ну, например, примерно так (тут есть варианты). Выкидываем все железозависимое из основной библиотеки:
void Init_Slow_Timer(void)
{
  uint8_t i;
  for(i=0;i<SLOW_TIMER_COUNT;i++)
  {
    Slow_Timers[i].timer_mode = TIMER_MODE_STOP;
    Slow_Timers[i].handler=0;
  }
  Init_Slow_Timer_HAL();
}

uint8_t Start_Slow_Timer(uint8_t mode, uint16_t compare, void (*handler)())
{
  //без изменений
}

void Slow_Timer_Update(void)
{
  uint8_t i;
  for(i=0;i<Timer_Counter-1;i++)
  {
    if(Slow_Timers[i].timer_mode)
    {
      Slow_Timers[i].counter++; 
      if(Slow_Timers[i].compare==Slow_Timers[i].counter)
      {
        if (Slow_Timers[i].handler)
          Slow_Timers[i].handler(); //вообще говоря, лучше вызывать асинхронно, а не тупить в прерывании
        Slow_Timers[i].counter = 0;
        if(Slow_Timers[i].timer_mode==TIMER_MODE_ONCE)
          Slow_Timers[i].timer_mode = TIMER_MODE_STOP;
} } } } // и закрывающие скобки принято ставить по одной на строку

И все дефайны из хедера тоже, кроме количества таймеров.
Добавляем модуль HAL:
// здесь все дефайны, выкинутые из хедера
void Init_Slow_Timer_HAL(void)
{
  BIT_BAND_PER(RCC->TIMER_SLOW_APBENR,SLOW_TIMER_RCC_EN)=SET;
  SLOW_TIMER->PSC = SLOW_TIMER_PRESCALER;
  SLOW_TIMER->CNT = 0x0000;
  SLOW_TIMER->ARR = SLOW_TIMER_PERIOD;
  NVIC_SetPriority(SLOW_TIMER_IRQn,15);
  NVIC_EnableIRQ(SLOW_TIMER_IRQn);
  BIT_BAND_PER(SLOW_TIMER->EGR,TIM_EGR_UG)=SET; 
  BIT_BAND_PER(SLOW_TIMER->SR,TIM_SR_UIF)=SET;
  BIT_BAND_PER(SLOW_TIMER->DIER,TIM_DIER_UIE)=SET;
  BIT_BAND_PER(SLOW_TIMER->CR1,TIM_CR1_CEN)=SET;
}

void Slow_Timer_Lock(void) // Если они вообще нужны - можно заменить на обеспечиваемые компилером атомик-блоки в функциях Add/Remove/ModifyTimer
{
  BIT_BAND_PER(SLOW_TIMER->DIER,TIM_DIER_UIE)=RESET;
}

void Slow_Timer_Unlock(void)
{
  BIT_BAND_PER(SLOW_TIMER->DIER,TIM_DIER_UIE)=SET;
}

void SLOW_TIMER_IRQHandler(void)
{
  BIT_BAND_PER(SLOW_TIMER->SR,TIM_SR_UIF)=RESET;
  Slow_Timer_Update();
}

Собственно, все. Теперь можно делать модуль HAL, который будет делать то же самое (вызывать Slow_Timer_Update() с заданной частотой) на любом другом МК и использовать эту же библиотеку там.
+1
Slow_Timers[i].handler(); //вообще говоря, лучше вызывать асинхронно, а не тупить в прерывании
— что имеете ввиду? поясните, как у вас
0
Подозреваю, что выставить флаг(добавить в очередь и т.д.), а потом из главного цикла вызвать нужный handler =) Чтобы прерывания не долгить
0
Именно.
0
мммм… интересно. Я как-то целую тему развил в группе вконтакте насчет прерываний и многих фобий типа «чтобы не долгить прерывания». ОЧень интересное обсуждение. Как нить на форуме темку открою здесь.
0
В данном случае время выполнения функции handler заранее архитектурно неизвестно, ибо она внешняя. Может, пара тактов, а может, целую миллисекунду.

Абсолютно не дело из-за внешней функции морозить прерывания фиг знает на сколько. Конечно, они вложенные, но тем не менее, это не хорошо.
+3
ну так и пусть миллисекунду крутится. Если есть другие важные прерывания — ставим приоритет. Кстати, если не заметили — то при инициализации этого таймера неслучайно указывается минимальный приоритет. Если в процессоре отсутствуют приоритеты прерываний вообще как класс — в самом начале прерывания делаем следующее: 1 — запрещаем вектор ,2 — разрешаем все прерывания. По окончании: 1 — запрещаем прерывания, 2 — разрешаем вектор. Ну и по выходу из прерывания флаг прерываний восстановится.
0
А если 10 миллисекунд?

Да и к тому же этим собъются все таймеры. Если тймер тикает 2 кГц,, за 1 мс он пропустит 2-3 тика. За 10 в десять раз больше:) Полная фигня получается
0
если 10 миллисекунд — значит ПОЛЮБОМУ все будет сбито, потому как если весь массив будет обрабатываться в основном цикле — эти же 10 миллисекунд она потратит, а таймеры к тому времени, вызываемые по прерываниям будут потеряны по причине того, что обработка этих флагов не началась вовремя.
0
Да, но частоту они тем не менее держать будут и какой-то медленный слоутаймер их сбивать с толку не будет. Ну, соберётся однажды очередь, ничего страшного.
Дело не в цифрах, а в том, что поведение таймера не совсем корректно.
0
Кстати, насчёт неприоритетных прерываний: а если прерывание таймера вызовет себя же, когда предыдущее не завершилось?:) Учитывать в коде и такое?:)
0
так говорю же: отключаем вектор прерывания и вуаля
0
фобий типа «чтобы не долгить прерывания»
Это не фобия, это правило хорошего тона.
+2
когда это «правило» нагромождает код до безобразия — иначе чем «фобия» не назовешь
0
Например?
0
Например, выставление каких-то флажков… Потом в теле программы нужно эти флажки выгребать… Потом нужно заботиться о том, чтобы то самое тело как можно быстрее реагировало на эти флажки… Да много чего
0
Это, как раз, правильный подход, применяемый в RTOS. И в OS тоже, кстати — погугли, например, виндовые DPC.
0
Это на самом деле весьма хороший метод избавления от конфликтов доступа при разного рода асинхронщине. Это второй важный критерий, который я взял за рекомендацию в реализации своих библиотек.

Если вызываем код из прерывания сразу, то нужно городить в вызываемом коде контроль доступа к переменным, ибо асинхронный код может с каким-то ещё кодом посоревноваться в праве владения и натворить дел. Вот.
Если же все асинхронные обратные связи будут идти уже из главного цикла (часто коего обёрнута в функцию в данном модуле), то такой гонки принципиально быть не может — главный цикл один и единовременно в двух точках его мы находиться не можем.

Потому я всегда стараюсь использовать подобную модель:

TCallback callback = null;
void module_IRQHandler(void)
{
    flag = true;
}

void module_SetCallback(TCallback Handler)
{
    callback = Handler;
}

// Вызывается из главного цикла как его часть
void module_Main(void)
{
    if(flag)
    {
        flag = false;

        // Полезный код с обратным вызовом
        if(callback) callback(nya);
    }
}


Вместо такой, которая неустойчива к внешним нагрузкам, мешает работать другим прерываниям (в lpc23хх с этим особенно плохо) да ещё и заставляет клиента беспокоится о порядке доступа к переменным:
TCallback callback = null;
void module_IRQHandler(void)
{
    // Полезный код с обратным вызовом
    if(callback) callback(nya);
}

void module_SetCallback(TCallback Handler)
{
    callback = Handler;
}


Я отказался.
+1
При этом все гонки переменных локализованы в модуле и каждый модуль должен их сам разруливать. Зато внешним модулям нет причин париться: ибо код хоть и асинхронный, но не параллельныйя.
0
до недавнего времени сам пользовалься подобными конструкциями. Но отказался в пользу быстродействия системы в общем. Кроме того, моя философия такая: Почти все размещать в прерываниях. В главном цикле у меня только меню, вывод на индикатор и другая «бесполезность». Маин — по сути у меня служит для интерфейса с пользователем. не больше. Поэтому там нет никаких серьезных функций. Да — мой подход сложно укладывается в голове. Но он оптимален с точки зрения расходования ресурсов, и вообще оптимален по быстродействию. В некоторых моих программах в майне вообще стоит одна единственная инструкция: Sleep (ASM AVR)
0
Но это верно до определённого предела всего лишь. Да, во множестве проектов можно обойтись прерываниями, пока нагрузка на них не очень велика и нет плодородных условий возникновения гонок. Ну или если требуется достаточно быстрый ответ с заранее заданным временем реакции, тут флаговый подход не годится.
И ничего удивительного в асинхронной работе нет (т.е. на прерываниях) — это обычная и широко распространённая практика. Из значительных минусов — низкая портируемость кода, в авр — даже между камнями соседних серий, да и код может оказаться запутанным — зачастую прерывание срабатывает на целый класс сигналов, из которого нужно выделить вызвавший, из-за чего его поддержка усложняется. Был ещё какой-то момент, но не могу так вот вспомнить, о чём я ещё вчера вечером думал.з.

Если же ничего особого не требуется, лучше сделать чуть медленнее, но зато универсальнее и предсказуемее (и тем самым надёжнее). И, насчёт быстродействия — пользователь не заметит, прошло 10 или 15 мкс после нажатия на кнопку, из-за чего многие вещи могут быть немного отложены в пользу более срочных.
+1
в первых, все обрабатываемые события в майне — ДЕ ФАКТО не могут быть перекрываемыми. А у прерываний есть хороший инструмент — приоритетность. Так что здесь 1:0 в пользу моего метода. 2-е: Портируемость ничуть не лучше, но и не хуже. При переносе на другое утройство нужно будет разбираться с теми же прерываниями, где выставляются флажки. Так — что здесь 1:1 — итого: 2:1. 3-е: У меня код работает по принципу «Нажал кнопку и забыл» То есть даю стартовую команду — все остальное крутится в прерываниях. Разных прерываниях. Использование железа выше — использование основного процессорного времени — ниже. Итого: 3:1
0
цикл майн освобождается от разного рода быстрокодов, дополнительных проверок, и прочего хлама, который приписывается в довесок обработчику событий. Там не страшны ни циклы задержки для индикатора, никаких критически важных процедур. Попросту цикл может быть пустым с одной командой — sleep. Тем самым проц работает только по событию, а остальное время спит. Энергопотребление на высоте: 4:1
0
в последнем моем проекте есть только один случай флага в прерываниях. И он вполне обоснованный: У меня опрос кнопок висит на тех же ножках, что и индикатор. А поскольку индикатор у меня в основном цикле — опросить по прерыванию кнопки — не представляется возможным. Да и смысл пропадает городить что-то. Поэтому в прерывании только флаг, а опрос в майне между выводами на экран. Обрабатываем флаг, по типу «пора опросить». Больше никаких флагов. Все работает стабильненько и весьма шустро. Например, при запросе от UART я довольно быстро отвечаю — практически сразу. Экономлю время. Другое устройство, запрашивающая инфу — имеет таймер. Если слейв не ответил за короткое время — значит его нет. А слейвов там на линии не мало (около 200). Короче эффективность (заполняемость) канала почти 100%. И скорость работы линии впечатляет. Если с умом сделать…
0
Это тоже не правило — мне попадалось всего два устройства, где требовался именно такой подход к построению. Обычно же у меня там всякие интенсивные вычисления и работа по приёму/передаче данных, которым спать просто некогда =) Да и в большинстве случаем на потребление всем пофиг — нагрузка ест на порядки больше.

Да, в условиях батарейного питания структура проекта будет иной. В остальных случаях лучше сделать её малосвязанной и модульной, имхо.
0
Для необходимости перекрываний есть просто прерывания относительно основного кода. Если они действительно должны быть выполнены тютелька в тютельку тотчас же, пусть будет так.

Я не про такую портируемость. Мы заранее создаём такой модуль, который бы включал в себя все эти флажки и прочую контроллерозависимую фигню, а на выходе имел универсальный и понятный интерфейс над основными и нужными нам функциями периферийного модуля. В дальнейшем, если нам требуется перенести код на другой контроллер, мы делаем точно такой же по логике модуль там. Остальной код от этого не поменяется ни капельки. Точно так же потом можно создавать другие проекты, используя готовый универсальный модуль. При возникновении нестандартных требований — модуль разрушается и перепиливается под задачу/интегрируется с вызываемым кодом, для ускорения ли или ещё чего. Но все слияния логики и низкоуровневой бяки лучше если будут исключением, чем правилом.

Использование процессорного времени важно, в основном, в разного рода автономных устройствах с ориентацией на низкое потребление, там идут другие требования и принципы построения программы. Когда проц крутится постоянно, что-то обрабатывая, не так и важно, где он крутится.
+1
чем хуже например при портировании функция
void IRQ_Handler()
{
  IRQ_Flag=1;
}


от такой:

void IRQ_Handler()
{
  IRQ_Exe();
}


где IRQ_Exe функция которая выполняется без всех железозависимых инструкций. Можно обозначить ее как __inline.
0
точнее наоборот, нужно читать как «чем лучше»
0
пока-что я не увидел весомого плюса в применении вашего способа… А у моего способа (не моего конечно, имеется ввиду мною предложенного здесь) я уже описал.
0
Обычно её стоит делать внешней, а не inline, иначе нет инкапсуляции =)

Я уже говорил:
1. Время обработки прерывания зависит от внешней неопределённой функции. Это не очень хорошо. В стм32 с этим свободнее, конечно, как и во всех кортексах. но конкретно это же прерывание она вторично не пустит в обработку, пока не закончим это.
2. Вызываемая функция работает параллельно остальному коду — возможен конфликт при доступе к одной переменной, если она часто используется и там, и сям. Если редко то всё же маловероятно. Но возможно.
3. В системах, где используется модуль защиты памяти (MPU) доступ к памяти в прерываниях и обычном коде отличается кардинально (у обычного он может быть для безопасности зарезан до упора). Таким образом можно ограничивать доступ к памяти даже «в прерываниях». Особенно это актуально в ОС.
4. Можно уменьшить время блокирования процессора инструкциями запрета прерываний (например, обеспечение атомарности доступа) и самими прерываниями, давая возможность сработать сразу по моменту возникновения события. В основном, это относится к контроллерам, где нет вложенных прерываний.
5. Конечно, можно сказать — зачем тогда прерывания, если флаги и так расставляются в нужных регистрах — бери и смотри, но часто ж надо что-то сделать, проверить, посчитать, и при каких-то условиях вызвать обработчик, а не просто его вызвать. А обычно проверки лучше делать сразу, а не как попало><

В таких случаях, для просто перенаправления, конкретно в кортексах можно использовать динамическую таблицу векторов вместо зашитой во флеш. Хотим перенаправить — пишем туда адрес нужного обработчика. Вот и всё, никаких затрат на доп. вызов не требуется. Но такое редко бывает>< Обычно нужно выяснить причину прерывания, а дальше разбираться, кому и куда её направлять, да и надо ли вообще.

В общем-то такую конструкцию я применяю, если действительно надо, но всё же стараюсь избегать…
0
1 если заморочиться то думаю можно процессору как бе намекнуть что прерывание закончилось чтобы можно было войти вторично
2 если используется и там и сям — добро пожаловать атомарность.
3 Осью у меня и не пахнет. Не люблю тех кто решает задачку уровня 2х2 на проце в гигагерц, линуксом, с полуминутной загрузкой и прочей чушью в обвязке. По остальному вроде согласен.
0
1. Городить костыли, чтоб это завелось и ничего не сломало? =)
2. Ага, критические секции, семафоры и мьютексы в пользовательском коде, чтоб её обеспечить?=) *битбенд есть не везде и не всегда пригоден*
3. Ну да, но это я уже пошёл «по идее», зачем это надо может быть.
+1
} } } } // и закрывающие скобки принято ставить по одной на строку
— это для экономии места. Для визуализации это позволительно. Понятное дело, что функциональность этих скобок не совпадает с визуальным представлением, однако чтобы не городить 4 строки по сути пустые — для отображения — удобнее так. Кстати этому и профессора в институте учат.
0
Мало ли чему учат =) Скобочки на отдельной строке не просто так должны быть, а ещё и прямо под открывающими — так визуально видно весь заключённый в них блок и вообще иерархию.
+3
так они и есть — строго под открывающими. Просто на одной строчке. Функциональность у них обратная — это понятно. Но для визуализации — оптимально. У меня в исходнике они располагаются так, как вы говорите. Ну что тут развивать тему из ничего?
0
это как бы проекция всех строк, в которых только одна скобочка на одну строку.
0
Да, но оперировать блоками это несколько мешает. Бывает, надо вставить в хвост блока else, или сдвинуть целый блок (выделение + таб), или убрать, или закомментить, или переместить. Загромождение скобочек эту свободу ограничивает: сначала надо выделить скобочки нужного блока, а потом их уже оперировать:)
+4
Да, но оперировать блоками это несколько мешает.
Собственно, с этим я уже столкнулся, когда писал пример выделения HAL.
+1
Экономить место не нужно. Код должен быть читабельный и именно поэтому скобочки ставятся каждая на свою личную строку, в той же позиции, что и открывающая.
Кроме того, не рекомендуется выкидывать скобки из конструкций вида
if(Slow_Timers[i].timer_mode==TIMER_MODE_ONCE)
  Slow_Timers[i].timer_mode = TIMER_MODE_STOP;

Этот кусок кода слишком склонен к возникновению малозаметной синтаксической ошибки.
Кстати этому и профессора в институте учат.
Печально, что они плохому учат.
+2
если делать отступ, как в описанном примере — ошибок не будет. Если захочется еще строчку воткнуть — будь добр, скобочки поставь. Все зависит от личного восприятия, наверное.
0
Это рекомендации тех, кто на С пишет давно и много. Рано или поздно будет написано
if(Slow_Timers[i].timer_mode==TIMER_MODE_ONCE);
  Slow_Timers[i].timer_mode = TIMER_MODE_STOP;
+3
в данном случае не помогут даже скобочки!
+1
Потому у меня маленькое правило: или пиши всё в одну строку, если маленькое, или городи новый блок:
if(a) b;


if(a)
{
    b;
}


Без исключений. Блоки ещё, что здорово, позволяют локальные переменные создавать.
+4

if(a);
{
    b;
}
можно же и так. Не пойму, чем ваше лучше моего…
0
Нет, это от другого огораживает:
if(a)
    b;
    c;


if(a)
{
    b;
    c;
}


Хотя, я на таком не попадался пока, ввиду пренебрежения макросами. Но лучше, чтоб привычка не давала шанса на ошибку =)

Поясняю:
if(a)
    B;


Если B — многострочный макрос и по какой-то причине без скобок фигурных, он будет работать неправильно, так, как в верхнем примере.з. Условно выполнится только первая строка макроса.
+1
а это уже проблема макроса — не ваша. Значит макрос написан криворуким
0
Да, но выгребать проблему придется пользователю макроса, а не автору.
+3
Если B — многострочный макрос
все макросы которые что-то делают должны быть обёрнуты в
#define B() do{ a=b; b=c;} while(0)
0
Не все это знают, к сожалению.

Я макросами так вообще не пользуюсь почти.
0
это я знаю
0
Кстати этому и профессора в институте учат.

Нет.
+2
как вы прям шустро! За всех профессоров сразу ответили. Может устроить голосование? )))
0
HAL модуль… мммм… понятно… Вопрос: Каким образом человек, втыкающий мой модуль, свободный от железяки должен знать, какие именно функции нужны ему? Это где-то все нужно описать? И сколько таких HAL модулей должно быть в проекте? один на весь проект или на каждую такую либу? Как это организуется у вас?
0
Каким образом человек, втыкающий мой модуль, свободный от железяки должен знать, какие именно функции нужны ему?
Для этого есть документация.
И сколько таких HAL модулей должно быть в проекте? один на весь проект или на каждую такую либу?
Это вопрос архитектуры.
0
Хм, я давно похожим пользуюсь, очень удобно. Только всё заведомо спрятал внутри модуля, оставив лишь торчащим интерфейс:
// Запуск таймера с заданной частотой (дискретизация)
    void timer_Init(uint32_t Frequency);
    
    // Добавить функцию в список вызова, Handler будет вызываться с заданной частотой
    // Аргументы: 2
    //  Frequency - частота вызова (при увеличении снижается точность)
    //  Handler - функция, которая будет вызываться
    // Результат: нет
    void timer_AddFunction(uint16_t Frequency, void * Handler);

    // Остановить таймер
    void timer_Stop(void * Handler);

    // Включить таймер
    void timer_Resume(void * Handler);

    // Изменить частоту таймера
    void timer_ChangeFrequency(void * Handler, uint16_t Frequency);
    
    // Главный цикл. В нём вызываются обработчики таймера
    void timer_Main(void);

Для увеличения скорости можно вызывать обработчики прям из прерывания, если они небольшие =) Но обычно это для периодических фоновых процессов, где точность не важна. В стм32 сделано на SysTick, в авр на TIMER0, в lpc23xx тоже на нулевом таймере. Хотя какая разница, на чём делать =)

Пример:
// Вызывается раз в 10 секунд
static void led_OnTimer(void)
{
    if(Toggle) led_Toggle();
}

void led_Init(void)
{
    timer_AddFunction(10, &led_OnTimer);
}


Было много вариантов, но в итоге пока что всё свелось к такому.
0
*10 раз в секунду, пирожки-пироженки
0
struct sSlow_Timer
{
   uint8_t timer_mode;
   uint16_t counter;
   uint16_t compare;
   void (*handler)();
};

А если вместо инкремента использовать декремент счетчика? Тогда для запуска таймера просто в counter записываем нужно число. В прерывании уменьшаем на единицу и сравниваем его с нулем. Итого можно сэкономить по uint16_t на каждый экземпляр таймера, ибо не понадобится переменная compare. Да и сравнение с нулем, если не ошибаюсь, выходит чуть быстрее.
+1
Тут суть в том, что таймер может «автоматически перезапускаться» (TIMER_MODE_REPEAT). То есть всё равно прийдется хранить период таймера, что бы повторно его запустить при срабатывании.
Но сделать декремент полезно для последующих оптимизаций. Когда список таймеров в поле времени содержит «остаток времени» после завершения предидущего таймера. Тогда при счете проверяется только первый таймер из списка, следующие заведомо известно сработают не раньше первого и им отсчет проводить не требуется. Соответственно список при этом должен быть упорядоченным.

А вот код:
if(Slow_Timers[i].compare==Slow_Timers[i].counter)
поспособствует проблеме при обновлении периода таймера в текущей реализации. Так установили таймер на 10 секунд, прошло 7 из них и мы переставили период на 5 секунд. Ждем переполнения счетчика. :)
+4
рациональное зерно в этом есть. Почерпнем!)
0
вернул счетчик на инкрементальный, так как полезнее знать, сколько времени прошло, чем сколько осталось. И второе: проблема с описанным переполнением решается легко: вместо == пишем >=
0
Кстати, такие таймеры обычно называют software timers.
-1
  • avatar
  • evsi
  • 07 августа 2013, 11:49
обновил статью
0
Кстати, я подобные конструкции сразу инвертирую:
void func(...)
{
    if (index<SLOW_TIMER_COUNT)
    {
        // code
    }
}


в примерно такое:
void func(...)
{
    if (index>=SLOW_TIMER_COUNT) return;

    // code
}


Сразу уменьшается уровень вложенности, да и можно несколько проверок в столбик повесить, не делая гигантского условия в if.
+3
справедливо, если выход функции void. не универсально
0
Никто не запрещает добавить туда возвращаемое значение, если это логически обосновано) Собственно, я так и делаю:
static const TChannel * pwm_GetChannel(int Channel)
{
    if (Channel >= ChannelCount) return 0;
    
    return &Channels[Channel];
}


Но в данном случае это просто пример оптимизации записи кода при неизменной логике работы. Очень встречающийся штамп, потому полезный.
+1
а вот несколько return-ов — точно некрасиво!
-1
Чем же это некрасиво? Очень даже.
int8_t Slow_Timer_Add(uint8_t mode, uint16_t counter, void (*handler)(void))
{
    uint8_t i;

    for (i = 0; i < SLOW_TIMER_COUNT; i++)
    {
        if (Slow_Timers[i].timer_mode == TIMER_MODE_DELETED)
        {
            Slow_Timers[i].counter = counter;
            Slow_Timers[i].reload = counter;
            Slow_Timers[i].handler = handler;
            Slow_Timers[i].timer_mode = mode;
            return i;
        }
    }

    return -1;
}
0
а то, что при беглом взгляде создается впечатление, что возвращается -1 только
0
Разве? Вполне обычно ожидать, что в коде несколько ретурнов, а в конце стоит что-то типа дефолтного варианта, когда ничего не подошло.
0
В принципе, несколько точек выхода действительно не рекомендуется. Хотя я и сам очень люблю конструкцию if SomeErrorCondition then Exit;
Впрочем, как раз для этого, ЕМНИП, из рекомендации делается исключение.
Но точно не помню. Надо перечитать. Источник, ЕМНИП, «Code Complete» МакКоннелла.
0
Надо поглядеть бы.
0
использовал у себя ваш пример, с «ретёнами», так как это оправдано. Нет лишнего условия.
0
лучше это было описать так:
return (Channel >= ChannelCount)? 0: &Channels[Channel];
0
В данном случае возможно, но обычно дальше там несколько инструкций, я просто взял самую короткую.
0
На этот счет есть разные мнения, но я знаю как минимум две техники, которые реально улучшают читаемость и надежность кода и которые невозможны без нескольких return-ов. Первая — так называемое контрактное программирование, при котором в начале тела функции проверяются входные параметры на валидность (что, собственно, и есть часть «контракта», то есть соглашения о входе и выходе/поведении функции). «Официального» названия второй техники я не знаю, для себя я ее называю early cut-off. Идея в том, что бы обнаруживать ошибки или специальные случаи как можно раньше, и по возможности быстрее вернуть ошибку. При таком подходе основная логика функции существенно очищается от проверок, глубина вложения if-ов резко снижается и код, реализующий основную функциональность, становится гораздо прозрачнее для чтения и понимания. Обе техники тесно переплетаются, первая несколько более формальна и ее имеет смысл применять «глобально», то есть по всему коду приложения. Вторая годится и в тех случаях, когда остальной код написан без такого подхода.
Все IMHO, естественно.
+2
Вот что любопытно — везде номер int и везде отсутствует проверка на его неотрицательность. А что скажет функция, если запросить у нее канал (или таймер) -1?
+1
Да уж, будет атата=D Посему правильнее:
static const TChannel * pwm_GetChannel(int Channel)
{
    if (Channel < 0) return 0;
    if (Channel >= ChannelCount) return 0;
    
    return &Channels[Channel];
}


*пошёл пофиксил*
0
Я бы здесь все же написал if((Channel < 0) || (Channel >= ChannelCount) return 0;
+2
Ой, последнюю скобку забыл.
0
Если условия сопоставимы, так даже лучше, согласен =)
0
Хах, я немного исправлял форматирование библиотечки и понял, почему такой лаг возник: там вместо int должен быть enum TPwmChannel, который беззнаковый и по какой-то причине сюда не записан, я забыл поменять, наверное. Потому всё вернул взад, изменив тип аргумента.з.
0
Как это беззнаковый enum?
+1
а можно же и так проверить:
if((uint32_t) Channel>= ChannelCount) return;

Всего одно условие проверки. Конечно, для понимания сложнее, зато для скорости работы — очень даже
0
Можно и так, но это уже всё оффтоп=)
0
Конечно, для понимания сложнее, зато для скорости работы — очень даже
Не лучше ли в таком случае сразу uint'ом индекс и передавать?
Ну и подобные хаки — не очень хорошо. Иногда может аукнуться при портировании, например.
0
можно и uint'ом. Только если при инициализации функция возвращает -1, то значит она использует знаковое число. в знаковой переменной обычно и хранят. И в знаковой запрашивается. А то получается точно не красиво. А то, как функция сравнивает это число у себя внутри — никого не должно волновать. Главное, чтобы реагировала правильно. При портировании никаких проблем возникать не должно в принципе.
0
Никто не заставляет возвращать из функции AddTimer знаковое число. Ошибку можно идентифицировать другим значением — например, uint(-1), как это делается в Win32 API.
Главное, чтобы реагировала правильно. При портировании никаких проблем возникать не должно в принципе.
Вот как раз этого подобные хаки и не гарантируют. Один автор приводил подобный хак, который за все время работы программы на всех компьютерах, на которых она запускалась, не сэкономил столько времени, сколько ушло на его поиск при портировании.
Выигрыш в скорости тоже дают сомнительный. К тому же, гнаться за производительностью в ущерб понятности кода стоит только тогда, когда она действительно критична.
0
B ещё для удобочитаемости, особенно в хороших редакторах с подсветкой кода и подсказками.

Кучу дефайнов с таблицей состояний лучше перебить в enum:
#define TIMER_MODE_DELETED                      0
#define TIMER_MODE_STOP                         1
#define TIMER_MODE_REPEAT                       2
#define TIMER_MODE_ONCE                         3


typedef enum
{
    TIMER_MODE_DELETED,
    TIMER_MODE_STOP,
    TIMER_MODE_REPEAT,
    TIMER_MODE_ONCE
} TTimerMode;
+3
Не только для удобочитаемости — у енума меньше область видимости, чем у макроса.
+2
с этим соглашусь. Надо будет подделать
+1
Вот, я примерно, не трогая код и логику, чуть косметически подправил два исходника, как бы это было ближе и понятнее мне визуально:
slowtimer.h
slowtimer.c

В целом там ничего не поменялось. Кроме пары конструкций и переноса макросов внутрь модуля — снаружи они точно не нужны.

Да уж, а как вспомню свой первый опыт конструирования программного таймера для авр два года назад — аж плакать хочется, настолько всё там убого было.
+3
в целом, мне понравилось. только несколько замечаний:
volatile sSlow_Timer Slow_Timers[SLOW_TIMER_COUNT];
убрали «struct», а напрасно. мой компилятор KEIL ругается на такое.
if(Slow_Timers[i].timer_mode <= TIMER_MODE_STOP) continue;
Видно, что вы программер — инвертор!)) мне такое не нра))
void (* Handler)(void) = Slow_Timers[i].handler;
Согласен, что вызвать лучше после, но переменную новую зачем?
По хедеру все нравится. Весьма логично. PS stm32f10x.h включает в себя stdint. В общем зачет! Спасибо за проделанную работу
0
убрали «struct», а напрасно
Я там typedef для этого прикрутил =) Потому struct не нужен, а точнее, теперь на него будет ругаться.

мне такое не нра))
Так реально короче и код за экран не уползает =D Хотя, конечно, может скрадывать логику…

но переменную новую зачем?
Это просто так, по привычке больше — очень часто используется шаблон. Там если несколько аргументов, получается чересчур длинно ^^"

Да, включает =) Но зато без него получился простой HAL — теперь никто не скажет, на чём же таймер работает =D
0
Видно, что вы программер — инвертор!)) мне такое не нра))
Вложенные условия — зло. Так что зря не нравится.
+2
Видно, что вы программер — инвертор!)) мне такое не нра))
void (* Handler)(void) = Slow_Timers[i].handler;

я так понимаю, Вам не нравится синтаксис «указатель на функцию»?

На самом деле, можно (и, ИМХО, нужно) один раз определить тип:
typedef void (*TimerCallBack)(void);


И дальше пользоваться этим типом

TimerCallBack Handler = Slow_Timers[i].handler; 
…
if(Handler) Handler(); 

ЕМНИП, как-то так…
+2
typedef void (*TimerCallBack)(void);

Упс, извиняюсь, опечатка, тип назван «не по ГОСТу» :), подразумевалось TimerCallback, хотя это дело стиля и не принципиально.

Идея в том, что в данном случае есть смысл определить свой тип для указателя на функцию, и везде его использовать. ИМХО, так код становится более понятным и читаемым. Например

int8_t Slow_Timer_Add(uint8_t mode, uint16_t counter, TimerCallback handler);
+1
Согласен, тип для Callback оставляет меньше шансов на косяк и улучшает читабельность кода заметно =)
0
да… типизация не помешает. Ладно завтра займусь перевариванием и обновлением статьи
0
Update!
0
Я, кстати, так и не понял, как тут простым способом переводить нужную частоту в удавыcounter. Нужно ж обычно, чтоб он не просто регулярно тикал, а с весьма конкретной реальной частотой, измеряемой во вполне себе человеческих герцах. Для каждой инициализации вести пересчёт? =) Точнее, здесь задаётся период в удавах…
+1
Пересчетом это и назвать нельзя. Если требуется частота — пишем
SLOW_TIMER_FREQUENCY / FREQUENCY
в инициализаторе или модифи — функции. В случае с переменной FREQUENCY в любом случае пересчет будет — либо внутрях, или в наружностях. А вот если использовать константы — то в моем случае пересчета не будет вообще. я начал свой путь с ассеблера и поэтому чувствую спиной лишние преобразования.
0
кстати, в примере со светодиодом все показано, как сделать. Сделать не сложно
0
Вооот, примера в статье не хватало.

Насчёт лишних преобразований — инициализация же, можно забить на несколько лишних тактов совершенно спокойно
+2
Добавлю в общую копилку классику, если кто не знает en.wikipedia.org/wiki/Fast_inverse_square_root

Еще очень интересные вещи встречаются внутри www.enlightenment.org/
Но это больше обработка полупрозрачности между иконками в PNG.

ЗЫ Rasterman висит мне пиво — он был уверен что EFL невозможно спортировать на VxWorks и ThreadX. ;-)
0
ээммм… вам железных таймеров мало?
0
Вэлкам бэк :)
+2
Да, я тут чутка отвлекся на другое хобби… ;)
0
Мало =) Большое количество действий работают на весьма низких частотах: моргание светодиода, опрос температурного датчика, отсчёт таймаутов неспешных и т.д. Причём, действий таких в проекте может быть достаточно много… Таймеров на все не напасёшься, особенно, если они заняты на какой-нибудь ШИМ.

А так всего один таймер может окучивать целую грядку фоновых процессов, которые ещё и достаточно легко настраиваются: «запускай такую-то функцию вот с таким периодом/частотой».
0
Эм. Я обычно на SysTick`е формирую временную сетку удобным интервалом, а уже в мэйне конечным автоматом разруливаю события. Как ни странно, получается и удобнее и мене затратно по ресурсам.
0
Я тоже на сустике (если брать стм32), только вот конечный автомат вынесен абсолютно отдельно… =)
0
(если брать стм32)
топик ведь про стм32.
только вот конечный автомат вынесен абсолютно отдельно… =)
У меня обычно мейн — инициализация и обслужка (в том числе и конечный автомат таймеров).
0
Ну я ж программным таймером пользуюсь везде, не только в стм32 =)

Хм, у меня мейн обычно вообще минималистичен, только вызовы, а вся логика раскидана отдельно.з.
0
Приведите pls пример вашего кода для сравнения, если не затруднит.
0
Хм, ладно, вот кусочек проекта stm32f4base (заготовка для относительно быстрого прототипирования программ отладочки stm32f4discovery):
systick.c (инициализируется из не приведённого тут drivers.c)
main.c
test.c — hello world по морганию светодиодом.
0
спасибо
0
NVIC_EnableIRQ(SysTick_IRQn);


Это у вас работает? У меня стабильно вываливалось в хард фаулт. Вообще для инициализации SysTick этого не нужно в принципе.
0
В Стм32ф1 это дейтвительно ведёт к хард фолту, но этот код для стм32ф4
0
Хотя да, он действительно не нужен
0
Причина, по которой я не написал на systick — очень большой приоритет этого таймера (выше 0-го). Systick больше подходит для операционных систем.
0
в сравнении с моей версией — приоритет у меня — минимальный (15). Это коррелирует с требуемыми задачами. Задачи не столь важные — значит и прерывание не столь высокое. А на systick получается чушь: задачи вроде бы не шустрые, а вот прерывание прерывает обработку любой периферии, даже самой важной.
0
кроме того, много дефайнов, в которые особо неохота вникать — это плата за легкий выбор любого таймера из существующих для работы модуля, поэтому настройка этого модуля — проще некуда.
0
Можно поднять в прерывании флаг «тик сработал» и затем обработать таймеры в главном цикле, на низком приоритете.
Собственно, в своей недортос на примере ДиХалтовской, которую заюзал в таймере для ванночки, я так и сделал. Правда, там свои недостатки — есть вероятность прощелкать тик, если обработчики будут долго тупить.
0
так чтобы поднять флаг, процессорное время не требуется? Оправдано ли это? Конечно же нет!
0
По крайней мере оно довольно мало и вполне детерминировано.
0
systick — очень большой приоритет этого таймера (выше 0-го)
Приоритет SysTick-а настраиваемый, и по смыслу должен иметь наименьший приоритет, меньший приоритет обычно задаётся для переключателя контекста.
0
спасибо за полезное уточнение. Сам в stm-ках недавно ковыряюсь и некоторые тонкости не знаю. Значит в следующем обновлении сделаю возможность использовать и SYSTICK наравне с остальными таймерами
0
Приведите pls свой пример кода, я то же склоняюсь к использованию SysClk. Вариант кода представленный автором данной статьи мне показался сложным.
0
вариант teplofizik'а мне показался не проще.
0
добавлена поддержка SysTick таймера. Теперь для его инициализации нужно указать

#define SLOW_TIMER_NUMBER     0
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.