Ще один зовнішній годинник. (c) Wiki |
Проект, використаний тут, спільний для трьох постів -- цього і двох наступних, (про внутрішні тригери та біт OPM, засобами HAL) тому на елементи конфігурації, яка стосується інших таймерів, просто не звертайте уваги.
Звичайно, краще було б зробити окремі проекти для кожного, але це чисто механічна робота, на яку мені бракує сил та часу, а ця серія постів їх і так забагато забрала.
Початок роботи із STM32CubeMX вже розглядався раніше -- створюємо проект, обираємо контролер, налаштовуємо тактування (RCC) і т.д. Далі розглядатимуться лише необхідні елементи конфігурування периферії.
Для кожного розділу подаю спочатку високорівневий огляд -- що треба зробити, щоб воно працювало. Потім йде детальний розбір, а як воно працює і як співвідноситься із тим, що говорилося, коли працювали із залізом безпосередньо, користуючись CMSIS.
Для кожного розділу подаю спочатку високорівневий огляд -- що треба зробити, щоб воно працювало. Потім йде детальний розбір, а як воно працює і як співвідноситься із тим, що говорилося, коли працювали із залізом безпосередньо, користуючись CMSIS.
External clock mode1 -- тактування від пінів TI1 або TI2
На вкладці Pinout клікаємо по піну PA0, обираємо його режим TIM2_CH1. Пін зафарбується жовтим -- таймер ще не сконфігуровано. Вибираємо Slave mode -- External Clock Mode 1, Trigger source -- TI1FP1. Думаю, з врахуванням інформації з попереднього, "симетричного" CMSIS-поста, вибір очевидний. При тому канал-1 стане недоступним, що теж зрозуміло. В результаті отримується щось таке:
На цій вкладці -- все. Переходимо до Configuration, клікаємо на TIM2. Там потрібно обрати параметри таймера:
Клікабельно! |
Частина параметрів будуть мати правильне значення за замовчуванням. Важливі виділено червоним -- подільник, період (хоч подія оновлення лічильника нами не використовується, але замалий період, зокрема -- нульовий, занулятиме лічильник явно невчасно), полярність сигналу. Інших варіантів підлеглого (slave) режиму, крім ETR mode 1, немає, то червоним його не відмічав. Є можливість сконфігурувати фільтр, поки тут залишено нуль. Керування підлеглими таймерами -- TRGO, нас зараз не цікавить, залишаємо як є.
Крім того, на вкладці "GPIO Settings" цього діалогу, задано ім'я піна -- USER_BTN_PA0_TIM2CH1, решта його параметрів вже сконфігуровано автоматично, згідно налаштувань на вкладці pinout головного вікна.
Можна генерувати код. Загальну його структуру ми недавно розглядали [1, 2], тому не зупинятимуся на ній детально, опишу лише особливості, що стосуються теми поста.
Створюється змінна, яка описує конфігурацію таймера (tim.c):
TIM_HandleTypeDef htim2;
В main() викликається визначена в тому ж tim.c функція ініціалізації, MX_TIM2_Init(), яку розглянемо детальніше пізніше.
Все, можна починати писати свій код. Запускаємо таймер, в циклі, кожних 5 секунд, виводимо його покази. На кожній ітерації змінюємо стан світлодіода -- для візуального контролю. Функціональність та ж, що в попередньому пості.
Розглянемо тепер детальніше, як функціонує автоматично згенерований код, порівняємо його із тим, що досліджувався раніше.
Все, можна починати писати свій код. Запускаємо таймер, в циклі, кожних 5 секунд, виводимо його покази. На кожній ітерації змінюємо стан світлодіода -- для візуального контролю. Функціональність та ж, що в попередньому пості.
/* USER CODE BEGIN 2 */ HAL_TIM_Base_Start(&htim2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ int prev_TIM2_CNT = TIM2->CNT; int idx = 0; while (1) { HAL_GPIO_TogglePin(BLUE_LED_PC9_GPIO_Port, BLUE_LED_PC9_Pin); // Зміною стану LED відмічаємо цикл HAL_Delay(5000); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ int new_TIM2_CNT = TIM2->CNT; printf("%02i: Counted: %i, change = %i\n", idx, new_TIM2_CNT, new_TIM2_CNT-prev_TIM2_CNT); prev_TIM2_CNT = new_TIM2_CNT; ++idx; } /* USER CODE END 3 */
Розглянемо тепер детальніше, як функціонує автоматично згенерований код, порівняємо його із тим, що досліджувався раніше.
Автоматично згенерована ініціалізація -- MX_TIM2_Init()
/* TIM2 init function */ void MX_TIM2_Init(void) { TIM_SlaveConfigTypeDef sSlaveConfig; TIM_MasterConfigTypeDef sMasterConfig; htim2.Instance = TIM2; htim2.Init.Prescaler = 0; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 10000; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&htim2); sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1; sSlaveConfig.InputTrigger = TIM_TS_TI1FP1; sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING; sSlaveConfig.TriggerFilter = 0; HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); }
Виклик HAL_TIM_Base_Init(&htim2) та HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) -- такі ж, як і раніше, з точністю до конкретних значень параметрів. Конфігурація тактування змінилася -- замість структури TIM_ClockConfigTypeDef із полем ClockSource = TIM_CLOCKSOURCE_INTERNAL і виклику HAL_TIM_ConfigClockSource(), як раніше (див. функцію конфігурації TIM1), використовується структура TIM_SlaveConfigTypeDef, у якій вказується:
- SlaveMode = TIM_SLAVEMODE_EXTERNAL1
- InputTrigger = TIM_TS_TI1FP1
- TriggerPolarity = TIM_TRIGGERPOLARITY_RISING
- TriggerFilter = 0
Функція HAL_TIM_SlaveConfigSynchronization() визначена в Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_tim.c, тобто є частиною HAL, і виглядає так:
HAL_StatusTypeDef HAL_TIM_SlaveConfigSynchronization(TIM_HandleTypeDef *htim, TIM_SlaveConfigTypeDef * sSlaveConfig) { /* Check the parameters */ assert_param(IS_TIM_SLAVE_INSTANCE(htim->Instance)); assert_param(IS_TIM_SLAVE_MODE(sSlaveConfig->SlaveMode)); assert_param(IS_TIM_TRIGGER_SELECTION(sSlaveConfig->InputTrigger)); __HAL_LOCK(htim); htim->State = HAL_TIM_STATE_BUSY; TIM_SlaveTimer_SetConfig(htim, sSlaveConfig); /* Disable Trigger Interrupt */ __HAL_TIM_DISABLE_IT(htim, TIM_IT_TRIGGER); /* Disable Trigger DMA request */ __HAL_TIM_DISABLE_DMA(htim, TIM_DMA_TRIGGER); htim->State = HAL_TIM_STATE_READY; __HAL_UNLOCK(htim); return HAL_OK; }
Вона, як і інші подібні розглянуті функції, перевіряє свої параметри, помічає структуру як заблоковану, змінює налаштування апаратури викликом TIM_SlaveTimer_SetConfig(htim, sSlaveConfig), далі, заборонивши переривання чи DMA запити по тригеру, помічає таймер як готовий і розблокований.
Функція TIM_SlaveTimer_SetConfig() виглядає ось так:
static void TIM_SlaveTimer_SetConfig(TIM_HandleTypeDef *htim, TIM_SlaveConfigTypeDef * sSlaveConfig) { uint32_t tmpsmcr = 0; uint32_t tmpccmr1 = 0; uint32_t tmpccer = 0; /* Get the TIMx SMCR register value */ tmpsmcr = htim->Instance->SMCR; /* Reset the Trigger Selection Bits */ tmpsmcr &= ~TIM_SMCR_TS; /* Set the Input Trigger source */ tmpsmcr |= sSlaveConfig->InputTrigger; /* Reset the slave mode Bits */ tmpsmcr &= ~TIM_SMCR_SMS; /* Set the slave mode */ tmpsmcr |= sSlaveConfig->SlaveMode; /* Write to TIMx SMCR */ htim->Instance->SMCR = tmpsmcr; /* Configure the trigger prescaler, filter, and polarity */ switch (sSlaveConfig->InputTrigger) { case TIM_TS_ETRF: { /* Check the parameters */ assert_param(IS_TIM_CLOCKSOURCE_ETRMODE1_INSTANCE(htim->Instance)); assert_param(IS_TIM_TRIGGERPRESCALER(sSlaveConfig->TriggerPrescaler)); assert_param(IS_TIM_TRIGGERPOLARITY(sSlaveConfig->TriggerPolarity)); assert_param(IS_TIM_TRIGGERFILTER(sSlaveConfig->TriggerFilter)); /* Configure the ETR Trigger source */ TIM_ETR_SetConfig(htim->Instance, sSlaveConfig->TriggerPrescaler, sSlaveConfig->TriggerPolarity, sSlaveConfig->TriggerFilter); } break; case TIM_TS_TI1F_ED: { /* Check the parameters */ assert_param(IS_TIM_CC1_INSTANCE(htim->Instance)); assert_param(IS_TIM_TRIGGERFILTER(sSlaveConfig->TriggerFilter)); /* Disable the Channel 1: Reset the CC1E Bit */ tmpccer = htim->Instance->CCER; htim->Instance->CCER &= ~TIM_CCER_CC1E; tmpccmr1 = htim->Instance->CCMR1; /* Set the filter */ tmpccmr1 &= ~TIM_CCMR1_IC1F; tmpccmr1 |= ((sSlaveConfig->TriggerFilter) << 4); /* Write to TIMx CCMR1 and CCER registers */ htim->Instance->CCMR1 = tmpccmr1; htim->Instance->CCER = tmpccer; } break; case TIM_TS_TI1FP1: { /* Check the parameters */ assert_param(IS_TIM_CC1_INSTANCE(htim->Instance)); assert_param(IS_TIM_TRIGGERPOLARITY(sSlaveConfig->TriggerPolarity)); assert_param(IS_TIM_TRIGGERFILTER(sSlaveConfig->TriggerFilter)); /* Configure TI1 Filter and Polarity */ TIM_TI1_ConfigInputStage(htim->Instance, sSlaveConfig->TriggerPolarity, sSlaveConfig->TriggerFilter); } break; case TIM_TS_TI2FP2: { /* Check the parameters */ assert_param(IS_TIM_CC2_INSTANCE(htim->Instance)); assert_param(IS_TIM_TRIGGERPOLARITY(sSlaveConfig->TriggerPolarity)); assert_param(IS_TIM_TRIGGERFILTER(sSlaveConfig->TriggerFilter)); /* Configure TI2 Filter and Polarity */ TIM_TI2_ConfigInputStage(htim->Instance, sSlaveConfig->TriggerPolarity, sSlaveConfig->TriggerFilter); } break; case TIM_TS_ITR0: { /* Check the parameter */ assert_param(IS_TIM_CC2_INSTANCE(htim->Instance)); } break; case TIM_TS_ITR1: { /* Check the parameter */ assert_param(IS_TIM_CC2_INSTANCE(htim->Instance)); } break; case TIM_TS_ITR2: { /* Check the parameter */ assert_param(IS_TIM_CC2_INSTANCE(htim->Instance)); } break; case TIM_TS_ITR3: { /* Check the parameter */ assert_param(IS_TIM_CC2_INSTANCE(htim->Instance)); } break; default: break; } }
Вона починає із того, що ми робили вручну в попередньому пості -- в регістр SMCR записує біти вибору тригера, TS та режиму, SMS. Тоді, для налаштування режиму TIM_TS_TI1FP1, за допомогою switch(), обирається виклик функції TIM_TI1_ConfigInputStage(), котрій передаються полярність тригера та біти конфігурації фільтра. Вона, у свою чергу, виглядає так:
static void TIM_TI1_ConfigInputStage(TIM_TypeDef *TIMx, uint32_t TIM_ICPolarity, uint32_t TIM_ICFilter) { uint32_t tmpccmr1 = 0; uint32_t tmpccer = 0; /* Disable the Channel 1: Reset the CC1E Bit */ tmpccer = TIMx->CCER; TIMx->CCER &= ~TIM_CCER_CC1E; tmpccmr1 = TIMx->CCMR1; /* Set the filter */ tmpccmr1 &= ~TIM_CCMR1_IC1F; tmpccmr1 |= (TIM_ICFilter << 4); /* Select the Polarity and set the CC1E Bit */ tmpccer &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP); tmpccer |= TIM_ICPolarity; /* Write to TIMx CCMR1 and CCER registers */ TIMx->CCMR1 = tmpccmr1; TIMx->CCER = tmpccer; }
І, з врахуванням згаданої вище інформації з попереднього поста, додаткових коментарів не потребує.
External clock mode2 -- від піна ETR
Візуально, конфігурування для роботи в цьому режимі дещо відрізнятиметься:Як видно, цей режим вважається варіантом тактування, а не варіантом підлеглого режиму:
Аналогічно до вже розглянутого вище, налаштовуються параметри такого тактування, в діалозі для TIM2 вкладки Configure:
На вкладці GPIO Settings цього діалогу також можна задати ім'я піна, наприклад USER_BTN_PA0_TIM2ETR.
У згенерованому коді, в порівнянні із попереднім випадком, відрізнятиметься код MX_TIM2_Init():
void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; htim2.Instance = TIM2; htim2.Init.Prescaler = 0; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 10000; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&htim2); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_ETRMODE2; sClockSourceConfig.ClockPolarity = TIM_CLOCKPOLARITY_NONINVERTED; sClockSourceConfig.ClockPrescaler = TIM_CLOCKPRESCALER_DIV1; sClockSourceConfig.ClockFilter = 0; HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); }
Тобто, від використаних вище SlaveConfigTypeDef / HAL_TIM_SlaveConfigSynchronization() знову повернулися до TIM_ClockConfigTypeDef / HAL_TIM_ConfigClockSource(), тільки тепер прописано ClockSource = TIM_CLOCKSOURCE_ETRMODE2.
Решта коду залишається незмінною (ну, формально, ще символьне ім'я піна змінилося на USER_BTN_PA0_TIM2ETR.)
HAL_TIM_ConfigClockSource()
Минулого разу ("Таймери STM32 -- відлік часу/HAL") ми оминули цю функцію, виправляємо дану несправедливість.
HAL_StatusTypeDef HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim, TIM_ClockConfigTypeDef * sClockSourceConfig) { uint32_t tmpsmcr = 0; /* Process Locked */ __HAL_LOCK(htim); htim->State = HAL_TIM_STATE_BUSY; /* Check the parameters */ assert_param(IS_TIM_CLOCKSOURCE(sClockSourceConfig->ClockSource)); assert_param(IS_TIM_CLOCKPOLARITY(sClockSourceConfig->ClockPolarity)); assert_param(IS_TIM_CLOCKPRESCALER(sClockSourceConfig->ClockPrescaler)); assert_param(IS_TIM_CLOCKFILTER(sClockSourceConfig->ClockFilter)); /* Reset the SMS, TS, ECE, ETPS and ETRF bits */ tmpsmcr = htim->Instance->SMCR; tmpsmcr &= ~(TIM_SMCR_SMS | TIM_SMCR_TS); tmpsmcr &= ~(TIM_SMCR_ETF | TIM_SMCR_ETPS | TIM_SMCR_ECE | TIM_SMCR_ETP); htim->Instance->SMCR = tmpsmcr; switch (sClockSourceConfig->ClockSource) { case TIM_CLOCKSOURCE_INTERNAL: { assert_param(IS_TIM_INSTANCE(htim->Instance)); /* Disable slave mode to clock the prescaler directly with the internal clock */ htim->Instance->SMCR &= ~TIM_SMCR_SMS; } break; case TIM_CLOCKSOURCE_ETRMODE1: { /* Check whether or not the timer instance supports external trigger input mode 1 (ETRF)*/ assert_param(IS_TIM_CLOCKSOURCE_ETRMODE1_INSTANCE(htim->Instance)); /* Configure the ETR Clock source */ TIM_ETR_SetConfig(htim->Instance, sClockSourceConfig->ClockPrescaler, sClockSourceConfig->ClockPolarity, sClockSourceConfig->ClockFilter); /* Get the TIMx SMCR register value */ tmpsmcr = htim->Instance->SMCR; /* Reset the SMS and TS Bits */ tmpsmcr &= ~(TIM_SMCR_SMS | TIM_SMCR_TS); /* Select the External clock mode1 and the ETRF trigger */ tmpsmcr |= (TIM_SLAVEMODE_EXTERNAL1 | TIM_CLOCKSOURCE_ETRMODE1); /* Write to TIMx SMCR */ htim->Instance->SMCR = tmpsmcr; } break; case TIM_CLOCKSOURCE_ETRMODE2: { /* Check whether or not the timer instance supports external trigger input mode 2 (ETRF)*/ assert_param(IS_TIM_CLOCKSOURCE_ETRMODE2_INSTANCE(htim->Instance)); /* Configure the ETR Clock source */ TIM_ETR_SetConfig(htim->Instance, sClockSourceConfig->ClockPrescaler, sClockSourceConfig->ClockPolarity, sClockSourceConfig->ClockFilter); /* Enable the External clock mode2 */ htim->Instance->SMCR |= TIM_SMCR_ECE; } break; case TIM_CLOCKSOURCE_TI1: { /* Check whether or not the timer instance supports external clock mode 1 */ assert_param(IS_TIM_CLOCKSOURCE_TIX_INSTANCE(htim->Instance)); TIM_TI1_ConfigInputStage(htim->Instance, sClockSourceConfig->ClockPolarity, sClockSourceConfig->ClockFilter); TIM_ITRx_SetConfig(htim->Instance, TIM_CLOCKSOURCE_TI1); } break; case TIM_CLOCKSOURCE_TI2: // ................................ <skipped> ............................. break; case TIM_CLOCKSOURCE_TI1ED: { /* Check whether or not the timer instance supports external clock mode 1 */ assert_param(IS_TIM_CLOCKSOURCE_TIX_INSTANCE(htim->Instance)); TIM_TI1_ConfigInputStage(htim->Instance, sClockSourceConfig->ClockPolarity, sClockSourceConfig->ClockFilter); TIM_ITRx_SetConfig(htim->Instance, TIM_CLOCKSOURCE_TI1ED); } break; case TIM_CLOCKSOURCE_ITR0: { /* Check whether or not the timer instance supports external clock mode 1 */ assert_param(IS_TIM_CLOCKSOURCE_ITRX_INSTANCE(htim->Instance)); TIM_ITRx_SetConfig(htim->Instance, TIM_CLOCKSOURCE_ITR0); } break; case TIM_CLOCKSOURCE_ITR1: // ................................ <skipped> ................................ break; case TIM_CLOCKSOURCE_ITR2: // ................................ <skipped> ................................ break; case TIM_CLOCKSOURCE_ITR3: // ................................ <skipped> ................................ break; default: break; } htim->State = HAL_TIM_STATE_READY; __HAL_UNLOCK(htim); return HAL_OK; }
Одноманітні фрагменти коду опущено. Варто звернути увагу, що як і TIM_SlaveTimer_SetConfig(), ця функція береться конфігурувати всі варіанти режимів, тільки як Clock Source а не Trigger source -- не слід їх плутати!
Після традиційної перевірки аргументів та блокування, функція очищає біти SMS, TS, ECE, ETPS і ETRF -- як то кажуть, перш ніж навчити говорити, слід навчити мовчати. Тоді, для цікавого нам зараз режиму TIM_CLOCKSOURCE_ETRMODE2, викликає TIM_ETR_SetConfig(), передавши їй подільник, полярність та біти для конфігурації фільтра. Після цього вмикає режим, встановивши біт ECE в регістрі SMCR.
Сама TIM_ETR_SetConfig() тривіальна:
static void TIM_ETR_SetConfig(TIM_TypeDef* TIMx, uint32_t TIM_ExtTRGPrescaler, uint32_t TIM_ExtTRGPolarity, uint32_t ExtTRGFilter) { uint32_t tmpsmcr = 0; tmpsmcr = TIMx->SMCR; /* Reset the ETR Bits */ tmpsmcr &= ~(TIM_SMCR_ETF | TIM_SMCR_ETPS | TIM_SMCR_ECE | TIM_SMCR_ETP); /* Set the Prescaler, the Filter value and the Polarity */ tmpsmcr |= (uint32_t)(TIM_ExtTRGPrescaler | (TIM_ExtTRGPolarity | (ExtTRGFilter << 8))); /* Write to TIMx SMCR */ TIMx->SMCR = tmpsmcr; }
Завершальне слово
Скачати проект із кодом для цього та двох наступних пості -- "Таймери STM32 -- внутрішні тригери/HAL" і "Таймери STM32 -- Автоматична зупинка/HAL", можна тут.
Автор видалив цей коментар.
ВідповістиВидалитиПо-перше, дуже дякую за статтю на цікаву тему. Нажаль, не зовсім зрозуміла різниця за призначенням між External clock mode1 External clock mode2
ВідповістиВидалити