W tym poście chciałbym opisać sposób generowania sygnału zegarowego z pinu PA8 dla STM32H725.
Będziemy wykorzystywać MCO czyli Microcontroller Clock Output. Jest to wyjściowy zegar mikrokontrolera. Dzięki niemu można wyprowadzić sygnał zegarowy na pin np. w celu podania sygnału zegarowego na inne układy. Jest ono przydatne w sytuacjach gdy potrzebujemy dostarczyć sygnał zegarowy do zewnętrznego układu, chcemy sprawdzić rzeczywistą częstotliwość zegara mikrokontrolera czy chcemy zsynchronizować kilka układów jednym źródłem zegara.
Domyślnie MCO1 podaje sygnał na PA8, MCO2 na PC9. Należy tutaj pamiętać, że maksymalna częstotliwość wyjściowa MCO jest ograniczona parametrami pinu GPIO, który wynosi 50MHz. Przy wyższych częstotliwościach sygnał może ulec degradacji, co pogorszy jakość generowanego sygnału.
Jeśli pod MCO podłączone będzie duże obciążenie z dużą pojemnością np. długie ścieżki czy kilka układów. to może pojawić się zniekształcenie sygnału.
Można trochę zapobiegać degradacji sygnału przez ustawienie GPIO_SPEED jako VERY HIGH. Można dodawać rezystor szeregowy np. 22Ohm czy 33Ohm, blisko pinu MCU. Popawi on niedopasowanie impedancji ścieźki. Należy także prowadzić krótkie ścieżki sygnałowe. Pozwoli to zmniejszenie pojemności i poprawienie parametrów sygnału.
Dodatkowo można użyć np. układu 74LVC1G125, który jest jednokanałowym buforem sygnału. Pomoże on w wyostrzeniu zboczy i poprawieniu jakości sygnału na większe obciążenia. Izoluje też MCU od obciążenia. Przez co całe obciążenie zamiast na STM'a idzie na układ bufora.
MCO1 pozwala na przekazywanie następujących sygnałów zegarowych:
- #define RCC_MCO1SOURCE_HSI (0x00000000U)
- #define RCC_MCO1SOURCE_LSE RCC_CFGR_MCO1_0
- #define RCC_MCO1SOURCE_HSE RCC_CFGR_MCO1_1
- #define RCC_MCO1SOURCE_PLL1QCLK ((uint32_t)RCC_CFGR_MCO1_0 | RCC_CFGR_MCO1_1)
- #define RCC_MCO1SOURCE_HSI48 RCC_CFGR_MCO1_2
MCO2 natomiast na:
- #define RCC_MCO2SOURCE_SYSCLK (0x00000000U)
- #define RCC_MCO2SOURCE_PLL2PCLK RCC_CFGR_MCO2_0
- #define RCC_MCO2SOURCE_HSE RCC_CFGR_MCO2_1
- #define RCC_MCO2SOURCE_PLLCLK ((uint32_t)RCC_CFGR_MCO2_0 | RCC_CFGR_MCO2_1)
- #define RCC_MCO2SOURCE_CSICLK RCC_CFGR_MCO2_2
- #define RCC_MCO2SOURCE_LSICLK ((uint32_t)RCC_CFGR_MCO2_0 | RCC_CFGR_MCO2_2)
Do tego można stosować odpowiednie dzielniki częstotliwości:
- #define RCC_MCODIV_1 RCC_CFGR_MCO1PRE_0
- #define RCC_MCODIV_2 RCC_CFGR_MCO1PRE_1
- #define RCC_MCODIV_3 ((uint32_t)RCC_CFGR_MCO1PRE_0 | RCC_CFGR_MCO1PRE_1)
- #define RCC_MCODIV_4 RCC_CFGR_MCO1PRE_2
- #define RCC_MCODIV_5 ((uint32_t)RCC_CFGR_MCO1PRE_0 | RCC_CFGR_MCO1PRE_2)
- #define RCC_MCODIV_6 ((uint32_t)RCC_CFGR_MCO1PRE_1 | RCC_CFGR_MCO1PRE_2)
- #define RCC_MCODIV_7 ((uint32_t)RCC_CFGR_MCO1PRE_0 | RCC_CFGR_MCO1PRE_1 | RCC_CFGR_MCO1PRE_2)
- #define RCC_MCODIV_8 RCC_CFGR_MCO1PRE_3
- #define RCC_MCODIV_9 ((uint32_t)RCC_CFGR_MCO1PRE_0 | RCC_CFGR_MCO1PRE_3)
- #define RCC_MCODIV_10 ((uint32_t)RCC_CFGR_MCO1PRE_1 | RCC_CFGR_MCO1PRE_3)
- #define RCC_MCODIV_11 ((uint32_t)RCC_CFGR_MCO1PRE_0 | RCC_CFGR_MCO1PRE_1 | RCC_CFGR_MCO1PRE_3)
- #define RCC_MCODIV_12 ((uint32_t)RCC_CFGR_MCO1PRE_2 | RCC_CFGR_MCO1PRE_3)
- #define RCC_MCODIV_13 ((uint32_t)RCC_CFGR_MCO1PRE_0 | RCC_CFGR_MCO1PRE_2 | RCC_CFGR_MCO1PRE_3)
- #define RCC_MCODIV_14 ((uint32_t)RCC_CFGR_MCO1PRE_1 | RCC_CFGR_MCO1PRE_2 | RCC_CFGR_MCO1PRE_3)
- #define RCC_MCODIV_15 RCC_CFGR_MCO1PRE
Aby wygenerować sygnał zegarowy wyjściowy wykorzystuje MCO1, który wygeneruje sygnał zegarowy na pin PA8.
Do układu mam podłączony rezonator kwarcowy 25MHz. Jeśli chcemy go przekazać na ten pin to można wykonać następującą operację z dzielnikiem. W przykładzie dzielnik ustawiony na 2.
void InitMCO1_12_5MHz(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF0_MCO;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// HSE/2 = 12,5 MHz
HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_2);
}
Jeśli jako źródło dla sygnału zegarowego wykorzystujemy HSE to należy pamietać, że musi on być wcześniej uruchomiony i stabilny. Modyfikując dzielnik otrzymamy częstotliwość podzieloną od wejściowej wartości 25MHz.
Dodatkowo jako źródło zegara można wykorzystać inne źródła jak SYSCLK:
- void InitMCO2_SYSCLK(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct = {0};
- __HAL_RCC_GPIOC_CLK_ENABLE();
- GPIO_InitStruct.Pin = GPIO_PIN_9;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
- GPIO_InitStruct.Alternate = GPIO_AF0_MCO;
- HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
- //np. SYSCLK 400MHz/10 = 40MHz
- HAL_RCC_MCOConfig(RCC_MCO2, RCC_MCO2SOURCE_SYSCLK, RCC_MCODIV_10);
- }
Dla MCO1, jeśli chcemy wyprowadzić sygnał PLL1QCLK to wyglądało by to tak:
- void InitMCO1_PLL1Q(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct = {0};
- __HAL_RCC_GPIOA_CLK_ENABLE();
- GPIO_InitStruct.Pin = GPIO_PIN_8;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
- GPIO_InitStruct.Alternate = GPIO_AF0_MCO;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_PLL1QCLK, RCC_MCODIV_10);
- }
Dzięki wyprowadzeniu sygnału jesteśmy w stanie sprawdzić czy PLL jest poprawnie ustawiony, i czy uzyskujemy odpowiednią częstotliwość bazową.
Jak nie MCO to jak:
Alternatywnie w celu wygenerowania odpowiedniego sygnału zegarowego należy wykorzystać generator sygnału zegarowego.
Jeśli chcemy zweryfikować taki sygnał to potrzebujemy np. oscyloskopu. Najdokładniej pokaże nam wygenerowany przebieg sygnału. Można także ustawić jeden z timerów w trybie Input Capture.
Podsumowując, MCO jest szybkim sposobem na wyprowadzenie zegara z mikrokontrolera i weryfikację konfiguracji PLL. Przy wyższych częstotliwościach należy pamiętać o ograniczeniach pinu, obciążeniu oraz poprawnym prowadzeniu sygnału.