Sida 1 av 1

Atmega328: Problem med PWM-period!

Postat: 18 maj 2017, 21:07:44
av Castello
Hej!

Jag ska försöka kontrollera ett servo med en atmega328. Det finns ganska mycket information om detta online, men jag har råkat ut får ett problem som jag inte har hittat lösningen på.
Min systemklocka ligger på 16MHz och jag använder "fast PWM"-tekniken. För att komma i närheten av en PWM-period på 50Hz behöver jag använda en prescaler på 1024. Då fås

f_pwm = f_osc/(1024*256) = 61Hz

Nu till problemet: Jag KAN ställa in prescalern till 1, 8, 64 och 256 (verifierat med oscilloskop), men INTE 1024. Om jag sätter rätt bitar för prescaler = 1024 för jag istället PWM-perioden för prescaler =256, dvs 244 Hz. Oberoende av vad jag hade för prescaler innan. Det är som om det var ett "golv" i PWM-frekvensen. Mycket märkligt.
En lösning för att komma ner i periodtid skulle ju vara att sänka systemklockans frekvens, men det känns som att det borde finnas ett bättre sätt. Som att använda prescalern, till exempel.

Är det någon som har varit med om det här innan? Eller som kan hjälpa mig att hitta felet? Svar mottages tacksamt!

*---------------- KOD -------------------*

// Servo test - read level from ADC and control servo using PWM

#define F_CPU 16000000 // External crystal

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

int dutyCycle = 0;

void setupADC();
void startConversion();

int main(void)
{
DDRD = (1 << PORTD6); // Set port D6 as output

TCCR0A = (1 << COM0A1) | (1 << WGM00) | (1 << WGM01); //Timer/Counter Control A (for timer 0)
TIMSK0 = (1 << TOIE0); // Timer/Counter Interrupt Mask Register: Enable overflow interupt enable

sei();

TCCR0B = 0;
TCCR0B |= (1 << CS00); // Start PWM timer
//TCCR0B |= (1 << CS01);
TCCR0B = (1 << CS02);

setupADC();
startConversion();

while(1)
{

}
}

void setupADC()
{
ADMUX = (1 << REFS0) | (1 << MUX0) | (1 << MUX2) | (1 << ADLAR); // Selecting voltage reference and ADC channel
ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2); //Enabling the ADC and selecting prescaler for conversion rate
DIDR0 = (1 << ADC5D); //Disable digital input buffer
}

void startConversion()
{
ADCSRA |= (1 << ADSC);
}

ISR(TIMER0_OVF_vect)
{
OCR0A = 40 + dutyCycle*0.45; // This works!
}

ISR(ADC_vect)
{
dutyCycle = ADCH;
startConversion();
}


*-------------- KOD SLUT -----------------*

Re: Atmega328: Problem med PWM-period!

Postat: 18 maj 2017, 21:26:15
av sodjan
Det blir snyggare om du använder code taggarna... :-)

Kod: Markera allt

[code]
code...
code...
[/code]-
Sen så... Det handlar alltså om vanliga RC-servon med 1.5 ms +/- 0.5 ms pulser?
Det är ganska vanligt att man sätter någon timer och sköter servohanteringen
i en interrupt rutin. De vanliga PWM modulerna är normalt byggda för högre frekvenser,
vilket du ju också har upptäckt. :-)

Re: Atmega328: Problem med PWM-period!

Postat: 18 maj 2017, 21:59:13
av Klas-Kenny

Kod: Markera allt

TCCR0B = 0;
TCCR0B |= (1 << CS00); // Start PWM timer
//TCCR0B |= (1 << CS01);
TCCR0B = (1 << CS02);
I sista raden sätter du registret till enbart 1<<CS02 (prescaler 256). Det skulle nog varit |= där.

Re: Atmega328: Problem med PWM-period!

Postat: 18 maj 2017, 23:43:12
av jesse
eller
TCCR0B = 0x05;

Re: Atmega328: Problem med PWM-period!

Postat: 21 maj 2017, 14:48:57
av Castello
sodjan: Ja, det stämmer, vanliga RC-servon. Jag försökte använda kod-taggarna men det misslyckades ju helt uppenbart. Ska försöka göra bot och bättring till nästa gång.
Kan du utveckla hur du menar att man ska göra det i en separat isr? Menar du att man uppdaterar duty cycle på sina pwm-utgångar i en separat isr säg, två gånger per sekund? Eller skapar man en "egen PWM" genom att använda "digital out" och sätta pinens state (hög eller låg) med hjälp av timerns isr?

Klas-Kenny, jesse: Ja! Det var ju helt uppenbart nu när man såg det! :doh: Båda sätten funkar. Tack!

Re: Atmega328: Problem med PWM-period!

Postat: 21 maj 2017, 15:19:09
av sodjan
> Eller skapar man en "egen PWM" genom att använda "digital out" och sätta pinens state (hög eller låg) med hjälp av timerns isr?

Ja, lite så. Antingen genom en timer tid som motsvarar "upplösning" man vill ha på servot.
Fungerar om man inte behöver finjustera servot allt för mycket. Om det t.ex. räcker med
20 positioner, så fungerar det med en tidbas på 1 ms / 20 = 50 us. Visst, det blir ganska
täta interrupt och man får ha effektiv kod, men det går.

Sen så kan man spela med timer tiden direkt och ställa hela tiden för pulsen där.

Slutligen så tror jag att en del moderna AVR eller PIC processorer även har utvecklade
PWM moduler som fixar lite lägre frekvenser som passar servon bättre. Men de typiska
PWM modulerna i AVR/PIC fungerar inte jättebra för RC-servon, som du har upptäckt.

Re: Atmega328: Problem med PWM-period!

Postat: 21 maj 2017, 15:31:29
av Gimbal
Jasså, vill minnas att det fungerade alldeles utmärkt med de inbyggda timerarna i AVR. Kan dock inte svära på att jag kört servon och 20MHz på en gång. Offrade man 16bitars timern fick man väldigt fin upplösning.

Re: Atmega328: Problem med PWM-period!

Postat: 21 maj 2017, 16:39:30
av sodjan
Jag säger inte att det inte går alls, men det krävde lite eftertanke.
PWM modulerna är primärt tänkta för lite högre frekvens på signalen.

Re: Atmega328: Problem med PWM-period!

Postat: 21 maj 2017, 21:48:41
av Castello
Ah, ok! Ja, det kan vara värt att testa. Får hitta på något fiffigt projekt att använda servon i nu bara så att jag får användning för mina nyvunna kunskaper :)

Re: Atmega328: Problem med PWM-period!

Postat: 21 maj 2017, 22:21:18
av sodjan
Och som sagt, en hel del beror också på vilken upplösning
men behöver. Det finns fall där servot bara ska styra något
"öppet" eller "stängt" och då behövs ju bara två olika pulser,
1.0 eller 2.0 ms med 20 ms intervall. Om man behöver flera
hundra steg så behövs det en lite annan lösning...