Jak wygląda kod na STM32F411,sprzętowy , zmiana polaryzacji między 2 pinami ,LOW 64 sekund ,HIGH 64 sekund , okres 128 sekund ,DutyCycle 50%
Tak — na STM32F411 można to zrobić całkowicie sprzętowo, bez przerwań i bez pętli opóźniających.
Poniżej masz poprawny przykład dla:
Zakładam, że chodzi o dwa piny pracujące przeciwsobnie:
| Czas | Pin 1 | Pin 2 |
|---|---|---|
| 0…64 s | HIGH | LOW |
| 64…128 s | LOW | HIGH |
| potem cykl od nowa |
Czyli:
To nie wymaga programu użytkownika w czasie pracy — wystarczy dobrze skonfigurować timer.
W wielu odpowiedziach internetowych pojawia się pomysł użycia:
To działa funkcjonalnie, ale:
Dla okresu 128 s najlepszy wybór to:
bo są 32-bitowe.
Załóżmy typową konfigurację STM32F411:
Dobieramy wygodne taktowanie licznika:
\[
f_{CNT} = 2000\ \text{Hz}
\]
Wtedy jeden krok licznika trwa:
\[
T_{tick} = \frac{1}{2000} = 0.5\ \text{ms}
\]
Preskaler:
\[
PSC = \frac{84\,000\,000}{2000} - 1 = 41999
\]
Okres 128 s:
\[
ARR = 128 \cdot 2000 - 1 = 255999
\]
Połowa okresu, czyli 64 s:
\[
CCR = 64 \cdot 2000 = 128000
\]
Czyli finalnie:
PSC = 41999ARR = 255999CCR1 = 128000CCR2 = 128000Ustawiamy:
Dla tej samej wartości CCR dostajesz automatycznie sygnały komplementarne:
CNT < CCRCNT >= CCRW praktyce:
Dokładnie to, czego potrzebujesz.
Poniższy kod jest najbliższy pojęciu „sprzętowy”:
#include "stm32f4xx.h"
static void TIM2_Complementary_128s_Init(void)
{
/* 1. Zegary dla GPIOA i TIM2 */
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
/* Krótka zwłoka po włączeniu zegara */
(void)RCC->AHB1ENR;
(void)RCC->APB1ENR;
/* 2. PA0 = TIM2_CH1, PA1 = TIM2_CH2, AF1 */
GPIOA->MODER &= ~((3U << (0 * 2)) | (3U << (1 * 2)));
GPIOA->MODER |= ((2U << (0 * 2)) | (2U << (1 * 2))); // Alternate Function
GPIOA->OTYPER &= ~((1U << 0) | (1U << 1)); // Push-pull
GPIOA->OSPEEDR &= ~((3U << (0 * 2)) | (3U << (1 * 2))); // Low speed wystarczy
GPIOA->PUPDR &= ~((3U << (0 * 2)) | (3U << (1 * 2))); // No pull
GPIOA->AFR[0] &= ~((0xFU << (0 * 4)) | (0xFU << (1 * 4)));
GPIOA->AFR[0] |= ((0x1U << (0 * 4)) | (0x1U << (1 * 4))); // AF1 = TIM2
/* 3. Wyłączenie timera na czas konfiguracji */
TIM2->CR1 = 0;
TIM2->CR2 = 0;
TIM2->SMCR = 0;
TIM2->DIER = 0; // bez przerwań
/* 4. Parametry czasu:
TIM2CLK = 84 MHz
PSC = 41999 => licznik 2000 Hz
ARR = 255999 => okres 128 s
CCRx = 128000 => 64 s */
TIM2->PSC = 41999;
TIM2->ARR = 255999;
TIM2->CCR1 = 128000;
TIM2->CCR2 = 128000;
/* 5. Kanał 1 = PWM1, kanał 2 = PWM2 */
TIM2->CCMR1 = 0;
/* CH1: PWM mode 1 + preload */
TIM2->CCMR1 |= (6U << TIM_CCMR1_OC1M_Pos);
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;
/* CH2: PWM mode 2 + preload */
TIM2->CCMR1 |= (7U << TIM_CCMR1_OC2M_Pos);
TIM2->CCMR1 |= TIM_CCMR1_OC2PE;
/* 6. Polaryzacja normalna, włączenie wyjść CH1 i CH2 */
TIM2->CCER = 0;
TIM2->CCER |= TIM_CCER_CC1E;
TIM2->CCER |= TIM_CCER_CC2E;
/* 7. Preload ARR */
TIM2->CR1 |= TIM_CR1_ARPE;
/* 8. Załadowanie rejestrów do aktywnych shadow registers */
TIM2->EGR |= TIM_EGR_UG;
/* 9. Start timera */
TIM2->CR1 |= TIM_CR1_CEN;
}
int main(void)
{
/* Zakładam, że zegar systemowy jest już skonfigurowany
np. przez startup / SystemInit / CubeMX */
TIM2_Complementary_128s_Init();
while (1)
{
/* CPU nic nie musi robić */
__NOP();
}
}
Po uruchomieniu:
Okres:
\[
128\ \text{s}
\]
Wypełnienie:
\[
50\%
\]
Jeżeli chcesz, aby na starcie było:
to masz kilka możliwości:
CCxP,Najczyściej:
Jeżeli wolisz HAL, logika jest identyczna:
TIM_OCMODE_PWM1TIM_OCMODE_PWM2Prescaler = 41999Period = 255999Pulse = 128000Minimalny szkic:
htim2.Instance = TIM2;
htim2.Init.Prescaler = 41999;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 255999;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_PWM_Init(&htim2);
TIM_OC_InitTypeDef sConfigOC = {0};
/* CH1 */
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 128000;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
/* CH2 */
sConfigOC.OCMode = TIM_OCMODE_PWM2;
sConfigOC.Pulse = 128000;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
Do tego oczywiście GPIO:
PA0 i PA1AF_PPGPIO_AF1_TIM2W praktyce spotyka się dwie błędne sugestie:
Z punktu widzenia inżynierskiego:
W bardziej złożonych projektach taki generator często łączy się z:
Bo:
CNT z CCR,CCR = 128000, a nie 127999W STM32 tryb PWM działa na relacji:
CNT < CCRPrzy:
ARR = 255999256000Połowa z tego to:
128000To daje idealne 50%.
W tym zagadnieniu aspekty etyczne i prawne są zwykle drugorzędne, ale istnieją kwestie bezpieczeństwa technicznego:
to nie wolno zakładać, że „przeciwsobność logiczna” wystarczy.
PA0 = TIM2_CH1PA1 = TIM2_CH2PSC = 41999ARR = 255999CCR1 = CCR2 = 128000Nie podałeś:
To ma znaczenie, bo jeśli TIM2 nie jest taktowany 84 MHz, trzeba przeliczyć PSC.
Ogólny wzór:
\[
f{CNT} = \frac{f{TIM}}{PSC+1}
\]
\[
ARR = T{okres} \cdot f{CNT} - 1
\]
\[
CCR = T{HIGH} \cdot f{CNT}
\]
Jeżeli chcesz rozwinąć temat, warto sprawdzić:
ARR i CCR,Najpoprawniejsze rozwiązanie dla STM32F411 to:
PSC = 41999ARR = 255999CCR1 = CCR2 = 128000To daje:
Jeżeli chcesz, mogę od razu przygotować Ci: