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

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


Пример ниже демонстрирует эту ситуацию. Для воспроизведения требуется запускать программу из ОЗУ (используя, например, скрипт линковки ram.ld и режим загрузки из ОЗУ).
Пример ниже демонстрирует эту ситуацию. Для воспроизведения потребуется использовать источник счета с частотой значительно ниже частоты тактирования системы, например, один из низкоскоростных таймеров.<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_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);
}


В примере включается прерывание таймера 16 по сравнению и устанавливается максимальное значение делителя (деление на 128). Потребуется подключить щуп осциллографа на вывод GPIO0_0. После загрузки программы, на осциллографе будут наблюдаться два импульса с периодом 60 мкс, с промежутком между импульсами 6,9 мкс, что свидетельствует о повторном срабатывании прерывания по сравнению.<syntaxhighlight lang="c">
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;
    }
}
</syntaxhighlight>
[[Файл:Timer16 Многократные срабатывания прерываний.png|мини|492x492пкс]]
На временной диаграмме видны многократные срабатывания:
 
Один из возможных вариантов исправления: временное отключение прерывания по сравнению, как в примере ниже:<syntaxhighlight lang="c">
#include "mik32_hal.h"
#include "mik32_hal.h"
#include "mik32_hal_timer16.h"
#include "mik32_hal_timer16.h"
Строка 29: Строка 150:
     Timer16_1_Init();
     Timer16_1_Init();


     HAL_Timer16_Counter_Start(&htimer16_1, 0xF);
     HAL_Timer16_Counter_Start(&htimer16_1, 0x7);
     HAL_Timer16_SetCMP(&htimer16_1, 0xF / 2);
     HAL_Timer16_SetCMP(&htimer16_1, 0x7 / 2);
     HAL_Timer16_SetInterruptMask(&htimer16_1, TIMER16_INT_CMPM_M);
     HAL_Timer16_SetInterruptMask(&htimer16_1, TIMER16_FLAG_CMPM | TIMER16_FLAG_ARRM);


     /* Включать прерывания Timer16 рекомендуется после его инициализации */
     /* Включать прерывания Timer16 рекомендуется после его инициализации */
Строка 39: Строка 160:
     while (1)
     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;
        }
     }
     }
}
}
Строка 65: Строка 196:


     /* Настройка тактирования */
     /* Настройка тактирования */
     htimer16_1.Clock.Source = TIMER16_SOURCE_INTERNAL_AHB;
     htimer16_1.Clock.Source = TIMER16_SOURCE_INTERNAL_OSC32K;
     htimer16_1.CountMode = TIMER16_COUNTMODE_INTERNAL; /* При тактировании от Input1 не имеет значения */
     htimer16_1.CountMode = TIMER16_COUNTMODE_INTERNAL; /* При тактировании от Input1 не имеет значения */
     htimer16_1.Clock.Prescaler = TIMER16_PRESCALER_128;
     htimer16_1.Clock.Prescaler = TIMER16_PRESCALER_16;
     htimer16_1.ActiveEdge = TIMER16_ACTIVEEDGE_RISING; /* Выбирается при тактировании от Input1 */
     htimer16_1.ActiveEdge = TIMER16_ACTIVEEDGE_RISING; /* Выбирается при тактировании от Input1 */


Строка 102: Строка 233:
     GPIO_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
     GPIO_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;
     GPIO_InitStruct.Pull = HAL_GPIO_PULL_NONE;
     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);
     HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);
}
}
Строка 107: Строка 240:
void trap_handler()
void trap_handler()
{
{
     GPIO_0->OUTPUT ^= 1;
     if (TIMER16_1->ISR & TIMER16_FLAG_CMPM)
    __HAL_TIMER16_CLEAR_FLAG(&htimer16_1, TIMER16_FLAG_CMPM);
    {
    GPIO_0->OUTPUT ^= 1;
        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)
     HAL_EPIC_Clear(HAL_EPIC_TIMER16_1_MASK);
     {
        GPIO_0->SET = 2;
        TIMER16_1->IER &= ~TIMER16_FLAG_ARRM;
        TIMER16_1->ICR = TIMER16_FLAG_ARRM;
        GPIO_0->CLEAR = 2;
    }
}
}
</syntaxhighlight>
</syntaxhighlight>
[[Файл:Timer16 Двойное срабатывание прерывания сравнения.png|мини|620x620пкс|Timer16 Двойное срабатывание прерывания сравнения]]
[[Файл:Timer16 Однократные срабатывания прерываний.png|мини|461x461пкс]]
Происходящее можно представить примерно следующим образом:
На временной диаграмме видны однократные срабатывания каждого из прерываний:

Версия от 06:21, 21 января 2025

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

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

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

#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;
    }
}
Timer16 Многократные срабатывания прерываний.png

На временной диаграмме видны многократные срабатывания:

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

#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;
    }
}
Timer16 Однократные срабатывания прерываний.png

На временной диаграмме видны однократные срабатывания каждого из прерываний: