Контакти

Basic таймери у STM32. Працюємо з простими таймерами STM32 F4 discovery Stm32 переривання по таймеру

У статті наведено опис таймерів 32-розрядних ARM-мікроконтролерів серії STM32 від компанії STMicroelectronics. Розглянуто архітектуру та склад регістрів базових таймерів, а також наведено практичні приклади програм.

Для будь-якого мікроконтролера тай-мер є одним з найважливіших вузлів, який дозволяє дуже точно відраховувати інтервали часу, вважати імпульси, що надходять на входи, генерувати внутрішні переривання, формувати сигнали з широтно-імпульсною модуляцією (ШІМ) і підтримувати процеси прямого доступу до пам'яті (ПДП).

Мікроконтролер STM32 має у своєму складі кілька типів таймерів, що відрізняються один від одного за функціональним призначенням. Перший тип таймерів є найпростішим і є базовими таймерами (Basic Timers). До даного типу належать таймери TIM6 та TIM7. Ці таймери дуже просто налаштовуються і управляються за допомогою мінімуму регістрів. Вони здатні відраховувати інтервали часу і генерувати переривання при досягненні таймером заданого значення.
Другий тип є тай-мерами загального призначення (General-Purpose Timers). До нього відносяться таймери з TIM2 по TIM5 і таймери з TIM12 по TIM17. Вони можуть генерувати ШІМ, вважати імпульси, що надходять на певні висновки мікроконтролера, обробляти сигнали від енкодера і т.п.

Третій тип визначає таймери з розвиненим керуванням (Advanced-Control Timer). До цього типу відноситься таймер TIM1, який здатний виконувати всі перераховані вище операції. Крім того, на основі даного таймера можна побудувати пристрій, здатне керувати трифазним електроприводом.

Влаштування базового таймера

Розглянемо пристрій та роботу базового таймера, структурна схема якого представлена ​​малюнку. Базовий таймер побудований на основі 16-бітних регістрів. Його основою є лічильний регістр TIMx_CNT. (Тут і далі символ «х» замінює номер 6 або 7 для базових таймерів TIM6 і TIM7 відповідно.) Попередній дільник TIMx_PSC дозволяє регулювати частоту тактових імпульсів для лічильного регістру, а регістр автозавантаження від TIMx_ARR дає можливість задавати діапазон . Контролер запуску та синхронізації разом з регістрами управління та стану служать для організації режиму роботи таймера і дозволяють контролювати його функціонування.

Завдяки своїй організації лічильник таймера може вважати в прямому і в зворотному напрямку, а також до середини заданого діапазону в прямому, а потім у зворотному напрямку. На вхід базового таймера може подаватися сигнал від кількох джерел, у тому числі тактовий сигнал синхронізації від шини APB1, зовнішній сигнал або вихідний сигнал інших таймерів, що подається на захоплення та порівняння. Таймери TIM6 та TIM7 тактуються від шини APB1. Якщо використовувати кварцовий резонатор із частотою 8 МГц та заводські налаштування тактування за умовчанням, то тактова частота із шини синхронізації APB1 становитиме 24 МГц.

Реєстри базового таймера

У таблиці наведено карту регістрів для базових таймерів TIM6 та TIM7. Базові таймери включають до свого складу наступні 8 регістрів:

●● TIMx_CNT – Counter (лічильний реєстр);
●● TIMx_PSC – Prescaler (попередній дільник);
●● TIMx_ARR – Auto Reload Register (реєстр автоматичного завантаження);
●● TIMx_CR1 – Control Register 1 (реєстр управління 1);
●● TIMx_CR2 – Control Register 2 (реєстр керування 2);
●● TIMx_DIER – DMA Interrupt Enable Register (реєстр дозволу ПДП та переривань);
●● TIMx_SR – Status Register (статусний регістр);
●● TIMx_EGR – Event Generation Register (реєстр генерації подій).

Регістри TIMx_CNT, TIMx_PSC і TIMx_ARR використовують 16 інформаційних розрядів і дозволяють записувати значення від 0 до 65535. Частота тактових імпульсів для рахункового регістра TIMx_CNT, пройшли через дільник TIMx_PSC, розрахували + 1), де Fcnt - частота імпульсів лічильного регістру таймера; Fin – тактова частота; PSC – вміст регістра TIMx_PSC таймера, що визначає коефіцієнт розподілу. Якщо записати в регістр TIMx_PSC значення 23999, то лічильний регістр TIMx_CNT при тактовій частоті 24 МГц змінюватиме своє значення 1000 разів на секунду. Регістр автоматичного завантаження зберігає значення завантаження счётного регістру TIMx_CNT. Оновлення вмісту регістра TIMx_CNT проводиться після його переповнення або обнулення, в залежності від заданого для нього напряму рахунку. Регістр управління TIMх_CR1 має кілька управляючих розрядів. Розряд ARPE дозволяє і забороняє буферування запису в регістр автоматичного завантаження TIMx_ARR. Якщо цей біт дорівнює нулю, то при записі нового значення TIMx_ARR воно буде завантажено в нього відразу. Якщо біт ARPE дорівнює одиниці, то завантаження в регістр відбудеться після події досягнення рахунковим регістром граничного значення. Розряд OPM включає режим «одного імпульсу». Якщо його встановлено, після переповнення рахункового регістру рахунок зупиняється і відбувається скидання розряду CEN. Розряд UDIS дозволяє та забороняє генерування події від таймера. Якщо він обнулений, то подія буде генеруватися при настанні умови генерування події, тобто при переповненні таймера або при програмній установці в регістрі TIMx_ EGR розряду UG. Розряд CEN включає та відключає таймер. Якщо обнулити цей розряд, то буде зупинено рахунок, а при його встановленні рахунок буде продовжено. Вхідний дільник у своїй почне рахунок з нуля. Регістр управління TIMх_CR2 має три управляючих розряду MMS2 ... MMS0, які визначають режим майстра для таймера. У регістрі TIMx_DIER використовується два розряди. Розряд UDE дозволяє і забороняє видавати запит DMA (ПДП) у разі події. Розряд UIE дозволяє і забороняє переривання від таймера. У регістрі TIMx_SR задіяний лише один розряд UIF як прапор переривання. Він встановлюється апаратно, у разі події від таймера. Скидати його потрібно програмно. Реєстр TIMx_EGR містить розряд UG, який дозволяє програмно генерувати подію «переповнення лічильного регістру». При установці цього розряду, відбувається генерація події і скидання рахункового регістру і попереднього дільника. Обнуля-ється цей розряд апаратно. Завдяки цьому розряду можна програмно генерувати подію від таймера, і тим самим примусово викликати функцію обробника переривання таймера.

Розглянемо призначення регістрів управління та стану таймера на конкретних прикладах програм.

Приклади програм

Для запуску таймера необхідно виконати кілька операцій, таких як подача тактування на таймер і ініціалізація його регістрів. Розглянемо ці операції на основі прикладів програм для роботи з таймерами. Досить часто в процесі програмування виникає завдання реалізації тимчасових затримок. Для вирішення цього завдання необхідна функція формування затримки. Приклад такої функції на основі базового таймера TIM7 для STM32 наведено в лістингу 1.

Лістинг 1

#define FAPB1 24000000 // Тактова частота шини APB1 // Функція затримки в мілісекундах та мікросекундах void delay(unsigned char t, unsigned int n)( // Завантажити регістр попереднього дільника PSC If(t = = 0) TIM7- /1000000-1;// для відліку мікросекунд If(t = = 1) TIM7->PSC = FAPB1/1000-1;// для відліку мілісекунд TIM7->ARR = n; ->EGR |= TIM_EGR_UG; // Згенерувати подію оновлення // для запису даних у регістри PSC і ARR TIM7->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; проходу OPM в регістр управління CR1 while (TIM7->CR1&TIM_CR1_CEN != 0); // Очікування закінчення рахунку )

Ця функція може формувати затримки в мікросекундах або мілісекундах в залежності від параметра «t». Тривалість затримки визначається параметром «n». У цій програмі задіяний режим одного проходу таймера TIM7, при якому лічильний регістр CNT виконує рахунок до значення переповнення, записаного в регістрі ARR. Коли ці значення зрівняються, таймер зупиняється. Факт зупинки таймера очікується у циклі while, шляхом перевірки біта CEN статусного регістру CR1. Включення тактування таймерів проводиться одноразово в головному модулі програми при їх ініціалізації. Базові таймери підключені до шини APB1, тому подача тактових імпульсів виглядає наступним чином:

RCC->APB1ENR |= RCC_APB1ENR_ TIM6EN; // Включити тактування на TIM6 RCC->APB1ENR |= RCC_APB1ENR_ TIM7EN; // Включити тактування на TIM7

Описаний вище програмний спосіб формування затримки має істотний недолік, пов'язаний з тим, що процесор змушений займатися опитуванням прапора протягом усього часу затримки і тому не має можливості в цей час виконувати інші завдання. Усунути такий недолік можна за допомогою використання режиму переривань від таймера. Функції обробки переривання для базових таймерів зазвичай виглядають наступним чином:

Void TIM7_IRQHandler()( TIM7->SR = ~TIM_SR_UIF; // Обнулити прапор //Виконати операції ) void TIM6_DAC_IRQHandler()( //Якщо подія від TIM6 if(TIM6->SR & TIM_SR_UIF)( TIM6->SR ; / / Обнулити прапор / / Виконати операції ) )

Розглянемо приклад програми для організації затримки на базовому таймері TIM6, яка використовує переривання від таймера. Для контролю виконання програми задіємо один з висновків мікроконтролера для управління світлодіодними індикаторами, які повинні будуть перемикатися з періодичністю, що визначається програмною затримкою, організованою на таймері TIM6. Приклад такої програми наведено у лістингу 2.

Лістинг 2

// Підключення бібліотек #include #include #include #include #include // Призначення висновків для світлодіодних індикаторів enum (LED1 = GPIO_Pin_8, LED2 = GPIO_Pin_9); // Функція ініціалізації портів управління світлодіодними індикаторами void init_leds() ( RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef gpio; GPIO_StructInit(&gpio); = LED1 | LED2; //Функція ініціалізації таймера TIM6 void init_timer_TIM6() ( // Включити тактування таймера RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseInitTypeDef base_timer; TIM_TimeBase 3999 base_timer.TIM_Prescaler = 24000 – 1; // Задати період рівним 500 мс base_timer.TIM_Period = 500; TIM_TimeBaseInit(TIM6, &base_timer); з переповнення лічильника таймера NVIC_EnableIRQ(TIM6_DAC_IRQn); ) //Функція обробки переривання таймера void TIM6_DAC_IRQHandler()( // Якщо відбулося переривання по переповненню лічильника таймера TIM6 if(TIM_GetITStatus) ного переривання TIM_ClearITPendingBit( TIM6, TIM_IT_Update); //Інвертувати стан світлодіодних індикаторів GPIO_Write(GPIOC, GPIO_ReadOutputData(GPIOC) ^ (LED1 | LED2)); ) ) // Головний модуль програми int main() ( init_leds(); GPIO_SetBits(GPIOC, LED1); GPIO_ResetBits(GPIOC, LED2); init_timer_TIM6(); while (1) ( // Місце для інших команд ) )

У цій програмі функція затримки викликається один раз, після чого процесор може виконувати інші операції, а таймер буде регулярно формувати переривання з заданим інтервалом затримки. Аналогічну програму можна написати і для таймера TIM7. Відмінність такої програми полягатиме в іменах реєстрів і назві обробника переривання. Обробник переривання таймера TIM6 має одну особливість, пов'язану з тим, що вектор обробки переривання цього таймера об'єднаний з переривання від цифро-аналогового перетворювача (ЦАП). Тому у функції оброблювача переривання виконується перевірка джерела переривання. Детальніше ознайомитися з таймерами мікроконтролера STM32 можна на сайті St.com. Для таймера існує безліч інших завдань, описаних вище, які він може успішно вирішити. Тому його застосування в програмі значно полегшує навантаження на процесор і робить програму ефективнішою.

У будь-якому сучасному контролері є таймери. У цій статті мова піде про простих (базових) таймерах stm32f4 discovery.
Це звичайні таймери. Вони 16 бітні з автоматичним перезавантаженням. Крім того є 16 біт програмований дільник частоти. Є можливість генерування переривання з переповнення лічильниката/або запит DMA.

Приступимо. Як і раніше, я користуюся Eclipse + st-util в ubuntu linux

Насамперед підключаємо заголовки:

#include #include #include #include #include

Нічого нового у цьому немає. Якщо не ясно, звідки вони беруться або читайте попередні статті, або відкривайте файл і читайте.

Визначимо дві константи. Одну для позначення діодів, іншу масив з тих самих діодів:

Const uint16_t LEDS = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; // всі діоди const uint16_t LED = (GPIO_Pin_12, GPIO_Pin_13, GPIO_Pin_14, GPIO_Pin_15); // масив із діодами

Швидше за все вже знайома вам функція-ініціалізації периферії (тобто діодів):

Void init_leds()( RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // дозволяємо тактування GPIO_InitTypeDef gpio; // структура GPIO_StructInit(&gpio); // заповнюємо стандартними значеннями gType; gpio.GPIO_Mode = GPIO_Mode_OUT; працюємо як вихід gpio.GPIO_Pin = LEDS; // всі піни діодів GPIO_Init(GPIOD, &gpio);

Функція ініціалізатора таймера:

Void init_timer()( RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); // включаємо тактування таймера /* Інші параметри структури TIM_TimeBaseInitTypeDef * не мають сенсу для базових таймерів. */ TIM_TimeBaseInitTypeDef * Дільник враховується як TIM_Prescaler + 1, тому віднімаємо 1 * / base_timer.TIM_Prescaler = 24000 - 1; // дільник 24000 base_timer.TIM_Period = 1000; // період 1000 імпульсів TIM_TimeBaseInit лічильника таймера TIM6. NVIC_EnableIRQ(TIM6_DAC_IRQn);

Я прокоментував код, так що думаю все ясно.
Ключовими параметрами тут є дільник (TIM_Prescaler) та період (TIM_Period) таймера. Це параметри, які власне і настроюють роботу таймера.

Наприклад, якщо у вас на STM32F4 DISCOVERY тактова частота встановлена ​​на 48МГц, то на таймерах загального призначення частота 24МГц. Якщо встановити дільник (TIM_Prescaler) 24000 (частота рахунку = 24МГц/24000 = 1КГц), а період (TIM_Period) 1000, то таймер буде відраховувати інтервал в 1с.

Зверніть увагу, що це залежить від тактової частоти. Її ви маєте з'ясувати точно.

Також зазначу, що у високих частотах перемикання світлодіода по перериванню істотно спотворює значення частоти. При значенні в 1МГц на виході одержував приблизно 250КГц, тобто. різниця не прийнятна. Такий результат мабуть виходить через витрати часу виконання переривання.

Глобальна змінна - прапор діода, що горить:

U16 flag = 0;

Обробник переривання, що генерує таймер. Т.к. це ж переривання генерується і при роботі ЦАП, спочатку перевіряємо, що спрацювало воно саме від таймера:

Void TIM6_DAC_IRQHandler()( /* Так як цей обробник викликається і для ЦАП, потрібно перевіряти, * чи відбулося переривання по переповненню лічильника таймера TIM6. */ if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) ( flag++ 3) flag = 0;

Функція main:

Int main()( init_leds(); init_timer(); do ( ) while(1); )

Цикл залишаємо порожнім. Лічильник виконує свою роботу асинхронно, а переривання на те і переривання, щоб не залежати від операції, що виконується в даний момент.

Добридень. Сьогодні накидаю першу статейку з таймерів у STM32. Взагалі таймери в STM32 настільки круті, що навіть Шварцнегер нервово палить по крутості))) І вивчати їх доведеться не в одній, і не в двох і не в трьох статтях. Але для початку не забиватимемо собі сильно голови, а просто вивчимо перші прості таймери і попрацюємо з ними.

У STM32 взагалі існує три види таймерів
1) базові (basic timers)
2) загального призначення (general-purpose timers)
3) просунуті (advanced-control timers)

Просунуті таймери найкрутіші і поєднують у собі можливості двох попередніх груп, плюс до цього ще безліч додаткових функцій типу робота з трифазними моторами і т.д. і т.п. До них нам ще далеко, тому в цій частині ми розглядатимемо роботу з базовими (basic timers).
Для початку розглянемо, які є таймери на нашому процесорі STM32F407VG (ви дивіться про свої процесори з якими працюєте)). У моєму процесорі 14 таймерів - 12 - 16ти бітних і 2 32 бітних

Як ми бачимо на картинках до шини АРВ1 підключені таймери TIM2, TIM3, TIM4, TIM5, TIM6, TIM7, TIM12
А до шини АРВ2 - TIM1, TIM8, TIM9, TIM10, TIM11
Тепер давайте розглянемо картинку налаштування нашого тактування у програмі CubeMX. Систему тактування я ще окремо опишу, тому що без неї нікуди, але просто поки покажу як можна затактувати наші таймери, використовуючи внутрішнє джерело тактування HSI.
Ось наше стандартне налаштування тактування без будь-яких перемножувачів частот і т.д. Її ми і використовуватимемо.

А ось варіант прискорення роботи)) Але раджу пустотливими ручонками туди сильно не лазити, а то може укласти процесор на лопатки)) Це все ми потім вивчимо і розглянемо.

Отже, відкриваємо Reference Manual на F4 серію мікроконтролерів і починаємо курити мануал. ТАК, в STM32 не все так просто, тому товариші вчіть англійську, і читайте мануали, тому що без цього довго шукатимете що до чого. Я раніше дуже важко до читання документації ставився (мабуть тому що завдання були простими і мені вистачало звичайних прикладів з інтернету). Ну а тепер читаємо… читаємо…читаємо…
Продовжимо…
Отже таймери 6 та 7 є базовими таймерами. Сидять вони на шині АРВ1 тому що ми бачимо на малюнку з reference manual.

Базові таймери 6 і 7 - 16ти бітні, мають налаштовується предделитель від 0 до 65535. Для цих таймерів є такі регістри доступні для читання \ запису.
Counter Register (TIMx_CNT) - лічильник
Prescaler Register (TIMx_PSC)
Auto-Reload Register (TIMx_ARR) - регістр перезавантаження

Не будемо сильно заглиблюватися в подробиці роботи, тому що там сторінок 10 опису доступних нам регістрів і т.д, нам вистачить трьох написаних вище
Отже, що це за регістри і навіщо вони нам потрібні. Та ось навіщо. Вирішили ми тут терміново поблимати світлодіодом, здивувати товаришів AVR-щиків наприклад, і говоримо - а давай хто швидше налаштує миготіння одним світлодіодом з періодом пів секунди, а другим з періодом за секунду той і виграв. (до речі можна зробити подібний експеримент))))
Для того, щоб це нам реалізувати, потрібно всього 5 кроків.
1) Запустити CubeMX та створити проект під наш контролер.
2) у CubeMX виставити роботу таймерів
3) згенерувати проект та відкрити його в Keil uVision
4) проініціалізувати таймери (по одному рядку на таймер)
5) прописати у перериванні кожного таймера код постійної зміни стану ніжки до якої підключено світлодіод.
Отже, давайте це розглянемо докладніше. Насамперед запускам нашу програму CubeMX
і налаштовуємо наші 2 виведення PD12 та PD13 на висновок (ніжки куди підключені світлодіоди). Встановлюємо для них режим GPIO_Output та режим Output Push_Pull.
Далі зліва активуємо наші базові таймери 6 та 7.

Тепер переходимо до вкладки конфігурації. Як ми пам'ятаємо, ми не стали нічого змінювати в налаштуваннях частот для нашого процесора, тому у нас усі шини тактуються частотою -16МГц. Тепер виходячи з цього, і виходячи з того, що нам потрібно отримати, давайте налаштуємо наші значення ділників і регістру автоперезавантаження.

Як ми пам'ятаємо, нам потрібно щоб один світлодіод блимав із частотою 1Гц (період 1000мсек), а другий із частотою 2Гц (період 500 мсек). Як нам це отримати — дуже просто. Оскільки розподільник на СТМ32 можна ставити будь-який, ми просто обчислимо його значення
Отже частота у нас 16 000 000 тиків за секунду, а потрібно 1000 тиків за секунду. Значить 16 000 000 \ 1 000 = 16 000. Це число мінус 1 і вписуємо значення предделителя. Тобто число у нас виходить 15 999.
Тепер наш таймер цокає із частотою 1000 разів на секунду. Далі, ми повинні вказати, коли ж нам потрібне переривання по переповненню. Для цього записуємо потрібне нам число в Counter Period (autoreload register).
Тобто нам потрібно отримати одне переривання на секунду, а як ми пам'ятаємо, наш таймер цокає 1 раз у мілісекунду. В одній секунді - 1000 мсек - означає це значення і вписуємо в регістр автоперезавантаження.
Для того, щоб отримати переривання раз на пів секунди – записуємо відповідно – 500.

Отже, налаштували, тепер можна сміливо генерувати наш проект. Згенерували, добре. залишилося дуже мало до моменту миготіння світлодіодиками.
Відкрили наш проект. У нас в принципі все налаштовано і готове, тільки потрібно запустити наші таймери, тому що хоч CubeMX все за нас і робить – цим він не займається. Отже- ініціалізуємо
наші таймери ось такими рядками

HAL_TIM_Base_Start_IT(&htim6);
HAL_TIM_Base_Start_IT(&htim7);

Саме в ньому і знаходяться наші переривання для наших таймерів.
Ось обробник переривання для таймера 7

void TIM7_IRQHandler(void)
{
/* USER CODE BEGIN TIM7_IRQn 0 */

/* USER CODE END TIM7_IRQn 0 */
HAL_TIM_IRQHandler(&htim7);
/* USER CODE BEGIN TIM7_IRQn 1 */

/* USER CODE END TIM7_IRQn 1 */
}

Вписуємо в обробник переривання те, що ми хочемо робити - а ми хочемо в кожному перериванні міняти стан наших ніжок до яких підключені світлодіоди.
Використовуємо ось таку конструкцію. HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);

Власне, все. Натискаємо F7, дивимося, щоб не було помилок - і можемо заливати все це справ у наш піддослідний процесор.
Ну і можемо вже насолоджуватися цікавими перемигування світлодіодів.
Відео додам трохи пізніше, ну а поки як зазвичай правильна картинка. Ну і не забуваємо про подяку))

Таймери в STM32, як і вся периферія, є дуже навороченими. Від безлічі різних функцій, які можуть виконувати таймери, може навіть закрутитися голова. Хоча, здавалося б, таймер він на те й таймер, аби просто рахувати. Але насправді все набагато крутіше)

Мало того, що таймери мають такі широкі можливості, так їх ще кілька у кожного контролера. І навіть не два і не три, а більше! Загалом нахвалювати все це можна нескінченно. Давайте вже розуміти, що і як працює. Отже, мікроконтролер STM32F103CB має:

  • 3 таймери загального призначення (TIM2, TIM3, TIM4)
  • 1 більш просунутий таймер із розширеними можливостями (TIM1)
  • 2 WDT (WatchDog Timer)
  • 1 SysTick Timer

Власне таймери загального призначення та таймер TIM1 не сильно відрізняються один від одного, так що обмежимося розглядом якогось одного таймера. До речі, я зупинив свій вибір на TIM4. Без особливої ​​причини просто так захотілося =). Таймери мають 4 незалежні канали, які можуть використовуватися для:

  • Захоплення сигналу
  • Порівняння
  • Генерації ШІМ
  • Генерація одиночного імпульсу
  • Переповнення
  • Захоплення сигналу
  • Порівняння
  • Подія-тригер

При настанні будь-якої з цих подій таймери можуть генерувати запит до DMA (DMA – прямий доступ до пам'яті, вже скоро ми розбиратимемося і з ним =)). Тепер трохи докладніше про кожний режим роботи таймерів.

Режим захвату сигналу. Дуже зручно при роботі таймера в цьому режимі вимірювати період проходження імпульсів. Дивіться самі: надходить імпульс, таймер кладе своє поточне значення лічильника в регістр TIM_CCR.Швидко забираємо це значення і ховаємо в якусь змінну. Сидимо, чекаємо на наступний імпульс. Опа! Імпульс прийшов, таймер знову сує значення лічильника в TIM_CCR, і нам залишається лише відняти з цього значення те, що ми попередньо зберегли. Це, мабуть, найпростіше використання цього таймера, але дуже корисне. Відловлювати можна як передній фронт імпульсу, так і задній, тому можливості досить великі.

Режим порівняння. Тут просто підключаємо якийсь канал таймера до відповідного висновку, і як тільки таймер дорахує до певного значення (воно TIM_CCR) стан виведення зміниться залежно від налаштування режиму (або виставиться в одиницю, або в нуль, або зміниться протилежне).

Режим генерації ШІМ. Ну тут все приховано у назві) У цьому режимі таймер генерує ШІМ! Мабуть, немає сенсу щось писати тут ще зараз. Незабаром буде приклад якраз на ШІМ, там і поколупаємо детальніше.

Режим Dead-Time. Суть режиму в тому, що між сигналами на основному та комплементарному висновках таймера з'являється певна затримка. В інтернеті є багато інформації про те, де це можна і потрібно застосовувати.

Ну от у принципі дуже коротко про основні режими роботи таймера. Якщо будуть питання про інші режими, специфічніші, пишіть у Коментарі 😉

Треба б потихеньку написати програму для роботи з таймерами. Але спочатку подивимося, що є у бібліотеці Standard Peripheral Library. Отже, за таймери відповідають файли – stm32f10x_tim.hі stm32f10x_tim.c. Відкриваємо перший і бачимо, що структура файлу повторює структуру файлу для роботи з GPIO, який ми розглядали у попередній статті. Тут описані структури та поля структур, які потрібні для конфігурування таймерів. Щоправда тут вже не одна, а кілька структур (режимів, а відповідно і налаштувань у таймерів більше, ніж у портів введення-виведення). Усі поля структур мають коментарі, так що не повинно тут виникати жодних проблем. Ну ось, наприклад:

uint16_t TIM_OCMode; // Specifies the TIM mode.

Тут задаватимемо режим роботи таймера. А ось ще:

uint16_t TIM_Channel; // Specifies the TIM channel.

Тут вибираємо канал таймера, нічого несподіваного) Загалом усе досить прозоро, якщо питайте =) З першим файлом зрозуміло. А у файлі stm32f10x_tim.c– готові функції для роботи з таймерами. Теж усе загалом ясно. Ми вже використовували бібліотеку для роботи з GPIO, тепер працюємо з таймерами, і очевидно, що для різної периферії все дуже схоже. Тож давайте створювати проект та писати програму.

Отже, запилюємо новий проект, додаємо всі необхідні файли:

Пишемо код:

Слід зазначити, що у полі TIM_Prescaler потрібно записувати значення, на одиницю менше, ніж ми хочемо отримати.

/****************************timers.c****************** *************/#include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_gpio.h" #include "stm32f10x_tim.h" //При такому ділителі у мене виходить один тик таймера на 10 мкс#define TIMER_PRESCALER 720 /*******************************************************************/ //Змінна для зберігання попереднього стану виведення PB0 uint16_t previousState; GPIO_InitTypeDef port; TIM_TimeBaseInitTypeDef timer; /*******************************************************************/ void initAll() ( //Включаємо тактування порту GPIOB та таймера TIM4 //Таймер 4 у нас висить на шині APB1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE) ; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE) ; //Тут налаштовуємо порт PB0 на вихід //Докладніше про це у статті про GPIO GPIO_StructInit(& port) ; port.GPIO_Mode = GPIO_Mode_Out_PP; port.GPIO_Pin = GPIO_Pin_0; port.GPIO_Speed ​​= GPIO_Speed_2MHz; GPIO_Init(GPIOB, & port); //А тут налаштування таймера //Заповнюємо поля структури дефолтними значеннями TIM_TimeBaseStructInit(& timer) ; //Виставляємо дільник timer.TIM_Prescaler = TIMER_PRESCALER - 1; //Тут значення, дорахувавши до якого таймер згенерує переривання //До речі це значення ми змінюватимемо в самому перериванні timer.TIM_Period = 50; //Ініціалізуємо TIM4 нашими значеннями TIM_TimeBaseInit(TIM4, & timer); ) /*******************************************************************/ int main() ( __enable_irq() ; initAll() ; //Налаштовуємо таймер для генерації переривання по оновленню (переповненню) TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //Запускаємо таймер TIM_Cmd(TIM4, ENABLE); // Дозволяємо відповідне переривання NVIC_EnableIRQ(TIM4_IRQn); while (1 ) ( //Безконечно тупим) Вся корисна робота - у перериванні __NOP() ; ) /*******************************************************************/ //Якщо на виході був 0. timer.TIM_Period = 50; TIM_TimeBaseInit(TIM4, & timer); //Очищаємо біт переривання TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; ) else ( //Виставляємо нуль на виході timer.TIM_Period = 250; TIM_TimeBaseInit(TIM4, & timer); TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; )

У цій програмі ми дивимося, що було на виході до моменту генерації переривання – якщо нуль виставляємо одиницю на 0.5 мс. Якщо була одиниця – ставимо нуль на 2.5 мс. Компілюємо та запускаємо налагодження =)

Невеликий, але дуже важливий відступ… Наш приклад, звичайно, працюватиме і для тесту він цілком пригодиться, але все-таки в “бойових” програмах слід стежити за оптимальністю коду як з погляду його обсягу, так і з точки зору продуктивності та витрати пам'яті. В даному випадку немає сенсу використовувати структуру timer, а також викликати функцію TIM_TimeBaseInit() щоразу при зміні періоду. Правильніше змінювати лише одне значення в одному регістрі, а саме в регістрі TIMx->ARR (де х – це номер таймера). У цьому прикладі код трансформується таким чином:

/*******************************************************************/ void TIM4_IRQHandler() ( //Якщо на виході був 0. if (попереднійState == 0 ) ( //Виставляємо одиницю на виходіпопереднійдержав = 1; GPIO_SetBits(GPIOB, GPIO_Pin_0); //Період 50 тиків таймера, тобто 0.5 мс TIM4-> ARR = 50; ) else ( //Виставляємо нуль на виходіПопереднядержава = 0; GPIO_ResetBits(GPIOB, GPIO_Pin_0) ; //А період тепер буде 250 тиків - 2.5 мс TIM4-> ARR = 250; ) TIM_ClearITPendingBit(TIM4, TIM_IT_Update) ; ) /****************************End of file****************** **********/

Отже, продовжуємо, на шляху у нас чергові граблі)

..\..\..\SPL\src\stm32f10x_tim.c(2870): error: #20: ID “TIM_CCER_CC4NP” is undefined

Не так страшно як може здатися, йдемо у файл stm32f10x.h, знаходимо рядки

Ось тепер усе збирається, можна налагоджувати. Включаємо логічний аналізатор. У командному рядку пишемо: la portb&0x01і спостерігаємо на виході:

Що хотіли, те й отримали) Тобто все працює правильно. У наступній статті поколупаємо режим генерації ШІМ, залишайтеся на зв'язку 😉

Не пропустіть хорошу статтю про таймери загалом – .

Режим захоплення - це спеціальний режим роботи таймера, суть якого в наступному, при зміні логічного рівня на певному виведенні мікроконтролера, значення лічильного регістру записується в інший регістр, який називають регістром захоплення.

Навіщо це треба?
За допомогою цього режиму можна виміряти тривалість імпульсу або період сигналу.

Режим захоплення у STM32 має деякі особливості:

  • можливість вибрати який фронт буде активним
  • можливість змінити частоту вхідного сигналу за допомогою розподільника (1,2,4,8)
  • кожен канал захоплення оснащений вбудованим вхідним фільтром
  • джерелом сигналу захоплення може бути інший таймер
  • для кожного каналу передбачено по два прапори, перший виставляється якщо відбулося захоплення, друге якщо відбулося захоплення при встановленому першому прапорі

Для налаштування режиму захоплення призначені регістри CCMR1(для 1 та 2 каналу) та CCMR2(для 3 та 4), а також регістри CCER, DIER.

Давайте розглянемо детальніше бітові поля регістру CCMR2, що відповідають за налаштування 4 каналу таймера, саме його ми налаштовуватимемо в прикладі. Ще хотілося б відзначити, що в цьому ж регістрі знаходяться бітові поля, які використовуються при налаштуванні таймера в режимі порівняння.

CC4S- Визначає напрям роботи четвертого каналу (вхід або вихід). При налаштуванні каналу як вхід зіставляє сигнал захоплення

  • 00 - канал працює як вихід
  • 01 - канал працює як вхід, сигнал захоплення - TI4
  • 10 - канал працює як вхід, сигнал захоплення - TI3
  • 11 - канал працює як вхід, сигнал захоплення - TRC
IC4PSC- Визначають коефіцієнт поділу, для сигналу захоплення
  • 00 - дільник не використовується, сигнал захоплення IC1PS формується за кожною подією
  • 01 - сигнал захоплення формується за кожною другою подією
  • 10 - сигнал захоплення формується за кожною четвертою подією
  • 11 - сигнал захоплення формується за кожною восьмою подією
IC4F- призначений для налаштування вхідного фільтра, крім кількості вибірок, протягом яких мікроконтролер не реагуватиме на вхідні сигнали, також можна налаштувати частоту вибірок. По суті, ми налаштовуємо час затримки з моменту приходу фронту до "підтверджуючої" вибірки.

Тепер давайте розглянемо регістр CCER.

CC4E- Вмикає/вимикає режим захоплення.
CC4P- визначає фронт яким буде здійснюватися захоплення, 0 - передній, 1 - задній.

І регістр DIER.

CC4DE- дозволяє формувати запит до DMA.
CC4IE- дозволяє переривання із захоплення.

Після того, як відбулося захоплення, формується подія захоплення, яка встановлює відповідний прапор. Це може призвести до генерації переривання та запиту DMA, якщо вони дозволені у регістрі DIER. Крім того, подія захоплення може бути сформована програмно, установкою бітового поля в регістрі генерації подій EGR:

Бітові поля CC1G, CC2G, CC3G та CC4Gдозволяють генерувати подію у відповідному каналі захоплення/порівняння.

До речі, CCR1, CCR2, CCR3 та CCR4- Регістри захоплення, в яких зберігається значення таймера сигналу захоплення.

Для того щоб контролювати формування сигналу захоплення, у регістрі SRдля кожного каналу виділено два прапори.

CC4IF- встановлюється коли формується сигнал захоплення, скидаються ці прапори програмно чи читанням відповідного регістру захоплення/порівняння.
CC4OF- встановлюється, якщо прапор CC4IF не був очищений, а надійшов черговий сигнал захоплення. Цей прапор очищається програмним записом нуля.

Тепер давайте застосуємо отримані знання практично, з генератора сигналів на вхід TIM5_CH4 подамо синусоїду з частотою 50Гц і спробуємо виміряти її період. Для того, щоб прискорити процес пропоную використовувати DMA. Який висновок МК відповідає 4 каналу TIM5 можна знайти в датасіті на МК в розділі Pinouts і pin description.

Для DMAпотрібна адреса регістра CCR4ось як його знайти. Відкриваємо RM0008та у таблиці Register boundary addressesзнаходимо початкову адресу TIM5.


зсув для регістру CCR4можна знайти в тому ж документі у розділі register map.

#include "stm32f10x.h" #define TIM5_CCR4_Address ((u32)0x40000C00+0x40) #define DMA_BUFF_SIZE 2 uint16_t buff;//Буфер uint16_t volatile T; void DMA2_Channel1_IRQHandler (void) ( T = (buff > buff) ? (buff - buff): (65535+ buff - buff); DMA2->IFCR |= DMA_IFCR_CGIF1; ) void Init_DMA(void) ; // Дозволяємо тактування першого DMA модуля DMA2_Channel1->CPAR = TIM5_CCR4_Address; //Вказуємо адресу периферії - регістр результату перетворення АЦП для регулярних каналів DMA2_Channel1->CMAR = ->CCR &= ~DMA_CCR1_DIR; //Указываем направление передачи данных, из периферии в память DMA2_Channel1->CNDTR = DMA_BUFF_SIZE; //Количество пересылаемых значений DMA2_Channel1->CCR &= ~DMA_CCR1_PINC; //Адрес периферии не инкрементируем после каждой пересылки DMA2_Channel1 -> CCR | = DMA_CCR1_MINC; // Адреса пам'яті інкрементуємо після кожної пересилки. hannel1- >CCR |= DMA_CCR1_PL; //Пріоритет - дуже високий DMA2_Channel1->CCR |= DMA_CCR1_CIRC; // Дозволяємо роботу DMA у циклічному режимі DMA2_Channel1->CCR |= DMA_CCR1_TCIE;// Дозволяємо переривання після закінчення передачі DMA2_Channel1->CCR |= DMA_CCR1_EN; //Дозволяємо роботу 1-го каналу DMA ) int main(void) ( Init_DMA(); //включаємо тактування порту А, альтернативних функцій і таймера RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; RCC->APB; ->PSC = 56000-1; // нова частота 1Khz TIM5-> CCMR2 | = TIM_CCMR2_CC4S_0; >CCER &= ~TIM_CCER_CC4P;//вибираємо захоплення по передньому фронту TIM5->CCER |= TIM_CCER_CC4E;//включаємо режим захоплення для 4-го каналу TIM5->DIER |= TIM_DIER_CC4DE;//дозволяємо формувати запит до DMA //TI ->DIER |= TIM_DIER_CC4IE; //дозволяємо переривання по захопленню TIM5->CR1 |= TIM_CR1_CEN; Interrupt while(1) ( ) )



Сподобалася стаття? Поділіться їй