| Author |
Message
|
Majtek Poziom 12

Joined: 28 Jan 2006 Posts: 93 Location: kalisz
|
#1
20 Apr 2011 20:21 Atmega16 generowanie przebiegów pwm na 16 serw |
|
|
|
Witam.
Tworzę sobie programik do sterowania 16 serwami. wykorzystuję 2 timery
timer2 generuje przez przerwanie odstępy 2.2ms. każdy impuls to obsługa 2 serwów, więc aby odsłużyć wszystkie mamy 8 impulsów(18.4ms) więc impulsy na serwo powtarzają się częściej niż te 20ms. więc tu jest dobrze.
Timer 2 steruje timerem1, blok A obsługuje pierwszą 8 serw, a blok B drugą 8 serw.
Mam taki kod i on dział dobrze.
void SerwaInit() { i=0; //ustawienie portów gdzie są podłączone serwa jako wyjscia DDRSERWOA=0xFF; #ifdef SERWA16 DDRSERWOA=0xFF; #endif //inicjacja poszczególnych serw int i; for( i=0;i<8;i++) { serwa[i].enable=0; serwa[i].nrPort=i; serwa[i].skok=2; serwa[i].pos=(1.0f/667.0f)/(64.0f/(float)F_CPU); //1.5ms 667hz serwa[i].max=(1.0f/455.0f)/(64.0f/(float)F_CPU); //2.2ms 455hz serwa[i].min=(1.0f/1250.0f)/(64.0f/(float)F_CPU); //0.8ms 1250hz #ifdef SERWA16 serwa[i+8].enable=0; serwa[i+8].nrPort=i; serwa[i+8].skok=2; serwa[i+8].pos=(1.0f/667.0f)/(64.0f/(float)F_CPU); //1.5ms 667hz serwa[i+8].max=(1.0f/455.0f)/(64.0f/(float)F_CPU); //2.2ms 455hz serwa[i+8].min=(1.0f/1250.0f)/(64.0f/(float)F_CPU); //0.8ms 1250hz #endif } //timer1 //Tryb CTC timera TCCR1B|=(1<<WGM12) | (1<<CS10)|(1 << CS11);//preskaler 64 i tryb CTC OCR1A=(1.0f/667.0f)/(64.0f/(float)F_CPU); //375-1.5ms 200-0.8ms 549-2.2ms dla zegara 16mhz //ustaw 1.5ms(neutrum serwa) 667hz #ifdef SERWA16 OCR1B=(1.0f/667.0f)/(64.0f/(float)F_CPU); //375-1.5ms 200-0.8ms 549-2.2ms dla zegara 16mhz //ustaw 1.5ms(neutrum serwa) 667hz #endif OCR2=(1.0f/417.0f)/(256.0f/(float)F_CPU);// coś około 2.6ms 385hz // tryb OCT | preskaler 256 TCCR2=_BV(WGM21) | _BV(CS22) | _BV(CS21); TIMSK|=(1<<OCIE2);//włączenie przerwania compare } //przerwanie timera2 compare // po koleji wszystkie serwa przechodzi, każde przerwanie to kolejna para serw // przetwarza 2 naraz 0 i 8 ,1 i 9, 2 i 10 itd. ISR(TIMER2_COMP_vect) { PORTSERWOA^=(1<<5); ////obsługa serw 2 naraz z timera1A i timera1B //pierwsza 8 serw na timer1A if(serwa[i].enable==1)//jak Serwo włączone to generuj mu impuls { //pierwsza 8 serw TCNT1=0;//licznik timera1, zerujemy OCR1A=serwa[i].pos; TIFR|=(0<<OCF1A);//wyzerowanie flagi CTC PORTSERWOA|=(1<<serwa[i].nrPort);//ustaw 1 while(bit_is_clear(TIFR,OCF1A)) {//i tu jest pies pogrzebany } PORTSERWOA&=~(1<<serwa[i].nrPort);//ustaw 0 } //druga 8 serw na timer1B do poprawy tak jak 1 8 ma wygladac //wyrzucone if(++i>7) i=0;//nastepne serwa, jak koniec to do poczatku }
o co chodzi? a no o ten kawałek
| Code: |
while(bit_is_clear(TIFR,OCF1A))
{//i tu jest pies pogrzebany
}
|
on zabiera cały czas procesora, było by ok gdyby było tylko generowanie przebiegów dla serw, ale ten procek jeszcze coś innego musi robić
więc wymyśliłem taki kod
//inicjacja taka sama //przerwanie timera2 compare // po koleji wszystkie serwa przechodzi, każde przerwanie to kolejna para serw // przetwarza 2 naraz 0 i 8 ,1 i 9, 2 i 10 itd. ISR(TIMER2_COMP_vect) { PORTSERWOA^=(1<<5); ////obsługa serw 2 naraz z timera1A i timera1B //pierwsza 8 serw na timer1A if(serwa[i].enable==1)//jak Serwo włączone to generuj mu impuls { //pierwsza 8 serw TCNT1=0;//licznik timera1, zerujemy OCR1A=serwa[i].pos; //to jest inne TIMSK |= (1 << OCIE1A); // Zezwolenie na przerwania dla CTC PORTSERWOA&=~(1<<serwa[i].nrPort);//ustaw 1 } //druga 8 serw na timer1B do poprawy tak jak 1 8 ma wygladac ///// if(++i>7) i=0;//nastepne serwa, jak koniec to do poczatku } //timer1a ISR(TIMER1_COMPA_vect) { PORTSERWOA|=(1<<serwa[i].nrPort);//ustaw 1 //TIMSK &= ~(1 << OCIE1A);/ } //timer1b //////
i co jest, niby generuje przebiegi ale ma o innym wypełnieniu do tego niektóre się zmieniają jak inna ilość serw ma generowany sygnał.
Wie ktoś jak rozwiązać ten problem, bo mnie zaczyna przerastać.
Chcę po prostu pozbyć się tej pętli i robić to w przerwaniu.
Wtedy zostaje w głównej pętli więcej czasu aby coś tam zrobić zależności czasowe dla 1 kodu
zależności czasowe dla 2 kodu żółty jakoś 200us
|
|
| Back to top |
|
 |
Szumlus Poziom 11

Joined: 23 Jan 2006 Posts: 69 Location: Tarnów
|
#2
20 Apr 2011 22:58 Re: atmega16 generowanie przebiegów pwm na 16 serw |
|
|
|
Witam,
Pomysł dobry ale sypie się najprawdopodobniej z powodu zbyt dużej ilości przerwań.
Proponuje projekt sterownika "równoległego", taki wykorzystałem w mojej pracy inżynierskiej. Wprawdzie korzystałem tylko z 8 serw ale 16 też obsłuży po drobnej modyfikacji.
Program zabiera większość mocy procesora do obsługi przerwań ale tylko przez czas 2ms potem procek jest całkowicie "wolny". Może realizować inne zadania.
Kod wymaga niewielkich modyfikacji ale myślę że dasz radę.
[code] /* ************************************************************************ Nazwa pliku - servos.h Kompilator - AVR Studio 4.17 + WinAVR 2010.01.10 Autor - Marcin Szumlański Data - 10-03-2010 Procesor - ATmega16/32 Opis : ************************************************************************ */ #ifndef _SERVOS_H_ #define _SERVOS_H_ #define cbi(sfr,b) (sfr &=~(1<<b)) #define sbi(sfr,b) (sfr |=(1<<b)) //port Servo #define ServoPort PORTA #define ServoDir DDRA //kanały Servo #define ServoChannel0 0 #define ServoChannel1 1 #define ServoChannel2 2 #define ServoChannel3 3 #define ServoChannel4 4 #define ServoChannel5 5 #define ServoChannel6 6 #define ServoChannel7 7 //min liczba przerwań T0 do odliczenia aby długośc imp nie trwała mniej niż 0,76ms #define ServoInterruptMin 76 //max liczba przerwań To do odliczenia aby długośc imp nie trwała dłużej niż 2,06ms #define ServoInterruptMax 130+ServoInterruptMin //pozycje domyślne (inicjalizacyjne) serv w stopniach #define ServoDefault0 0+ServoInterruptMin #define ServoDefault1 0+ServoInterruptMin #define ServoDefault2 0+ServoInterruptMin #define ServoDefault3 0+ServoInterruptMin #define ServoDefault4 0+ServoInterruptMin #define ServoDefault5 0+ServoInterruptMin #define ServoDefault6 0+ServoInterruptMin #define ServoDefault7 0+ServoInterruptMin //inicjalizacja sterownika Servo (konfig T0,T1 i I/O) void ServosInit(void); //ustawia/pobiera pozycję serwa w określonym kanale format wyskalowany w stopniach (0-130) void ServoSetPosition(unsigned char Channel, unsigned char Position); unsigned char ServoGetPosition(unsigned char Channel); #endif
#include <avr/io.h> #include <avr/interrupt.h> #include "servos.h" volatile unsigned char ServoInterruptCounter = 0; volatile unsigned char ServoPerioid[8]; void ServosInit(void) { ServoDir = 0xFF; //Port PWM=Output ServoPort = 0x00; //Zerowanie wyść //Timer1 OCR1A = 0x04E2; //1250 //Odmaskowanie przerwania CTC sbi(TIMSK,OCIE1A); //Tryb CTC,presk.=256(przerwanie co 20ms dla F_CPU 16Mhz) TCCR1B = _BV(WGM12)|_BV(CS12); //Timer0 OCR0 = 20; //Tryb CTC,presk.=8(przerwanie co 0,01ms dla F_CPU 16Mhz) sbi(TCCR0,WGM01); //Odmaskowanie przerwania CTC sbi(TIMSK,OCIE0); } //ustawia pozycję serwa w określonym kanale format wyskalowany void ServoSetPosition(unsigned char Channel, unsigned char Position) { ServoPerioid[Channel] = Position+ServoInterruptMin; } //pobiera pozycję serwa w określonym kanale format wyskalowany w stopniach unsigned char ServoGetPosition(unsigned char Channel) { return(ServoPerioid[Channel]-ServoInterruptMin); } //OBSŁUGA PRZERWAŃ ISR(TIMER1_COMPA_vect) { //wyjścia PWM w stan wysoki ServoPort = 0xFF; //zerowanie T0 TCNT0 = 0x00; //start T0 sbi(TCCR0,CS01); } ISR(TIMER0_COMP_vect) { //jeśli liczba przerwań odpowiada wypełnieniu kanału zeruj odpowiedni pin I/O if(ServoInterruptCounter == ServoPerioid[ServoChannel0]) { cbi(ServoPort,ServoChannel0); } if(ServoInterruptCounter == ServoPerioid[ServoChannel1]) { cbi(ServoPort,ServoChannel1); } if(ServoInterruptCounter == ServoPerioid[ServoChannel2]) { cbi(ServoPort,ServoChannel2); } if(ServoInterruptCounter == ServoPerioid[ServoChannel3]) { cbi(ServoPort,ServoChannel3); } if(ServoInterruptCounter == ServoPerioid[ServoChannel4]) { cbi(ServoPort,ServoChannel4); } if(ServoInterruptCounter == ServoPerioid[ServoChannel5]) { cbi(ServoPort,ServoChannel5); } if(ServoInterruptCounter == ServoPerioid[ServoChannel6]) { cbi(ServoPort,ServoChannel6); } if(ServoInterruptCounter == ServoPerioid[ServoChannel7]) { cbi(ServoPort,ServoChannel7); } //inkrementujemy licznik przerwań T0 ServoInterruptCounter++; //jeśli liczba przerwan > max wyłącz T0 i zeruj licznik if(ServoInterruptCounter > ServoInterruptMax) { cbi(TCCR0,CS01); ServoInterruptCounter = 0; } }
Wyznacznikiem maksymalnej liczby serw jest czas wykonywania przerwania Timer0, musi się zakończyć zanim T0 zdąży się przepełnić i wywołać kolejne przerwanie. Oryginalnie to część programu sterująca modelem ramienia, przystosowana do czasów serw Tower Pro 955. Jak kolega ma inne możliwe że trzeba będzie troszkę pozmieniać ustawienia programu. Jeśli program nie zdążył by się wyrobić można zoptymalizować procedurę obsługi przerwania w asemblerze.
:D. U mnie procesor oprócz obsługi serw komunikował się z PC przez RS232 na 19200 bod. protokół miał 12 bajtową ramkę wysyłaną z częstotliwością 50Hz i nie było żadnych problemów.
Co do kodu kolegi....jak coś ma być szybko zrobione unikamy jakichkolwiek pętli iteracyjnych!!! Wpisujemy wszystko na "sztywno" tzn żadnych pętli po tablicy tylko wypisujemy w słupku Tablica[0]=...Tablica[1]=....więcej flash zajmie ale prędkość wykonania błyskawiczna. Polecam podglądać kod dissasemblera można dużo się dowiedzieć i obliczyć czas wykonywania danej funkcji a w takich "napiętych" aplikacjach to niezastąpione.
W razie czego śmiało pytać.
Pozdrawiam,
Marcin
|
|
| Back to top |
|
 |
Google

|
#
20 Apr 2011 22:58 |
|
|
|
|
|
| Back to top |
|
 |
Majtek Poziom 12

Joined: 28 Jan 2006 Posts: 93 Location: kalisz
|
#3
21 Apr 2011 06:39 Re: atmega16 generowanie przebiegów pwm na 16 serw |
|
|
|
fajny patent :)
wypróbuje go wieczorkiem. Na taki sposób sterowanie serwami nie wpadłem, do tego bardzo dużą ilość serw może obsłużyć, kosztem ilości kroków serwa.
|
|
| Back to top |
|
 |
Google

|
#
21 Apr 2011 06:39 |
|
|
|
|
|
| Back to top |
|
 |
Szumlus Poziom 11

Joined: 23 Jan 2006 Posts: 69 Location: Tarnów
|
#4
23 Apr 2011 21:37 Re: atmega16 generowanie przebiegów pwm na 16 serw |
|
|
|
Wersja programu którą wrzuciłem obsługiwała rozdzielczość do 1 stopnia.
Klasyczne serwa ze średniej półki cenowej i tak większej dokładności nie osiągną więc nie ma co przesadzać z precyzją w sofcie.
|
|
| Back to top |
|
 |
Google

|
#
23 Apr 2011 21:37 |
|
|
|
|
|
| Back to top |
|
 |
Majtek Poziom 12

Joined: 28 Jan 2006 Posts: 93 Location: kalisz
|
#5
03 May 2011 08:56 Re: Atmega16 generowanie przebiegów pwm na 16 serw |
|
|
|
Mam pytanko. Jaki jest minimalny czas odmierzany przez timer 8-bitowy?
bo mi minimum na zegarze 8mhz wychodzi 0.016ms, nawet jak preskaler dam 1 i OCR0 =1. A 16 bitowym mogę mniejsze odmierzyć. według mojej wiedzy powinny minimalny czas mieć taki sam (1/czestotliwość zegara głównego)
edit:
No i jest problem atmega z zegarem 16mhz jest za wolna. Gdy generuje przebiegi dla 5 serw, to procek jest tak zajęty, że reszty nie wyrabia obsłużyć, głównie to wysyłanie danych po rs do drugiego procka i odbieranie i wysyłanie danych poprzez nadajnik rfm12 (magistrala SPI). Zostaje chyba tylko następny procek do obsługi tylko serw i komunikaty po rs dostaje.
Albo przesiąść się na lepsze procki ARM (STM32 np.):)
|
|
| Back to top |
|
 |
Szumlus Poziom 11

Joined: 23 Jan 2006 Posts: 69 Location: Tarnów
|
#6
10 Jun 2011 11:48 Re: Atmega16 generowanie przebiegów pwm na 16 serw |
|
|
|
A te pozostałe peryferia śmigają na przerwaniach??
Minimalny czas przerwania powinien być tak dobrany aby zanim przepełni się Timer zdążyło się wykonać jego przerwanie.
U mnie tylko działał UART podczas pracy z serwami więc żadnych problemów nie było.
|
|
| Back to top |
|
 |
albertb Poziom 22

Joined: 04 May 2004 Posts: 2074 Location: Nowy Targ
|
#7
10 Jun 2011 14:27 Re: Atmega16 generowanie przebiegów pwm na 16 serw |
|
|
|
Temat często powraca, a rozwiązania ... takie sobie.
Skoro czas wykonania przerwania jest wyznacznikiem ilości obsługiwanych serw to go skróćmy.
We wszystkich opisanych rozwiązaniach w przerwaniu wykonuje się lwia część logiki, a występujące rzadko przestawienia wartości to funkcje zawierające jedną instrukcję. Bez sensu.
Proponuję zmienić format danych o czasach włączenia serw na tablicę której indeksami są kolejne czasy, a wartościami liczby całkowite, w których każdy bit określa, które serwo należy wyłączyć:
Zalety: przerwanie to jedna inkrementacja indeksu i wyzerowanie odpowiednich wyjść bez jakichkolwiek sprawdzań - czas pomijalnie mały nawet dla kilkudziesięciu serw.
Wady:
- Bardziej skomplikowane funkcje do zmiany położeń (ale tylko troszeczkę, każdy powinien sobie poradzić)
- przy dużej rozdzielczości wzrost zapotrzebowania na RAM (ale to także można wyeliminować tworząc odpowiednią strukturę danych)
Albert
|
|
| Back to top |
|
 |
dj_max84 Poziom 13

Joined: 24 Feb 2004 Posts: 127 Location: Farnham
|
#8
14 Jun 2011 10:47 Re: Atmega16 generowanie przebiegów pwm na 16 serw |
|
|
|
na stronie AMTELa jest opis dla 8 kanalowego software'owego PWM'a z mozliwoscia rozszerzenia do 24 kanalow. wartosci PWM dla kazdego kanalu sa przesylane sa po RS232. przyklad 8 kanalowy jest oparty na ATTINY2313. kiedys tez widzialem gdzies kod w C ale nie moge go teraz znalezc..
http://www.atmel.com/dyn/resources/prod_documents/doc8020.pdf
|
|
| Back to top |
|
 |
Google

|
#
14 Jun 2011 10:47 |
|
|
|
|
|
| Back to top |
|
 |
janbernat Poziom 23

Joined: 22 Oct 2008 Posts: 3918 Location: Warszawa
|
|
| Back to top |
|
 |