Endlich lebt der Bär und spricht! Er erkennt auch, dass man sich ihm nähert und fäng automatisch mit der Beratung an 🙂

Advertisements

Hier ist ein kurzer Ausschnitt aus unserem Code des Microkontrollers mit dem wir 5 Servos parallel steuern. Wir wollen eine hohe Bewegungsgenauigkeit in der Servosteuerung erreichen und verwenden daher keine Softwarelösung mit delays. So mit ist ein Timer mit Interrupts und Compare-Match unser einziger Ausweg. Mit den zwei Registern OCR1A und OCR1B können wir bis zu 12 Servos parallel steuern. In diesem Code-Beispiel werden nur 5 Motoren angesprochen und nur das Register OCRA wird benutzt.

/*
 * myinterrupt.c
 *
 *  Created on: 26.05.2012
 */
#include <avr/io.h>
#include <avr/interrupt.h>

// ATmega8
// =======
//
// PWM output to the servo motor utilizes Timer/Counter1 in 8-bit mode.
// Output to the motor is assigned as follows:
//
//  OC1A (PB1) - Servo PWM output direction A
//  OC1B (PB2) - Servo PWM output direction B

// Values for up to six PWM channels.
// On each channel, the special value 0 can be used to switch the signal off.
// volatile uint8_t pwm_value[6];
extern volatile uint16_t pwm_value[6];
static volatile uint8_t resetcounter = 1;

// For ATmega 8
void servoinit() {
    // PSR10 -> 1 reset prescaler timer0 and timer 1, no prescale for timer
    SFIOR = (1 << PSR10);
    // Stop the timer1
    TCCR1B &=(~((1<<CS12)|(1<<CS11)|(1<<CS10)));

    // Enable PB1/OC1A and PB2/OC1B as outputs.
    // Enable PB3/OC2
    DDRB |= ((1<<DDB1) | (1<<DDB2) | (1<<DDB3) | (1<<DDB4) | (1<<DDB5));

    // Set PB1..5 to low.
    // Set PB1 oder PB2 not high, its very useful
    PORTB &= ~((1<<PB1) | (1<<PB2) | (1<<PB3) | (1<<PB4) | (1<<PB5));

    // Disable timer 1 and enable timer 2.
    TCCR1A = (0<<COM1A1) | (0<<COM1A0) |                    // Clear OC1A, OC1B on compare match on up-count.
             (0<<COM1B1) | (0<<COM1B0) |                    // (non-inverting mode)
             (1<<WGM11) | (0<<WGM10);                       // Fast PWM - waveform generation mode 14. dataScheet page 97
                                                            // Top ICR1, update of OCR1x BOTTOM

    // Set clock select bits to start timer.
    TCCR1B = (0<<ICNC1) | (0<<ICES1) |                      // Input on ICP1 disabled, not receiving ICP Event from ICP pin 1
             (1<<WGM13) | (1<<WGM12);                       // Select waveform mode 14.
             //| (0<<CS12) | (0<<CS11) | (1<<CS10);             // Timer1 16bits prescaling 256 clkIO/256. From DataScheet Page 99

    // Set TOP of Timer1 to 1249 for 50Hz output Top + 1 = clkIO / (Prescale * PWM_Output_frequenz).
    // get 245Hz output Top + 1 = 16000000 / 256 * 245 interrupts occur at a frequency of 244.14Hz

    //ICR1 = 255;
    ICR1 = 0xFFFF; // output compare match appears in 4ms , 250Hz
    // Reset count and compare registers. // TCNT1 16 Bit Timer TCNT2 8 Bit Timer
    TCNT1 = 0;
    // f_OCnA * 2 * N * ( 1 + OCRnA ) = f_clkIO
    // N = 8 (Fast PWM prescale, set by WGMxx )

    //clkIO 16MHz/256 =  62500 Hz ,after prescaling, per instruction is 16us
    // one Cycle of counter steps with 16bit counter and ICR1 = 255
    //OCR1A = 92; // should be 95 for 1.52ms , Set Top of Compare Match A
    // initializing the fire motors
    // pro step 0,0625us
    pwm_value[0] = 23552; // not used
    pwm_value[1] = 23552;
    pwm_value[2] = 23552;
    pwm_value[3] = 23552;
    pwm_value[4] = 23552;
    pwm_value[5] = 23552;

    // Timer1 Output Compare Match A Interrupt Enable : DataScheet Page 100
    // Timer1 Output Compare Match B Interrupt Enable
    // Timer2 Output Compare Match Interrupt Enable
    TIMSK = (1 << OCIE1A) | (1 << TOIE1)  ;

    // Enable Interrupts : Restore interrupts
    sei();

    // Start the Timer 1 // Timer1 16bits prescaling No.
    TCCR1B |= (1<<CS10); // Select waveform mode 5. CS11 and CS10 are equal to 0
}

// The timer compare interrupt ends the current PWM pulse
ISR(TIMER1_COMPA_vect) {
    // clear all output in compare match A
    // PB1 .. 5 to low
    // PORTB &= ~(2+4+8+16+32); // set all 5 PWM outputs to low
    PORTB &= ~((1<<PB1) | (1<<PB2) | (1<<PB3) | (1<<PB4) | (1<<PB5));
}

// The timer overflow interrupt starts a PWM pulse,
// on each interrupt, the port == resetcounter will be set to high
ISR(TIMER1_OVF_vect) {
    uint16_t value = pwm_value[resetcounter];
    // Turn on the output PWM Port
    /**
     * because of the time delay.
     * we need to switch 1 to PB5 instead of PB1, so that pwm_value(1)
     * is used for PB1 otherwise it is used for PB5
     * It is very trigg here.
     */
    if (value>0) {
        switch (resetcounter) {
            case 1: PORTB |= (1<<PB5); break;
            case 2: PORTB |= (1<<PB1); break;
            case 3: PORTB |= (1<<PB2); break;
            case 4: PORTB |= (1<<PB3); break;
            case 5: PORTB |= (1<<PB4); break;
        }
        OCR1A=value;
    }
    // increase the reset counter
    resetcounter++;

    if (resetcounter >= 6) { // Do not change the 6, even when you need less than 6 channels!
        // reset counter to 0 we use counter start from 1 instead of 0
        resetcounter = 1;
    }
}

Eigentlich sollte schon alles laufen – aber wie so oft, steckt der Teufel im Detail. Nachdem mittlerweile alle Fragen im Wav-Format eingesprochen (und kräftig mit Kompressoren, Noise-Gates und Pitch) nachbearbeitet wurden, machte speziell die sequentielle Steuerung des Frage-Antwort-Frage-Ablaufs Schwierigkeiten. Zudem zickt die Kinect noch ab und an, wenn es darum geht, die Sprache des Users zu erfassen.
Auch der Einbau des kompletten Gestänges mit den Motoren gestaltete sich schwieriger als angenommen. Auch wenn es uns selbst weh tat: Der Kopf musste zwangsläufig fast komplett abgeschnitten werden, damit das neue Skelett entsprechend eingepasst werden konnte.
Das noch letzte Problem neben allerhand Feinarbeit: Lassen sich die Motoren parallel ansprechen – und falls ja: Wie kriegen wir das (schmerzfrei) hin?

Der Bär lebt! Mit einer Konstruktion aus Aluminumstangen, Schellen und Servomotoren haben wir es tatsächlich geschafft, dem Bären Leben einzuhauchen. Als Herz dient wie geplant ein Steckbrett mit einem Atmega8-Microcontroller, über den sich nun Arme, Kopf und Schnauze ansteuern lassen. Problem dabei: Der Bär macht das noch ziemlich sinnfrei, nachdem noch keine Logik existiert, wann (und vor allem: wieso?) er sich bewegen soll.

Deswegen steht nun vor allem auf dem Plan, die bereits programmierte Kinect-Schnittstelle (ein Programm das “Ja”, “Nein” und “Weiter” versteht) mit dem Atmega8-Microcontroller zu verbinden. Das Ziel: Wenn Sprache vom Laptop über den Lautsprecher (wiederum im Inneren des Bären) ausgegeben wird, soll der Bär etwas Gestik rüberbringen. Im Informatikersprech: Eine Schnittstelle von Laptop (C#) zum Microcontroller (C) inklusive Protokoll muss her.

Essenzielle Überlegung: Welche Hardware brauchen wir für das Projekt? Der Bär soll Sprache verstehen und sich bewegen, wenn er selbst spricht. Für die Spracherkennung war der Einsatz der Kinect von Microsoft von Anfang an fest. Im Laufe der Praktikumsvorträge haben wir zudem gelernt, einfache Befehle für Servomotoren über einen Atmega8-Microcontroller zu programmieren, die die Gestik des Bären beim Sprechen (hoffentlich) simulieren können. Als Vermittlerstelle zwischen Microcontroller und Kinect ist zudem ein Laptop nötig sowie ein Lautsprecher, der die Audioausgabe des Bären übernimmt. Darüber hinaus brauchen wir ein Gerüst, über das die Servomotoren Arme, Kopf und eventuell die Schnauze steuern können. Nachteil dabei: Der arme Teddybär musste ausgeweidet werden…