TIMER16

Длительность тактов таймера и ШИМ

Описание

Если делитель таймера (PRESCALER) больше 1, то длительность первого счёта таймера занимает PRESCALER - 1 тактов входной частоты таймера до делителя (Timer16_clk), а длительность последнего счёта занимает 1 такт частоты таймера.

Длительность счёта влияет на длительность уровней ШИМ сигнала следующим образом:

  • при PRESCALER = 1 (PRESC = 0) низкий уровень ШИМ занимает CMP тактов Timer16_clk, а высокий уровень ARR - CMP + 1 тактов;

  • при PRESCALER > 1 (PRESC > 0) низкий уровень ШИМ занимает PRESCALER * CMP - 1 тактов Timer16_clk, а высокий уровень PRESCALER * (ARR - CMP) + 1.

timer16 arr prescaler1
Рисунок 1. Диаграмма работы канала в режиме ШИМ (PRESC = 0, ARR = 6, CMP = 3, pol = 0)
timer16 arr prescaler4
Рисунок 2. Диаграмма работы канала в режиме ШИМ (PRESC = 2, ARR = 6, CMP = 3, pol = 0)

Обход

Нет.

Повторная установка флага сравнения CMPM

Описание

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

На временной диаграмме Повторная установка флага CMPM видны многократные срабатывания прерывания по сравнению (CMP).

timer16 irq cmp multiple
Рисунок 3. Повторная установка флага CMPM

Обход

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

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

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

#include "mik32_hal.h"
#include "mik32_hal_timer16.h"
#include "mik32_hal_irq.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)
    {
        GPIO_1->OUTPUT = TIMER16_1->CNT;
        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;
    htimer16_1.Clock.Prescaler = TIMER16_PRESCALER_16;
    htimer16_1.ActiveEdge = TIMER16_ACTIVEEDGE_RISING;

    /* Настройка режима обновления регистра 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;
    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_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_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
    HAL_GPIO_Init(GPIO_1, &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;
    }
}

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

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

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

#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.
 * */

#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()
{
    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;
    htimer16_1.Clock.Prescaler = TIMER16_PRESCALER_128;
    htimer16_1.ActiveEdge = TIMER16_ACTIVEEDGE_RISING;

    /* Настройка режима обновления регистра 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;
    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;
    }
}