Elektroda.pl
Elektroda.pl
X
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

STM32 F0 discovery - Timer 1

lycon5 09 Dec 2013 14:06 2928 8
  • #1
    lycon5
    Level 11  
    Witam,
    już dobrych kilka godzin zastanawiam się, dlaczego timer 1 nie działa tak jak chcę. A chcę, poprzez poniżej przedstawione funkcje włączać żarówkę po odliczeniu przez timer na czas też odliczany przez timer.
    Konfiguracja timera 1:
    
    void Tim1_config(void)
    {
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
    TIM1->CR1 |= TIM_CR1_ARPE;
    TIM1->CR1 &= ~TIM_CR1_DIR;
    TIM1->ARR = 65535;
    TIM1->PSC = 48; // taktowanie mam 48MHz
    TIM1->CR1 |= TIM_CR1_CEN;
    NVIC_EnableIRQ(TIM1_CC_IRQn);
    }
    

    Funkcja włączająca dany kanał timera, i odliczająca daną wartość czasu
    
    void Tim1_canal_start(int us_1, int kanal)
    {
    	long int tmp=0;
    
    	if(us_1>10000)
    	{
    		us_1=10000; //ograniczenie do 10ms
    	}
    
    	if (kanal==1)
    	{
    		TIM1->DIER |= TIM_DIER_CC1IE; // włączenie przerwania od 1 kanału trybu compare
    		tmp= TIM1->CNT;
    
    		if ((tmp+us_1)>65535)
    		{
    		TIM1->CCR1 = (tmp + us_1)%65535;
    		}
    		else
    		{
    		TIM1->CCR1 = tmp + us_1;
    		}
    	}
    	if (kanal==2)
    	{
    		TIM1->DIER |= TIM_DIER_CC2IE;
    		tmp= TIM1->CNT;
    
    		if ((tmp+us_1)>65535)
    		{
    		TIM1->CCR2 = (tmp + us_1)%65535;
    		}
    		else
    		{
    		TIM1->CCR2 = tmp + us_1;
    		}
    	}
    	if (kanal==3)
    	{
    		TIM1->DIER |= TIM_DIER_CC3IE;
    		tmp= TIM1->CNT;
    
    		if ((tmp+us_1)>65535)
    		{
    		TIM1->CCR3 = (tmp + us_1)%65535;
    		}
    		else
    		{
    		TIM1->CCR3 = tmp + us_1;
    		}
    	}
    	if (kanal==4)
    	{
    		TIM1->DIER |= TIM_DIER_CC4IE;
    		tmp= TIM1->CNT;
    
    		if ((tmp+us_1)>65535)
    		{
    		TIM1->CCR4 = (tmp + us_1)%65535;
    		}
    		TIM1->CCR4 = tmp + us_1;
    	}
    }
    

    i funkcja wyłączająca dany kanał:
    
    void Tim1_canal_stop(int kanal)
    {
    	if (kanal==1)
    	{
    		TIM1->DIER &= ~TIM_DIER_CC1IE;
    	}
    	if (kanal==2)
    	{
    		TIM1->DIER &= ~TIM_DIER_CC2IE;
    	}
    	if (kanal==3)
    	{
    		TIM1->DIER &= ~TIM_DIER_CC3IE;
    	}
    	if (kanal==4)
    	{
    		TIM1->DIER &= ~TIM_DIER_CC4IE;
    	}
    }
    

    Obsługa przerwania
    
    void TIM1_CC_IRQHandler(void)
    {
    	//Sprawdzamy który kanał
    	if(TIM1->SR&(TIM_SR_CC1IF))
    	{
    	TIM1->SR &= ~(TIM_SR_CC1IF);
    	Zarowka_on();
    	Tim1_canal_stop(1);
    	Tim1_canal_start(1000,2);
    	}
    
    	if(TIM1->SR&(TIM_SR_CC2IF))
    	{
    	Zarowka_off();
    	TIM1->SR &= ~(TIM_SR_CC2IF);
    	Tim1_canal_stop(2);
    	}
    	NVIC_ClearPendingIRQ(TIM1_CC_IRQn);
    }
    

    Pętla główna
    
     while(1)
        {
        	Tim1_canal_start(1000,1);
            czekaj(1000000); // zwykły for tyle razy ile wprowadzimy do funkcji
        }
    


    Widzi ktoś jakiegoś buga? bo mi już mózg wybucha od RM0091.
    Pozdrawiam

    Dodano po 1 [godziny] 8 [minuty]:

    Matko boska, już wiem. Wychodzi na to, że jak nie ustawiam wcześniej rejestrów CCRx, to on tam ma jakieś randomowe wartości i flaga przerwania tak czy siak jest ustawiana, tak więc od razu jak włączałem mu przerwania, to on od razu w nie wchodził. Dlatego miałem takie głupoty. Przepraszam za wstawienie śmieciowego tematu, ale może ktoś będzie miał kiedyś podobny problem i znajdzie tutaj odpowiedź. Funkcja włączająca kanał powinna wyglądać tak:
    
    	if (kanal==1)
    	{
    		TIM1->DIER |= TIM_DIER_CC1IE;
    		TIM1->SR &= ~(TIM_SR_CC1IF);
    		tmp= TIM1->CNT;
    
    		if ((tmp+us_1)>65535)
    		{
    		TIM1->CCR1 = (tmp + us_1)%65535;
    		}
    		else
    		{
    		TIM1->CCR1 = tmp + us_1;
    		}
    	}
    
  • #2
    BlueDraco
    MCUs specialist
    Ta funkcja nie powinna tak wyglądać.

    Jeśli już, to po prostu:
    TIM1->CCR1 = tmp + us_1;

    Bez żadnych rozejść warunkowych w celu wykonania błędnie "zawinięcia", które wykona się samo.

    Z kolei "canal_stop" jest świetną ilustracją jak nie należy dzielić programu na procedury. Wystarczyłoby każde wywołanie tej procedury zastąpić prostą instrukcją:
    TIM1->DIER &= ~TIM_DIER_CCxIE;

    Z tym, że modyfikacja rejestru DIER zarówno w programie głównym jak i w procedurze przerwania będzie powodowała błędy w działaniu programu.

    Tu też jest gruby błąd:
    TIM1->SR &= ~(TIM_SR_CC1IF);

    Powinno być:
    TIM1->SR = ~TIM_SR_CC1IF;
  • #3
    lycon5
    Level 11  
    Co do automatycznego "zwinięcia" to po prostu nie byłem pewien, czy tak się wykona automatycznie : ) ale jeśli kolega tak mówi, to pewnie tak jest.
    Co do funkcji sprawdzającej kanał, fakt, jest to strasznie nieoszczędne, ale pisałem to na szybko i chciałem żeby było w miarę widoczne co się dzieje, dlatego wrzuciłem to w funkcję. Optymalizacje planowałem w późniejszym czasie
    Quote:

    Tu też jest gruby błąd:
    TIM1->SR &= ~(TIM_SR_CC1IF);

    Powinno być:
    TIM1->SR = ~TIM_SR_CC1IF;

    Tutaj za bardzo nie rozumiem, jak zmieniam na Twoją wersję, to nie działa tak jak chcę ; < Jak na mój gust moja wersja jest logiczniejsza, ale czekam na wyjaśnienie : )
  • #4
    BlueDraco
    MCUs specialist
    TIM1->SR = ~TIM_SR_CC1IF;
    - to "skasuj znacznik CC1IF"

    TIM1->SR &= ~(TIM_SR_CC1IF);
    - to "skasuj znacznik CC1IF oraz znaczniki innych przerwań timera, które zostały zgłoszone po odczytaniu rejestru SR, a przed jego zapisem".
  • #5
    lycon5
    Level 11  
    Nie przemawia to do mnie, w stm32f0xx.h mamy coś takiego:
    
    #define  TIM_SR_CC1IF                        ((uint16_t)0x0002)            /*!<Capture/Compare 1 interrupt Flag */
    

    jak na mój gust zapis:

    TIM1->SR = ~TIM_SR_CC1IF;

    spowoduje najpierw zanegowanie TIM_SR_CC1IF a potem przypisanie do rejestru SR, czyli de facto wrzucimy tam wartość 0xFFFD.

    Zapis:

    TIM1->SR &= ~(TIM_SR_CC1IF);

    Wyzeruje tylko ten jeden bit w rejestrze.

    Czy ja coś przegapiłem ? : < Proszę o wyjaśnienie.
  • #6
    BlueDraco
    MCUs specialist
    Przegapiłeś dwie rzeczy:
    - reakcję rejestru na zapis opisaną w manualu - "write 0 to clear" - zapis jedynek jest ignorowany
    - fakt, że operacja logiczna &= wymaga najpierw odczytania, a potem zapisania rejestru,a przez czas pomiędzy odczytem i zapisem może nastąpić sprzętowe ustawienie znacznika przerwania, który ten zapis natychmiast skasuje.

    Czyli: jest dokładnie odwrotnie, niż to sobie wyobrażasz:

    TIM1->SR = ~TIM_SR_CC1IF;
    wyzeruje tylko tej jeden bit w rejestrze, a

    TIM1->SR &= ~(TIM_SR_CC1IF);
    Wyzeruje ten bit oraz wszystkie, które w chwili czytania rejestru miały wartość zero.
  • #7
    lycon5
    Level 11  
    A moim zdaniem obie instrukcje są sobie równoważne. Nieważne czy ustawię rejestr z zerem w miejscu, w którym znajduje się bit do wyczyszczenia, czy zamaskuję rejestr i wstawię zero. Obie instrukcje działają tak samo, obie dają ten sam rezultat w debuggerze jak i z prostej obserwacji. To, że zapis jedynki jest ignorowany, to faktycznie zapomniałem o tym :D Dziękuję za pomoc i dyskusję. Mam nadzieję na ewentualną pomoc w przyszłości.
  • #8
    BlueDraco
    MCUs specialist
    No to po kolei. Załóżmy, że masz ustawiony znacznik przerwania X. Próbujesz wykonać swoją błędną operację:

    SR &= ~X;

    którą procesor wykonuje tak:
    temp = SR;
    temp &= ~X;
    SR = temp;

    Po wykonaniu temp = SR;
    w SR jest ustawiany bit Y, bo timer właśnie wygenerował inne przerwanie. Twój błędny kod skasuje bit Y, bo zapisze 0 na pozycję bitu Y. Kod poprawny zawiera o 2..3 instrukcje mniej i nie skasuje błędnie znacznika przerwania. Proste?
  • #9
    lycon5
    Level 11  
    Przepraszam, że z takim zapłonem piszę, ale zapomniałem już o tym temacie. Faktycznie, jak to wytłumaczyłeś to ma to sens. Dziękuję za poświęcony czas. Pozdrawiam i życzę Wesołych Świąt.