вівторок, 15 березня 2016 р.

Таймери STM32 -- автоматична зупинка/CMSIS

Цього разу -- зовсім коротка нотатка. Таймери володіють цікавою особливістю -- якщо встановити біт OPM в CR1, таймер автоматично зупинятиметься після події оновлення.

Важливе зауваження -- цей біт, OPM -- One-Pulse Mode, використовується для реалізації однойменного режиму One-pulse mode, який призначений керувати формою імпульсу -- генерувати його у відповідь на подію, із заданою затримкою після події та заданою довжиною. Про цей режим говоритимемо окремо. Цей пост -- своєрідна прелюдія до відповідної розмови -- ми просто навчимо таймер зупинятися. Така можливість теж корисна -- наприклад, для нашого далекоміра немає потреби робити якісь затримки, генерувати хитрі хвилі, абощо, але хотілося б, щоб таймер генерував один імпульс заданої довжини, і хотілося б, щоб він робив це повністю автоматично -- не використовуючи переривання та ручне вмикання-вимикання логічної одинички на піні.

Отож, беремо попередній пост, "Таймери STM32 -- ШІМ/CMSIS", додаємо до коду лише один рядок:

 TIM3->CR1  &= ~TIM_CR1_DIR;    // Відлік -- вверх. (Default)

/*==>*/ TIM3->CR1  |= TIM_CR1_OPM;         // One pulse mode
 TIM3->CR1  |= TIM_CR1_CEN;

Тепер, згенерувавши один період ШІМ (див. попередній пост за подробицями), таймер зупинятиметься. Фактично, ми отримуємо один імпульс заданої довжини. Якщо після імпульсу треба ще щось зробити, наприклад -- дочекатися результату далекоміра, можна зробити цикл, який перевірятиме стан біту CEN. Ну, до далекоміра ми ще повернемося, а поки -- приклад, мигаємо світлодіодом:

    while(1)
    {
     GPIOC->ODR^=(GPIO_Pin_9); // Зміною стану LED відмічаємо цикл
     delay_some_ms(5000000);
     TIM3->CR1  |= TIM_CR1_CEN;
    }

Дозволяючи таймер знову -- встановлюючи біт CEN, ми запускаємо генерацію чергового циклу ШІМ, який змусить світлодіод засвітитися, після чого таймер його погасить і знову зупиниться.

Важливе зауваження: таймер, звичайно, зупинятиметься, дорахувавши до значення в ARR, однак, перед тим переведе керовані канали у новий стан:

Поведінка каналу ШІМ за автоматичної зупинки в режимі PWM1 (на прикладі TIM1)

Поведінка каналу ШІМ за автоматичної зупинки в режимі PWM2 (на прикладі TIM1)
Про це слід пам'ятати! Трішки більше про наслідки цього буде мова, коли працюватимемо із далекоміром з допомогою таймерів.


main.c



#include <stm32f10x.h>

volatile uint32_t curTicks;

#ifdef __cplusplus
extern "C"{
#endif
void SysTick_Handler(void) {
 curTicks++;       /* Зроблено ще один тік */
}
#ifdef __cplusplus
}
#endif


#define MICROSECONDS_GRANULARITY 10
// Потрібна частота спрацювань таймера SysTick в герцах
#define FREQ ((1000000)/(MICROSECONDS_GRANULARITY))
#define TICKS ((SystemCoreClock)/(FREQ))

/*! Затримка в мікросекундах, грануляція -- microseconds_granularity мікросекунд,
 * одну не витягує сам контролер. 24МГц -- один такт це 42нс=0.042мкс */
inline static void delay_some_us(uint32_t mks)
{
 uint32_t ticks=mks/MICROSECONDS_GRANULARITY;
 int stop_ticks=ticks+curTicks;
 while (curTicks < stop_ticks) {};
}


int main(void)
{
 RCC->APB2ENR  |= RCC_APB2ENR_IOPCEN; // Дозволяємо тактування порту C із
       // світлодіодами плати STM32VLDiscovery
       // PC8 -- blue, PC9 -- green

 // TIM3 -- використовується в пості "Таймери STM32 -- Автоматична зупинка/CMSIS"
 RCC->APB1ENR  |= RCC_APB1Periph_TIM3; // Дозволяємо тактування TIM3
 
 // Порти світлодіодів - теж на вивід. Звертаємося до старших пінів, тому CRH -- High
 GPIOC->CRH &= ~GPIO_CRH_CNF8;
 GPIOC->CRH  |= GPIO_CRH_MODE8;
 GPIOC->CRH &= ~GPIO_CRH_CNF9;
 GPIOC->CRH  |= GPIO_CRH_MODE9;

 //-------------------------------------------------------------------------

 TIM3->PSC = 24000-1;   // Подільник частоти
 TIM3->ARR = 5000;              // Оновлюємо кожних XXX тіків

 TIM3->CCR2 = 3000;

 TIM3->CCER |= (TIM_CCER_CC2E); // Capture/Compare 2 output enable -- PA7
 TIM3->CCER &= ~TIM_CCER_CC2P;   // Полярність -- active high (Default)

 //TIM3->CCMR1|=(TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2); // PWM1 -- OCxM=0b110
 //TIM3->CCMR1&=~TIM_CCMR1_OC2M_0;
 TIM3->CCMR1|=(TIM_CCMR1_OC2M_0| TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2); // PWM2 -- OCxM=0b111

 TIM3->CCMR1&= ~TIM_CCMR1_CC2S; // Зануляємо -- на вивід. (Default)
 TIM3->CCMR1|= TIM_CCMR1_OC2PE; // Preload для CCR2, вимагається Datasheet для PWM

 TIM3->CR1  |= TIM_CR1_ARPE;    // Preload для ARR,  вимагається Datasheet для PWM
 TIM3->CR1  &= ~TIM_CR1_DIR;    // Відлік -- вверх. (Default)

 TIM3->CR1  |= TIM_CR1_OPM;  // One pulse mode
 TIM3->CR1  |= TIM_CR1_CEN;

 // Ініціалізуємо таймер SysTick
 if (SysTick_Config (TICKS))
 {
  // Помилка -- таймер не ініціалізувався
  GPIOC->BSRR = GPIO_BSRR_BS8; // Синім позначатимемо помилку
  while(1); // Зависаємо
 }

    while(1)
    {
     GPIOC->ODR^=(GPIO_Pin_9); // Зміною стану LED відмічаємо цикл
     delay_some_us(5000000/3);
     TIM3->CR1  |= TIM_CR1_CEN; // Запускаємо знову -- завдяки OPM він зупиниться
    }
}



Завершальне слово


Скачати проект із кодом для цього та двох попередніх постів ("Таймери STM32 -- зовнішнє тактування/CMSIS", "Таймери STM32 -- внутрішні тригери/CMSIS"), можна тут.

Код, який стосується лише цього посту, можна знайти у файлі main_OPM.c ,  в директорії app/ проекту (не включений у проект CoIDE -- щоб не конфліктувати із int main(), котрий містить все зразу).

Немає коментарів:

Дописати коментар