Продолжая цикл публикаций по микроконтроллерам на ядре Cortex-M0 компании Megawin (см. предыдущие статьи 1, 2, 3 и 4), сегодня рассмотрим часы реального времени RTC, сторожевые таймеры IWDT и WWDT, а также стандартный для Cortex-M0 таймер SysTick.

Также отметим изменения в организации исходного кода. В структуру кода добавлен файл src/core.h, включающий короткие макросы доступа к регистрам МК:

/// Макрос 8-битного доступа к ячейке (регистру)
#define RB(addr)      (*(volatile uint8_t*)(addr))
/// Макрос 16-битного доступа к ячейке (регистру)
#define RH(addr)      (*(volatile uint16_t*)(addr))
/// Макрос 32-битного доступа к ячейке (регистру)
#define RW(addr)      (*(volatile uint32_t*)(addr))

Для доступа к полям регистров будут применяться макросы из Device Family Pack (структура DFP описана во второй статье цикла). Для каждого периферийного модуля необходимо будет подключать свой заголовочный файл. Все тестовые функции по периферии МК перенесены в отдельные файлы в каталог test. Весь исходный код, настроечные файлы проекта, скрипты сборки и прочее будут размещаться в репозитории GitHub в основной ветке master.

Таймер SysTick

Функциональные возможности и принцип работы

МК серии MG32F02 включают стандартный таймер System Tick Timer, являющийся, как и контроллер прерываний NVIC, частью процессорного модуля Cortex-M0 (см. первую статью цикла). Таймер имеет следующие характеристики:

  • разрядность: 24 бита,

  • режим работы: циклический обратный отсчет от загружаемого значения,

  • генерация исключения SysTick (15) при достижении нуля,

  • возможность программного сброса,

  • возможность получения текущего значения,

  • тактирование от ЦПУ или от одного из двух внешних сигналов.

Таймер включает 4 регистра статуса и управления, приведенных в таблице.

Регистр

Назначение

Описание полей (биты)

CPU_SYST_CSR

Статус и управление

ENCNT (0) — включение таймера,TICKINT (1) — разрешение генерации исключения (прерывания),CLKSOURCE (2) — выбор источника тактирования (0-внешний CK_ST, 1-от ЦПУ),COUNTFLAG (16) — флаг, устанавливается при достижении нуля, сбрасывается при чтении этого регистра

CPU_SYST_RVR

Загружаемое (начальное) значение

RELOAD (0-23) — загружаемое значение, должно быть на 1 меньше требуемого периода счета (коэффициента деления)

CPU_SYST_CVR

Текущее значение счетчика

CURRENT (0-23) — текущее значение, запись любого значения сбрасывает счетчик и флаг COUNTFLAG (не вызывает исключение)

CPU_SYST_CALIB

Калибровочное значения

Все поля доступны только по-чтению.TENMS (0-23) — калибровочное загружаемое значение для интервала 10 мс и внешнего тактированияSKEW (30) — флаг: 0-значение TENMS точное, 1-неточное или не установленоNOREF (31) — флаг: 0-имеется эталонный источник тактирования, 1-не имеется

Адреса регистров стандартные для процессорных модулей Cortex-M0. Значение по-умолчанию для регистра CPU_SYST_CSR — 0, для регистров CPU_SYST_RVR и CPU_SYST_CVR — 0x00FFFFFF. К регистрам возможен только 32-битный доступ.

Выбор внешнего источника тактирования (сигнал CK_ST) определяется битом CSC_CR0.CSC_ST_SEL подсистемы тактирования МК:

  • 0 (HCLK/8) — сигнал тактирования процессорного модуля HCLK (фактически CK_AHB) с делением частоты на 8,

  • 1 (CK_LS/2) — общесистемный низкочастотный сигнал тактирования CK_LS с делением частоты на 2.

Все варианты выбора источника тактирования приведены в следующей таблице.

Источник тактирования

CPU_SYST_CSR.CLKSOURCE

CSC_CR0.CSC_ST_SEL

HCLK/8

0

0

CK_LS/2

0

1

SCLK (CK_AHB)

1

x

Прерывание от таймера SysTick относится к исключениям Cortex-M0 (номер 15) и не является внешним прерыванием IRQ, поэтому имеет единственный бит разрешения прерывания только в собственном регистре CPU_SYST_CSR.TICKINT. Приоритет прерывания определяется в поле CPU_SHPR2.PRI_15. В обработчике прерывания SysTick_Handler() никаких флагов сбрасывать не требуется.

Без прерывания таймер SysTick может использоваться в программе для измерения времени работы фрагмента кода. С прерыванием таймер может использоваться в ОС для переключения между задачами или как периодический генератор событий.

Тестирование

Прежде всего посмотрим с помощью OpenOCD через telnet, какие значения по-умолчанию находятся в регистрах на примере МК MG32F02A064:

> mdw 0xe000e010 4
0xe000e010: 00000000 00ffffff 00ffffff 40028b0a

Первые три регистра имеют ожидаемые значения. В регистре CPU_SYST_CALIB установленный бит SKEW показывает, что калибровочное значение 0x28B0A (166666) не является точным. Оно действительно не подходит под настройки тактирования по-умолчанию, которые предполагают источник тактирования таймера HCLK/8, а частоту ЦПУ 12 МГц. Т.е. тактовая частота таймера получается 1.5 МГц, соответственно для периода 10 мс нужно загружать значение 15000 (минус 1). Проверим это.

Для тестирования таймера SysTick создадим в файле app.c функции:

// Обработчик исключения SysTick
void systick_hdl() {
  RH(PC_SC_h0) = 2; // set PC1
  RH(PC_SC_h1) = 2; // clear PC1
}

// Функция тестирования SysTick Timer
void systick_test() {
  RH(PC_CR1_h0) = 0x0002; // Выход таймера -> PC1
  SVC2(SVC_CHANDLER_SET,15,systick_hdl);   // Устанавливаем обработчик исключения 15
  RW(CPU_SYST_CSR_w) = 0; // Stop timer
  RW(CPU_SYST_RVR_w) = 15000-1; // RELOAD
  RW(CPU_SYST_CVR_w) = 1; // Clear CURRENT
  RW(CPU_SYST_CSR_w) = 3; // CLKSOURCE = 0 (External), TICKINT=1, ENCNT=1
  while (1) ;
}

При прерывании будем генерировать единичный импульс на выводе PC1. Для этого в начале функции systick_test() настраиваем выход PC1 как push-pull. Далее устанавливаем обработчик исключения (прерывания) systick_hdl() (в файле svr.c также нужно добавить первичный обработчик SysTick_Handler(), исходный код которого прилагается в общем архиве). Затем настраиваем таймер. В начале на всякий случай таймер отключаем (так рекомендуется), затем устанавливаем загрузочное значение, сбрасываем и после запускаем от источника HCLK/8 частотой 1.5 МГц. Убеждаемся, что частота импульсов составляет 100.0 Гц:

Частота сигнала прерывания от таймера SysTick
Частота сигнала прерывания от таймера SysTick

Аналогично проверяем частоту 1000.0 Гц для устанавливаемого значения 1500. В завершении тестирования включаем тактирование от SCLK:

 RW(CPU_SYST_CSR_w) = 7;

И убеждаемся, что частота стала ровно в 8 раз выше.

Часы реального времени RTC

Функциональные возможности и принцип работы

МК серии MG32F02 включают модуль часов реального времени RTC (Real Time Clock), имеющий следующие характеристики:

  • разрядность: 32 бита,

  • основной режим работы: циклический прямой отсчет,

  • возможность задания времени срабатывания "будильника" (функция Alarm),

  • возможность сохранения временной отметки (Time Stamp) по команде или внешнему сигналу (функция Capture),

  • возможность загрузки произвольного значения счетчика по команде или переполнению (функция Reload),

  • возможность вывода сигнала тактирования или событий на внешний вывод,

  • тактирование от нескольких источников, включая внешний или внутренний часовой генератор.

Отметим, что модуль содержит только счетчик (например, секунд), функцию календаря необходимо реализовывать программно.

Функциональная схема модуля RTC приведена на следующем рисунке.

Функциональная схема модуля RTC
Функциональная схема модуля RTC

Основным узлом модуля является 32-разрядный счетчик 32-bit Timer, тактируемый от выбираемого источника сигнала. Частота сигнала тактирования предварительно может быть поделена двумя блоками деления PDIV (на 4096) и DIV (на 2, 4 или 8). Модуль содержит следующие программно доступные 32-битные регистры:

  • ALARM Register (RTC_ALM) для задания времени события Alarm,

  • Capture Register (RTC_CAP) для сохранения временной отметки,

  • Reload Register (RTC_RLR) для загрузки произвольного значения счетчика.

В модуле можно использовать внешний управляющий сигнал с вывода RTC_TS для сохранения временной отметки (Time Stamp).

Один из внутренних сигналов модуля может быть выведен как внешний сигнал на вывод RTC_OUT. Активный уровень сигнала выбирается в поле RTC_OUT_STA (при модификации поля также требуется установить бит защиты RTC_OUT_LCK). Источник сигнала определяется полем RTC_CR0.RTC_OUT_SEL из следующих значений:

  • 0 (ALM) — сигнал по событию Alarm,

  • 1 (PC) — сигнал счетных импульсов CK_RTC_INT,

  • 2 (TS) — сигнал по событию срабатывания триггера Time stamp trigger,

  • 3 (TO) — сигнал по событию переполнения Timer overflow.

Все регистры модуля (кроме RTC_STA) защищены от случайной модификации. Перед записью в какой-либо регистр необходимо записать код разблокировки 0xA217 в поле RTC_KEY.RTC_KEY. Вернуться в состояние блокировки можно записью любого другого значения в это поле.

Тактирование

Источник тактирования модуля RTC определяется полем RTC_CLK.RTC_CK_SEL из числа следующих:

  • 0 — общесистемный НЧ-сигнал CK_LS,

  • 1 — общесистемный НЧ-сигнал CK_UT,

  • 2 — сигнал тактирования шины CK_APB,

  • 3 — выход таймера TM01_TRGO.

Модуль RTC, в первую очередь, ориентирован на работу от низкочастотных сигналов CK_LS или CK_UT (см. первую статью цикла). Сигнал CK_UT формируется делителем частоты на 8, 16, 32 (по-умолчанию) или 128 (согласно значению поля CSC_DIV.CSC_UT_DIV) из сигнала CK_ILRCO от встроенного в МК низкочастотного RC-генератора частотой 32 кГц. Отметим, что низкие точность (около 4%) и стабильность генератора ILRCO не позволяют использовать его для отсчета временных интервалов, тем более для часов реального времени.

Сигнал CK_LS может быть получен либо с внешнего генератора EXTCK, либо со встроенного кварцевого генератора XOSC, либо от того же сигнала CK_ILRCO. Но поскольку генератор XOSC логично использовать как основной задающий генератор МК на высоких частотах порядка мегагерц, а вывод для подключения внешнего генератора совпадает с один из выводов подключения кварцевого резонатора, использование сигнала CK_LS также проблематично.

Таким образом, в большинстве случаев модуль придется тактировать либо от ВЧ-сигнала CK_APB с возможным включением значительного деления частоты, либо от таймера TM01. Как вариант, если в приоритете точность RTC, МК можно тактировать от встроенного высокочастотного RC-генератора IHRCO (имеет значительно более высокую точность), а к XOSC подключить часовой кварцевый резонатор на 32768 Гц и получить секундные импульсы на входе счетчика RTC. Для получения миллисекундных импульсов (1000 Гц) на входе RTC можно тактировать весь МК от XOSC или EXTCK с частотой, кратной степени 2 в кГц, т.е. 4.096 МГц, 8.192 МГц или 16.384 МГц (максимально 32.768 МГц для внешнего генератора).

Установка коэффициентов деления частоты сигнала CK_RTC перед его подачей на вход счетчика (в виде сигнала CK_RTC_INT) выполняется в поле RTC_CLK.RTC_CK_PDIV (1 или 4096) и в поле RTC_CLK.RTC_CK_DIV (1, 2, 4 или 8).

Для включения модуля RTC необходимо:

  • включить тактирование модуля установкой бита CSC_APB0.CSC_RTC_EN (предварительно нужно разблокировать возможность записи через регистр CSC_KEY),

  • включить модуль установкой бита RTC_CR0.RTC_EN (предварительно нужно разблокировать возможность записи через регистр RTC_KEY).

Режимы работы и события

В основном режиме работы счетчик модуля RTC под действием тактовых импульсов CK_RTC_INT выполняет прямой счет от 0 до максимального значения 2^32-1, после чего вновь начинает с 0. В этот момент формируется событие по переполнению и активируется флаг TOF (Timer Overflow).

В каждом периоде счетных импульсов CK_RTC_INT в модуле RTC активируется флаг PCF (Periodic Interrupt), который может быть использован для генерации прерывания. Например, при периоде счета 1 с в обработчике этого прерывания могут обновляться данные, связанные с календарем (минуты, часы, и т.д.).

Если при работе модуля включен бит RTC_CR0.RTC_ALM_EN, при совпадении значений счетчика и регистра RTC_ALM генерируется событие Alarm и активируется флаг ALMF. Изменять значение регистра RTC_ALM нужно при сброшенном бите RTC_CR0.RTC_ALM_EN.

Модуль позволяет сохранять текущее состояние счетчика (временная отметка Time Stamp) в регистре RTC_CAP (функция Capture) в следующих двух случаях:

  • по внешнему сигналу с вывода МК RTC_TS, активный фронт которого выбирается в поле RTC_CR0.RTC_TS_TRGS (если 0, функция отключена);

  • из программы при установке бита RTC_CR0.RTC_RC_START.

При разрешении и активации внешнего управляющего сигнала RTC_TS текущее значение счетчика копируется в регистр RTC_CAP и активируется флаг TSF (Time Stamp).

В поле RTC_CR0.RTC_RCR_MDS имеется возможность настроить алгоритм работы модуля, связанный с программным управлением через бит RTC_RC_START и загрузкой нового значения в счетчик (функция Reload). Имеются следующие варианты:

  • 0 (Directly capture) — текущее значение счетчика копируется в регистр RTC_CAP в указанных выше двух случаях;

  • 1 (Delayed capture) — аналогично предыдущему (отличие не описано в документации);

  • 2 (Forced reload) — в счетчик будет загружаться новое значение из регистра RTC_RLR, как только оно будет записано в регистр RTC_RLR;

  • 3 (Auto reload) — в счетчик будет загружаться новое значение из регистра RTC_RLR автоматически при переполнении счетчика.

В модуле имеется дополнительный флаг RCRF, который активируется в следующих случаях:

  • завершено копирование значения счетчика в регистр RTC_CAP,

  • завершена загрузка нового значения счетчика из регистра RTC_RLR.

Прерывания

Все события модуля, приводящие к активации флагов, могут быть использованы для генерации прерывания. Все флаги собраны в регистре статуса RTC_STA. Модуль RTC не имеет свой номер прерывания IRQ и, соответственно, адрес вектора в таблице векторов. Модуль может генерировать прерывание INT_RTC, которое может быть использовано для генерации более общего системного прерывания INT_SYS (IRQ#1). Для использования прерывания от модуля RTC необходимо:

  1. Выбрать событие (или события) в регистре RTC_INT. Например, для разрешения прерывания по событию Alarm нужно установить бит RTC_ALM_IE.

  2. Разрешить прерывание самого модуля установкой бита RTC_INT.RTC_IEA.

  3. Разрешить общесистемное прерывание INT_SYS установкой бита SYS_INT.SYS_IEA.

  4. Разрешить прерывание INT_SYS (IRQ#1) в контроллере прерываний NVIC в регистре CPU_ISER установкой бита 1.

  5. В обработчике прерываний, если включено более одного источника прерывания INT_RTC, определять по флагам, какое именно прерывание сработало.

Тестирование

Перейдем к тестированию RTC. В исходном коде базовой части (Supervisor) нужно добавить первичный обработчик прерывания SYS_IRQHandler() (ISR#1) в файле svr.c:

__attribute__ ((interrupt))
void SYS_IRQHandler() {
  if (hdlr[1]) hdlr[1]();
}

Для выходного сигнала RTC_OUT нам потребуется вывод PD10, поэтому в функции init_clock(), наряду с портами PB и PC, нужно включить и тактирование порта PD (код см. в прилагаемом архиве). Весь остальной код, как и прежде, включаем в отлаживаемую часть (Application) для запуска в ОЗУ. В начале создадим несколько функций для работы с RTC и выделим их в отдельный файл rtc.c:

/// Инициализация RTC
void rtc_init() {
  RH(CSC_KEY_h0) = 0xA217; // unlock access to CSC regs
  RB(CSC_APB0_b0) |= CSC_APB0_RTC_EN_enable_b0; // CSC_RTC_EN = 1
  RH(CSC_KEY_h0) = 0; // lock access to CSC regs
}

/// Включение прерывания INT_SYS по флагам, указанным в flags согласно формату RTC_INT
void rtc_set_int(uint8_t flags) {
  RH(RTC_KEY_h0) = 0xA217; // unlock access to regs
  // включаем прерывания в модуле:
  RB(RTC_INT_b0) = flags | RTC_INT_IEA_enable_b0; // RTC_IEA
  RH(RTC_KEY_h0) = 0; // lock access to regs
  // включаем прерывание INT_SYS:
  RB(SYS_INT_b0) = 1; // SYS_IEA=1
  // включаем прерывание в модуле NVIC:
  RW(CPU_ISER_w) = (1 << 1); // SETENA 1
}

/// Разблокировка записи
void rtc_write_unlock() {
  RH(RTC_KEY_h0) = 0xA217; // unlock access to regs
}

/// Блокировка записи
void rtc_write_lock() {
  RH(RTC_KEY_h0) = 0; // lock access to regs
}

/// Подключение выхода. Задаются согласно формату RTC_CR0_b1:
/// RTC_OUT_LCK (7) | RTC_OUT_STA (6) | RTC_TS_TRGS (4-5) | RTC_OUT_SEL (0-1)
/// Разблокировка не требуется. При изменении RTC_OUT_STA также устанавливать RTC_OUT_LCK.
void rtc_set_out(uint8_t out_mode) {
  RH(RTC_KEY_h0) = 0xA217; // unlock access to regs
  RB(RTC_CR0_b1) = out_mode; // RTC_OUT mode
  RH(RTC_KEY_h0) = 0; // lock access to regs
}

В первом тесте организуем инициализацию и настройку модуля RTC и выведем его счетные импульсы на выход RTC_OUT (PD10). Для этого в файле app.c создадим следующую функцию:

/// Вывод счетных импульсов на RTC_OUT
void rtc_test_clock() {
  RH(PD_CR10_h0) = (5 << 12) | 2; // PD10: RTC_OUT, push-pull output
  rtc_init();
  rtc_write_unlock();
  // Вариант на 1000 Гц от CK_UT 4 кГц
  csc_set_ck_ut();
  RB(RTC_CLK_b0) =
      RTC_CLK_CK_PDIV_div1_b0 |
      RTC_CLK_CK_DIV_div4_b0 |
      RTC_CLK_CK_SEL_ck_ut_b0;   // Используем сигнал CK_UT (Unit clock) для тактирования RTC
  // Вариант на 1500 кГц от CK_APB 12 МГц
//  RB(RTC_CLK_b0) =
//      RTC_CLK_CK_PDIV_div1_b0 |
//      RTC_CLK_CK_DIV_div8_b0 |
//      RTC_CLK_CK_SEL_ck_apb_b0;
  RB(RTC_CR0_b0) =
      RTC_CR0_EN_enable_b0;// RTC_EN = 1
  rtc_write_lock();
  rtc_out(RTC_CR0_OUT_SEL_pc_b1); // PC (CK_RTC_INT)
}

Здесь предусмотрены два варианта частоты счетных импульсов:

  • 1000 Гц от сигнала CK_UT с частотой 4 кГц,

  • 1500 кГц от сигнала CK_APB с частотой 12 МГц.

В первом варианте мы получаем результат, когда десятые доли и даже единицы герц меняются "на глазах" каждую секунду, что подтверждает вывод о сомнительной возможности применения генератора ILRCO в RTC:

Частота сигнала RTC_OUT при тактировании от ILRCO
Частота сигнала RTC_OUT при тактировании от ILRCO

Во втором варианте получаем стабильную частоту от кварцевого генератора XOSC:

Частота сигнала RTC_OUT при тактировании от XOSC
Частота сигнала RTC_OUT при тактировании от XOSC

В следующем тесте испытаем функции Alarm и Reload вместе:

#define RTC_ALARM_ADD 15000 // 10 мс

void rtc_hdl() {
  RH(PB_SC_h0) = (1 << 13); // Тестовый сигнал прерывания PB13 HI
  uint32_t d;
  d=RW(RTC_ALM_w);
  rtc_write_unlock();
  if (d == 1000*RTC_ALARM_ADD) {
    d=0;
    RW(RTC_RLR_w)=0;
    RB(RTC_CR1_b0) = 1; // RTC_RC_START
  }
  // Обновляем значение ALARM:
  RB(RTC_CR0_b0) &= ~RTC_CR0_ALM_EN_enable_b0; // RTC_ALM_EN=0
  RW(RTC_ALM_w) = d + RTC_ALARM_ADD;
  RB(RTC_CR0_b0) |= RTC_CR0_ALM_EN_enable_b0; // RTC_ALM_EN=1

  rtc_write_lock();
  RB(RTC_STA_b0) = RTC_STA_ALMF_mask_b0; // Clear ALMF flag
  RH(PB_SC_h1) = (1 << 13); // Тестовый сигнал прерывания PB13 LO
}


/// Тестирование режима ALARM с прерыванием
void rtc_test_alarm() {
  RH(PD_CR10_h0) = (5 << 12) | 2; // PD10: RTC_OUT, push-pull output
  rtc_init();
  rtc_write_unlock();
  // Вариант на 1500 кГц от CK_APB 12 МГц
  RB(RTC_CLK_b0) =
      RTC_CLK_CK_PDIV_div1_b0 |
      RTC_CLK_CK_DIV_div8_b0 |
      RTC_CLK_CK_SEL_ck_apb_b0;
  RW(RTC_ALM_w) = RTC_ALARM_ADD; // Alarm через 10 мс
  RW(RTC_CR0_w) =
      RTC_CR0_RCR_MDS_forced_reload_w |  // RTC_RCR_MDS = 2 (Force Reload)
      RTC_CR0_ALM_EN_enable_w |          // Включаем режим ALARM
      RTC_CR0_EN_enable_w;               // RTC_EN = 1
  rtc_write_lock();

  SVC2(SVC_HANDLER_SET,1,rtc_hdl); // Устанавливаем обработчик прерывания
  rtc_set_int(RTC_INT_ALM_IE_enable_b0); // Разрешаем прерывание по флагу ALMF
  rtc_set_out(RTC_CR0_OUT_SEL_alm_b1); // Настраиваем выход RTC_OUT Alarm
}

Тактирование счетчика настраиваем от сигнала CK_APB с итоговой частотой счета 1500 кГц. На выходе модуля RTC_OUT будем формировать импульсы с периодом 10 мс, в константе RTC_ALARM_ADD указано значение счетчика, которое для этого нужно добавлять каждый раз в регистр RTC_ALM после события Alarm. В функции rtc_test_alarm() устанавливаем прерывание по флагу ALMF, а в регистре RTC_CR0 устанавливаем режим Force Reload и включаем функцию Alarm.

Обработчик прерывания rtc_hdl() работает следующим образом. При наступлении прерывания на вывод PB13 выводится дополнительный контрольный импульс индикации прерывания. Далее значение регистра RTC_ALM увеличивается на константу RTC_ALARM_ADD, после чего сбрасывается флаг прерывания и устанавливается в "0" вывод PB13. Поскольку верхнее значение счетчика не кратно RTC_ALARM_ADD, в обработчике оно программно устанавливается на меньшее значение, например на 1000 циклов по RTC_ALARM_ADD счетных импульсов. Если достигается это число, счетчик программно сбрасывается путем загрузки в него нового значения 0 из регистра RTC_RLR, после чего счетчик перезапускается установкой бита RTC_RC_START. Таким образом, на выводах RTC_OUT (PD10) и PB13 ожидаем импульсы с частотой 100 Гц.

Осциллограммы сигналов на выводах RTC_OUT и PB13
Осциллограммы сигналов на выводах RTC_OUT и PB13

На канал "1" осциллографа подан сигнал RTC_OUT, на канал "2" — сигнал с вывода PB13. Видно, что прерывание работает как и предполагалось, а вот сигнал RTC_OUT ведет себя странно. Согласно функциональной схеме ожидались импульсы с длительностью от момента активации флага ALMF при наступлении события Alarm до программного сброса флага в обработчике rtc_hdl(). В итоге выходной узел формирования сигнала RTC_OUT стал работать как T-триггер, поделив частоту события на 2. Тем не менее, в программном плане функции Alarm и Reload работают корректно.

Таймер IWDT (Independent Watch Dog Timer)

МК серии MG32F02 включают независимый сторожевой таймер IWDT (Independent Watch Dog Timer), имеющий следующие характеристики:

  • разрядность счетчика: 8 бит;

  • тактирование счетчика от генератора ILRCO с предделителем частоты на 1, 2, 4,..., 4096;

  • возможность генерации сигнала сброса МК после полного цикла счета;

  • генерации дополнительных двух событий для сигнала пробуждения или прерывания.

  • работа во всех режимах МК (ON, SLEEP, STOP).

Функциональная схема таймера IWDT приведена на следующем рисунке.

Функциональная схема таймера IWDT
Функциональная схема таймера IWDT

Основным узлом модуля является 8-разрядный счетчик обратного отсчета, тактируемый от генератора ILRCO с опциональным предварительным делением частоты. Коэффициент деления выбирается в 4-битном поле IWDT_CLK.IWDT_CK_DIV как степень двух из диапазона от 1 до 4096. Следующая временная диаграмма иллюстрирует работу таймера IWDT.

Временная диаграмма работы таймера IWDT
Временная диаграмма работы таймера IWDT

После включения модуля счетчик начинает обратный циклический отсчет от 255 до 0. При достижении нуля активируется флаг TF, который может быть использован для формирования сигнала сброса RST_IWDT (момент "C"). Текущее значение счетчика доступно только по чтению в регистре IWDT_CNT (младший байт). В модуле также имеются два компаратора, непрерывно сравнивающие значение счетчика с фиксированными константами. При совпадении с числом 0x20 (момент "B") генерируется событие "Early Wakeup 0" и активируется флаг EW0F. При совпадении с числом 0x40 (момент "A") генерируется событие "Early Wakeup 1" и активируется флаг EW1F. Все три флага могут быть использованы как для формирования сигнала пробуждения WUP_IWDT, так и для генерации прерывания INT_IWDT. Флаги собраны в регистре статуса IWDT_STA.

Модификация всех регистров модуля (кроме IWDT_STA) по-умолчанию заблокирована. Перед записью в какой-либо регистр необходимо записать код разблокировки 0xA217 в поле IWDT_KEY.IWDT_KEY, а для восстановления блокировки — любое другое значение.

Основное назначение модуля — формирование сигнала "холодного" или "горячего" сброса при зависании программы. "Холодный" сброс от модуля IWDT разрешается установкой бита RST_IWDT_CE в регистре RST_CE подсистемы сброса. "Горячий" сброс от модуля IWDT разрешается установкой бита RST_IWDT_WE в регистре RST_WE. По-умолчанию сброс МК от периферийных модулей запрещен. Общее описание подсистемы сброса МК приведено в первой статье цикла.

В процессе работы программа МК для недопущения формирования сигнала сброса должна периодически записывать специальное значение 0x2014 в поле IWDT_KEY.IWDT_KEY, что будет приводить к перезапуску счетчика со значения 255. Если временной интервал между этими записями превысит период счета, т.е. счетчик успеет досчитать до нуля, будет выполнен сброс, если он, конечно, был разрешен в подсистеме сброса МК.

Для включения модуля IWDT необходимо:

  • включить тактирование модуля установкой бита CSC_APB0.CSC_IWDT_EN (предварительно нужно разблокировать возможность записи через регистр CSC_KEY),

  • включить модуль установкой бита IWDT_CR0.IWDT_EN.

Модуль IWDT, как и RTC, не имеет свой номер прерывания IRQ и, соответственно, адрес вектора в таблице векторов. Модуль может генерировать прерывание INT_IWDT, которое может быть использовано для генерации более общего системного прерывания INT_SYS (IRQ#1). Для использования прерывания от модуля IWDT необходимо:

  1. Выбрать события в регистре IWDT_INT.

  2. Разрешить общесистемное прерывание INT_SYS установкой бита SYS_INT.SYS_IEA.

  3. Разрешить прерывание INT_SYS (IRQ#1) в контроллере прерываний NVIC в регистре CPU_ISER установкой бита 1.

  4. В обработчике прерываний, если включено более одного источника прерывания INT_RTC, определять по флагам, какое именно прерывание сработало.

Таймер WWDT (Window Watch Dog Timer)

МК серии MG32F02 включают оконный сторожевой таймер WWDT (Window Watch Dog Timer), имеющий следующие характеристики:

  • разрядность счетчика: 10 бит;

  • тактирование счетчика от одного из двух источников с двумя предделителями частоты;

  • задаваемое разрешенное временное окно для выполнения перезагрузки счетчика со стороны ПО;

  • возможность генерации прерывания по событию "предупреждение";

  • возможность генерации прерывания или сигнала сброса по завершению цикла счета;

  • возможность генерации прерывания или сигнала сброса при преждевременной перезагрузке счетчика;

  • работа в режимах МК ON и SLEEP.

Функциональная схема таймера WWDT приведена на следующем рисунке.

Функциональная схема таймера WWDT
Функциональная схема таймера WWDT

Основным узлом модуля является 10-разрядный счетчик обратного отсчета, тактируемый через два настраиваемых делителя частоты от одного из двух сигналов: CK_APB или CK_UT. Настройки тактирования счетчика определяются следующими полями регистра WWDT_CLK:

  • WWDT_CK_PDIV (1 бит) — коэффициент первого делителя (1 или 256),

  • WWDT_CK_DIV (3 бита) — коэффициент второго делителя (1, 2, 4, ..., 128),

  • WWDT_CK_SEL (1 бит) — выбор исходного сигнала (0 - CK_APB, 1 - CK_UT).

Модуль содержит следующие программно доступные 10-битные регистры, определяющие временные параметры:

  • Reload Register (WWDT_RLR) — начальное значение счетчика (по-умолчанию 0x3FF),

  • Window Register (WWDT_WIN) — значение счетчика, определяющее начало окна разрешенной перезагрузки счетчика (по-умолчанию 0x3FF),

  • Warning Register (WWDT_WRN) — значение счетчика, определяющее момент прерывания по событию "предупреждение" (по-умолчанию 0).

Следующая временная диаграмма иллюстрирует работу таймера WWDT.

Временная диаграмма работы таймера WWDT
Временная диаграмма работы таймера WWDT

После включения модуля счетчик начинает обратный циклический отсчет от значения, записанного в регистре WWDT_RLR, до нуля. При достижении значения из регистра WWDT_WIN (момент "A") открывается окно разрешенного перезапуска счетчика со стороны ПО. При достижении значения из регистра WWDT_WRN (момент "B") активируется флаг WRNF и может быть сгенерировано прерывание по событию "предупреждение" (если установлен бит WWDT_INT.WWDT_WRN_IE). При достижении нуля (момент "C") окно разрешенного перезапуска закрывается, активируется флаг TF (Timeout), что может быть использовано для формирования сигнала сброса RST_WWDT (если установлен бит WWDT_CR0.WWDT_RSTF_EN) или прерывания INT_WWDT (если установлен бит WWDT_INT.WWDT_TIE). Текущее значение счетчика доступно только по чтению в регистре WWDT_CNT (биты 0-9).

В процессе работы программа МК для недопущения формирования сигнала сброса должна в течение времени действия окна разрешенного перезапуска (от момента "A" до момента "B") записывать специальное значение 0x2014 в поле WWDT_KEY.WWDT_KEY, что будет приводить к перезапуску счетчика со значения из регистра WWDT_RLR. Если перезапуск счетчика инициируется до начала окна, активируется флаг WINF, что также может быть использовано для формирования сигнала сброса RST_WWDT (если установлен бит WWDT_CR0.WWDT_RSTW_EN) или прерывания INT_WWDT (если установлен бит WWDT_INT.WWDT_WIN_IE). Флаги собраны в регистре статуса WWDT_STA.

Модификация всех регистров модуля (кроме WWDT_STA) по-умолчанию заблокирована. Перед записью в какой-либо регистр необходимо записать код разблокировки 0xA217 в поле WWDT_KEY.WWDT_KEY, а для восстановления блокировки — любое другое значение.

Модуль WWDT сбрасывается в исходное состояние после "холодного" сброса МК. Возможность сброса модуля при "горячем" сбросе МК определяется значением бита RST_CR0.RST_WWDT_DIS:

  • 0 — сброс модуля разрешен (по-умолчанию),

  • 1 — сброс модуля запрещен, т.е. счетчик будет продолжать считать после "горячего" сброса МК.

Для включения модуля WWDT необходимо:

  • включить тактирование модуля установкой бита CSC_APB0.CSC_WWDT_EN,

  • включить модуль установкой бита WWDT_CR0.WWDT_EN.

Для разрешения сигнала сброса от модуля WWDT необходимо:

  1. Установить биты в регистре WWDT_CR0 для выбора событий.

  2. "Холодный" сброс от модуля WWDT разрешить установкой бита RST_WWDT_CE в регистре RST_CE подсистемы сброса, "горячий" сброс — установкой бита RST_WWDT_WE в регистре RST_WE. По-умолчанию оба сброса запрещены.

Для использования прерывания от модуля WWDT необходимо:

  1. Установить биты в регистре WWDT_INT для выбора событий.

  2. Разрешить прерывание IRQ от модуля WWDT (IRQ#0) в контроллере прерываний NVIC установкой бита 0 в регистре CPU_ISER.

Тестирование сторожевых таймеров

Общий код тестирования

Оба сторожевых таймера, в принципе, решают одну и ту же задачу, поэтому тестировать их логично по общей методике. Все основные функции по работе с таймерами IWDT и WWDT собраны в одном файле src/wdt.c, а функции тестирования и обработчики прерываний — в файле test/wdt_test.c. Для запуска конечной тестовой функции достаточно ее поместить в файл app.c, добавив, разумеется, включение файла test/wdt_test.h.

Прежде всего, в основной функции app() после вывода в терминал приветствия "Hello" добавим вызов функции debug_reset_status(), а ее реализацию поместим в файл wdt_test.c:

void debug_reset_status() {
  uint32_t d;
  d = RW(RST_STA_w);
  debug32('F',d);
  if (d & RST_STA_CRF_mask_w) uart_puts(PORT,"RST:COLD",UART_NEWLINE_CRLF);
  if (d & RST_STA_WRF_mask_w) uart_puts(PORT,"RST:WARM",UART_NEWLINE_CRLF);
  if (d & RST_STA_WWDTF_mask_w) uart_puts(PORT,"SRC:WWDT",UART_NEWLINE_CRLF);
  if (d & RST_STA_IWDTF_mask_w) uart_puts(PORT,"SRC:IWDT",UART_NEWLINE_CRLF);
  // Сбрасываем все флаги:
  RW(RST_STA_w) = 0xFFFFFFFF;
}

Она нам потребуется для чтения флагов регистра статуса RST_STA и определения типа и причины сброса при старте МК. В начале выводится в hex-формате содержимое регистра, затем отдельно анализируются 4 интересующих нас флага.

Таймер IWDT

Создадим в файле wdt.c следующие функции:

/// Инициализация IWDT
void iwdt_init() {
  RH(CSC_KEY_h0) = 0xA217; // unlock access to CSC regs
  RB(CSC_APB0_b0) |= CSC_APB0_IWDT_EN_enable_b0; // CSC_IWDT_EN = 1
  RH(CSC_KEY_h0) = 0; // lock access to CSC regs
}

/// Перезагрузка IWDT
void iwdt_reload() {
  RH(IWDT_KEY_h0) = 0x2014;
}

/// Разблокировка записи регистров IWDT
void iwdt_write_unlock() {
  RH(IWDT_KEY_h0) = 0xA217; // unlock access to regs
}

/// Блокировка записи регистров IWDT
void iwdt_write_lock() {
  RH(IWDT_KEY_h0) = 0; // lock access to regs
}

/// Включение прерывания INT_SYS по флагам, указанным в flags согласно формату IWDT_INT
void iwdt_set_int(uint8_t flags) {
  RH(IWDT_KEY_h0) = 0xA217; // unlock access to regs
  RB(IWDT_INT_b0) = flags; // включаем прерывания в модуле
  RH(IWDT_KEY_h0) = 0; // lock access to regs
  // включаем прерывание INT_SYS:
  RB(SYS_INT_b0) = 1; // SYS_IEA=1
  // включаем прерывание в модуле NVIC:
  RW(CPU_ISER_w) = (1 << 1); // SETENA 1
}

Теперь перейдем собственно к функции тестирования:

// Тест сторожевого таймера IWDT
void iwdt_test() {
  SVC2(SVC_HANDLER_SET,1,iwdt_hdl); // устанавливаем обработчик прерывания INT_SYS
  iwdt_set_int(IWDT_INT_EW0_IE_enable_b0); // разрешаем прерывание IWDT_EW0_IE

  iwdt_init(); // включаем общее тактирование IWDT в CSC
  iwdt_write_unlock();
  RB(IWDT_CLK_b0) = IWDT_CLK_CK_DIV_div1024_b0; // выбираем делитель частоты 4096
  RB(IWDT_CR0_b0) = IWDT_CR0_EN_enable_b0; // включаем IWDT_EN=1
  iwdt_write_lock();

  // Разрешаем горячий сброс:
  RH(RST_KEY_h0) = 0xA217; // разблокируем запись в регистры RST
  RW(RST_WE_w) |= RST_WE_IWDT_WE_enable_w;
  RH(RST_KEY_h0) = 0; // возвращаем блокировку записи в регистры RST

  // Выводим в терминал значение счетчика:
  while (1) {
    __disable_irq();
    debug('C',RB(IWDT_CNT_b0));
    __enable_irq();
    delay_ms(50);
  }
}

Действия функции следующие. В начале устанавливается обработчик прерывания iwdt_hdl() по флагу EW0F и происходит инициализация модуля. Включаем делитель частоты 1024 для счетного сигнала CK_ILRCO частотой 32 кГц и получаем итоговую частоту счета около 31.25 Гц. Полный период таймера составит 256/31.25 ≈ 8.2 с. Разрешаем "горячий" сброс и далее в цикле выводим в терминал значение счетчика с задержкой 50 мс. Чтобы сразу протестировать генерацию события "Early Wakeup 0" используем прерывание с обработчиком:

// Обработчик прерывания IWDT (INT_SYS)
void iwdt_hdl() {
  uart_puts(PORT,"INT:IWDT",UART_NEWLINE_CRLF);
  RB(IWDT_STA_b0) = 0xFF; // Сбрасываем флаги IWDT_STA
}

В нем в UART будет выводиться сообщение о его срабатывании, после чего будут сбрасываться все флаги регистра IWDT_STA. Для того, чтобы вывод в UART из обработчика прерывания не "вклинивался" в основной вывод счетчика в цикле в функции iwdt_test() на время работы debug() отключаются прерывания. Для справки: макрос библиотеки CMSIS __disable_irq() (инструкция CPSID I) запрещает прерывания (кроме исключений NMI и HardFault), а макрос __enable_irq() (инструкция CPSIE I) — разрешает.

Запускаем программу и через несколько секунд в терминале получаем такой вывод (фрагмент в момент сброса МК):

C 0008 00008
C 0007 00007
C 0005 00005
C 0004 00004
C 0002 00002
C 0001 00001
Hello
F 40000800
RST:WARM
SRC:IWDT
C 00FA 00250
C 00F8 00248
C 00F7 00247
C 00F5 00245

Видно, что таймер запустился и начал отсчет. Спустя около 8 с значение дошло до нуля, и поскольку мы не сбрасывали IWDT, произошел "горячий" сброс МК (RST:WARM) по источнику IWDT. Теперь в выводе терминала найдем срабатывание флага EW0F:

C 0026 00038
C 0025 00037
C 0023 00035
C 0022 00034
C 0020 00032
INT:IWDT
C 001F 00031
C 001D 00029
C 001C 00028
C 001A 00026
C 0019 00025

Прерывание сработало как и ожидалось: после перехода счетчика на значение 0x20. Наконец, добавим периодическую перезагрузку счетчика, чтобы не происходило срабатывание IWDT. В начале функции iwdt_test() объявим переменную uint32_t i, а в цикле добавим вызов функции iwdt_reload() с выводом сообщения в терминал:

void iwdt_test() {
  uint32_t i=0;

  // ........

  while (1) {
    __disable_irq();
    debug('C',RB(IWDT_CNT_b0));
    __enable_irq();
    delay_ms(50);
    if (++i==100) {
      iwdt_reload();
      uart_puts(PORT,"RELOAD",UART_NEWLINE_CRLF);
      i=0;
    }
  }
}

Проверяем работу:

C 0070 00112
C 006E 00110
C 006D 00109
C 006B 00107
RELOAD
C 00FF 00255
C 00FE 00254
C 00FC 00252
C 00FB 00251

Теперь мы не допускаем срабатывания таймера: примерно в середине его периода происходит перезагрузка счетчика и счет начинается заново с 255. Мы также не допускаем события "Early Wakeup 0" и срабатывания прерывания.

Таймер WWDT

Создадим в файле wdt.c аналогичные функции для таймера WWDT:

void wwdt_init() {
  RH(CSC_KEY_h0) = 0xA217; // unlock access to CSC regs
  RB(CSC_APB0_b0) |= CSC_APB0_WWDT_EN_enable_b0; // CSC_WWDT_EN = 1
  RH(CSC_KEY_h0) = 0; // lock access to CSC regs
}

void wwdt_reload() {
  RH(WWDT_KEY_h0) = 0x2014;
}

void wwdt_write_unlock() {
  RH(WWDT_KEY_h0) = 0xA217; // unlock access to regs
}

void wwdt_write_lock() {
  RH(WWDT_KEY_h0) = 0; // lock access to regs
}

Для начала запустим такой вариант функции тестирования:

// Тест сторожевого таймера WWDT
void wwdt_test() {
  wwdt_init(); // включаем общее тактирование WWDT в CSC
  wwdt_write_unlock();
  RB(WWDT_CLK_h0) =
    WWDT_CLK_CK_PDIV_divided_by_256_h0 |  // делитель частоты PDIV: 256
    WWDT_CLK_CK_DIV_div128_h0;            // делитель частоты DIV: 128
  RB(WWDT_CR0_b0) =
    WWDT_CR0_RSTW_EN_enable_b0 | // разрешаем сброс по выходу из окна
    WWDT_CR0_RSTF_EN_enable_b0 | // разрешаем сброс по завершению счета
    WWDT_CR0_EN_enable_b0;       // включаем IWDT_EN=1
  wwdt_write_lock();

  // Разрешаем горячий сброс от модуля WWDT:
  RH(RST_KEY_h0) = 0xA217; // разблокируем запись в регистры RST
  RW(RST_WE_w) |= RST_WE_WWDT_WE_enable_w;
  RH(RST_KEY_h0) = 0; // возвращаем блокировку записи в регистры RST

  // Выводим в терминал значение счетчика:
  while (1) {
    __disable_irq();
    debug('C',RH(WWDT_CNT_h0));
    __enable_irq();
    delay_ms(50);
  }
}

В начале происходит инициализация модуля (включение общего тактирования). Тактирование счетчика оставляем от сигнала CK_APB, оба делителя включаем на максимальный коэффициент. Таким образом, получаем итоговую частоту счета 12 МГц/256/128 ≈ 366.2 Гц, а полный период таймера 1024/366.2 ≈ 2.8 с. Разрешаем "горячий" сброс по обоим событиям (выходу из окна и таймауту) и далее в цикле выводим в терминал значение счетчика. После запуска в терминале получаем такой результат:

C 0045 00069
C 0033 00051
C 0021 00033
C 0010 00016
Hello
F 40001000
RST:WARM
SRC:WWDT
C 03B9 00953
C 03A8 00936
C 0396 00918
C 0384 00900

Таймер успешно стартует, и поскольку мы его не перезапускаем, возникает "горячий" сброс МК, при этом сам таймер перезапускается с начального значения. Теперь добавим программный перезапуск таймера:

  while (1) {
    __disable_irq();
    debug('C',RH(WWDT_CNT_h0));
    __enable_irq();
    delay_ms(50);
    if (++i==20) {
      wwdt_reload();
      uart_puts(PORT,"RELOAD",UART_NEWLINE_CRLF);
      i=0;
    }
  }

Проверяем работу:

C 02E4 00740
C 02D3 00723
C 02C1 00705
C 02AF 00687
RELOAD
C 03FF 01023
C 03EE 01006
C 03DC 00988
C 03CA 00970

Сброса не происходит, т.к. мы выполняем перезагрузку примерно на отметке 687/366.2 ≈ 1.88 с, причем укладываемся в окно, которое по-умолчанию начинается от старта.

Теперь проверим главную функцию модуля WWDT — контроль временного окна. Добавим перед вызовом wwdt_write_lock():

  RH(WWDT_WIN_h0) = 700;         // устанавливаем окно

Результат работы аналогичен предыдущему случаю: мы попадаем в окно и сброса МК не происходит. Установим окно на значение 500, т.е. сместим нижнюю границу вперед (счет обратный). Посмотрим результат:

C 029E 00670
C 028D 00653
C 027B 00635
C 0269 00617
Hello
F 40001000
RST:WARM
SRC:WWDT
C 03B9 00953
C 03A8 00936
C 0396 00918
C 0384 00900

Теперь мы не попадаем в окно и генерируется сигнал сброса по выходу за пределы окна — главная функция WWDT работает. Если поставить значение порога окна около 600, через раз будет то срабатывать сброс, то перезагрузка, поскольку в цикле мы не привязывались жестко ко времени.

В завершении тестирования проверим прерывание от модуля WWDT по событию "предупреждение", добавив в начало функции тестирования код:

  SVC2(SVC_HANDLER_SET,0,wwdt_hdl); // устанавливаем обработчик прерывания
  wwdt_set_int(WWDT_INT_WRN_IE_enable_b0); // разрешаем прерывание WWDT_WRN_IE

а перед вызовом wwdt_write_lock() установим порог предупреждения на значение между началом окна и перезагрузкой:

  RH(WWDT_WIN_h0) = 700;         // устанавливаем окно
  RH(WWDT_WRN_h0) = 690;         // устанавливаем время предупреждения

Добавим также сам обработчик прерывания в файл wdt_test.c:

void wwdt_hdl() {
  uart_puts(PORT,"INT:WRN",UART_NEWLINE_CRLF);
  RB(WWDT_STA_b0) = 0xFF; // Сбрасываем флаги
}

Кроме того, в файл базовой части svr.c нужно добавить первичный обработчик прерывания IRQ#0 WWDT_IRQHandler().

Запускаем тест и смотрим результат:

C 02F6 00758
C 02E4 00740
C 02D3 00723
C 02C1 00705
INT:WRN
C 02AF 00687
RELOAD
C 03FF 01023
C 03EE 01006
C 03DC 00988
C 03CA 00970

Предупреждение сработало тогда, когда мы его и ожидали. Все тесты выполнены успешно.

На этом мы завершаем пятую статью цикла. В следующий раз рассмотрим общие таймеры МК серии MG32F02.

Комментарии (0)