Hallo

Aber ein pwm signal im timer mit einer bestimmte breite für ein servo zu erstellen, bekomme ich nicht hin
Das wollte ich mal aufgreifen. 16-Bit Hardware-PWM für Servos zu verschwenden kann man sich meist nicht leisten, weil man das mit der Getriebemotoransteuerung belegt hat. Zufällig sind die OC1A- und OC1B-Pins an meinem cat16 nicht belegt, deshalb möchte ich mal zeigen, wie man das angehen könnte. (btw.: Es gibt 1001 Möglichkeiten ein Servo anzusteuern und man kann ungemein weit abschweifen, wenn man es erklären will.)

Mit etwas Grundwissen über Servosignale, dem Datenblatt und einem Taschenrechner bewaffnet gehen wir ans Werk: Da die Servoimpulse alle 20ms gesendet werden sollten (alle Angaben über die Impulswiederholung beziehen sich auf analoge Servos!), berechnen wir zuerst, wieviele Systemtakte in 20ms vergehen: 16MHz/20ms sind 16000000/0,02 oder 320000 Systemtakte. Also müssen wir bei einem 16MHz-Systemtakt immer nach 320000 Takten einen Impuls senden. Soviele Takte können wir aber mit einem 16-Bit-Timer nicht zählen, deshalb verwenden wir den Vorteiler (=prescaler) um die Systemtakte vorzuteilen und einen eigenen Takt für den Timer zu erzeugen. Das geübte Auge erkennt sofort, das 320000/8 genau 40000 Takte ergibt. Das bedeutet, mit einem Perscaler von /8 passt der 20ms-Bereich in ein 16-Bit-Register und dass nach 8 Systemtakten jeweils ein Timertakt abgearbeitet wird.

Günstig wäre, wenn der Timer von alleine, also ohne ISR-Aufruf, genau in einem 20ms-Zyklus laufen und nebenher noch die Impulse am OC1x-Pin erzeugen würde. Deshalb wählen wir Mode 14: "FastPWM mit ICR1 als Top". Was bedeutet das? In diesem Mode zählt der Timer immer von 0 bis zu dem Wert der im ICR1-Register steht. Wenn der Zähler den ICR1-Wert erreicht hat, startet er wieder bei 0. Wenn wir 40000 ins ICR1-Register schreiben, haben wir genau den gewünschten 20ms-Zyklus.

Für die Impulserzeugung müssen wir die Ausgabe der Impulse an den OC1x-Pins aktivieren. Wir wählen den "non-inverting Mode": Clear OC1A/OC1B on compare match, set OC1A/OC1B at BOTTOM.

Nun müssen wir noch den Wert für den Compare-Match im OCR1x-Register festlegen. Match ist, wenn das Zählregister des Timers den Wert im OCR1x-Register erreicht hat und der Ausgang von high nach low geschaltet wird. Dieser Wert ist die Impulslänge und sollte für eine Millisekunde 20ms/20 oder 40000/20=2000 Takte sein. (Bei meinen ES5-Servos ist die Mitte des Drehbereichs etwa OCR1x=3000 oder 1,5ms)

Das war's schon. Jetzt noch die Datenrichtungsregister der OC1X-Pins auf Ausgang schalten und fertig:

Code:
// Servoansteuerung mit 16Bit-Timer1 Hardware-PWM (2 Servos mit arexx cat16) mic 21.10.2011

#include <avr/io.h>
#include <avr/interrupt.h> // wegen cli() und sei()

// Definitionen für das cat16
# define OC1A 				PD5
# define OC1B 				PD4

# define servoa_DDR		DDRB
# define servoa_PORT 	PORTB
# define servoa_PIN 		PB0
# define servob_DDR		DDRB
# define servob_PORT 	PORTB
# define servob_PIN 		PB1

#define green	0x10
#define red		0x20
#define yellow 0x30

// Funktionen fürs cat16
void sleep(uint16_t pause);                   // ca. 1/1000 Sekunde blockierende Pause
void leds(uint8_t mask);

int main(void)
{
	cli();

	// InitSPI für Leds des cat16
	// Disable SPI, Master, set clock rate fck/128
   SPCR = ( (0<<SPE)|(1<<MSTR) | (1<<SPR1) |(1<<SPR0));

	DDRB = 0b10110011;                        // SCK: PD7, MoSi: PB5, SS: PB4 (!)?
	DDRC = 0b11111100;                        // und Servoausgänge setzen
	DDRD = 0b01000000;                        // PD6 ist STRB fürs 4094

	// Timer1 für 16MHz-ATMega16
  	TCCR1A = 0;
  	TCCR1B = (0<<CS12)|(1<<CS11)|(0<<CS10);	// prescaler /8

	TCCR1A |= (1<<WGM11)|(0<<WGM10);       	// Mode 14: FastPWM mit ICR1 als Top
	TCCR1B |= (1<<WGM13)|(1<<WGM12);

	TCCR1A |= (1<<COM1A1)|(0<<COM1A0);       	// OCR1A set on Botton, reset on Match
	TCCR1A |= (1<<COM1B1)|(0<<COM1B0);       	// OCR1B set on Botton, reset on Match

	TCNT1 = 0;                                // Timer1 zurücksetzen;
   ICR1 = 40000;                             // 16MHz/8/20mS für 20ms-Zyklus
	OCR1A = 3000;                             // Match und OC1A auf Low nach 1ms
	OCR1B = 3000;                             // Match und OC1B auf Low nach 1ms

	DDRD |= (1<<OC1A) | (1<<OC1B);         	// PWM-Pins sind Ausgang...
	PORTD &= ~((1<<OC1A) | (1<<OC1B));     	// ...und low
	
	// Portdefinitionen für das cat16
   servoa_DDR |= (1<<servoa_PIN);
	servoa_PORT |= (1<<servoa_PIN);
	servob_DDR |= (1<<servob_PIN);
	servob_PORT |= (1<<servob_PIN);

	sei();
	leds(green);
   sleep(1000);

	while(1)                                  // Demo
	{
      leds(red);
      OCR1A=OCR1B=2000;
      sleep(1000);

      leds(yellow);
      OCR1A=OCR1B=4000;
      sleep(1000);
	}
	return(0);
}

void sleep(uint16_t pause)
{
   uint32_t p = pause*5;
   uint8_t temp = 1;
   if(p) while(p--) while(temp++)
   {
      // OCRx-Pins kopieren auf normale Pins für cat16
      if(PIND & (1<<OC1A)) servoa_PORT |= (1<<servoa_PIN); else servoa_PORT &= ~(1<<servoa_PIN);
      if(PIND & (1<<OC1B)) servob_PORT |= (1<<servob_PIN); else servob_PORT &= ~(1<<servob_PIN);
   }
}
void leds(uint8_t mask)
{
	SPCR = ( (1<<SPE)|(1<<MSTR) | (1<<SPR1) |(1<<SPR0));
	SPDR = mask;
	while(!(SPSR & (1<<SPIF))); // Wait for transmission complete!
	SPCR = ( (0<<SPE)|(1<<MSTR) | (1<<SPR1) |(1<<SPR0));
	PORTD |= (1<<6); 	// STRB high
	PORTD &= ~(1<<6);	// STRB low
}
Da meine Servos nicht an den OCR1x-Pins angeschlossen sind, kopiere ich in der sleep()-Funktion die Pegel der OC1x-pins auf frei wählbare Pins. Deshalb verwende ich diese verschachtelten Zählschleifen in sleep(). Wenn die Servos wirklich an den OC1x-Pins angeschlossen sind, kann man die Pausen auch mit delay.h umsetzen. Die Impulserzeugung läuft dann trotzdem im Hintergund ohne dem Kontroller Resourcen zu nehmen.

Und so sieht das dann aus:



Gruß

mic