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

m8 [c] ATMEL STUDIO - dziwne zachowanie odczyt z PROGMEM

Jarosław J 25 Gru 2012 19:50 3069 20
  • #1 11695467
    Jarosław J
    Poziom 14  
    Przepisałem kod z książki tmf'a, ze strony 231 dotyczący wyświetlania multipleksowego.
    Z początku miałem kłopot ze skompilowaniem przykładu w ATMEL STUDIO bo kompilator sygnalizował błąd
    Cytat:
    Error 1 variable 'DIGITS' must be const in order to be put into read-only section by means of '__attribute__((progmem))'

    Rozumiem to w ten sposób, że zmienne DIGITS jako, ze zostaną zapisane w sekcji tylko do odczytu muszą być typu const. W przykładzie zmienna jest zadeklarowana jako static. Dopisałem więc modyfikator const do deklaracji zmiennej co rozwiązało problem kompilacji

    Po moich kosmetycznych modyfikacjach program wygląda tak:

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


    a za zadanie ma on wyświetlanie cyfr od 0 do 9.
    Wszystko grało i nie pisałbym o tym, gdyby nie jeden błąd który zdarzyło mi się popełnić.
    Wydawało mi się, że sterowanie kropką polega na modyfikacji zmiennej DP jednak jest ona zadeklarowana jako const, więc usunąłem ten modyfikator i wpisałem wcześniej binarny adres kropki na moim porcie i próbowałem modyfikować zmienną DP. Szybko zauważyłem że nie o to chodzi i wróciłem do oryginalnego programu, który zamieściłem powyżej.
    Po skompilowaniu od tej pory zauważyłem, że przy wyświetlaniu cyfr wyświetlacze wskazują kolejno

    0,
    8!!!!!!
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,

    Z początku myślałem, że skoro tablica znajduje się w pamięci FLASH to są to jakieś pozostałości po próbach, ale wyzerowanie (ff) flasha i ponowne wgranie programu nie dało efektu. Nijak adres "CF" pod którym znajduje się moja jedynka nie będzie ósemką.

    1. Czy ktoś wie czy ten błąd to wina kompilatora, czy jeszcze czegoś innego?
    2. Jak w końcu sterować tą kropką?
  • #2 11695595
    tmf
    VIP Zasłużony dla elektroda
    Nowy kompilator (nie było go jeszcze w chwili pisania książki) wymaga, aby stałe we FLASH były zadeklarowane z modyfikatorem const tak jak to zrobiłeś.
    Co do kropki - program zakłada, że jest ona podłączona pod najstarszy bit portu. Niezależnie czy tak jest czy, nie, DP powinno zawierać 0x80 tak tak jak jest w oryginale. Kropkę włączasz wpisując do tablicy przechowującej znaki (LEDDIGITS) na danej pozycji wartość z ustawionym najstarszym bitem. W funkcji ShowOnLED należy zmienić linię:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

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

    Jeśli kropka podłączona jest pod inny pin niż najstarszy, zamiast tmp&=~(DP); należy wpisać tmp&=~(_BV(nr pinu z kropką);

    Kolejna sprawa - tak jak korzystasz z tej funkcji to ona do multipleksowania nie zadziała, ale o tym wiesz.
  • #3 11695828
    Jarosław J
    Poziom 14  
    Ja chyba zrezygnuję z obsługi kropki (włączę ją na stałe), bo wymagałoby to zwielokrotnienia zapisów w tablicy DIGITS. (każda cyfra musiałaby być zapisana podwójnie z kropka i bez przynajmniej tak to zrozumiałem)

    Układ działa już jak chciałem, jednak wydaje mi się, że kiedy kropka będzie na stałe zapalona można by skrócić jakoś ten kod do wrzucania adresu cyfry na port. Ale popracuję nad tym później.

    Mam tylko jeszcze pytanie dotyczące ostatniej linijki kodu obsługi przerwania W funkcji inicjalizacji zmieniłem nazwę rejestru na odpowiedni dla Atmegi8, a dalej

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


    chodzi o tę linijkę:
    PORTB=(PORTB & 0xf0) | (~(1<<LEDNO) & 0x0f);
    nie bardzo wiem co się tu dzieje.
    Pierwsza część zeruje starsze bity portu
    druga część wskazuje i ustawia kolejno cztery młodsze bity

    Ale Gdybym chciał multipleksować starsze bity portu zgodnie z tą zasadą to zamieniam po prostu wartości w ten sposób
    PORTB=(PORTB & 0x0f) | (~(1<<LEDNO) & 0xf0);
    jednak to nic nie daje.
    Wynika z tego, że nie właściwie zrozumiałem te operacje. Czy ktoś może mi je wyjaśnić?
  • Pomocny post
    #4 11695856
    tmf
    VIP Zasłużony dla elektroda
    W tablicvy DIGITS nic nie zmieniasz, ciągle jest tylko 10 cyfr.Kropka jest dodawana automatycznie, jeśli na danej pozycji tablicy LEDDIGITS najstarszy (7 bit) jest ustawiony. Czyli chcesz wyświetlić np. 9 to wpisujesz do tej tablicy 9 | 128.
    Co do tego zapisu, to istotnie, kasuje on 4 najmłodsze bity, po czym wpisuje na nie 0 na pozycji określonej zmienną LEDNO. Pozostałe bity tej tetrady będą miały wartość 1. Jeśli twoje LED są podłączone nie do młodszej tetrady, a do starszej, to zapis będzie wyglądał tak:
    PORTB=(PORTB & 0x0f) | (~(1<<(LEDNO+4)) & 0xf0);
    LEDNO wskazuje na nr bitu, który ma być zgaszony. Jeśli wyświetlacz masz podłączony pod PB4-7 to LEDNO musi zawierać wartość z zakresu 4-7, stąd dodanie tej czwórki. Oczywiście wtedy efektywniej to zapisać w inny sposób, ale póki co nie komplikujmy :)
  • #5 11696189
    Jarosław J
    Poziom 14  
    Jasne teraz rozumiem sprawę cyfr w tablicy
    Oczywiście sprawę starszych i młodszych bitów już też.

    Zauważyłem jeszcze takie coś, że w każdym kolejnym przemiatanym segmencie była poświata cyfry poprzednio wyświetlanej (a raczej segmentów zaświeconych we wcześniejszym wyświetlaczu.
    Ale zauważyłem też, że w programie z książki w obsłudze przerwania wyłączana jest tylko młodsza połowa bajtu Jako że moje wyświetlacze podłączone są do starszej części trzeba było wyzerować starszą część :)

    Ech Dzięki tmf. Nie sądziłem, że w święta ktokolwiek przejmie się moim wyświetlaczem. Wszystkiego dobrego na resztę świąt.

    Może jeszcze dziś popchnę program dalej o obsługę dwóch DS'ów 18B20. Od kilku lat próbuję to poskładać na wyświetlaczach LED, Wiem trudny temat bo przerwania - opóźnienia... a ja miotam się w tym jak wróbel na nitce :) , ale liczę na Twoją pomoc.
  • #6 11696759
    tmf
    VIP Zasłużony dla elektroda
    W książce masz przykłąd jak zrobić obsługę DS820 w oparciu o USART - polecam ci skorzystanie z tego. Będzie o wiele łatwiej niż klasycznie na opóźnieniach.
  • #7 11696871
    BlueDraco
    Specjalista - Mikrokontrolery
    Ja tylko dodam, że w powyższym programie DP nie powinna być zadeklarowana jako stała w pamięci, a jako symbol preprocesora:

    #define DP 0x80

    Zdanie poniżej, to była pomyłka - proszę zignorować: (Przy takiej deklaracji, jak jest teraz, wszystkie odwołania do DP dają śmieci, a nie wartość zadeklarowaną.)
  • #9 11696949
    BlueDraco
    Specjalista - Mikrokontrolery
    A, przepraszam, śmieci nie dają, bo nie ma PROGMEM - przywidziało mi się. A powinno być zadeklarowane jako symbol preprocesora, bo w ten sposób kod będzie krótszy i szybszy.

    Tak, jak jest teraz, DP zajmuje pamięć ROM i RAM. Gdyby było PROGMEM - zajmowałoby (też bez potrzeby) tylko pamięć ROM.

    Ogólnie jako const PROGMEM jest sens deklarować dane nieskalarne - łańcuchy tekstowe, tablice itp..
  • #10 11696990
    tmf
    VIP Zasłużony dla elektroda
    To co piszesz nie jest prawdą. Kompilator optymalizuje taki zapis, dopóki ktoś nie próbuje pobrać adresu DP to kompilator nie utworzy instancji tej zmiennej. Aby mieć pewność co do tego można dodać static const, chociaż zazwyczaj nie jest to potrzebne. To co proponujesz, czyli wykorzystanie definicji preprocesora jest błędnym pomysłem, osiąga się to samo co w przypadku definicji const, ale traci się kontrolę typów. Mniej więcej tak, jakby sobie odstrzelić nogę po to, żeby jej nie złamać :)
  • #11 11697022
    BlueDraco
    Specjalista - Mikrokontrolery
    Ok, tak może (nie musi) zrobić kompilator, jeśli symbol ma atrybut static. Tylko po co polegać na optymalizacji, skoro to samo można uzyskać bez tej optymalizacji nie tworząc zbędnych danych w pamięci? Bez takiej optymalizacji kod będzie dłuższy i wolniejszy. Kontrola typu stałej rzadko ma uzasadnienie. Moim zdaniem więcej strat niż zysków.
    Poza tym filozofia "zadeklaruję daną, którą kompilator usunie" jest dość pokrętna - mniej więcej tak, jakby sobie przyszyć trzecią nogę, żeby ją zaraz potem urwać. :)

    Bez static kompilator nie ma prawa usunąć takiej danej. Może to ew. zrobić konsolidator. W każdym razie musi ona być wyemitowana w postaci pośredniej, chociaż odwołania do niej mogą być w kodzie zoptymalizowane jako stałe natychmiastowe (czyli może ich nie być). To trochę tak, jak z funkcjami inline bez atrybutu static.
  • #12 11697124
    tmf
    VIP Zasłużony dla elektroda
    Ale kompilator to robi na każdym poziomie optymalizacji (przykład dla najniższej -O1):
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    static dodatkowo nie utworzy zbędnej kopii zmiennej, z drugiej strony po to jest opcja data-sections i gc-sections, aby linker sam o to zadbał. Stąd też kod nigdy nie będzie dłuższy ani wolniejszy.
    Nie ma też co do tego dorabiać filozofii - po to piszę w c aby nie interesowały mnie niuanse jak kompilator coś potraktuje. Ma wygenerować poprawny kod i to wszystko czego programista od niego oczekuje. Rzeczy ponad to robi się przy pomocy rozszerzeń, np. atrybutów, opcji kompilacji itd.
    W ogóle preprocesor to zło i tylko pewne niedociągnięcia c powodują, że trzeba go używać (czasami i rozsądnie). W C99 konieczność jego użycia jest już mocno ograniczona, a w C++ praktycznie nie jest potrzebny.
    Swoją droga jeśli piszesz, że kontrola typów jest zbędna to dalsza dyskusja nie ma sensu. Zreszą nie tylko o samą kontrolę typów chodzi. Symbol preprocesora może być czymkolwiek.
  • #13 11697609
    BlueDraco
    Specjalista - Mikrokontrolery
    To nie tak. Są rzeczy, które kompilator może zoptymalizować, są takie, których mu nie wolno zoptymalizować. Nie ma takich, które MUSI optymalizować. Jeżeli polegasz na optymalizacji, to tak naprawdę nie wiadomo, jaki będzie efekt. Nie dyskutujemy o poprawności kodu (bo to oczywiste), a o jego rozmiarze i wydajności - a na to ma znaczący wpływ technika programowania. Ja staram się propagować takie techniki programowania, by zapis kodu był w miarę przyzwoity i bez polegania na geniuszu kompilatora. Weźmy np. kolejność pól w strukturach - tu kompilator nie podskoczy, a bezmyślność programisty owocuje np. 3 razy większą od potrzebnej zajętością pamięci.

    Z tą zbędnością preprocesora to chyba nieco pojechałeś. To jak wygląda program "hello" w C bez preprocesora? Albo program migający diodą na mikrokontrolerze nie korzystający z definicji rejestrów i bitów...

    Nie4 napisałem, że kontrola typów jest zbędna. Pisałem, że kontrola typu stałej ma niewielkie znaczenie w praktyce programowania, o ile programista wie, co robi. Ja zakładam, że programista powinien wiedzieć, co robi i wiedzieć, co może zrobić z programem kompilator. Ja w każdym razie się staram. ;)
  • #14 11698022
    tmf
    VIP Zasłużony dla elektroda
    Trochę sam sobie przeczysz. Skoro nie polegamy na optymalizacji wbudowanej w kompilator i jego "inteligencji" to rozważania na temat wielkości czy prędkości kodu są bez sensu. Techniki programowania to jedno, ale nie da się napisać optymalnego kodu całkowicie abstrahując od znajomości funkcjonowania używanego kompilatora. Zresztą łatwo się przekonać o tym zmieniając optymalizację na -O0 - powstały kod będzie tragedią niezależenie od sposobu napisania programu.
    Więc albo piszemy po prostu w c i problemy optymalizacji zlewamy, albo piszemy w c na kompilatorze, który znamy i wiemy, że coś zadziała tak a nie inaczej.
    A o nieużywaniu define piszę nie w kontekście jednej stałej, którą sobie moge zapisać najdziwaczniej jak tylko zechcę, w programie takich stałych mogą być dziesiątki, albo i setki. I robi się co raz większy bałagan. Im więcej przerzucę na kompilator tym większa szansa, że ew. problemy zostaną przez niego wykryte.
    Co do rejestrów, przecież tam kompletnie nie ma nic takiego co by wymagało preprocesora.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    generuje identyczny wynik jak wersja z użyciem preprocesora. Zajętość SRAM - 0 bajtów.
    Przy programowaniu oczywiście warto zakładac, że programista wie co robi, ale każdy kto trochę poprogramował wie, że nie zawsze tak jest. I każda pomoc ze strony kompilatora jest przydatna, w efekcie moim zganiem nie jest mądrym pomysłem z niej rezygnować nie otrzymując nic w zamian.
  • #15 11698429
    BlueDraco
    Specjalista - Mikrokontrolery
    Ok, ja tylko naprawdę nie rozumiem, w jakim celu programista deklaruje dane z założeniem, że kompilator tych danych nie zrealizuje, bo jak przypadkiem zrealizuje (czyli zrobi to, co programista napisał, nie przechytrzając i nie poprawiając go), to program będzie działał wolno i zajmował niepotrzebnie dużo pamięci. Moim zdaniem takie założenie jest chore. Nie podpisuję się pod taką filozofią i takim podejściem do programowania w ogóle. Moim zdaniem programista powinien pisać dobry, nienadmiarowy kod, bez niepotrzebnych zmiennych, stałych i funkcji.

    PS. Chciałem nieśmiało zauważyć, że w podanym przez Ciebie przykładzie bez preprocesora kompilator nie wiedziałby, co to takiego "PORTA". Więc preprocesora jak najbardziej używasz i żyć bez niego nie możesz.

    Pozdrawiam świątecznie.
  • #16 11698491
    tmf
    VIP Zasłużony dla elektroda
    Co do przykładu to używam PORTA, bo nie chciało mi się sprawdzać adresu PORTA, nic nie stoi na przeszkodzie, aby tam wstawić po prostu adres.
    Co do twoich założeń - programista pisze w c, jako programistę zazwyczaj nie obchodzi mnie co kompilator z tym zrobi - jedynym założeniem jest, że wygeneruje poprawny kod. Jako programiście, moim zadaniem jest także przekazać kompilatorowi maksymalną ilość informacji o kodzie. Korzystając z define nie robię tego, bo to żadna informacja dla kompilatora. Korzystając z mechanizmów języka c robię to. Kod też ma być czytelny - preprocesor to kompletnie uniemożliwia. Świetnym przykładem jest kompilator gcc, który w 90% napisany jest przy pomocy makr i wyszła z tego totalna sieczka, do tego stopnia kaszaniasta, że obecnie jest przepisywany na C++ z myślą właśnie aby preprocesor wywalić.
    Nie wiem dlaczego upierasz się, że kompilator dla stałej ma alokować jakąś pamięć? Jedyne co wynika ze standardu języka dla stałej to to, że można pobrać jej adres, czyli że operacja *(&const cośtam) jest poprawna. Bynajmniej nie wynika z tego konieczność jakiejkolwiek alokacji pamięci w każdym przypadku (nora bene dla symbolu zdefiniowanego przez define taka operacja w ogóle jest niemożliwa). Podobnie jak taka konieczność nie wynika z jakiejkolwiek deklaracji zmiennej. Stąd też twoja obawa o jakiś nadmiarowy kod nie jest niczym uzasadniona, równie dobrze, można by założyć, że kompilator przy operacji dodawania wstawi operację na zmiennych composite. Może, tylko, że tego nie robi...
    Preprocesor został wymyślony, aby uzupełnić pewne niedomogi języka i do niczego więcej. Należy więc go używać tam gdzie istotnie jest to niezbędne. Zresztą w tym kierunku idzie i rozwój C i C++ - w C++ jak pisałem użycie preprocesora z powodu istnienia odpowiednich mechanizmów w samym języku jest praktycznie niepotrzebne.
  • #17 11698923
    Jarosław J
    Poziom 14  
    Czytałem o USART i jest napisane, że potrzebuje on dokładnego taktowania i nie nadaje się do tego wewnętrzny RC. Ja swój układ taktuję wewnętrznym oscylatorem, 8 MHz (stąd mam cały PORTB Atmegi8) jednak wydaje mi się, że dotyczy to tylko układów komunikacji z innym urządzeniem, a generacja odcinków czasu nie jest aż tak krytyczna przy takiej wysokiej częstotliwości?
  • #18 11698941
    tmf
    VIP Zasłużony dla elektroda
    Tak, to dotyczy tylko komunikacji z innym mikrokontrolerem lub PC. Jeśli wykorzystujesz USART do realizacji 1-wire to dokładność wewnętrznego generatora RC jest wystarczająca.
  • #19 11699311
    Jarosław J
    Poziom 14  
    No tak doczytałem o dokładności taktowania w rozdziale 24.
    Zaniepokoiło mnie jednak to, że wymagane jest do 1WIRE poprzez USART wykorzystanie pinu D0. Ja mam tam podpięte katody wyświetlacza (całe 8 bitów portu D). Mam w związku z tym kilka pytań:

    1. Czy darować sobie w takim razie 1W po USART i bawić się z _delay'ami? mam dwa czujniki podpięte pod port C0 i C1 i chciałbym tę rozdzielność zachować Może jest możliwość przekierowania właściwości tego portuD0 przy pomocy jakichś operacji bitowych na inny port? (choć w sumie to kwestia sprzętowa i chyba się nie da).
    2. Czy obsługa 1w przy pomocy _delay'ów nie wprowadzi do układu tego słynnego ( m inn z Bascoma) zatrzymywania multipleksowania wyświetlaczy? Co prawda słyszałem o rozwiązaniu tzw piątego wyświetlacza (widmo) podczas obsługi którego wykonują się te krytyczne operacje, jednak nie jest to eleganckie rozwiązanie.
    3.Czytałem też o tym modyfikatorze ATOMIC BLOCK czy jest możliwość i sens zastosowania go w przerwaniach? tak żeby obsługa przerwania była zawsze dokańczana? czy o to w tym chodzi?
  • #20 11699393
    tmf
    VIP Zasłużony dla elektroda
    ad. 1. Jeśli chcesz niezależne dwa interfejsy 1-w to oczywiście mając tylko jeden USART tego nie osiągniesz. Jeśli możesz to najprościej będzie wymienić procka na taki, który posiada dwa.
    ad. 2. Nie, gdyż multipleksowanie jest w przerwaniach, efekt może być odwrotny - problemy z komunikacją 1-W.
    ad. 3. W przerwaniu na AVR ATOMIC_BLOCK się nie stosuje bo nie ma sensu - zwykłe AVRy mają jednopoziomową obsługę przerwań.

    Ponieważ multipleksowanie pokazane w książce praktycznie nie zabiera czasu procesora, więc jeśli go taktujesz 8-16 MHz to nie powinno być problemu z obsługa 1-w nawet na delayach. Jeśli by były to należy dodać sekcje ATOMIC_BLOCK wszędzie tam, gdzie odmierzane są naprawde krókie czasy (1-10 us), na wyświetlanie to nie powinno wpłnąć. Czyli nieco zmodyfikować dwie funkcje w bibliotece 1-W, która jest w przykładach.
  • #21 11699452
    Jarosław J
    Poziom 14  
    Ok. Czyli pozostając przy moim układzie:

    - wykorzystam _delay'e do 1Wire
    - zachowam rozdzielność komunikacji

    Chcę jeszcze zapytać czy wykorzystać pliki z katalogu rozdziału 24
    o nazwie
    1wire_basic.c (ona zawiera ATOMIC_BLOCK)
    czy
    1wire.c?

    No tak - nie było pytania doczytałem, że jeden jest częścią składową drugiego .
REKLAMA