четвер, 10 березня 2016 р.

Таймери STM32 -- зовнішнє тактування/HAL

Ще один зовнішній годинник.
(c) Wiki
Повторимо зроблене в пості про зовнішнє тактування таймерів з використанням CMSIS за допомогою HAL та STM32CubeMX. Як завжди із графічними утилітами конфігурації і на відміну CMSIS-постів, тут буде багато картинок і мало власного коду. 

Проект, використаний тут, спільний для трьох постів -- цього і двох наступних, (про внутрішні тригери та біт OPM, засобами HAL) тому на елементи конфігурації, яка стосується інших таймерів, просто не звертайте уваги. 

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


Початок роботи із STM32CubeMX вже розглядався раніше -- створюємо проект, обираємо контролер, налаштовуємо тактування (RCC) і т.д. Далі розглядатимуться лише необхідні елементи конфігурування периферії.

Для кожного розділу подаю спочатку високорівневий огляд -- що треба зробити, щоб воно працювало. Потім  йде детальний розбір, а як воно працює і як співвідноситься із тим, що говорилося, коли працювали із залізом безпосередньо, користуючись 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 секунд, виводимо його покази. На кожній ітерації змінюємо стан світлодіода -- для візуального контролю. Функціональність та ж, що в попередньому пості.

/* 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", можна тут.

2 коментарі:

  1. По-перше, дуже дякую за статтю на цікаву тему. Нажаль, не зовсім зрозуміла різниця за призначенням між External clock mode1 External clock mode2

    ВідповістиВидалити