Переполнение при отсчете времени в STM32Cube

Ранее я писал об отсчете времени в STM32Cube.

STM32Cube считает время в миллисекундах. Время хранится в беззнаковой целочисленной 32-битной переменной:

static __IO uint32_t uwTick;

Это означает, что через 49 суток 17 часов 2 минуты 47 секунд и 295 миллисекунд произойдет переполнение этой переменной.

Хорошо, если устройство не может так долго работать, например, гарантировано сядет батарейка. Но, что если устройство должно работать непрерывно месяцами?

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

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

Посмотрим, на код, отвечающий за обработку времени:

файл stm32***_it.c:

void SysTick_Handler(void)
{
  HAL_IncTick();
}

файл stm32***_hal.c:

static __IO uint32_t uwTick;

__weak void HAL_IncTick(void)
{
  uwTick++;
}

__weak uint32_t HAL_GetTick(void)
{
  return uwTick;
}

__weak void HAL_Delay(__IO uint32_t Delay)
{
  uint32_t tickstart = 0;
  tickstart = HAL_GetTick();
  while((HAL_GetTick() - tickstart) < Delay)
  {
  }
}

Обработчик прерывания SysTick_Handler() вызывается каждую миллисекунду, и вызывает функцию HAL_IncTick(), которая просто увеличивает uwTick на единицу (инкрементирует). Функция HAL_GetTick() возвращает текущее время в миллисекундах; эта функция обильно используется библиотекой следующим образом:

uint32_t tickstart = HAL_GetTick();
...
if((HAL_GetTick()-tickstart) >= Timeout)
{ ... }

Операция сравнения времени довольно проста. Аналогичная операция используется в функции HAL_Delay().

Видите обработку переполнения? И я не вижу. А она есть.

Рассмотрим функцию HAL_Delay(). Для начала ситуацию без переполнения.

Допустим:

Delay = 100,
tickstart = 1000.

Разница

HAL_GetTick()-tickstart

будет расти от 0 до 100. Цикл будет прерван, когда HAL_GetTick() вернет 1100:

1100 - 1000 = 100

100 < 100 = ложь,

Теперь рассмотрим ситуацию с переполнением.

Допустим:

Delay = 100,
tickstart = 4294967290
.

Максимальное число в беззнаковой 32-битной переменной равно 4294967295. То есть до переполнения остается всего 5 инкрементов. При 6-м инкременте HAL_GetTick() вернет 0.

Что тогда вернет операция

HAL_GetTick()-tickstart

то есть, что будет если из 0 вычесть 4294967290?

Обе переменные беззнаковые и целочисленные. И операция вычитания вернет 6. А дальше, когда HAL_GetTick() возвратит 1, операция вычитания вернет 7.

То есть разница между текущим временем и tickstart все время будет расти, как и в нормальной ситуации. Функция задержки отработает правильно.

Важно, что такое поведение стандартизировано и должно реализовываться любым компилятор C и C++. Можете ознакомиться с обсуждением этого вопроса на Stack Overflow.

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

Вывод

Обработка времени в библиотеке STM32Cube происходит корректно, несмотря на то, что переменная uwTick, хранящая время, может переполнится.

Реклама

Поддержка ввода двоичных чисел в Си

Людям, занимающимся низкоуровневым программированием, часто хочется непосредственно ввести двоичную константу, но Си такой возможности не предоставляет. Только некоторые компиляторы поддерживают соответствующее расширение языка. Но если пользоваться таким расширением, то код становится непереносимым.

Чтобы иметь возможность ввода двоичных чисел и сохранить переносимость, вам пригодится этот заголовочный файл: bit.h.

Файл содержит код следующего вида:

#ifndef BIT_H
#define BIT_H

#define b0000_0000 0
#define b0000_0001 1
#define b0000_0010 2
// ...
#define b1111_1110 254
#define b1111_1111 255

#define b0000_0000_1 0
// ...
#define b1111_1111_1 65280

#define b0000_0000_2 0
// ...
#define b1111_1111_2 16711680

#define b0000_0000_3 0
// ...
#define b1111_1111_3 4278190080

#endif

Просто подключаем bit.h:

#include "bit.h"

и вводим двоичные числа, используя префикс b и разделяя тетрады подчеркиванием. Например:

#define BIT_MASK b0001_0101 // 21
const int a = b0011_1110;
...

При задании многобайтового значения такое число задаст только первый байт.
Чтобы задавать многобайтовые значения, требуется в конце двоичного числа ставить суффикс:

  • _1 — для смещения числа на 1 байт влево, то есть задания второго байта,
  • _2 — для смещения числа на 2 байта влево, то есть задания третьего байта,
  • _3 — для смещения числа на 3 байта влево, то есть задания четвертого байта,

при этом между числами требуется производить операцию или (|).

Например:

// 0xFF00FF00
uint32_t mask = b1111_1111_3 | b0000_0000_2 | b1111_1111_1 | b0000_0000;
...

Можно задавать один какой-то байт или несколько байт идущих не по порядку, например:

uint32_t mask = b1111_1111_3 | b1111_1111_1; // 0xFF00FF00
...