Errata MIK32V2

1. ADC

1.1. Смещение SAH_TIME

Описание

В регистре ADC_CONFIG поле SAH_TIME имеет разные разряды для записи и для чтения. Для записи разряды [13:8], для чтения [14:9].

Обход

Используйте для чтения и записи поля SAH_TIME разные разряды. Используйте библиотечные макросы смещения ADC_CONFIG_SAH_TIME_WRITE_S для записи и ADC_CONFIG_SAH_TIME_READ_S для чтения.

При записи других полей регистра ADC_CONFIG следует учитывать смещение значения SAH_TIME. Например, для записи номера канала в поле:

uint32_t channel_selection = 1; // Номер канала.
ANALOG_REG->ADC_CONFIG =
    ((ANALOG_REG->ADC_CONFIG & (~ADC_CONFIG_SAH_TIME_READ_M)) & (~ADC_CONFIG_SEL_M)) |
    ((ANALOG_REG->ADC_CONFIG >> 1) & ADC_CONFIG_SAH_TIME_WRITE_M) |
    ((channel_selection) << ADC_CONFIG_SEL_S);

2. EEPROM

2.1. Ошибка чтения по AHB после записи, стирания и чтения по APB

Описание

Если выполняется несколько чтений подряд с одного и того же адреса по шине AHB, то второе и последующие чтения выполняются без реальной выборки данных из EEPROM, а на выходе блока EEPROM хранится последнее прочитанное значение.

При операции записи или стирания на выходе блока EEPROM формируется 0. Часть контроллера EEPROM, отвечающая за взаимодействия с AHB, не отрабатывает такую ситуацию и считает, что на выходе EEPROM по-прежнему валидные данные. Затем при повторном чтении после записи или стирания будет считан 0.

Аналогичная ситуация возникает, если между чтениями по AHB было чтение из EEPROM через регистры контроллера по интерфейсу APB. В этом случае на выходе EEPROM будут считанные данные.

Таким образом, если программа выполняется из EEPROM, а затем вызывается функция из RAM, то в конвейер загружается следующая инструкция из EEPROM, которая не выполняется и отбрасывается. При возврате из функции RAM инструкция из EEPROM считана не будет, так как была считана ранее при входе в функцию и отброшена. Если в функции проводилась операция чтения или стирания EEPROM, то на выходе EEPROM будет 0, который будет считан при выходе из функции, что вызовет исключение. Если внутри функции выполнить чтение EEPROM через регистры по интерфейсу APB, то считанное значение останется на выходе блока и будет считано ядром при выходе из функции, что также может вызвать исключение.

Обход

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

Пример для функции в RAM, которая вызывается из EEPROM и содержит операции записи, стирания или чтения через регистры контроллера EEPROM:

#define RAM_ATTR __attribute__((section(".ram_text")))

RAM_ATTR void eeprom_write_word(uint32_t data, uint32_t address)
{
    /* Стирание */
    EEPROM_REGS->EECON = EEPROM_EECON_BWE_M;
    EEPROM_REGS->EEA = address;
    EEPROM_REGS->EEDAT = 0;
    EEPROM_REGS->EECON |= EEPROM_EECON_WRBEH(EEPROM_WRBEH_SINGLE) |
                        EEPROM_EECON_OP(EEPROM_EECON_OP_ER) |
                        EEPROM_EECON_EX_M;
    while ((EEPROM_REGS->EESTA & EEPROM_EESTA_BSY_M) != 0)
        ;

    /* Запись */
    EEPROM_REGS->EECON = EEPROM_EECON_BWE_M;
    EEPROM_REGS->EEA = address;
    EEPROM_REGS->EEDAT = data;
    EEPROM_REGS->EECON |= EEPROM_EECON_WRBEH(EEPROM_WRBEH_SINGLE) |
                        EEPROM_EECON_OP(EEPROM_EECON_OP_PR) |
                        EEPROM_EECON_EX_M;
    while ((EEPROM_REGS->EESTA & EEPROM_EESTA_BSY_M) != 0)
        ;

    // Два чтения для гарантированного считывания инструкции из EEPROM
    // при выходе из функции.
    asm volatile(
        "lw x0,0(%0)\n\t"
        "lw x0,4(%0)\n\t"
        :
        : "r"(EEPROM_BASE_ADDRESS));
}

3. GPIO

3.1. Чтение регистра LINE_MUX

Описание

В блоке GPIO_IRQ в регистре LINE_MUX считывается только младший байт (MUX_0 и MUX_1). В остальных разрядах всегда считывается 0.

Обход

Используйте переменную для хранения значения регистра LINE_MUX.

4. I2C

4.1. Флаг TXIS

Описание

Флаг TXIS формируется в конце передачи байта, а не в его начале. Из-за этого, если байт был предварительно загружен, то после подтверждения адреса первый TXIS не выставляется. Первая установка TXIS будет после отправки первого байта. Если в режиме без растягивания (NOSTRETCH = 1) за время от установки TXIS до отправки следующего байта не были загружены данные, то возникает недозагрузка.

Ведомый передача без растягивания
Рисунок 1. Ожидаемая диаграмма состояния шины I2C и события при передаче данных в режиме ведомый без растягивания (NOSTRETCH = 1)
Ведомый передача без растягивания ошибка
Рисунок 2. Диаграмма работы флага TXIS с ошибкой в режиме ведомый без растягивания (NOSTRETCH = 1)

Обход

Варианты:

  1. Передача по флагу TXE без прерываний;

  2. Передача по флагу TXIS с прерываниями. Потребует значительно снизить частоту передачи (приблизительно до 50 кГц) и использовать очень короткий обработчик прерываний;

  3. Использование DMA. Этот блок тоже работает по флагу TXIS, но передает байт значительно быстрее, чем это возможно по прерыванию, что позволит работать на частоте 400 кГц и выше.

5. OTP

5.1. Чтение в 2 этапа

Описание

При чтении в 2 этапа данные читаются по предыдущему адресу.

Обход

После записи адреса в регистр OTPA следует прочитать регистр данных OTPDAT 2 раза.

6. RTC

6.1. Активный уровень срабатывания будильника

Описание

Бит WU.SYS_LEVEL.LVL_RTC - активный уровень срабатывания будильника функциональной нагрузки не несёт.

Обход

Для корректной работы RTC бит LVL_RTC должен быть установлен в 1.

7. SPI

7.1. Автоматический выбор ведомых SPI0

Описание

В автоматическом режиме выбора ведомых (MANUAL_CS = 0) вывод SPI0_n_ss_out_3 не работает. При использовании SPI0_n_ss_out_2 сигнал дублируется на SPI0_n_ss_out_3.

Обход

Используйте вместо SPI0_n_ss_out_2 и SPI0_n_ss_out_3 любые выводы GPIO в режиме ручного выбора ведомого (MANUAL_CS = 1).

7.2. Режим ведущего устройства

Описание

Если во время обмена вывод SPIx_n_ss_in не настроен в режим SPI (PADx_CFG = 1), возникнет ошибка MODE_FAIL, а передача прервётся.

Обход

Переведите вывод SPIx_n_ss_in в режим SPI и используйте подтяжку к напряжению питания.

8. TIMER16

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

Описание

Если делитель таймера (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
Рисунок 3. Диаграмма работы канала в режиме ШИМ (PRESC = 0, ARR = 6, CMP = 3, pol = 0)
timer16 arr prescaler4
Рисунок 4. Диаграмма работы канала в режиме ШИМ (PRESC = 2, ARR = 6, CMP = 3, pol = 0)

Обход

Нет.

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

Описание

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

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

timer16 irq cmp multiple
Рисунок 5. Повторная установка флага 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;
    }
}

9. TIMER32

9.1. Сброс флагов

Описание

Сброс флагов IC_INT_CHx, OC_INT_CHx, UDF_INT и OVF_INT происходит в конце счёта таймера, во время которого была запись в регистр INT_CLEAR. По этой причине возможен повторный вход в обработчик прерываний при низкой частоте счёта таймера.

timer32 flags multiple ic
Рисунок 6. Повторная установка флага IC_INT_CHx, захват сигнала IC_CHx_IN по фронту

Обход

Отключение прерывания по захвату и повторное включение в основном цикле после того как флаг был очищен

#include "mik32_hal.h"
#include "mik32_hal_timer32.h"
#include "mik32_hal_irq.h"

TIMER32_HandleTypeDef htimer32;
TIMER32_CHANNEL_HandleTypeDef htimer32_channel;

void SystemClock_Config(void);
static void Timer32_Init(void);

int main()
{
    __HAL_PCC_EPIC_CLK_ENABLE();

    Timer32_Init();

    HAL_Timer32_Value_Clear(&htimer32);

    HAL_Timer32_Capture_Start_IT(&htimer32, &htimer32_channel);

    HAL_EPIC_MaskLevelSet(HAL_EPIC_TIMER32_2_MASK);

    HAL_IRQ_EnableInterrupts();

    while (1)
    {
        /* Разрешить прерывание после того как флаг был сброшен. */
        if ((htimer32.Instance->INT_FLAGS & TIMER32_INT_IC_M(htimer32_channel.ChannelIndex)) == 0)
        {
            htimer32.Instance->INT_MASK |= TIMER32_INT_IC_M(htimer32_channel.ChannelIndex);
        }
    }
}

static void Timer32_Init(void)
{
    htimer32.Instance = TIMER32_2;
    htimer32.Top = 6;
    htimer32.Clock.Source = TIMER32_SOURCE_PRESCALER;
    htimer32.Clock.Prescaler = 32000000;
    htimer32.CountMode = TIMER32_COUNTMODE_FORWARD;
    HAL_Timer32_Init(&htimer32);

    htimer32_channel.TimerInstance = htimer32.Instance;
    htimer32_channel.ChannelIndex = TIMER32_CHANNEL_0;
    htimer32_channel.PWM_Invert = TIMER32_CHANNEL_NON_INVERTED_PWM;
    htimer32_channel.Mode = TIMER32_CHANNEL_MODE_CAPTURE;
    htimer32_channel.CaptureEdge = TIMER32_CHANNEL_CAPTUREEDGE_RISING;
    htimer32_channel.OCR = 0;
    htimer32_channel.Noise = TIMER32_CHANNEL_FILTER_OFF;
    HAL_Timer32_Channel_Init(&htimer32_channel);
}

__attribute__((noinline, interrupt, section(".trap_text"))) void raw_trap_handler()
{
    uint32_t interrupt_status = HAL_Timer32_InterruptFlags_Get(&htimer32);
    uint32_t interrupt_mask = htimer32.Instance->INT_MASK;

    if ((interrupt_status & TIMER32_INT_IC_M(htimer32_channel.ChannelIndex)) & interrupt_mask)
    {
        // Очистить флаг и отключить прерывание по захвату.
        htimer32.Instance->INT_MASK &= ~TIMER32_INT_IC_M(htimer32_channel.ChannelIndex);
        htimer32.Instance->INT_CLEAR = TIMER32_INT_IC_M(htimer32_channel.ChannelIndex);
    }
}

10. USART

10.1. Сигнал CK в USART1

Условия

  • Синхронный режим включен (CONTROL2.CLKEN = 1);

  • Контроль четности выключен (CONTROL1.PCE = 0);

  • Вывод PORT2_6 в режиме UART1_ck (PADx_CFG = 2)

Описание

Вырабатывается непрекращающийся сигнал CK на выводе PORT2_6.

Обход

Нет.

11. Основные режимы работы микроконтроллера

11.1. Спящий

11.1.1. Выход по прерыванию RTC

Описание

Если система принудительно тактируется от источника LSI32K и осуществляется вход в спящий режим записью PM→SLEEP_MODE = 0b111, то после выхода из сна по прерыванию RTC бит включения глобальных прерываний MIE в регистре ядра MSTATUS сброшен, из-за чего не вызывается обработчик прерываний.

Ядро некорректно отрабатывает ситуацию, когда прерывание выставляется до появления частоты. При входе в обработчик, для предотвращения повторного входа в обработчик, ядро автоматически снимает MIE. При появлении прерывания от RTC ядро пересинхронизируется на системную частоту (LSI32K), но частота после выхода из спящего режима подается чуть позже.

Обход

Варианты:

  1. Повторно установить бит MIE после выхода из спящего режима;

  2. Обрамить PM→SLEEP c обоих сторон asm volatile ("nop").

11.2. Стоп

11.2.1. Выход по сигналу RESET

Описание

После внешнего сброса (RESET) микроконтроллер не выходит из режима, а затем не удается выйти другими способами.

Обход

Нет.

11.3. Ожидание

11.3.1. Не выключается OSC32M

Описание

Если отключить кварц OSC32M в регистре CLOCKS_SYS блока WakeUp, а затем войти в режим ожидания, то источник OSC32M включится.

Обход

Если OSC32M не используется и важно снизить энергопотребление, рекомендуется не подключать кварц, а вывод OSC32M_XI соединить с GND.

12. Важные особенности при работе с перифериями

12.1. DMA

12.1.1. Остановка работы канала

Перед изменением регистра CHx_CFG сначала следует отключить канал в CHx_CFG.ENABLE, иначе запись в CHx_CFG остановит канал.

12.2. PVD

12.2.1. Размерность регистра цифрового фильтра

Монитор питания VCC не детектирует напряжение, если значение цифрового фильтра в регистре DPF_VALUE больше 255. Значения DPF_VALUE[31:16] должно быть 0.

Аналогично для монитора напряжения AVCC и регистра PVD_DPF_VALUE.

12.3. SPIFI

12.3.1. TIMEOUT

В техническом описании до версии 2.2.2 в регистре CTRL присутствовало поле TIMEOUT, которое отсутствует в ревизии контроллера MIK32_V2 и не оказывает влияние на работу с внешней флеш-памятью.

12.4. WAKE UP

12.4.1. Поправочный коэффициент HSI32M

Значения поправочного коэффициента HSI32M (модуль WakeUp поле CLOCKS_SYS.ADJ_HSI32M) лежат в диапазоне от 16 до 255. При значениях ниже 16 работа HSI32M не гарантируется.

12.5. WDT

12.5.1. Сброс периферии

Таймер сбрасывает только свои настройки и ядро. Для программного сброса периферий системного домена и памяти можно использовать вход в режим ожидания с выходом по RTC.

12.6. Основные режимы работы микроконтроллера

12.6.1. Стоп

Выход из режима стоп по будильнику RTC возможен при условии: (системная частота) >= (2 * частота RTC).

12.6.2. Ожидание

Для выхода из режима ожидания необходимо, чтобы работал любой источник 32 МГц и любой источник 32 кГц.