Сторожевой таймер

Сторожевой таймер

На этом уроке мы познакомимся со сторожевыми таймерами микроконтроллера STM32.
 

 
Сторожевой таймер или Watchdog предназначен для защиты микроконтроллера от сбоев в процессе работы.
Суть его работы заключается в том, что мы инициализируем сторожевой таймер для работы с определенным периодом. Для этого настраиваем предварительный делитель тактовой частоты и период, как и для обычных таймеров общего назначения. Тактирование сторожевого таймера осуществляется от внутреннего низкочастотного генератора с частотой 40 кГц.

После включения сторожевой таймер начинает считать от установленного в регистр значения до нуля. И если он дойдет до нуля, то в этот момент произойдет перезапуск микроконтроллера.

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

Обычно функцию сброса сторожевого таймера размещают внутри бесконечного цикла программы. Допустим, мы точно знаем, что в нормальном режиме работы время одной итерации бесконечного цикла всегда составляет величину, меньшую 500 мс. Таким образом, сторожевой таймер никак не повлияет на работу программы в нормальном режиме если его период установлен больше указанной величины. Но если вдруг внутри бесконечного цикла произойдет какой-то непредусмотренный переход и программа зависнет, то Watchdog перезапустит микроконтроллер по истечении установленного для него периода. Получается, что он предотвратит глобальное зависание микроконтроллера и запустит программу заново. В этом, собственно, и состоит задача сторожевого таймера.

У микроконтроллеров STM32 есть даже два модуля:

  • независимый IWDG
  • системный WWDG.

Во многом они похожи, но есть и отличия.

 

Независимый сторожевой таймер

Давайте сначала поработаем с модулем IWDG. Открываем проект с предыдущего урока в STM32CubeMX и на вкладке Pinout активируем сторожевой таймер IWDG.
Активация независимого сторожевого таймера

Во вкладке Clock Configuration сторожевой таймер стал активным.
Конфигурация тактирования сторожевого таймера
А во вкладке Configuration появилась возможность настройки модуля сторожевого таймера.
Конфигурация Watchdog

Зайдем в настройки и откроем вкладку Parameter Settings. Здесь имеется возможность установить предварительный делитель частоты. Это степень двойки от 4 до 256. Оставим этот параметр со значением 4.
В последней строке можно указать период сторожевого таймера, то есть временной интервал, через который будет осуществляться сброс микроконтроллера если сторожевой таймер досчитает до нуля. Давайте сделаем период сторожевого таймера равным 100 мс. Для этого в регистр периода поместим значение, равное 1000.
Установка периода сторожевого таймера

Кроме того, независимый сторожевой таймер поддерживает опцию окна. Значение регистра окна должно лежать в диапазоне от 0 до 4095. При этом в случае если обновление счетного регистра сторожевого таймера происходит в момент, когда значение этого счетного регистра больше значения регистра окна, то тоже происходит сброс микроконтроллера. Таким образом, можно реализовать защиту от чересчур частого обновления счетного регистра сторожевого таймера.

Если значение в регистре окна не меньше периода сторожевого таймера, то оконный режим можно считать неактивным.

Давайте сгенерируем код проекта и откроем проект в IAR. Теперь давайте включим сторожевой таймер. Для этого используется функция

 
HAL_StatusTypeDef HAL_IWDG_Start(IWDG_HandleTypeDef *hiwdg)

которой передается дескриптор сторожевого таймера.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_IWDG_Init();
  /* USER CODE BEGIN 2 */
  HAL_IWDG_Start(&hiwdg);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
      htim1.Instance->CCR1 = 72;
    }
    else
      htim1.Instance->CCR1 = 8;
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Скомпилируем проект и загрузим в отладочную плату. Поставим точку останова на вызове первой функции внутри main() и запустим на выполнение – нажимаем F5. Мы видим, что программа снова и снова возвращается на точку останова, хотя должна бы находиться внутри бесконечного цикла. Происходит сброс микроконтроллера, поскольку сторожевой таймер досчитывает до 0, а значение его счетного регистра не обновляется.

Завершим отладку и добавим внутрь бесконечного цикла функцию обновления счетного регистра сторожевого таймера. Это функция

 
HAL_StatusTypeDef HAL_IWDG_Refresh(IWDG_HandleTypeDef *hiwdg)

которой тоже передается ссылка на дескриптор сторожевого таймера.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_IWDG_Init();
  /* USER CODE BEGIN 2 */
  HAL_IWDG_Start(&hiwdg);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_IWDG_Refresh(&hiwdg);
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
      htim1.Instance->CCR1 = 72;
    }
    else
      htim1.Instance->CCR1 = 8;
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Добавилась строка 22.

Откомпилируем проект и снова запустим на выполнение, нажав F5 и не убирая точку останова. При запуске программа дошла до точки останова, но больше в эту точку уже не возвращается, поскольку находится в бесконечном цикле и сброс микроконтроллера уже не происходит.

Системный сторожевой таймер можно также включить аппаратно во вкладке Option Bytes с использованием Visual Programmer. При этом сторожевой таймер активируется сразу при подаче питания. Сброс микроконтроллера происходит как только счетный регистр сторожевого таймера досчитает до нуля. Регистры предварительного делителя, периода и окна проинициализированы значениями по умолчанию.

 

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

Системный сторожевой таймер WWDG используется для обнаружения возникновения сбоев программного обеспечения и обычно генерируется внешними непредвиденными логическими условиями, которые вызывает программа. Он может быть запрограммирован для обнаружения аномального поведения программы слишком поздно или слишком рано.

Аналогично независимому сторожевому таймеру, сброс микроконтроллера генерируется в случае попытки обновления значения счетного регистра в момент когда это содержимое больше величины регистра окна или когда счетный регистр досчитал до нуля. Из этого следует, что счетный регистр должен быть обновлен в ограниченном окне.

Тактирование системного сторожевого таймера осуществляется от предварительного делителя тактовой частоты шины APB1. Частота тактирования системного сторожевого таймера получается делением частоты шины APB1 на постоянный делитель 4096. Кроме того, в настройках таймера мы можем добавить еще и дополнительный предделитель. Его возможные значения - 1, 2, 4 и 8.

Системный таймер можно включить только программным способом. И, в отличие от независимого сторожевого таймера, есть дополнительная возможность обработать прерывание системного сторожевого таймера перед сбросом микроконтроллера. Конечно, при сбросе содержимое всех регистров оперативной памяти будет утеряно, но в обработчике прерываний можно произвести запись какой-то важной информации, например, код ошибки, во FLASH-память программ. О том, как сохранять информацию во FLASH-память, было рассмотрено в уроке «Сохранение данных».

Давайте активируем системный сторожевой таймер.
Системный сторожевой таймер

В настройках можно задать

  • предварительный делитель от 1 до 8
  • начальное значение Downcounter value, по умолчанию составляет 64
  • значение окна Window value, по умолчанию составляет 64.

Начальное значение мы можем установить равным величине, которая лежит в пределах от 64 до 127. Таким образом, таймер начинает считать вниз от того значения, которое мы в него записали, до 63 и, досчитав до 63, перезапускает контроллер. Значение счетного регистра этого сторожевого таймера мы можем обновить только в определенный промежуток времени, который задается параметром Window value. Этот параметр мы также можем установить равным любому числу от 64 до 127.
Настройка системного сторожевого таймера

Таймер перезапустит микроконтроллер по истечении интервала времени, определяемого по формуле
Период системного сторожевого таймера

Пусть начальное значение таймера равно 70, а значение окна — 69. Включаем сторожевой таймер, и он начинает считать от 70 до 63. При этом интервал перезапуска составит около 3,5 мс, причем в течение первых 500 мкс обновление счетного регистра не предусмотрено.

Обновить значение счетного регистра сторожевого таймера мы сможем только после того, как таймер досчитает до 69. Если мы сделаем это раньше установленного срока, то произойдет перезапуск микроконтроллера. Также микроконтроллер будет перезапущен в том случае, если таймер досчитает до 63. Таким образом, при таких настройках параметров таймера мы можем обновить его счетный регистр в те моменты, когда его значение лежит в интервале от 63 до 69.

Зайдем в настройки контроллера прерываний и активируем прерывание сторожевого таймера. Отмечу, что для независимого сторожевого таймера прерывания не предусмотрены.
Активация прерываний системного сторожевого таймера

Сгенерируем проект и запустим его в IAR.
Для запуска системного сторожевого таймера используется функция

 
HAL_StatusTypeDef HAL_WWDG_Start_IT(WWDG_HandleTypeDef *hwwdg)

которой также передается ссылка на соответствующий дескриптор.
Эта функция сразу активирует прерывания.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_IWDG_Init();
  /* USER CODE BEGIN 2 */
  HAL_IWDG_Start(&hiwdg);
  HAL_WWDG_Start_IT(&hwwdg);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_IWDG_Refresh(&hiwdg);
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
      htim1.Instance->CCR1 = 72;
    }
    else
      htim1.Instance->CCR1 = 8;
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Добавлена строка 16.

Поставим точку останова в обработчике прерывания и загрузим проект в отладочную плату. Запускаем на выполнение – нажимаем F5. Программа выполняется до первой точки останова, распложенной в функции main().
При повторном нажатии F5 программа переходит на точку останова в обработчике прерывания.
Следующее нажатие F5 вновь останавливается в обработчике прерывания.
А следующий запуск на выполнение производит сброс микроконтроллера – мы попадаем снова на начало программы.
Двойная обработка прерывания здесь связана с формированием прерывания в момент включения сторожевого таймера. При этом сброс микроконтроллера не происходит, но следующая обработка, связанная с переполнением сторожевого таймера, приводит к сбросу микроконтроллера.

Давайте добавим сброс счетного регистра в конец бесконечного цикла. Это делает функция
 
HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg, uint32_t Counter)

которой передается дескриптор системного сторожевого таймера и новое значение регистра окна. Зададим его равным 69 как было в конфигураторе.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_IWDG_Init();
  MX_WWDG_Init();
  /* USER CODE BEGIN 2 */
  HAL_IWDG_Start(&hiwdg);
  HAL_WWDG_Start_IT(&hwwdg);
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_IWDG_Refresh(&hiwdg);
    if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
      htim1.Instance->CCR1 = 72;
    }
    else
      htim1.Instance->CCR1 = 8;
    HAL_WWDG_Refresh(&hwwdg, 69);
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

Добавлена строка 30.

Снова загрузим проект в отладочную плату и запустим, нажав F5. Первая точка останова в начале программы… Затем переходим к обработчику прерывания сразу после включения сторожевого таймера… И все, больше программа не останавливается, поскольку находится внутри бесконечного цикла. При этом работоспособность программы сохраняется – светодиод начинает светиться ярче при нажатии на кнопку.

Использование сторожевых таймеров позволяет повысить стабильность работы системы и предотвратить ее зависания.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх