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

Удобной первичной настройки программного таймера благодаря «всемогущему» препроцессору.
Удобной инициализации вложенных таймеров.
Хочу поделиться с вами исходниками и, как всегда, прежде всего вкусностями препроцессора. Файл 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
Slow_Timers[i].handler();
Я бы добавил
if(Slow_Timers[i].handler) Slow_Timers[i].handler();
тогда вместо калбека можно задавать NULL. Иногда калбек не нужен, состояние TIMER_MODE_ONCE таймера можно проверять через timer_mode
Еще, стоит объявить поля структуры (или саму структуру) как volatile. У Вас заполнение структуры из основной программы, изменение из прерывания. Теоретически, компилятор может сделать оптимизацию, которая все угробит.
Здесь есть 2 потенциальные пробелы — первая связанна с эффективностью, вторая — с декомпозицией кода.
Доступ к timer_mode ароматен, я могу этим пользоваться, не вызывая lock/unlock
Например как-то так:
Дык вот, последние 2 строки, с точки зрения компилятора лишены смысла. Мы делам присвоение
а потом проверяем это значение
Компилятор (вернее оптимизатор) не знает о прерываниях (вызов которого НЕОЖИДАННО для него может изменить Slow_Timers[0].timer_mode), с его точки переменная Slow_Timers[0].timer_mode никогда не изменит свое значение, поэтому данная конструкция эквивалентна бесконечному циклу.
Ели каждую подобную конструкцию обрамлять в lock/unlock возникнет барьер для оптимизации, и код будет работать корректно. Но, это пока есть декомпозиция между модулями (и то не факт). Если изменить декомпозицию (всю программу реализовать в одном С файле), то оптимизатор может захотеть заклинить методы Slow_Timer_Lock(), Slow_Timer_Unlock() и барьер исчезнет.
Доступ к 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() и барьер исчезнет.
Вообще, вместо вывешивания наружу функций lock, unlock и массива таймеров лучше предоставить функции AddTimer (вместо StartTimer), DeleteTimer, ModifyTimer (здесь можно и более удачное название подобрать, пожалуй), которые будут при необходимости сами блокировать таймер, модифицировать массив и возвращать состояние таймера.
Ну и текущий вариант очень сильно завязан на STM32. Вполне можно вынести инициализацию аппаратного таймера и интерфейс к прерыванию в HAL-модуль.
Ну и текущий вариант очень сильно завязан на STM32. Вполне можно вынести инициализацию аппаратного таймера и интерфейс к прерыванию в HAL-модуль.
Вообще, вместо вывешивания наружу функций lock, unlock и массива таймеров лучше предоставить функции AddTimer (вместо StartTimer), DeleteTimer, ModifyTime
Поддерживаю. Во-первых такой подход в проектировании API изначально избавлен от некорректно использования (нельзя «забыть» вызвать lock() или unlock()). Во-вторых не всегда этот lock/unlock нужен. Например, доступ к полю структуры таймера timer_mode ароматен, можно к нему обращается без блокировки таймера. А вот для доступа к counter блокировка потенциально нужна.
В третьих — здесь можно удачно применить связанный список из «таймеров» вместо массива (имеется ввиду реализация без динамического выделения памяти). Это сделает код более гибким и позволит некоторые оптимизации (например удалять «неактивные» таймеры из списка и снизить нагрузку на обработчик прерывания). Ну и такой подход уменьшает связанность кода, нет необходимости в обмене данными через глобальный массив (extern struct sSlow_Timer Slow_Timers[SLOW_TIMER_COUNT];), доступ к которому неконтролируем.
AddTimer (вместо StartTimer), DeleteTimer, ModifyTimerэто верная идея. в моем проекте не требуется удалять таймеры. Но для полноценного модуля нужно будет добавить. Про HAL модуль можно поподробнее?
Про HAL модуль можно поподробнее?
Реализация таймера и всего механизма выносится в отдельный c-файл таким образом, чтоб остальному коду было пофиг, как этот таймер реализован — работает и ладно. И никоим образом его интерфейс (что выносят в h-файл) не был привязан к STM32 =) Я чуть ниже пример привёл, правда, не знаю, насколько понятный. Но он придерживается идеологии hal.
А в том сишнике уж и битбенги, и макросы во все поля, прерывания и вообще что угодно.
- teplofizik
- 06 августа 2013, 23:42
- ↑
- ↓
Ну, например, примерно так (тут есть варианты). Выкидываем все железозависимое из основной библиотеки:
И все дефайны из хедера тоже, кроме количества таймеров.
Добавляем модуль HAL:
Собственно, все. Теперь можно делать модуль HAL, который будет делать то же самое (вызывать Slow_Timer_Update() с заданной частотой) на любом другом МК и использовать эту же библиотеку там.
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() с заданной частотой) на любом другом МК и использовать эту же библиотеку там.
Slow_Timers[i].handler(); //вообще говоря, лучше вызывать асинхронно, а не тупить в прерывании— что имеете ввиду? поясните, как у вас
Подозреваю, что выставить флаг(добавить в очередь и т.д.), а потом из главного цикла вызвать нужный handler =) Чтобы прерывания не долгить
- teplofizik
- 07 августа 2013, 17:19
- ↑
- ↓
мммм… интересно. Я как-то целую тему развил в группе вконтакте насчет прерываний и многих фобий типа «чтобы не долгить прерывания». ОЧень интересное обсуждение. Как нить на форуме темку открою здесь.
В данном случае время выполнения функции handler заранее архитектурно неизвестно, ибо она внешняя. Может, пара тактов, а может, целую миллисекунду.
Абсолютно не дело из-за внешней функции морозить прерывания фиг знает на сколько. Конечно, они вложенные, но тем не менее, это не хорошо.
Абсолютно не дело из-за внешней функции морозить прерывания фиг знает на сколько. Конечно, они вложенные, но тем не менее, это не хорошо.
- teplofizik
- 07 августа 2013, 18:03
- ↑
- ↓
ну так и пусть миллисекунду крутится. Если есть другие важные прерывания — ставим приоритет. Кстати, если не заметили — то при инициализации этого таймера неслучайно указывается минимальный приоритет. Если в процессоре отсутствуют приоритеты прерываний вообще как класс — в самом начале прерывания делаем следующее: 1 — запрещаем вектор ,2 — разрешаем все прерывания. По окончании: 1 — запрещаем прерывания, 2 — разрешаем вектор. Ну и по выходу из прерывания флаг прерываний восстановится.
А если 10 миллисекунд?
Да и к тому же этим собъются все таймеры. Если тймер тикает 2 кГц,, за 1 мс он пропустит 2-3 тика. За 10 в десять раз больше:) Полная фигня получается
Да и к тому же этим собъются все таймеры. Если тймер тикает 2 кГц,, за 1 мс он пропустит 2-3 тика. За 10 в десять раз больше:) Полная фигня получается
- teplofizik
- 07 августа 2013, 18:14
- ↑
- ↓
Да, но частоту они тем не менее держать будут и какой-то медленный слоутаймер их сбивать с толку не будет. Ну, соберётся однажды очередь, ничего страшного.
Дело не в цифрах, а в том, что поведение таймера не совсем корректно.
Дело не в цифрах, а в том, что поведение таймера не совсем корректно.
- teplofizik
- 07 августа 2013, 18:28
- ↑
- ↓
Кстати, насчёт неприоритетных прерываний: а если прерывание таймера вызовет себя же, когда предыдущее не завершилось?:) Учитывать в коде и такое?:)
- teplofizik
- 07 августа 2013, 19:18
- ↑
- ↓
Это на самом деле весьма хороший метод избавления от конфликтов доступа при разного рода асинхронщине. Это второй важный критерий, который я взял за рекомендацию в реализации своих библиотек.
Если вызываем код из прерывания сразу, то нужно городить в вызываемом коде контроль доступа к переменным, ибо асинхронный код может с каким-то ещё кодом посоревноваться в праве владения и натворить дел. Вот.
Если же все асинхронные обратные связи будут идти уже из главного цикла (часто коего обёрнута в функцию в данном модуле), то такой гонки принципиально быть не может — главный цикл один и единовременно в двух точках его мы находиться не можем.
Потому я всегда стараюсь использовать подобную модель:
Вместо такой, которая неустойчива к внешним нагрузкам, мешает работать другим прерываниям (в lpc23хх с этим особенно плохо) да ещё и заставляет клиента беспокоится о порядке доступа к переменным:
Я отказался.
Если вызываем код из прерывания сразу, то нужно городить в вызываемом коде контроль доступа к переменным, ибо асинхронный код может с каким-то ещё кодом посоревноваться в праве владения и натворить дел. Вот.
Если же все асинхронные обратные связи будут идти уже из главного цикла (часто коего обёрнута в функцию в данном модуле), то такой гонки принципиально быть не может — главный цикл один и единовременно в двух точках его мы находиться не можем.
Потому я всегда стараюсь использовать подобную модель:
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;
}
Я отказался.
- teplofizik
- 09 августа 2013, 02:10
- ↑
- ↓
При этом все гонки переменных локализованы в модуле и каждый модуль должен их сам разруливать. Зато внешним модулям нет причин париться: ибо код хоть и асинхронный, но не параллельныйя.
- teplofizik
- 09 августа 2013, 02:12
- ↑
- ↓
до недавнего времени сам пользовалься подобными конструкциями. Но отказался в пользу быстродействия системы в общем. Кроме того, моя философия такая: Почти все размещать в прерываниях. В главном цикле у меня только меню, вывод на индикатор и другая «бесполезность». Маин — по сути у меня служит для интерфейса с пользователем. не больше. Поэтому там нет никаких серьезных функций. Да — мой подход сложно укладывается в голове. Но он оптимален с точки зрения расходования ресурсов, и вообще оптимален по быстродействию. В некоторых моих программах в майне вообще стоит одна единственная инструкция: Sleep (ASM AVR)
Но это верно до определённого предела всего лишь. Да, во множестве проектов можно обойтись прерываниями, пока нагрузка на них не очень велика и нет плодородных условий возникновения гонок. Ну или если требуется достаточно быстрый ответ с заранее заданным временем реакции, тут флаговый подход не годится.
И ничего удивительного в асинхронной работе нет (т.е. на прерываниях) — это обычная и широко распространённая практика. Из значительных минусов — низкая портируемость кода, в авр — даже между камнями соседних серий, да и код может оказаться запутанным — зачастую прерывание срабатывает на целый класс сигналов, из которого нужно выделить вызвавший, из-за чего его поддержка усложняется. Был ещё какой-то момент, но не могу так вот вспомнить, о чём я ещё вчера вечером думал.з.
Если же ничего особого не требуется, лучше сделать чуть медленнее, но зато универсальнее и предсказуемее (и тем самым надёжнее). И, насчёт быстродействия — пользователь не заметит, прошло 10 или 15 мкс после нажатия на кнопку, из-за чего многие вещи могут быть немного отложены в пользу более срочных.
И ничего удивительного в асинхронной работе нет (т.е. на прерываниях) — это обычная и широко распространённая практика. Из значительных минусов — низкая портируемость кода, в авр — даже между камнями соседних серий, да и код может оказаться запутанным — зачастую прерывание срабатывает на целый класс сигналов, из которого нужно выделить вызвавший, из-за чего его поддержка усложняется. Был ещё какой-то момент, но не могу так вот вспомнить, о чём я ещё вчера вечером думал.з.
Если же ничего особого не требуется, лучше сделать чуть медленнее, но зато универсальнее и предсказуемее (и тем самым надёжнее). И, насчёт быстродействия — пользователь не заметит, прошло 10 или 15 мкс после нажатия на кнопку, из-за чего многие вещи могут быть немного отложены в пользу более срочных.
- teplofizik
- 09 августа 2013, 12:42
- ↑
- ↓
в первых, все обрабатываемые события в майне — ДЕ ФАКТО не могут быть перекрываемыми. А у прерываний есть хороший инструмент — приоритетность. Так что здесь 1:0 в пользу моего метода. 2-е: Портируемость ничуть не лучше, но и не хуже. При переносе на другое утройство нужно будет разбираться с теми же прерываниями, где выставляются флажки. Так — что здесь 1:1 — итого: 2:1. 3-е: У меня код работает по принципу «Нажал кнопку и забыл» То есть даю стартовую команду — все остальное крутится в прерываниях. Разных прерываниях. Использование железа выше — использование основного процессорного времени — ниже. Итого: 3:1
цикл майн освобождается от разного рода быстрокодов, дополнительных проверок, и прочего хлама, который приписывается в довесок обработчику событий. Там не страшны ни циклы задержки для индикатора, никаких критически важных процедур. Попросту цикл может быть пустым с одной командой — sleep. Тем самым проц работает только по событию, а остальное время спит. Энергопотребление на высоте: 4:1
в последнем моем проекте есть только один случай флага в прерываниях. И он вполне обоснованный: У меня опрос кнопок висит на тех же ножках, что и индикатор. А поскольку индикатор у меня в основном цикле — опросить по прерыванию кнопки — не представляется возможным. Да и смысл пропадает городить что-то. Поэтому в прерывании только флаг, а опрос в майне между выводами на экран. Обрабатываем флаг, по типу «пора опросить». Больше никаких флагов. Все работает стабильненько и весьма шустро. Например, при запросе от UART я довольно быстро отвечаю — практически сразу. Экономлю время. Другое устройство, запрашивающая инфу — имеет таймер. Если слейв не ответил за короткое время — значит его нет. А слейвов там на линии не мало (около 200). Короче эффективность (заполняемость) канала почти 100%. И скорость работы линии впечатляет. Если с умом сделать…
Это тоже не правило — мне попадалось всего два устройства, где требовался именно такой подход к построению. Обычно же у меня там всякие интенсивные вычисления и работа по приёму/передаче данных, которым спать просто некогда =) Да и в большинстве случаем на потребление всем пофиг — нагрузка ест на порядки больше.
Да, в условиях батарейного питания структура проекта будет иной. В остальных случаях лучше сделать её малосвязанной и модульной, имхо.
Да, в условиях батарейного питания структура проекта будет иной. В остальных случаях лучше сделать её малосвязанной и модульной, имхо.
- teplofizik
- 12 августа 2013, 14:43
- ↑
- ↓
Для необходимости перекрываний есть просто прерывания относительно основного кода. Если они действительно должны быть выполнены тютелька в тютельку тотчас же, пусть будет так.
Я не про такую портируемость. Мы заранее создаём такой модуль, который бы включал в себя все эти флажки и прочую контроллерозависимую фигню, а на выходе имел универсальный и понятный интерфейс над основными и нужными нам функциями периферийного модуля. В дальнейшем, если нам требуется перенести код на другой контроллер, мы делаем точно такой же по логике модуль там. Остальной код от этого не поменяется ни капельки. Точно так же потом можно создавать другие проекты, используя готовый универсальный модуль. При возникновении нестандартных требований — модуль разрушается и перепиливается под задачу/интегрируется с вызываемым кодом, для ускорения ли или ещё чего. Но все слияния логики и низкоуровневой бяки лучше если будут исключением, чем правилом.
Использование процессорного времени важно, в основном, в разного рода автономных устройствах с ориентацией на низкое потребление, там идут другие требования и принципы построения программы. Когда проц крутится постоянно, что-то обрабатывая, не так и важно, где он крутится.
Я не про такую портируемость. Мы заранее создаём такой модуль, который бы включал в себя все эти флажки и прочую контроллерозависимую фигню, а на выходе имел универсальный и понятный интерфейс над основными и нужными нам функциями периферийного модуля. В дальнейшем, если нам требуется перенести код на другой контроллер, мы делаем точно такой же по логике модуль там. Остальной код от этого не поменяется ни капельки. Точно так же потом можно создавать другие проекты, используя готовый универсальный модуль. При возникновении нестандартных требований — модуль разрушается и перепиливается под задачу/интегрируется с вызываемым кодом, для ускорения ли или ещё чего. Но все слияния логики и низкоуровневой бяки лучше если будут исключением, чем правилом.
Использование процессорного времени важно, в основном, в разного рода автономных устройствах с ориентацией на низкое потребление, там идут другие требования и принципы построения программы. Когда проц крутится постоянно, что-то обрабатывая, не так и важно, где он крутится.
- teplofizik
- 12 августа 2013, 14:36
- ↑
- ↓
Обычно её стоит делать внешней, а не inline, иначе нет инкапсуляции =)
Я уже говорил:
1. Время обработки прерывания зависит от внешней неопределённой функции. Это не очень хорошо. В стм32 с этим свободнее, конечно, как и во всех кортексах. но конкретно это же прерывание она вторично не пустит в обработку, пока не закончим это.
2. Вызываемая функция работает параллельно остальному коду — возможен конфликт при доступе к одной переменной, если она часто используется и там, и сям. Если редко то всё же маловероятно. Но возможно.
3. В системах, где используется модуль защиты памяти (MPU) доступ к памяти в прерываниях и обычном коде отличается кардинально (у обычного он может быть для безопасности зарезан до упора). Таким образом можно ограничивать доступ к памяти даже «в прерываниях». Особенно это актуально в ОС.
4. Можно уменьшить время блокирования процессора инструкциями запрета прерываний (например, обеспечение атомарности доступа) и самими прерываниями, давая возможность сработать сразу по моменту возникновения события. В основном, это относится к контроллерам, где нет вложенных прерываний.
5. Конечно, можно сказать — зачем тогда прерывания, если флаги и так расставляются в нужных регистрах — бери и смотри, но часто ж надо что-то сделать, проверить, посчитать, и при каких-то условиях вызвать обработчик, а не просто его вызвать. А обычно проверки лучше делать сразу, а не как попало><
В таких случаях, для просто перенаправления, конкретно в кортексах можно использовать динамическую таблицу векторов вместо зашитой во флеш. Хотим перенаправить — пишем туда адрес нужного обработчика. Вот и всё, никаких затрат на доп. вызов не требуется. Но такое редко бывает>< Обычно нужно выяснить причину прерывания, а дальше разбираться, кому и куда её направлять, да и надо ли вообще.
В общем-то такую конструкцию я применяю, если действительно надо, но всё же стараюсь избегать…
Я уже говорил:
1. Время обработки прерывания зависит от внешней неопределённой функции. Это не очень хорошо. В стм32 с этим свободнее, конечно, как и во всех кортексах. но конкретно это же прерывание она вторично не пустит в обработку, пока не закончим это.
2. Вызываемая функция работает параллельно остальному коду — возможен конфликт при доступе к одной переменной, если она часто используется и там, и сям. Если редко то всё же маловероятно. Но возможно.
3. В системах, где используется модуль защиты памяти (MPU) доступ к памяти в прерываниях и обычном коде отличается кардинально (у обычного он может быть для безопасности зарезан до упора). Таким образом можно ограничивать доступ к памяти даже «в прерываниях». Особенно это актуально в ОС.
4. Можно уменьшить время блокирования процессора инструкциями запрета прерываний (например, обеспечение атомарности доступа) и самими прерываниями, давая возможность сработать сразу по моменту возникновения события. В основном, это относится к контроллерам, где нет вложенных прерываний.
5. Конечно, можно сказать — зачем тогда прерывания, если флаги и так расставляются в нужных регистрах — бери и смотри, но часто ж надо что-то сделать, проверить, посчитать, и при каких-то условиях вызвать обработчик, а не просто его вызвать. А обычно проверки лучше делать сразу, а не как попало><
В таких случаях, для просто перенаправления, конкретно в кортексах можно использовать динамическую таблицу векторов вместо зашитой во флеш. Хотим перенаправить — пишем туда адрес нужного обработчика. Вот и всё, никаких затрат на доп. вызов не требуется. Но такое редко бывает>< Обычно нужно выяснить причину прерывания, а дальше разбираться, кому и куда её направлять, да и надо ли вообще.
В общем-то такую конструкцию я применяю, если действительно надо, но всё же стараюсь избегать…
- teplofizik
- 12 августа 2013, 16:48
- ↑
- ↓
1 если заморочиться то думаю можно процессору как бе намекнуть что прерывание закончилось чтобы можно было войти вторично
2 если используется и там и сям — добро пожаловать атомарность.
3 Осью у меня и не пахнет. Не люблю тех кто решает задачку уровня 2х2 на проце в гигагерц, линуксом, с полуминутной загрузкой и прочей чушью в обвязке. По остальному вроде согласен.
2 если используется и там и сям — добро пожаловать атомарность.
3 Осью у меня и не пахнет. Не люблю тех кто решает задачку уровня 2х2 на проце в гигагерц, линуксом, с полуминутной загрузкой и прочей чушью в обвязке. По остальному вроде согласен.
1. Городить костыли, чтоб это завелось и ничего не сломало? =)
2. Ага, критические секции, семафоры и мьютексы в пользовательском коде, чтоб её обеспечить?=) *битбенд есть не везде и не всегда пригоден*
3. Ну да, но это я уже пошёл «по идее», зачем это надо может быть.
2. Ага, критические секции, семафоры и мьютексы в пользовательском коде, чтоб её обеспечить?=) *битбенд есть не везде и не всегда пригоден*
3. Ну да, но это я уже пошёл «по идее», зачем это надо может быть.
- teplofizik
- 14 августа 2013, 23:29
- ↑
- ↓
} } } } // и закрывающие скобки принято ставить по одной на строку— это для экономии места. Для визуализации это позволительно. Понятное дело, что функциональность этих скобок не совпадает с визуальным представлением, однако чтобы не городить 4 строки по сути пустые — для отображения — удобнее так. Кстати этому и профессора в институте учат.
Мало ли чему учат =) Скобочки на отдельной строке не просто так должны быть, а ещё и прямо под открывающими — так визуально видно весь заключённый в них блок и вообще иерархию.
- teplofizik
- 07 августа 2013, 17:20
- ↑
- ↓
так они и есть — строго под открывающими. Просто на одной строчке. Функциональность у них обратная — это понятно. Но для визуализации — оптимально. У меня в исходнике они располагаются так, как вы говорите. Ну что тут развивать тему из ничего?
Да, но оперировать блоками это несколько мешает. Бывает, надо вставить в хвост блока else, или сдвинуть целый блок (выделение + таб), или убрать, или закомментить, или переместить. Загромождение скобочек эту свободу ограничивает: сначала надо выделить скобочки нужного блока, а потом их уже оперировать:)
- teplofizik
- 07 августа 2013, 18:21
- ↑
- ↓
Экономить место не нужно. Код должен быть читабельный и именно поэтому скобочки ставятся каждая на свою личную строку, в той же позиции, что и открывающая.
Кроме того, не рекомендуется выкидывать скобки из конструкций вида
Этот кусок кода слишком склонен к возникновению малозаметной синтаксической ошибки.
Кроме того, не рекомендуется выкидывать скобки из конструкций вида
if(Slow_Timers[i].timer_mode==TIMER_MODE_ONCE)
Slow_Timers[i].timer_mode = TIMER_MODE_STOP;
Этот кусок кода слишком склонен к возникновению малозаметной синтаксической ошибки.
Кстати этому и профессора в институте учат.Печально, что они плохому учат.
если делать отступ, как в описанном примере — ошибок не будет. Если захочется еще строчку воткнуть — будь добр, скобочки поставь. Все зависит от личного восприятия, наверное.
Это рекомендации тех, кто на С пишет давно и много. Рано или поздно будет написано
if(Slow_Timers[i].timer_mode==TIMER_MODE_ONCE);
Slow_Timers[i].timer_mode = TIMER_MODE_STOP;
Потому у меня маленькое правило: или пиши всё в одну строку, если маленькое, или городи новый блок:
Без исключений. Блоки ещё, что здорово, позволяют локальные переменные создавать.
if(a) b;
if(a)
{
b;
}
Без исключений. Блоки ещё, что здорово, позволяют локальные переменные создавать.
- teplofizik
- 07 августа 2013, 20:22
- ↑
- ↓
Нет, это от другого огораживает:
Хотя, я на таком не попадался пока, ввиду пренебрежения макросами. Но лучше, чтоб привычка не давала шанса на ошибку =)
Поясняю:
Если B — многострочный макрос и по какой-то причине без скобок фигурных, он будет работать неправильно, так, как в верхнем примере.з. Условно выполнится только первая строка макроса.
if(a)
b;
c;
if(a)
{
b;
c;
}
Хотя, я на таком не попадался пока, ввиду пренебрежения макросами. Но лучше, чтоб привычка не давала шанса на ошибку =)
Поясняю:
if(a)
B;
Если B — многострочный макрос и по какой-то причине без скобок фигурных, он будет работать неправильно, так, как в верхнем примере.з. Условно выполнится только первая строка макроса.
- teplofizik
- 07 августа 2013, 21:14
- ↑
- ↓
Не все это знают, к сожалению.
Я макросами так вообще не пользуюсь почти.
Я макросами так вообще не пользуюсь почти.
- teplofizik
- 08 августа 2013, 17:43
- ↑
- ↓
HAL модуль… мммм… понятно… Вопрос: Каким образом человек, втыкающий мой модуль, свободный от железяки должен знать, какие именно функции нужны ему? Это где-то все нужно описать? И сколько таких HAL модулей должно быть в проекте? один на весь проект или на каждую такую либу? Как это организуется у вас?
Хм, я давно похожим пользуюсь, очень удобно. Только всё заведомо спрятал внутри модуля, оставив лишь торчащим интерфейс:
Для увеличения скорости можно вызывать обработчики прям из прерывания, если они небольшие =) Но обычно это для периодических фоновых процессов, где точность не важна. В стм32 сделано на SysTick, в авр на TIMER0, в lpc23xx тоже на нулевом таймере. Хотя какая разница, на чём делать =)
Пример:
Было много вариантов, но в итоге пока что всё свелось к такому.
// Запуск таймера с заданной частотой (дискретизация)
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);
}
Было много вариантов, но в итоге пока что всё свелось к такому.
- teplofizik
- 06 августа 2013, 23:25
- ↓
struct sSlow_Timer
А если вместо инкремента использовать декремент счетчика? Тогда для запуска таймера просто в counter записываем нужно число. В прерывании уменьшаем на единицу и сравниваем его с нулем. Итого можно сэкономить по uint16_t на каждый экземпляр таймера, ибо не понадобится переменная compare. Да и сравнение с нулем, если не ошибаюсь, выходит чуть быстрее.
{
uint8_t timer_mode;
uint16_t counter;
uint16_t compare;
void (*handler)();
};
А если вместо инкремента использовать декремент счетчика? Тогда для запуска таймера просто в counter записываем нужно число. В прерывании уменьшаем на единицу и сравниваем его с нулем. Итого можно сэкономить по uint16_t на каждый экземпляр таймера, ибо не понадобится переменная compare. Да и сравнение с нулем, если не ошибаюсь, выходит чуть быстрее.
Тут суть в том, что таймер может «автоматически перезапускаться» (TIMER_MODE_REPEAT). То есть всё равно прийдется хранить период таймера, что бы повторно его запустить при срабатывании.
Но сделать декремент полезно для последующих оптимизаций. Когда список таймеров в поле времени содержит «остаток времени» после завершения предидущего таймера. Тогда при счете проверяется только первый таймер из списка, следующие заведомо известно сработают не раньше первого и им отсчет проводить не требуется. Соответственно список при этом должен быть упорядоченным.
А вот код:
Но сделать декремент полезно для последующих оптимизаций. Когда список таймеров в поле времени содержит «остаток времени» после завершения предидущего таймера. Тогда при счете проверяется только первый таймер из списка, следующие заведомо известно сработают не раньше первого и им отсчет проводить не требуется. Соответственно список при этом должен быть упорядоченным.
А вот код:
if(Slow_Timers[i].compare==Slow_Timers[i].counter)
поспособствует проблеме при обновлении периода таймера в текущей реализации. Так установили таймер на 10 секунд, прошло 7 из них и мы переставили период на 5 секунд. Ждем переполнения счетчика. :)
Кстати, я подобные конструкции сразу инвертирую:
в примерно такое:
Сразу уменьшается уровень вложенности, да и можно несколько проверок в столбик повесить, не делая гигантского условия в if.
void func(...)
{
if (index<SLOW_TIMER_COUNT)
{
// code
}
}
в примерно такое:
void func(...)
{
if (index>=SLOW_TIMER_COUNT) return;
// code
}
Сразу уменьшается уровень вложенности, да и можно несколько проверок в столбик повесить, не делая гигантского условия в if.
- teplofizik
- 07 августа 2013, 20:19
- ↓
Никто не запрещает добавить туда возвращаемое значение, если это логически обосновано) Собственно, я так и делаю:
Но в данном случае это просто пример оптимизации записи кода при неизменной логике работы. Очень встречающийся штамп, потому полезный.
static const TChannel * pwm_GetChannel(int Channel)
{
if (Channel >= ChannelCount) return 0;
return &Channels[Channel];
}
Но в данном случае это просто пример оптимизации записи кода при неизменной логике работы. Очень встречающийся штамп, потому полезный.
- teplofizik
- 07 августа 2013, 21:20
- ↑
- ↓
Чем же это некрасиво? Очень даже.
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;
}
- teplofizik
- 07 августа 2013, 21:54
- ↑
- ↓
Разве? Вполне обычно ожидать, что в коде несколько ретурнов, а в конце стоит что-то типа дефолтного варианта, когда ничего не подошло.
- teplofizik
- 07 августа 2013, 22:04
- ↑
- ↓
В данном случае возможно, но обычно дальше там несколько инструкций, я просто взял самую короткую.
- teplofizik
- 07 августа 2013, 22:02
- ↑
- ↓
На этот счет есть разные мнения, но я знаю как минимум две техники, которые реально улучшают читаемость и надежность кода и которые невозможны без нескольких return-ов. Первая — так называемое контрактное программирование, при котором в начале тела функции проверяются входные параметры на валидность (что, собственно, и есть часть «контракта», то есть соглашения о входе и выходе/поведении функции). «Официального» названия второй техники я не знаю, для себя я ее называю early cut-off. Идея в том, что бы обнаруживать ошибки или специальные случаи как можно раньше, и по возможности быстрее вернуть ошибку. При таком подходе основная логика функции существенно очищается от проверок, глубина вложения if-ов резко снижается и код, реализующий основную функциональность, становится гораздо прозрачнее для чтения и понимания. Обе техники тесно переплетаются, первая несколько более формальна и ее имеет смысл применять «глобально», то есть по всему коду приложения. Вторая годится и в тех случаях, когда остальной код написан без такого подхода.
Все IMHO, естественно.
Все IMHO, естественно.
Вот что любопытно — везде номер int и везде отсутствует проверка на его неотрицательность. А что скажет функция, если запросить у нее канал (или таймер) -1?
Да уж, будет атата=D Посему правильнее:
*пошёл пофиксил*
static const TChannel * pwm_GetChannel(int Channel)
{
if (Channel < 0) return 0;
if (Channel >= ChannelCount) return 0;
return &Channels[Channel];
}
*пошёл пофиксил*
- teplofizik
- 07 августа 2013, 22:10
- ↑
- ↓
Хах, я немного исправлял форматирование библиотечки и понял, почему такой лаг возник: там вместо int должен быть enum TPwmChannel, который беззнаковый и по какой-то причине сюда не записан, я забыл поменять, наверное. Потому всё вернул взад, изменив тип аргумента.з.
- teplofizik
- 08 августа 2013, 10:41
- ↑
- ↓
а можно же и так проверить:
Всего одно условие проверки. Конечно, для понимания сложнее, зато для скорости работы — очень даже
if((uint32_t) Channel>= ChannelCount) return;
Всего одно условие проверки. Конечно, для понимания сложнее, зато для скорости работы — очень даже
Конечно, для понимания сложнее, зато для скорости работы — очень дажеНе лучше ли в таком случае сразу uint'ом индекс и передавать?
Ну и подобные хаки — не очень хорошо. Иногда может аукнуться при портировании, например.
можно и uint'ом. Только если при инициализации функция возвращает -1, то значит она использует знаковое число. в знаковой переменной обычно и хранят. И в знаковой запрашивается. А то получается точно не красиво. А то, как функция сравнивает это число у себя внутри — никого не должно волновать. Главное, чтобы реагировала правильно. При портировании никаких проблем возникать не должно в принципе.
Никто не заставляет возвращать из функции AddTimer знаковое число. Ошибку можно идентифицировать другим значением — например, uint(-1), как это делается в Win32 API.
Выигрыш в скорости тоже дают сомнительный. К тому же, гнаться за производительностью в ущерб понятности кода стоит только тогда, когда она действительно критична.
Главное, чтобы реагировала правильно. При портировании никаких проблем возникать не должно в принципе.Вот как раз этого подобные хаки и не гарантируют. Один автор приводил подобный хак, который за все время работы программы на всех компьютерах, на которых она запускалась, не сэкономил столько времени, сколько ушло на его поиск при портировании.
Выигрыш в скорости тоже дают сомнительный. К тому же, гнаться за производительностью в ущерб понятности кода стоит только тогда, когда она действительно критична.
B ещё для удобочитаемости, особенно в хороших редакторах с подсветкой кода и подсказками.
Кучу дефайнов с таблицей состояний лучше перебить в enum:
Кучу дефайнов с таблицей состояний лучше перебить в 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;
- teplofizik
- 07 августа 2013, 20:25
- ↓
Вот, я примерно, не трогая код и логику, чуть косметически подправил два исходника, как бы это было ближе и понятнее мне визуально:
slowtimer.h
slowtimer.c
В целом там ничего не поменялось. Кроме пары конструкций и переноса макросов внутрь модуля — снаружи они точно не нужны.
Да уж, а как вспомню свой первый опыт конструирования программного таймера для авр два года назад — аж плакать хочется, настолько всё там убого было.
slowtimer.h
slowtimer.c
В целом там ничего не поменялось. Кроме пары конструкций и переноса макросов внутрь модуля — снаружи они точно не нужны.
Да уж, а как вспомню свой первый опыт конструирования программного таймера для авр два года назад — аж плакать хочется, настолько всё там убого было.
- teplofizik
- 07 августа 2013, 21:59
- ↓
в целом, мне понравилось. только несколько замечаний:
По хедеру все нравится. Весьма логично. PS stm32f10x.h включает в себя stdint. В общем зачет! Спасибо за проделанную работу
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. В общем зачет! Спасибо за проделанную работу
убрали «struct», а напрасноЯ там typedef для этого прикрутил =) Потому struct не нужен, а точнее, теперь на него будет ругаться.
мне такое не нра))Так реально короче и код за экран не уползает =D Хотя, конечно, может скрадывать логику…
но переменную новую зачем?Это просто так, по привычке больше — очень часто используется шаблон. Там если несколько аргументов, получается чересчур длинно ^^"
Да, включает =) Но зато без него получился простой HAL — теперь никто не скажет, на чём же таймер работает =D
- teplofizik
- 07 августа 2013, 22:19
- ↑
- ↓
Видно, что вы программер — инвертор!)) мне такое не нра))
void (* Handler)(void) = Slow_Timers[i].handler;
я так понимаю, Вам не нравится синтаксис «указатель на функцию»?
На самом деле, можно (и, ИМХО, нужно) один раз определить тип:
typedef void (*TimerCallBack)(void);
И дальше пользоваться этим типом
TimerCallBack Handler = Slow_Timers[i].handler;
…
if(Handler) Handler();
ЕМНИП, как-то так…
typedef void (*TimerCallBack)(void);
Упс, извиняюсь, опечатка, тип назван «не по ГОСТу» :), подразумевалось TimerCallback, хотя это дело стиля и не принципиально.
Идея в том, что в данном случае есть смысл определить свой тип для указателя на функцию, и везде его использовать. ИМХО, так код становится более понятным и читаемым. Например
int8_t Slow_Timer_Add(uint8_t mode, uint16_t counter, TimerCallback handler);
Я, кстати, так и не понял, как тут простым способом переводить нужную частоту в удавыcounter. Нужно ж обычно, чтоб он не просто регулярно тикал, а с весьма конкретной реальной частотой, измеряемой во вполне себе человеческих герцах. Для каждой инициализации вести пересчёт? =) Точнее, здесь задаётся период в удавах…
- teplofizik
- 09 августа 2013, 13:58
- ↓
Пересчетом это и назвать нельзя. Если требуется частота — пишем
SLOW_TIMER_FREQUENCY / FREQUENCY
в инициализаторе или модифи — функции. В случае с переменной FREQUENCY в любом случае пересчет будет — либо внутрях, или в наружностях. А вот если использовать константы — то в моем случае пересчета не будет вообще. я начал свой путь с ассеблера и поэтому чувствую спиной лишние преобразования.
Добавлю в общую копилку классику, если кто не знает en.wikipedia.org/wiki/Fast_inverse_square_root
Еще очень интересные вещи встречаются внутри www.enlightenment.org/
Но это больше обработка полупрозрачности между иконками в PNG.
ЗЫ Rasterman висит мне пиво — он был уверен что EFL невозможно спортировать на VxWorks и ThreadX. ;-)
Еще очень интересные вещи встречаются внутри www.enlightenment.org/
Но это больше обработка полупрозрачности между иконками в PNG.
ЗЫ Rasterman висит мне пиво — он был уверен что EFL невозможно спортировать на VxWorks и ThreadX. ;-)
Мало =) Большое количество действий работают на весьма низких частотах: моргание светодиода, опрос температурного датчика, отсчёт таймаутов неспешных и т.д. Причём, действий таких в проекте может быть достаточно много… Таймеров на все не напасёшься, особенно, если они заняты на какой-нибудь ШИМ.
А так всего один таймер может окучивать целую грядку фоновых процессов, которые ещё и достаточно легко настраиваются: «запускай такую-то функцию вот с таким периодом/частотой».
А так всего один таймер может окучивать целую грядку фоновых процессов, которые ещё и достаточно легко настраиваются: «запускай такую-то функцию вот с таким периодом/частотой».
- teplofizik
- 17 августа 2013, 01:13
- ↑
- ↓
Эм. Я обычно на SysTick`е формирую временную сетку удобным интервалом, а уже в мэйне конечным автоматом разруливаю события. Как ни странно, получается и удобнее и мене затратно по ресурсам.
Я тоже на сустике (если брать стм32), только вот конечный автомат вынесен абсолютно отдельно… =)
- teplofizik
- 17 августа 2013, 21:42
- ↑
- ↓
(если брать стм32)топик ведь про стм32.
только вот конечный автомат вынесен абсолютно отдельно… =)У меня обычно мейн — инициализация и обслужка (в том числе и конечный автомат таймеров).
Ну я ж программным таймером пользуюсь везде, не только в стм32 =)
Хм, у меня мейн обычно вообще минималистичен, только вызовы, а вся логика раскидана отдельно.з.
Хм, у меня мейн обычно вообще минималистичен, только вызовы, а вся логика раскидана отдельно.з.
- teplofizik
- 17 августа 2013, 22:20
- ↑
- ↓
Хм, ладно, вот кусочек проекта stm32f4base (заготовка для относительно быстрого прототипирования программ отладочки stm32f4discovery):
systick.c (инициализируется из не приведённого тут drivers.c)
main.c
test.c — hello world по морганию светодиодом.
systick.c (инициализируется из не приведённого тут drivers.c)
main.c
test.c — hello world по морганию светодиодом.
- teplofizik
- 18 августа 2013, 12:38
- ↑
- ↓
NVIC_EnableIRQ(SysTick_IRQn);
Это у вас работает? У меня стабильно вываливалось в хард фаулт. Вообще для инициализации SysTick этого не нужно в принципе.
В Стм32ф1 это дейтвительно ведёт к хард фолту, но этот код для стм32ф4
- teplofizik
- 21 августа 2013, 14:24
- ↑
- ↓
Причина, по которой я не написал на systick — очень большой приоритет этого таймера (выше 0-го). Systick больше подходит для операционных систем.
Можно поднять в прерывании флаг «тик сработал» и затем обработать таймеры в главном цикле, на низком приоритете.
Собственно, в своей недортос на примере ДиХалтовской, которую заюзал в таймере для ванночки, я так и сделал. Правда, там свои недостатки — есть вероятность прощелкать тик, если обработчики будут долго тупить.
Собственно, в своей недортос на примере ДиХалтовской, которую заюзал в таймере для ванночки, я так и сделал. Правда, там свои недостатки — есть вероятность прощелкать тик, если обработчики будут долго тупить.
Комментарии (146)
RSS свернуть / развернуть