Forum: Mikrocontroller und Digitale Elektronik 2 Servos mit attiny85


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Roman B. (dxx255)


Bewertung
0 lesenswert
nicht lesenswert
Hallo,
ich möchte 2 Servos mit einem attiny85 steuern.
Code:
#define F_CPU 1000000UL
#define SERVOS 2
#define OFFSET 99
#define PIN_SET_HIGH 1
#define OFFSET_MARK 3
#define MS_20_MARK 2
#define WAIT_20_MS 4
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
char state=MS_20_MARK;
typedef struct servoregistry {
    int servo_id;
    int pins[SERVOS];
    int values[SERVOS];
} servo_registry;

servo_registry servos;
void servo_init(void){
   servos.servo_id=0;
   servos.pins[0] = (1<<PB0);
   servos.pins[1]=(1<<PB1);
   servos.values[0]=270;
   servos.values[1]=99;
}
void ADC_Init(void) {
 
  uint16_t result;
 
  ADMUX = (0<<REFS1) | (1<<REFS0);      // AVcc als Referenz benutzen
  ADCSRA = (1<<ADPS2)|(1<<ADPS1) | (1<<ADPS0);     // Frequenzvorteiler
  ADCSRA |= (1<<ADEN);                  // ADC aktivieren
 
  /* nach Aktivieren des ADC wird ein "Dummy-Readout" empfohlen, man liest
     also einen Wert und verwirft diesen, um den ADC "warmlaufen zu lassen" */
 
  ADCSRA |= (1<<ADSC);                // eine ADC-Wandlung 
  while (ADCSRA & (1<<ADSC) ) {}      // auf Abschluss der Konvertierung warten
  /* ADCW muss einmal gelesen werden, sonst wird Ergebnis der nächsten
     Wandlung nicht übernommen. */
  result = ADCW;
}
uint16_t ADC_Read( uint8_t channel )
{
  // Kanal waehlen, ohne andere Bits zu beeinflußen
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
  ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
  while (ADCSRA & (1<<ADSC) ) {}  // auf Abschluss der Konvertierung warten
  return ADCW;                    // ADC auslesen und zurückgeben
}
int ovf_cnt=0;
int wait_cnt;
ISR( TIMER0_COMPA_vect )                // Interruptbehandlungsroutine
{    
          
      if(servos.servo_id==SERVOS){
            servos.servo_id=0;
            state=WAIT_20_MS;
            ovf_cnt=0;
            wait_cnt=2500-(SERVOS*OFFSET); //2500(~20ms) - SERVOS*OFFSET;
            uint8_t x=0;
            for(;x<SERVOS;x++)wait_cnt-=servos.values[x];
            wait_cnt/=10;
            OCR0A=wait_cnt;
      }else if(state==MS_20_MARK){
           PORTB |=servos.pins[servos.servo_id];
           OCR0A=OFFSET;
           state=OFFSET_MARK;
      }else if(state==OFFSET_MARK&&servos.servo_id<SERVOS){
          OCR0A = servos.values[servos.servo_id]-OFFSET;
          state=PIN_SET_HIGH;
      }else if(state==PIN_SET_HIGH){
          PORTB &=~(servos.pins[servos.servo_id]);
          PORTB |=servos.pins[++servos.servo_id];
           OCR0A=OFFSET;
           state=OFFSET_MARK;
      }else if(state==WAIT_20_MS){
             ovf_cnt++;
          if(ovf_cnt==10){
             state=MS_20_MARK; 
          }
      }
      
            
}
 
 
int main (void)
{
  servo_init();
  ADC_Init();
  ADC_Read(0);
  srand(ADC_Read(0));
  DDRB=(1<<PB1)|(1<<PB0)|(1<<PB3);
  PORTB|=(1<<PB3);
  TCCR0A=(1<<WGM01);
  TCCR0B = (1<<CS01);      // CTC-Mode; Prescaler 8
  TIMSK  = (1<<OCIE0A);             // Timer-Compare Interrupt an
 //Werte für 0,004ms
 //Servo groß
 //530==0°                      
 //357==90°
 //198==(beinahe)180°
 
 //Servo klein
 //540==0°                      
 //357==90°
 //198==(beinahe)180°
  OCR0A=0;
  sei();                                // Interrupts global an
 
  while( 1 ) {
    servos.values[0]=rand()%270;
    servos.values[1]=rand()%270;
  }
 
  return 0;
}

Es bewegt sich jedoch immer nur der Servo an PB1.
Wo liegt mein Fehler?

LG

: Bearbeitet durch User
von Roman B. (dxx255)


Bewertung
0 lesenswert
nicht lesenswert
Weiß keiner Rat?

von MWS (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Roman B. schrieb:
> Weiß keiner Rat?

Bevor Du wartest, dass sich jemand durch Deinen unkommentierten Code 
wühlt, wärst Du mit einer Simulation schon längst fertig, eine SM wie 
diese lässt sich gut simulieren.

Ansonsten, es gibt die Regel dass im Main und ISR gemeinsam verwendete 
Variablen volatile zu deklarieren sind, die Verwendung eines Struct 
ändert daran nichts.

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Roman B. schrieb:
> Weiß keiner Rat?
Dir ist hoffentlich klar, dass das ein amoklaufender Pointer ist:
Roman B. schrieb:
> PORTB |=servos.pins[++servos.servo_id];
Denn wenn zuvor servo_id == 1 ist, dann wird es hier erst mal auf 2 
erhöht (PreIncrement!) und danach auf ein Element zugegriffen, das nicht 
mehr Inhalt von servos.pins ist. Es wird nämlich auf servos.pins[2] 
zugegriffen. Das ist aber das selbe wie servos.values[0]. Und der "Pin", 
der dort deklariert ist, wird dann gesetzt...  :-o

Noch eine kleine Unstimmigkeit: ein int ist 16 Bit breit. Ein Port hat 
nur 8 Bit...

> Es bewegt sich jedoch immer nur der Servo an PB1.
Was passiert, wenn du die Ports einfach mal tauschst?

> Wo liegt mein Fehler?
Du analysierst das Problem nicht. Oder du teilst uns nicht mit, was du 
schon herausgefunden hast...
Hast du ein Oszilloskop?

von Peter D. (peda)


Bewertung
0 lesenswert
nicht lesenswert
Lothar Miller schrieb:
> Noch eine kleine Unstimmigkeit: ein int ist 16 Bit breit. Ein Port hat
> nur 8 Bit...

Und der Timer (OCR0A) auch.
Nimm daher uint8_t.
Ansonsten muß man bei int die Mainzugriffe atomar kapseln.

von Roman B. (dxx255)


Bewertung
-1 lesenswert
nicht lesenswert
Danke für diese Hinweise!!
Habe das jetzt ausgebessert, irgendwo hängts aber immer noch...

MWS schrieb:
> Bevor Du wartest, dass sich jemand durch Deinen unkommentierten Code
> wühlt, wärst Du mit einer Simulation schon längst fertig, eine SM wie
> diese lässt sich gut simulieren.

Wie mache ich das?
(Verwende avr dragon)

: Bearbeitet durch User
von Rolf M. (rmagnus)


Bewertung
0 lesenswert
nicht lesenswert
Roman B. schrieb:
> while( 1 ) {
>     servos.values[0]=rand()%270;
>     servos.values[1]=rand()%270;
>   }

Diesen Testfall finde ich eher ungeschickt. Der dürfte zu wildem Gezucke 
der Servos führen. Genau das gleiche, was auch passiert, wenn die 
Timings nicht richtig funnktioniern.
Auch ist mir aufgefallen, daß im deiner Statemachine in der ISR in zwei 
verschiedenen States (und damit in zwei getrennten ISR-Aufrufen) auf die 
Servo-Werte zugegriffen wird. Dein Hauptprogramm ändert zwischendrin 
aber diese Werte.

MWS schrieb:
> eine SM wie diese lässt sich gut simulieren

Suchmaschine? Seemeile? Sailor Moon? Was könnte eine "SM" sein?

von HildeK (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
> Suchmaschine? Seemeile? Sailor Moon? Was könnte eine "SM" sein?

State Machine!

von MWS (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Roman B. schrieb:
> Wie mache ich das?
> (Verwende avr dragon)

Mit dem Dragon dürftest Du In-Circuit debuggen können, mir hat immer die 
Simulation im AVR-Studio gereicht.

Wenn Du per AVR-Studio simulieren möchtest, dann compilierst Du mit -O0, 
startest den Simulator (Debug -> Start Debugging), legst einen 
Haltepunkt mit F9 auf die ISR, lässt mit F5 laufen und steppst per F11 
durch den Code. Dabei schaust Du Dir die Variablen an, das geht per 
Watch (Rechtsklick -> Add Watch), in der IO-View beobachtest Du, wie 
sich der Port ändert.

Studio simuliert auch die Timer und ruft die ISR passend auf, bei großen 
Prescalern kann's sinnvoll sein, diese nur für die Simulation auf 1 zu 
verkleinern. Damit führt das Studio den Interrupt schneller aus.

In der Simulation vergleichst Du, was Deiner Meinung nach passieren soll 
und was tatsächlich passiert.

von MWS (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Rolf Magnus schrieb:
> MWS schrieb:
>> eine SM wie diese lässt sich gut simulieren
>
> Suchmaschine? Seemeile? Sailor Moon? Was könnte eine "SM" sein?

OMG (oh my god, Oh.., mein Gott)

Ok, versuchen wir mal:

> Eine Suchmaschine wie diese lässt sich gut simulieren
Hm.
> eine Seemeile wie diese lässt sich gut simulieren
Hmm.
> eine Sailor Moon wie diese lässt sich gut simulieren
Hmmm.
> eine Sado Maso wie diese lässt sich gut simulieren
Hmmmm.
> eine State Machine wie diese lässt sich gut simulieren
Bingo.

Und was ist das, was der TS in seinem Code zeigt? Eine State Machine, 
oder nicht-Angelsächsisch auch Zustandsautomat genannt. Aber Du machst 
doch den Job lang genug, um das zu wissen, warum hast Du gefragt?

Wolltest Du Klarstellung für die Leserschaft?

Aber selbst dann - jemand, der den Code nicht lesen kann, der nicht 
erkennt dass es sich dabei um eine State Machine handelt und der auch 
den Ausdruck "State Machine" nie gehört hat, wird zum Thema nicht viel 
beitragen können, dann ist's mir auch egal, ob er SM versteht.

Wer dagegen den Ausdruck State Machine kennt, der wird's problemlos in 
Kontext bringen.

von JWD (Gast)


Bewertung
0 lesenswert
nicht lesenswert
MWS schrieb:
> OMG (oh my god, Oh.., mein Gott)
>

Gut, daß man für "MWS" keine Erklärung braucht.

von Rolf M. (rmagnus)


Bewertung
0 lesenswert
nicht lesenswert
MWS schrieb:
> Und was ist das, was der TS in seinem Code zeigt? Eine State Machine,
> oder nicht-Angelsächsisch auch Zustandsautomat genannt. Aber Du machst
> doch den Job lang genug, um das zu wissen, warum hast Du gefragt?

Dass in seinem Code eine Statemachine steckt, weiß ich. Hab ich ja 
selbst geschrieben.

> Wolltest Du Klarstellung für die Leserschaft?

Nein, ich hab wirklich die Verbindung nicht gesehen. Ich hatte bei "SM" 
eher gedacht, daß damit das Programm als ganzes gemeint ist und eben 
nach alternativen Begriffen für "Code", "Compilat" oder "Programm" 
gesucht und keinen gefunden, der mit "SM" abgekürzt werden könnte. Bei 
Arduino gibt es "Sketch", aber was das "M" bedeuten könnte, wäre dann 
auch unklar. Ich hatte sogar an sowas wie "Servo-Manager" gedacht, aber 
konnte mir nicht so recht vorstellen, daß das wirklich gemeint sein 
könnte, abgesehen davon, daß es mit dem Genus nicht gepasst hätte.

> Wer dagegen den Ausdruck State Machine kennt, der wird's problemlos in
> Kontext bringen.

Oder auch nicht. ;-)

von Peter D. (peda)


Bewertung
0 lesenswert
nicht lesenswert
Mir ist das viel zu viel Holz im Interrupt.
Insbesondere * und / kosten einiges auf CPUs ohne MUL/DIV-Befehl.
Ich würde den Interrupt soweit wie möglich entschlacken, z.B.:
//#define F_CPU 1000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdlib.h>
#include "sbit.h"

#define PRESCALER       8
#define SERVO_MIN       (uint8_t)(F_CPU * 1e-3 / PRESCALER )
#define SERVO_MID       (uint8_t)(F_CPU * 1.5e-3 / PRESCALER )
#define SERVO_MAX       (uint8_t)(F_CPU * 2e-3 / PRESCALER )

#define SERVO0          PORT_B0
#define SERVO0_oe       DDR_B0
#define SERVO1          PORT_B1
#define SERVO1_oe       DDR_B1

volatile uint8_t servos[2] = { SERVO_MID, SERVO_MID };

enum { SV1_ON, SV_OFF, SV_END = 20 };

ISR( TIMER0_COMPA_vect )                // Interruptbehandlungsroutine
{    
  static uint8_t servostate = SV_END;
  
  switch( servostate++ ){
    case SV_END:                        // and again
      servostate = SV1_ON;
      SERVO0 = 1;
      OCR0A += servos[0];
      break;
    case SV1_ON:
      SERVO1 = 1;
      SERVO0 = 0;
      OCR0A += servos[1];
      break;
    case SV_OFF:
      SERVO1 = 0;
      OCR0A += SERVO_MIN;
      break;
    default:
      OCR0A += SERVO_MIN;
      break;
  }
}
 
int main (void)
{
  SERVO0_oe = 1; 
  SERVO1_oe = 1;
  TCCR0A=0;                             // Mode 0
  TCCR0B = (1<<CS01);                   // Prescaler 8
  TIMSK  = (1<<OCIE0A);                 // Timer-Compare Interrupt an
  sei();                                // Interrupts global an
  
  while( 1 ) {
    _delay_ms( 1000 ); 
    servos[0]=rand()%(SERVO_MAX - SERVO_MIN) + SERVO_MIN;
    servos[1]=rand()%(SERVO_MAX - SERVO_MIN) + SERVO_MIN;
  }
}

von MWS (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Peter Dannegger schrieb:
> Ich würde den Interrupt soweit wie möglich entschlacken, z.B.:

Damit hast Du bewiesen, dass Du programmieren kannst, was man vorher 
auch wusste. Ein Erfolgserlebnis für den TE, "seinen" Fehler zu finden, 
bescherst Du damit nicht.

von Peter D. (peda)


Bewertung
0 lesenswert
nicht lesenswert
MWS schrieb:
> Ein Erfolgserlebnis für den TE, "seinen" Fehler zu finden,
> bescherst Du damit nicht.

Das wird eher daran liegen, daß niemand da durchsieht, ich jedenfalls 
nicht.
Das Fehlen von Kommentaren ist hier besonders störend.

Auchmagichesnicht,wennmandenCodeohneLeerzeichenaneinanderklatscht. 
Leerzeichen können die Lesbarkeit deutlich erhöhen, wie bei jedem 
anderen Text auch.

If-else-Monster finde ich auch schlecht lesbar. Wann immer möglich, 
nehme ich switch-case. Da hat man eine klare Struktur, so daß es auch 
ohne Kommentare leicht verstehbar ist.

von MWS (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Peter Dannegger schrieb:
> Das wird eher daran liegen, daß niemand da durchsieht, ich jedenfalls
> nicht.

Auch ich fand's schwer, daher mein Rat, es zu simulieren.

von Roman B. (dxx255)


Bewertung
0 lesenswert
nicht lesenswert
Peter Dannegger schrieb:
> Das wird eher daran liegen, daß niemand da durchsieht, ich jedenfalls
> nicht.

Danke für die Hilfe trotz dieses Umstandes!!

Peter Dannegger schrieb:
> Mir ist das viel zu viel Holz im Interrupt.

Natürlich lässt es sich entschlacken. Ich wäre nie auf diese 
Möglichkeiten gekommen! Danke dafür.

Eine Frage zu dem verbesserten Programm hätte ich noch:
Warum wird Timer-Mode 0 verwendet und nicht CTC bzw. warum ruckeln die 
Servos bei Verwendung von CTC?

: Bearbeitet durch User
von Peter D. (peda)


Bewertung
0 lesenswert
nicht lesenswert
Roman B. schrieb:
> Warum wird Timer-Mode 0 verwendet und nicht CTC bzw. warum ruckeln die
> Servos bei Verwendung von CTC?

Das war nur mal schnell dahingeschrieben.

CTC sollte auch gehen. Bin mir aber nicht sicher, ob dabei OCR1A 
gepuffert wird, dann muß es einen Interrupt früher gesetzt werden oder 
die Pins einen Interrupt später.

: Bearbeitet durch User
von MWS (Gast)


Bewertung
-1 lesenswert
nicht lesenswert
Peter Dannegger schrieb:
> CTC sollte auch gehen. Bin mir aber nicht sicher, ob dabei OCR1A
> gepuffert wird, dann muß es einen Interrupt früher gesetzt werden oder
> die Pins einen Interrupt später.

OCR0A

Der Double Buffer ist bei den 8Bit AVRs nur in den PWM-Modes an.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.