Skocz do zawartości

STM32 Timery - bramkowanie, ext_pulse_count i łączenie kaskadowe


virtualny

Pomocna odpowiedź

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

Ż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:

master-slave.thumb.png.4e3dfafd7c6ed74ab8cc361a9855b03f.png

 

Do kompletu należało także znaleźć tabele "wewnętrznych połączeń timerów"

ITR.thumb.png.492c46306dd10c0dca2badba1066b063.png

 

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

 

 

 

 

  • Lubię! 1
Link do komentarza
Share on other sites

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.

real_app.thumb.jpg.13323695763c50146e20ba1dd620f78c.jpg

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.

TIM1_CONFIG.thumb.png.5fc88fee2ffaec98b8fc8a2a98d25679.png

TIM2_CONFIG.thumb.png.19017304da2f658d2bb619d816a816df.png

TIM3_CONFIG.thumb.png.fa4e9499f746e75706d2f911e262ba74.png

 

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ę:

CONSOLE.thumb.png.76815f04919a46f8e297e33306f99ea9.png

 

 

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

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ę »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.