virtualny Napisano Marzec 6, 2023 Udostępnij Napisano Marzec 6, 2023 Witam, Moje pytanie dotyczy pomiaru częstotliwości, który można (najlepiej programowo) włączać i przerywać po określonym czasie, jednocześnie nie tracąc zawartości zliczonej przez timer. Nie znalazłem takiego rozwiązania, jakie bym chciał, a rozwiązania jakie spotkałem (m.in. AVT5398) bramkowały timer za pomocą zewnętrznej logiki dołączonej do pinów procesora w postaci układu TTL. Ponadto użyty w tym mierniku procesor jest już dość archaiczny i rzadko spotykany. Co chcę uzyskać? Zmierzyć ilość impulsów (częstotliwość) na danym pinie napędzającym jakiś timer, w zadanym oknie czasowym na przykład 1s. Chodzi mi o to, żeby odbyło się to "automatycznie", czyli żeby włączyć rozpoczęcie zliczania zewnętrznych impulsów pędzących timer i po zaprogramowanym czasie zaprzestać zliczania tych impulsów, jednakże mieć możliwość odczytania ilości impulsów zliczonych w danym oknie czasowym. Tu pojawia się kolejny problem, ponieważ sygnał prostokątny jest w okolicach 1 MHz, co oznaczałoby, że "zwykłego" 16 bitowego timera do tego "nie wystarczy", więc przydałby się albo 24 bitowy timer, który można zaprogramować na bramkowanie, albo dwa timery 16 bitowe połączone kaskadowo. Oczywiście wiem, że można by ten problem rozwiązać półśrodkiem, który by wywoływał przerwanie po zadanym okresie zliczania i w tym przerwaniu można by odczytać zawartość rejestru timera, natomiast chodzi mi o rozwiązanie bardziej "automatyczne" i wygodne - godne tych new age procesorów. Link do komentarza Share on other sites More sharing options...
virtualny Marzec 20, 2023 Autor tematu Udostępnij Marzec 20, 2023 Żebym nie zapomniał, póki co udało mi się połączyć kaskadowo dwa timery jako jeden 32 bitowy timer. Żeby było prosto przykład i program działający zamieszczam dla "blupila". Ogólnie w STM32 można to zrobić prawdopodobnie na każdym ARM'ie. Temat timerów jest mocno skomplikowany z uwagi na bardzo rozbudowane ich możliwości - po prostu cały ich "gąszcz". Moim największym przyjacielem był RM, gdzie w dziale TIMERS znalazłem tekst: "Using one timer as prescaler for another timer" z poniżej zamieszczonym rysunkiem: Do kompletu należało także znaleźć tabele "wewnętrznych połączeń timerów" Pod rysunkiem "Master/Slave timer example" zamieszczono opis, jak połączyć kaskadowa dwa timery, w którym brakuje tylko przykładowego kodu, który pozwoliłem sobie zamieścić (raptem 4 linie) : Timer synchronization Note: The clock of the slave timer must be enabled prior to receiving events from the master timer, and must not be changed on-the-fly while triggers are received from the master timer. * Configure Timer 1 in master mode so that it outputs a periodic trigger signal on each update event UEV. If you write MMS=010 in the TIM1_CR2 register, a rising edge is output on TRGO1 each time an update event is generated. * To connect the TRGO1 output of Timer 1 to Timer 2, Timer 2 must be configured in slave mode using ITR0 as internal trigger. You select this through the TS bits in the TIM2_SMCR register (writing TS=000). TIM1->CR2 = 0x20; // MMS = [010] = master mode to TIM2 * Then you put the slave mode controller in external clock mode 1 (write SMS=111 in the TIM2_SMCR register). This causes Timer 2 to be clocked by the rising edge of the periodic Timer 1 trigger signal (which correspond to the timer 1 counter overflow). TIM2->SMCR = 0x07UL; // TS=[000], SMS=[111] => [TS = ITR0 from TIM1] and // [SMS = External Clock mode 1 - Rising edges of the // selected trigger (TRGI) clock the counter.] * Finally both timers must be enabled by setting their respective CEN bits (TIMx_CR1 register). // Run TIMER1 and TIMER2 as 32 bit counter TIM2->CR1 |= TIM_CR1_CEN; TIM1->CR1 |= TIM_CR1_CEN; Note: If OCx is selected on Timer 1 as trigger output (MMS=1xx), its rising edge is used to clock the counter of timer 2. Gwoli ścisłości dodam że przed owymi 4ma liniami należy zainicjować oba timery wartością preskalera PSC i przeładowania ARR, a najpierw włączyć taktowanie timerów: // Enable system CLOCK for TIMER1 and preset TIMER1 RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; TIM1->PSC = 0U; TIM1->ARR = 0xFFFF; // Enable system CLOCK for TIMER2 and preset TIMER2 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; TIM2->PSC = 0U; TIM2->ARR = 0xFFFF; Poniżej zamieszczam przykładowy program. Co takiego ten programi robi? 1. Inicjuje blupila do taktowania 72Mhz 2. Inicjije GPIO PC13 do mrugania diodą 3 Konfiguruje TIMER1 i TIMER2 jako master/slave, pozwalając odliczać cykle taktujące rdzeń ARM'a 4. W pętli głównej program sprawdza czy nastąpiło przepełnienie TIMER2, który odpowiada za najstarsze 16 bitów kaskadowego, 32bitowego timera. Jeżeli wystąpiło przepełnienie, zmieniany jest kolor diody na PC13. Przy 72 MHz, czyli przy 72000000 cykli na sekundę 32bitowy timer "przekręca się" co około 59 sekund (0xFFFFFFFF / 72 000 000 = 59). Program zajmuje 676 bajtów, nieużywa bibliotek HAL|LL, jest bare metal i wygląda tak: /** *************************** * @file : main.c *************************** */ #include "stm32f1xx.h" static inline void rcc_HSE_config(void); int main(void); /************************************************************************* * This program make 32bit TIMER from TIMER1 and TIMER2 *************************************************************************/ /** * @brief HSE Configuration */ static inline void rcc_HSE_config(void) { /* * Configuration parameters --> STMF103 Clock Tree * * HSE = 8MHz (High Speed External Clock) * PLL_M = 9 (9*8 = 72[MHz]) * USB prescaler = 1.5 (for 48[MHz]) * AHB prescaler = 1 * Cortex prescaler = 1 * --> 72[MHz] System Clock * * APB1 prescaler = 2 --> (36[MHz] from 72[MHz]) * APB2 prescaler = 1 --> 72[MHz] * ADC prescaler = 6 --> 12[MHz] for ADC conversion * */ /* PLL Configuration */ // PLL = 9 //RCC->CFGR &= ~ //Clear bitfields [21:18] 11111111111000011111111111111111 /* USB prescaler = 1.5 (for 48[MHz]) */ RCC->CFGR &= ~(RCC_CFGR_PLLMULL_Msk | RCC_CFGR_USBPRE); RCC->CFGR |= (7UL << 18); /* HSE Oscillator */ // Enable HSE Oscillator RCC->CR |= RCC_CR_HSEON; // Wait for it to stabilize while((RCC->CR & RCC_CR_HSERDY) == 0); //Select HSE as PLL source RCC->CFGR |= RCC_CFGR_PLLSRC; //Enable PLL RCC->CR |= RCC_CR_PLLON; //Wait for PLL ready while((RCC->CR & RCC_CR_PLLRDY) == 0); //Flash pre-fetch enable and wait-state=2 //0WS: 0-24MHz //1WS: 24-48MHz //2WS: 48-72MHz FLASH->ACR = 0; FLASH->ACR |= (FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_1); //Select PLL as main System Clock source RCC->CFGR &= (RCC_CFGR_SW); RCC->CFGR |= RCC_CFGR_SW_1; //Wait for active clock source while((RCC->CFGR & RCC_CFGR_SWS_1) == 0); /* Peripherials */ //AHB Prescaler = 1 //APB1 prescaler //APB2 prescaler /* ADC Prescaler to 12[MHz] */ RCC->CFGR &= ~(RCC_CFGR_HPRE | RCC_CFGR_PPRE1 | RCC_CFGR_PPRE2 | RCC_CFGR_ADCPRE); RCC->CFGR |= (RCC_CFGR_PPRE1_2 | RCC_CFGR_ADCPRE_1); } int main(void) { //uint32_t *ptr; //ptr = (uint32_t *) 0xE000E018U; //printf("SystTick = 0x%.8lX\r\n" , ((*(uint32_t *) 0xE000E018U) & 0x00FFFFFF) ); //****************************************** // for blinking code (PC13) //****************************************** #define GPIOC_CRH_VALUE 0x44144444UL #define GPIOC_ODR_ODR13 0x00002000UL RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; GPIOC->CRH = GPIOC_CRH_VALUE; GPIOC->ODR = 0U; /* turn on the LED on PC13 */ //****************************************** /* Max CLOCLK of 72[MHz] */ rcc_HSE_config(); //******************************************* // Enable system CLOCK for TIMER1 and preset TIMER1 RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; TIM1->PSC = 0U; TIM1->ARR = 0xFFFF; // Enable system CLOCK for TIMER2 and preset TIMER2 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; TIM2->PSC = 0U; TIM2->ARR = 0xFFFF; /* * MASTER/SLAVE TIMERS CONFIGURATION - TIMER1 AND TIMER2 AS 32 BIT COUNTER * * Configure Timer 1 in master mode so that it outputs a periodic trigger signal on each update event UEV. If you write MMS=010 in the TIM1_CR2 register, a rising edge is output on TRGO1 each time an update event is generated. * To connect the TRGO1 output of Timer 1 to Timer 2, Timer 2 must be configured in slave mode using ITR0 as internal trigger. You select this through the TS bits in the TIM2_SMCR register (writing TS=000). * Then you put the slave mode controller in external clock mode 1 (write SMS=111 in the TIM2_SMCR register). This causes Timer 2 to be clocked by the rising edge of the periodic Timer 1 trigger signal (which correspond to the timer 1 counter overflow). * Finally both timers must be enabled by setting their respective CEN bits (TIMx_CR1 register). Note: If OCx is selected on Timer 1 as trigger output (MMS=1xx), its rising edge is used to clock the counter of timer 2. */ TIM1->CR2 = 0x20; // MMS = [010] = master mode to TIM2 TIM2->SMCR = 0x07UL; // TS=[000], SMS=[111] => [TS = ITR0 from TIM1] and // [SMS = External Clock mode 1 - Rising edges of the selected trigger // (TRGI) clock the counter.] // Run TIMER1 and TIMER2 as 32 bit counter TIM2->CR1 |= TIM_CR1_CEN; TIM1->CR1 |= TIM_CR1_CEN; while(1) { /* wait for overflow of TIMER2 (arround 59sec.) */ while(((uint32_t) TIM2->CNT & 0xF000) != 0x1000); while(((uint32_t) TIM2->CNT & 0xF000) != 0x0000); /* And one time per 59sec. blink the LED on PC13 */ GPIOC->ODR ^= GPIOC_ODR_ODR13; } } /************************************************/ W załączniku gotowy projekt z CubeIDE TIMER05_CASCADE.ZIP 1 Link do komentarza Share on other sites More sharing options...
virtualny Czerwiec 19, 2023 Autor tematu Udostępnij Czerwiec 19, 2023 Zwycięstwo! Udało mi się zrobić co chciałem… Nie było to łatwe. Tym razem moim najlepszym przyjacielem był CubeMX, który powie mi prawdę np. „Ile timerów ma STM32F103C8T6?”, a nie jak w dejtaszitach specjaliści od marketingu podają „UP TO … X timers”. Bardzo pomocny był także słynny na cały internet poradnik Szczywronka. Drugą bezcenną właściwością CubeMX jest możliwość obejrzenia wszystkich możliwych ustawień timerów. Dzięki temu można bardzo szybko zmieniać ich ustawienia i kompilować program. W taki sposób gmerając w ustawieniach timerów udało mi się bramkować kaskadowy timer, kolejnym timerem. Taka opcja jest naprawdę nie do przecenienia – wyobraźmy sobie że chcemy dokładnie zmierzyć ilość impulsów na pinie w jednostce czasu – tu w grę wchodzą takie zagadnienia jak czas przyjęcia przerwania, ilość wait state’ów – podczas gdy mogą dochodzić kolejne impulsy, zwłaszcza przy pomiarach szybkich sygnałów. Tutaj bramkując timer po zadanym czasie z dokładnością do 1 cyklu zegarowego zliczający timer zostaje zatrzymany, a w jego rejestrach pozostaje zachowana ilość zliczonych impulsów. Do najprostszej realizacji takiego zadania potrzebowałem dwóch bluepill’ów. Jeden zaprogramowałem jako generator przebiegu prostokątnego 1MHz z 50% duty cycle, drugi posłużył jako miernik częstotliwości tego sygnału. Najpierw w bare metal udało mi się zrobić timer połączony kaskadowo, który zliczał impulsy z PA8 (Channel 1) timera 1. Jak się później okazało jeżeli chce się bramkować timer, wówczas już nie można zliczać impulsów z Channel 1 (PA8), ale należy do tego celu wykorzystać pin skojarzony jako wejście ETR dla zadanego timera. W tym przypadku dla T1 był to pin PA12, który z uwagi na jego użycie z USB był dość nieporęczny. Wobec tego zmieniłem rozkład i „pierwszym” timerem zliczającym impulsy zewnętrzne został T2, który wejście ETR ma na pinie PA0. Rozkład użycia timerów jest następujący: - TIMER 2 jest timerem zliczającym impulsy zewnętrzne i jest timerem nadrzędnym dla TIMER1, jednocześnie TIMER 2 pracuje w trycie SLAVE dla TIMER3, który bramkuje (uruchamia i zatrzymuje) TIMER2 (tym samym także podrzędny dla T2 TIMER1 połączony kaskadowo z T2). - TIMER 1 – to najstarsze 16 bitów z 32bitowego timera utworzonego kaskadowo z T2. Najkrócej ujmując T1 zlicza przepełnienia T2. - TIMER 3 działa na kanale 1 PWM Generation No Output zliczając impulsy Internal Clock (72 [MHz]) produkuje „wewnętrznie” falę prostokątną (wewnętrznie – dlatego PWM Generation No Output) o wypełnieniu 10/11 w okresie 1.1s. To oznacza, że TIMER 3 uruchamia T2 z T1 na okres 1 sekundy pozwalając zliczać impulsy z wejścia ETR (PA0), by następnie zatrzymać T2 z T1 na 0.1 sekundy. Po tym czasie TIMER 3 zaczyna cykl od nowa. W tym wypadku (oczywiście można inaczej to zrobić) program śledząc zawartość TIM3->CNT wie, kiedy może odczytać wartość T2_T1->CNT (kiedy bramka dla T1_T2 jest „wyłączona/zatrzymana/OFF”). Trudno tutaj wymieniać jakie ustawienia timerów wykorzystano, zamiast tego bardziej przydatne będą screeny z CubeMX. Można dodać taką informację, że nie każdy timer ma skojarzone wejście ETR - w przypadku STM32F103C8T6 taki ficzer mają tylko T1 i T2... albo że kaskadowo można połączyć więcej niż 2 timery. Do debugowania wspomagałem się „wyrzucaniem” interesujących mnie danych na konsolę: Funkcja main() wygląda tak: int main(void) { /* USER CODE BEGIN 1 */ uint32_t data = 0; uint32_t data2 = 0; uint32_t i=0 ; uint32_t data3, data4; int first_timer, second_timer, third_timer; //****************************************** // for blinking code (PC13) //****************************************** #define GPIOC_CRH_VALUE 0x44144444UL #define GPIOC_ODR_ODR13 0x00002000UL RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; GPIOC->CRH = GPIOC_CRH_VALUE; BB(GPIOC->ODR, GPIO_ODR_ODR13) = 1; // Disable user diode //****************************************** /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_TIM1_Init(); MX_TIM2_Init(); MX_TIM3_Init(); /* USER CODE BEGIN 2 */ /* UART Configuration */ uart_UART1_GPIO_config(); uart_UART1_config(); SysTick_Config(8000); static const char clr_scr[] = {"\033[2J\033[3J\033[92m\033[0;0f ...\r\n" \ "\033[7;0fEXT Ticks going from TIMER2_ETR2 which is PA0" }; printf(clr_scr); //printf(info1); // Enable counters TIM1->CR1 = 1; // high 16 bytes of 32 bit value of cascade counter TIM2->CR1 = 1; // low 16 bytes of 32 bit value of cascade counter TIM3->CR1 = 1; // start gating time of TIMER1 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { //****************************** BB(GPIOC->ODR, GPIO_ODR_ODR13) ^= 1; // BLINK user diode //****************************** // wait for gate off time while(TIM3->CNT < 10003); // now read gate time and 32bit cascade timer value first_timer = (TIM3->CNT & 0xFFFF); data = (TIM1->CNT << 16); data |= (TIM2->CNT & 0xFFFF); // repetition read counter i++; // calculate how many tick's incremented data2 = data - data2; // just for check gating time is work // wait for later gate off time while(TIM3->CNT < 10050); // now read gate time and 32bit cascade timer value second_timer = (TIM3->CNT & 0xFFFF); data3 = (TIM1->CNT << 16); data3 |= (TIM2->CNT & 0xFFFF); // print info and statistic our 32bit timer valueas and gating time printf("\033[1;0f " ); printf("\033[1;0fAfter %d seconds" , (int) i); printf("\033[2;0fTIM3->CNT = %d" , first_timer) ; printf("\033[3;0f " ); printf("\033[3;0fTimer Value = %d" , (int) data); printf("\033[4;0fTIM3->CNT = %d [Under GATING OFF time]" , second_timer) ; printf("\033[5;0f " ); printf("\033[5;0fTimer Value = %d [GATE OFF]" , (int) data3); printf("\033[6;0f " ); printf("\033[6;0f1s. EXT Ticks Count = %d" , (int) data2); // for future incremented tick's calculations data2 = data; // show most interesting timers registers values reg_print(); // just for check gating time is work // wait for 10% gate on time while(TIM3->CNT > 500); while(TIM3->CNT < 500); // now read gate time and 32bit cascade timer value third_timer = (TIM3->CNT & 0xFFFF); data4 = (TIM1->CNT << 16); data4 |= (TIM2->CNT & 0xFFFF); // print info and statistic our 32bit timer valueas and gating time printf("\033[17;0fTIM3->CNT = %d [Under 5%% GATE ON time]" , third_timer) ; printf("\033[18;0f " ); printf("\033[18;0fTimer Incremented = %d [GATE ON]" , (int) (data4-data2)); printf("\033[19;0fTIM3->CNT = %d" , (int) TIM3->CNT) ; printf("\r\n"); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ } W załączniku program z CubeIDE TIMERS_TRY.ZIP Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »