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));
}
4. I2C
4.1. Флаг TXIS
Описание
Флаг TXIS формируется в конце передачи байта, а не в его начале. Из-за этого, если байт был предварительно загружен, то после подтверждения адреса первый TXIS не выставляется. Первая установка TXIS будет после отправки первого байта. Если в режиме без растягивания (NOSTRETCH = 1) за время от установки TXIS до отправки следующего байта не были загружены данные, то возникает недозагрузка.
Обход
Варианты:
-
Передача по флагу TXE без прерываний;
-
Передача по флагу TXIS с прерываниями. Потребует значительно снизить частоту передачи (приблизительно до 50 кГц) и использовать очень короткий обработчик прерываний;
-
Использование DMA. Этот блок тоже работает по флагу TXIS, но передает байт значительно быстрее, чем это возможно по прерыванию, что позволит работать на частоте 400 кГц и выше.
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).
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.
Обход
Нет.
8.2. Повторная установка флага сравнения CMPM
Описание
Флаг прерывания по сравнению устанавливается повторно, если значение счета таймера после сброса флага совпадает со значением сравнения. Из-за этого возможна ситуация, когда после сброса флага прерывания по сравнению внутри обработчика флаг снова выставляется на следующем такте, если счетчик таймера не успел увеличиться. В таком случае, после выхода из обработчика прерывания происходит повторное срабатывание.
На временной диаграмме Рисунок 5 видны многократные срабатывания прерывания по сравнению (CMP).
Обход
Повторная установка флага потребует подбора обходного пути в зависимости от задачи. Основная идея - отключение прерывания на время счета, равного значению сравнения. Рассмотрим некоторые из возможных обходных путей работы с прерываниями по сравнению.
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.
По этой причине возможен повторный вход в обработчик прерываний при низкой частоте
счёта таймера.
Обход
Отключение прерывания по захвату и повторное включение в основном цикле после того как флаг был очищен
#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);
}
}
11. Основные режимы работы микроконтроллера
11.1. Спящий
11.1.1. Выход по прерыванию RTC
Описание
Если система принудительно тактируется от источника LSI32K и осуществляется вход в спящий режим записью PM→SLEEP_MODE = 0b111, то после выхода из сна по прерыванию RTC бит включения глобальных прерываний MIE в регистре ядра MSTATUS сброшен, из-за чего не вызывается обработчик прерываний.
Ядро некорректно отрабатывает ситуацию, когда прерывание выставляется до появления частоты.
При входе в обработчик, для предотвращения повторного входа в обработчик, ядро автоматически снимает MIE. При появлении прерывания от RTC ядро пересинхронизируется на системную частоту (LSI32K), но частота после выхода из спящего режима подается чуть позже.
Обход
Варианты:
-
Повторно установить бит
MIEпосле выхода из спящего режима; -
Обрамить
PM→SLEEPc обоих сторон asm volatile ("nop").
11.3. Ожидание
11.3.1. Не выключается OSC32M
Описание
Если отключить кварц OSC32M в регистре CLOCKS_SYS блока WakeUp, а затем войти в
режим ожидания, то источник OSC32M включится.
Обход
Если OSC32M не используется и важно снизить энергопотребление, рекомендуется не подключать кварц, а вывод OSC32M_XI соединить с GND.