Skocz do zawartości

[C] ATMEGA88 sterowanie 6 serwami


Pomocna odpowiedź

Napisano

Witam

Chciałem stworzyc sterownik PWM dla 6 serw na podstawie atmegi88. Kożystając z sprzetowych generatorow 4 z 6 serw maja rozdzielczość 24 kroków na ok.190 stopni czyli jakies 8 stopni. Postanowilem napisac sterownik oparty na 16bitowym timerze1 przy częstotliwości 8Mhz udało mi sie uzyskac rozdzielczość 15250 kroków na 190 stopni ale napotkałem problem. Dla testów włączyłem również sobie przetwornik analogowo cyfrowy w ktorym na potencjometrze okreslam kat wychylenia. Wartosc z potencjometru pobrana jest tylko zapierwszym razem potem juz "nie odswieza" i wsumie nie wiem w ktora strone isc.

docelowo ma byc zrealizowana jeszcze obsluga portu USART

kod programu.

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/iom88.h>
#define zero 546*8  			//czas przy ktorym serwo ustawia sie na 0 stopni
#define czasi 3000*8			//okres oblsugi jednego pinu (ns)
//przedzial nastawy 0 do 15250

/*
Steruje 6 serwami kazdym po kolei i przewiduje ze kazdym serwo bede obslugiwal przez 3ms
daje mi to 6 * 3 = 18ms, co jest okresem dla PWM co jest zblizone do katalogowego 20ms
*/

void delay_us(int ttw)
{
int i;
for(i = 0; i<ttw; i++)
_delay_us(1);
}

void delay_ms( int ttw)
{
int i;
for (i = 0 ; i<ttw ; i++)
_delay_ms(1);
};

int ID ;
int tryb;
int volatile nastawa[6];// = {0, 100, 7500, 10000, 12500, 15250};

/* 
Przyjelem ze uzyje do stowrzenia sygnalu PWM przerwania od porownania z wartoscia OCR1A
Sygnał pwm podzieliłem na 3 fazy
1. odczekanie czasu do do poczatkowego polozenia serwa (tryb 3)
2. odczekanie czasu nastawy (tryb  0)
3. odczekanie do konca okresu obslugi jednego pinu. (tryb 1)

inicjalizujac PWM funkcja startPWM konfiguruje timer1 wlaczam przerwania oraz
ustawiam czas do odmierzenia dla zerowego polozenia

funkcja stopPWM jedyne co wylacza to zezwolenie na globalne przerwania

wartości nastaw przechowuje w tablicy nastawa
ID jest to numer wysterowywanego serwa
tryb okresla w ktorej faze generacji sygnalu pwm jestesmy

*/

ISR(TIMER1_COMPA_vect){
switch(tryb){
case 0:
	OCR1A =150+nastawa[ID]; 
	tryb=1;
break;
case 1:
	PORTB&=~(1<<ID);
	OCR1A=czasi-150-zero-nastawa[ID];
	tryb=2;
break;
case 2:
ID++;
if (ID==6) ID = 0;
PORTB|=1<<ID;
tryb = 0;
OCR1A=zero;	
break;
default:
PORTB=0;	
break;
}
}

int startPWM(void){
//włączam licznik
TCCR1A=0b00000000; 	// CTC z ICR1A Piny rozlączone
TCCR1B=0b00001001; 	//prescaler 1
PORTB|=1;
tryb=0;
ID=0;
OCR1A=zero;
sei();
TIMSK1=0b00000010;
}
int stopPWM(void){
PORTB=0;
cli();
}

void InitADC(void){			//Funkcja konfiugurujaca uC
ADMUX=0b01100101;	   		//Napiecie odniesienia na AVcc(01) przypisanie do lewej(1) zarezerwowany(0) Wybor ADC5(0101)
ADCSRA= 0b10000011;    		//wlaczam ADC (1) niezaczynam konwersji(0) wylaczam Atrigergin(0) bitCzyskonczone(0) niewlaczam przerwan od skonczenia(0) ustawiam preskaler 8 (011)
ADCSRB=0b0;
DIDR0=0b0;
}
void StartADC(void){			//Funkcja zaczyna konwersje
ADCSRA|=1<<ADSC;
}
void WaitADC(void){				//Funckja czeka na skonczenie konwersji
while(ADCSRA&(1<<ADIF));
}

int volatile ii;
int main (void)
{
nastawa[0] = 150;
nastawa[1] = 0;
nastawa[2] = 0;
nastawa[3] = 0;
nastawa[4] = 0;
nastawa[5] = 0;
ii=0;
DDRB=0b00111111;
startPWM();
InitADC();
//nastawa[0]=15000;
while(1)
{
StartADC();
WaitADC();
ii = ADCH;
nastawa[0]=ii*60;
}

return 0;
}

Pozdrawiam

W StartADC() nie zerujesz każdorazowo flagi przerwania ADIF.

Nie udało mi się tego osiągnąć. Pomimo zmiany niestety nadal niedziałało. Jak tymczasowo zrezygnowałem z czekania na ukończenie konwersji zaskoczyło i działo. Ostatecznie wyrzuciłem przypisanie nastawy do przerwania od skonczenia kowersji i teraz działa.

Mam takie zapytanie jeszcze czy podczas używania makra _delay_ms peryferia również wstrzymują pracę?

Nie - _delay_ms bazuje na _delay_cycles, a ta funkcja to po prostu powtarzanie instrukcji nop n-razy. Peryferia zawsze pracują niezależnie, chyba że uśpimy mikrokontroler (do pewnego stopnia)

  • Pomogłeś! 1
Nie - _delay_ms bazuje na _delay_cycles, a ta funkcja to po prostu powtarzanie instrukcji nop n-razy. Peryferia zawsze pracują niezależnie, chyba że uśpimy mikrokontroler (do pewnego stopnia)

Chm... To ja czegos nie rozumiem. Na początku przy testowaniu tego programowego PWM chciałem zrobić zeby sie powoli sam obracał od 0 do 190 stopni własnie w oparciu o funkcje delay_ms() ktora powtarza _delay_ms() kod wyglądał mniejwiecej tak

nastawa[0] = i*38;
delay_ms(10);
i++;
if (i==400) i=0;

i w sumie nic się nie działo, pierwsze o czym pomyslalem to wlasnie to ze licznik tez jest wstrzymywany wiec nie jest wstanie doliczyc do zadanej wartosci.

Odpalałeś ten kod w symulatorze? zapewne przerwania globalne nie są włączone - przed InitADC() daj sei();.

globalne zezwolenie udzielam w funkcji startPWM(). Symulatora niestety jeszcze nie nauczyłem się używać. Wszystko testuję na płytce rozwojowej.

Ogólnie moge powiedzieć, że ten PWM mi działa tylko obawiam się że jak dołącze obsługę USART'u to zaczną się problemy.

Jeśli bym mógł jeszcze prosić o opinie samego PWM, czy zostawic go w takiej formie czy podejsc do tego z innej strony.

Pozdrawiam.

PWM nie gryzie się z UARTem - w przeciwieństwie do 8051, w którym taktowaniem UARTa zajmował się jeden z Timerów, w AVRach ma on własny sygnał zegarowy.

Ostatni powód jaki mi przychodzi do głowy, dlaczego nie działa ADC - optymalizacja. Niestety optymalizacja jest dobra tak długo jak długo nie korzysta się z przerwań, w takim wypadku potrafi wszystko popsuć.

  • Pomogłeś! 1

Z przetwornikiem ADC wydaje mi sie ze komenda WaitADC() mogła nie działać bo było własnie zezwolenie globalne na przerwania. Jak tylko sie udało ukonczyc konwersje zostało wykonane przerwanie i flaga ADIF została wyzerowana a nie miała o tym jak sie dowiedziec petla while() w WaitADC() ja to tak rozumiem, ale nie wiem ile w tym prawdy.

aktualnie program wygląda następująco

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/iom88.h>
#define zero 546*8  			//czas przy ktorym serwo ustawia sie na 0 stopni
#define czasi 3000*8			//okres oblsugi jednego pinu (ns)
//przedzial nastawy 0 do 15250

/*
Steruje 6 serwami kazdym po kolei i przewiduje ze kazdym serwo bede obslugiwal przez 3ms
daje mi to 6 * 3 = 18ms, co jest okresem dla PWM co jest zblizone do katalogowego 20ms
*/

void delay_us(int ttw)
{
int i;
for(i = 0; i<ttw; i++)
_delay_us(1);
}

void delay_ms( int ttw)
{
int i;
for (i = 0 ; i<ttw ; i++)
_delay_ms(1);
};

int ID ;
int tryb;
int volatile nastawa[6];// = {0, 100, 7500, 10000, 12500, 15250};

/* 
Przyjelem ze uzyje do stowrzenia sygnalu PWM przerwania od porownania z wartoscia OCR1A
Sygnał pwm podzieliłem na 3 fazy
1. odczekanie czasu do do poczatkowego polozenia serwa (tryb 3)
2. odczekanie czasu nastawy (tryb  0)
3. odczekanie do konca okresu obslugi jednego pinu. (tryb 1)

inicjalizujac PWM funkcja startPWM konfiguruje timer1 wlaczam przerwania oraz
ustawiam czas do odmierzenia dla zerowego polozenia

funkcja stopPWM jedyne co wylacza to zezwolenie na globalne przerwania

wartości nastaw przechowuje w tablicy nastawa
ID jest to numer wysterowywanego serwa
tryb okresla w ktorej faze generacji sygnalu pwm jestesmy

*/

ISR(TIMER1_COMPA_vect){
switch(tryb){
case 0:
	OCR1A =150+nastawa[ID]; 
	tryb=1;
break;
case 1:
	PORTB&=~(1<<ID);
	OCR1A=czasi-150-zero-nastawa[ID];
	tryb=2;
break;
case 2:
ID++;
if (ID==6) ID = 0;
PORTB|=1<<ID;
tryb = 0;
OCR1A=zero;	
break;
default:
PORTB=0;	
break;
}
}

ISR(ADC_vect){  //przerwanie od zakonczenia Konwersjio ADC.
int val;
val = ADCH;
nastawa[0]=val*60;
}

int startPWM(void){
//włączam licznik
TCCR1A=0b00000000; 	// CTC z ICR1A Piny rozlączone
TCCR1B=0b00001001; 	//prescaler 1
PORTB|=1;
tryb=0;
ID=0;
OCR1A=zero;
sei();
TIMSK1=0b00000010;
}
int stopPWM(void){
PORTB=0;
cli();
}

void InitADC(void){			//Funkcja konfiugurujaca uC
ADMUX=0b01100101;	   		//Napiecie odniesienia na AVcc(01) przypisanie do lewej(1) zarezerwowany(0) Wybor ADC5(0101)
ADCSRA= 0b10001011;    		//wlaczam ADC (1) niezaczynam konwersji(0) wylaczam Atrigergin(0) bitCzyskonczone(0) niewlaczam przerwan od skonczenia(0) ustawiam preskaler 8 (011)
ADCSRB=0b0;
DIDR0=0b0;
}
void StartADC(void){			//Funkcja zaczyna konwersje
ADCSRA|=1<<ADSC;
}
void WaitADC(void){				//Funckja czeka na skonczenie konwersji
while(ADCSRA&(1<<ADIF));
}

int volatile ii;
int main (void)
{
nastawa[0] = 150;
nastawa[1] = 0;
nastawa[2] = 0;
nastawa[3] = 0;
nastawa[4] = 0;
nastawa[5] = 0;
ii=0;
DDRB=0b00111111;
startPWM();
InitADC();
while(1)
{
StartADC();
}

return 0;
}

Jeśli ktoś widzi jakieś "głupoty" to prosił bym o zwrócenie uwagi.

Pozdrawiam

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...