Stm32 таймери с общо предназначение. STM32 от нулата

Таймерите са такава периферия на контролера STM32, която ни позволява много точно да броим времевите интервали. Това е може би една от най-важните и най-използваните функции, но има и други. Трябва да започнем с факта, че в контролерите STM32 има таймери с различна степен на охлаждане. Най-простите са Основен таймери . Те са добри, защото са много лесни за конфигуриране и управление с помощта на минимум регистри. Всичко, което могат да направят, е да броят времеви интервали и да генерират, когато таймерът достигне дадена стойност. Следваща група ( таймери с общо предназначение ) са много по-готини от първите, могат да генерират ШИМ, могат да броят импулси, пристигащи на определени крака, можете да свържете енкодер и т.н. И най-готиният таймер е таймер с усъвършенствано управление , мисля, че няма да го използвам много дълго време, тъй като все още нямам нужда да управлявам трифазен електродвигател. Трябва да започнете да се запознавате с таймерите с нещо по-просто; реших да се справя с основните таймери. Задачата, която си поставих: Накарайте таймера да генерира прекъсвания всяка секунда.

Първо, ще отбележа, че основните таймери (TIM6 и TIM7) са свързани към шината APB1, така че ако честотата на тактовите импулси на нея се промени, таймерите ще започнат да тиктакат по-бързо или по-бавно. Ако не промените нищо в настройките на часовника и ги оставите по подразбиране, тогава честотата APB1е 24 MHz, при условие че външен кварц е свързан на честота 8 MHz. Като цяло, тактовата система на STM32 е много сложна и ще се опитам да напиша отделна публикация за нея правилно. Засега ще използваме само настройките на часовника, зададени от кода, автоматично генериран от CooCox. Струва си да започнете с най-важния регистър - TIMx_CNT(по-нататък x е номерът на основния таймер 6 или 7). Това е 16-битов регистър за броене, който се занимава директно с броенето на времето. Всеки път от автобуса APB1пристига тактов импулс, съдържанието на този регистър се увеличава с единица. Когато регистърът се препълни, всичко започва от нулата. При нашата честота на шината APB1 по подразбиране, таймерът ще тиктака 24 милиона пъти за една секунда! Това е много и затова таймерът има прескалер, който можем да контролираме с помощта на регистър TIMx_PSC. Като напишем стойността 24000-1 в него, ще задействаме регистъра за отчитане TIMx_CNTувеличава стойността си всяка милисекунда (Честота APB1разделете на числото в регистъра на предскалера и получете колко пъти в секунда се увеличава броячът). Единицата трябва да се извади, защото ако в регистъра има нула, това означава, че делителят на единица е активиран. Сега, когато броячът достигне 1000, определено можем да кажем, че е изминала точно една секунда! Защо сега да анкетирате регистъра за преброяване и да чакате, докато там се появи 1000? Това не е нашият метод, защото можем да използваме ! Но проблемът е, че имаме само едно прекъсване и то се случва, когато броячът отиде на нула. За да може броячът да се нулира предсрочно, а не когато достигне 0xFFFF, се използва регистърът TIMx_ARR. Записваме в него числото, до което регистърът трябва да брои TIMx_CNTпреди да отиде на нула. Ако искаме прекъсването да се случва веднъж в секунда, тогава трябва да напишем 1000 по отношение на директното време, това е всичко, но самият таймер няма да започне да тиктака. Трябва да се активира чрез настройка на бита CENв регистъра TIMx_CR1. Този бит позволява обратното броене да започне, така че ако се нулира, обратното броене ще спре (вашият C.O.). Има и други битове в регистъра, но те не са особено интересни за нас. Но ние се интересуваме от още един бит, но вече в регистъра TIMx_DIER. Нарича се UIE,Като го зададем, позволяваме на таймера да генерира прекъсвания, когато регистърът за броене се нулира. Това е всичко, дори не е по-сложно, отколкото в някои AVR. И така, малко обобщение: За да използвате основния таймер, имате нужда от:

  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; //Активиране на порт C и таймер 6 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); ClockCmd( RCC_APB1Periph_TIM6, ENABLE) ; // Настройка на краката с изхода PORT.GPIO_Pin_8; PORT.GPIO_Mode_Out_PP; PORT.GPIO_Speed ​​​​= GPIO_Speed_2MHz; // Така че прекъсването се случва веднъж в секунда TIM_DIER_UIE; // Разрешаване на прекъсване от таймера TIM_CR1_CEN; // Разрешаване на прекъсване на TIM6_DAC_IRQn (1) ( //Програмата не прави нищо в празен цикъл) ) // TIM6_DAC манипулатор на прекъсвания void TIM6_DAC_IRQHandler(void) ( TIM6->SR &= ~TIM_SR_UIF; // Нулирайте флага UIF GPIOC->ODR^=(GPIO_Pin_9 |. GPIO_Pin_8); //Инвертиране на състоянието на светодиодите)

Струва си да добавите малка бележка към манипулатора на прекъсванията. Факт е, че той се използва от два периферни блока наведнъж: таймер 6 и DAC. Това означава, че ако напишете програма, която позволява прекъсвания от двете периферни устройства, тогава в тялото на манипулатора трябва да проверите кое от тях е причинило прекъсването. В нашия случай не направих това, тъй като не могат да възникнат прекъсвания от DAC. Не е конфигуриран и прекъсванията са деактивирани по подразбиране. Следващият път ще разгледаме таймерите с общо предназначение и техните практически приложения.

Вече разгледахме таймера SysTick, който е част от ядрото Cortex. Това обаче не свършва дотук. Има много таймери в stm32 и те са различни. В зависимост от целта ще трябва да изберете един или друг таймер:

  • SysTick;
  • таймери с общо предназначение - TIM2, TIM3, TIM4, TIM15, TIM16, TIM17;
  • разширен таймер - TIM1;
  • таймер за наблюдение.

Единственото нещо, което си струва да се спомене за последното, е, че е проектирано да контролира спиранията на системата и е таймер, който трябва да се нулира периодично. Ако таймерът не бъде нулиран в рамките на определен период от време, таймерът за наблюдение ще рестартира системата (микроконтролера).

Таймерите се предлагат в различни битови размери: например SysTick е 24-битов, а всички таймери, които имаме в камъка, са 16-битови (т.е. те могат да броят до 2 16 = 65535), с изключение на WatchDog. Освен това всеки таймер има определен брой канали, т.е. всъщност може да работи за двама, три и т.н. Тези таймери могат да работят с инкрементални енкодери, сензори на Хол и могат да генерират PWM (импулсна модулация, английски) .импулсна модулация - за която ще говорим по-късно) и много повече. В допълнение, те могат да генерират прекъсвания или да правят заявки към други модули (например към DMA - Директен достъп до паметта) за различни събития:

  • преливане;
  • улавяне на сигнал (англ. input capture);
  • сравнение (англ. output compere);
  • тригер за събитие.

Ако всичко ни е ясно с препълване на таймера (по-точно достигане на „0“) - разгледахме SysTick - тогава все още не сме запознати с други възможни режими на работа. Нека ги разгледаме по-отблизо.

Улавяне на сигнал

Този режим е много подходящ за измерване на периода на повторение на импулса (или броя на импулсите, да речем, в секунда). Когато на изхода MK пристигне импулс, таймерът генерира прекъсване, от което можем да премахнем текущата стойност на брояча (от регистъра TIM_CCRx, където x е номерът на канала) и да я запазим във външна променлива. След това ще дойде следващият импулс и чрез просто изваждане ще получим „времето“ между два импулса. Можете да уловите както предния, така и задния фронт на импулса или дори и двата наведнъж. Защо е необходимо това? Да кажем, че имате магнит, който сте залепили към джанта на колело, и сензор на Хол към вилицата на велосипед. След това, когато завъртите колелото, ще получавате импулс всеки път, когато магнитът на колелото е в същата равнина като сензора на Хол. Знаейки разстоянието, което магнитът изминава за оборот и времето, можете да изчислите скоростта на движение.

Има и режим на улавяне на ШИМ, но това е по-скоро специален начин за настройка на таймера, отколкото отделен режим на работа: единият канал улавя нарастващите ръбове, а вторият - падащите ръбове. Тогава първият канал засича периода, а вторият - пълнежа.

Режим на сравнение

В този режим избраният канал на таймера е свързан към съответния щифт и след като таймерът достигне определена стойност, състоянието на щифта ще се промени в зависимост от настройката на режима (може да бъде "1" или "0", или изходното състояние е просто обърнато).

Режим на генериране на ШИМ

Както подсказва името, таймерът в този режим генерира модулация на ширината на импулса. Ще говорим повече за този режим, както и къде може/трябва да се използва, в следващия урок след преглед на улавянето на сигнал.

Използвайки усъвършенстван таймер, можете да генерирате трифазен PWM, който е много полезен за управление на трифазен двигател.

Режим на мъртво време

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

В курса ще използваме само „улавяне на сигнал“ и „генериране на ШИМ“.

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

За всеки микроконтролер таймерът е един от най-важните компоненти, който ви позволява много точно да броите времеви интервали, да броите импулси, пристигащи на входовете, да генерирате вътрешни прекъсвания, да генерирате сигнали с модулация на ширината на импулса (PWM) и да поддържате директен достъп до паметта ( DMA) процеси.

Микроконтролерът STM32 съдържа няколко вида таймери, които се различават един от друг по функционалност. Първият тип таймери е най-простият и е Основни таймери. Таймерите TIM6 и TIM7 принадлежат към този тип. Тези таймери са много лесни за конфигуриране и управление с помощта на минимум регистри. Те са способни да отчитат времеви интервали и да генерират прекъсвания, когато таймерът достигне определена стойност.
Вторият тип са таймери с общо предназначение. Това включва таймери TIM2 до TIM5 и таймери TIM12 до TIM17. Те могат да генерират ШИМ, да броят импулси, пристигащи на определени изводи на микроконтролера, да обработват сигнали от енкодера и т.н.

Третият тип дефинира таймери с разширен контрол (Advanced-Control Timer). Този тип включва таймера TIM1, който е в състояние да изпълнява всички горепосочени операции. Освен това, въз основа на този таймер, можете да изградите устройство, способно да управлява трифазно електрическо задвижване.

Основно устройство с таймер

Нека разгледаме дизайна и работата на основен таймер, чиято блокова схема е показана на фигурата. Основният таймер е изграден на базата на 16-битови регистри. Неговата основа е броячният регистър TIMx_CNT. (По-нататък символът “x” замества числото 6 или 7 за основните таймери TIM6 и TIM7, съответно.) TIMx_PSC prescaler ви позволява да регулирате тактовата честота за регистъра на брояча, а TIMx_ARR регистърът за автоматично зареждане ви позволява да зададете обхватът на отчитане на таймера. Контролерът за задействане и синхронизация, заедно с регистрите за управление и състояние, служат за организиране на режима на работа на таймера и ви позволяват да контролирате неговата работа.

Благодарение на организацията си, броячът на таймера може да брои напред и назад, както и до средата на даден диапазон в права посока и след това в обратна посока. Входът на основния таймер може да бъде доставен от няколко източника, включително часовников сигнал от шината APB1, външен сигнал или изход на други таймери, приложени към щифтовете за улавяне и сравнение. Таймерите TIM6 и TIM7 се тактират от шината APB1. Ако използвате 8 MHz кристал и фабрични настройки на часовника по подразбиране, тактовата честота от тактовата шина APB1 ще бъде 24 MHz.

Основни регистри на таймера

Таблицата показва картата на регистъра за основните таймери TIM6 и TIM7. Основните таймери включват следните 8 регистъра:

●● TIMx_CNT – Брояч (преброяващ регистър);
●● TIMx_PSC – Prescaler (прескалер);
●● TIMx_ARR – Регистър за автоматично презареждане;
●● TIMx_CR1 – Контролен регистър 1 (контролен регистър 1);
●● TIMx_CR2 – Контролен регистър 2 (контролен регистър 2);
●● TIMx_DIER – DMA регистър за разрешаване на прекъсвания (DAP и регистър за разрешаване на прекъсвания);
●● TIMx_SR – Регистър на състоянието;
●● TIMx_EGR – Регистър за генериране на събития.

Регистрите TIMx_CNT, TIMx_PSC и TIMx_ARR използват 16 информационни бита и ви позволяват да записвате стойности от 0 до 65535. Честотата на тактовите импулси за регистъра на брояча TIMx_CNT, преминавайки през делителя TIMx_PSC, се изчислява по формулата: Fcnt = Fin /(PSC + 1), където Fcnt е честотата на импулсите на регистъра на брояча на таймера; Fin – тактова честота; PSC – съдържанието на таймерния регистър TIMx_PSC, който определя коефициента на разделяне. Ако запишете стойността 23999 в регистъра TIMx_PSC, тогава регистърът на брояча TIMx_CNT при тактова честота от 24 MHz ще промени стойността си 1000 пъти в секунда. Регистърът за автоматично зареждане съхранява стойността за зареждане на регистъра на брояча TIMx_CNT. Съдържанието на регистъра TIMx_CNT се актуализира, след като бъде препълнен или нулиран, в зависимост от указаната за него посока на броене. Контролният регистър TIMх_CR1 има няколко контролни бита. ARPE битът разрешава и забранява буферирането на записи в регистъра за автоматично зареждане TIMx_ARR. Ако този бит е нула, тогава, когато нова стойност бъде записана в TIMx_ARR, тя ще бъде заредена в него веднага. Ако битът ARPE е равен на единица, тогава зареждането в регистъра ще се извърши след събитието регистърът за броене да достигне граничната стойност. OPM разрядът позволява режим „единичен импулс“. Ако е зададено, след препълване на регистъра на брояча, броенето спира и битът CEN се нулира. Битът UDIS разрешава и забранява генерирането на събитие на таймера. Ако е изчистено, събитието ще се генерира, когато настъпи условието за генериране на събитието, тоест, когато таймерът препълни или когато битът UG е програмиран в регистъра TIMx_ EGR. Битът CEN включва и изключва таймера. Ако нулирате този бит, броенето ще бъде спряно и когато е зададено, броенето ще продължи. Входният делител ще започне да брои от нула. Контролният регистър TIMx_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 prescaler If(t = = 0) TIM7->PSC = FAPB1 /1000000-1; // за броене на микросекунди If(t = = 1) TIM7->PSC = FAPB1/1000-1; // за броене на милисекунди TIM7->ARR = n; // Зареждане на броя проби в автоматичното зареждане регистър ARR TIM7 ->EGR |= TIM_EGR_UG; // Генериране на събитие за актуализиране // за запис на данни в регистрите PSC и ARR TIM7->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; // Стартиране на таймера // чрез запис на бита за разрешаване на брояча CEN // и битът на режима предава OPM към контролния регистър CR1 докато (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 =~ TIM_SR_UIF ; // Нулиране на флага // Извършване на операции ) )

Нека разгледаме пример за програма за организиране на забавяне на основен таймер TIM6, който използва прекъсвания от таймер. За да контролираме изпълнението на програмата, използваме един от щифтовете на микроконтролера, за да управляваме светодиодните индикатори, които ще трябва да превключват на интервали, определени от забавянето на програмата, организирано на таймера TIM6. Пример за такава програма е показан в списък 2.

Списък 2

// Включване на библиотеки #include #включи #включи #включи #включи // Присвояване на щифтове за LED индикатори enum ( LED1 = GPIO_Pin_8, LED2 = GPIO_Pin_9 ); // Функция за инициализиране на портове за управление на светодиоди void init_leds() ( RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef gpio; GPIO_StructInit(&gpio); gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Pin = LED1 | GPIO_; Init(GPIOC, &gpio); /TIM6 функция за инициализация на таймера void init_timer_TIM6() ( // Разрешаване на часовника на таймера RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseInitTypeDef base_timer; TIM_TimeBaseStructInit(&base_timer); // Задаване на делителя на 23999 base_timer.TIM_Prescale r = 24000 - 1; // Задайте период до 500 ms base_timer.TIM_TimeBaseInit(TIM6, &base_timer); // Активиране на прекъсване на таймера за препълване TIM_IT_Update, ENABLE; // Активиране на прекъсване на таймера за препълване. Функция за обработка на прекъсване на таймера void TIM6_DAC_IRQHandler())( // Ако възникне прекъсване поради препълване на брояча на таймера на TIM6 if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) ( // Нулирайте бита на обработеното прекъсване 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 има една функция, свързана с факта, че векторът за обработка на прекъсване за този таймер се комбинира с прекъсване от цифрово-аналогов преобразувател (DAC). Следователно функцията за обработка на прекъсване проверява източника на прекъсването. Можете да научите повече за таймерите на микроконтролера STM32 на уебсайта St.com. Има много други задачи за таймера, описани по-горе, които той може успешно да реши. Следователно използването му в програма значително облекчава натоварването на процесора и прави програмата по-ефективна.

Всеки съвременен контролер има таймери. Тази статия ще говори за прости (основни) таймери stm32f4 откриване.
Това са обикновени таймери. Те са 16 битови с автоматично рестартиране. Освен това има 16-битов програмируем честотен делител. Възможно е генериране прекъсвания при препълване на броячаи/или DMA заявка.

Нека започваме. Както и преди, използвам Eclipse + st-util в ubuntu linux

Първо свързваме заглавките:

#включи #включи #включи #включи #включи

В това няма нищо ново. Ако не е ясно откъде идват, или прочетете предишни статии, или отворете файла и прочетете.

Нека дефинираме две константи. Единият за обозначаване на диоди, другият масив от същите диоди:

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); // попълване със стандартни стойности gpio.GPIO_OType = GPIO_OType_PP; // изтегляне с резистори 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 base_timer; TIM_TimeBaseStructInit(&base_timer); / * Делителят е взети под внимание като TIM_Prescaler + 1, следователно извадете 1 */ base_timer.TIM_Prescaler = 24000 - 1; // делител 24000 base_timer.TIM_Period = 1000; // период от 1000 импулса TIM_TimeBaseInit(TIM6, &base_timer); /* Разрешаване на прекъсване за актуализиране (в този случай - за препълване) TIM6 таймер */ TIM_ITConfig(TIM_IT_Update, ENABLE); // Разрешаване на таймера /* Разрешаване на препълване на таймера TIM6 също е отговорен за изпразването на DAC NVIC_EnableIRQ(TIM6_DAC_IRQn);

Коментирах кода, така че мисля, че всичко е ясно.
Ключовите параметри тук са делителя (TIM_Prescaler) и периода (TIM_Period) на таймера. Това са параметрите, които всъщност конфигурират работата на таймера.

Например, ако имате тактова честота, зададена на 48 MHz на STM32F4 DISCOVERY, тогава честотата на таймерите с общо предназначение е 24 MHz. Ако зададете делителя (TIM_Prescaler) на 24000 (честота на броене = 24MHz/24000 = 1KHz) и периода (TIM_Period) на 1000, тогава таймерът ще брои интервала в 1s.

Моля, обърнете внимание, че всичко зависи от тактовата честота. Трябва да разберете точно.

Също така отбелязвам, че при високи честоти превключването на светодиода чрез прекъсване значително изкривява стойността на честотата. При стойност 1 MHz на изхода получих приблизително 250 KHz, т.е. разликата е неприемлива. Този резултат очевидно се получава поради времето, изразходвано за изпълнение на прекъсването.

Глобална променлива - светещ диоден флаг:

U16 флаг = 0;

Манипулаторът на прекъсване, който генерира таймера. защото Същото прекъсване се генерира, когато DAC работи, първо проверяваме дали е задействано от таймера:

Void TIM6_DAC_IRQHandler())( /* Тъй като този манипулатор се извиква и за DAC, е необходимо да се провери * дали е възникнало прекъсване при препълване на брояча на таймера TIM6. */ if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) ( флаг++; if (флаг> 3) флаг = 0; /* Изчистване на обработвания бит */ TIM_ClearITPendingBit(TIM6, TIM_IT_Update); GPIO_Write(GPIOD, LED);

основна функция:

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

Оставяме цикъла празен. Броячът изпълнява работата си асинхронно, а прекъсването е прекъсване, за да не зависи от изпълняваната в момента операция.

STM32 има много много удобни и гъвкави таймери. Дори най-младият микроконтролер (STM32F030F4P6) има 4 такива таймера.

8. Настройте проекта - добавете необходимите файлове

За да използваме таймера, ще трябва да включим файла на периферната библиотека stm32f10x_tim.c. По същия начин щракнете с десния бутон в работното пространство (прозорец вляво) върху групата StdPeriphLib, Добавяне –> Добавяне на файлове, файл LibrariesSTM32F10x_StdPeriph_Driversrcstm32f10x_tim.c.

Трябва също да разрешите използването на заглавка за този файл. Отворете stm32f10x_conf.h (щракнете с десния бутон върху името на този файл в кода, „Open stm32f10x_conf.h“. Разкоментирайте реда #include „stm32f10x_tim.h“.

9. Добавете таймер

Забавяне с празен цикъл е богохулство, особено на такъв мощен кристал като STM32, с куп таймери. Следователно ще направим това забавяне с помощта на таймер.

STM32 има различни таймери с различни набори от свойства. Най-простите са основните таймери, по-сложните са таймерите с общо предназначение, а най-сложните са таймерите за напреднали. Простите таймери са ограничени до просто броене на часовниковите цикли. При по-сложните таймери се появява ШИМ. Най-сложните таймери, например, могат да генерират 3-фазна ШИМ с предни и обратни изходи и мъртво време. Един обикновен таймер, номер 6, ще ни е достатъчен.

Малко теория

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

Сигналите на часовника от своя страна увеличават брояча на вътрешния таймер, започвайки от нула. Веднага след като стойността на брояча достигне стойността на 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; // Prescaler TIM_InitStructure.TIM_Period = 1000; // Период на таймера TIM_TimeBaseInit(TIM6, &TIM_InitStructure); // Функция за настройка на таймера TIM_Cmd(TIM6, ENABLE); // Включете таймера

Кои са магическите числа? Както си спомняме, шината има тактова честота от 24 MHz (с нашите настройки на проекта). Като настроим прескалера на таймера на 24000, разделяме тази честота на 24 хиляди и получаваме 1 kHz. Това е честотата, която ще отиде на входа на брояча на таймера.

Стойността в брояча е 1000. Това означава, че броячът ще препълни след 1000 тактови цикъла, т.е. точно за 1 секунда.

След това всъщност имаме работещ таймер. Но това не е всичко.

10. Да се ​​справим с прекъсванията

Добре, прекъсвания. За мен някога (по времето на PIC) те бяха тъмна гора и се опитах да не ги използвам изобщо - и всъщност не знаех как. Те обаче съдържат сила, която е напълно недостойна да бъде игнорирана. Вярно е, че прекъсванията в STM32 са още по-сложно нещо, особено механизмът за тяхното изместване; но повече за това по-късно.

Както отбелязахме по-рано, таймерът генерира прекъсване, когато броячът препълни - ако обработката на прекъсване за това устройство изобщо е разрешена, това конкретно прекъсване е разрешено и предишното се нулира. Анализирайки тази фраза, разбираме от какво се нуждаем:

  1. Активирайте прекъсванията на таймера TIM6 като цяло;
  2. Активиране на прекъсване на таймера TIM6 за препълване на брояча;
  3. Напишете процедура за обработка на прекъсвания;
  4. След обработка на прекъсването го нулирайте.

Разрешаване на прекъсвания

Честно казано, тук няма нищо сложно. Първо, активирайте TIM6 прекъсванията: NVIC_EnableIRQ(TIM6_DAC_IRQn); Защо това име? Защото в ядрото STM32 прекъсванията от TIM6 и от DAC са с еднакъв брой. Не знам защо беше направено това - спестявания, липса на номера или просто някакво наследство - във всеки случай това няма да създаде проблеми, защото този проект не използва DAC. Дори нашият проект да използва DAC, бихме могли да разберем кой точно го е извикал при влизане в прекъсването. Почти всички други таймери имат едно прекъсване.

Конфигуриране на събитието източник на прекъсване: TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE); - активирайте прекъсването на таймера TIM6 въз основа на събитието TIM_DIER_UIE, т.е. Събитие за актуализиране на стойността на ARR. Както помним от снимката, това се случва едновременно с препълването на брояча - така че това е точно събитието, от което се нуждаем.

В момента кодът за случаи на таймер е следният:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, РАЗРЕШАВАНЕ); 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_stm32f10x_md_vl.s - можете да видите сами, ред 126). Всъщност векторът е адресът на манипулатора на прекъсване и когато възникне прекъсване, ARM ядрото се изкачва в началната област (към която се превежда стартовият файл - т.е. местоположението му е зададено напълно твърдо, в самото начало на флаш памет), търси вектора там и отива на правилното място в кода.

Проверка на събитието

Първото нещо, което трябва да направим, когато въвеждаме такъв манипулатор, е да проверим кое събитие е причинило прекъсването. Сега имаме само едно събитие, но в реален проект може да има няколко събития на един таймер. Затова проверяваме събитието и изпълняваме съответния код.

В нашата програма тази проверка ще изглежда така: ако (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) - всичко е ясно, функцията TIM_GetITStatus проверява за наличието на определеното събитие в таймера и връща 0 или 1.

Изчистване на UIF флага

Втората стъпка е да изчистите флага за прекъсване. Върнете се към картината: последната UIF графика е флагът за прекъсване. Ако не бъде изчистено, следващото прекъсване няма да бъде извикано и контролерът отново ще изпадне в HARD_FAULT (какво е това!).

Изпълнение на действия при прекъсване

Просто ще превключим състоянието на светодиода, както в първата програма. Разликата е, че сега нашата програма го прави по-трудно! Всъщност много по-правилно е да се пише така.

If(състояние) GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET); иначе GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_RESET); състояние = 1 - състояние;

Използваме глобалната променлива int state=0;

11. Целият код на проекта с таймер

#include "stm32f10x_conf.h" int състояние=0; void TIM6_DAC_IRQHandler(void) ( if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) ( TIM_ClearITPendingBit(TIM6, TIM_IT_Update); if(state) GPIO_WriteBit(GPIOC, GPIO_Pin_8, Bit_SET); else GPIO_WriteBit(GPIOC, GPIO_ Pin_8, Bit_RESET); = 1 - състояние; ) ) void main() ( RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_StructInit(&GPIO_InitStructure); GPIO_InitStructure.GPIO_Speed ​​​​= GPIO_Speed_2MHz; GPIO_ InitS structure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure(GPIO_Pin_8, Bit_SET); r = 24000; TIM_TimeBaseInit(TIM6, &TIM_InitStructure); IC_EnableIRQ(TIM6_DAC_IRQn); TIM_ITConfig(TIM6, TIM_DIER_UIE, ENABLE );

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

Е, между другото, таймерът може сам да превключи крака, без прекъсвания или ръчна обработка. Това ще бъде нашият трети проект.

Целият цикъл:

1. I/O портове

2. Таймер и прекъсвания

3. Изходи на таймера

4. Външни прекъсвания и NVIC

5. Инсталирайте FreeRTOS

Преглеждания на публикация: 235

Споделете