Черговий пост в серії -- одноімпульсний режим, OPM, із використанням STM32CubeMX/HAL. Вирішимо тих же дві задачі -- реакція на кнопку, із затримкою та реакція на внутрішній тригер, яким служить подія переповнення іншого таймера.
Запуск OPM кнопкою
Почнемо із візуальної конфігурації:
Клікабельно! Знадобиться, щоб роздивитися конфігурацію таймера. |
- Піни PC8 i PC9, на яких знаходяться світлодіоди нашої жертви, плати STM32VLDiscovery -- на вивід
- До PA1 під'єднуємо світлодіод -- його ввімкнення буде візуалізувати саму подію. Піном керуватиме таймер, тому режим -- TIM2_CH2, другий канал TIM2.
- PA0 зайнятий кнопкою, він на першому каналі таймера -- TIM2_CH1.
- Назви пінів, нагадаємо, можна задати на вкладці Configure->GPIO.
- Тактування від внутрішнього джерела -- відлік іде незалежно від натискань кнопки
- Запуск відбувається за тригером -- Trigger mode, а тригером служить TI1FP1 -- "фільтрований вхід тригера - 1", іншими словами -- тригером буде зміна сигналу на піні PA0. Тобто, натискання кнопки запускатиме таймер
- Галочка One Pulse Mode змушує таймер автоматично зупинятися по переповненню лічильника -- досягненню ним значення, збереженого в регістрі ARR.
- Канал 2 використовується для генерації ШІМ. В такому режимі, правда, буде генеруватися лише один його період за запуск.
Вони вже мають бути звичними, тому зверну увагу лише на декілька ключових елементів:
- Тригер на каналі 1 спрацьовуватиме по фронту сигналу
- Так як ми хочемо, щоб світлодіод загорався із затримкою, стоїть режим PWM2 та полярність HIGH -- в цьому режимі PWM, вивід спочатку не активний, потім, по досягненню CNT значення в CCR1, стає активним, а HIGH каже, що активний рівень -- це високий, логічна одиничка
Можна генерувати код та дописувати логіку роботи програми.
Нагадаю, переривання в цій задачі ми використовуємо лише для візуалізації процесу. Відповідні функції зворотного виклику виглядатимуть так:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if( htim->Instance == TIM2 ) { // Оновлення підлеглого таймера, після якого він зупиниться // завдяки OPM біту HAL_GPIO_WritePin(GREEN_LED_PC9_GPIO_Port, GREEN_LED_PC9_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(BLUE_LED_PC8_GPIO_Port, BLUE_LED_PC8_Pin, GPIO_PIN_SET); // Вимикаємо зелений коли закінчили імпульс // При тому вмикаємо синій -- свідчить про // очікування на наступну подію } } void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim) { if( htim->Instance == TIM2 ) { // Для спрощення, можливість overcapture ігноруємо if( htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2 ) // Capture-compare каналу 2 відбулося { HAL_GPIO_WritePin(GREEN_LED_PC9_GPIO_Port, GREEN_LED_PC9_Pin, GPIO_PIN_RESET); // Вимикаємо зелений, коли підлеглий таймер // дорахував до значення в CCR2 // буде світитися світлодіод, керований таймером -- на PA7 } } } void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim) { if( htim->Instance == TIM2 ) { HAL_GPIO_WritePin(GREEN_LED_PC9_GPIO_Port, GREEN_LED_PC9_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(BLUE_LED_PC8_GPIO_Port, BLUE_LED_PC8_Pin, GPIO_PIN_RESET); // Гасимо синій та // вмикаємо зелений // коли підлеглий таймер почав відлік } }
Всі використані елементи та логіка роботи вже знайома з попередніх постів.
Функція main(), фактично, сама програма, проста настільки, що наведу її цілу:
Єдина "новинка" -- таймер запускаємо функцією HAL_TIM_OnePulse_Start_IT() (перед тим дозволивши потрібні нам переривання). Зауважте -- якщо б не візуалізація, то це була б вся програма! Цю функцію та ініціалізацію розглянемо пізніше, а поки перейдемо до суміжної задачі -- запуску OPM від внутрішньої події.
Функція main(), фактично, сама програма, проста настільки, що наведу її цілу:
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration----------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM2_Init(); /* USER CODE BEGIN 2 */ __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE); __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_TRIGGER); HAL_TIM_OnePulse_Start_IT(&htim2, TIM_CHANNEL_2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
Єдина "новинка" -- таймер запускаємо функцією HAL_TIM_OnePulse_Start_IT() (перед тим дозволивши потрібні нам переривання). Зауважте -- якщо б не візуалізація, то це була б вся програма! Цю функцію та ініціалізацію розглянемо пізніше, а поки перейдемо до суміжної задачі -- запуску OPM від внутрішньої події.
Запуск OPM іншим таймером
Тепер нам знадобиться ще один таймер -- TIM1, зате не знадобиться кнопка та "її" канал CH1 таймера TIM2. Конфігуруємо:Клікабельно! |
- TIM1 просто ввімкнено -- тактується від внутрішнього джерела
- Конфігурація TIM2 змінилася мало, найважливіша зміна -- тригером служить внутрішнє джерело, ITR0, яке, як ми вже знаємо, пов'язане із TRGO TIM1. Також став не потрібен його перший канал.
В конфігурації TIM1, на вкладці Configure, слід налаштувати генерування сигналу TRGO за події оновлення:
Конфігурація для TIM2 відрізняється від попереднього прикладу, із кнопкою, хіба відсутністю налаштувань першого каналу:
Щодо коду, обробники переривань -- ті ж, що в попередньому прикладі, адже суть візуалізації не змінилася. main() теж простий:
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration----------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM1_Init(); MX_TIM2_Init(); /* USER CODE BEGIN 2 */ __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE); __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_TRIGGER); HAL_TIM_OnePulse_Start_IT(&htim2, TIM_CHANNEL_2); HAL_TIM_Base_Start(&htim1); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
Дозволивши всі необхідні переривання, запускаємо підлеглий TIM2 за допомогою HAL_TIM_OnePulse_Start_IT(), після чого запускаємо TIM1 звичною HAL_TIM_Base_Start().
Перейдемо до розгляду ініціалізації та HAL_TIM_OnePulse_Start_IT().
Ініціалізація -- запуск OPM кнопкою
/* TIM2 init function */ void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig; TIM_SlaveConfigTypeDef sSlaveConfig; TIM_MasterConfigTypeDef sMasterConfig; TIM_OC_InitTypeDef sConfigOC; htim2.Instance = TIM2; htim2.Init.Prescaler = 23999; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 7000; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&htim2); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig); HAL_TIM_PWM_Init(&htim2); HAL_TIM_OnePulse_Init(&htim2, TIM_OPMODE_SINGLE); sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER; sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING; sSlaveConfig.TriggerFilter = 15; HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); sConfigOC.OCMode = TIM_OCMODE_PWM2; sConfigOC.Pulse = 2000; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2); }
В принципі, всі елементи цієї функції, крім HAL_TIM_OnePulse_Init(), ми вже розглядали в інших постах, немає сенсу заглиблюватися, хіба що перерахуємо:
- Виконується базова ініціалізація, HAL_TIM_Base_Init()
- Вибирається внутрішнє тактування, HAL_TIM_ConfigClockSource()
- Ініціалізується використання ШІМ для таймера, HAL_TIM_PWM_Init()
- Ініціалізується одноімпульсний режим, HAL_TIM_OnePulse_Init() -- цю функцію розглянемо окремо
- Вказується не генерувати TGRO, HAL_TIMEx_MasterConfigSynchronization()
- Ініціалізується ШІМ для каналу 2, HAL_TIM_PWM_ConfigChannel()
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) { GPIO_InitTypeDef GPIO_InitStruct; if(htim_base->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* Peripheral clock enable */ __TIM2_CLK_ENABLE(); /**TIM2 GPIO Configuration PA0-WKUP ------> TIM2_CH1 PA1 ------> TIM2_CH2 */ GPIO_InitStruct.Pin = USER_BTN_PA0_TIM2CH1_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(USER_BTN_PA0_TIM2CH1_GPIO_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = OUTPUT_PA1_TIM2CH2_Pin; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_LOW; HAL_GPIO_Init(OUTPUT_PA1_TIM2CH2_GPIO_Port, &GPIO_InitStruct); /* Peripheral interrupt init*/ HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); /* USER CODE BEGIN TIM2_MspInit 1 */ /* USER CODE END TIM2_MspInit 1 */ } }
Ініціалізація -- запуск OPM іншим таймером
Для повноти, наведу і цей код, але особливих нюансів у ньому немає:/* TIM1 init function */ void MX_TIM1_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; htim1.Instance = TIM1; htim1.Init.Prescaler = 23999; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 10000; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; HAL_TIM_Base_Init(&htim1); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig); sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE; HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig); } /* TIM2 init function */ void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig; TIM_SlaveConfigTypeDef sSlaveConfig; TIM_MasterConfigTypeDef sMasterConfig; TIM_OC_InitTypeDef sConfigOC; htim2.Instance = TIM2; htim2.Init.Prescaler = 23999; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 7000; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&htim2); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig); HAL_TIM_PWM_Init(&htim2); HAL_TIM_OnePulse_Init(&htim2, TIM_OPMODE_SINGLE); sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER; sSlaveConfig.InputTrigger = TIM_TS_ITR0; HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); sConfigOC.OCMode = TIM_OCMODE_PWM2; sConfigOC.Pulse = 2000; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2); } void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) { GPIO_InitTypeDef GPIO_InitStruct; if(htim_base->Instance==TIM1) { /* USER CODE BEGIN TIM1_MspInit 0 */ /* USER CODE END TIM1_MspInit 0 */ /* Peripheral clock enable */ __TIM1_CLK_ENABLE(); /* USER CODE BEGIN TIM1_MspInit 1 */ /* USER CODE END TIM1_MspInit 1 */ } else if(htim_base->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 */ /* USER CODE END TIM2_MspInit 0 */ /* Peripheral clock enable */ __TIM2_CLK_ENABLE(); /**TIM2 GPIO Configuration PA1 ------> TIM2_CH2 */ GPIO_InitStruct.Pin = GPIO_PIN_1; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* Peripheral interrupt init*/ HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); /* USER CODE BEGIN TIM2_MspInit 1 */ /* USER CODE END TIM2_MspInit 1 */ } }
Залишається розглянути HAL_TIM_OnePulse_Start_IT()
HAL_TIM_OnePulse_Start_IT()
Це -- ще одна функція із сімейства різних засобів запуску таймерів (думаю, в чому її відмінність від HAL_TIM_OnePulse_Start() та HAL_TIM_OnePulse_Start_DMA() мало б вже бути зрозумілим):HAL_StatusTypeDef HAL_TIM_OnePulse_Start_IT(TIM_HandleTypeDef *htim, uint32_t OutputChannel) { /* Enable the Capture compare and the Input Capture channels (in the OPM Mode the two possible channels that can be used are TIM_CHANNEL_1 and TIM_CHANNEL_2) if TIM_CHANNEL_1 is used as output, the TIM_CHANNEL_2 will be used as input and if TIM_CHANNEL_1 is used as input, the TIM_CHANNEL_2 will be used as output in all combinations, the TIM_CHANNEL_1 and TIM_CHANNEL_2 should be enabled together No need to enable the counter, it's enabled automatically by hardware (the counter starts in response to a stimulus and generate a pulse */ /* Enable the TIM Capture/Compare 1 interrupt */ __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1); /* Enable the TIM Capture/Compare 2 interrupt */ __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2); TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE); TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE); if(IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET) { /* Enable the main output */ __HAL_TIM_MOE_ENABLE(htim); } /* Return function status */ return HAL_OK; }
Як бачимо, вона нічого складного теж не робить -- налаштовує обидва канали та дозволяє переривання від них, але таймер завбачливо не запускає -- це відбудеться по тому чи іншому тригеру.
Скачати проекти можна тут та тут.
Дякую за гарну подачу в статті. Нарешті зрозумів як правильно конфігурувати призначення та джерело тригерів.
ВідповістиВидалити