Precyzyjne opóźnienia w połączeniu z trybami oszczędzania energii w STM32, część 2
W pierwszej części artykułu przedstawiono funkcje realizujące precyzyjne odmierzanie opóźnień w połączeniu z trybami oszczędzania energii dla mikrokontrolerów STM32 z rdzeniem Cortex-M3. W części drugiej omówiono konfigurowanie sygnałów zegarowych, przykładowy program testowy i układ, na którym wykonano testy. Zamieszczono też wyniki pomiarów prądu zasilania w poszczególnych trybach obniżonego poboru energii oraz rzeczywistych czasów opóźnień.
Dystrybucja sygnałów zegarowych w STM32
Aby wykorzystać opisane w pierwszej części artykułu funkcje opóźniające, musimy jeszcze skonfigurować oscylator kwarcowy mikrokontrolera oraz sygnały taktujące rdzeń i peryferie.
Rys. 1. Budowa systemu taktującego w STM32F107
Dystrybucję sygnałów zegarowych w STM32F107 w uproszczony sposób przedstawiono na rysunku 1. Rdzeń jest taktowany sygnałem HCLK, którego maksymalna częstotliwość wynosi 72 MHz. Peryferie są taktowane sygnałami PCLK1 i PCLK2 otrzymywanymi z HCLK za pomocą dzielników wstępnych (ang. prescalers) APB1 i APB2. Sygnały PCLK1 i PCLK2 mogą mieć maksymalne częstotliwości odpowiednio 36 i 72 MHz, dlatego ustawiamy wartości dzielników APB1 i APB2 odpowiednio na 2 i 1. Częstotliwość taktowania liczników TIM2, TIM3, TIM4 i TIM5 zależy od współczynnika podziału APB1. Jeśli ma on wartość 1, to liczniki te są taktowane sygnałem PCLK1 o tej samej częstotliwości co HCLK. Jeśli natomiast dzielnik APB1 ma wartość różną od 1, to PCLK1 ma częstotliwość APB1 razy mniejszą niż HCLK, ale omawiane liczniki są taktowane sygnałem o częstotliwości dwa razy większej niż PCLK1. W naszym przypadku APB1 ma wartość 2, zatem liczniki te są taktowane z częstotliwością HCLK.
List. 4. Plik nagłówkowy clock.h
#ifndef _CLOCK_H #define _CLOCK_H 1 void AllPinsDisable(void); int ClockConfigure(unsigned); #endif
Sygnały taktujące są konfigurowane za pomocą funkcji ClockConfigure, której argumentem jest częstotliwość HCLK w MHz, jaką chcemy ustawić. Dopuszczalne wartości argumentu to 12, 14, 16, 18, 24, 28, 32, 36, 48, 56, 64, 72 MHz. Funkcja ta zwraca zero, gdy konfiguracja powiedzie się lub wartość ujemną, gdy podano błędną wartość argumentu, gdy nie uda się wystartować oscylatora kwarcowego HSE lub któraś z PLL nie zsynchronizuje się. Odpowiednia deklaracja jest umieszczona na listingu 4. Znajduje się tam też deklaracja funkcji AllPinsDisable, która konfiguruje wszystkie porty wejścia-wyjścia, tak aby zminimalizować pobór prądu przez mikrokontroler. Implementacja zamieszczona jest na listingu 5.
List. 5. Implementacja konfiguracji sygnałów taktujących, umieszczona w pliku clock.c
#include "check.h" #include "clock.h" #include #include #include #ifndef STM32F10X_CL #error Przykład jest dla STM32F107. #endif /* Konfiguruj wszystkie wyprowadzenia w analogowym trybie wejściowym (ang. analog input mode, trigger off), co redukuje zużycie energii i zwiększa odporność na zakłócenia elektromagnetyczne. */ void AllPinsDisable() { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_All; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStruct.GPIO_Speed = 0; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_Init(GPIOC, &GPIO_InitStruct); GPIO_Init(GPIOD, &GPIO_InitStruct); GPIO_Init(GPIOE, &GPIO_InitStruct); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, DISABLE); } #ifndef HSE_VALUE #error W projekcie nie zdefiniowano częstotliwości kwarcu. #endif #if HSE_VALUE == 25000000 #define RCC_PREDIV2_DivX RCC_PREDIV2_Div5 #elif HSE_VALUE == 20000000 #define RCC_PREDIV2_DivX RCC_PREDIV2_Div4 #elif HSE_VALUE == 15000000 #define RCC_PREDIV2_DivX RCC_PREDIV2_Div3 #elif HSE_VALUE == 10000000 #define RCC_PREDIV2_DivX RCC_PREDIV2_Div2 #elif HSE_VALUE == 5000000 #define RCC_PREDIV2_DivX RCC_PREDIV2_Div1 #else #error Błędna wartość HSE_VALUE #endif int ClockConfigure(unsigned freqMHz) { static const int maxAttempts = 1000000; RCC_DeInit(); RCC_HSEConfig(RCC_HSE_ON); /* Wykonuje maksymalnie HSEStartUp_TimeOut = 1280 prób. */ if (RCC_WaitForHSEStartUp() == ERROR) return -1; FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); if (freqMHz > 48) FLASH_SetLatency(FLASH_Latency_2); else if (freqMHz > 14) /* Dokumentacja mówi o 24 MHz. */ FLASH_SetLatency(FLASH_Latency_1); else FLASH_SetLatency(FLASH_Latency_0); if (freqMHz >= 48) /* preskaler AHB, HCLK = SYSCLK = freqMHz */ RCC_HCLKConfig(RCC_SYSCLK_Div1); else if (freqMHz >= 24) /* preskaler AHB, HCLK = SYSCLK / 2 = freqMHz */ RCC_HCLKConfig(RCC_SYSCLK_Div2); else /* preskaler AHB, HCLK = SYSCLK / 4 = freqMHz */ RCC_HCLKConfig(RCC_SYSCLK_Div4); /* preskaler APB1, PCLK1 = HCLK / 2 = freqMHz / 2 */ RCC_PCLK1Config(RCC_HCLK_Div2); /* preskaler APB2, PCLK2 = HCLK = freqMHz */ RCC_PCLK2Config(RCC_HCLK_Div1); /* PLL2: PLL2CLK = HSE / RCC_PREDIV2_DivX * 8 = 40 MHz */ RCC_PREDIV2Config(RCC_PREDIV2_DivX); RCC_PLL2Config(RCC_PLL2Mul_8); RCC_PLL2Cmd(ENABLE); active_check(RCC_GetFlagStatus(RCC_FLAG_PLL2RDY), maxAttempts); /* PLL1: PLLCLK = (PLL2 / 5) * RCC_PLLMul */ RCC_PREDIV1Config(RCC_PREDIV1_Source_PLL2, RCC_PREDIV1_Div5); if (freqMHz == 48 || freqMHz == 24 || freqMHz == 12) RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_6); else if (freqMHz == 56 || freqMHz == 28 || freqMHz == 14) RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_7); else if (freqMHz == 64 || freqMHz == 32 || freqMHz == 16) RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_8); else if (freqMHz == 72 || freqMHz == 36 || freqMHz == 18) RCC_PLLConfig(RCC_PLLSource_PREDIV1, RCC_PLLMul_9); else return -1; RCC_PLLCmd(ENABLE); active_check(RCC_GetFlagStatus(RCC_FLAG_PLLRDY), maxAttempts); /* Ustaw SYSCLK = PLLCLK i czekaj aż PLL zostanie ustawiony jako zegar systemowy. */ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); active_check(RCC_GetSYSCLKSource() == 0x08, maxAttempts); return 0; }