Top.Mail.Ru

Простейшая организация микросекундной задержки для STM32.

Приветствую, начнем новый год с завершения дел прошлогодних, а именно, с данной форумной темы. В двух словах суть такова - HAL'овая HAL_Delay(), которая в подавляющем большинстве случаев используется для осуществления задержек, дает возможность оперировать интервалами, кратными 1 мс. Чего зачастую бывает недостаточно, поэтому рассмотрим простейшую реализацию микросекундного варианта. Вообще я бы лично использовал скорее модуль DWT для этого, но сегодня разберем вариант с выделением под эту задачу одного из таймеров, поскольку именно о таком попросили.

Создаем проект и инициализируем в STM32CubeMx как обычно:

  • тактирование и отладку:
Настройки проекта в STM32CubeMx
  • тот самый таймер, пусть будет TIM4, разницы существенной нет:
  • настраиваем частоты, у меня внешний кварц 8 МГц на плате:
Настройка тактирования.

Пройдем чуть подробнее. Контроллер я взял тот же, что и у автора темы на форуме - STM32F103C8. Опять же - разницы никакой нет, все элементарно адаптируется под другой чип/семейство/таймер и т. д. TIM4 находится на шине APB1, в данном случае для таймеров имеем частоту - 72 МГц. Значение предделителя, равное 71, дает итоговую величину (71 + 1 = 72), что, в свою очередь, задает частоту таймера, равной:

f_{T} = 72 \medspace МГц \medspace / \medspace 72 = 1 \medspace МГц

То есть один "тик" таймера соответствует 1 мкс. Период таймера, то есть величину, при которой произойдет переполнение, задаем максимально возможной - 65535. Практической ценности в данном проекте она в себе не несет.

Итак, переходим к сути. Работать механизм будет следующим образом:

void usDelay(uint16_t useconds)
{
  __HAL_TIM_SET_COUNTER(&htim4, 0);
  while(__HAL_TIM_GET_COUNTER(&htim4) < useconds);
}

Обнуляем значение счетного регистра таймера (TIM4->CNT), а затем в цикле просто ожидаем, когда его величина станет равной значению useconds, переданному в функцию в качестве аргумента. Что и даст требуемый результат. Макросы __HAL_TIM_SET_COUNTER() и __HAL_TIM_GET_COUNTER(), соответственно, устанавливают значение регистра CNT и возвращают его текущую величину. Не забываем предварительно запустить таймер при помощи HAL_TIM_Base_Start():

/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM4_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim4);

/* USER CODE END 2 */

В проекте я добавил инициализацию PA4 в качестве выхода, так что подергаем выводом, задавая разные значения задержки для проверки:

while (1)
{
	/* USER CODE END WHILE */

	/* USER CODE BEGIN 3 */
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_4);
	usDelay(delayValue);
}
/* USER CODE END 3 */

Переменная delayValue определена все в этом же файле main.c:

/* USER CODE BEGIN PV */
uint16_t delayValue = 125;

/* USER CODE END PV */

В итоге получаем:

STM32, микросекундная задержка.

Благодаря дельному замечанию в группе ВК 👍, добавляю небольшое дополнение. Если во время ожидания в while(__HAL_TIM_GET_COUNTER(&htim4) < useconds), например, произошло прерывание, и за это время таймер успел перпеполниться, то возникнет ошибка. Поскольку значение регистра CNT может и не будет превышать useconds численно, но при этом заданное время уже давно истекло, просто CNT обнулился после переполнения.

Объективно говоря, я слабо представляю себе возникновение данной ситуации в реальности, все-таки, если ты используешь в проекте микросекундные задержки, то наверняка ты позаботился о том, чтобы процесс не прерывался прерываниями, код которых выполняется дольше 65 мс... Тем не менее можно добавить проверку флага переполнения таймера, к примеру таким образом:

void usDelay(uint16_t useconds)
{
  __HAL_TIM_SET_COUNTER(&htim4, 0);
  __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);

  while(__HAL_TIM_GET_COUNTER(&htim4) < useconds)
  {
    if (__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE) != RESET)
    {
      // Error
      break;
    }
  }
}

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

Ссылка на проект в STM32CubeIDE: MT_usDelayExample.

Подписаться
Уведомить о
guest

7 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Max
Max
2 лет назад

Что бы переполнение не было можно при входе в функцию запускать таймер, а при выходе останавливать.

Павел
Павел
2 лет назад

спасибо

Андрей С.
2 лет назад

Я тоже запускаю таймер, и ни каких проблем с этим не возникает.

Игорь
Игорь
2 лет назад

Подскажите, а например с 2-x микросекундной задержкой сработает?

Ruslan
Ruslan
10 месяцев назад

И ничего конфигурировать не надо, просто на дополнительном таймере

void delayMicro(uint16_t microseconds){
SCB_DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // разрешаем использовать счётчик
DWT_CONTROL |= DWT_CTRL_CYCCNTENA_Msk; // запускаем счётчик

uint32_t us_count_tic = microseconds * (SystemCoreClock / 1000000); // получаем кол-во тактов за 1 мкс и умножаем на наше значение
DWT->CYCCNT = 0U; // обнуляем счётчик
while(DWT->CYCCNT < us_count_tic);
}

7
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x