Организация функции Delay(), она же delay_ms() и функции delay_us().
Организация функции задержки через цикл - это некорректно сразу по нескольким причинам. У нас могут использоваться прерывания, которые могут вмешаться в работу функции задержки, причем не раз. Если писать функцию задержки на Си, то ее точность будет сильно зависеть от компилятора. Можно написать на на ассемблере, но проблему с прерываниями это не решит и читабельность кода резко упадет (хотя для кого как).
В любом случае лучше всего использовать таймеры. Таймеры ведут счет не зависимо от того чем занят процессор и прерывания могут внести свою долю в работу такой задержки, но незначительно.
Вариант 1.
инициализируем системный таймер
Код: Выделить всё
RCC_GetClocksFreq(&RCC_Clocks);
SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);
Создаем для него обработчик прерывания
Код: Выделить всё
void SysTick_Handler(void) // Счетчики милисекунд
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
if (UserTimingDelay != 0x00)
{
UserTimingDelay--;
}
}
Не забываем, конечно, проинициализировать глобальные переменные в самом начале кода
Код: Выделить всё
volatile __IO uint32_t TimingDelay;
volatile __IO uint32_t UserTimingDelay=0;
Эти две переменные - это 2 счетчика, которые декрементируются в обработчике прерывания таймера 1 раз за одну миллисекунду до нуля. Их можно использовать независимо друг от друга, можно еще больше сделать счетчиков, если очень хочется.
Теперь очередь за самой функцией задержки, проще не куда
Код: Выделить всё
void Delay(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
Ошибка данной функции только на время входа выхода в данную функцию и времени выполнения обработчика прерывания, что всего менее чем в одну микросекунду, чем, по сравнению с миллисекундой, можно пренебречь.
Здесь использовалось прерывание, но если мы хотим считать микросекунды, то постоянные вызовы данного прерывания могут внести нежеланные ошибки в ответственную работу основного кода (микрозадержки, но когда их много, уже существенно). Можно обойтись вторым методом.
Вариант 2.
Инициализируем базовый таймер. Но не забываем смотреть в мануалы по конкретному МК, может у Вас вообще нету нужного таймера? На этом потерял кучу времени, думал, что если на серию таймер есть, значит его не может не быть. А вот оказалось, что TIM5 в моем stm32f103c8t6 попросту отсутствует, вернее их там только с TIM1 до TIM4, еще две сторожевые собаки и системный, ВСЕГО 7 штук и ВСЕ! Примеры мне попадались только на TIM5 и я долго не мог понять, что делаю не так и почему таймер не запускается . Пока не нашел на китайском форуме недоумение, что почему-то таймеры работают только до 4-го, а остальные не работают. Переделал на 2-й, получил положительный результат и только после этого посмотрел в даташит на камешек...
Код: Выделить всё
/* Enable timer clock - use TIMER2 */
TIM_TimeBaseInitTypeDef Tim2;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
Tim2.TIM_Period=0xFFFF;
Tim2.TIM_Prescaler=(RCC_Clocks.HCLK_Frequency / 1000000)-1;
Tim2.TIM_ClockDivision=TIM_CKD_DIV1;
Tim2.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2,&Tim2);
После данного кода таймер запустится и его счетчик будет крутится по кругу от 0 до 0xFFFF. В функции задержки мы будем включать таймер только когда он нужен, выставлять на старт и ждать пока он досчитает до нужного значения и выключим его - типа ВСЕ!
Код: Выделить всё
void delay_us( uint16_t uSecs )
{
volatile uint16_t counter=1; // Погрешность в 1мкСекунду
TIM_Cmd(TIM2,ENABLE);
TIM_SetCounter(TIM2,counter);
while(counter<uSecs)
{
counter=TIM_GetCounter(TIM2);
}
TIM_Cmd(TIM2,DISABLE);
}
В этот раз функция задержки менее идеальная, ведь задержка всего на микросекунды и тут погрешность сравнимая с самой задержкой. Если прикинуть сколько времени уйдет на вызовы всех функций, то получится где-то около одной микросекунды. Вот на одну микросекунду, я внес поправку в счетчик. Тут решений 2: или вносить поправки, или оптимизировать код. Если оптимизировать код, то придется работать напрямую с регистрами и отказаться от включения выключения таймера (честно говоря незнаю нафига его отключаю, вообще нечего идти по стопам чужих примеров), тогда погрешность резко снизится. Работа с регистрами не так привлекательна из-за последующего ухудшения портируемости на другие МК STM32 и читабельности кода, да и мне просто больше нравится использовать стандартные библиотеки, а вот потом, при необходимости будет куда оптимизировать код
Ближе к делу.
Поскольку в реализации подобия осциллографа мне понадобилось использование таких задержек, я потратил уйму времени на их организацию и выяснение причин не работающего таймера, решил начать именно с таймера и задержки в микросекундах (вариант для миллисекунд использовал уже и раньше).
А вдохновила меня вот эта тема "http://arduino.ru/forum/proekty/mini-ostsillograf-arduino-na-lcd-5110". Оригинал кода я не искал, но немного его оптимизировал, в том числе для более удобной смены дисплеев, с разным разрешением экрана.