Timer16 Использование прерываний
Особенность установки флагов прерываний
Флаг прерывания по сравнению выставляется, если значение счета таймера совпадает со значением сравнения. Из-за этого возможна ситуация, когда после сброса флага прерывания по сравнению внутри обработчика флаг снова выставляется на следующем такте, если счетчик таймера не успел увеличиться. В таком случае, после выхода из обработчика прерывания происходит повторное срабатывание.
Пример ниже демонстрирует эту ситуацию. В этом примере Timer16 тактируется от встроенного низкочастотного источника тактирования.
#include "mik32_hal.h"
#include "mik32_hal_timer16.h"
#include "mik32_hal_irq.h"
#include "uart_lib.h"
#include "xprintf.h"
Timer16_HandleTypeDef htimer16_1;
void SystemClock_Config(void);
static void Timer16_1_Init(void);
void GPIO_Init();
int main()
{
HAL_Init();
SystemClock_Config();
GPIO_Init();
Timer16_1_Init();
HAL_Timer16_Counter_Start(&htimer16_1, 0x7);
HAL_Timer16_SetCMP(&htimer16_1, 0x7 / 2);
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)
{
}
}
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_16;
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_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
GPIO_InitStruct.Pull = HAL_GPIO_PULL_NONE;
HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_1;
HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);
}
void trap_handler()
{
if (TIMER16_1->ISR & TIMER16_FLAG_CMPM)
{
GPIO_0->SET = 1;
TIMER16_1->ICR = TIMER16_FLAG_CMPM;
GPIO_0->CLEAR = 1;
}
if (TIMER16_1->ISR & TIMER16_FLAG_ARRM)
{
GPIO_0->SET = 2;
TIMER16_1->ICR = TIMER16_FLAG_ARRM;
GPIO_0->CLEAR = 2;
}
}
На временной диаграмме видны многократные срабатывания прерывания по сравнению (CMP). Однократные прерывания по перезагрузке (ARR) связаны с реализацией Timer16.
Один из возможных вариантов обхода: временное отключение прерывания по сравнению на время до смены значения счетчика в другой части программы, например, в основном цикле, как в примере ниже:
#include "mik32_hal.h"
#include "mik32_hal_timer16.h"
#include "mik32_hal_irq.h"
#include "uart_lib.h"
#include "xprintf.h"
Timer16_HandleTypeDef htimer16_1;
void SystemClock_Config(void);
static void Timer16_1_Init(void);
void GPIO_Init();
int main()
{
HAL_Init();
SystemClock_Config();
GPIO_Init();
Timer16_1_Init();
HAL_Timer16_Counter_Start(&htimer16_1, 0x7);
HAL_Timer16_SetCMP(&htimer16_1, 0x7 / 2);
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)
{
if ((TIMER16_1->CNT != TIMER16_1->CMP) && ((TIMER16_1->IER & TIMER16_FLAG_CMPM) == 0))
{
TIMER16_1->IER |= TIMER16_FLAG_CMPM;
TIMER16_1->ICR = TIMER16_FLAG_CMPM;
}
if ((TIMER16_1->CNT != TIMER16_1->ARR) && ((TIMER16_1->IER & TIMER16_FLAG_ARRM) == 0))
{
TIMER16_1->IER |= TIMER16_FLAG_ARRM;
TIMER16_1->ICR = TIMER16_FLAG_ARRM;
}
}
}
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_16;
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_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
GPIO_InitStruct.Pull = HAL_GPIO_PULL_NONE;
HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_1;
HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);
}
void trap_handler()
{
if (TIMER16_1->ISR & TIMER16_FLAG_CMPM)
{
GPIO_0->SET = 1;
TIMER16_1->IER &= ~TIMER16_FLAG_CMPM;
TIMER16_1->ICR = TIMER16_FLAG_CMPM;
GPIO_0->CLEAR = 1;
}
if (TIMER16_1->ISR & TIMER16_FLAG_ARRM)
{
GPIO_0->SET = 2;
TIMER16_1->IER &= ~TIMER16_FLAG_ARRM;
TIMER16_1->ICR = TIMER16_FLAG_ARRM;
GPIO_0->CLEAR = 2;
}
}
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR):