Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

STM32 UART RS232 dziwny problem z buforem.

qazpylades 26 Lut 2016 10:13 924 17
  • #1 26 Lut 2016 10:13
    qazpylades
    Poziom 12  

    Klepnąłęm kawałek kodu na STM32F0x dla obsługi transmisji uK=>PC.
    Jednak pojawił się dziwny objaw. Może ja nie widzę błędu....
    Warto aby ktoś zerknął w ten kod:

    Code:

    #define TBUF_SIZE_UART2   128   

    struct uart2_tx_buf_st
    {
       u16_t in;        /* indeks zapisu */                       
       u16_t out;       /* indeks odczytu */
       u16_t dat;       /* licznik danych w buforze */                       
       char buf [TBUF_SIZE_UART2]; /* dane */
    };


    volatile struct uart2_tx_buf_st tbuf = { 0, 0, };




    void UART2_interrupt_handler (void)
    {
       register u32_t tflags;
       tflags = USART2->ISR;
        if (tflags & USART_FLAG_TXE)
        {
           struct uart2_tx_buf_st* pt = &tbuf;

           if (0<pt->dat)
           { /* są znaki do wysłania w buforze */
              USART2->TDR = pt->buf [pt->out];
              pt->out=((pt->out+1)&(TBUF_SIZE_UART2-1));
              pt->dat--;
           }
           else
           { /* bufor jest pusty */
              USART2->CR1&=(~USART_FLAG_TXE);       /* disable interrupt */
           }
        }
    }


    int newSendChar (int c)
    {
       volatile struct uart2_tx_buf_st *pt = &tbuf;
       volatile u16_t   index_temp;
       do{}while((pt->dat)>=(TBUF_SIZE_UART2-2)); /*czekaj na miejsce w buforze */
       do{
         /* jeśli po inkrementacji indeks zapisu i odczytu są rowne to bufor jest pełny */
       }while((pt->dat)>=(TBUF_SIZE_UART2-2)); /*czekaj aż zwolni się miejsce w buforze !!!!!!!!!!!!!!!!*/

       pt->buf [pt->in] = c;
       pt->in=((pt->in+1) & ((u16_t)(TBUF_SIZE_UART2-1))); /* oblicz nowy indeks */

       USART2->CR1&=(~USART_CR1_TXEIE);   /* zablokuj przerwanie */
       pt->dat++;
       USART2->CR1|=USART_CR1_TXEIE;    /* odblokuj przerwanie */
       return (0);
    }

    mam funkcje piszącą do bufora kołowego i przerwanie odczytujące dane z bufora i wysyłające przez sprzętowy USART.

    Kod działa do momentu gdy zapis dochodzi do miejsca odczytu (pełny bufor)

    wtedy przez pewien czas blokujące oczekiwanie zaznaczone wykrzyknikami działa poprawnie (przez kilkadziesiąt znaków poprawnie oczekuje), aż niespodziewanie zapis wyprzedza odczyt.
    W tym samym momencie mimo iż licznik 'dat' w strukturze obsługi bufora ma wartość maksymalną funkcja if w przerwaniu wykrywa dat==0 i wyłącza przerwania.
    Jak to jest możliwe? nie widzę gdzie tkwi błąd.
    Odnoszę wrażenie jak gdyby blokowanie przerwania od UART-u na czas zmiany w strukturze wartości licznika dat nie funkcjonowało.

    Może ktoś go zobaczy?

    0 17
  • #2 26 Lut 2016 11:02
    Freddie Chopin
    Specjalista - Mikrokontrolery

    qazpylades napisał:
    USART2->CR1&=(~USART_FLAG_TXE); /* disable interrupt */

    Jeśli powyższe działa, to tylko przypadkiem.

    qazpylades napisał:
    if (tflags & USART_FLAG_TXE)

    Dobrą opcją jest obsługa przerwania tylko gdy jest ono włączone. W Twoim kodzie to akurat nie problem, bo masz włączone tylko jedno źródło, ale jak dodasz ich więcej to będzie problem. Warunek ten powinien sprawdzać czy TXE jest ustawione i czy TXEIE jest ustawione.

    0
  • #4 26 Lut 2016 11:13
    qazpylades
    Poziom 12  

    obsługę odbioru - TXE sprawdzam ale chodzi konkretnie o ten kawałek kodu dlatego nie dawałem całości aby nie zaciemniać niepotrzebnie. Poza tym jeśli bym wykorzystywał tylko nadajnik to nie jest potrzebna obsługa odbiornika a akurt nie jest gdyż to jest taki kontrolny terminal komunikatów z urządzenia(sygnalizacja dla użytkownika który w konsoli może sprawdzić co program robi).
    Co do volatile. może i nadużycie ale bez tego będzie na pewno problem chyba że licznik dat wyłączę poza strukturęi dam jej oddzielnie volatile a tego nie chcę robić. Generalnie wszystko "strukturyzuję" dzięki temu mam dzrzewiastą konstrukcję wszystkich wartości w programie.

    0
  • #5 26 Lut 2016 11:22
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Jeśli masz problem z kodem i pokazujesz tylko jego fragment, to raczej nie licz na to że znajdziemy tu błąd... Funkcji do odbierania nam może nie musisz pokazywać, ale za to całe przerwanie owszem.

    0
  • #6 26 Lut 2016 11:30
    qazpylades
    Poziom 12  

    Proszę bardzo lecz jetem pewien, iż nie wniesie to nic. ustawiony znacznik w kodzie nigdy nie zostł wyzwolony (załączenie LED) w tej części kodu.

    Code:

    void UART2_interrupt_handler (void)
    {
       register u32_t tflags;
       
        if (tflags & USART_ISR_RXNE)
        {
          USART2->RQR|=USART_RQR_RXFRQ;   
          struct uart2_rx_buf_st* pr = &rbuf;
          if (((pr->in - pr->out) & ~(RBUF_SIZE_UART2-1)) == 0)
          {
            pr->buf [pr->in] = ((u8_t)USART2->RDR);
            pr->in=(pr->in+1)(RBUF_SIZE_UART2-1);
          }
        }
       
       tflags = USART2->ISR;
        if (tflags & USART_FLAG_TXE)
        {
           struct uart2_tx_buf_st* pt = &tbuf;

           if (0<pt->dat)
           { /* są znaki do wysłania w buforze */
              USART2->TDR = pt->buf [pt->out];
              pt->out=((pt->out+1)&(TBUF_SIZE_UART2-1));
              pt->dat--;
           }
           else
           { /* bufor jest pusty */
              USART2->CR1&=(~USART_FLAG_TXE);       /* disable interrupt */
           }
        }
    }

    0
  • #7 26 Lut 2016 11:47
    Freddie Chopin
    Specjalista - Mikrokontrolery

    qazpylades napisał:
    Proszę bardzo lecz jetem pewien, iż nie wniesie to nic.

    Skoro tak twierdzisz, to po co w ogóle pytać?

    Masz błąd o którym Ci pisałem. Tyle że wtedy pisałem, że "warto na przyszłość", a teraz mówię że masz błąd.

    To:

    Code:
    if (tflags & USART_FLAG_TXE) 


    wykona się niezależnie od tego czy masz włączone przerwanie czy nie. A wiec występuje u Ciebie (jednak) typowy "race condition". Tobie się wydaje, że blokada przerwań w funkcji wysyłającej daje Ci atomowy dostęp do zmiennej licznika, ale tak nie jest. Ten warunek MUSI wyglądać tak:

    Code:
    if ((tflags & USART_FLAG_TXE) && (USART2->CR1 & USART_CR1_TXEIE)) 


    Powód jest trywialny. Jeśli przerwanie od wysyłania masz wyłączone, to TXE na 100% będzie (w końcu) pusty. Jeśli wystąpi przerwanie od odbioru (które masz pewnie włączone cały czas), to wykona Ci się kod od nadawania, choć wcale tego nie chciałeś. I np. wykona się - całkiem przypadkiem - w trakcie środkowej linijki poniższego fragmentu:

    Code:
       USART2->CR1&=(~USART_CR1_TXEIE);   /* zablokuj przerwanie */ 
    
       pt->dat++;
       USART2->CR1|=USART_CR1_TXEIE;    /* odblokuj przerwanie */

    1
  • #8 26 Lut 2016 12:41
    qazpylades
    Poziom 12  

    Zaczyna się robić ciekaw dyskusja.
    zacznijmy od prostego testu.
    Podmiana warunku w kodzie na

    Code:
    if ((tflags & USART_FLAG_TXE) && (USART2->CR1 & USART_CR1_TXEIE))

    (uznaję to za słuszne, gdyż gdybym chciał używać również odbiornika to skończyło by sięto źle)

    Dodatkowo wykonuję przed serią zapisów do bufora
    Code:
    USART2->CR1&=(~USART_CR1_RXNEIE);

    co gwarantuje, iż odbiornik nigdy się nie odezwie.


    zapewnijmy atomowość względem czegokolwiek:
    Code:

       __ASM volatile ("cpsid i");
      //USART2->CR1&=(~USART_CR1_TXEIE);   /* zablokuj przerwanie */
      pt->dat++;
      //USART2->CR1|=USART_CR1_TXEIE;    /* odblokuj przerwanie */
       __ASM volatile ("cpsie i");


    robimy make clean dla pewności
    make run (compile & run )

    wynik: Zachowuje się dokładnie tak samo.
    Jak mówiłem załączenie LED w części odbiorczej nigdy się nie wydarzyło.
    W związku z tym wyłączenie odbiornika nic nie zmienia.
    Problem jest innej natury.
    Na dat wykonuję dwie operacje: ++ lub --.
    w ramach testu robię bufor o długości 4k. i nagle bez powodu wartość pola dat z 4k (bufor pełny) zmienia się na "0" !!! taka sytuacja możliwo jest tylko przy init.
    ale na pewno tam nie wchodzi gdyż kod init włączyłby mi buzzer na 1 sekundę a to ciężko nie zauważyć. ;)
    Aby się takich dziwactw ustrzec stosuję struktury (nie ma problemu z przypadkowym konfliktem nazw).
    No nic...
    biorę się za analizę śmiecia w .lst ................

    0
  • #9 26 Lut 2016 12:45
    tadzik85
    Poziom 38  

    qazpylades napisał:
    zapewnijmy atomowość względem czegokolwiek:
    Kod:

    __ASM volatile ("cpsid i");
    //USART2->CR1&=(~USART_CR1_TXEIE); /* zablokuj przerwanie */
    pt->dat++;
    //USART2->CR1|=USART_CR1_TXEIE; /* odblokuj przerwanie */
    __ASM volatile ("cpsie i");

    Od czegoś jest CMSIS _enable_interrupts(); itd.

    Powyższy kod nie gwarantuje pełnej atomowości.

    0
  • #10 26 Lut 2016 12:56
    Freddie Chopin
    Specjalista - Mikrokontrolery

    Odwołuj się do struktury bezpośrednio, bez tych wskaźników.

    Nie masz gdzieś po prostu przepełnienia stosu albo innego kodu który sobie coś robi przy pomocy wskaźników i po prostu nadpisuje Ci akurat tą zmienną?

    0
  • #11 26 Lut 2016 13:10
    grko
    Poziom 32  

    @qazpylades
    Jakie masz ustawienia PRIMASK oraz jaki jest priorytet Twojego przerwania? Instrukcja cpsid i niekoniecznie maskuje Twoje przerwanie.

    0
  • #12 26 Lut 2016 13:33
    qazpylades
    Poziom 12  

    Wpisałem asmem z przyzwyczajenia.
    Co do przepełnienia stosu to niemożliwe gdyż wszystko działa tylko ta funkcja umiera.
    Użycie wskaźnika to ze względu na to, iż szybciej się przerabia kod na wiele struktur(urządzeń) Zmieniłem już na bezwskaźnikowe z dostępem przez '.'
    To co mówiłem przed zbieżnością nazw zabezpieczam się strukturami (nigdy nie stosuję "wolnych" zmiennych globalnych lub lokalnych utrymywanych przez całe życie programu)
    Nie stosuję chamskich metod dosępu przez wskaźnik + beszczelne przesunięcie obliczane ręcznie.
    przerwania na pewno są blokowane przez __ASM volatile ("cpsid i");
    prosty test to wykazuje.

    Jak przeanalizuję .lst to dam znać co namąciłem.

    0
  • #13 26 Lut 2016 13:35
    vonar
    Poziom 28  

    @GrzegorzKostka chyba pomyliłeś PRIMASK z BASEPRI (którego zresztą w ARMv6-M nie ma). cpsid i zablokuje wszystko poza HardFault, NMI i resetem.

    0
  • #15 26 Lut 2016 14:41
    Freddie Chopin
    Specjalista - Mikrokontrolery

    qazpylades napisał:
    Co do przepełnienia stosu to niemożliwe gdyż wszystko działa tylko ta funkcja umiera.

    Proponowałbym jednak powiększyć stos i sprawdzić - zarówno stos dla aplikacji jak i stos dla przerwań (jeśli używasz dwóch osobnych).

    0
  • #16 26 Lut 2016 17:14
    qazpylades
    Poziom 12  

    Uywam jednego stosu. Nie ma potrzeby dzielić. aplikacja jest dość prosta. kontrola środowiska, akumulatora sterowania ładowaniem, obsługa klawiatury i LCD + pomiary. wtyniki mają być wywalane ku ułatwieniu pracy przez RS232 bez potwierdzeń.
    Mam znaczniki kontrolne stosu. Nie zostały nadpisane.
    Zgodnie z IEC60730 mam kontrolę przekroczenia stosu.
    Jest badany co 1ms.

    0
  • #17 26 Lut 2016 17:43
    Freddie Chopin
    Specjalista - Mikrokontrolery

    No tak, bo powiększenie i sprawdzenie tego byłoby takie trudne i czasochłonne... No ale OK - wg IEC60730 Twoja aplikacja jest poprawna, więc w sumie nie ma o czym debatować.

    0