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

Материал из MIK32 микроконтроллер
Нет описания правки
Нет описания правки
Строка 426: Строка 426:
[[Файл:Timer16 Однократные срабатывания прерываний 2.png|мини|374x374пкс]]
[[Файл:Timer16 Однократные срабатывания прерываний 2.png|мини|374x374пкс]]
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно:
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно:
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно:
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно:
{{#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>
}}

Версия от 12:47, 5 марта 2025

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

Развернуть код