Передмова
Звісно, штучних затримок в програмі потрібно уникати. І часові затримки краще організовувати за допомоги переривань по таймерам з подальшої обробкою. Але в невеличких проектах і там де виконання програми не критичне до затримок, дуже зручно використовувати прості функції delay. Нещодавно почав використовувати драйвер HAL де є вже функція затримки в мілісекундах. Функції затримки в мікросекундах на жаль там немає. Та і прив'язуватись до якогось високорівневого драйверу в такій простій задачі, як затримки, теж немає сенсу. Вирішив створити бібліотеку delay для своїх потреб з затримками в мілісекундах і мікросекундах. Щоб відлік часу був точним, обов'язково організувати відлік часу на таймерах. Щоб була відв'язана від високорівневих драйверів HAL чи SPL і мала мінімум налаштувань. Щоб достатньо було вказати який використовувати для затримок таймер.
Бібліотека delay
Бібліотека "delay" складається з двох файлів: delay.h і delay.c
Має такі функції:
- void delayInit(void) - функція вмикання потрібного таймеру для відліку пауз;
- void delayDeInit(void) - функція вимикання таймеру, як відлік пауз вже не потрібен;
- void delayMs(volatile uint32_t delay) - функція відліку затримки в мілісекундах, на вхід ціле число мілісекунд
- void delayUs(volatile uint32_t delay) - функція відліку затримки в мікросекундах, на вхід ціле число мікросекунд
В файлі delay.h обрати який таймер буде вести відлік часу. Можна обрати з TIM1, TIM2, TIM3 та TIM4. Як потрібен інший таймер, дописати в файлі delay.c необхідний таймер по аналогії.
Частота для правильного відліку часу береться зі змінної SystemCoreClock, це якщо частота шини таймеру співпадає з SystemCoreClock (дивись малюнок). Червоним обведено ключові місця:
 |
Однакові частоти |
Бувають випадки що частоти не співпадають, як, наприклад, на іншому малюнку:
 |
Частоти не однакові |
Як видно з малюнку, частота SYSCLK - 32MHz, а шина таймера на шині APB1 периферії вже тактується 8MHz. І шина таймерів на шині периферії APB2 тактується 16MHz. Це все завдяки дільникам і множникам шин APB. Тому будьте уважні і задавайте правильні частоти в герцах для таймерів в файлі delay.h.
Звичайно можна визначити частоту автоматично за допомоги функцій драйверу HAL, наприклад такими як:
HAL_RCC_GetSysClockFreq(), HAL_RCC_GetHCLKFreq(), HAL_RCC_GetPCLK1Freq(), HAL_RCC_GetPCLK2Freq(), тощо.
Але ж хотілось відійти від високорівневих драйверів. Напевно є спосіб автоматизувати визначення частоти таймеру і без HAL чи SPL, але я ще не дійшов до цього :-)
delay.h
/*
* my_delay.h
*
* Created on: 20 груд. 2017 р.
* Author: Andriy
*/
#ifndef DELAY_H_
#define DELAY_H_
#include <stdint.h>
//Розкоментувати потрібний таймер для функцій затримки
#define TIMER1
// #define TIMER2
// #define TIMER3
// #define TIMER4
#ifdef TIMER1
#define CURRENT_TIMER ((TIM_TypeDef *) TIM1)
#endif
#ifdef TIMER2
#define CURRENT_TIMER ((TIM_TypeDef *) TIM2)
#endif
#ifdef TIMER3
#define CURRENT_TIMER ((TIM_TypeDef *) TIM3)
#endif
#ifdef TIMER4
#define CURRENT_TIMER ((TIM_TypeDef *) TIM4)
#endif
#define CURRENT_FREQ SystemCoreClock
void delayInit(void);
void delayDeInit(void);
void delayMs(volatile uint32_t delay);
void delayUs(volatile uint32_t delay);
#endif /* DELAY_H_ */
delay.c
/*
* delay.c
*
* Created on: 20 груд. 2017 р.
* Author: Andriy
*/
#include <delay.h>
#include "stm32f1xx.h"
// Функція вмикання таймеру для потреб delay
void delayInit(void)
{
#ifdef TIMER1
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
#endif
#ifdef TIMER2
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
#endif
#ifdef TIMER3
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
#endif
#ifdef TIMER4
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
#endif
}
// Функція вимикання таймеру як немає потреби в delay
void delayDeInit(void)
{
#ifdef TIMER1
RCC->APB2ENR &= ~RCC_APB2ENR_TIM1EN;
#endif
#ifdef TIMER2
RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN;
#endif
#ifdef TIMER3
RCC->APB1ENR &= ~RCC_APB1ENR_TIM3EN;
#endif
#ifdef TIMER4
RCC->APB1ENR &= ~RCC_APB1ENR_TIM4EN;
#endif
}
//Функція формування затримки в мілісекундах
void delayMs(volatile uint32_t delay)
{
CURRENT_TIMER->PSC = CURRENT_FREQ/1000-1; //Встановлюємо подрібнювач
CURRENT_TIMER->ARR = delay; //встановлюємо значення переповнювання таймеру, а також і значення при якому генеруеться подія оновлення
CURRENT_TIMER->EGR |= TIM_EGR_UG; //Генерируемо Подію оновлення для запису даних в регістри PSC і ARR
CURRENT_TIMER->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; //Запускаемо таймер записом биту CEN і встановлюємо режим Одного проходу встановленням біту OPM
while ((CURRENT_TIMER->CR1) & (TIM_CR1_CEN!=0)); //Виконуємо цикл поки рахує таймер до нуля
}
//Функція формування затримки в мікросекундах
void delayUs(volatile uint32_t delay)
{
CURRENT_TIMER->PSC = CURRENT_FREQ/1000000-1; ///Встановлюємо подрібнювач
CURRENT_TIMER->ARR = delay; //встановлюємо значення переповнювання таймеру, а також і значення при якому генеруеться подія оновлення
CURRENT_TIMER->EGR |= TIM_EGR_UG; //Генерируемо Подію оновлення для запису даних в регістри PSC і ARR
CURRENT_TIMER->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; //Запускаемо таймер записом биту CEN і встановлюємо режим Одного проходу встановленням біту OPM
while ((CURRENT_TIMER->CR1) & (TIM_CR1_CEN!=0)); //Виконуємо цикл поки рахує таймер до нуля
}
Як користуватись
Скопіюйте теку, або файли бібліотеки до теки свого проекту. Приєднайте до свого проекту теку, або файли бібліотеки. До головного файлу main.c додайте #include "delay.h". В файлі "delay.h" зазначте який таймер для відліку затримок будете використовувати. І при потребі, частоту шини таймерів в герцах. Коли потрібна затримка просто викличте одну із функцій delayMs(xxx) чи delayUs(xxx) - де xxx часова затримка в мілісекундах чи мікросекундах. Наприклад, delayMs(500) - означає почекаємо пів секунди. Та не забувайте перед викликом цих функцій увімкнути таймер викликом функції delayInit().
Архів з бібліотекою
Завантажити архів з бібліотекою -
download