Timer16 Использование прерываний: различия между версиями
Sh-sergey (обсуждение | вклад) Нет описания правки |
Sh-sergey (обсуждение | вклад) Нет описания правки |
||
Строка 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 тактируется от встроенного низкочастотного источника тактирования.
На временной диаграмме видны многократные срабатывания прерывания по сравнению (CMP). Однократные прерывания по перезагрузке (ARR) связаны с реализацией Timer16.
Варианты обхода
Повторная установка флага потребует подбора обходного пути в зависимости от задачи. Основная идея - отключение прерывания на время счета, равного значению сравнения. Рассмотрим некоторые из возможных обходных путей работы с прерываниями по сравнению.
Отключение прерывания по сравнению и повторное включение в основном цикле
Один из возможных вариантов обхода: временное отключение прерывания по сравнению на время до смены значения счетчика в другой части программы, например, в основном цикле, как в примере ниже:
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR):
Такой вариант можно использовать, если программа работает в режиме pooling'а, регулярно вызывая основной цикл.
Отключение прерывания по сравнению и повторное включение в прерывании по перезагрузке
Возможный вариант обхода: отключение прерывания по сравнению и повторное включение в прерывании по переполнению. Пример представлен ниже:
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно:
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно:
На временной диаграмме видны однократные срабатывания прерываний по сравнению (CMP) и перезагрузке (ARR) на выводах GPIO0.0 и GPIO0.2 соответственно: