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

Таймери STM32 -- одноімпульсний режим/HAL

Черговий пост в серії -- одноімпульсний режим, 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 використовується для генерації ШІМ. В такому режимі, правда, буде генеруватися лише один його період за запуск.
 Детальні налаштування TIM2 з вкладки Configure (натискаємо там кнопку TIM2):


Вони вже мають бути звичними, тому зверну увагу лише на декілька ключових елементів:
  • Тригер на каналі 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(), фактично, сама програма, проста настільки, що наведу її цілу:

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;
}

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



Скачати проекти можна тут та тут.



1 коментар:

  1. Дякую за гарну подачу в статті. Нарешті зрозумів як правильно конфігурувати призначення та джерело тригерів.

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