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

[ATMega16][C]Program do sterowania serwem na liczniku 0

Zibizzetkowski 22 Sie 2011 10:43 3872 22
  • #1 9849799
    Zibizzetkowski
    Poziom 10  
    Witam
    Napisałem program dla atmegi 16 w języku C, który ma za zadanie odczytywać sygnał z potencjometru i przekazywać ruch na serwo. Temat sterowania serwem był wiele razy poruszany, ale mi chodzi o mój kod, robiłem go sam i chce się dowiedzieć gdzie popełniłem błąd. Otóż kiedy ruszam potencjometrem w miarę szybko, to serwo chodzi normalnie, jednak kiedy zaczynam ruszać potencjometrem bardzo wolno program się zawiesza i serwo przestaje się kręcić, muszę wtedy zresetować mikro kontroler.
    Oto kod:
    Kod: text
    Zaloguj się, aby zobaczyć kod


    W danym kodzie nie zamieściłem konfiguracji przetwornika AC.
    Pozdrawiam

    Poprawiłem temat, treść i tagi.
    [zumek]
  • #3 9850012
    tmf
    VIP Zasłużony dla elektroda
    Wpis dotyczący postu przeniesionego do kosza, usunąłem.
    [zumek]


    Zibizzetkowski: sprecyzuj problem. Podziel program na mniejsze części, oddające problem. Nie spodziewaj się, że komuś chce się pisać za ciebie. Pokaż także te fragmenty, które przed nami ukryłeś.
  • #4 9850244
    dondu
    Moderator na urlopie...
    1. read_ADC(); - brak tej funkcji w kodzie

    2.
    Zibizzetkowski napisał:
    W danym kodzie nie zamieściłem konfiguracji przetwornika AC.

    A dlaczego? Mamy się domyślać?

    3.
    LordBlick napisał:
    2. F_CPU do Makefile, a nie w kodzie.

    A skąd wiadomo w jakim środowisku autor pracuje?
    @Zibizzetkowski F_CPU masz poprawnie zadeklarowane (choć na końcu warto podać typ UL by nie było wątpliwości), aczkolwiek polecam lekturę: http://mikrokontrolery.blogspot.com/2011/04/bledy-kompilacji-programu.html

    4. LordBlick słusznie pyta o warningi, bo Twój kod je generuje (co najmniej jeden), a to znaczy, że je ignorujesz. Dla Ciebie WARNING = BŁĄD
    stopień nr 10: http://mikrokontrolery.blogspot.com/2011/04/pieklo-poczatkujacych.html
  • #5 9850443
    Zibizzetkowski
    Poziom 10  
    Oto cały kod:
    Kod: text
    Zaloguj się, aby zobaczyć kod


    #define F_CPU 8000000 //ustawienie oscylatora na 8MHz

    #include <avr/io.h> //dołączenie podstawowej biblioteki

    #include <avr/interrupt.h> //dołączenie biblioteki z przerwaniami



    #define LED PORTC //zdefiniowanie stałych

    #define PORTD2 2

    volatile uint16_t a, a1, a2=0;
    volatile uint16_t analog1, analog0;

    void init_ADC(void)
    {
    ADMUX = ADMUX& ~(1<<REFS1) | (1<<REFS0);//00 AREF, 01 AVCC, 11 int 2,5V
    ADMUX |= (1<<ADLAR);// 8 bit=1, 10 bit=0
    ADCSRA = (1<<ADPS2) | (1<<ADPS1) & ~(1<<ADPS0); // select the ADC clock frequency
    ADCSRA = ADCSRA | (1<<ADEN); // enable the ADC
    }
    void ADC_0()
    {
    ADMUX = ADMUX& ~(1<<MUX0)& ~(1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3);
    }


    uint16_t read_ADC()
    {
    //_delay_ms(10);
    ADCSRA = ADCSRA | (1<<ADSC); // start a conversion

    while( (ADCSRA & (1<<ADIF)) == 0 ); // wait for conversion to be completed

    ADCSRA = ADCSRA | (1<<ADIF); // clear the flag

    return ADCH;
    }

    int volatile licznik = 0, licznik2 = 0; //zmienna dla licznika programowego



    int main()

    {

    init_ADC();

    DDRC = 32; //konfiguracja portu jako wyjścia

    TCCR0 |= (1<<CS00); //bez preskalera

    TIMSK = 0b00000001; //włączenie przerwania od przepełnienia licznika

    TCNT0 = 225; //ustawienie wartości początkowej

    sei(); //globalne włączenie przerwań

    while(1) //sczytywanie ADC

    {
    a1=read_ADC();


    }

    }



    SIGNAL(SIG_OVERFLOW0) //początek funkcji obsługi przerwania

    {
    //licznik = 0;
    licznik++; //zwiększenie o 1

    TCNT0 = 225;
    if(licznik > 5100) //sprawdzamy, czy nie minęło już 5100 przepełnień=20ms

    {

    PORTC ^= 32; //zmiana stanu na porcie c

    if(PORTC & 32){
    //licznik = 4920;
    licznik = 5037-a1*3/6;} //regulacja szerokości impulsu
    if(!(PORTC & 32))
    licznik = 512;//ustawinie okresu
    TCNT0 = 225;
    }

    Kod: text
    Zaloguj się, aby zobaczyć kod

    Przepraszam że nie podałem całego kodu. Rrzeczywiście generuje 6 warningów podczas kompilacji. Myślałem że błędnie rozumuję i że błąd zrobiłem w funkcji SIGNAL(SIG_OVERFLOW0), w której miał być zawsze generowany sygnał o okresie ok 18ms i szerokości impulsu od 1ms do 2ms w zależności od położenia potencjometru.
    Pozdrawiam
    Pozdrawiam
    Kod: text
    Zaloguj się, aby zobaczyć kod


    Dodano po 8 [minuty]:

    Takie błędy generuje:

    led.c:19: warning: suggest parentheses around arithmetic in operand of |
    led.c:21: warning: suggest parentheses around arithmetic in operand of |
    led.c: At top level:
    led.c:25: warning: function declaration isn't a prototype
    led.c:31: warning: function declaration isn't a prototype
    led.c:48: warning: function declaration isn't a prototype

    Dodano po 1 [minuty]:

    4 pierwsze błędy odnoszą się do przetwornika adc, natomiast ostatni do funkcji main
  • #7 9850529
    Zibizzetkowski
    Poziom 10  
    W pierwszych dwóch dodałem nawiasy, a do funkcji dopisałem void i już nie ma żadnych błędów


    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #8 9850569
    dondu
    Moderator na urlopie...
    Jakie ustawienia źródła referencyjnego chcesz wybrać?
    Z sposobu pisania kodu domyślam się, że REFS=00, czyli AREF, Internal Vref turned off.
    Czy tak? Jeżeli tak to w ten sposób jest klarownie:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod



    Po co ta (wytłuszczona) część skoro generuje na tym bicie 0?
    ADCSRA = (1<<ADPS2) | ((1<<ADPS1) & ~ (1<<ADPS0));

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



    LordBlick w pkt nr 1 zasugerował abyś używał nowych wektorów przerwań i funkcji ISR() i podał Ci link.

    Wklej cały kod po zmianach. Oczywiście nie zapomnij o sprawdzeniu warningów.
  • #9 9850860
    Zibizzetkowski
    Poziom 10  
    Dobrze, poczytam i jak mi coś sensownego wyjdzie to napisz :-)

    Dodano po 35 [minuty]:

    Zmieniłem i uporządkowałem trochę kod, teraz wygląda tak, trochę się poprawiło, ale i tak czasami serwo łapie zwiechy.
    Kod: text
    Zaloguj się, aby zobaczyć kod

    #define F_CPU 8000000UL //ustawienie oscylatora na 8MHz

    #include <avr/io.h> //dołączenie podstawowej biblioteki

    #include <avr/interrupt.h> //dołączenie biblioteki z przerwaniami

    #define LED PORTC //zdefiniowanie stałych



    volatile uint16_t a1;

    int volatile licznik = 0; //zmienna dla licznika programowego

    //inicjalizacja adc

    void init_ADC(void)
    {
    ADMUX = (ADMUX & ~(1<<REFS1)) | (1<<REFS0);//00 AREF, 01 AVCC, 11 int 2,5V
    ADMUX |= (1<<ADLAR);// 8 bit=1, 10 bit=0
    ADCSRA = (1<<ADPS2) | (1<<ADPS1); // select the ADC clock frequency
    ADCSRA = ADCSRA | (1<<ADEN); // enable the ADC
    }

    void ADC_0(void)
    {
    ADMUX = ADMUX& ~(1<<MUX0)& ~(1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3);
    }


    uint16_t read_ADC(void)
    {

    ADCSRA = ADCSRA | (1<<ADSC); // start a conversion

    while( (ADCSRA & (1<<ADIF)) == 0 ); // wait for conversion to be completed

    ADCSRA = ADCSRA | (1<<ADIF); // clear the flag

    return ADCH;
    }




    ISR(TIMER0_OVF_vect) //początek funkcji obsługi przerwania

    {

    licznik++; //zwiększenie o 1

    TCNT0 = 225;

    if(licznik > 5100) //sprawdzamy, czy nie minęło już 5100 przepełnień

    {

    PORTC ^= 32; //zmiana stanu na porcie c

    if(PORTC & 32)
    licznik = 5037-a1*3/6;//jedna skrajna pozycja 4905 druga 5048 - ustawinie wypełnienia

    if(!(PORTC & 32))
    licznik = 512;//ustawinie okresu

    }


    }


    int main(void)

    {

    init_ADC();

    DDRC = 32; //konfiguracja portu jako wyjścia

    TCCR0 |= (1<<CS00); //bez preskalera

    TIMSK = 0b00000001; //włączenie przerwania od przepełnienia licznika

    TCNT0 = 225; //ustawienie wartości początkowej

    sei(); //globalne włączenie przerwań

    while(1) //sczytywanie ADC

    {
    ADC_0();
    a1=read_ADC();

    }

    }
    Kod: text
    Zaloguj się, aby zobaczyć kod
  • #10 9851191
    tmf
    VIP Zasłużony dla elektroda
    Ciągle jeszcze możesz uporządkować kod. Np. twoja funkcja read_ADC zwraca wartość uint16, a z ADC czhytasz tylko 8-bitów.
    Jedynym miejscem gdzie może się robić zwiecha to fragment:
    while( (ADCSRA & (1<<ADIF)) == 0 ); // wait for conversion to be completed

    ADCSRA = ADCSRA | (1<<ADIF); // clear the flag

    Dlaczego nie czytasz flagi ADSC?
  • #11 9852026
    Zibizzetkowski
    Poziom 10  
    Witam
    Nie rozumiem pytania, nie jestem biegły w programowaniu więc nie wszystko roumiem, ADSC ustawiam na 1 aby rozpocząć konwersje, po co mam czytać flage ADSC?
    Zauważyłem jeszcze jedną istotną rzecz, mianowicie jak podłączam zewnętrzne zasilanie, czyli nie zasilam układu z programatora to serwo gubi się szybciej.
    Program napisany na pwm również nie zapewnia bezawaryjnej pracy serwa, więc są chyba dwie możliwości:
    Albo serwa są tak kiepskie że sie psuje wszystko, albo wszystko zależy od przetwornika, gdzieś zrobiłem błąd, jak sądzicie? Niżej podaje program z wcześniejszy z dodaniem pwmu.
    Kod: text
    Zaloguj się, aby zobaczyć kod

    #define F_CPU 8000000UL //ustawienie oscylatora na 8MHz

    #include <avr/io.h> //dołączenie podstawowej biblioteki

    #include <avr/interrupt.h> //dołączenie biblioteki z przerwaniami

    #define LED PORTC //zdefiniowanie stałych



    volatile uint16_t a1;

    int volatile licznik = 0; //zmienna dla licznika programowego

    //inicjalizacja adc

    void init_ADC(void)
    {
    ADMUX = (ADMUX & ~(1<<REFS1)) | (1<<REFS0);//00 AREF, 01 AVCC, 11 int 2,5V
    ADMUX |= (1<<ADLAR);// 8 bit=1, 10 bit=0
    ADCSRA = (1<<ADPS2) | (1<<ADPS1); // select the ADC clock frequency
    ADCSRA = ADCSRA | (1<<ADEN); // enable the ADC
    }

    void ADC_0(void)
    {
    ADMUX = ADMUX& ~(1<<MUX0)& ~(1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3);
    }


    uint16_t read_ADC(void)
    {

    ADCSRA = ADCSRA | (1<<ADSC); // start a conversion

    while( (ADCSRA & (1<<ADIF)) == 0 ); // wait for conversion to be completed

    ADCSRA = ADCSRA | (1<<ADIF); // clear the flag

    return ADCH;
    }


    void init_PWM(void)
    {

    TCCR0 = 0b01101001;
    TCCR1A = 0b10100010; // ustawienie pinów i trybu fast PWM
    TCCR1B = 0b00011010; // ustawienia z brakiem preskalera
    ICR1=20000;// ustawinie okresu

    }

    double PWM(double a;)
    {

    OCR1A = 800+a1*5.5;
    OCR1B = 800+a1*5.5;

    }

    ISR(TIMER0_OVF_vect) //początek funkcji obsługi przerwania

    {

    licznik++; //zwiększenie o 1

    TCNT0 = 225;

    if(licznik > 5100) //sprawdzamy, czy nie minęło już 5100 przepełnień

    {

    PORTC ^= 32; //zmiana stanu na porcie c

    if(PORTC & 32)
    licznik = 5037-a1*3/6;//jedna skrajna pozycja 4905 druga 5048 - ustawinie wypełnienia

    if(!(PORTC & 32))
    licznik = 512;//ustawinie okresu

    }


    }


    int main(void)

    {

    init_ADC();
    init_PWM();

    DDRC = 32; //konfiguracja portu jako wyjścia
    DDRB=255;// PORTY B ustawione jako wyjscia
    DDRD=255;//PORTY D ustawione jako wyjscia
    DDRA=0;

    TCCR0 |= (1<<CS00); //bez preskalera

    TIMSK = 0b00000001; //włączenie przerwania od przepełnienia licznika

    TCNT0 = 225; //ustawienie wartości początkowej

    sei(); //globalne włączenie przerwań

    while(1) //sczytywanie ADC

    {
    ADC_0();
    a1=read_ADC();
    PWM(a1);
    }

    }
    Kod: text
    Zaloguj się, aby zobaczyć kod
  • #12 9852295
    LordBlick
    VIP Zasłużony dla elektroda
    Zibizzetkowski napisał:
    Witam
    Nie rozumiem pytania, nie jestem biegły w programowaniu więc nie wszystko roumiem, ADSC ustawiam na 1 aby rozpocząć konwersje, po co mam czytać flage ADSC?
    Proponuję szukać na to odpowiedzi w sekcji ADC w nocie katalogowej http://www.atmel.com/dyn/resources/prod_documents/doc2466.pdf
  • Pomocny post
    #13 9852311
    janbernat
    Poziom 38  
    Co 3.88us wywołujesz przerwanie od przepełnienia timera.
    A przetwarzanie ADC w pętli while trwa 0.12ms- czyli 120us.
    Czyli podczas tej pętli pojawi się wielokrotnie przerwanie od timera0.
    A jeśli się pojawi podczas sprawdzania warunków tej pętli to moga się dziać dziwne rzeczy.
    Jedna instrukcja przy 8Mhz trwa 0.125us.
    Czyli obsługa przerwania powinna być krótsza niż 31 pojedynczych rozkazów- a wydaje mi się ze nie jest.
    Bo jeśli jest ustawiona flaga żądania obsługi przerwania to następne ustawienie flagi nic nie zmieni- możesz w ten sposób tracić sporo nieobsłużonych przerwań od timer0.
    Serwa podobnie jak silniki krokowe wydają się być bardzo wolne w porównaniu z szybkością procesora- ale tylko do momentu wykonania obliczeń czasowych.
  • #15 9857755
    Zibizzetkowski
    Poziom 10  
    Dziękuje wszystkim za pomoc. Zwiększyłem czas przerwania i wydaje się być ok, ale teraz pojawił mi się jeszcze jeden problem ponieważ chciał bym tym programem obsłużyć więcej serw niż jedno, a dokładniej 4 i nie bardzo wiem jak to zrobić.
  • #17 9857836
    Zibizzetkowski
    Poziom 10  
    Kod: text
    Zaloguj się, aby zobaczyć kod

    define F_CPU 8000000UL //ustawienie oscylatora na 8MHz

    #include <avr/io.h> //dołączenie podstawowej biblioteki

    #include <avr/interrupt.h> //dołączenie biblioteki z przerwaniami

    #define LED PORTC //zdefiniowanie stałych



    volatile uint16_t a1, a2, a3;

    int volatile licznik1 = 0; //zmienna dla licznika programowego
    int volatile licznik2 = 0;
    int volatile licznik3 = 0;
    //inicjalizacja adc

    void init_ADC(void)
    {
    ADMUX = (ADMUX & ~(1<<REFS1)) | (1<<REFS0);//00 AREF, 01 AVCC, 11 int 2,5V
    ADMUX |= (1<<ADLAR);// 8 bit=1, 10 bit=0
    ADCSRA = (1<<ADPS2) | (1<<ADPS1); // select the ADC clock frequency
    ADCSRA = ADCSRA | (1<<ADEN); // enable the ADC
    }

    void ADC_0(void)
    {
    ADMUX = ADMUX& ~(1<<MUX0)& ~(1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3);
    }

    void ADC_1(void)
    {
    ADMUX = ADMUX | ((1<<MUX0)& ~(1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3));
    }

    void ADC_2(void)
    {
    ADMUX = (ADMUX& ~(1<<MUX0)) | ((1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3));
    }

    uint16_t read_ADC(void)
    {

    ADCSRA = ADCSRA | (1<<ADSC); // start a conversion

    while( (ADCSRA & (1<<ADIF)) == 0 ); // wait for conversion to be completed

    ADCSRA = ADCSRA | (1<<ADIF); // clear the flag

    return ADCH;
    }


    void init_PWM(void)
    {

    TCCR0 = 0b01101001;
    TCCR1A = 0b10100010; // ustawienie pinów i trybu fast PWM
    TCCR1B = 0b00011010; // ustawienia z brakiem preskalera
    ICR1=20000;// ustawinie okresu

    }

    double PWM(double a;)
    {

    OCR1A = 800+a1*5.5;
    OCR1B = 800+a1*5.5;

    }

    ISR(TIMER0_OVF_vect) //początek funkcji obsługi przerwania

    {


    licznik1++; //zwiększenie o 1
    licznik2++; //zwiększenie o 1
    licznik3++; //zwiększenie o 1

    TCNT0 = 176;

    if(licznik1 > 2000) //sprawdzamy, czy nie minęło już 5100 przepełnień

    {

    PORTC ^= 128; //zmiana stanu na porcie c

    if(PORTC & 128)
    licznik1 = 1930-(a1-140)*1,5;//sterowanie szerokością impulsu

    if(!(PORTC & 128))
    licznik1 = 200;//ustawinie okresu


    }

    if(licznik2 > 2000) //sprawdzamy, czy nie minęło już 5100 przepełnień

    {

    PORTC ^= 64; //zmiana stanu na porcie c

    if(PORTC & 64)
    licznik2 = 1930-(a1-140)*1,5-(a2-140)*1,5;//sterowanie szerokością impulsu

    if(!(PORTC & 64))
    licznik2 = 200;//ustawinie okresu

    }

    if(licznik3 > 2000) //sprawdzamy, czy nie minęło już 5100 przepełnień

    {

    PORTC ^= 32; //zmiana stanu na porcie c

    if(PORTC & 32)
    licznik3 =1930-(a1-140)*1,5-(a2-140)*1,5-(a3-140)*1,5;//sterowanie szerokością impulsu

    if(!(PORTC & 32))
    licznik3 = 200;//ustawinie okresu

    }
    }


    int main(void)

    {

    init_ADC();
    init_PWM();

    DDRC = 224; //konfiguracja portu jako wyjścia
    DDRB=255;// PORTY B ustawione jako wyjscia
    DDRD=255;//PORTY D ustawione jako wyjscia
    DDRA=0;

    TCCR0 |= (1<<CS00); //bez preskalera

    TIMSK = 0b00000001; //włączenie przerwania od przepełnienia licznika

    TCNT0 = 176; //ustawienie wartości początkowej

    sei(); //globalne włączenie przerwań

    while(1) //sczytywanie ADC

    {
    ADC_0();
    a1=read_ADC();
    ADC_1();
    a2=read_ADC();
    ADC_2();
    a3=read_ADC();
    PWM(a1);
    }

    }
    Kod: text
    Zaloguj się, aby zobaczyć kod


    Dodano po 8 [minuty]:

    Myslałem zeby to zrobić w taki sposób ale widocznie tak sie nie da :-)
    A to schemat:
    [ATMega16][C]Program do sterowania serwem na liczniku 0

    Dodano po 2 [minuty]:

    W programie chodziło mi o to aby potencjometr P1 ruszał serwem1,2,3. Potencjometr 2 miał by ruszać tylko serwami 2,3. Potencjometr 3 ruszał by tylko serwem3

    Dodano po 21 [minuty]:

    W tym programie wszystkie serwa ruszają się tak jak potencjometr1, ale żadne nie rusza się kiedy ruszam potencjometrem 2,3.
  • #18 9858885
    janbernat
    Poziom 38  
    Co to jest za zwierzę z rogami i zębami?
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Po co Ci double- czyli float do wstawiania do rejestrów licznika?
    Można dać *55/10 i nie masz float tylko normalny typ zmiennej.
    Następna sprawa- czy przecinki przy obliczeniach to aby jest to o co chodziło?
    A nie kropki?
    I zamiast 1.5 też dajesz 15/10 - przecież nie wpiszesz do rejestrów połowy bitu.
    No to co pisał dondu- wykorzystaj przerwanie od ADC.
    Tryb przetwarzania na żądanie- a nie oczekiwanie w pętli.
    W opisie rejestru SFIOR są podane źródła wyzwalania ADC.
    Jak skończy- to zgłasza przerwanie a w przerwaniu tylko odczytujesz wynik i zmieniasz kanał odczytu.
    Niepotrzebny jest tak częsty odczyt- przecież nie jesteś w stanie tak szybko kręcic potencjometrem.
    A potem pomyśl o kilku odczytach uśrednianiu wyników.[/code]
  • #19 9860344
    Zibizzetkowski
    Poziom 10  
    Dzięki janbernat :-)
    Masz racje tam powinny być kropki, doble tez jest bez potrzeby :-)
    Jak mnie nie przerośnie zrobienie przerwania na analogu to zrobię, a jak mnie przerośnie to napisze ;-)
    Pozdrawiam
  • #20 9862027
    Zibizzetkowski
    Poziom 10  
    No i nie mogę się połapać w zeznaniach, nie wiem co do końca chce zrobić z tym przerwaniem i Ac, mógł by mi to ktoś na chłopski rozum wytłumaczyć?
    W rejestrze SFIOR mam ustawić żeby wchodziło do przerwania kiedy nastąpi przepełnienie licznika 0? i tam mam uruchamiać odczyt adc?
  • #21 9862996
    janbernat
    Poziom 38  
    Przepisz poprawiony program i dodaj komentarze.
    Nie- "początek obsługi przerwania" bo to każdy widzi.
    Ale- np. "przerwanie od przepełnienia timer0 wywoływane co x us"- bo nie chce się liczyć co ile.
    http://www.starz.pl/elektronika/kalk.rar
    Teraz tak- możesz URUCHOMIĆ przetwarzanie ADC ustawiająć odpowiednio rejestr SFIOR.
    Możesz dać dodatkową flagę tak jak dałeś flagi do licznika programowego np.
    int volatile start_ADC.
    Możesz do tego wykorzystać Timer1- dla ATmega32 na str. 109 podane jest kiedy ustawiana jest flaga TOV1 dla każdego z trybów działania.
    Ale to jest uruchomienie przetwarzania ADC.
    A jak ADC skończy przetwarzanie to wystawia flagę ADIF i przy ustawionej fladze ADIE może wejść w obsługę przerwania- całkiem tak jak przy timerach.
    No ale to przerwanie trzeba zwykle obsłużyć.
    W przerwaniu od ADC po prostu przepisujemy wynik z rejestrów (rejestru) do jakiejś zmiennej int volatile pomiar_adc i już.
    Ale co wykorzystać jak źródło startu przetwarzania- to trzeba popatrzeć na czasy.
    Ponieważ pokręcanie potencjometrem od zera do max zajmuje ok.1s to uruchamianie przetwarzania ADC co 10ms wystarczy w tym wypadku.
    Trzeba znaleźć takie źródełko wyzwalania w już wykorzystywanych timerach żeby nie zaprzęgać do roboty następnych.
    Czy to będzie 5ms czy 20ms- to bez znaczenia.
    Aby nie co kilka us- to bez sensu.
    Dlatego pisałem o komentarzach- co jak szybko chodzi.
  • #22 9864502
    Zibizzetkowski
    Poziom 10  
    Kod: text
    Zaloguj się, aby zobaczyć kod

    #define F_CPU 8000000UL //ustawienie oscylatora na 8MHz

    #include <avr/io.h> //dołączenie podstawowej biblioteki

    #include <avr/interrupt.h> //dołączenie biblioteki z przerwaniami

    #define LED PORTC //zdefiniowanie stałych



    volatile uint16_t a1, a2, a3;

    int volatile licznik1 = 0; //zmienna dla licznika programowego
    int volatile licznik2 = 0;
    int volatile licznik3 = 0;
    //inicjalizacja adc

    void init_ADC(void)
    {
    ADMUX = (ADMUX & ~(1<<REFS1)) | (1<<REFS0);//00 AREF, 01 AVCC, 11 int 2,5V
    ADMUX |= (1<<ADLAR);// 8 bit=1, 10 bit=0
    ADCSRA = (1<<ADPS2) | (1<<ADPS1); // select the ADC clock frequency
    ADCSRA = ADCSRA | (1<<ADEN); // enable the ADC
    }

    void ADC_0(void)
    {
    ADMUX = ADMUX& ~(1<<MUX0)& ~(1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3);
    }

    void ADC_1(void)
    {
    ADMUX = ADMUX | ((1<<MUX0)& ~(1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3));
    }

    void ADC_2(void)
    {
    ADMUX = (ADMUX& ~(1<<MUX0)) | ((1<<MUX1)& ~(1<<MUX2)& ~(1<<MUX3));
    }

    uint16_t read_ADC(void)
    {

    ADCSRA = ADCSRA | (1<<ADSC); // start a conversion

    while( (ADCSRA & (1<<ADIF)) == 0 ); // wait for conversion to be completed

    ADCSRA = ADCSRA | (1<<ADIF); // clear the flag

    return ADCH;
    }


    void init_PWM(void)
    {

    TCCR0 = 0b01101001;
    TCCR1A = 0b10100010; // ustawienie pinów i trybu fast PWM
    TCCR1B = 0b00011010; // ustawienia z brakiem preskalera
    ICR1=20000;// ustawinie okresu 20ms

    }

    int PWM(int a;)
    {

    OCR1A = 800+a1*55/10;// sterowanie szerokością impulsu od 0,8ms do ok. 2ms
    OCR1B = 800+a1*55/10;// sterowanie szerokością impulsu od 0,8ms do ok. 2ms

    }

    ISR(TIMER0_OVF_vect) //początek funkcji obsługi przerwania

    {


    licznik1++; //zwiększenie o 1
    licznik2++; //zwiększenie o 1
    licznik3++; //zwiększenie o 1

    TCNT0 = 176;//początkowa wartość timera 0, daje nam przerwanie co 0,00001s

    if(licznik1 > 2000) //sprawdzamy, czy nie minęło już 2000 przepełnień, czyli 0,00001s*2000=0,02s=20ms - okres sygnału

    {

    PORTC ^= 128; //zmiana stanu na porcie c

    if(PORTC & 128)
    licznik1 = 1930-(a1-140);//ustawienie szerokości impulsu z potencjometru 1

    if(!(PORTC & 128))
    licznik1 = 200;//ustawinie okresu


    }

    if(licznik2 > 2000) //sprawdzamy, czy nie minęło już 2000 przepełnień, czyli 0,00001s*2000=0,02s=20ms - okres sygnału

    {

    PORTC ^= 64; //zmiana stanu na porcie c

    if(PORTC & 64)
    licznik2 = 1930-(a1-140)-(a2-140);//ustawienie szerokości impulsu z potencjometru 1 i 2

    if(!(PORTC & 64))
    licznik2 = 200;//ustawinie okresu

    }

    if(licznik3 > 2000) //sprawdzamy, czy nie minęło już 2000 przepełnień, czyli 0,00001s*2000=0,02s=20ms - okres sygnału

    {

    PORTC ^= 32; //zmiana stanu na porcie c

    if(PORTC & 32)
    licznik3 = 1930-(a1-140)-(a2-140)-(a3-140);//ustawienie szerokości impulsu z potencjometru 1, 2 i 3

    if(!(PORTC & 32))
    licznik3 = 200;//ustawinie okresu

    }
    }


    int main(void)

    {

    init_ADC();
    init_PWM();

    DDRC = 224; //konfiguracja portu jako wyjścia
    DDRB=255;// PORTY B ustawione jako wyjscia
    DDRD=255;//PORTY D ustawione jako wyjscia
    DDRA=0;

    TCCR0 |= (1<<CS00); //bez preskalera

    TIMSK = 0b00000001; //włączenie przerwania od przepełnienia licznika

    TCNT0 = 176; //początkowa wartość timera 0, daje nam przerwanie co 0,00001s

    sei(); //globalne włączenie przerwań

    while(1) //sczytywanie ADC

    {
    ADC_0();
    a1=read_ADC();
    ADC_1();
    a2=read_ADC();
    ADC_2();
    a3=read_ADC();
    PWM(a1);
    }

    }
    Kod: text
    Zaloguj się, aby zobaczyć kod


    Dodano po 4 [minuty]:

    Cały program z komentarzami :-)
    Dzięki wielkie za kalkulator, nie muszę już na piechotę liczyć :-)

    Dodano po 8 [minuty]:

    Jeszcze zapomniałem zmienić w tym miejscu

    TCCR1B = 0b00011010; // ustawienia preskalera na 8

    Mam jeszcze jedno pytanie, dlaczego jak mnoże dany odczyt z ADC to zakres serwa sie nie zwiększa tylko serwo nie wie co ma robić i zachowuje sie nie przewidywalnie, podaje fragment kodu:

    Dodano po 1 [minuty]:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    if(licznik1 > 2000) //sprawdzamy, czy nie minęło już 2000 przepełnień, czyli 0,00001s*2000=0,02s=20ms - okres sygnału

    {

    PORTC ^= 128; //zmiana stanu na porcie c

    if(PORTC & 128)
    licznik1 = 1930-(a1-140)*15/10;//serwo zaczyna świrować

    if(!(PORTC & 128))
    licznik1 = 200;//ustawinie okresu


    }
    Kod: text
    Zaloguj się, aby zobaczyć kod
  • #23 9876388
    janbernat
    Poziom 38  
    No jest coś irytującego w sterowaniu serw.
    No bo tak- serwo ma dostać sygnał co 20ms.
    No niekoniecznie- 15-30ms wystarczy.
    Czyli procesor może przez wieczność robić co chce albo spać.
    Potem ma wystawić impuls 0.6- 2.4ms.
    Ale tylko jeden impuls na każdy z kanałów.
    Ale z rozdzielczością powiedzmy 1/256.
    Co powoduje konieczność wywołania przerwań co kilka us- jeśli jest to jeden timer.
    No jest to rozwiązanie siłowe- pewnie się da ale jakoś mi się nie podoba.
    Bo na główną pętlę zostaje mało czasu.
    A może zrobić tak żeby przerwanie było co number_of_channels/20ms i sterować kanałami po kolei?
    Co 7ms w tym wypadku.
    Na każdy kanał podać impuls o długości zmierzonej w ADC- też zmieniając kanały pomiarowe.
    Jak się da ustawić w przerwaniu od Timer1ovf rejestry OCR1A i OCR1B to może się da wygenerować pojedynczy impuls na jakiś pin?
    Próbuję to "ubrać w C" ale jeszcze mi się nie udaje.
REKLAMA