Forum: Mikrocontroller und Digitale Elektronik 2 Servos mit attiny85


von R. B. (dxx255)


Lesenswert?

Hallo,
ich möchte 2 Servos mit einem attiny85 steuern.
Code:
1
#define F_CPU 1000000UL
2
#define SERVOS 2
3
#define OFFSET 99
4
#define PIN_SET_HIGH 1
5
#define OFFSET_MARK 3
6
#define MS_20_MARK 2
7
#define WAIT_20_MS 4
8
#include <avr/io.h>
9
#include <avr/interrupt.h>
10
#include <util/delay.h>
11
#include <stdlib.h>
12
char state=MS_20_MARK;
13
typedef struct servoregistry {
14
    int servo_id;
15
    int pins[SERVOS];
16
    int values[SERVOS];
17
} servo_registry;
18
19
servo_registry servos;
20
void servo_init(void){
21
   servos.servo_id=0;
22
   servos.pins[0] = (1<<PB0);
23
   servos.pins[1]=(1<<PB1);
24
   servos.values[0]=270;
25
   servos.values[1]=99;
26
}
27
void ADC_Init(void) {
28
 
29
  uint16_t result;
30
 
31
  ADMUX = (0<<REFS1) | (1<<REFS0);      // AVcc als Referenz benutzen
32
  ADCSRA = (1<<ADPS2)|(1<<ADPS1) | (1<<ADPS0);     // Frequenzvorteiler
33
  ADCSRA |= (1<<ADEN);                  // ADC aktivieren
34
 
35
  /* nach Aktivieren des ADC wird ein "Dummy-Readout" empfohlen, man liest
36
     also einen Wert und verwirft diesen, um den ADC "warmlaufen zu lassen" */
37
 
38
  ADCSRA |= (1<<ADSC);                // eine ADC-Wandlung 
39
  while (ADCSRA & (1<<ADSC) ) {}      // auf Abschluss der Konvertierung warten
40
  /* ADCW muss einmal gelesen werden, sonst wird Ergebnis der nächsten
41
     Wandlung nicht übernommen. */
42
  result = ADCW;
43
}
44
uint16_t ADC_Read( uint8_t channel )
45
{
46
  // Kanal waehlen, ohne andere Bits zu beeinflußen
47
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
48
  ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
49
  while (ADCSRA & (1<<ADSC) ) {}  // auf Abschluss der Konvertierung warten
50
  return ADCW;                    // ADC auslesen und zurückgeben
51
}
52
int ovf_cnt=0;
53
int wait_cnt;
54
ISR( TIMER0_COMPA_vect )                // Interruptbehandlungsroutine
55
{    
56
          
57
      if(servos.servo_id==SERVOS){
58
            servos.servo_id=0;
59
            state=WAIT_20_MS;
60
            ovf_cnt=0;
61
            wait_cnt=2500-(SERVOS*OFFSET); //2500(~20ms) - SERVOS*OFFSET;
62
            uint8_t x=0;
63
            for(;x<SERVOS;x++)wait_cnt-=servos.values[x];
64
            wait_cnt/=10;
65
            OCR0A=wait_cnt;
66
      }else if(state==MS_20_MARK){
67
           PORTB |=servos.pins[servos.servo_id];
68
           OCR0A=OFFSET;
69
           state=OFFSET_MARK;
70
      }else if(state==OFFSET_MARK&&servos.servo_id<SERVOS){
71
          OCR0A = servos.values[servos.servo_id]-OFFSET;
72
          state=PIN_SET_HIGH;
73
      }else if(state==PIN_SET_HIGH){
74
          PORTB &=~(servos.pins[servos.servo_id]);
75
          PORTB |=servos.pins[++servos.servo_id];
76
           OCR0A=OFFSET;
77
           state=OFFSET_MARK;
78
      }else if(state==WAIT_20_MS){
79
             ovf_cnt++;
80
          if(ovf_cnt==10){
81
             state=MS_20_MARK; 
82
          }
83
      }
84
      
85
            
86
}
87
 
88
 
89
int main (void)
90
{
91
  servo_init();
92
  ADC_Init();
93
  ADC_Read(0);
94
  srand(ADC_Read(0));
95
  DDRB=(1<<PB1)|(1<<PB0)|(1<<PB3);
96
  PORTB|=(1<<PB3);
97
  TCCR0A=(1<<WGM01);
98
  TCCR0B = (1<<CS01);      // CTC-Mode; Prescaler 8
99
  TIMSK  = (1<<OCIE0A);             // Timer-Compare Interrupt an
100
 //Werte für 0,004ms
101
 //Servo groß
102
 //530==0°                      
103
 //357==90°
104
 //198==(beinahe)180°
105
 
106
 //Servo klein
107
 //540==0°                      
108
 //357==90°
109
 //198==(beinahe)180°
110
  OCR0A=0;
111
  sei();                                // Interrupts global an
112
 
113
  while( 1 ) {
114
    servos.values[0]=rand()%270;
115
    servos.values[1]=rand()%270;
116
  }
117
 
118
  return 0;
119
}

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

LG

: Bearbeitet durch User
von R. B. (dxx255)


Lesenswert?

Weiß keiner Rat?

von MWS (Gast)


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. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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)


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 R. B. (dxx255)


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)


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)


Lesenswert?

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

State Machine!

von MWS (Gast)


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)


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)


Lesenswert?

MWS schrieb:
> OMG (oh my god, Oh.., mein Gott)
>

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

von Rolf M. (rmagnus)


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)


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.:
1
//#define F_CPU 1000000UL
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/delay.h>
5
#include <stdlib.h>
6
#include "sbit.h"
7
8
#define PRESCALER       8
9
#define SERVO_MIN       (uint8_t)(F_CPU * 1e-3 / PRESCALER )
10
#define SERVO_MID       (uint8_t)(F_CPU * 1.5e-3 / PRESCALER )
11
#define SERVO_MAX       (uint8_t)(F_CPU * 2e-3 / PRESCALER )
12
13
#define SERVO0          PORT_B0
14
#define SERVO0_oe       DDR_B0
15
#define SERVO1          PORT_B1
16
#define SERVO1_oe       DDR_B1
17
18
volatile uint8_t servos[2] = { SERVO_MID, SERVO_MID };
19
20
enum { SV1_ON, SV_OFF, SV_END = 20 };
21
22
ISR( TIMER0_COMPA_vect )                // Interruptbehandlungsroutine
23
{    
24
  static uint8_t servostate = SV_END;
25
  
26
  switch( servostate++ ){
27
    case SV_END:                        // and again
28
      servostate = SV1_ON;
29
      SERVO0 = 1;
30
      OCR0A += servos[0];
31
      break;
32
    case SV1_ON:
33
      SERVO1 = 1;
34
      SERVO0 = 0;
35
      OCR0A += servos[1];
36
      break;
37
    case SV_OFF:
38
      SERVO1 = 0;
39
      OCR0A += SERVO_MIN;
40
      break;
41
    default:
42
      OCR0A += SERVO_MIN;
43
      break;
44
  }
45
}
46
 
47
int main (void)
48
{
49
  SERVO0_oe = 1; 
50
  SERVO1_oe = 1;
51
  TCCR0A=0;                             // Mode 0
52
  TCCR0B = (1<<CS01);                   // Prescaler 8
53
  TIMSK  = (1<<OCIE0A);                 // Timer-Compare Interrupt an
54
  sei();                                // Interrupts global an
55
  
56
  while( 1 ) {
57
    _delay_ms( 1000 ); 
58
    servos[0]=rand()%(SERVO_MAX - SERVO_MIN) + SERVO_MIN;
59
    servos[1]=rand()%(SERVO_MAX - SERVO_MIN) + SERVO_MIN;
60
  }
61
}

von MWS (Gast)


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)


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)


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 R. B. (dxx255)


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)


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)


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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.