Atmega328: Problem med PWM-period!
Postat: 18 maj 2017, 21:07:44
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 -----------------*
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 -----------------*