Многозадачный программный таймер, ver 2.0

Поскольку мой таймер не отличается большой приоритетностью и частотой срабатывания — его прерыванию был назначен минимальный приоритет — 15.
Все остальные прерывания могут его «обскакивать» без проблем. Проблемы начинаются, когда вот в этих самых прерываниях имеется необходимость управлять программным таймером. И еще не надо забывать и об основном цикле. Эти функции нужны и для основного цикла тоже.
И что же теперь делать?
Почитав замечательную статью на нашем форуме Атомарные операции в Cortex-M3 у меня возникла идея, как это воплотить. Хорошая это идея или нет — решать вам.
Итак, сначала идет заголовок:
#ifndef SYSTEM_TIMER_H
#define SYSTEM_TIMER_H
//-------- <<< Use Configuration Wizard in Context Menu >>> --------------------
// <h> System Timer Initialization
// <o> Count: <1-20>
// <i> System-Timer count setup
#define SYSTEM_TIMER_COUNT 25
// <o> Frequency Hz <5-2000>
// <i> System-Timer Frequency setup
#define SYSTEM_TIMER_FREQUENCY 500
// </h>
typedef enum
{
tm_Free,
tm_Stop,
tm_Repeat,
tm_Once
} Timer_Mode_t;
#include <stdint.h>
void Init_System_Timer(void);
uint8_t System_Timer_Get_Status(int8_t TimerId);
void System_Timer_Reset_Status(int8_t TimerId);
int8_t System_Timer_Add(Timer_Mode_t mode, uint16_t compare, void (*handler)());
int8_t System_Timer_Set_Mode(int8_t TimerId, Timer_Mode_t mode);
void System_Timer_Clear_Counter(int8_t TimerId);
void System_Timer_Set_Compare(int8_t TimerId, uint16_t compare);
uint16_t System_Timer_Get_Counter(int8_t TimerId);
void System_Timer_Delete(int8_t TimerId);
void System_Timer_Lock(int8_t TimerId);
void System_Timer_Unlock(int8_t TimerId);
#endif
далее само «тело»:
#include "stm32f10x.h"
#include "system_timer.h"
#include "RTE_Device.h"
#include "mutex.h"
#include "bitbanding.h"
typedef struct
{
uint8_t timer_mode;
uint8_t timer_status;
// 0..6 bit - lock counter; 7 bit - timer event;
uint16_t counter;
uint16_t compare;
void (*handler)();
} System_Timer_t;
volatile System_Timer_t System_Timers[SYSTEM_TIMER_COUNT];
uint8_t System_Timer_Get_Status(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
if (System_Timers[TimerId].timer_mode != tm_Free)
return System_Timers[TimerId].timer_status & 0x80;
return 0;
}
void System_Timer_Reset_Status(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
BIT_BAND_SRAM(&System_Timers[TimerId].timer_status,7) = 0;
}
void System_Timer_Lock(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
Atomic8AddAndFetch(&(System_Timers[TimerId].timer_status), 1);
}
void System_Timer_Unlock(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
Atomic8AddAndFetch(&(System_Timers[TimerId].timer_status), -1);
}
void System_Timer_Delete(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
System_Timers[TimerId].timer_mode = tm_Free;
__CLREX():
}
}
void Init_System_Timer(void)
{
uint8_t i;
NVIC_SetPriority(SysTick_IRQn, 15);
for(i = 0; i < SYSTEM_TIMER_COUNT; i++)
System_Timer_Delete(i);
SysTick_Config(RTE_SYSCLK / SYSTEM_TIMER_FREQUENCY -1);
}
int8_t System_Timer_Set_Mode(int8_t TimerId, Timer_Mode_t mode) //if OK - return 1
{
int8_t temp = 0;
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
if (mode != tm_Free)
{
do
{
temp = __LDREXB(&System_Timers[TimerId].timer_mode);
if (temp != tm_Free)
temp = mode;
}while(__STREXB(temp, &System_Timers[TimerId].timer_mode));
}
}
return temp != 0;
}
void System_Timer_Clear_Counter(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
System_Timers[TimerId].counter = 1;
__CLREX():
}
}
void System_Timer_Set_Compare(int8_t TimerId, uint16_t compare)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
System_Timers[TimerId].compare = compare;
}
}
uint16_t System_Timer_Get_Counter(int8_t TimerId)
{
uint16_t counter = 0;
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
counter = System_Timers[TimerId].counter;
}
return counter;
}
int8_t System_Timer_Add(Timer_Mode_t mode, uint16_t compare, void (*handler)())
{
uint8_t i;
Timer_Mode_t temp;
uint8_t result;
if(mode != tm_Free)
{
for (i = 0; i < SYSTEM_TIMER_COUNT; i++)
{
do
{
result = 2;
temp = (Timer_Mode_t)__LDREXB(&System_Timers[i].timer_mode);
if (temp == tm_Free)
result = __STREXB(tm_Stop, &System_Timers[i].timer_mode);
else
__CLREX();
}while(result == 1);
if (!result)
{
System_Timers[i].counter = 1;
System_Timers[i].compare = compare;
System_Timers[i].handler = handler;
System_Timers[i].timer_status = 0;
System_Timers[i].timer_mode = mode;
return i;
}
}
}
return -1;
}
void SysTick_Handler(void)
{
uint8_t i;
void (*handler)();
Timer_Mode_t mode;
for(i = 0; i < SYSTEM_TIMER_COUNT; i++)
{
if ((!(System_Timers[i].timer_status & 0x7F)) && (System_Timers[i].timer_mode > tm_Stop))
{
if(System_Timers[i].counter >= System_Timers[i].compare)
{
System_Timers[i].counter = 1;
handler = System_Timers[i].handler;
do
{
mode = (Timer_Mode_t)__LDREXB(&(System_Timers[i].timer_mode));
if (mode == tm_Once)
mode = tm_Stop;
}while(__STREXB(mode, &System_Timers[i].timer_mode));
BIT_BAND_SRAM(&System_Timers[i].timer_status,7) = 1;
if ((handler) && (mode))
handler();
}
else
Atomic16AddAndFetch(&System_Timers[i].counter, 1);
}
}
}
А теперь разберемся что да как:
Функция SysTick_Handler() является тем самым «сердцем» нашего таймера. Здесь происходит периодичное выполнение всех назначенных задач. Все остальные функции — доступны извне, то есть, могут вызываться как и из основного цикла, так и из более приоритетных прерываний. Следовательно — работа этого «сердца» может прерывать свои же интерфейсные функции, и может быть прервана ими же. Для решения этих задач требуется синхронизация, и здесь активно используется связка команд процессора LDREX и STREX. Теория работы этих команд хорошо изложена в вышеуказанной статье, поэтому я не буду вдаваться в подробности. Итак, разберем функцию SysTick_Handler:
void SysTick_Handler(void)
{
uint8_t i;
void (*handler)();
Timer_Mode_t mode;
for(i = 0; i < SYSTEM_TIMER_COUNT; i++)
{
if ((!(System_Timers[i].timer_status & 0x7F)) && (System_Timers[i].timer_mode > tm_Stop))
{
if(System_Timers[i].counter >= System_Timers[i].compare)
{
System_Timers[i].counter = 1;
handler = System_Timers[i].handler;
До сих пор нет ничего необычного. Организуется цикл, проверяется режим работы таймеров, сравниваются счетчики, проверка блокировки таймеров и сброс счетчиков в начало (==1) при удачном сравнении. Отмечу лишь, что мы сохраняем указатель на функцию в отдельную переменную. Для чего это — будет разъяснено позже.Дальше нам нужно переключить режим таймера с «однократный» (tm_Once) на «остановлен» (tm_Stop).
Только вот незадача: А вдруг в тот самый момент, когда мы производим «чтение — модификация — запись»(далее ЧМЗ) у нас этот самый таймер был удален вызовом функции System_Timer_Delete(TimerId)? Вот здесь нам на помощь и придут те самые команды процессора LDREX STREX. Смотрим дальше:
do
{
mode = (Timer_Mode_t)__LDREXB(&(System_Timers[i].timer_mode));
if (mode == tm_Once)
mode = tm_Stop;
}while(__STREXB(mode, &System_Timers[i].timer_mode));
Ну что, кажется теперь все будет работать. Если в процессе ЧМЗ флаг эксклюзивного доступа будет сброшен — то однозначно произойдет все сначала. Цикл повторится и данные будут правильно обработаны. Кстати сказать, не нужно забывать, что все подобные переменные должны иметь приписочку "volatile" иначе все будет напрасно.Едем дальше:
BIT_BAND_SRAM(&System_Timers[i].timer_status,7) = 1;
if ((handler) && (mode))
handler();
Здесь, собственно, устанавливаем событие вызова (7 бит статуса). Используется модуль «bitbanding.h» далее — вызов подпрограмм. На последнем рубеже снова удостоверимся, что удаления не было. После чего осуществляем вызов. Даже если таймер удалился после последней проверки — мы успели сохранить указатель на функцию, и счетчик команд не уйдет абы куда, а последний раз (на прощание) обратится к законной функции. Далее:
}
else
Atomic16AddAndFetch(&System_Timers[i].counter, 1);
}
}
}
А здесь у меня используется немного модифицированная функция из статьи уважаемого neiver'а. и выглядит так:uint16_t Atomic16AddAndFetch(volatile uint16_t * ptr, int16_t value)
{
uint16_t oldValue, newValue;
do
{
oldValue = __LDREXH(ptr);
newValue = oldValue + value;
}while(__STREXH(newValue, ptr));
return newValue;
}
Она атомарно инкрементирует счетчик, позволяя внедренному высокоприоритетному прерыванию сбрасывать его без последствий.Так, с главной функцией разобрались.
Теперь поговорим об интерфейсных функциях:
uint8_t System_Timer_Get_Status(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
if (System_Timers[TimerId].timer_mode != tm_Free)
return System_Timers[TimerId].timer_status & 0x80;
return 0;
}
Здесь мы получаем положительное значение (0x80) если таймер сработал или 0 — если нет.void System_Timer_Reset_Status(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
BIT_BAND_SRAM(&System_Timers[TimerId].timer_status,7) = 0;
}
Здесь сбрасываем атомарно событие таймера.void System_Timer_Lock(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
Atomic8AddAndFetch(&(System_Timers[TimerId].timer_status), 1);
}
void System_Timer_Unlock(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
Atomic8AddAndFetch(&(System_Timers[TimerId].timer_status), -1);
}
Здесь, собственно функция блокировки/разблокировки одного из программных таймеров. Поскольку эти функции могут быть прерваны самими же собой или своими «антиподами» из основного цикла и какого-нибудь прерывания, необходимо не потерять кол-во вхождений подобных функций. С этим опять же хорошо справляется функция Atomic8AddAndFetch(). Вот ее код:uint8_t Atomic8AddAndFetch(volatile uint8_t * ptr, int8_t value)
{
uint8_t oldValue, newValue;
do
{
oldValue = __LDREXB(ptr);
newValue = oldValue + value;
}while(__STREXB(newValue, ptr));
return newValue;
}
Функции блокировки/разблокировки не поддерживают более 127 вложений lock/unlock, так что будьте внимательней. Дополнительных проверок в целях экономии ресурсов процессора нет.Едем далее:
void System_Timer_Delete(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
System_Timers[TimerId].timer_mode = tm_Free;
__CLREX();
}
}
Здесь нет никаких блокировок и прочее. Здесь главное, timer_mode выполняется атомарной операцией. И добавляем сброс эксклюзива, на случай, если удаление будет вызвано в высокоприоритетном прерывании.Функция Init_System_Timer() не участвует в процессе работы, ее рассматривать не будем.
Едем дальше:
int8_t System_Timer_Set_Mode(int8_t TimerId, Timer_Mode_t mode) //if OK - return 1
{
int8_t temp = 0;
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
if (mode != tm_Free)
{
do
{
temp = __LDREXB(&System_Timers[TimerId].timer_mode);
if (temp != tm_Free)
temp = mode;
}while(__STREXB(temp, &System_Timers[TimerId].timer_mode));
}
}
return temp != 0;
}
Смысл работы этой функции в следующем: Изменяя режим работы таймера, не напороться на подлянку — если этот самый таймер был удален… Функция возвращает «0», если изменение не удалось по той самой причине, если все ОК — возвращаем единичкуДальше и дальше:
void System_Timer_Clear_Counter(int8_t TimerId)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
System_Timers[TimerId].counter = 1;
__CLREX();
}
}
Функции сброса счетчика. Поскольку мы записываем в поле сounter, необходимо поставить сброс эксклюзивного доступа, чтобы обозначить, что изменения в данной ячейке производились.
void System_Timer_Set_Compare(int8_t TimerId, uint16_t compare)
{
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
System_Timers[TimerId].compare = compare;
}
}
uint16_t System_Timer_Get_Counter(int8_t TimerId)
{
uint16_t counter = 0;
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
{
counter = System_Timers[TimerId].counter;
}
return counter;
}
Функции сброса счетчика, установки значения сравнения и считывания счетчика — производятся де-факто атомарно, поэтому тут городить ничего не требуется.Главное, чтобы в программе не путать местами используемые функции. Например, сначала мы сбрасываем счетчик, а уж потом изменяем значение сравнения (ну если это потребуется конечно)… И да, не забываем, что это все — volatile.Следующая функция требует более внимательного рассмотрения. System_Timer_Add() — позволяет добавлять новые таймеры уже в процессе работы. Предполагается, что эта функция будет перекрыта и сама собою, и прерыванием таймера SysTick. Алгоритм работы следующий. Функция ищет пустой программный таймер в массиве таймеров и занимает его. Если учитывать, что место может быть занято дважды(трижды, пятижды))) — то нужно как то от этого защититься. Смотрим код:
int8_t System_Timer_Add(Timer_Mode_t mode, uint16_t compare, void (*handler)())
{
uint8_t i;
Timer_Mode_t temp;
uint8_t result;
if(mode != tm_Free)
{
for (i = 0; i < SYSTEM_TIMER_COUNT; i++)
{
Организовываем первоначальные проверки и запускаем цикл. Далее:
do
{
result = 2;
temp = (Timer_Mode_t)__LDREXB(&System_Timers[i].timer_mode);
if (temp == tm_Free)
result = __STREXB(tm_Stop, &System_Timers[i].timer_mode);
else
__CLREX();
}while(result == 1);
Тот самый блок синхронизации. Сначала в temp помещаем значение режима таймера через LDREX.если temp — означает «пустой» таймер, то мы его пытаемся занять через запись STREX. Устанавливаем режим таймера как «остановлен», чтобы избежать несанкционированной обработки от прерывания SysTick.
Итак, запись удачна? — значит в result — 0, если не удалось записать — значит в result — единица и повтор по циклу. Впрочем, если таймер уже занят, то мы так же выкатываемся из цикла со значением в result == 2
Ну а дальше все просто:
if (!result)
{
System_Timers[i].counter = 1;
System_Timers[i].compare = compare;
System_Timers[i].handler = handler;
System_Timers[i].timer_status = 0;
System_Timers[i].timer_mode = mode;
return i;
}
}
}
return -1;
}
Главное, чтобы timer_mode — заполнялся последним. И здесь снова вспомним про заветный volatileНу вот, подробный обзор завершен, прошу ваше мнение, господа!
UPDATE:
В связи с развенчанием коллективного заблуждения по поводу работы пары LDREX — STREX нынешний код мною был подправлен.
Сделаю небольшой экскурс в историю.
Мною было замечено, что цикл LDREX и STREX работает не всегда так как предполагалось. Оглашу несколько заблуждений, витающих в рунете:
1. LDREX — STREX контролируют определенную ячейку в памяти и не допускают ее изменения в промежутке между этими командами.
2. LDREX — STREX не терпит обращения к памяти внутри блока.
Тест:
ptr = &Counters[channel];
do
{
oldValue = __LDREXH(ptr);
newValue = (oldValue < CHECK_COUNTER)? CHECK_COUNTER : oldValue;
(*ptr)++;
}while(__STREXH(newValue, ptr));
Результат отрицательный. Команда STREX успешно выполняется и глазом не моргнув, что ячейка подверглась изменению.3. LDREX — STREX не срабатывает, если пришло прерывание внутри блока.
Тест:
ptr = &Counters[channel];
do
{
oldValue = __LDREXH(ptr);
newValue = (oldValue < CHECK_COUNTER)? CHECK_COUNTER : oldValue;
EXTI->SWIER = 0x00000001;//программно вызываем прерывание
__NOP();
}while(__STREXH(newValue, ptr));
void EXTI0_IRQHandler(void)
{
EXTI->SWIER = 0x00000000;
EXTI->PR = 0x00000001;
(*ptr)++;
}
Результат абсолютно аналогичен. Система не отреагировала ни на прерывание, ни на изменение в нем ячейки памяти.Как же оно работает? А вот так:
ptr = &Counters[channel];
do
{
oldValue = __LDREXH(ptr);
newValue = (oldValue < CHECK_COUNTER)? CHECK_COUNTER : oldValue;
__CLREX(); // сбрасываем флаг эксклюзива
}while(__STREXH(newValue, ptr));
И так:ptr = &Counters[channel];
do
{
oldValue = __LDREXH(ptr);
newValue = (oldValue < CHECK_COUNTER)? CHECK_COUNTER : oldValue;
__STREXH(__LDREXH(ptr) + 1, ptr); //Инструкции LDREX - STREX. После выполнения сбрасывается флаг эксклюзива
}while(__STREXH(newValue, ptr));
И даже вот так:ptr = &Counters[channel];
ptr2 = &Counters[(channel + 1)&0x07];
do
{
oldValue = __LDREXH(ptr);
newValue = (oldValue < CHECK_COUNTER)? CHECK_COUNTER : oldValue;
__STREXH(__LDREXH(ptr2) + 1, ptr2);
}while(__STREXH(newValue, ptr));
Здесь видно, что изменяется внутри цикла другая(!) ячейка памяти, однако STREX уже выдает ошибку записи. Отсюда вывод:1. LDREX при чтении взводит некий флаг эксклюзива. STREX выполняет сохранение только в случае, если этот флаг возведен, и после сбрасывает флаг. Все! Нет ничего более, кроме этого.
Как правильно пользоваться:
1. Определить для себя ячейку(и) памяти, которая(ые) будет(ут) контролироваться эксклюзивным методом.
2. Если требуется произвести ЧМЗ — выполнять только через пару эксклюзивного доступа. Исключением может быть только самое высокоприоритетное прерывание, где можно указать только сброс флага:
(*ptr)++;
__CLREX();
3. Если требуется только запись — то можно ограничиться сбросом эксклюзивного флага:
(*ptr) = value;
__CLREX();
4. Если читаем — то ничего дополнительного писать не требуется.
Теперь вернемся к теме.
Последние изменения коснулись функций
System_Timer_Clear_Counter();
System_Timer_Delete();
, где было добавлено__CLREX();
в связи с последними замечаниями.Успехов в программировании!
- +3
- 16 апреля 2014, 15:23
- Mihail
- 1
Файлы в топике:
system_timer.zip
не поверите, я говорю довольно неторопливо. Да, текста здесь не очень много, не хотел утруждать изобилием слов. Текст написан в стиле datasheet. Каждое слово — выверенное. Ну по крайней мере старался так сделать))
Я немного про другое — хотя таких много
if ((TimerId>=0)&&(TimerId<SYSTEM_TIMER_COUNT))
if ((TimerId >= 0) && (TimerId < SYSTEM_TIMER_COUNT))
if ((TimerId>=0)&&(TimerId<SYSTEM_TIMER_COUNT))
if ((TimerId >= 0) && (TimerId < SYSTEM_TIMER_COUNT))
Ох, да это же гораздо приятнее, чем
if ( ( TimerId >= 0 ) && ( TimerId < SYSTEM_TIMER_COUNT ) )
Пробельные символы в С стоит использовать не для разделения токенов, а для разделения логически независимых участков. И ежу ясно, что код
if ((TimerId>=0)&&(TimerId<SYSTEM_TIMER_COUNT))
никто не будет переписывать по частям, а удалит и напишет заново => это один блок, и пробелы не нужны.
if ( ( TimerId >= 0 ) && ( TimerId < SYSTEM_TIMER_COUNT ) )
Пробельные символы в С стоит использовать не для разделения токенов, а для разделения логически независимых участков. И ежу ясно, что код
if ((TimerId>=0)&&(TimerId<SYSTEM_TIMER_COUNT))
никто не будет переписывать по частям, а удалит и напишет заново => это один блок, и пробелы не нужны.
ИМХО, вы не о том спорите. Ставить (в данном случае) пробелы или нет — это мелочи.
Лучше задуматься над тем — а зачем индекс счетчика в массиве сделан как знаковый тип. Логичнее сделать его uint8_t, тогда и проверка (TimerId >= 0) не нужна в принципе.
Или (если все оставлять как есть) зачем копипастить в начале каждой функции строку
Можно повторяющийся код вынести в макрос или инлайн функцию. Типа
Лучше задуматься над тем — а зачем индекс счетчика в массиве сделан как знаковый тип. Логичнее сделать его uint8_t, тогда и проверка (TimerId >= 0) не нужна в принципе.
Или (если все оставлять как есть) зачем копипастить в начале каждой функции строку
if ((TimerId < SYSTEM_TIMER_COUNT) && (TimerId >= 0))
Можно повторяющийся код вынести в макрос или инлайн функцию. Типа
if(IsTimerIndexValid(TimerId)) {}
int8_t — для того, чтобы был ответ -1 в случае неудачи добавления таймера. Можно сделать и функцию одну, вынести ее, только насколько это целесообразно? Не такая уж и сложная проверка… И места при компиляции много не занимает.
int8_t — для того, чтобы был ответ -1 в случае неудачи добавления таймера
С возвращаемым значением для System_Timer_Add() все понятно.
Я имел ввиду зачем делать индекс int8_t в функциях, где индекс передается как аргумент.
Код будет выглядеть так:
Ну, да, вы же должны обрабатывать ошибку а не подавлять. Если таймер не добавился – нужно это сразу обработать а не подавлять ошибку передавая дальше в функции не корректный индекс.
Но, даже если вы не добавите проверку, то ваш код будет работать как и раньше. Ели вы передадите в функцию типа
void System_Timer_Clear_Counter(uint8_t TimerId)
{
if (TimerId < SYSTEM_TIMER_COUNT) {
…
}
}
значение -1, то -1 будет приведено к 0xFF и не продет проверка if (TimerId < SYSTEM_TIMER_COUNT) (ведь вы ограничиваете значение SYSTEM_TIMER_COUNT до 127)
Но правильнее именно не ленится и проверять/обрабатывать ошибку добавления таймера.
Я, кстати, пришёл к тому, что нужны два типа периодически вызываемых функций:
1. Быстрые, короткие, которые вызываются прямо из прерывания (systick'a), они нужны редко и в особых случаях (например, fatfs таймер 1 кГц);
2. Более тяжёлые, которые выполняются из главного цикла — работа с файлами, расчётами и т.д. В кортексах это менее важно, а вот в арм тдми7 без них очень тяжело — работа с файлами из режима прерывания через mci невозможна.
Вторые отличаются от первых только тем, что в прерывании устанавливается флаг, а в главном цикле инициируется выполнение:
1. Быстрые, короткие, которые вызываются прямо из прерывания (systick'a), они нужны редко и в особых случаях (например, fatfs таймер 1 кГц);
2. Более тяжёлые, которые выполняются из главного цикла — работа с файлами, расчётами и т.д. В кортексах это менее важно, а вот в арм тдми7 без них очень тяжело — работа с файлами из режима прерывания через mci невозможна.
Вторые отличаются от первых только тем, что в прерывании устанавливается флаг, а в главном цикле инициируется выполнение:
void main(void)
{
timer_Init();
while(1)
{
...
timer_Main();
...
}
}
// Простенький пример:
void timer_Main(void)
{
int i;
for(i = 0; i < TIMER_HANDLERS; i++)
{
// Если сработало в сустике или где ещё - вызовем.
if(Handlers[i].Fired)
{
Handlers[i].Fired = false;
Handlers[i].Handler();
// Чтоб быстрее главный цикл крутился, обрабатываем не более одной сработавшей функции за раз
break;
}
}
}
- teplofizik
- 17 апреля 2014, 16:18
- ↓
Ага. Ещё один бонус — не надо парится (в отличие от вызываемых напрямую из сустика функциях) о порядке доступа к данным, об атомарности операций и прочей фигне. За счёт того, что функции вызываются из основного потока кода, они автоматически разнесены по времени и не могут перекрываться чисто физически с другим кодом того же потока.
Ладно прерывания — там уж ничего не сделаешь, но программных таймеров бывает много и незачем городить контроль доступа к переменным ещё и в них.
Ладно прерывания — там уж ничего не сделаешь, но программных таймеров бывает много и незачем городить контроль доступа к переменным ещё и в них.
- teplofizik
- 18 апреля 2014, 10:45
- ↑
- ↓
да, это нужно понимать. В многопоточных приложениях в таких диспетчерских функциях нужно заботиться о том, чтобы данные не перекрывались. Это отдельный геморрой, который необходимо будет делать… Все зависит от того, оправданно ли это…
Ну это простая версия, где данные не перекрываются заведомо, в том и плюс, за то и люблю. Как это делать из прерывания — не представляю.
- teplofizik
- 18 апреля 2014, 11:08
- ↑
- ↓
Ээ? Я не понял.
Я говорил про вызываемые функции, которые заранее неизвестно какие, и неизвестно с чем они работают (какими данными). Подход гарантирует, что код из главного цикла и из программного таймера не будет пересекаться по времени. И в них, во внешних функциях, атомарность доступа по отношению к параллельному коду главного цикла обеспечивается автоматически.
Я говорил про вызываемые функции, которые заранее неизвестно какие, и неизвестно с чем они работают (какими данными). Подход гарантирует, что код из главного цикла и из программного таймера не будет пересекаться по времени. И в них, во внешних функциях, атомарность доступа по отношению к параллельному коду главного цикла обеспечивается автоматически.
- teplofizik
- 18 апреля 2014, 13:32
- ↑
- ↓
void Switches_Read(void)
{
Switches_New.ADDR = ((GPIOA->IDR)&0xFF) | ((GPIOE->IDR)&0xFF00);
Switches_New.IND = (GPIOD->IDR)>>8;
Switches_New.CRC8 = (((GPIOC->IDR)>>6)&0x0F)|(((GPIOA->IDR)>>4)&0xF0);
if ((Switches_New.ADDR == Switches_Old.ADDR)&&
(Switches_New.IND == Switches_Old.IND)&&(Switches_New.CRC8 == Switches_Old.CRC8))
{
if (Switches_Counter>=5)
{
Switch_Interrupt = 1;
Switches = Switches_New;
Switch_Interrupt = 2;
Switches2 = Switches_New;
Switch_Interrupt = 0;
Switches_Status = Switches_Verify();
}
else
Switches_Counter++;
Switches_Status = Switches_Verify_OK;
Switches_Counter = 5;
}
else
Switches_Counter=0;
Switches_Old = Switches_New;
}
uint16_t GetADDR()
{
uint8_t interrupt;
uint16_t addr;
do
{
interrupt = __LDREXB(&Switch_Interrupt);
if (interrupt ==1)
{
addr = Switches2.ADDR;
__CLREX();
break;
}
else
{
addr = Switches.ADDR;
if (interrupt==2)
{
__CLREX();
break;
}
}
}while(__STREXB(interrupt,&Switch_Interrupt));
return addr;
}
Вот две функции. Верхняя выполняет чтение информации с портов в прерывании программного таймера. А нижняя — читает эти данные, при чем не важно, откуда эта функция вызвана — она всегда прочитает адекватные данные
Вот этого я и хотел бы избежать — код ото таких костылей становится непортируемым.
- teplofizik
- 18 апреля 2014, 14:59
- ↑
- ↓
Так я ж говорю не про прерывания, а про взаимодействие обычного кода и программных таймеров. С прерываниями никуда не денешься.
- teplofizik
- 18 апреля 2014, 16:40
- ↑
- ↓
и здесь еще одно нужно понимать, куда портировать… Если на кристал без приоритетов, то понятно, нафиг оно не нужно там… Но ради портируемости уравнивать возможности до самого хилого — ну скажем так… не совсем правильное решение. Данный код будет работать на всех ARM-ах. Для AVR конечно это абсолютно не подходит… Однако эффективность работы этого кода выше, так как скорость реакции выше… В общем, тут философский вопрос.
Есть много кода неспециализированного, где никакой суперскорости не надо =)
Зато его можно написать раз — и таскать из проекта в проект (на любой платформе).
Зато его можно написать раз — и таскать из проекта в проект (на любой платформе).
- teplofizik
- 18 апреля 2014, 16:42
- ↑
- ↓
Рассматривалась ли возможность использовать atomic-и из C++11?
- fat_lor_troll
- 23 апреля 2014, 14:16
- ↓
Главное, чтобы timer_mode — заполнялся последним. И здесь снова вспомним про заветный volatile
И здесь вспомним про барьеры компилятора(
asm volatile ("" ::: «memory»);
), т.к. использование volatile по стандарту не запрещает компилятору(оптимизатору) изменять порядок выполнения.Рекомендую почитать we.easyelectronics.ru/Soft/skolzkaya-dorozhka-dlya-poklonnikov-volatile.html
Особенно раздел
2. volatile помогает создать lockless код
Эту статью я читал.
т.к. использование volatile по стандарту не запрещает компилятору(оптимизатору) изменять порядок выполнения.Это справедливо лишь если одна из инструкций или обе — не volatile. Если бы в примере в статье буффер был указан volatile — пример бы стал рабочим. Обращаю внимание, что у меня вся структура объявлена как volatile, а это значит, что изменение порядка в моем случае — исключено.
Ну, может быть вы и правы… должно работать. я просто уже принял за правило вставлять барьер там где реально важно чтобы порядок соблюдался, потому сразу и подметил.
Барьер — это дополнительные инструкции процессора. Совсем не эффективно…
Хм, вроде как (в данном случае) барьер перед
System_Timers[i].timer_mode = mode
не должен повлиять на генерируемый код (не должно быть дополнительных инструкций).
Но, в принципе, я с Вами согласен, volatile всей структуры обеспечит правильную последовательность операций и корректную работу программы (отбросим экзотику вроде выполнения вашего кода в разных потоках на SMP).
Правильно, барьер дает оверхед там, где он нужен.
Опять же, при вдумчивом его использовании с части переменных удаётся снять volatile и тем самым даже уменьшить код/повысить скорость. Сейчас не хочется сильно вникать в код таймера и предлагать что-то конкретное на примерах, но я в своих программах этот факт проверял неоднократно.
Опять же, при вдумчивом его использовании с части переменных удаётся снять volatile и тем самым даже уменьшить код/повысить скорость. Сейчас не хочется сильно вникать в код таймера и предлагать что-то конкретное на примерах, но я в своих программах этот факт проверял неоднократно.
Забыл добавить что барьер это не инструкция а указание компилятору закончить все свои мутки с оптимизацией и исполнить следующую за барьером инструкцию с «чистой совестью» ) Это не какая-то отдельная магическая инструкция.
Если конкретнее, то оптимизатор не будет перемешивать операции до барьера и после. Таким образом гарантируется, что на момент исполнения кода после барьера, все операции с памятью которые были до барьера будут завершены.
Да, иногда это приводит к дополнительным инструкциям, но это плата за эти гарантии. Работа с volatile тоже практически вызывает раздувание кода и снижение производительности. Короче барьер это такой «местный volatile» по требованию)
Если конкретнее, то оптимизатор не будет перемешивать операции до барьера и после. Таким образом гарантируется, что на момент исполнения кода после барьера, все операции с памятью которые были до барьера будут завершены.
Да, иногда это приводит к дополнительным инструкциям, но это плата за эти гарантии. Работа с volatile тоже практически вызывает раздувание кода и снижение производительности. Короче барьер это такой «местный volatile» по требованию)
Оно и понятно… То есть если структура не volatile — то он может в регистрах все операции производить, а после барьера он сбрасывает все в память. Такие вещи хороши, если в этих переменных производятся какие-то там вычисления или сложные преобразования. В моем же случае — это всего лишь однократная запись в память. Использование барьера в данном случае — абсолютно ненужная вещь… Налицо избыток операций.
Забыл добавить что барьер это не инструкция а указание компилятору закончить все свои мутки
Ну, дык я о этом и говорил.
Хм, вроде как (в данном случае) барьер перед … не должен повлиять на генерируемый код
В данном случае идет последовательное заполнение полей volatile структуры, и оптимизации оно не подлежит. Посему — установка барьера компиляции на код повлиять не должна.
Более того, иногда барьер может быть более эффективен (ели взять частный случай — МК, где нет кеширования памяти и прочих радостей).
Вот, даже в данном случае (если упростить, и не вдаваться в подробности):
System_Timers[i].counter = 1;
System_Timers[i].compare = compare;
System_Timers[i].handler = handler;
System_Timers[i].timer_lock = 0;
System_Timers[i].timer_mode = mode;
Важно, чтобы операция
System_Timers[i].timer_mode = mode
была последней, т. к. она является «триггером» для обработки таймера. Последовательность заполнения остальных полей структуры не имеет значения. Мы можем их менять
System_Timers[i].handler = handler;
System_Timers[i].timer_lock = 0;
System_Timers[i].counter = 1;
System_Timers[i].compare = compare;
System_Timers[i].timer_mode = mode;
И ничего не изменится. Ели мы захотим избавься от volatile структуры, и поставит барьер перед триггером
System_Timers[i].counter = 1;
System_Timers[i].compare = compare;
System_Timers[i].handler = handler;
System_Timers[i].timer_lock = 0;
asm volatile ("" ::: «memory»);
System_Timers[i].timer_mode = mode;
То мы «развязываем руки» оптимизатору, он может (если посчитает нужным) менять порядок заполнения полей структуры (что для нас не важно), но будет гарантировать, что поле timer_mode будет заполнено последним (что для нас важно).
кейл не понимает инструкцию
Зато есть команда процессора __DSB();
Может ли она полнофункционально заменить вышеуказанную строчку?
asm volatile ("" ::: "memory");
Зато есть команда процессора __DSB();
Может ли она полнофункционально заменить вышеуказанную строчку?
Пробежался по коду по диагонали. Почему-то знак захотелось поправить
if(System_Timers[i].counter == System_Timers[i].compare)
Может и ошибаюсь
Не очень понимаю привязанность к «мелким» типам на 32-бит машинке. Экономия сомнительна. Компилятору окучивать части слова как-бы чуток сложнее, да и в выражениях char и short преобразуются к типу int, unsigned char и unsigned short — к unsigned int, что может приводить к «неожиданному поведению» некоторых выражений
if(System_Timers[i].counter == System_Timers[i].compare)
Может и ошибаюсь
Не очень понимаю привязанность к «мелким» типам на 32-бит машинке. Экономия сомнительна. Компилятору окучивать части слова как-бы чуток сложнее, да и в выражениях char и short преобразуются к типу int, unsigned char и unsigned short — к unsigned int, что может приводить к «неожиданному поведению» некоторых выражений
>= используется в том случае, если изменился compare в меньшую сторону и стал меньше текущего counter. В вашем случае таймер сработает лишь после переполнения счетчика, а это плохо.
Аппаратный Compare обычно взводит флаг(триггер) когда ==. И довольно часто в обработчике встречаются конструкции типа Compare = Counter+Period; В идеале это эквивалентно Compare += Period;
причем здесь аппаратный? да взводит при равных величинах. И это уже забота программиста, чтобы при изменении числа сравнения не допустить ухода счетчика из требуемого интервала.
Скажите чем такой вариант таймера лучше, от простого инкремента временной переменной в таймере?
Я вижу несколько преимуществ, но они не критичны.
1) Выделение в отдельный модуль.
2) Легко добавить новую временную переменную.
3) Просто запустить функции на циклический опрос.
Если ли ещё какие то преимущества, которые я не замечаю?
До недавнего времени использовал модуль в котором объявлен массив временных переменных, который инкриминируются в таймере по 1ms и с функциями взаимодействия ResetTimers(index) GetTimers(index).
Но в последнее время мне надоело что при подключения библиотек надо в модуле timer прописывать/корректировать переменные. По этому решил в каждом модуле заводить некоторую функцию SystemTimerModul, в ней прописывать инкремент временных переменных используемых данным модулем а саму функцию закидывать в таймер.
Я вижу несколько преимуществ, но они не критичны.
1) Выделение в отдельный модуль.
2) Легко добавить новую временную переменную.
3) Просто запустить функции на циклический опрос.
Если ли ещё какие то преимущества, которые я не замечаю?
До недавнего времени использовал модуль в котором объявлен массив временных переменных, который инкриминируются в таймере по 1ms и с функциями взаимодействия ResetTimers(index) GetTimers(index).
Но в последнее время мне надоело что при подключения библиотек надо в модуле timer прописывать/корректировать переменные. По этому решил в каждом модуле заводить некоторую функцию SystemTimerModul, в ней прописывать инкремент временных переменных используемых данным модулем а саму функцию закидывать в таймер.
ну конкретно у этого таймера еще есть возможность вызова процедур, хотя я уже данным таймером не пользуюсь. Устарел))
вообще, оказалось правильнее в прерывании иметь всего лишь один счетчик, а таймеры основываются на структурах, в которых хранится время старта этого таймера и интервал. По интервалу вычисляется время, когда сработает этот «таймер». таким образом нет нагрузки на само прерывание, и имеется возможность условно бесконечного числа структур, каждая отвечающая за свою функцию. Таймер больше не нужен? удалил данные структуры и все. Важное замечание только, что счетчик в прерывании, а так же поля структуры должны иметь одинаковую размерность, чтобы не было проблем с переполнением.
А обработка всех таймеров тогда где происходит? Периодически вызывается функция обработчик из главного цикла программы?
Реализация напоминает вызов таймера в S7-STL.
L "DB_005_Parametrs_All".Delay_debounce ; // задержка антидребезга
A "DB_001_DPIO".INP.DI_10; // проверка включения датчика
SD "Timer_10_Enabled_LIS03"; // таймер 10 с задержкой включения
AN "DB_001_DPIO".INP.DI_10; //
R "Timer_10_Enabled_LIS03"; // сброс при дребезге
A "Timer_10_Enabled_LIS03"; //
S "MRK_033_Level_LowLow" ; //
Комментарии (80)
RSS свернуть / развернуть