Приветствую, начнем новый год с завершения дел прошлогодних, а именно, с данной форумной темы. В двух словах суть такова - HAL'овая HAL_Delay()
, которая в подавляющем большинстве случаев используется для осуществления задержек, дает возможность оперировать интервалами, кратными 1 мс. Чего зачастую бывает недостаточно, поэтому рассмотрим простейшую реализацию микросекундного варианта. Вообще я бы лично использовал скорее модуль DWT для этого, но сегодня разберем вариант с выделением под эту задачу одного из таймеров, поскольку именно о таком попросили.
Создаем проект и инициализируем в 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 */
В итоге получаем:
Благодаря дельному замечанию в группе ВК 👍, добавляю небольшое дополнение. Если во время ожидания в 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.
Что бы переполнение не было можно при входе в функцию запускать таймер, а при выходе останавливать.
Не, там проблема именно в том может быть, что во время выполнения while(__HAL_TIM_GET_COUNTER(&htim4) < useconds) произойдет прерывание стороннее и в функцию управление вернется, когда таймер уже переполнится.
спасибо
Я тоже запускаю таймер, и ни каких проблем с этим не возникает.
Подскажите, а например с 2-x микросекундной задержкой сработает?
Да, без проблем)
И ничего конфигурировать не надо, просто на дополнительном таймере
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);
}