Hallo
Nun kann ich schon mal ein Zwischenergebniss abliefern. Zufällig habe ich die Steuerplatine des Caterpillar (Danke an den Sponsor), ein schnuckeliges Platinchen mit einem 16MHz-Mega16. Darauf läuft nun dieses Programm das theoretisch 18 Servos ansteuern kann. Theoretisch deshalb, weil ich grad keine 18 Servos rumliegen habe und vermutlich das Platinchen abrauchen würde, wenn ich das ohne seperate Spannungsversorgung für die Servos versuchen würde.
Wie schon in der ersten Vorversion läuft der Timer1 mit Prescaler /64, die Auflösung ist deshalb ca. 0,000004 Sekunde (1/(16MHz/64)) oder 4µs. Eine Millisekunde dauert deshalb 250 Zähltakte, das wären etwa 1600 Kontrollertakte zwischen den ISR-Aufrufen. In meinem Testprogramm zeige ich nebenher die Registerinhalte an und schiebe die Bits für die Statusled per Hardware-SPI raus ohne die Ansteuerung merklich zu stören.
Die errechneten Werte scheinen auch zu passen: Ein 20ms-Zyklus dauert 5000 Zählschritte des Timers (0,02s/0,000004s), der Drehbereich meines Testservos beträgt ca. von 90 bis 450 (muss ich noch genauer erforschen). Das wären bei neun Servos pro ISR ca. 4050 Zähltakte oder knapp 1000 Zähltakte für die Impulspause. Da wäre also sogar noch Platz für weitere Servos oder eine schnellere Wiederholfrequenz:
Code:
// 18 Servos ansteuern mit 16MHz-Mega16 (cat16) und 16-Bit Timer1 24.8.2011 mic
// https://www.roboternetz.de/community/threads/54583-Code-Optimierung-f%C3%BCr-Interrupt-m%C3%B6glich
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h> // für itoa() in writeInteger()
// Servoausgänge A 1-9
// S1 bis S8 und Speaker als Dummy (PB3)
#define servoainit {DDRB |= 0b1011; PORTB &= ~0b1011; DDRC |= 0b11111100; PORTC &= ~0b11111100;}
#define servoa1on PORTB |= (1<<PB0) // S1
#define servoa1off PORTB &= ~(1<<PB0)
#define servoa2on PORTB |= (1<<PB1) // S2
#define servoa2off PORTB &= ~(1<<PB1)
#define servoa3on PORTC |= (1<<PC2) // S3
#define servoa3off PORTC &= ~(1<<PC2)
#define servoa4on PORTC |= (1<<PC3) // S4
#define servoa4off PORTC &= ~(1<<PC3)
#define servoa5on PORTC |= (1<<PC4) // S5
#define servoa5off PORTC &= ~(1<<PC4)
#define servoa6on PORTC |= (1<<PC5) // S6
#define servoa6off PORTC &= ~(1<<PC5)
#define servoa7on PORTC |= (1<<PC6) // S7
#define servoa7off PORTC &= ~(1<<PC6)
#define servoa8on PORTC |= (1<<PC7) // S8
#define servoa8off PORTC &= ~(1<<PC7)
#define servoa9on PORTB |= (1<<PB3) // Dummy (Speaker)
#define servoa9off PORTB &= ~(1<<PB3)
// Servoausgänge B 1-9
// J1 PD2/RC5, J2 PA0/Roll, J5 PA6, J6 PA7, J7 PA7-PD7/OCR2, J8 PA4/HeadL, J9 PA2/Tail, J10 PA1/Rang
#define servobinit {DDRA |= 0b11011111; PORTA &= ~0b11011111; DDRD |= 0b10000100; PORTD &= ~0b10000100;}
#define servob1on PORTD |= (1<<PD2) // J1 PD2/RC5
#define servob1off PORTD &= ~(1<<PD2)
#define servob2on PORTA |= (1<<PA0) // J2 PA0/Roll
#define servob2off PORTA &= ~(1<<PA0)
#define servob3on PORTA |= (1<<PA6) // J5 PA6/ADC6
#define servob3off PORTA &= ~(1<<PA6)
#define servob4on PORTA |= (1<<PA7) // J6 PA7/ADC7
#define servob4off PORTA &= ~(1<<PA7)
#define servob5on PORTD |= (1<<PD7) // J7 PD7/OCR2 (Pin4, PA7 ist auf Pin3!)
#define servob5off PORTD &= ~(1<<PD7)
#define servob6on PORTA |= (1<<PA4) // J8 PA4/HeadL
#define servob6off PORTA &= ~(1<<PA4)
#define servob7on PORTA |= (1<<PA2) // J9 PA2/Tail
#define servob7off PORTA &= ~(1<<PA2)
#define servob8on PORTA |= (1<<PA1) // J10 PA1/Rang
#define servob8off PORTA &= ~(1<<PA1)
#define servob9on PORTA |= (1<<PA3) // J8 PA3 auf Pin4
#define servob9off PORTA &= ~(1<<PA3)
#define green 0x10
#define red 0x20
#define yellow 0x30
volatile uint8_t p; // 20ms-Timer
volatile uint16_t servo_pos_a[10]={5000, 250, 250, 250, 250, 250, 250, 250, 250, 250}; // Pos 0 ist Impulspause
volatile uint16_t servo_pos_b[10]={0, 250, 250, 250, 250, 250, 250, 250, 250, 250};
/************************* Ausgabe an Terminal ********************************/
void writeChar(char ch) {while (!(UCSRA & (1<<UDRE))); UDR = (uint8_t)ch;}
void writeString(char *string) {while(*string) writeChar(*string++);}
void writeInteger(int16_t number, uint8_t base)
{char buffer[17]; itoa(number, &buffer[0], base); writeString(&buffer[0]);}
/******************************************************************************/
void sleep(uint8_t pause); // 1/50 Sekunde blockierende Pause
void leds(uint8_t mask);
int main(void)
{
cli();
/************************ UART-Setup für cat16 *******************************/
#define BAUD_LOW 38400 //Low speed - 38.4 kBaud
#define UBRR_BAUD_LOW ((F_CPU/(16*BAUD_LOW))-1)
UBRRH = UBRR_BAUD_LOW >> 8; // Baudrate is Low Speed
UBRRL = (uint8_t) UBRR_BAUD_LOW;
UCSRA = 0x00;
UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);
UCSRB = (1 << TXEN) | (1 << RXEN) | (1 << RXCIE);
/***************************************************************************/
//Init SPI für leds()
SPCR = ( (0<<SPE)|(1<<MSTR) | (1<<SPR1) |(1<<SPR0)); // Disable SPI, Master, set clock rate fck/128
DDRB = 0b10110000; // SCK: PB7, MoSi: PB5, SS: PB4 (!)?
DDRD = 0b01000000; // PD6 ist STRB fürs 4094
//Timer1 Initialisierung
TCCR1A = 0;
TCCR1B = (0<<CS12) | (1<<CS11) | (1<<CS10); // Prescaler /64
//TCCR1B|= (1<<WGM12); // CTC-Mode 23.8.11 NormalMode!
TCNT1=0; // Zählregister initialisieren (vorsichtshalber)
OCR1A=5000; // 20ms bis zum ersten Interrupt
OCR1B=5000;
TIMSK |= (1 << OCIE1A);
TIMSK |= (1 << OCIE1B);
servoainit; // Datenrichtung der Servopins A einstellen
servobinit; // Datenrichtung der Servopins B einstellen
sei(); // ... und los!
leds(red);
writeString("Hallo\n");
sleep(50);
while(1)
{
leds(yellow);
servo_pos_a[1]=90;
servo_pos_b[3]=90;
sleep(50);
leds(red);
servo_pos_a[1]=250;
servo_pos_b[3]=210;
sleep(50);
leds(green);
servo_pos_a[1]=450;
servo_pos_b[3]=330;
sleep(50);
leds(red);
servo_pos_a[1]=250;
servo_pos_b[3]=450;
sleep(100);
}
return(0);
}
ISR (TIMER1_COMPA_vect)
{
static uint8_t servo_nr=1;
uint16_t temp=0;
switch(servo_nr)
{
case 1: temp=servo_pos_a[1]; TCNT1=0; OCR1B=50; servoa1on; break; // ;)
case 2: temp=servo_pos_a[2]; servoa1off; servoa2on; break;
case 3: temp=servo_pos_a[3]; servoa2off; servoa3on; break;
case 4: temp=servo_pos_a[4]; servoa3off; servoa4on; break;
case 5: temp=servo_pos_a[5]; servoa4off; servoa5on; break;
case 6: temp=servo_pos_a[6]; servoa5off; servoa6on; break;
case 7: temp=servo_pos_a[7]; servoa6off; servoa7on; break;
case 8: temp=servo_pos_a[8]; servoa7off; servoa8on; break;
case 9: temp=servo_pos_a[9]; servoa8off; servoa9on; break;
case 10: servoa9off; servo_nr=0; OCR1A = servo_pos_a[0]; if(p) p--; break; // Impulspause
}
if(servo_nr) OCR1A = temp + TCNT1; // nächsten Interruptzeitpunkt berechnen
/*
else
{
writeInteger(TCNT1, 10);
writeChar('A');
writeInteger(OCR1A, 10);
writeChar('\n');
}
*/
servo_nr++;
}
ISR (TIMER1_COMPB_vect)
{
static uint8_t servo_nr=1;
uint16_t temp=0;
switch(servo_nr)
{
case 1: temp=servo_pos_b[1]; servob1on; break;
case 2: temp=servo_pos_b[2]; servob1off; servob2on; break;
case 3: temp=servo_pos_b[3]; servob2off; servob3on; break;
case 4: temp=servo_pos_b[4]; servob3off; servob4on; break;
case 5: temp=servo_pos_b[5]; servob4off; servob5on; break;
case 6: temp=servo_pos_b[6]; servob5off; servob6on; break;
case 7: temp=servo_pos_b[7]; servob6off; servob7on; break;
case 8: temp=servo_pos_b[8]; servob7off; servob8on; break;
case 9: temp=servo_pos_b[9]; servob8off; servob9on; break;
case 10: servob9off; servo_nr=0; break; // nach dem letzen Impuls warten auf A!
}
if(servo_nr) OCR1B = temp + TCNT1; // nächsten Interruptzeitpunkt berechnen
/*
else
{
writeInteger(TCNT1, 10);
writeChar('B');
writeInteger(OCR1B, 10);
writeChar('\n');
}
*/
servo_nr++;
}
void sleep(uint8_t pause) // 1/50 Sekunde blockierende Pause
{
p=pause+1;
while(p);
}
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
}
Ob sich die zwei ISRs ungünstig beeinflussen kann ich noch nicht sagen. Mit der ausgekommentierten Ausgabefunktion in den ISRs erzeugte das Demo folgende Ausgabe:
Code:
Hallo
2268A5000
2805B2267
2267A5000
2804B2316
2268A5000
2805B2315
2268A5000
Links jeweils der Wert des TCNT1 nach allen Impulsen, rechts der Wert des OCR1X-Registers. Führend ist die A-ISR, hier wird neben den Impulsen auch die Impulspause erzeugt. Nach Ende der Impulspause wird das Zählregister zurückgesetzt und das OCR1B-Register mit einem Startwert geladen der dafür sorgt, dass die B-ISR wieder startet. Der OCR1A-Wert für die Wiederholfrequenz ist zu Testzwecken und für Spielereinen im Wert des Dummy-Servo servo_pos_a[0] gespeichert.
Gruß
mic
Lesezeichen