Таймери - це така периферія контролера STM32, що дозволяє нам дуже точно відраховувати інтервали часу. Це, мабуть, одна з найважливіших і найбільш використовуваних функцій, проте є й інші. Потрібно почати з того, що в контролерах STM32 існують таймери різного ступеня крутості. Найпростіші це Basic timers . Вони хороші тим, що дуже просто налаштовуються та управляються за допомогою мінімуму регістрів. Все, що вони вміють це відраховувати тимчасові інтервали і генерувати коли таймер дотикається до заданого значення. Наступна група ( general-purpose timers ) набагато крутіше першої, вони вміють генерувати ШІМ, вміють вважати імпульси, що надходять на певні ніжки, можна підключати енкодер ітд. І найкрутіший таймер це advanced-control timer , Думаю що його я використовувати не буду ще дуже довго так як мені поки що без необхідності керувати трифазним електродвигуном. Почати знайомство з таймерами слід з чогось простіше, я вирішив взятися за Basic таймери. Завдання, яке я собі поставив: Змусити таймер генерувати переривання кожну секунду.

Насамперед зазначу, що Basic таймери (TIM6 і TIM7) причеплені до шини APB1, тому якщо частота тактових імпульсів на ній буде змінюватися, то і таймери почнуть цокати швидше або повільніше. Якщо нічого не змінювати в налаштуваннях тактування та залишити їх за замовчуванням, то частота APB1становить 24МГц за умови, що підключений зовнішній кварц на частоту 8 МГц. Взагалі система тактування у STM32 дуже хитромудра і я спробую про неї нормально написати окремий пост. А поки що просто скористаємося тими налаштуваннями тактування, які задаються кодом автоматично згенерованим CooCox'ом. Почати варто з найголовнішого регістру TIMx_CNT(Тут і далі x - номер basic таймера 6 або 7). Це лічильний 16-бітний регістр, що займається безпосередньо рахунком часу. Щоразу коли з шини APB1приходить тактовий імпульс, вміст цього регістру збільшується на одницю. Коли регістр переповнюється, все починається з нуля. За нашої дефолтної частоти шини APB1, таймер за одну секунду тицьне 24 млн разів! Це дуже дофіга, і тому таймер має ділника, керувати яким ми можемо за допомогою регістру TIMx_PSC. Записавши в нього значення 24000-1 ми змусимо лічильний регістр TIMx_CNTзбільшувати своє значення кожну мілісекунду (Частота APB1ділимо на число в регістрі предделителя і отримуємо скільки разів на секунду збільшується лічильник). Одиницю треба відняти оскільки у регістрі нуль це означає, що включений дільник на одиницю. Тепер, коли лічильний регістр дотикається до 1000 ми можемо точно заявити, що пройшла рівно одна секунда! І че тепер опитувати лічильний регістр і чекати доки там з'явиться 1000? Це не наш метод, адже ми можемо заюзати! Але біда, переривання у нас всього одне, і виникає воно коли лічильник обнулиться. Щоб лічильник обнулялся достроково, а чи не коли дотикається до 0xFFFF, служить регістр TIMx_ARR. Записуємо в нього те число, до якого повинен дораховувати регістр TIMx_CNTперед тим як обнулитися. Якщо ми хочемо щоб переривання виникало раз на секунду, то нам потрібно записати туди 1000. Щодо безпосереднього відліку часу це все, але таймер сам по собі цокати не почне. Його потрібно включити встановивши біт CENу регістрі TIMx_CR1. Цей біт дозволяє почати відлік, відповідно якщо його скинути, то відлік зупиниться (ваш К.О.). У регістрі є й інші біти, але вони нам не особливо цікаві. Зате цікавий нам ще один біт, але вже у регістрі TIMx_DIER. Називається він UIE,встановивши його ми дозволяємо таймеру генерувати переривання при скиданні лічильного регістру. Ось власне і все, навіть не складніше, ніж у яких-небудь AVRках. Отже, невелике резюме: Щоб отримати basic таймер потрібно:

  1. Встановити предделитель щоб таймер не цокав швидко ( TIMx_PSC)
  2. Задати межу до якої таймер повинен торкатися свого скидання ( TIMx_ARR)
  3. Увімкнути відлік бітом CENу регістрі TIMx_CR1
  4. Включити переривання з переповнення бітом UIEу регістрі TIMx_DIER

Ось така нехитра послідовність. А тепер настав час дістати і спробувати в мільйонний раз блимнути цим нещасним світлодіодом, але вже за допомогою таймера 🙂

#include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" int main () (GPIO_InitTypeDef PORT; // Включаємо порт С і таймер 6 RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOC, ENABLE); RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM6, ENABLE) // Налаштуємо ноги зі світлодіодами на вихід PORT.GPIO_Pin = (GPIO_Pin_9 | GPIO_Pin_8);PORT.GPIO_Mode = GPIO_Mode_Out_PP; PORT.GPIO_Speed ​​= GPIO_Speed_2MHz; дільник що таймер цокав 1000 разів на секунду TIM6->ARR = 1000;// Щоб переривання траплялося раз на секунду TIM6->DIER |= TIM_DIER_UIE;// дозволяємо переривання від таймера TIM6->CR1 |= TIM_CR1_CEN; NVIC_EnableIRQ(TIM6_DAC_IRQn);// Дозвіл TIM6_DAC_IRQn переривання while(1) ( //Програма нічого не робить у порожньому циклі)) // Обробник переривання TIM6_DAC void TIM6_DAC_IRQHandler(void) ( TIM6; UIF GPIOC->ODR^=(GPIO_Pin_9 | GPIO_Pin_8);// Інвертуємо склад освітлення світлодіодів )

Варто додати невелику примітку до обробника переривання. Справа в тому, що він у нас використовується одразу двома блоками периферії: таймером 6 та DAC'ом. Це означає, що якщо ви писатимете програму яка дозволяє переривання від обох цих периферійних пристроїв, то в тілі оброблювача необхідно перевіряти хто ж із них викликав переривання. У нашому випадку я не став цього робити, тому що жодних переривань від DAC виникнути не може. Він не налаштований, а за дефолтом переривання заборонено. Наступного разу розглянемо general-purpose таймери та їх практичне застосування.

У будь-якому сучасному контролері є таймери. У цій статті мова піде про простих (базових) таймерах 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); // заповнюємо стандартними значеннями g_o; працюємо як вихід gpio.GPIO_Pin = LEDS;// всі піни діодів GPIO_Init(GPIOD, & gpio);

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

Void init_timer()( RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); // включаємо тактування таймера /* Інші параметри структури TIM_TimeBaseInitTypeDef * не мають сенсу для базових таймерів. */ TIM_TimeBaseInitTypeDef; тому віднімаємо 1 * / base_timer.TIM_Prescaler = 24000 - 1; // дільник 24000 base_timer.TIM_Period = 1000; // період 1000 імпульсів TIM_TimeBaseInit (TIM6, &base_timer); лічильника таймера TIM6.*/ TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);TIM_Cmd(TIM6, ENABLE); 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; /* Очищаємо біт оброблюваного переривання */ TIM_ClearITPendingBit(TIM6, TIM_IT_Update);

Функція main:

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

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

Ми вже розглядали таймер SysTick, що є частиною ядра Cortex. Однак на цьому все не закінчується. Таймерів у stm32 багато, і вони бувають різні. Залежно від мети вам доведеться вибрати той чи інший таймер:

  • SysTick;
  • таймери загального призначення (англ. general purpose timer) – TIM2, TIM3, TIM4, TIM15, TIM16, TIM17;
  • просунутий таймер (англ. advanced timer) – TIM1;
  • сторожовий таймер (англ. watchdog timer).

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

Таймери бувають різної бітності: наприклад, SysTick – 24-бітний, а всі таймери, що є у нас у камені – 16-бітні (тобто можуть рахувати до 2 16 = 65535), крім WatchDog. Крім того, кожен таймер має кілька каналів, тобто, по суті, він може працювати за двох, трьох і т. д. Ці таймери вміють працювати з інкрементними енкодерами, датчиками Холла, можуть генерувати ШІМ (широтно-імпульсна модуляція, англ. Pulse Width Modulation - про яку ми поговоримо пізніше) та багато іншого. Крім того, вони можуть генерувати переривання або здійснювати запит до інших модулів (наприклад, DMA - Direct Memory Access) за різними подіями:

  • переповнення (англ. overflow);
  • захоплення сигналу (англ. input capture);
  • порівняння (англ. output compere);
  • подія-тригер (англ. event trigger).

Якщо з переповненням таймера (точніше досягненням «0») нам все зрозуміло - ми розглядали SysTick - то з іншими можливими режимами роботи ми ще не знайомі. Давайте розглянемо їх докладніше.

Захоплення сигналу

Цей режим добре підходить, щоб вимірювати період проходження імпульсів (або їх кількість, скажімо, за секунду). Коли вихід МК приходить імпульс, таймер генерує переривання, з якого ми можемо зняти поточне значення лічильника (з регістру TIM_CCRx , де x - номер каналу) і зберегти у зовнішню змінну. Потім настане наступний імпульс, і ми простою операцією віднімання отримаємо «час» між двома імпульсами. Відловлювати можна як передній, так і задній фронт імпульсу, або навіть відразу обидва. Навіщо це потрібно? Припустимо, у вас є магніт, який ви приклеїли до диска на колесі, а датчик Холла – до вилки велосипеда. Тоді, обертаючи колесо, ви отримуватимете імпульс кожен раз, коли магніт на колесі буде в тій же площині, що і датчик Холла. Знаючи відстань, яку пролітає магніт за оборот і час можна обчислити швидкість руху.

Також існує режим захоплення ШІМ, проте це скоріше особливий спосіб налаштування таймера, ніж окремий режим роботи: один канал ловить передні фронти, а другий задні фронти. Тоді перший канал детектує період, а другий – заповнення.

Режим порівняння

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

Режим генерації ШІМ

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

За допомогою просунутого таймера можна формувати трифазний ШІМ, який стане в нагоді для керування трифазним двигуном.

Режим Dead-time

Ця функція має деякі таймери; вона потрібна для створення затримок на виходах, які потрібні, наприклад, виключення наскрізних струмів під час управління силовими ключами.

В курсі ми використовуватимемо лише «захоплення сигналу» та «генерацію ШІМ».

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

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

Режим захоплення у 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; //Дозволяємо тактування першого DMA модуля DMA2_Channel1->CPAR = TIM5_CCR4_Address;// Вказуємо адресу периферії - регістр результату перетворення АЦП для регулярних каналів DMA2_Channel1->CMAR = (uint32_t)buff;//Задана ->CCR &= ~DMA_CCR1_DIR;// Вказуємо напрям передачі даних, з периферії в пам'ять DMA2_Channel1->CNDTR = DMA_BUFF_SIZE;// Кількість пересилаються значень DMA2_Channel1->CCR &= ~DMA_CCR1_PINC; ->CCR |= DMA_CCR1_MINC;//Адреса пам'яті інкрементуємо після кожної пересилки DMA2_Channel1->CCR |= DMA_CCR1_PSIZE_0; >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; //включаємо лічильник //NVIC->ISER |= NVIC_ISER_SETENA_18; Interrupt while(1) ( ) )

У STM32 є безліч дуже зручних та гнучких у налаштуванні таймерів. Навіть у наймолодшого мікроконтролера (STM32F030F4P6) є 4 таких таймери.

8. Налаштуємо проект - додамо потрібні файли

Для використання таймера нам потрібно підключити файл бібліотеки периферії stm32f10x_tim.c. Так само, правою кнопкою клацаємо в Workspace (ліворуч) по групі StdPeriphLib, Add -> Add files, файл LibrariesSTM32F10x_StdPeriph_Driversrcstm32f10x_tim.c.

Ще потрібно увімкнути використання заголовка до цього файлу. Відкриваємо stm32f10x_conf.h (правою кнопкою за назвою цього файлу в коді, "Open stm32f10x_conf.h". Розкоментуємо рядок #include "stm32f10x_tim.h".

9. Додамо таймер

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

У STM32 є різні таймери, що відрізняються набором властивостей. Найпростіші – Basic timers, складніше – General purpose timers, і найскладніші – Advanced timers. Прості таймери обмежуються просто відліком тактів. У складніших таймерах з'являється ШІМ. Найскладніші таймери, наприклад, можуть згенерувати 3-фазний ШІМ з прямими та інверсними виходами та дедтаймом. Нам вистачить і простого таймера під номером 6.

Трохи теорії

Все, що нам потрібно від таймера - дораховувати до певного значення та генерувати переривання (так, ми ще й навчимося використовувати переривання). Таймер TIM6 тактується від системної шини, але не безпосередньо а через прескалер - простий програмований лічильник-дільник (подумати тільки, в СРСР випускалися спеціальні мікросхеми-лічильники, причому програмовані були особливим дефіцитом - а тепер я говорю про такий лічильник просто між справою). Прескалер можна налаштовувати будь-яке значення від 1 (тобто. на лічильник потрапить повна частота шини, 24МГц) до 65536 (тобто. 366 Гц).

Тактові сигнали, у свою чергу, збільшують внутрішній лічильник таймера, починаючи з нуля. Як тільки значення лічильника доходить до значення ARR – лічильник переповнюється, і виникає відповідна подія. Після настання цієї події таймер знову завантажує 0 в лічильник, і починає рахувати з нуля. Одночасно він може спричинити переривання (якщо воно налаштоване).

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

Код

Код буде схожий на попередній, т.к. ініціалізація всієї периферії відбувається однотипно - лише винятком, що таймер TIM6 висить на шині APB1. Тому включення таймера: RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);

Тепер заводимо структуру типу TIM_TimeBaseInitTypeDef, ініціалізуємо її (TIM_TimeBaseStructInit), налаштовуємо, передаємо її у функцію ініціалізації таймера (TIM_TimeBaseInit) і нарешті вмикаємо таймер (TIM_Cmd).

TIM_TimeBaseInitTypeDef TIM_InitStructure; // Заводимо структуру TIM_TimeBaseStructInit(&TIM_InitStructure); // Ініціалізація структури TIM_InitStructure.TIM_Prescaler = 24000; // Предділник TIM_InitStructure.TIM_Period = 1000; // Період таймера TIM_TimeBaseInit(TIM6, TIM_InitStructure); // Функція налаштування таймера TIM_Cmd (TIM6, ENABLE); // Увімкнення таймера

Що це за магічні числа? Як ми пам'ятаємо, на шині є тактова частота 24МГц (при наших налаштуваннях проекту). Налаштувавши таймера на 24000, ми поділимо цю частоту на 24 тисячі, і отримаємо 1кГц. Саме така частота потрапить на вхід лічильника таймера.

Значення ж у лічильнику - 1000. Отже, лічильник переповниться за 1000 тактів, тобто. рівно за 1 секунду.

Після цього у нас справді з'являється працюючий таймер. Але це ще не все.

10. Розберемося з перериваннями

Окей, переривання. Для мене колись (за часів PIC) вони були темним лісом, і я намагався взагалі їх не використовувати - та й не вмів, насправді. Однак у них укладено силу, ігнорувати яку взагалі негідно. Правда, переривання в STM32 - ще складніша штука, особливо механізм їх витіснення; але це пізніше.

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

  1. Включити взагалі переривання таймера TIM6;
  2. Включити переривання таймера TIM6 на переповнення лічильника;
  3. Написати процедуру-обробник переривання;
  4. Після обробки переривання скинути його.

Увімкнення переривань

Щиро кажучи, тут взагалі нічого складного. Насамперед включаємо переривання TIM6: NVIC_EnableIRQ(TIM6_DAC_IRQn); Чому така назва? Тому що в ядрі STM32 переривання від TIM6 і ЦАП мають однаковий номер. Не знаю, чому так зроблено - економія, брак номерів або просто якась спадкова штука - у будь-якому випадку, жодних проблем це не принесе, тому що в цьому проекті не використовується ЦАП. Навіть якщо в нашому проекті використовувався б ЦАП - ми могли б при вході в переривання дізнаватися, хто його викликав. Майже всі інші таймери мають одноосібне переривання.

Налаштування події-джерела переривань: TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE); - Включаємо переривання таймера TIM6 за подією TIM_DIER_UIE, тобто. подія оновлення значення ARR. Як ми пам'ятаємо з картинки, це відбувається одночасно з переповненням лічильника - так що це саме та подія, яка нам потрібна.

На даний момент код таймерних справ такий:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseInitTypeDef TIM_InitStructure; TIM_TimeBaseStructInit(&TIM_InitStructure); TIM_InitStructure.TIM_Prescaler = 24000; TIM_InitStructure.TIM_Period = 1000; TIM_TimeBaseInit(TIM6, &TIM_InitStructure); TIM_Cmd(TIM6, ENABLE); NVIC_EnableIRQ(TIM6_DAC_IRQn); TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE);

Обробка переривань

Зараз запускати проект не можна - перше ж переривання від таймера не знайде свій обробник, і контролер повисне (точніше, потрапить в обробник HARD_FAULT, що по суті те саме). Потрібно його написати.

Трохи теорії

Він повинен мати певне ім'я, void TIM6_DAC_IRQHandler(void). Це ім'я, так званий вектор переривання, описане у файлі startup (у нашому проекті це startup_stm32f10x_md_vl.s – можете самі побачити, 126 рядок). Насправді вектор - це адреса обробника переривання, і при виникненні переривання ядро ​​ARM лізе в початкову область (в яку трансльований файл startup - тобто його місцезнаходження задано абсолютно жорстко, на початку флеш-пам'яті), шукає там вектор і переходить у потрібне місце коду.

Перевірка події

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

У нашій програмі ця перевірка буде виглядати так: if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) - все зрозуміло, функція TIM_GetITStatus перевіряє наявність вказаної події у таймера і повертає 0 або 1.

Очищення прапора UIF

Другий крок – очищення прапора переривання. Поверніться до картинки: останній графік UIF це і є прапор переривання. Якщо його не очистити, наступне переривання не зможе викликатися, і контролер знову впаде в HARD_FAULT (та що таке!).

Виконання дій у перериванні

Просто перемикатимемо стан світлодіода, як і в першій програмі. Різниця в тому, що тепер наша програма робить це складніше! Насправді так писати набагато правильніше.

If(state) GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET); else GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET); state = 1 – state;

Використовуємо глобальну змінну int state = 0;

11. Весь код проекту з таймером

#include "stm32f10x_conf.h" int state=0; void TIM6_DAC_IRQHandler(void) ( if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) ( TIM_ClearITPendingBit(TIM6, TIM_IT_Update); if(state) = 1 - state;)) void main () (RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_StructInit (& GPIO_InitStructure); GPIO_InitStructure.GPIO_Speed ​​= GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_Init (GPIOC, & GPIO_InitStructure ); GPIO_WriteBit (GPIOC, GPIO_Pin_8, Bit_SET); RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseInitTypeDef TIM_InitStructure; TIM_TimeBaseStructInit (& TIM_InitStructure); TIM_InitStructure.TIM_Prescaler = 24000; TIM_InitStructure.TIM_Period = 1000; TIM_TimeBaseInit (TIM6, & TIM_InitStructure); TIM_Cmd (TIM6, ENABLE ), NVIC_EnableIRQ(TIM6_DAC_IRQn); TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE); while(1) ( ) )

Архів із проектом таймера.

Та й до речі, таймер вміє перемикати ногу і сам, без переривань та ручної обробки. Це буде наш третій проект.

Весь цикл:

1. Порти введення-виводу

2. Таймер та переривання

3. Виходи таймера

4. Зовнішні переривання та NVIC

5. Ставимо FreeRTOS

Post Views: 235