Полностью согласен с
@Gereykhanov, только на таймерах.
Берём таймер TIM6 (самый простой таймер с базовыми функциями - чтобы более крутые таймеры оставить свободными), настраиваем его:
// Включаем тактирование таймера
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
// Настраиваем прескалер. При тактовой частоте 24МГц частота
// таймера будет равна 24МГц/24000 = 1кГц
TIM6->PSC = 24000;
// Настраиваем период таймера = 1000 циклов - полный цикл таймера
// будет равен 1/1кГц*1000 = 1 секунда.
TIM6->ARR = 1000;
// Разрешаем прерывание по окончанию счёта
TIM6->DIER |= TIM_DIER_UIE;
// Включаем обработку всех прерываний от таймера TIM6
NVIC_EnableIRQ(TIM6_DAC_IRQn);
// Включаем таймер
TIM6->CR1 |= TIM_CR1_CEN;
после срабатывания таймера будет вызван обработчик прерывания
void TIM6_DAC_IRQHandler(void)
{
if (TIM6->SR & TIM_SR_UIF) {
// Сбрасываем флаг прерывания
TIM6->SR &= ~TIM_SR_UIF;
/* ваш код после задержки*/
}
}
Это не функция delay(100), это скорее демонстрация асинхронного алгоритма - но нужно понимать, что в сложных программах, особенно связанных с передачей и приёмом данных снаружи, практически всё нужно делать на прерываниях.
Во время работы таймера процессор полностью свободен, и может рассчитывать какую-то математику или обслуживать другие прерывания.
Довольно часто в моих программах для STM32 void main выглядит просто как while(1) {}, а вся логика работы находится именно в прерываниях. Кстати, здесь уже рукой подать до конечных автоматов.