Skocz do zawartości

ATMega 8 Sterownik serwonapędów.


davidpi

Pomocna odpowiedź

Pozwolę sobie jeszcze raz nadużyć Waszej uprzejmości i znowu zwracam się z prośbą o pomoc

Sprawa dotyczy sterownika do serwonapędu (na razie jednego, później rozbuduje do 8).

Taktowanie 8MHz.

Oto kod programu:

#include<avr/io.h> 
#include<avr/interrupt.h> 
#include<HD44780.c> 
#include<util/delay.h> 
#include<stdlib.h> 

//------------ F_CPU 8MHz -----------// 

#define SERWPORT     PORTB 
#define SERWDIR     DDRB 

void PORT_Init() 
{ 
   SERWDIR = 0xFF; 
   SERWPORT = 0x00; 
} 

void TIMER1_Init() 
{ 
   TCCR1B |= (1<<WGM12) | (1<<CS11);                            //Tryb CTC, Preskaler przez 8    
   OCR1A = 1500;                        
} 

void TIMER2_Init() 
{ 
   TCCR2 |= (1<<WGM21) | (1<<CS20) | (1<<CS21) | (1<<CS22);     //Tryb CTC, preskaler przez 1024 
   OCR2 = 156;                                                 //Odmierzanie 20ms 
   TIMSK |= (1<<OCIE2);                                        //Zezwolenie na przerwanie CTC 
} 

ISR(TIMER1_COMPA_vect) 
{ 
   SERWPORT = 0x00; 
   TIMSK &= ~_BV(OCIE1A); 
} 

ISR(TIMER2_COMP_vect) 
{ 
   TCNT1=0; 
   SERWPORT = 0xFF; 
   TIMSK |= _BV(OCIE1A); 
} 

void LCD() 
{ 
   char buf[5]; 
   itoa(OCR1A,buf,10); 
   LCD_Clear(); 
   LCD_WriteText(buf); 
   _delay_ms(1); 
} 

int main() 
{ 
   LCD_Initalize(); 
   PORT_Init(); 
   TIMER1_Init(); 
   TIMER2_Init(); 
   sei(); 
   while(1) 
   { 
       LCD(); 
   } 
   return 0; 
} 

Serwo podłączone do dowolnego pinu PORTB. TIMER2 odmierza 20ms i generuje przerwanie. W przerwaniu zerowany jest TIMER1, ustawiany jest PORTB i włączane jest zezwolenie na przerwanie od TIMER1. Mija czas zależny od wartości OCR1A ( ustawiłem na razie na 1,5ms). Po tym czasie TIMER1 generuje przerwanie. W tym przerwaniu PORTB jest zerowany i wyłączane jest zezwolenie na przerwanie od TIMER1.

Program się kompiluje. Przerwania się uruchamiają. Jednak serwo ucieka w skrajne położenie i próbuje iść dalej, tak jakby OCR1A była z poza zakresu pracy serwa.

Czy ktoś ma jakiś pomysł co może być źle w tym programie?

Pozdrawiam

Link do komentarza
Share on other sites

Moim zdaniem błędem jest to, że nie pozwalasz timerowi 2 liczyć. Podzielnik=0x07 oznacza w nim taktowanie z wejścia T2. Ponieważ prawdopodobnie nie masz tam żadnego zegara, timer stoi a na wyjściach SERWPORT będzie ciągłe zero. Muszę sprawdzić moje serwa ale one chyba nie wariują przy braku impulsów PPM.

(Trzy minuty później..)

Jest tak: niektóre serwa stoją bez ruchu (np. Esky8g), inne poruszają się po włączeniu zasilania kilka-kilkanaście stopni w przypadkową stronę zanim się opamiętają (małe 4.5grama) a jedno (GWS pico std) jedzie dosłownie kilka stopni ale zawsze w tę samą stronę. Twoje chyba są jeszcze innego rodzaju 🙂

Przy okazji: w trybie CTC timer zeruje się PO komparacji z odpowiednim OCR a to oznacza, że licznik będzie miał n+1 stanów. Jeśli chcesz mieć podział przez 156 to musisz wpisać 155. Niby nic ale.. no wiesz, sztuka cierpi 🙂

EDIT: A nie chciałbyś zrobić tego wszystkiego na jednym timerze kręcącym się w kółko (modulo 2^16) z wykorzystaniem funkcji Output Compare? W każdym przerwaniu mógłbyś zmieniać stan swojego automatu, zmieniać stan pinów, obliczać (na podstawie aktualnej wartości i żądanego odstępu czasu) i wpisywać odpowiednią wartość do OCR i spokojnie czekać na następne przerwanie. U mnie to działa bez pudła.

Link do komentarza
Share on other sites

Moim zdaniem błędem jest to, że nie pozwalasz timerowi 2 liczyć. Podzielnik=0x07 oznacza w nim taktowanie z wejścia T2. Ponieważ prawdopodobnie nie masz tam żadnego zegara, timer stoi a na wyjściach SERWPORT będzie ciągłe zero. Muszę sprawdzić moje serwa ale one chyba nie wariują przy braku impulsów PPM.

Hmm nie rozumiem czemu TIMER2 miałby nie liczyć. Taktowany jest z wewnętrznego zegara przez prescaler 1024.

Timer 2 pracuje poprawnie. Sprawdziełem to w ten sposób, że wyłączyłem TIMER1, a do obsługi przerwania TIMER2_COMP_vect wstawiłem taki program:

SERWPORT=0xFF;

_delay_us(1500);

SERWPORT=0x00;

i wszystko działało poprawnie.

Myślę, że problem leży po stronie TIMER1. Tylko nie wiem gdzie.

EDIT: A nie chciałbyś zrobić tego wszystkiego na jednym timerze kręcącym się w kółko (modulo 2^16) z wykorzystaniem funkcji Output Compare? W każdym przerwaniu mógłbyś zmieniać stan swojego automatu, zmieniać stan pinów, obliczać (na podstawie aktualnej wartości i żądanego odstępu czasu) i wpisywać odpowiednią wartość do OCR i spokojnie czekać na następne przerwanie. U mnie to działa bez pudła.

Hmm mógłbyś bardziej rozwinąć ideę wykorzystania tylko jednego licznika?? Zaciekawił mnie ten pomysł.

Link do komentarza
Share on other sites

Ups, akurat piszę kod na megę128 i tam przy CS20=CS21=CS22=1 timer 2 napędzany jest z pinu T2, przepraszam. W mniejszych prockach nie ma wejścia T2 i gradacja podzielników rozciąga się na 7 możliwości.

Idea jednego timera jest prosta. Puszczasz np. timer 1 swobodnie, z zegarem np. 1us - będzie się łatwiej liczyło a maksymalny czas to 65msec. Wykorzystujesz któryś OCR ale nie pozwalasz mu na sprzętową zmianę pinów - ma tylko zgłaszać przerwania. W odpowiednim ISR piszesz automat "wymyślający" wartość timera przy której ma być zgłoszone następne przerwanie.

Może zacznijmy od początku: w inicjalizacji ustawiasz jakiś licznik wyjść na pierwsze wyjście, stan automatu na - nazwijmy to "FSM_SET", wpisujesz do OCR cokolwiek (np. 1000), zerujesz i puszczasz timer. Od tej pory nigdy nie będziesz już modyfikował jego zawartości 'ręcznie". Za 1ms dostaniesz pierwsze przerwanie. Automat będzie w stanie "FSM_SET" więc ustawi wyjście o numerze pobranym z licznika. Na OCR musi zrobić proste dodawanie:

OCR += długość_impulsu_PPM[licznik];

Zmieniasz stan automatu na "FSM_RESET" i koniec.

Następne przerwanie przyjdzie wtedy, gdy powinieneś wyzerować wyjście. Robisz to bo automat jest w stanie FSM_RESET a OCR modyfikujesz tak:

OCR += (2000 - długość_impulsu_PPM[licznik]);

Dzięki temu będziesz miał początki impulsów PPM równo rozmieszczone co 2msec. Inkrementujesz licznik modulo liczba wyjść, zmieniasz stan automatu na FSM_SET i koniec.

Jeżeli dojdziesz do ostatniego wyjścia modyfikujesz OCR tak, by odczekać przerwę dopełniającą całość do 20ms.

Mam nadzieję, że jakoś tego bardzo nie pogmatwałem i coś z tego zrozumiesz.

W rzeczywistości w moich projektach używam zewnętrznego dekodera "zezwalanego" odpowiednim wyjściem OC i adresowanego licznikiem wyjść. Dzięki temu program w ogóle nie rusza pinami bo robi to za mnie komparator sprzężony z wyjściem OC a dokładność impulsów jest bezwzględna.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Może teraz pójdzie mi lepiej 🙂

Timer pracujący w trybie CTC nie zgłasza przerwania od komparacji z OCR1A (OCF1A) mimo, że de facto porównanie się dokonuje tylko ustawia flagę TOV1, bo się "przepełnia i zeruje". Musisz swoją funkcję "podpiąć" pod inny wektor przerwania. Chyba już kiedyś na to nadepnąłem..

Poza tym praca w trybie CTC spowoduje wielokrotne przerwania (co 1.5ms) i mimo, że ich nie obsługujesz bo są zablokowane, po odblokowaniu wejdą "od razu" i impuls będzie trwał kilka us. Puść timer 1 wolno w trybie normal albo go zatrzymaj albo kasuj flagę przed odblokowaniem przerwań.

Link do komentarza
Share on other sites

Timer pracujący w trybie CTC nie zgłasza przerwania od komparacji z OCR1A (OCF1A) mimo, że de facto porównanie się dokonuje tylko ustawia flagę TOV1, bo się "przepełnia i zeruje". Musisz swoją funkcję "podpiąć" pod inny wektor przerwania. Chyba już kiedyś na to nadepnąłem..

Poza tym praca w trybie CTC spowoduje wielokrotne przerwania (co 1.5ms) i mimo, że ich nie obsługujesz bo są zablokowane, po odblokowaniu wejdą "od razu" i impuls będzie trwał kilka us. Puść timer 1 wolno w trybie normal albo go zatrzymaj albo kasuj flagę przed odblokowaniem przerwań.

To może być przyczyną. Te przerwania od TIMER1 mogą się nawarstwiać i po odblokowaniu się wykonują wszystkie.

Jutro przetestuję to na urządzeniu i dam znać.

Link do komentarza
Share on other sites

"Nawarstwi" i obsłuży się tylko jedno - choć w czasie 20ms przyjdzie ich więcej - bo tylko 1-bitowe są flagi zgłoszeń przerwań. W każdym razie to wystarczy, żeby zepsuć impuls skracając go do kilku us.

  • Lubię! 1
Link do komentarza
Share on other sites

"Nawarstwi" i obsłuży się tylko jedno - choć w czasie 20ms przyjdzie ich więcej - bo tylko 1-bitowe są flagi zgłoszeń przerwań. W każdym razie to wystarczy, żeby zepsuć impuls skracając go do kilku us.

Tak. Miałeś racje. Impuls wypełnienia wynosił ok 4us.

Rozwiązałem problem poprzez wyłączanie TIMER1 gdy nie jest potrzebny.

Oto poprawiony kod, może się komuś przyda.

#include<avr/io.h>
#include<avr/interrupt.h>
#include<HD44780.c>
#include<util/delay.h>
#include<stdlib.h>

//------------ F_CPU 8MHz -----------//

#define SERWPORT 	PORTB
#define SERWDIR 	DDRB
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit));
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit));

int dana=0;
int Odczyt=0;

void PORT_Init()
{
SERWDIR = 0xFF;
SERWPORT = 0x00;
}

void TIMER1_Init()
{
TCCR1B |= (1<<WGM12);										//Tryb CTC, Preskaler przez 8	
OCR1A = 2200;						
}

void TIMER2_Init()
{
TCCR2 |= (1<<WGM21) | (1<<CS20) | (1<<CS21) | (1<<CS22); 	//Tryb CTC, preskaler przez 1024
OCR2 = 156; 												//Odmierzanie 20ms
TIMSK |= (1<<OCIE2);										//Zezwolenie na przerwanie CTC
}

ISR(TIMER1_COMPA_vect)
{
TCCR1B &= ~_BV(CS11);
SERWPORT = 0x00;
TIMSK &= ~_BV(OCIE1A);
}

ISR(TIMER2_COMP_vect)
{
TCNT1=0;
TCCR1B |= _BV(CS11);										//Uruchomienie TIMER1 z preskalerem 8
SERWPORT = 0xFF;
TIMSK |= _BV(OCIE1A);
}

void LCD()
{
char buf[5];
itoa(OCR1A,buf,10);
LCD_Clear();
LCD_WriteText(buf);
_delay_ms(5);
}

int main()
{
LCD_Initalize();
PORT_Init();
TIMER1_Init();
TIMER2_Init();
sei();
while(1)
{
	LCD();
}
return 0;
}

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.