Timer16 Использование прерываний: различия между версиями

Материал из MIK32 микроконтроллер
Нет описания правки
Нет описания правки
 
(не показано 7 промежуточных версий этого же участника)
Строка 2: Строка 2:
Флаг прерывания по сравнению выставляется, если значение счета таймера совпадает со значением сравнения. Из-за этого возможна ситуация, когда после сброса флага прерывания по сравнению внутри обработчика флаг снова выставляется на следующем такте, если счетчик таймера не успел увеличиться. В таком случае, после выхода из обработчика прерывания происходит повторное срабатывание.
Флаг прерывания по сравнению выставляется, если значение счета таймера совпадает со значением сравнения. Из-за этого возможна ситуация, когда после сброса флага прерывания по сравнению внутри обработчика флаг снова выставляется на следующем такте, если счетчик таймера не успел увеличиться. В таком случае, после выхода из обработчика прерывания происходит повторное срабатывание.


Пример ниже демонстрирует эту ситуацию. Для воспроизведения потребуется использовать источник счета с частотой значительно ниже частоты тактирования системы, например, один из низкоскоростных таймеров.
Пример ниже демонстрирует эту ситуацию. В этом примере Timer16 тактируется от встроенного низкочастотного источника тактирования.


{{#spoiler:show=Развернуть код|hide=Свернуть код|
{{#spoiler:show=Развернуть код|hide=Свернуть код|
Строка 129: Строка 129:
На временной диаграмме видны многократные срабатывания прерывания по сравнению (CMP). Однократные прерывания по перезагрузке (ARR) связаны с реализацией Timer16.
На временной диаграмме видны многократные срабатывания прерывания по сравнению (CMP). Однократные прерывания по перезагрузке (ARR) связаны с реализацией Timer16.


=== Варианты обхода ===
Повторная установка флага потребует подбора обходного пути в зависимости от задачи. Основная идея - отключение прерывания на время счета, равного значению сравнения. Рассмотрим некоторые из возможных обходных путей работы с прерываниями по сравнению.
==== Отключение прерывания по сравнению и повторное включение в основном цикле ====
Один из возможных вариантов обхода: временное отключение прерывания по сравнению на время до смены значения счетчика в другой части программы, например, в основном цикле, как в примере ниже:
Один из возможных вариантов обхода: временное отключение прерывания по сравнению на время до смены значения счетчика в другой части программы, например, в основном цикле, как в примере ниже:
{{#spoiler:show=Развернуть код|hide=Свернуть код|
{{#spoiler:show=Развернуть код|hide=Свернуть код|
Строка 264: Строка 268:
</syntaxhighlight>
</syntaxhighlight>
}}
}}
[[Файл:Timer16 Однократные срабатывания прерываний.png|мини|375x375px]]
[[Файл:Timer16 Однократные срабатывания прерываний.png|мини|375x375px]]На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR):
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR):
 
Такой вариант можно использовать, если программа работает в режиме pooling'а, регулярно вызывая основной цикл.
 
==== Отключение прерывания по сравнению и повторное включение в прерывании по перезагрузке ====
Возможный вариант обхода: отключение прерывания по сравнению и повторное включение в прерывании по переполнению. Пример представлен ниже:
{{#spoiler:show=Развернуть код|hide=Свернуть код|
<syntaxhighlight lang="c">
#include "mik32_hal.h"
#include "mik32_hal_timer16.h"
#include "mik32_hal_irq.h"
 
#include "uart_lib.h"
#include "xprintf.h"
 
/*
* В примере демонстрируется обход повторного входа в прерывание по CMP.
* Обход заключается в отключении прерывания CMP при внутри обработчика флага CMP
* и его включении внутри обработчика прерывания ARR.
* */
 
extern unsigned long __TEXT_START__;
 
#define ARR_VALUE 6
#define CMP_VALUE (ARR_VALUE / 2)
 
Timer16_HandleTypeDef htimer16_1;
 
void SystemClock_Config(void);
 
static void Timer16_1_Init(void);
 
void GPIO_Init();
 
int main()
{
    write_csr(mtvec, &__TEXT_START__);
 
    HAL_Init();
 
    SystemClock_Config();
 
    UART_Init(UART_0, 3333, UART_CONTROL1_TE_M | UART_CONTROL1_M_8BIT_M, 0, 0);
    xprintf("\nStart\n");
 
    GPIO_Init();
 
    Timer16_1_Init();
 
    HAL_Timer16_Counter_Start(&htimer16_1, ARR_VALUE);
    HAL_Timer16_SetCMP(&htimer16_1, CMP_VALUE);
    HAL_Timer16_SetInterruptMask(&htimer16_1, TIMER16_FLAG_CMPM | TIMER16_FLAG_ARRM);
 
    /* Включать прерывания Timer16 рекомендуется после его инициализации */
    HAL_EPIC_MaskLevelSet(HAL_EPIC_TIMER16_1_MASK);
    HAL_IRQ_EnableInterrupts();
 
    while (1)
    {
        GPIO_0->OUTPUT = (GPIO_0->OUTPUT & ~(0b111 << 7)) | (TIMER16_1->CNT << 7); // Вывод текущего счета таймера
    }
}
 
void SystemClock_Config(void)
{
    PCC_InitTypeDef PCC_OscInit = {0};
 
    PCC_OscInit.OscillatorEnable = PCC_OSCILLATORTYPE_ALL;
    PCC_OscInit.FreqMon.OscillatorSystem = PCC_OSCILLATORTYPE_OSC32M;
    PCC_OscInit.FreqMon.ForceOscSys = PCC_FORCE_OSC_SYS_UNFIXED;
    PCC_OscInit.FreqMon.Force32KClk = PCC_FREQ_MONITOR_SOURCE_OSC32K;
    PCC_OscInit.AHBDivider = 0;
    PCC_OscInit.APBMDivider = 0;
    PCC_OscInit.APBPDivider = 0;
    PCC_OscInit.HSI32MCalibrationValue = 128;
    PCC_OscInit.LSI32KCalibrationValue = 8;
    PCC_OscInit.RTCClockSelection = PCC_RTC_CLOCK_SOURCE_AUTO;
    PCC_OscInit.RTCClockCPUSelection = PCC_CPU_RTC_CLOCK_SOURCE_OSC32K;
    HAL_PCC_Config(&PCC_OscInit);
}
 
static void Timer16_1_Init(void)
{
    htimer16_1.Instance = TIMER16_1;
 
    /* Настройка тактирования */
    htimer16_1.Clock.Source = TIMER16_SOURCE_INTERNAL_OSC32K;
    htimer16_1.CountMode = TIMER16_COUNTMODE_INTERNAL; /* При тактировании от Input1 не имеет значения */
    htimer16_1.Clock.Prescaler = TIMER16_PRESCALER_128;
    htimer16_1.ActiveEdge = TIMER16_ACTIVEEDGE_RISING; /* Выбирается при тактировании от Input1 */
 
    /* Настройка режима обновления регистра ARR и CMP */
    htimer16_1.Preload = TIMER16_PRELOAD_AFTERWRITE;
 
    /* Настройка триггера */
    htimer16_1.Trigger.Source = TIMER16_TRIGGER_TIM1_GPIO1_9;
    htimer16_1.Trigger.ActiveEdge = TIMER16_TRIGGER_ACTIVEEDGE_SOFTWARE; /* При использовании триггера значение должно быть отлично от software */
    htimer16_1.Trigger.TimeOut = TIMER16_TIMEOUT_DISABLE;                /* Разрешить повторное срабатывание триггера */
 
    /* Настройки фильтра */
    htimer16_1.Filter.ExternalClock = TIMER16_FILTER_NONE;
    htimer16_1.Filter.Trigger = TIMER16_FILTER_NONE;
 
    /* Настройка режима энкодера */
    htimer16_1.EncoderMode = TIMER16_ENCODER_DISABLE;
 
    /* Выходной сигнал */
    htimer16_1.Waveform.Enable = TIMER16_WAVEFORM_GENERATION_DISABLE;
    htimer16_1.Waveform.Polarity = TIMER16_WAVEFORM_POLARITY_NONINVERTED;
 
    HAL_Timer16_Init(&htimer16_1);
}
 
void GPIO_Init()
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_PCC_GPIO_0_CLK_ENABLE();
    __HAL_PCC_GPIO_1_CLK_ENABLE();
    __HAL_PCC_GPIO_2_CLK_ENABLE();
 
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9;
    GPIO_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
    GPIO_InitStruct.Pull = HAL_GPIO_PULL_NONE;
    HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);
}
 
void trap_handler()
{
    GPIO_0->OUTPUT = (GPIO_0->OUTPUT & ~(0b111 << 7)) | (TIMER16_1->CNT << 7); // Вывод текущего счета таймера
 
    if ((TIMER16_1->ISR & TIMER16_FLAG_CMPM) && (TIMER16_1->IER & TIMER16_FLAG_CMPM))
    {
        GPIO_0->SET = 1 << 0;
        TIMER16_1->IER &= ~TIMER16_FLAG_CMPM; // Запретить прерывание по CMP.
 
        /* Обработчик CMP */
 
        TIMER16_1->ICR = TIMER16_FLAG_CMPM;
        GPIO_0->CLEAR = 1 << 0;
    }
 
 
    if ((TIMER16_1->ISR & TIMER16_FLAG_ARRM) && (TIMER16_1->IER & TIMER16_FLAG_ARRM))
    {
        GPIO_0->SET = 1 << 2;
        TIMER16_1->ICR = TIMER16_FLAG_CMPM;
        TIMER16_1->IER |= TIMER16_FLAG_CMPM;  // Разрешить прерывание по CMP.
 
        /* Обработчик ARR */
 
        TIMER16_1->ICR = TIMER16_FLAG_ARRM;
        GPIO_0->CLEAR = 1 << 2;
    }
}
</syntaxhighlight>
}}
[[Файл:Timer16 Однократные срабатывания прерываний 2.png|мини|374x374пкс]]
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно:
 
==== Пример с использованием двух таймеров ====
Подобный подход можно применить к прерываниям от двух таймеров. В примере ниже запущены два таймера. В их обработчиках меняется состояние выводов GPIO0.3 и GPIO1.3, это происходит "околосинхронно".
 
Программа работает неправильно при выполнении двух условий одновременно:
 
* Закомментирована строка #define TIMER_CMP_IRQ_WORKAROUND;
* Работа из внешней флеш в режиме Quad SPI или XIP с кэшированием.
 
Это выражается в несинхронном переключении выводов.
 
Фрагменты, активные при заданном макросе TIMER_CMP_IRQ_WORKAROUND, отвечают за обход повторной установки флага CMP.
 
Код примера представлен ниже:{{#spoiler:show=Развернуть код|hide=Свернуть код|
<syntaxhighlight lang="c">
#include "mik32_hal.h"
#include "mik32_hal_timer16.h"
#include "mik32_hal_irq.h"
 
#include "uart_lib.h"
#include "xprintf.h"
 
/*
* Пример использования двух Timer16 в прерывании по CMP.
* В примере демонстрируется обход повторного входа в прерывание по CMP.
* Обход заключается в отключении прерывания CMP при внутри обработчика флага CMP
* и его включении внутри обработчика прерывания ARR.
* */
 
#define TIMER_CMP_IRQ_WORKAROUND
 
extern unsigned long __TEXT_START__;
 
#define ARR_VALUE 50000
#define CMP_VALUE 25000
 
Timer16_HandleTypeDef htimer16_0;
Timer16_HandleTypeDef htimer16_1;
 
void SystemClock_Config(void);
 
static void Timer16_Init(
Timer16_HandleTypeDef *htimer16,
TIMER16_TypeDef *timer_ptr);
 
void GPIO_Init();
 
void main()
{
// interrupt vector init
write_csr(mtvec, &__TEXT_START__);
 
HAL_Init();
 
SystemClock_Config();
 
UART_Init(UART_0, 3333, UART_CONTROL1_TE_M | UART_CONTROL1_M_8BIT_M, 0, 0);
xprintf("\nStart\n");
 
GPIO_Init();
 
Timer16_Init(&htimer16_0, TIMER16_0);
Timer16_Init(&htimer16_1, TIMER16_1);
 
HAL_Timer16_Counter_Start(&htimer16_0, ARR_VALUE);
HAL_Timer16_Counter_Start(&htimer16_1, ARR_VALUE);
HAL_Timer16_SetCMP(&htimer16_0, CMP_VALUE);
HAL_Timer16_SetCMP(&htimer16_1, CMP_VALUE);
HAL_Timer16_SetInterruptMask(&htimer16_0, TIMER16_FLAG_CMPM | TIMER16_FLAG_ARRM);
HAL_Timer16_SetInterruptMask(&htimer16_1, TIMER16_FLAG_CMPM | TIMER16_FLAG_ARRM);
 
/* Включать прерывания Timer16 рекомендуется после его инициализации */
HAL_EPIC_MaskLevelSet(HAL_EPIC_TIMER16_0_MASK | HAL_EPIC_TIMER16_1_MASK);
HAL_IRQ_EnableInterrupts();
 
for (;;)
{
}
}
 
void SystemClock_Config(void)
{
PCC_InitTypeDef PCC_OscInit = {0};
 
PCC_OscInit.OscillatorEnable = PCC_OSCILLATORTYPE_ALL;
PCC_OscInit.FreqMon.OscillatorSystem = PCC_OSCILLATORTYPE_OSC32M;
PCC_OscInit.FreqMon.ForceOscSys = PCC_FORCE_OSC_SYS_UNFIXED;
PCC_OscInit.FreqMon.Force32KClk = PCC_FREQ_MONITOR_SOURCE_OSC32K;
PCC_OscInit.AHBDivider = 0;
PCC_OscInit.APBMDivider = 0;
PCC_OscInit.APBPDivider = 0;
PCC_OscInit.HSI32MCalibrationValue = 128;
PCC_OscInit.LSI32KCalibrationValue = 8;
PCC_OscInit.RTCClockSelection = PCC_RTC_CLOCK_SOURCE_AUTO;
PCC_OscInit.RTCClockCPUSelection = PCC_CPU_RTC_CLOCK_SOURCE_OSC32K;
HAL_PCC_Config(&PCC_OscInit);
}
 
static void Timer16_Init(
Timer16_HandleTypeDef *htimer16,
TIMER16_TypeDef *timer_ptr)
{
htimer16->Instance = timer_ptr;
 
/* Настройка тактирования */
htimer16->Clock.Source = TIMER16_SOURCE_INTERNAL_OSC32M;
htimer16->CountMode = TIMER16_COUNTMODE_INTERNAL; /* При тактировании от Input1 не имеет значения */
htimer16->Clock.Prescaler = TIMER16_PRESCALER_128;
htimer16->ActiveEdge = TIMER16_ACTIVEEDGE_RISING; /* Выбирается при тактировании от Input1 */
 
/* Настройка режима обновления регистра ARR и CMP */
htimer16->Preload = TIMER16_PRELOAD_AFTERWRITE;
 
/* Настройка триггера */
htimer16->Trigger.Source = TIMER16_TRIGGER_TIM1_GPIO1_9;
htimer16->Trigger.ActiveEdge = TIMER16_TRIGGER_ACTIVEEDGE_SOFTWARE; /* При использовании триггера значение должно быть отлично от software */
htimer16->Trigger.TimeOut = TIMER16_TIMEOUT_DISABLE; /* Разрешить повторное срабатывание триггера */
 
/* Настройки фильтра */
htimer16->Filter.ExternalClock = TIMER16_FILTER_NONE;
htimer16->Filter.Trigger = TIMER16_FILTER_NONE;
 
/* Настройка режима энкодера */
htimer16->EncoderMode = TIMER16_ENCODER_DISABLE;
 
/* Выходной сигнал */
htimer16->Waveform.Enable = TIMER16_WAVEFORM_GENERATION_DISABLE;
htimer16->Waveform.Polarity = TIMER16_WAVEFORM_POLARITY_NONINVERTED;
 
HAL_Timer16_Init(htimer16);
}
 
void GPIO_Init()
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_PCC_GPIO_0_CLK_ENABLE();
__HAL_PCC_GPIO_1_CLK_ENABLE();
__HAL_PCC_GPIO_2_CLK_ENABLE();
 
GPIO_InitStruct.Pin = GPIO_PIN_3;
GPIO_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
GPIO_InitStruct.Pull = HAL_GPIO_PULL_NONE;
HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);
HAL_GPIO_Init(GPIO_1, &GPIO_InitStruct);
}
 
void trap_handler()
{
if ((TIMER16_0->ISR & TIMER16_FLAG_CMPM) && (TIMER16_0->IER & TIMER16_FLAG_CMPM))
{
GPIO_0->OUTPUT ^= 1 << 3;
 
#ifdef TIMER_CMP_IRQ_WORKAROUND
TIMER16_0->IER &= ~TIMER16_FLAG_CMPM; // Запретить прерывание по CMP.
#endif
 
/* Обработчик CMP */
 
TIMER16_0->ICR = TIMER16_FLAG_CMPM;
}
 
#ifdef TIMER_CMP_IRQ_WORKAROUND
if ((TIMER16_0->ISR & TIMER16_FLAG_ARRM) && (TIMER16_0->IER & TIMER16_FLAG_ARRM))
{
TIMER16_0->ICR = TIMER16_FLAG_CMPM;
TIMER16_0->IER |= TIMER16_FLAG_CMPM; // Разрешить прерывание по CMP.
 
/* Обработчик ARR */
 
TIMER16_0->ICR = TIMER16_FLAG_ARRM;
}
#endif
 
if ((TIMER16_1->ISR & TIMER16_FLAG_CMPM) && (TIMER16_1->IER & TIMER16_FLAG_CMPM))
{
GPIO_1->OUTPUT ^= 1 << 3;
 
#ifdef TIMER_CMP_IRQ_WORKAROUND
TIMER16_1->IER &= ~TIMER16_FLAG_CMPM; // Запретить прерывание по CMP.
#endif
 
/* Обработчик CMP */
 
TIMER16_1->ICR = TIMER16_FLAG_CMPM;
}
 
#ifdef TIMER_CMP_IRQ_WORKAROUND
if ((TIMER16_1->ISR & TIMER16_FLAG_ARRM) && (TIMER16_1->IER & TIMER16_FLAG_ARRM))
{
TIMER16_1->ICR = TIMER16_FLAG_CMPM;
TIMER16_1->IER |= TIMER16_FLAG_CMPM; // Разрешить прерывание по CMP.
 
/* Обработчик ARR */
 
TIMER16_1->ICR = TIMER16_FLAG_ARRM;
}
#endif
}
</syntaxhighlight>
}}

Текущая версия от 09:05, 7 апреля 2025

Особенность установки флагов прерываний

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

Пример ниже демонстрирует эту ситуацию. В этом примере Timer16 тактируется от встроенного низкочастотного источника тактирования.

Timer16 Многократные срабатывания прерываний.png

На временной диаграмме видны многократные срабатывания прерывания по сравнению (CMP). Однократные прерывания по перезагрузке (ARR) связаны с реализацией Timer16.

Варианты обхода

Повторная установка флага потребует подбора обходного пути в зависимости от задачи. Основная идея - отключение прерывания на время счета, равного значению сравнения. Рассмотрим некоторые из возможных обходных путей работы с прерываниями по сравнению.

Отключение прерывания по сравнению и повторное включение в основном цикле

Один из возможных вариантов обхода: временное отключение прерывания по сравнению на время до смены значения счетчика в другой части программы, например, в основном цикле, как в примере ниже:

Timer16 Однократные срабатывания прерываний.png

На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR):

Такой вариант можно использовать, если программа работает в режиме pooling'а, регулярно вызывая основной цикл.

Отключение прерывания по сравнению и повторное включение в прерывании по перезагрузке

Возможный вариант обхода: отключение прерывания по сравнению и повторное включение в прерывании по переполнению. Пример представлен ниже:

Timer16 Однократные срабатывания прерываний 2.png

На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно:

Пример с использованием двух таймеров

Подобный подход можно применить к прерываниям от двух таймеров. В примере ниже запущены два таймера. В их обработчиках меняется состояние выводов GPIO0.3 и GPIO1.3, это происходит "околосинхронно".

Программа работает неправильно при выполнении двух условий одновременно:

  • Закомментирована строка #define TIMER_CMP_IRQ_WORKAROUND;
  • Работа из внешней флеш в режиме Quad SPI или XIP с кэшированием.

Это выражается в несинхронном переключении выводов.

Фрагменты, активные при заданном макросе TIMER_CMP_IRQ_WORKAROUND, отвечают за обход повторной установки флага CMP.

Код примера представлен ниже: