logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[atmega8] [C] USART - bufor kołowy

Logan 23 Sty 2012 22:15 4155 43
  • #1 10439715
    Logan
    Poziom 30  
    Witam serdecznie.

    Niedawno postanowiłem przesiąść się z Bascoma na C. Postanowiłem napisać bufor kołowy do wysyłania znaków przez USART. Znaki niby się drukują, ale, jeśli rozmiar bufora jest ustawiony na 2 bajty to po drugim znaku dostaje "śmieci", jeśli na 3 bajty, to po trzecim, itd.

    main.c
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    usart.h
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    usart.c
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    W wyniku tego otrzymuje:
    [atmega8] [C] USART - bufor kołowy
  • Pomocny post
    #2 10439833
    gaskoin
    Poziom 38  
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    tablice w C są indeksowane od zera. Jak masz rozmiar = 3 to ostatnim indeksem tablicy jest 2. Powyższy kod powinień więc wyglądać tak:

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #3 10439923
    Logan
    Poziom 30  
    Działa. Proszę mi jeszcze powiedzieć, czy sama idea nadawania przez USART jest dobra, czy można to zrobić lepiej ?
  • #4 10440005
    tmf
    VIP Zasłużony dla elektroda
    Można to zrobić lepiej umieszczając stosowne funkcje jako funkcje obsługi przerwania. Jaki jest sens w przerwaniu zmieniać flagę i ją badać w pętli głównej, jeśli można to od razu zrobić w przerwaniu? Zresztą dublujesz flagę, która w procesorze już istnieje. Kolejna rzecz - warto jakoś sygnalizować błąd w sytuacji przepełnienia bufora.
  • #6 10441312
    tmf
    VIP Zasłużony dla elektroda
    BTW, ja tam wolę dawać return -1 - działa niezależnie od typu, więc jak zmienisz uint na ulong to ciągle będziesz miał -1 (czyli 0xFFFF i tyle F ile potrzeba).
  • #8 16857885
    Andrzej_;)
    Poziom 14  
    Panowie, głupie pytanie:

    Dlaczego w przykładowym kodzie, który zamieścił kolega nsvinc rozmiar bufora BUFSIZE musi być potęgą dwójki ?

    Moim zdaniem BUFSIZE musi być < od pojemności typu int i to wszystko..
  • #9 16857963
    michalko12
    Specjalista - Mikrokontrolery
    Prawdopodobnie komentarz jest pozostałością po kopiuj wklej z kodu autora. Takie ograniczenia najczęściej narzucane są od strony sprzętu lub FFT.
  • #10 16857995
    Andrzej_;)
    Poziom 14  
    michalko12
    - dzięki za odp :)
  • #11 16858185
    JacekCz
    Poziom 42  
    nsvinc napisał:
    To jest połączenie trzech koszmarków:
    - bufor kołowy o wielkości 2 bajty (wtf?!)
    - chcęć ustawiania wielkości bufora kołowego na 3 bajty (wtf? * wtf?)
    - if w obsłudze bufora...

    jest ich więcej, Flagi, od których się roi i najstarsi górale nie wytłumaczą się z subtelnościami jakie są ich role.
    Mam własną zasadę: jeśli kod nie jest w oczywisty sposób czytelny, jest prawdopodobnie błędny.
    Czuję mocną intuicję: jest z tymi połączonymi flagami taka ścieżka, w której algorytm wywija kota ogonem.

    tmf napisał:
    Można to zrobić lepiej umieszczając stosowne funkcje jako funkcje obsługi przerwania. Jaki jest sens w przerwaniu zmieniać flagę i ją badać w pętli głównej, jeśli można to od razu zrobić w przerwaniu? Zresztą dublujesz flagę, która w procesorze już istnieje. Kolejna rzecz - warto jakoś sygnalizować błąd w sytuacji przepełnienia bufora.


    dokładnie.

    Nawiasem śliczny temat na lekkie (tzn w pełni szanujące realia uP) C++, pięknie rozdzielić co jest prywatne dla instancji bufora, co jest zmienną aplikacji.
  • #12 16858448
    Konto nie istnieje
    Konto nie istnieje  
  • #13 16861539
    nsvinc
    Poziom 35  
    W kodzie jest przecież błąd, i aż dziwne że nikt nie zauważył :D

    Powinno byc:
    Kod: text
    Zaloguj się, aby zobaczyć kod


    1. potęga dwójki wynika z indeksowania %BUFSIZE. W tym przypadku kompilator zamienia to albo na logiczny AND, albo BFX, jesli BUFSIZE jest potęgą dwójki - jedna instrukcja, i zadnego 'matematycznego' modulo czy uzycia DIV (lub programowego odpowiednika) i odejmowania.
    2. kod jest przykładowy; będzie działał jesli da sie zagwarantowac, ze dane z bufora będą wyjmowane szybciej niz wsadzane. Jesli tej gwarancji nie ma, push() powinien mieć warunek sprawdzajacy czy (writeptr+1)!=readptr
  • #14 16861672
    BlueDraco
    Specjalista - Mikrokontrolery
    Ja w kodzie z poprzedniego postu widzę dwa oczywiste błędy. Błędna jest też uwaga 2. W kodzie oryginalnym jest ich z 10. dajmy szansę Autorowi, niech coś poprawi.
  • #15 16861780
    grko
    Poziom 33  
    BlueDraco napisał:
    Ja w kodzie z poprzedniego postu widzę dwa oczywiste błędy. Błędna jest też uwaga 2. W kodzie oryginalnym jest ich z 10. dajmy szansę Autorowi, niech coś poprawi.


    Nie liczyłbym na szybką odpowiedź biorąc pod uwagę to, że temat jest sprzed 5 lat. Przez to posty z cyklu "wiem ale nie powiem" w tego typu temacie wyglądają kuriozalnie.
  • #17 16862032
    Konto nie istnieje
    Konto nie istnieje  
  • #18 16862067
    nsvinc
    Poziom 35  
    To jest nieoptymalne pod kątem dostępu do tych indeksów. Nie daj borze 2x9bitowy bitfield. Po to cała zabawa z potęgami dwójki aby minimalizować ilość potrzebnych transakcji pamięci i instrukcji modyfikujących indeksy. Na dokładke mamy cięzkie problemy z RMW - zeby to dzialalo trzeba stosowac mechanizmy typu mutex albo sekcje krytyczne albo wyłączać przerwanie. Kompilator nie zrobi tego za ciebie.
    Skrajnie nieoptymalny kod.
  • #19 16862117
    BlueDraco
    Specjalista - Mikrokontrolery
    Jeśli rozmiar nie jest potęgą dwójki, to...?
    Jeśli indeksy są dłuższe niż 8 bitów, to...?
  • #20 16862139
    Konto nie istnieje
    Konto nie istnieje  
  • #21 16862180
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Piotrus_999 napisał:
    a modulo to za Ciebie zrobi rozumiem. Już bedzie od razu atomowe.

    Podstawową kwestią nie jest R-M-W, tylko to, że jak masz dwie osobne zmienne, to "producer" modyfikuje swoją zmienną, a "consumer" swoją. Nigdy nie modyfikują "nie swoich" zmiennych. W Twojej propozycji jedna lokalizacja pamięci jest modyfikowana jednocześnie z dwóch "kontekstów". Typowy data race. Zupełnie bez sensu, bo oszczędzasz aż jeden bajt przy znaczącej komplikacji kodu.
  • #22 16862206
    Konto nie istnieje
    Konto nie istnieje  
  • #23 16862338
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Wiedząc co się robi i co się chce osiągnąć można stosunkowo łatwo napisać bufor kołowy, który będzie "lock-free" - nie będzie on wymagał żadnych dodatkowych synchronizacji (np. przerwanie może sobie do niego wpisywać dane kiedy mu się podoba, a program główny może je odczytywać bez chwilowego blokowania przerwań, podobnie gdyby mieć RTOSa i dwa wątki). Niemniej jednak właściwość ta bazuje na tym, że zmienne podzielone są logicznie i fizycznie na dwie grupy. "Producer" (np. przerwanie odbiorcze) zapisuje tylko i wyłącznie swoją grupę, a "consumer" (np. program główny) - tylko i wyłącznie swoją. Mogą nawzajem czytać "nie swoją" grupę zmiennych, ale nie mogą jej modyfikować. Tej podstawowej i kluczowej zależności nie spełnisz w przypadku pól bitowych, bo zapis jednej "połówki" zapisuje też drugą - jedynym sposobem aby tego uniknąć jest stosowanie sekcji krytycznych. Przy dobrze zrobionym "lock-free" sekcje krytyczne nie są potrzebne wcale.

    Piotrus_999 napisał:
    dla mnie wersja z bitfieldem jest czytelniejsza

    Myślę, że główną cechą poprawiającą tutaj czytelność jest to, że masz obydwie zmienne w strukturze. Można mieć obydwie zmienne w strukturze i _NIE_ korzystać z pól bitowych - problem solved.
  • #24 16862340
    grko
    Poziom 33  
    Problem bitfieldów jest taki, że jeżeli ktoś chciałby zrobić sobie generyczną kolejkę używaną w wielu miejscach w kodzie to byłoby to trudne w implementacji. Jeżeli kolejki miałyby różne rozmiary to dla każdej trzeba byłoby generować inną instancje struktury + nowy zestaw funkcji operujących na tej strukturze. Więc zysk taki średni.
  • #25 16862351
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Piotrus_999 napisał:
    ja tylko pokazałem że w takich samych funkcjach nie będzie różnicy w długości kodu

    Przecież jest różnica. Funkcja push faktycznie ma taką samą długość, za to funkcja pop z Twojego przykładu ma 27 instrukcji w wersji normalnej vs 30 w wersji z polem bitowym. Różnica niewielka, ale różnica między użyciem dwóch bajtów a dwóch pól bitowych na jednym bajcie też jest niewielka. Ta analiza i tak jest bezsensowna, bo te funkcje mają sporo błędów, poczynając od tego, że zmienne powinny być "volatile". Już ta jedna zmiana sprawia, że push w wersji normalnej jest o jedną instrukcję krótszy, choć dla pop różnica maleje do dwóch instrukcji. Tyle że normalny pop jest napisany w skrajnie nieoptymalny sposób (3x modulo), więc...
  • #26 16862353
    Konto nie istnieje
    Konto nie istnieje  
  • #27 16862358
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Przeczytaj mojego posta wyżej. Jeśli chcesz sobie brnąć dalej w pola bitowe, to proszę bardzo. Traktuj moje posty jako ostrzeżenie dla szerszej publiczności, która na podstawie dwóch spojrzeń będzie sobie sama mogła zdecydować czy woli kod który używa jednego bajtu mniej i zajmuje więcej flash (a do tego najeżony jest problemami data race), czy może zrobi to normalnie. W tym konkretnym przypadku zysk z użycia pól bitowych jest żaden, ponieważ prawdziwa różnica uwidoczniłaby się dopiero po dodaniu odpowiednich sekcji krytycznych (tak żeby kod miał szansę być poprawny), choć nawet bez nich kod jest większy.
    Dodano po 3 [minuty]:
    Piotrus_999 napisał:
    Przy okazji pokazuje błąd logiki autora, który sądzi ze użycie modulo sprowadzi sprawę do jakiejś atomowej operacji.

    Chyba niestety Ty nie rozumiesz... Przy dwóch osobnych zestawach zmiennych te operacje nie muszą być atomowe. Problem R-M-W (w takim rozumieniu o jakim tu piszemy (*)) nie występuje. Tak więc Ty również popełniasz błąd logiczny. Jeśli taki kod z polami bitowymi napisałeś i Ci działał, to tylko pogratulować szczęścia - w końcu w totolotka też można wygrać, no nie? No chyba że całkiem przypadkiem obydwa pola bitowe miały 8 bitów (; W każdym innym przypadku w końcu trafisz na błąd tego kodu i tyle.

    Zresztą w ogóle nie wiem po co się ze mną kłócisz i czemu od razu tak konfrontacyjnie. Spokojnie i (chyba) dogłębnie napisałem Ci w czym problem, a Ty od razu ostro że czegoś nie rozumiem... Może nam wytłumacz w takim razie, bo nie zwykłem brać niczego na wiarę - jeśli udowodnisz nam poprawność swojego kodu, to będzie super sytuacja, bo każdy się czegoś nauczy.

    (*) - dostęp do zmiennych dalej musi być atomowy w znaczeniu zapisu/odczytu, tzn. że np. na 8-bitowym AVRze zmienna musi być odczytywalna/zapisywalna jednym rozkazem, niemniej jednak ilość czasu jaka upłynie od czasu odczytu do zapisu zmodyfikowanej wartości nie ma znaczenia.
    Dodano po 5 [minuty]:
    Piotrus_999 napisał:
    I tak przy okazji autor zwraca jako artosc 32 bitowa zamiast 16 co świadczy o tym ze raczej myślał np o ARMach. Tam różnica zanika

    Różnica rozmiaru? Co ciekawe jest jeszcze większa (11 vs 14 dla push i 16 vs 18 dla pop, funkcje po drobnych poprawkach). Problem z dostępem do pól bitowych zostaje dokładnie taki sam jak był - dalej jest to data race.
  • #28 16862402
    Konto nie istnieje
    Konto nie istnieje  
  • #29 16862426
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Piotrus_999 napisał:
    Różnice rzędu 2 instrukcji jakoś do mnie w tym przypadku nie trafiają.

    Czyli różnice czterech czy sześciu bajtów flasha (zakładam że na AVR jedna instrukcja ma 2 bajty) do Ciebie nie trafiają, ale różnica jednego bajta w RAM już trafia? Widzisz niekonsekwencję?

    Piotrus_999 napisał:
    Ja tu data race nie widzę. Czy jest coś polem bitowym czy int-em. (i abstrahujmy od tego błędnego kodu autora, którego żeś się uczepił a jak Ci już pisałem specjalnie nic nie zmieniałem w treści funkcji). Data race może wynikać z programu a nie typu danej.

    Mylisz się. Typ danych - np. użycie 64-bitowej zmiennej do której nie ma atomowego dostępu - jak najbardziej może powodować data race. Ale w tym przypadku akurat rozmiar nie ma specjalnego znaczenia, więc skupmy się na tych buforach kołowych. Najprostszy przypadek:

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Czy w tym kodzie jest data race? Czy jesteś mi w stanie udzielić na to odpowiedzi bez informacji o tym, jak dokładnie wygląda ta struktura? Bo wydaje mi się, że nie jesteś, gdyż to zależy od... typu danych... Jeśli struktura wyglądałaby jak przedstawiona niżej, to data race nie występuje (specjalnie użyłem 64-bitowych zmiennych).

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Program z taką strukturą możesz sobie uruchomić i będzie działał POPRAWNIE nieskończenie długo.

    Ale co jeśli struktura wyglądałaby trochę inaczej?

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Ile cykli przerwań minie, zanim jeden z kontekstów (przerwanie lub program główny) nadpisze zmienną która nie jest "jego"? Max kilka, więc nie trzeba czekać specjalnie długo.
  • #30 16862480
    sawitar
    Poziom 18  
    Uuuu. Ale się gorąco zrobiło .... w wątku z 2012 roku :-)
    A może w duchu rywalizacji zaczniemy nowy wątek w którym każdy będzie mógł przedstawić swoją implementację bufora kołowego? Jeśli będzie wola w narodzi to założę taki wątek wraz z wymaganiami.
REKLAMA