[PIC18F46K80] Delay na timerze, atomowy dostęp do zmiennej

Tu możesz pisać o swoich problemach z pisaniem programów w języku C dla PIC.
Awatar użytkownika
Antystatyczny
Geek
Geek
Posty: 1096
Rejestracja: czwartek 03 wrz 2015, 22:02

[PIC18F46K80] Delay na timerze, atomowy dostęp do zmiennej

Postautor: Antystatyczny » czwartek 23 lut 2017, 21:59

Witam serdecznie.

Mam pewien drobny problemik, który chciałbym z Wami skonsultować. Posiadam w kodzie taką zmienną:

Kod: Zaznacz cały

volatile uint16_t TICK_Ticks;


Zmienna ta inkrementowana jest w przerwaniu tysiąc razy na sekundę i oczywiście ulega przepełnieniu co około 65 sekund. Zwróćcie uwagę na fakt, że jest 16 bitowa. Mam również napisaną prostą funkcję opóźniającą:

Kod: Zaznacz cały

void Delay(uint16_t ms)
{
    uint16_t temp;
   
    /* Disable Timer 0 overflow interrupt request */
    INTCON &= ~_INTCON_TMR0IE_MASK;
   
    /* Get ticks value (atomic access) */
    temp = ms + TICK_Ticks;
   
    /* Enable Timer 0 overflow interrupt request */
    INTCON |= _INTCON_TMR0IE_MASK;
   
    while(TICK_Ticks < temp);
}


Jak widać wyłączam na chwilę przerwanie od timera, który zarządza inkrementacją zmiennej, by pobrać jej wartość i dodać do oczekiwanego opóźnienia. Następnie włączam przerwanie i czekam w pętli, aż zmienna inkrementowana zrówna się z wartością obliczoną i przechowywaną w temp. Niestety, co około 65 sekund, czyli w okolicach przepełniania się zmiennej TICK_Ticks, funkcja opóźniająca szaleje. Mam wrażenie, że nie dopełniłem czegoś w związku z atomowym dostępem do zmiennej. Czyżbym musiał wewnątrz pętli while każdorazowo testować zmienną TICK_Ticks atomowo?


Pozdrawiam.
"The true sign of intelligence is not knowledge but imagination" Albert Einstein.

Awatar użytkownika
Antystatyczny
Geek
Geek
Posty: 1096
Rejestracja: czwartek 03 wrz 2015, 22:02

Re: [PIC18F46K80] Delay na timerze, atomowy dostęp do zmiennej

Postautor: Antystatyczny » czwartek 23 lut 2017, 22:33

Przerobiłem funkcję Delay na następującą:

Kod: Zaznacz cały

void Delay(uint16_t ms)
{
    uint16_t temp;
   
    /* Disable Timer 0 overflow interrupt request */
    INTCON &= ~_INTCON_TMR0IE_MASK;
   
    /* Calculate new ticks value (atomic access) */
    ms += TICK_Ticks;
   
    /* Enable Timer 0 overflow interrupt request */
    INTCON |= _INTCON_TMR0IE_MASK;
   
    do
    {
        /* Disable Timer 0 overflow interrupt request */
        INTCON &= ~_INTCON_TMR0IE_MASK;
       
        temp = TICK_Ticks;
       
        /* Enable Timer 0 overflow interrupt request */
        INTCON |= _INTCON_TMR0IE_MASK;
    }
    while(temp < ms);
}


Nadal widać błędne działanie tej funkcji dokładnie co 65 sekund. Jakby w tym momencie delay nie działał. Jakieś pomysły?
"The true sign of intelligence is not knowledge but imagination" Albert Einstein.

Awatar użytkownika
xor
User
User
Posty: 139
Rejestracja: poniedziałek 05 wrz 2016, 21:44

Re: [PIC18F46K80] Delay na timerze, atomowy dostęp do zmiennej

Postautor: xor » piątek 24 lut 2017, 18:03

W sytuacji gdy ms+TICK_Ticks>UINT16_MAX nastąpi przepełnienie i warunek temp<ms będzie false. Może zmienna temp powina być typu uint32_t?

Awatar użytkownika
Antystatyczny
Geek
Geek
Posty: 1096
Rejestracja: czwartek 03 wrz 2015, 22:02

Re: [PIC18F46K80] Delay na timerze, atomowy dostęp do zmiennej

Postautor: Antystatyczny » piątek 24 lut 2017, 18:30

Znalazłem wyjaśnienie dziwnego działania funkcji Delay. Zmienna TICK_Ticks jest 16 bitowa, a więc jest w stanie przechowywać liczbę o maksymalnej wartości 2^16 - 1( dwa do potęgi szesnastej minus jeden), czyli 65535. Wyobraźmy sobie następujący scenariusz:

1. Wywołujemy funkcję Delay z parametrem 1000, czyli życzymy sobie sekundowe opóźnienie.
2. W funkcji pobieramy aktualną wartość zmiennej TICK_Ticks. Niech będzie, że jej wartość to 65500.
3. Obliczamy docelową wartość zmiennej TICK_Ticks, do której funkcja będzie musiała czekać. Do wartości 65500 dodajemy 1000 i wychodzi nam... 964!

4. Wpadamy w pętlę while i czekamy dopóki zmienna TICK_Ticks jest mniejsza od wartości docelowej, którą przed chwilą obliczyliśmy.
5. Wartość zmiennej TICK_Ticks wynosi grubo ponad 65000... A zatem warunek zostaje natychmiast spełniony, bo przecież 65500 jest znacznie większe od 964, prawda? No i to jest właśnie ten błąd...

Oczywiście mógłbym zwiększyć zakres zmiennej TICK_Ticks, ms oraz temp, ale to tylko wydłużyłoby okresy pomiędzy wystąpieniami tego błędu. W przypadku zastosowania typu uint32_t błąd występowałby co około 49.7 dnia. No dobra, to teraz rozwiązanie problemu:

Zmiana trybu pracy zmiennej TICK_Ticks z bezwględnej inkrementacji podczas każdego przerwania na dekrementację, o ile jej wartość jest różna od zera...

Kod: Zaznacz cały

if(TICK_Ticks != 0)
{
        TICK_Ticks--;
}


Oraz zmiana sposobu pracy samej funkcji Delay... Nie jest to jeszcze ostateczna wersja, ale działa poprawnie (przetestowałem).

Kod: Zaznacz cały

void Delay(uint16_t ms)
{
    uint16_t temp;
   
    /* Disable Timer 0 overflow interrupt request */
    INTCON &= ~_INTCON_TMR0IE_MASK;
   
    /* Calculate new ticks value (atomic access) */
    TICK_Ticks = ms;
   
    /* Enable Timer 0 overflow interrupt request */
    INTCON |= _INTCON_TMR0IE_MASK;
   
    do
    {
        /* Disable Timer 0 overflow interrupt request */
        INTCON &= ~_INTCON_TMR0IE_MASK;
       
        temp = TICK_Ticks;
       
        /* Enable Timer 0 overflow interrupt request */
        INTCON |= _INTCON_TMR0IE_MASK;
    }
    while(temp != 0);
}


Zrewidowałem swe potrzeby odnośnie zmiennej TICK_Ticks i stwierdziłem, że wcale nie potrzebuję w programie zmiennej, która tysiąc na sekundę się inkrementuje, przepełnia...itd. bez końca. W związku z tym zrezygnowałem z pierwotnego rozwiązania na rzecz obecnego.

Pozdrawiam.
"The true sign of intelligence is not knowledge but imagination" Albert Einstein.


Wróć do „Programowanie PIC w C”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 1 gość