"Малюємо" проект
На вкладці Configuration тиснемо TIM3, бачимо вже звичне віконечко, в якому, правда, з'явився пункт PWM Generation Channel 2. Всі налаштування очевидні:
Варто зайти на вкладку GPIO Settings цього діалогу, хоча б щоб дати назву піну (згенеровані при виборі піна налаштування по замовчуванню цілком підійдуть):
Можна генерувати код (решту налаштувань детально розглядалися в попередньому пості). Зокрема, в наступних постах, до налаштування RCC не повертатимемося -- вони всюди однакові.
Код проекту
main()
У коді додасться функція MX_TIM3_Init(), яку слід викликати разом із іншими. Частина, пов'язана із TIM3, виглядатиме так:
MX_TIM3_Init(); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HAL_Delay(50); if( TIM3->CCR2<TIM3->ARR ) TIM3->CCR2 += 10; else TIM3->CCR2 = 10; /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
Функція HAL_TIM_PWM_Start() запускає генерацію ШІМ (PWM) на каналі 2. Раз HAL сам надає відлік часу, для затримки скористаємося його HAL_Delay(). Можливо погано шукав, але не знайшовши макрос чи функцію для маніпуляції CCRx, без якоїсь додаткової зміни стану таймера чи каналу, тому змінюю його вручну, згідно ідеї, вираженої в попередньому пості, що HAL не заважає (в більшості випадків) безпосередньо звертатися до периферії, якщо є така потреба.
Ініціалізація та запуск
Розглянемо коротко MX_TIM3_Init() та HAL_TIM_PWM_Start() -- нові функції, пов'язані із таким використанням TIM3 та зміни до інших функцій, ним викликані -- нові рядки в HAL_TIM_Base_MspInit(). (Дозвіл тактування порту А виконується в MX_GPIO_Init() з gpio.c).MX_TIM3_Init()
TIM_HandleTypeDef htim3; /* TIM3 init function */ void MX_TIM3_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig; TIM_MasterConfigTypeDef sMasterConfig; TIM_OC_InitTypeDef sConfigOC; htim3.Instance = TIM3; htim3.Init.Prescaler = 0; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 1000; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(&htim3); sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig); HAL_TIM_PWM_Init(&htim3); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 0; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2); }
Знайому з попереднього поста (див. MX_TIM1_Init()) частину пропустимо: це ініціалізація таймера викликом HAL_TIM_Base_Init(), джерел тактування функцією HAL_TIM_ConfigClockSource() та (відсутніх) підлеглих таймерів, HAL_TIMEx_MasterConfigSynchronization().
Новим є використання структури конфігурації каналу, TIM_OC_InitTypeDef, у якій вибирається режим PWM -- mode 1 (OCMode = TIM_OCMODE_PWM1), початкова тривалість імпульсу (Pulse = 0) та полярність (OCPolarity = TIM_OCPOLARITY_HIGH). Ще ця структура містить поля OCNPolarity (полярність інвертованого каналу, якщо такий присутній -- TIM3 його не має, це "привілей" лише просунутих таймерів), OCFastMode -- швидкий режим (подробиці див. документацію), OCIdleState -- стан піна під час стану спокою і OCNIdleState -- стан інвертованого піну, якщо такий є (див. Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_tim.h).
Функція ініціалізації відносно проста (варіанти для інших каналів викинуто для компактності):
Макрос __HAL_LOCK перевіряє поле Lock структури TIM_HandleTypeDef, що описує таймер. Якщо таймер заблокований -- повертає HAL_BUSY, інакше блокує його. (Дана функція, правда, ігнорує результат цього макросу), __HAL_UNLOCK -- розблоковує, записуючи в поле Lock значення HAL_UNLOCKED.
Перевірка параметрів вже має бути менш-більш звичною. Після всіх перевірок переходить до конфігурування. Спершу викликає функцію TIM_OC2_SetConfig(), потім встановлює preload-біт для CCR2, налаштовує (у нас -- вимикає) швидкий режим.
Викликана TIM_OC2_SetConfig() робить все решту:
Детально коментувати її не буду, але з поправкою на if()-и, які налаштовують інвертовані канали та поведінку по break ("аварійна зупинка") -- відсутні для TIM3 можливості, порівняйте код із тим, що писався вручну у попередньому пості. Знайомо, правда? :-) Зауважу хіба, що функція не заморочується встановленням/очищенням окремих бітових полів -- вона спочатку їх повністю зануляє, а потім записує потрібне значення.
Як би там не було, знову видно, що концепція та реалізація HAL -- доволі проста.
Новим є використання структури конфігурації каналу, TIM_OC_InitTypeDef, у якій вибирається режим PWM -- mode 1 (OCMode = TIM_OCMODE_PWM1), початкова тривалість імпульсу (Pulse = 0) та полярність (OCPolarity = TIM_OCPOLARITY_HIGH). Ще ця структура містить поля OCNPolarity (полярність інвертованого каналу, якщо такий присутній -- TIM3 його не має, це "привілей" лише просунутих таймерів), OCFastMode -- швидкий режим (подробиці див. документацію), OCIdleState -- стан піна під час стану спокою і OCNIdleState -- стан інвертованого піну, якщо такий є (див. Drivers\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal_tim.h).
Функція ініціалізації відносно проста (варіанти для інших каналів викинуто для компактності):
HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef* sConfig, uint32_t Channel) { __HAL_LOCK(htim); /* Check the parameters */ assert_param(IS_TIM_CHANNELS(Channel)); assert_param(IS_TIM_PWM_MODE(sConfig->OCMode)); assert_param(IS_TIM_OC_POLARITY(sConfig->OCPolarity)); assert_param(IS_TIM_OCN_POLARITY(sConfig->OCNPolarity)); assert_param(IS_TIM_FAST_STATE(sConfig->OCFastMode)); assert_param(IS_TIM_OCNIDLE_STATE(sConfig->OCNIdleState)); assert_param(IS_TIM_OCIDLE_STATE(sConfig->OCIdleState)); htim->State = HAL_TIM_STATE_BUSY; switch (Channel) { case TIM_CHANNEL_1: { // <.................. SKIPPED ....................> } break; case TIM_CHANNEL_2: { assert_param(IS_TIM_CC2_INSTANCE(htim->Instance)); /* Configure the Channel 2 in PWM mode */ TIM_OC2_SetConfig(htim->Instance, sConfig); /* Set the Preload enable bit for channel2 */ htim->Instance->CCMR1 |= TIM_CCMR1_OC2PE; /* Configure the Output Fast mode */ htim->Instance->CCMR1 &= ~TIM_CCMR1_OC2FE; htim->Instance->CCMR1 |= sConfig->OCFastMode << 8; } break; case TIM_CHANNEL_3: { // <.................. SKIPPED ....................> } break; case TIM_CHANNEL_4: { // <.................. SKIPPED ....................> } break; default: break; } htim->State = HAL_TIM_STATE_READY; __HAL_UNLOCK(htim); return HAL_OK; }
Макрос __HAL_LOCK перевіряє поле Lock структури TIM_HandleTypeDef, що описує таймер. Якщо таймер заблокований -- повертає HAL_BUSY, інакше блокує його. (Дана функція, правда, ігнорує результат цього макросу), __HAL_UNLOCK -- розблоковує, записуючи в поле Lock значення HAL_UNLOCKED.
Перевірка параметрів вже має бути менш-більш звичною. Після всіх перевірок переходить до конфігурування. Спершу викликає функцію TIM_OC2_SetConfig(), потім встановлює preload-біт для CCR2, налаштовує (у нас -- вимикає) швидкий режим.
Викликана TIM_OC2_SetConfig() робить все решту:
void TIM_OC2_SetConfig(TIM_TypeDef *TIMx, TIM_OC_InitTypeDef *OC_Config) { uint32_t tmpccmrx = 0; uint32_t tmpccer = 0; uint32_t tmpcr2 = 0; /* Disable the Channel 2: Reset the CC2E Bit */ TIMx->CCER &= ~TIM_CCER_CC2E; /* Get the TIMx CCER register value */ tmpccer = TIMx->CCER; /* Get the TIMx CR2 register value */ tmpcr2 = TIMx->CR2; /* Get the TIMx CCMR1 register value */ tmpccmrx = TIMx->CCMR1; /* Reset the Output Compare mode and Capture/Compare selection Bits */ tmpccmrx &= ~TIM_CCMR1_OC2M; tmpccmrx &= ~TIM_CCMR1_CC2S; /* Select the Output Compare Mode */ tmpccmrx |= (OC_Config->OCMode << 8); /* Reset the Output Polarity level */ tmpccer &= ~TIM_CCER_CC2P; /* Set the Output Compare Polarity */ tmpccer |= (OC_Config->OCPolarity << 4); if(IS_TIM_CCXN_INSTANCE(TIMx, TIM_CHANNEL_2)) { assert_param(IS_TIM_OCN_POLARITY(OC_Config->OCNPolarity)); assert_param(IS_TIM_OCNIDLE_STATE(OC_Config->OCNIdleState)); assert_param(IS_TIM_OCIDLE_STATE(OC_Config->OCIdleState)); /* Reset the Output N Polarity level */ tmpccer &= ~TIM_CCER_CC2NP; /* Set the Output N Polarity */ tmpccer |= (OC_Config->OCNPolarity << 4); /* Reset the Output N State */ tmpccer &= ~TIM_CCER_CC2NE; } if(IS_TIM_BREAK_INSTANCE(TIMx)) { /* Check parameters */ assert_param(IS_TIM_OCNIDLE_STATE(OC_Config->OCNIdleState)); assert_param(IS_TIM_OCIDLE_STATE(OC_Config->OCIdleState)); /* Reset the Output Compare and Output Compare N IDLE State */ tmpcr2 &= ~TIM_CR2_OIS2; tmpcr2 &= ~TIM_CR2_OIS2N; /* Set the Output Idle state */ tmpcr2 |= (OC_Config->OCIdleState << 2); /* Set the Output N Idle state */ tmpcr2 |= (OC_Config->OCNIdleState << 2); } /* Write to TIMx CR2 */ TIMx->CR2 = tmpcr2; /* Write to TIMx CCMR1 */ TIMx->CCMR1 = tmpccmrx; /* Set the Capture Compare Register value */ TIMx->CCR2 = OC_Config->Pulse; /* Write to TIMx CCER */ TIMx->CCER = tmpccer; }
Детально коментувати її не буду, але з поправкою на if()-и, які налаштовують інвертовані канали та поведінку по break ("аварійна зупинка") -- відсутні для TIM3 можливості, порівняйте код із тим, що писався вручну у попередньому пості. Знайомо, правда? :-) Зауважу хіба, що функція не заморочується встановленням/очищенням окремих бітових полів -- вона спочатку їх повністю зануляє, а потім записує потрібне значення.
Як би там не було, знову видно, що концепція та реалізація HAL -- доволі проста.
HAL_TIM_PWM_Start()
Доволі проста функція, загальна схема якої має бути знайомою із попереднього поста:
Як і обіцяв, про біт MOE, будь такий у таймера є, HAL турбується, дозволяючи і просунутим таймерам керувати виводами.
Існують також "колеги" цієї функції, із трішки відмінними властивостями: HAL_TIM_PWM_Start_IT() та HAL_TIM_PWM_Start_DMA() і "опоненти": HAL_TIM_PWM_Stop(), HAL_TIM_PWM_Stop_IT(), HAL_TIM_PWM_Stop_DMA().
Використана нею TIM_CCxChannelCmd() теж проста -- всі дії мали б бути знайомими після ручної роботи з CMSIS:
(Звичайно, всі "знайомо", "просто" і т.д., виходять із того, що читач володіє бітовими маніпуляціями і сяк-так розібрався із відповідними місцями попередніх постів.)
Як бачимо, все просто -- вмикається тактування таймера, пін налаштовується в режимі AF-PP -- Alternative function-push-pull.
/** * @brief Starts the PWM signal generation. * @param htim : TIM handle * @param Channel : TIM Channels to be enabled * This parameter can be one of the following values: * @arg TIM_CHANNEL_1: TIM Channel 1 selected * @arg TIM_CHANNEL_2: TIM Channel 2 selected * @arg TIM_CHANNEL_3: TIM Channel 3 selected * @arg TIM_CHANNEL_4: TIM Channel 4 selected * @retval HAL status */ HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel) { /* Check the parameters */ assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel)); /* Enable the Capture compare channel */ TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE); if(IS_TIM_BREAK_INSTANCE(htim->Instance) != RESET) { /* Enable the main output */ __HAL_TIM_MOE_ENABLE(htim); } /* Enable the Peripheral */ __HAL_TIM_ENABLE(htim); /* Return function status */ return HAL_OK; }
Як і обіцяв, про біт MOE, будь такий у таймера є, HAL турбується, дозволяючи і просунутим таймерам керувати виводами.
Існують також "колеги" цієї функції, із трішки відмінними властивостями: HAL_TIM_PWM_Start_IT() та HAL_TIM_PWM_Start_DMA() і "опоненти": HAL_TIM_PWM_Stop(), HAL_TIM_PWM_Stop_IT(), HAL_TIM_PWM_Stop_DMA().
Використана нею TIM_CCxChannelCmd() теж проста -- всі дії мали б бути знайомими після ручної роботи з CMSIS:
void TIM_CCxChannelCmd(TIM_TypeDef* TIMx, uint32_t Channel, uint32_t ChannelState) { uint32_t tmp = 0; /* Check the parameters */ assert_param(IS_TIM_CC1_INSTANCE(TIMx)); assert_param(IS_TIM_CHANNELS(Channel)); tmp = TIM_CCER_CC1E << Channel; /* Reset the CCxE Bit */ TIMx->CCER &= ~tmp; /* Set or reset the CCxE Bit */ TIMx->CCER |= (uint32_t)(ChannelState << Channel); }
(Звичайно, всі "знайомо", "просто" і т.д., виходять із того, що читач володіє бітовими маніпуляціями і сяк-так розібрався із відповідними місцями попередніх постів.)
HAL_TIM_Base_MspInit() -- нове
Новими у цій функції є наступні рядки, що стосуються TIM3:else if(htim_base->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspInit 0 */ /* USER CODE END TIM3_MspInit 0 */ /* Peripheral clock enable */ __TIM3_CLK_ENABLE(); /**TIM3 GPIO Configuration PA7 ------> TIM3_CH2 */ GPIO_InitStruct.Pin = RED_LED_PA7_TIM3CH2_Pin; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; HAL_GPIO_Init(RED_LED_PA7_TIM3CH2_GPIO_Port, &GPIO_InitStruct); /* USER CODE BEGIN TIM3_MspInit 1 */ /* USER CODE END TIM3_MspInit 1 */ }
Як бачимо, все просто -- вмикається тактування таймера, пін налаштовується в режимі AF-PP -- Alternative function-push-pull.
Завершальне слово
Повний main.c не наводжу -- весь код із нього описано, а виглядає він, через коментарі Cube, громіздко.Скачати проект можна тут.
Немає коментарів:
Дописати коментар