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

[ATmega8][C] Obsługa ADC - odczyt kanałów dla linefollowera

Irvine Kinneas 09 Mar 2011 20:45 6916 41
  • #1 9256399
    Irvine Kinneas
    Poziom 17  
    Witam.

    Mam taki problem. Zbudowałem robota LF napisałem do niego całkiem sprawny kod w BASCOMIE. Teraz zacząłem uczyć się C (pisze w WinAVR) i chciałbym napisać program do niego. Mam jednak problem jak zrobić odczyt poszczególnych kanałów. W BAS robie to tak:
    Kod: text
    Zaloguj się, aby zobaczyć kod


    W C napisałem jak do tej pory ogólną konfiguracje wejść i przetwornika. Próbuję robić odczyt na razie na domyślnym kanale 0 (MUX0...3 nieustawione). Mam taki kod:

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


    Jednak nie daje to żadnego efektu. Dioda nie zapala się w momencie kiedy linia jest pod czujnikiem. Nie wiem co jest nie tak, przeglądałem kilka tematów o ADC na elektrodzie, czytałem też datasheet i nie mogę dojść do błędu. Nie wiem też jak zrobić to żeby zmieniać kanały po kolej i wczytywać z nich wartości do zmiennych. Próbowałem też w definicjach ustawić konfiguracje poszczególnych bitów MUX jednak i przełączać je na raz jednak też nie dawało to efektu. Więc pytanie co nie tak z konfiguracją że nawet odczyt z jednego czujnika jest niemożliwy i w jaki sposób przełączać czujniki(według mnie dobrym wyjściem byłoby użyć funkcji której parametrem byłby numer kanału a funkcja zwracałaby wartość odczytu z czujnika, a funkcje umieścić w pętli for gdzie wartość zmiennej sterującej była by numerem kanału).
  • Pomocny post
    #2 9256989
    tmf
    VIP Zasłużony dla elektroda
    Zmień podejście. Tryb free running praktycznie nie nadaje się w sytuacji w której planujesz zmieniać kanały. Lepiej zastosować single conversion i przerwania. W takiej sytuacji w procedurze obsługi przerwania zapisujesz do tablicy wartość ADC, inkrementujesz indeks tablicy, inkrementujesz ADMUX (kanał) i zaczynasz kolejną konwersję. I tak w kółko. W reszcie programu masz tablicę której elementy zawierają aktualne wartości poszczególnych kanałów ADC. Prosto i skutecznie.
  • #3 9260294
    Irvine Kinneas
    Poziom 17  
    Dzięki za sugestie. Zrobiłem obsługę przerwań tak jak mówiłeś jednak nadal nie działa odczyt(chwilowo skupiam się na odczycie ADC0, jeżeli to już zrobię to ze zmianami kanałów poradzę sobie). Oto aktualny "kod" w komentarzach są moje uwagi.

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


    Wiem, że pewnie sprawa jest banalna jednak dopiero uczę się programowania w C i proszę o dalsze wskazówki.
  • #5 9260739
    Irvine Kinneas
    Poziom 17  
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Jest tak, ale nadal nic się nie zmieniło :| Zmieniałem nawet procki aby sprawdzić czy nie jest uwalony jeden z nich, ale nadal zero rezultatu.

    Ok, działa pomógł taki zabieg:

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


    Nie wiem jakie to miało znaczenie, ale umieszczenie startu konwersji w "pętli głównej" spowodowało działanie przetwornika.
  • #7 9261412
    Irvine Kinneas
    Poziom 17  
    W takiej postaci:

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


    Konwersja działa prawidłowo, ustawienie bitu ADSC po sei(); ale nie w pętli while skutkowało tym że konwersje nie działała. Teraz kwestia zmiany kanału, przyjmijmy że chcę na sztywno czytać tylko jeden kanał więc ustawiam MUX0...2 zgodnie z datasheet'em w zależności od kanału w taki sposób:


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


    i wszystko gra aż do momentu kiedy chcę czytać z ADC2(ustawiony MUX1) wtedy odczyt nie działa.

    Natomiast sposób na samoczynną zmianę kanałów znalazłem taki:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    W dalszej części programu odczytuje tylko wynik z 2 elementu tablicy pomiar czyli z ADC1 mimo tego odczyt nie działa prawidłowo i warunek

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


    jest zawsze spełniony i dioda świeci ciągle. Co sądzicie o tej metodzie i co ewentualnie w niej nie gra.
  • #9 9266444
    Irvine Kinneas
    Poziom 17  
    Zmienna 'channel' byla typu int poprzedzona modyfikatorem volatile, zmiana jej typu na uint8_t nic nie zmieniła w działaniu programu.

    Dla jasności wklejam cały obecny kod:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Zastanawiam się czy sposób przełączania kanałów jest poprawny :? Wydaje mi się że wykorzystanie instrukcji switch case gdzie każdy case zawiera odpowiednie ustawienie bitów mux0...2 byłby lepszy.
  • Pomocny post
    #10 9266682
    dondu
    Moderator na urlopie...
    Każdy sposób jest dobry pod warunkiem, że działa.
    Nie widzę ani potrzeby zmiany, ani przeciwwskazań, by jej dokonać.
    Wybór należy do Ciebie.

    Dodano po 7 [minuty]:

    Jedyny problem jaki masz jest taki, że jedno z przerwań jest pomijane. Konkretnie to to które na wejściu przerwania ma wartość channel = 5

    prawidłowo powinno być tak:

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

    No chyba, że taki miałeś zamiar :)
  • #11 9266750
    Irvine Kinneas
    Poziom 17  
    Celowo pomijam przerwanie o numerze 5(czyli kanał ADC6) ponieważ wykorzystuję tylko 5 czujników(ADC0...4) ;) Jak wrócę z pracy sprawdzę jeszcze czy aby "zerowanie" zmiennej channel wg Twojego sposobu nie zmieni sytuacji w jakiś sposób.
  • #13 9266853
    Irvine Kinneas
    Poziom 17  
    Hmm, nie wiem czy dobrze myślę. Przerwanie od adc występuje po każdej zakończonej konwersji, więc jakie ma znaczenie czy odczytuję dane ze wszystkich kanałów czy tylko z 5? Konwersja kończy się odczytuje wartość rejestru, następnie zmieniam kanał. Kiedy zmienna channel osiąga wartość 4 czyli odczyt ADC4 automatycznie jest zerowana i w następnym przerwaniu czytane są dane z kanału 0. Dobrze myślę, czy jest jakieś 'ale'?
  • #14 9266911
    dondu
    Moderator na urlopie...
    Nigdzie nie piszę, że masz robić 6 kanałów.
    Przyglądnij się warunkowi który podałem: if (channel > 4)
    To 5 kanałów, a nie 6.

    A teraz pomyśl, co zrobi Twoja funkcja przerwania ISR() po dokonaniu pomiaru, gdy wykona przerwanie z channel=5 ?

    ... ponieważ:
    Irvine Kinneas napisał:
    Kiedy zmienna channel osiąga wartość 4 czyli odczyt ADC4 automatycznie jest zerowana...

    ... nie jest zerowana w tym momencie.
  • #15 9267979
    Irvine Kinneas
    Poziom 17  
    Hmm, sprawdziłem ten warunek jednak nic się nie zmieniło. Analizując Twojego if'a, wychodzi mi że w momencie kiedy channal == 4 następuje inkrementacja na 5 po czym jest on zerowany gdyż channal > 4. Więc w obsłudze jednego przerwania jest to wykonywane. Nie zmienia to faktu że nadal nie działa odczyt ze zmianą kanałów poszczególnych.
  • #16 9268012
    dondu
    Moderator na urlopie...
    Irvine Kinneas napisał:
    Hmm, sprawdziłem ten warunek jednak nic się nie zmieniło. Analizując Twojego if'a, wychodzi mi że w momencie kiedy channal == 4 następuje inkrementacja na 5 po czym jest on zerowany gdyż channal > 4. Więc w obsłudze jednego przerwania jest to wykonywane.

    No właśnie, w Twojej funkcji tak nie było, ten problem już masz z głowy.

    Irvine Kinneas napisał:
    Nie zmienia to faktu że nadal nie działa odczyt ze zmianą kanałów poszczególnych.

    A skąd taki wniosek?
  • #17 9268044
    Irvine Kinneas
    Poziom 17  
    dondu napisał:

    Irvine Kinneas napisał:
    Nie zmienia to faktu że nadal nie działa odczyt ze zmianą kanałów poszczególnych.

    A skąd taki wniosek?


    Sprawdziłem już działanie programu ze zmianami i widzę, że nadal bez zmian :| Miałem też lekko uszkodzony jeden z czujników, ale jest zmieniony i nadal nie działa.
  • #20 9268066
    dondu
    Moderator na urlopie...
    A masz jakiś schemat swojego układu?
    Napisz jeszcze co według Ciebie powinno się dziać z Twoim układem, a co się dzieje faktycznie, bo określenie "nadal nie działa" to zbyt mało.

    Dodano po 33 [minuty]:

    ... i zastanów się jak to:

    ADMUX = (ADMUX & 0xb01000000) + channel; 

    ma się do tego:

    ADMUX |= _BV(REFS0); // napięcie odniesienia z AVCC
    ADMUX |= _BV(ADLAR); // wynik w postaci 8-bitowej

    i do tego:

    if (pomiar[1] < 120)
  • #21 9268705
    Irvine Kinneas
    Poziom 17  
    Tutaj jest schemat:
    [ATmega8][C] Obsługa ADC - odczyt kanałów dla linefollowera

    Co do wyróżnionych przez Ciebie fragmentów kodu. Zmieniłem ustawianie bitów rejestru ADMUX w obsłudze przerwania na takie:
    ADMUX = (ADMUX & 0xb01100000) + channel; 

    czyli ustawienie REFS0 i ADLAR nie zmienia się, dodawany jest do tego numer kanału który będzie zapisany na bitach 0...3 rejestru ADMUX. Przynajmniej tak mi się wydaje. A co do sprawdzania wartości elementu 1 tablicy 'pomiar' ma to na celu tylko sprawdzenie czy przetwornik działa.

    Sam układ składa się z listwy 5 transoptorów refleksyjnych CNY70. Emitery fototranzystorów podłączone są do masy, kolektor do wejścia ADC ATmegi które podciągnięte jest do plusa zasilania(+5V) poprzez opornik 10kΩ. W momencie kiedy czujnik znajduje się nad czarna linią fototranzystor nie przewodzi i napięcie pojawia się na wejściu przetwornika ADC, kiedy znajduje się nad jaśniejszym podłożem napięcie przepływa do masy przez fototranzystor. Celem dla jakiego chcę odczytywać wartości z czujników przez ADC jest ustalenie czy dany CNY jest aktualnie nad linią czy poza nią. Prosty warunek:
    if (pomiar[1] < 120)
      {
       LED_ON;
      } 
      else
      {
       LED_OFF;
      }

    Sprawdza czy odczyt w ogóle działa(przy korzystaniu z pojedynczego wcześniej zdefiniowanego czujnika umieszczenie danego CNY nad linia powodowało gaśnięcie diody) a chwili obecnej chcę sprawdzić czy czujniki się przełączają i sprawdzam wartość na ADC1(pomiar[1]) mimo tego dioda cały czas świeci.
  • #22 9269261
    janbernat
    Poziom 38  
    W głównej pętli stale ustawiasz start konwersji ADC.
    A nie sprawdzasz czy już poprzednia się skończyła- to znaczy czy już bit ADIF jest ustawiony.
    A nie wiadomo czy obsługa przerwania się szybciej wykonuje czy też główna pętla.
    A do zmiany kanałów też jest potrzebny czas.
    ATmega8 jest uboga w źródła przerwań do uruchamiania ADC.
    ATmega88- dużo bogatsza.
  • #23 9270012
    dondu
    Moderator na urlopie...
    No to jedziemy po kolei:

    1. Schemat - jeżeli na pewno masz tak podłączony to jest OK.


    2.
    Irvine Kinneas napisał:
    Co do wyróżnionych przez Ciebie fragmentów kodu. Zmieniłem ustawianie bitów rejestru ADMUX w obsłudze przerwania na takie:
    ADMUX = (ADMUX & 0xb01100000) + channel; 

    Tak, właśnie tu był błąd. Czyli następny problem z głowy.



    3.
    Irvine Kinneas napisał:
    A co do sprawdzania wartości elementu 1 tablicy 'pomiar' ma to na celu tylko sprawdzenie czy przetwornik działa....

    OK, chciałem się upewnić.



    4. Janbernat wspominał o ciągłym startowaniu w pętli głównej.
    Moim zdaniem nie powinno to w niczym przeszkadzać, ponieważ:
    - nadpisywanie bitu startu nie powoduje przerwania aktualnie wykonywanego pomiaru,
    - po zakończeniu pomiaru od razu (następna instrukcja po zapaleniu flagi przerwania) jest generowane przerwanie odczytujące.
    datasheet napisał:
    This bit (ADSC) stays high as long as the conversion is in progress and will be cleared by hardware when the conversion is completed.

    Ale może warto się upewnić?



    5. Uwaga Janka dot. ADMUX jest bardzo słuszna. Powinieneś najpierw ustawić ADMUX, a dopiero później startować pomiar np:

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




    Gdyby nadal nie działało to:


    6. Wprawdzie nie zaznaczyłeś fototranzystorów na schemacie, ale opisałeś słownie więc jeżeli tak masz podłączone to jest ok.
    W związku z tym mam pytania:
    - czy zrobiłeś pomiary napięcia na wyjściu fototranzystora?
    - czy są prawidłowe w stosunku do wartości 120 jaką sprawdzasz w swoim warunku zapalającym LED?
    - czy robiłeś próby z inną wartością niż 120?



    7. Czy jesteś pewien, że przerwanie jest generowane przez ADC?
    Jeżeli nie, to trzeba to sprawdzić najlepiej poprzez np.:

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

    Efekt oczekiwany to mrugająca dioda z częstotliwością 2 Hz co oznaczać będzie iż ADC prawidłowo wywołuje przerwanie.



    8. Jeżeli pkt 7 będzie działać to czas na podłączenie potencjometru pod ADC0 i podmiana kodu na np.:



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

    Oczekiwane działanie pomiar dokonywany z częstotliwością 1 Hz i w zależności od stanu potencjometru LED świeci lub nie.


    9. Tak poza głównym tematem.
    Ponieważ to robot, czyli na zasilaniu bateryjnym, itp. to warto abyś:
    - robił co się da na przerwaniach,
    - usypiał procesor w pętli głównej do możliwie najbardziej głębokiego snu jaki możliwy w danym momencie (to nie jest trudne).
    To pozwoli Ci zaoszczędzić kilka mA tym bardziej że pracujesz na 10MHz
    ... a poprawnie powinno być (xxxxUL):
    #define F_CPU 10000000UL




    No i godzinka uciekła - z racji późnej pory (ostatnia poprawka 3:48) mogłem popełnić jakieś błędy - proszę o wyrozumiałość :)
  • #24 9270600
    janbernat
    Poziom 38  
    Właśnie co do tego "nudzę się..." mam wątpliwości.
    Po co w linefollower pomiary co ok. 13 cykli zegara (zegara ADC)- o ile pamietam?
    A teraz- ile cykli wykonuje się pętla główna?
    Podejrzewam że mniej niż 13x64=832 cykle.
    Czyli w pętli wielokrotnie zmieniamy numer kanału- pomimo że nie zakończył się poprzedni pomiar.
    Może wykorzystać jakiś timer do generowania czasu systemowego co 10-100ms- to zależy od potrzeb.
    I w pętli głównej mierzyć co np.100ms stan transoptorów.
    Bo w tym rozwiązaniu to chyba oprócz sprawdzania napięcia nic innego się nie zmieści- żadne sterowanie silników itp.
  • #25 9270655
    dondu
    Moderator na urlopie...
    janbernat napisał:
    Właśnie co do tego "nudzę się..." mam wątpliwości.
    Po co w linefollower pomiary co ok. 13 cykli zegara
    ...
    Bo w tym rozwiązaniu to chyba oprócz sprawdzania napięcia nic innego się nie zmieści- żadne sterowanie silników itp.

    Może jest kosmicznie szybki :)

    13 - owszem ale μs a nie cykli: (13 - 260 μs Conversion Time).
    Trzeba go jeszcze pomnożyć razy 5 (ilość czujników) i dopiero wyjdzie faktyczna częstotliwość pomiaru całego "oka" tego robota.

    Czy to za dużo? To zależy jak szybko jeździ.
    Trzeba pamiętać, że mogą powstawać błędy (dynamika ruchu, refleksy świetlne) więc może jednak wskazane jest szybkie analizowanie?
    Nie robiłem takiego robota, więc nie mam doświadczenia w tym temacie.


    janbernat napisał:
    Czyli w pętli wielokrotnie zmieniamy numer kanału- pomimo że nie zakończył się poprzedni pomiar.

    W kodach podanych przeze mnie już tak nie jest patrz pkt. 5.


    janbernat napisał:
    Może wykorzystać jakiś timer do generowania czasu systemowego co 10-100ms- to zależy od potrzeb....

    Jeżeli będzie chciał zmniejszyć ilość pomiarów, to jak najbardziej dobry pomysł.
  • #26 9271041
    janbernat
    Poziom 38  
    Cykli- a nie us.
    Razem ze startem- 14.5 cykli.
    str. 200 ds.
    Przy optymalnym zegarze ADC- 100kHz- daje to 145us na jedno przetwarzanie.
    Około- bo Irvine Kinneas ma 156250kHz podawane na ADC- więc nieco szybciej.
    Moze 100us- nie chce mi się dalej obliczać- chodzi o rząd wielkości.
    Ale faktycznie dondu- nowy kanał ustawiasz w przerwaniu- czyli tak jak być powinno.
    Czyli pętla główna ma 100us na wykonanie swojego programu.
    Dużo to nie jest- ale jeśli linefollower ma silnik rakietowy...
  • #27 9271078
    dondu
    Moderator na urlopie...
    Cykli zegara ADC, a nie CPU :)

    Str 198 rysunek preskalera ADC a pod nim:

    Cytat:

    By default, the successive approximation circuitry requires an input clock frequency between 50 kHz and 200 kHz to get maximum resolution. If a lower resolution than 10 bits is needed, the input clock frequency to the ADC can be higher than 200 kHz to get a higher sample rate.
    ...
    The ADC module contains a prescaler, which generates an acceptable ADC clock frequency from any CPU frequency above 100 kHz.

    Dlatego czas konwersji zawiera się w przedziale 13 - 260 μs.

    [ATmega8][C] Obsługa ADC - odczyt kanałów dla linefollowera

    Dodano po 5 [minuty]:

    Ale gdyby było 13 cykli CPU to Atmega kosztowałaby 30zł :D:D:D
  • #28 9271112
    Irvine Kinneas
    Poziom 17  
    Witam, dziękuje za zainteresowanie :)

    Hmm, to jest coraz dziwniejsza sprawa. Rozwiązanie z pkt 5 nie działa tak jak zakładamy, cały czas jest spełniony warunek z if'a. Natomiast 'procedury testowe' z punktów 7 i 8 działają tak jak zakładasz. Odnośnie pkt 6 robot działa już pod kontrolą programu w BASCOM'ie więc elektronika jest zlutowana poprawnie. Napięcia na CNY wynoszą odpowiednio 0,15V nad białym tłem i 4,3V nad czarną linią. Testowałem działanie programu z progiem od 50 do 250 także wszelkie możliwości są sprawdzone. Jeżeli jeden kanał ustawiony jest na sztywno działa poprawnie, problemy są tylko przy zmianie kanału.

    Robot w chwili obecnej porusza się z prędkością ok 0,3m/s docelowo będzie to 1m/s(na więcej silniki nie pozwolą). Do sterowania jak na razie wykorzystuje instrukcje switch(case) która w zależności od tego pod którym czujnikiem/ami znajduje się linia ustawia odpowiednia wartości PWM dla danego silnika(sterowanie przez L293DNE) dlatego żadne sztuczne opóźnienia nie wchodzą w grę odczyt musi być wykonywany jak najszybciej i jak największą ilość razy(szybsze stabilizowanie jego pozycji nad linia np. po wyjściu z zakrętu). Docelowo będzie zaimplementowany algorytm PID który również musi otrzymywać na bieżąco informacje z czujników.

    Cytat:
    9. Tak poza głównym tematem.
    Ponieważ to robot, czyli na zasilaniu bateryjnym, itp. to warto abyś:
    - robił co się da na przerwaniach,
    - usypiał procesor w pętli głównej do możliwie najbardziej głębokiego snu jaki możliwy w danym momencie (to nie jest trudne).


    Nie ma to większego sensu, ponieważ przejazd jeden to ok 30s maks więc na turniejach robot pracuje może ok 5min a przy pakiecie Li-pol 1200mAh nie ma to większego znaczenia i zawsze jest możliwość podładowania go ;)

    Cytat:
    Kod C

    ISR(ADC_vect)
    {
    //odczyt pomiaru
    pomiar[channel] = ADCH;

    if (pomiar[channel] < 120)
    {
    LED_ON;
    }
    else
    {
    LED_OFF;
    }


    ++channel;
    if (channel > 4)
    {
    channel = 0;
    }

    //startuj nowy pomiar
    ADMUX = (ADMUX & 0xb01100000) + channel;
    ADCSRA |= _BV(ADSC); //start konwersji

    } //end of ISR()


    Sprawdzanie warunku w obsłudze przerwania to średni pomysł moim zdaniem. Cały algorytm sterujący praca robota będzie zawarty w funkcji 'main', ponieważ będzie tam zaimplementowana auto-kalibracja, 'starter', algorytm sterowania silnikami, a w przyszłości PID. Dlatego przerwanie obecnie ma pełnić funkcję odczytu wartości i wpisania ich do tablicy, a dalszym wykorzystaniem tych danych zajmą się odpowiednie algorytmy w pętli głównej.
  • Pomocny post
    #29 9271122
    dondu
    Moderator na urlopie...
    Chwila na zrobienie herbatki i zastanowienie ...

    Dodano po 12 [minuty]:

    Będę odpowiadał na raty:

    Irvine Kinneas napisał:
    Sprawdzanie warunku w obsłudze przerwania to średni pomysł moim zdaniem. Cały algorytm sterujący praca robota będzie zawarty w funkcji 'main'...

    Kod który prezentowałeś był tylko kodem testowym i tak go traktuję, więc oczywiście warunki sterowania w main(), ale warunek channel w ISR() tak jak jest.

    Dodano po 2 [minuty]:

    Irvine Kinneas napisał:
    Nie ma to większego sensu, ponieważ przejazd jeden to ok 30s maks więc na turniejach robot pracuje może ok 5min a przy pakiecie Li-pol 1200mAh

    Jasne...

    Dodano po 2 [minuty]:

    Irvine Kinneas napisał:
    ... więc elektronika jest zlutowana poprawnie. Napięcia na CNY wynoszą odpowiednio 0,15V nad białym tłem i 4,3V nad czarną linią.

    OK, czyli szukamy problemu po stronie programu.

    Dodano po 2 [minuty]:

    Wklej proszę kompletny program w wersji pkt. 5 który jak mówisz ma cały czas spełniony warunek if.
  • #30 9271684
    Irvine Kinneas
    Poziom 17  
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


     if (pomiar[channel] < 120)//------------------------
        {
            LED_ON;
        }
        else                     // ten fragment umieścił bym tak czy inaczej w funkcji main
        {
            LED_OFF;
        }//--------------------------------------------


    Tutaj jest nasz warunek testowy, jednak jeżeli będziemy sprawdzać każdy element tablicy to w 4 na 5 przypadków będzie on spełniony, więc mignięcie diody będzie niewidoczne. Dlatego do testów proponuje wybrać na sztywno któryś element tablicy.
REKLAMA