неділю, 27 березня 2016 р.

Таймери STM32 -- захоплення ШІМ/HAL

Приклади, які розглядаються в цій серії, свідомо дещо неконвенційні. Проблема в тому, що вирішити задачу, описану в документації, зазвичай справді так просто, як описано в документації. Однак, крок ліворуч-крок праворуч і складність може різко зрости. Тому вирішив спробувати продемонструвати ідею використання (таймерів у даному випадку), трішки відступивши від типових задач. Приклади, при тому, іноді стають надуманими, але, залишаючись простими (описуючи розв'язок реальної задачі, складно відділити особливості задачі від особливостей засобів), демонструють роботу із відповідним пристроєм під більшою кількістю кутів (особливо -- разом із прикладами з документації та appnote). 

Черговий такий приклад -- вирішена раніше, засобами CMSIS, задача визначення тривалості натискання кнопки із використанням режиму захоплення ШІМ. Задача, насправді, зовсім трішки відрізняється від "канонічної", із документації -- один таймер генерує ШІМ, інший його ловить, але дає можливість "помацати" таймер руками -- натискаючи кнопку.


Для піна, PA0, до якого під'єднана кнопка, вибираємо, як і в попередньому пості, TIM2_CH1: 

Налаштування TIM2 дещо менш очевидні:
  • Таймер тактується від внутрішнього годинника
  • Канал 1 (CH1) служить для захоплення вводу (далі йому буде вказано ловити по зростанню -- він фіксуватиме завершення імпульсу).
  • Цей же канал служитимете тригером -- TI1FP1, який буде перезапускати таймер -- Reset mode. В режимі захоплення ШІМ, так і треба.
  • Канал 2, який в цьому режимі має працювати із тим же піном, сконфігуровано як "Input Capture indirect mode" -- як на мене, дещо неочевидна назва. Він буде спрацьовувати по спаданню -- фіксувати кінець імпульсу.
Подробиці, як завжди, на вкладці Configure->TIM2:

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

Переходимо до коду.

"Структури даних":

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
volatile int timer_overflows = 0;

volatile bool is_overflow = false;
volatile bool have_pulse = false;
volatile bool have_period = false; // we _have_ period :-)
volatile bool has_started = false; // it _has_ started :-)
volatile int pulse_width;
volatile int pulse_timer_overflows;
volatile int period_width;
volatile int period_timer_overflows;
volatile bool have_data = false;

/* USER CODE END PV */

та обробники переривань:


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
 if( htim->Instance == TIM2 )
 {
  ++timer_overflows;
 }
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
 if( htim->Instance == TIM2  )
 {
  // Для спрощення, можливість overcapture ігноруємо
  if( htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1 )
  {
   __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
   // Оновлює лічильник! А ми не хочемо, щоб псувався timer_overflows
   if(has_started)
   {
    if(have_period)
     is_overflow = true;
    have_period = true;
   }
   period_width = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
   period_timer_overflows = timer_overflows;
   timer_overflows = 0;
  }
  if( htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2 )
  {
   has_started = true;
   if(have_pulse)
    is_overflow = true;
   have_pulse = true;
   pulse_width = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
   pulse_timer_overflows = timer_overflows;
  }

 }
}

такі ж, як і в CMSIS-варіанті. Всі використані макроси вже розглянуто в "Таймери STM32 -- захоплення вводу/HAL".

В main(), після вже звичної ініціалізації:

 /* 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();

Дозволяється робота обох каналів та, для глобального відліку часу, переривання по переповненню:

 HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
  HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);
  __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);

Решта коду --- така ж, як і для CMSIS варіанту, хіба із заміною NVIC_DisableIRQ() на HAL_NVIC_DisableIRQ(), тому не наводитиму.

Все. Ще, традиційно, глянемо на ініціалізацію та нові засоби HAL.

Ініціалізація


void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_SlaveConfigTypeDef sSlaveConfig;
  TIM_MasterConfigTypeDef sMasterConfig;
  TIM_IC_InitTypeDef sConfigIC;

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 23999;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = reload_val;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  HAL_TIM_Base_Init(&htim2);

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);

  HAL_TIM_IC_Init(&htim2);

  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
  sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
  sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sSlaveConfig.TriggerFilter = 15;
  HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig);

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);

  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 15;
  HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);

  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
  sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
  sConfigIC.ICFilter = 0;
  HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2);

}

  • Спочатку, як завжди, базова ініціалізація, з використанням HAL_TIM_Base_Init()
  • Далі --  HAL_TIM_ConfigClockSource() вказує тактуватися від внутрішнього джерела.
  • Розглянута недавно HAL_TIM_IC_Init() ініціалізує таймер для захоплення вводу.
  • HAL_TIM_SlaveConfigSynchronization(), яку було розглянуто раніше: "Таймери STM32 -- внутрішні тригери/HAL", користуючись даними із структури TIM_SlaveConfigTypeDef: SlaveMode = TIM_SLAVEMODE_RESET, InputTrigger = TIM_TS_TI1FP1, TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING, TriggerFilter = 15, налаштовує підлеглий режим.
  • HAL_TIMEx_MasterConfigSynchronization() ми зазвичай опускаємо -- вона, в більшості прикладів, каже не займатися TRGO.
  • Нарешті, HAL_TIM_IC_ConfigChannel() викликається для кожного каналу із заданими раніше (в GUI) параметрами
Усе. Ми вже стільки функцій із HAL детально розглянули, що іноді нові  навіть не додаються.



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

2 коментарі:

  1. Доброго дня. Підскажіть будь-ласка.
    Описую ситуацію. Мікроконтролер STM32F030F4, до ньго підключено енкодер, використовуючи Encoder Mode - TIM3, піни РА6(TIM3_CH1) та РА7(TIM3_CH2). Цей режим я відпрацював, все норм. Проект в CubeMX.
    Тепер потрібно додати до проекта обробник пульта із кодуванням RC5. Так склалося, що залишився вільним тільки пін РВ1 який і був зарезервований для RC5. Плату замовив у людей і вона вже виготовлена, тому змінити нічого не можливо. Отже, пін РВ1 може працювати із ТІМ3_СН4, ТІМ14_СН1, ТІМ1_СН3N.
    Планую використати бібліотеку від ST - X-CUBE-IRREMOTE – Implementing transmitters and receivers for infrared remote control protocols using STM32Cube (AN4834)
    http://www.st.com/content/st_com/en/products/embedded-software/mcus-embedded-software/stm32-embedded-software/stm32cube-expansion-software/x-cube-irremote.html
    Щоб застосувати "лібу" потрібно два канали одного таймера, які в нас впринципі є вільними - СН4 та СН3 таймера TIM3. Щось схоже, тільки для каналів 1,2 на цьому малюнку http://we.easyelectronics.ru/uploads/images/00/04/62/2012/11/24/0edd8e.png.
    І тепер питання до Вас. Чи зможу я "подружити" обробник енкодера із обробником декодера RC5 в одному перериванні від ТІМ3? Взагалі є така можливість обробляти різні канали в одному перериванні?
    Я звачайний радіолюбитель, у вільний час вивчаю програмування..., так що вибачте можливо за дещо делетанські питання...

    ВідповістиВидалити
  2. Трохи "порився" в доках. Виходить не вийде в мене із X-CUBE-IRREMOTE. Просто банально немає тригерів на 3 та 4 каналах

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