Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

[STM32][C/CodeSourcery] realizacja delay

saturnim 31 Mar 2010 08:54 14390 13
Computer Controls
  • #1
    saturnim
    Level 9  
    Witam!
    Rozpoczynam przygodę z ARM i potrzebuje zrealizować funkcję(w sumie dwie) opóźniającą która będzie przyjmowała jako argument ilość us i ms. Wiem że można to zrealizować na pętlach tylko nie jestem w stanie przewidzieć ile czasu trwa jedna iteracja, można też użyć timerów tylko nie wiem czy to jest poprawne rozwiązanie. Może są jakieś gotowe funkcje z bibliotek standardowych ? a może macie jakieś swoje ciekawe implementacje delay_us i delay_ms ? Mój procesor jest taktowany częstotliwością 72 MHz. Proszę o pomoc.
    Kamery 3D Time of Flight - zastosowania w przemyśle. Darmowe szkolenie 16.12.2021r. g. 10.00 Zarejestruj się
  • Computer Controls
  • #2
    User removed account
    User removed account  
  • #4
    saturnim
    Level 9  
    OK w takim razie napisze to na Timerach ,a potem wrzucę to tutaj do oceny ::) (to może trochę potrwać :p )
  • Computer Controls
  • #5
    ksarim
    Level 15  
    Takich gotowych funkcji w bibliotekach standardowych nie ma.

    Kiedyś napisałem taką funkcję:
    Code:
    void delay_ms(u16 zp99)
    
    {
        // dla zegara 72MHz
        u32 i=0;
        u32 j=0;
        for(i=0;i<zp99*8;i++)
        {
            for(j=0;j<1000;j++)
            {
                asm volatile("nop");
                asm volatile("nop");
                asm volatile("nop");
                asm volatile("nop");
                asm volatile("nop");
                asm volatile("nop");
                asm volatile("nop");
            }
        }
    }

    Czuję, że tym prostym przykładem otworzę puszkę Pandory i zaraz koledzy zaczną wytykać mi błędy w myśleniu ale najwyżej też się czegoś przy okazji nauczę.

    Oczywiście ta pętla nie daje idealnego rezultatu. Założyłem, że pętla wewnętrzna trwa dokładnie 9000 cykli zegara(7000 cykli to co w pętli for, 1000 cykli na inkrementację zmiennej j, 1000 cykl na sprawdzenie warunku). Pętla zewnętrzna trwa w przybliżeniu 8*(argument funkcji) cykli. Całość w przybliżeniu miała dać 72000 cykli na 1ms. Na ile poprawne są moje założenia i na ile poprawne funkcja daje rezultaty nie jestem w stanie dokładnie powiedzieć.
  • #6
    Freddie Chopin
    MCUs specialist
    Nie mają zbytniego sensu hipotezy, bo takie założenia są piękne, ale bez potwierdzenia w rzeczywistości powiem tylko tyle:

    Dokumentacja firmy ARM wrote:
    NOP - Action: None, might not even consume any time.


    Trzeba pamiętać, że rdzeń Cortex ma dosyć mądrego pipeline'a, który ślicznie takie bezsensowne instrukcje pomija...

    4\/3!!
  • #7
    saturnim
    Level 9  
    Napisałem delay oparty o timer konfiguracja TIM1:
    Code:

    void TimerConfig()
    {
       RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
       TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

       TIM_TimeBaseStructure.TIM_ClockDivision=0;
       TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
       TIM_TimeBaseStructure.TIM_Period=72;
       TIM_TimeBaseStructure.TIM_Prescaler=1;

       TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure);

       TIM_OCInitTypeDef TIM_OCInitStructure;

         TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
         TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
         TIM_OCInitStructure.TIM_Pulse = 72;   // T= 1us!
         TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

       TIM_OC1Init(TIM1, &TIM_OCInitStructure);

       TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);
       TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);

         //TIM_Cmd(TIM1, ENABLE);
    }

    Chce uzyskać delay o wielokrotności us. Częstotliwość procka to 72 MHz dlatego period i pulse ustawiam na 72 czyli uzyskuje 1MHz, T=1us. Zwielokrotnienie tego okresu chciałem zrobić przez ustawianie preskalera.
    Code:

    void delayus(unsigned int us)
    {
       TIM1->CNT=0;   //zerowanie stanu licznika
       TIM1->PSC=us;   //ustawienie preskalera na żądaną wartość
       TIM_Cmd(TIM1, ENABLE); //uruchomienie licznika
       while(delayFlag!=0);   // oczekiwanie na zmiane stanu delayFlag
       delayFlag=1;
    }

    void delayms(unsigned int ms)
    {
       int i;
       for(i=0; i<=(ms-1);++i)
          delayus(1000);
    }

    void TIM1_CC_IRQHandler(void)
    {
       delayFlag=0;   //zerowanie flagi
       TIM_Cmd(TIM1, DISABLE); // wyłączenie timera
       TIM_ClearITPendingBit(TIM1,TIM_IT_CC1); //zerowanie bitu przerwania
    }

    Funkcja delayus ustawia preskaler, uruchamia licznik i wchodzi w pętle oczekującą na zmianę flagi delayFlag (która ma miejsce podczas obsługi przerwania od timera), wówczas pętla się kończy i program idzie dalej.
    Wydawało mi się że wszystko działa, ale jak próbuje obsłużyć magistrale OneWire to zaczynam mieć wątpliwości ;]. Jakieś sugestie ?
  • #8
    ksarim
    Level 15  
    Moim zdaniem na pewno powinno być:
    Code:
    TIM_TimeBaseStructure.TIM_Period=71
    
    TIM_TimeBaseStructure.TIM_Prescaler=0;

    Nie do końca rozumiem tryb pracy w jaki ustawiasz Timer1 bo nigdy nie używałem takiej konfiguracji jednak sprawdziłem też Twoje rozwiązanie z flagą i pętlą while czekającą na jej zmianę i co dziwne takie rozwiązanie u mnie nie działa. Założenia wydają się poprawne ale w praktyce u mnie program zawsze zatrzymuje się na tej pętli.

    Jeżeli chodzi o obsługę magistrali 1-Wire to ja funkcję opóźniającą zrobiłem podobnie jak w przykładzie, który przedstawiłem wyżej. U mnie komunikacja działa bez problemu.
  • #9
    DosinskY
    Level 18  
    Jeżeli nie potrzebujesz dokładnych opóźnień a takich Π•oko, to IMHO delay na pętlach w zupełności wystarczy. Timerów w stm32 niby wiele...ale prawie zawsze, w większych aplikacjach znajdzie się szczytniejsze dla nich zastosowanie.
    Z drugiej strony stosowanie pętli opóźniających na ARM-ach trąci profanacją :)
    Jeżeli nie interesuje Cie korzystanie z OS-ów, to jako alternatywę gorąco polecam Timer SysTick. Oszczędza on "poważne" timery + pozwala wykorzystać moc obliczeniową rdzenia. Jest bardzo łatwy w konfiguracji, koniec odliczania zgłasza przerwaniem i jest wystarczająco dokładny żeby realizować dobre opóźnienia milisekundowe. Z krótszymi niestety może być problem.

    Pozdrawiam

    EDIT:
    Z krótszymi nie będzie problemu. SysTick w STM32 jest dekrementowany co 111 ns.
  • #10
    saturnim
    Level 9  
    Ksarim, a ustawiłeś odpowiednio kontroler przerwań ?
  • #11
    ksarim
    Level 15  
    Raczej tak, wykorzystałem fragment programu, który jest sprawdzony. Użyłem Timera2 i zrobiłem dokładnie to samo co Ty czyli pętla while czekająca na wyzerowanie flagi. Nigdy program nie wyszedł z tej pętli chociaż program wchodził do przerwania bo sprawdzałem. Naprawdę nie umiem tego wyjaśnić.
  • #12
    Tantalos
    Level 18  
    A flaga jest zadeklarowana jako volatile?
  • #13
    markosik20
    Level 33  
    Spróbuj czegoś takiego.

    Code:
    void delay (u8 type,u16 d)
    
    {
        TIM3->CNT = d;
        if(type == ms)TIM3->PSC = 36000;
        else TIM3->PSC = 36;


        TIM3->CR1 = TIM_CounterMode_Down;// | TIM_OPMode_Single;
        TIM3->CR1 &= CR1_OPM_Reset;
             TIM3->CR1 |= TIM_OPMode;

        TIM3->CR1 |= CR1_CEN_Set;
        while((TIM3->SR & TIM_IT_Update) != (u16)RESET);
        TIM3->SR = (u16)~TIM_IT_Update;
    }
  • #14
    ksarim
    Level 15  
    Flaga nie była zadeklarowana jako volatile. Poprawiłem i działa.