Программирование виртуальных таймеров Xmega

    Зачастую, при написании сложной программы для микропроцессоров, разработчики сталкиваются с проблемой нехватки таймеров, т.к. в большинстве процессоров и микроконтроллеров количество таймеров не более трех. В таких случаях программисты, как правило, используют простейшие счетчики, зачастую делающие программу более сложной для чтения и восприятия. Более удобным в данном случае является применение так называемых виртуальных таймеров (тоже использующие счетчики, но в более удобной для работы форме), суть которых заключается в создании ряда процедур по включению, выключению и обработке счетчиков затактированых по основному (системному) таймеру. Использование процедур виртуальных таймеров упрощает читаемость программы, добавление новых, изменение настроек существующих, а также включение, отключение и сброс виртуальных таймеров.
    При использовании виртуальных таймеров в процедуре обработки прерываний по основному таймеру желательно не использовать другие команды, кроме как установки флага по срабатыванию основного таймера, что в ряде случаев, позволяет избежать превышения временем обработки прерывания по системному таймеру периода основной тактовой частоты, и тем самым поддерживать постоянную тактовую частоту основного цикла работы программы.
    Виртуальный таймер обычно представляет собой завуалированный счетчик с установленным числом интервалов от основного (системного) таймера, который выставляет флаг срабатывания по достижению основным таймером этого числа. При этом счет числа интервалов может быть сброшен, остановлен или запущен с помощью специальных процедур виртуального таймера. Таким образом, можно на один основной таймер подключить огромное число виртуальных таймеров решающих практически любой спектр задач.
    Виртуальный таймер должен иметь следующие объявления и процедуры:
- объявление структуры виртуального таймера;
- объявление имен виртуальных таймеров;
- процедура запуска виртуального таймера;
- процедура остановки виртуального таймера;
- процедура сброса виртуального таймера;
- процедура обсчета виртуальных таймеров.
Программа с использованием виртуальных таймеров может иметь следующую структуру:
- объявление основных библиотек, функций и переменных;
- объявления структуры, имен и процедур виртуальных таймеров;
- инициализация основного (системного) таймера;
- настройка и запуск виртуальных таймеров;
- процедура обработки прерывания по основному таймеру с установкой флага срабатывания;
- основной цикл программы, включающий в себя: выполнение необходимых команд и сбросе виртуального таймера в начальное состояние по установке флага соответствующего виртуального таймера; выполнение необходимых команд, сброс флага основного таймера и запуск процедуры обработки виртуальных таймеров по установке флага основного таймера.
    Следует сказать, что виртуальные таймеры модно вкладывать друг в друга, организовывая виртуальные таймера от виртуального таймера (подвиртуальные таймера), что может быть полезно в ряде случаев.
    Ну и небольшой пример с двумя вариантами его реализации по использованию виртуальных таймеров написанный для микропроцессора XMega128A фирмы AVR.
    Пример.
    Задача: Написать программу с настройкой двух виртуальных таймеров с частотой 100 Гц и 1 Гц соответственно, при частоте основного таймера 1 кГц.
    Для решения задачи будем использовать внешний генератор с частотой 16МГц. Более подробную информацию о тактировании от внешнего источника можно найти в уроке «Задание частоты тактирования Xmega A от внешнего генератора». Основной таймер настроим на частоту 1 кГц (более подробную информацию о настройке системных таймеров и обработке прерываний можно найти в уроке «Использование таймера на Xmega A»). Виртуальным таймерам присвоим названия VTimer0 и VTimer1. Для наглядности выведем на ножку 0 порта А частоту основного таймера с переключением состояния ножки в каждом цикле (частота меандра на осциллографе будет 500Гц), на ножку 1 порта А – частоту виртуального таймера VTimer0 (частота меандра на осциллографе будет 50Гц), а на ножку 2 порта А – частоту виртуального таймера VTimer1 (частота меандра на осциллографе будет 0,5Гц).
    Первый вариант. Более классический, с точки зрения реализации виртуальных таймеров.
#define ENABLE_BIT_DEFINITIONS     // разрешение использования групповых битовых имен
/* Объявление используемых библиотек */
#include <ioxm128a1.h>
#include <ina90.h>
/* Объявление логических имен */
#define FALSE 0
#define TRUE 1
void InitTimers(void); // Объявление процедуры инициализации основного таймера
char MainTimerFlag=FALSE; // Объявление флага срабатывания основного таймера
typedef struct // Объявление структуры виртуального таймера, которая содержит:
{
char Flag; // флаг
char Enabled; // разрешение работы
unsigned short Interval; // интервал
unsigned short Count; // счетчик
} struc_timer;
/* объявления имен виртуальных таймеров (количество определяется автоматически)*/
enum list_timers
{
VTimer0, VTimer1
};
struc_timer Timers[VTimer1+1]; // инициализация виртуальных таймеров
/* Процедуры запуска и остановки счета виртуальных таймеров */
#define StartTimer(i) Timers[(i)].Enabled = TRUE;
#define StopTimer(i) Timers[(i)].Enabled = FALSE;
/* Объявление процедур сброса и обработки виртуальных таймеров */
void ResetTimer(char i);
void WorkingTimers(void);
void main(void)
{
__disable_interrupt(); // Отключение прерываний
/* Установка тактирования от внешнего генератора 16МГц */
OSC.XOSCCTRL = 0xCB; // выбор внешнего генератора с временем запуска 16 тыс. CLK и частотой 12-16 МГц
OSC.CTRL = 0x08; // разрешение работы внешнего генератора
while((OSC.STATUS & 0x08) == 0 ) ; // ожидание появления в регистре статуса бита включения синхронизации от внешнего генератора
OSC.PLLCTRL = 0xC1; // настройка блока PLL на синхронизацию от внешнего источника без умножения
OSC.CTRL = OSC.CTRL | 0x10; // разрешение работы блока PLL
while((OSC.STATUS & 0x10) == 0 ) ; // ожидание появления в регистре статуса бита включения блока PLL
CCP = 0xD8; // включение защиты от изменения регистров ввода-вывода на время изменения синхронизации
CLK.CTRL = 0x04; // настройка системной синхронизации от блока PLL
OSC.CTRL = OSC.CTRL & 0xFE; // отключение системной синхронизации от внутреннего RC-генератора частотой 2 МГц
PORTA.DIRSET = 0x07; // настройка ножек 0-2 порта А как выходов
PORTA.OUTSET = 0x07; // установка ножек 0-2 порта А в 1
InitTimers(); // инициализация основного таймера
PMIC.CTRL = 1; // приоритет прерываний уровня low
StartTimer(VTimer0); // разрешение работы виртуального таймера VTimer0
StartTimer(VTimer1); // разрешение работы виртуального таймера VTimer1
Timers[VTimer0].Interval=10; // установка виртуального таймера VTimer0 как 10 циклов основного (1000/10=100 Гц)
Timers[VTimer1].Interval=1000; // установка виртуального таймера VTimer1 как 1000 циклов основного (1000/1000=1 Гц)
__enable_interrupt(); // Разрешение прерываний
while(1) // основной бесконечный цикл
{
if (Timers[VTimer0].Flag) // если флаг срабатывания виртуального таймера установлен, то
{
PORTA.OUTTGL = 0x02; // переключение состояния ножки 1 порта А на противоположное
ResetTimer(VTimer0); // сброс виртуального таймера
}
if (Timers[VTimer1].Flag) // если флаг срабатывания виртуального таймера установлен, то
{
PORTA.OUTTGL = 0x04; // переключение состояния ножки 2 порта А на противоположное
ResetTimer(VTimer1); // сброс виртуального таймера
}
if (MainTimerFlag) // если флаг срабатывания основного таймера установлен, то
{
MainTimerFlag=FALSE; // очищение флага срабатывания основного таймера
PORTA.OUTTGL = 0x01; // переключение состояния ножки 0 порта А на противоположное
WorkingTimers(); // запуск обработки виртуальных таймеров
}
}
}
#pragma vector=TCC0_OVF_vect // обработка прерываний по переполнению таймера TCС0
__interrupt void irqTCC0_OVF_vect(void)
{
MainTimerFlag = TRUE; // установка флага срабатывания основного таймера
}
/* Процедура инициализации основного таймера */
void InitTimers(void)
{
/* Настройка таймера TCС0 */
TCC0.CTRLA=0x05; // N=64;
TCC0.PER=250-1; // частота таймера 1кГц при частоте процессора 16МГц
TCC0.INTCTRLA=1; // уровень прерываний таймера low
}
/* Процедура сброса виртуального таймера */
void ResetTimer(char i)
{
Timers[i].Count = 0; // сброс счетчика виртуального таймера
Timers[i].Flag = FALSE; // сброс флага виртуального таймера
}
/* Процедура обработки виртуальных таймеров */
void WorkingTimers(void)
{
char i;
for (i=0;i<VTimer1+1;i++) // цикл переключения между виртуальными таймерами
if (Timers[i].Enabled) // если виртуальный таймер включен, то
{
Timers[i].Count++; // инкрементация его счетчика
if (Timers[i].Count >= Timers[i].Interval) // если счетчик достиг установленного интервала, то
{
Timers[i].Count = 0; // сброс счетчика
Timers[i].Flag = TRUE; // установка флага срабатывания виртуального таймера
}
}
}

    Второй вариант. Более сложен для понимания, но содержит отдельные процедуры по срабатывания виртуальных таймеров – аналогов процедур прерывания по основному таймеру.
#define ENABLE_BIT_DEFINITIONS     // разрешение использования групповых битовых имен
/* Объявление используемых библиотек */
#include <ioxm128a1.h>
#include <ina90.h>
/* Объявление логических имен */
#define FALSE 0
#define TRUE 1
void InitTimers(void); // Объявление процедуры инициализации основного таймера
char MainTimerFlag=FALSE; // Объявление флага срабатывания основного таймера
/* Порядковые номера пвиртуальных таймеров VTimer0 */
#define pVTimer0 (0)
#define pVTimer1 (1)
/* Инициализайия вызываемых процедур обработки по срабатыванию виртуальных таймеров*/
void VTimer0(void);
void VTimer1(void);
// Определение битовых операций для работы с виртуальными таймерами
#define setBit(Address,bit) ( Address |= (1<<(bit)))
#define clrBit(Address,bit) ( Address &= ~(1<<(bit)))
#define tstBit(Address,bit) ( Address & (1<<(bit)))
#define invBit(Address,bit) ( Address ^= (1<<(bit)))
/* Состояния виртуальных таймеров */
#define EN (0) // Разрешение работы
#define IE (1) // Прерывание разрешено
#define OVF (2) // Флаг переполнения
#define VT_N (2) // Количество виртуальных таймеров
unsigned int Mass[VT_N]; // Массив таймеров
unsigned char Mass_sost[VT_N];// Массив словосостояния таймеров
/* Массив указателей на подпрограммы обработки действий по прерыванию виртуального таймера */
void (*Mass_ACT[VT_N])(void);
/* Подпрограмма записи новой уставки выбранного виртуального таймера
N - номер виртуального таймера, INT - величина уставки виртуального таймера */
void Mass_setINT(unsigned char N, unsigned int INT) {Mass[N] = INT;}
/* Подпрограмма включения/выключения работы выбранного виртуального таймера
N - номер виртуального таймера EN - разрешения/запрета работы виртуального таймера
1 - разрешение 0 - запрет */
void Mass_setWE(unsigned char N, unsigned char sWE) {if (sWE) setBit(Mass_sost[N],EN); else clrBit(Mass_sost[N],EN);}
/* Подпрограмма установки функции обработки прерывания выбранного виртуального таймера
N - номер виртуального таймера pFunc - ссылка на имя функции */
void Mass_setF(unsigned char N, void (*pFunc)(void)) {Mass_ACT[N] = pFunc;}
void Init_VTimers(void); // инициализация процедуры настройки виртальных таймеров
void Working_VTimers(void); // инициализация процедуры обработки виртальных таймеров
void main(void)
{
__disable_interrupt(); // Отключение прерываний
/* Установка тактирования от внешнего генератора 16МГц */
OSC.XOSCCTRL = 0xCB; // выбор внешнего генератора с временем запуска 16 тыс. CLK и частотой 12-16 МГц
OSC.CTRL = 0x08; // разрешение работы внешнего генератора
while((OSC.STATUS & 0x08) == 0 ) ; // ожидание появления в регистре статуса бита включения синхронизации от внешнего генератора
OSC.PLLCTRL = 0xC1; // настройка блока PLL на синхронизацию от внешнего источника без умножения
OSC.CTRL = OSC.CTRL | 0x10; // разрешение работы блока PLL
while((OSC.STATUS & 0x10) == 0 ) ; // ожидание появления в регистре статуса бита включения блока PLL
CCP = 0xD8; // включение защиты от изменения регистров ввода-вывода на время изменения синхронизации
CLK.CTRL = 0x04; // настройка системной синхронизации от блока PLL
OSC.CTRL = OSC.CTRL & 0xFE; // отключение системной синхронизации от внутреннего RC-генератора частотой 2 МГц
PORTA.DIRSET = 0x07; // настройка ножек 0-2 порта А как выходов
PORTA.OUTSET = 0x07; // установка ножек 0-2 порта А в 1
InitTimers(); // инициализация основного таймера
PMIC.CTRL = 1; // приоритет прерываний уровня low
Init_VTimers(); // процедуры настройки виртуальных таймеров
__enable_interrupt(); // Разрешение прерываний
while(1) // основной бесконечный цикл
{
if (MainTimerFlag) // если флаг срабатывания основного таймера установлен, то
{
MainTimerFlag=FALSE; // очищение флага срабатывания основного таймера
PORTA.OUTTGL = 0x01; // переключение состояния ножки 0 порта А на противоположное
Working_VTimers(); // запуск обработки виртуальных таймеров
}
}
}
#pragma vector=TCC0_OVF_vect // обработка прерываний по переполнению таймера TCС0
__interrupt void irqTCC0_OVF_vect(void)
{
MainTimerFlag = TRUE; // установка флага срабатывания основного таймера
}
/* Процедура инициализации основного таймера */
void InitTimers(void)
{
/* Настройка таймера TCС0 */
TCC0.CTRLA=0x05; // N=64;
TCC0.PER=250-1; // частота таймера 1кГц при частоте процессора 16МГц
TCC0.INTCTRLA=1; // уровень прерываний таймера low
}
/* Обработка по срабатыванию виртуального таймера 1 */
void VTimer0(void)
{
PORTA.OUTTGL = 0x02; // переключение состояния ножки 1 порта А на противоположное
Mass_setINT(pVTimer0,10); // установка интервала виртуального таймера VTimer0 (1000Гц/10Гц=100Гц)
}
/* Обработка по срабатыванию виртуального таймера 2 */
void VTimer1(void)
{
PORTA.OUTTGL = 0x04; // переключение состояния ножки 2 порта А на противоположное
Mass_setINT(pVTimer1,1000); // установка интервала виртуального таймера VTimer1 (1000Гц/1000Гц=1Гц)
}
void Init_VTimers(void)
{
char i = 0;
for (i=0; i<VT_N; i++) // установка начальных нулевых значений
{
Mass_ACT[i] = 0; // очищения массива указателей
Mass[i] = 0; // очищение массива счетчиков
Mass_sost[i] = 0x02; // очищение массива словосостояний
}
Mass_setF(pVTimer0, VTimer0); // установка указателя на процедуру обработки прерываний по VTimer0
Mass_setF(pVTimer1, VTimer1); // установка указателя на процедуру обработки прерываний по VTimer1
Mass_setINT(pVTimer0,10); // установка интервала виртуального таймера VTimer0 (1000Гц/10Гц=100Гц)
Mass_setWE(pVTimer0,1); // запуск виртуального таймера VTimer0
Mass_setINT(pVTimer1,1000); // установка интервала виртуального таймера VTimer1 (1000Гц/1000Гц=1Гц)
Mass_setWE(pVTimer1,1); // запуск виртуального таймера VTimer1
}
/* Процедура обработки виртуальных таймеров VTimer0 */
void Working_VTimers(void)
{
unsigned char i = 0;
register unsigned char sost;
for (i=0; i<VT_N; i++) // Переключение между виртуальными таймерами
{
sost = Mass_sost[i];
if (tstBit(sost,EN)) // Работа i-ого виртуального таймера разрешена?
{ // Да
if (Mass[i] > 1) // i-ый виртуальный таймер очищен?
{ // Нет
Mass[i]--; // Уменьшаем содержимое i-ого виртуального таймера
clrBit(sost,OVF); // Очищаем прерывание i-ого виртуального таймера
}
else
{ // Да
if (tstBit(sost,IE)) // Прерывание i-ого виртуального таймера разрешена?
setBit(sost,OVF); // Да, тогда устанавливаем прерывание i-ого виртуального таймера
else
clrBit(sost,OVF); // Нет, тогда сбрасываем прерывание i-ого виртуального таймера
}
}
else
clrBit(sost,OVF); // Нет, тогда сбрасываем прерывание i-ого виртуального таймера
Mass_sost[i] = sost;
}
for (i=0; i<VT_N; i++) // Обработка действий на прерывание по виртуальному таймеру
{
sost = Mass_sost[i];
if (tstBit(sost,OVF)) // виртуальный таймер переполнен?
{ // Да
if ((Mass_ACT[i]) != 0) Mass_ACT[i](); // Если виртуальный таймер используется, то отработка его прерывания
sost = Mass_sost[i];
clrBit(sost,OVF); // Очищение переполнения
}
Mass_sost[i] = sost;
}
}
    Blogger Comment
    Facebook Comment

0 коммент.:

Отправить комментарий