32 Servos an Atmega328

OP #5087548
Lesenswert?

Hallo allerseits,
ich habe zur Zeit ein kleines Problemchen...
Ich möchte an einem Atmega328 32 Servos betreiben. Ich habe bereits 
etwas rechachiert und bin auf das Breakout des PCA9685 von Adafruit 
gestoßen was mir aber für fast 15€ zu teuer ist und ich eigentlich einen 
Chip auf einer Platine fest einsetzen möchte. Andere Überlegung wäre 
gewesen per SoftwarePWM und MCP23017 die Servos zu steuern, nur habe ich 
da keine Beispiele oder ähnliches gefunden wie das gehen soll.
Nun die Frage: hat vielleicht jemand von euch eine Ahnung oder eine Idee 
wie ich das am einfachsten hinbekommen kann?

MFG
Lukas
Gast #5087586
Lesenswert?

Schon mal etwas gerechnet?
Glauben hilft da nich viel.

Mit etwas Programmiergeschick wird das zeitlich völlig unkritisch sein.
Nimm einfach 2 Atmegas damit Du genug portpins hast, billiger und 
straiter gehts nicht
#5087609
Lesenswert?

Da der Servo-Impuls deutlich kürzer ist, als die Periodendauer, kann man 
das Ausgangssignal von einem Hardware PWM-Ausgang per Dekoder, im 
Zeitmultiplex, auf mehrere Servos verteilen. Das würde auch das Problem 
lösen, das der ATmega328 gar keine 32 Signalausgänge hat. Wobei solche 
Aufgaben mit Software-PWM durchaus lösbar sind, auch ohne Zittern der 
Servos, wenn man entsprechend gut programmieren kann. Wenn Du mit 20ms 
Periodendauer (ist ein gängiger Wert) arbeiten willst, kannst Du gerade 
so 8 Servos (max. 2,5ms Impuls) aus einem PWM-Ausgang bedienen. Die 
Arbeit auf mehrere, eventuelle recht kleine (z.B. ATtiny), 
Mikrocontroller zu verteilen, wäre vielleicht auch noch eine 
Möglichkeit.

Mit freundlichen Grüßen - Martin
Gast #5087613
Lesenswert?

Lukas D. schrieb:
> Robbe schrieb:
>> Schon mal etwas gerechnet?
>> Glauben hilft da nich viel.
>>
>> Mit etwas Programmiergeschick wird das zeitlich völlig unkritisch sein.
>> Nimm einfach 2 Atmegas damit Du genug portpins hast, billiger und
>> straiter gehts nicht
>
> Du meinst zwei Atmegas per Seriell kommunizieren lassen?

Wie auch immer, die Stellwerte kommen doch irgendwo her.
OP #5087645
Lesenswert?

Robbe schrieb:
> Außerdem benötigt der Servo PPM nicht PWM.
>
> Welche Plattform Du nutzt hängt von deinen Kenntnissen ab, Hardware wie
> Software.
>
> Arduino unterstützt, meine ich, bis zu 12 Servos an beliebigen Pins.

Also ich denke die Hardware wäre nicht das Problem nur Software bin ich 
in c nicht grad auf der Höhe.
Noch habe ich alle Projekte mit der Arduino Plattform realisieren 
können...
Gast #5087658
Lesenswert?

Jeweils 8 Servos kann man doch einfach bedienen, indem man eine "1" 
durch ein Schieberegister schiebt und den Abstand der Taktflanke 
entsprechend der jeweils gewünschten Servopulslänge wählt. Dann braucht 
man als µC auch keinen "100-Füßler".
Gast #5087739
Lesenswert?

>Die PCA9685 waren schon der richtige Ansatz.

Das hängt alles von dem Anspruch an den Optimierungsgrad und den genauen 
Anforderungen ab.
Zwei PCA9685 kosten ca. 30 Euro
https://www.adafruit.com/product/815
3 Arduino Nano vom Chinesen kann man für unter 10 Euro erwerben.

>So einen Schwachsinn können sich auch nur "Arduidioten" ausdenken....
Solche Sprüche lassen die Dumpfbacken (TM) im MC-Netz gerne mal los
( ist scheinbar schon seit 2006 aktiv:
Beitrag "Re: Programmierbarer Widerstand?" )

Ich weiß, bei einer Vielzahl an Lösungsmöglichkeiten eines Problems 
kommt eine Dumpfbacke schnell an die Grenzen ihres geistigen Horizonts.
#5087789
Lesenswert?

Der 4017 ist der richtige Baustein. Mit dem wurden früher auch die 
Signale der Mehrkanalfernbedienungen auf Servos verteilt.
Mit 5 Leitungen kann man so bequem 32 Servos ansteuern.

Reset aller Bausteine bei 0, wenn die On-Zeit für Servo 1 abgelaufen 
ist, Taktimpuls und die On-Zeit für Servo 2 startet. Wenn die abgelaufen 
ist, wieder einen Taktimpuls und On-Zeit für Servo 3 startet ...
1
R ___|_______________________________________________________________
2

3
T__________|____|___|_______|______|______|____|_____________________
4
      _____
5
S1 __|     |_________________________________________________________
6
            ____
7
S2 ________|    |____________________________________________________
8
                 ___
9
S3 _____________|   |________________________________________________
10
                     _______
11
S4 _________________|       |________________________________________
12

13
S5 ...


Gruß
Jobst
#5087956
Lesenswert?

Also ich habe 24 Servos an einem Mega8515 mit 8Mhz hängen, das geht ganz 
gut.
Ich habe die Servos erstmal in 4 Gruppen aufgeteilt. Für jede Gruppe 
werden die Werte der PWM immer neu vergleichen und gesetzt.
Dann einen Timer verwendet, der die Zeit per Output-Compare auflöst. Um 
die einzelenen Schritte auszurechnen, lasse ich den Timer alle 10µs 
einen Interrupt erzeugen. Dieser beendet dadurch einen Sleep-Befehl. Der 
Code läuft dadurch synchron.
1
    for(sd_timer=0;sd_timer<SD_TIMER_TOP;sd_timer++)   /*10µs pro loop*/
2
    {
3
      SD_SLEEP;   /*timer weckt durch interrupt*/
4
      if(sd_timer >= val0) { IO_PWM_OUT_00_DIS; } else { IO_PWM_OUT_00_EN; }
5
      if(sd_timer >= val1) { IO_PWM_OUT_01_DIS; } else { IO_PWM_OUT_01_EN; }
6
      ...
7
      if(UCSRA & (1<<RXC)) sio_in_buffer[bptr++]=UDR;   /*Schnittstelle lesen*/
In den Pausen zwischen den 4 Gruppen wird die Position der Servos neu 
berechnet und Kommunikation am laufen gehalten.
Ein paar Sachen muss man optimieren, z.B. die ISR
1
/*dieser INT reisst den µc aus dem schlaf*/
2
/*ACHTUNG !!! - Das ist eine pseudo-Interruptroutine*/
3
/*sie wird korrekt eingetragen, aber es werden keine Register gesichert*/
4
void TIMER1_COMPA_vect(void)
5
{
6
  asm("reti");
7
}

Eine andere Idee ist, wenn nicht alle Servos gleichzeitig laufen müssen, 
kann man die Servos auch nur dann anschalten, wenn sie gebraucht werden, 
also PWM ausschalten. Dann bleiben sie stehen und werden durchs die 
Getriebe-Übersetzung gehalten. Das sollte man sowieso machen, da ein 
Servo, der immer gegen einen Anschlag drückt warm wird. Deswegen ist bei 
mir mal ein Servo richtig abgebrannt. Durch die Erwärmung verstellt sich 
bei einfachen Typen der Winkel und das kann das Problem verschlimmern.
Gast #5088038
Lesenswert?

Verteile das auf mehrere Mikrocontroller. Ohne großartige Klimmzüge sind 
10 Servos an einem ATtiny möglich. Nimm vier davon und verbinde sie per 
I2C oder irgend einer anderen seriellen Schnittstelle mit deinem ATmega.

Das Auslagern des zeitkritischen Teils auf separate Controller 
verinfacht auch die Programmierung ganz erheblich.

Zum abgucken: http://stefanfrings.de/servocontroller/index.html
Gast #5088042
Lesenswert?

> 8-Bit Auflösung für die Servo-Pulsbreite ist nicht so der Bringer

Das dachte ich auch mal. Aber in der Praxis wirst du zwischen 16bit und 
8bit keinen Unterschied bemerken. So genau arbeiten die Dinger ohnehin 
nicht.
Gast #5088073
Lesenswert?

> Du bräuchtest also 3 Arduino Nanos über eine Schnittstelle verbunden.
> Was spricht gegen Multiprozessing?

Nichts.

> So einen Schwachsinn können sich auch nur "Arduidioten" ausdenken....

Das halte ich ganz und gar nicht für Schwachsinn. Mit Welcher Begründung 
kommst du zu dieser krassen Bemerkung?

> Zwei PCA9685 kosten ca. 30 Euro
> Wie kommst du da drauf? ... (Vorschlag für 2x 5,99)

Arduino Nanos sind immer noch bedeutend billiger, falls es noch keiner 
gemerkt hat. Und die von mir vorgeschlagenen ATtinies kosten bei 
Reichelt noch weniger, und man muss nicht 6 Wochen auf die Lieferung 
warten oder gar zum Zoll fahren.
#5088083
Lesenswert?

Tja, wenn man nicht programmieren kann, nimmt man eben für jede popelige 
Funktion einen eigenen µC - typisch Arduino-Jünger

Dabei vergessen sie meist, daß das Kommunikationsprotokoll zwischen den 
µCs für diese Anfänger i.d.R. die viel grössere Hürde darstellt.

Man muß auch keine 6 Wo. auf den PCA9685 warten.
Mein Link zeigt auf einen deutschen Händler, und da hat man die Teile 
i.d.R. innerhalb von 2-3 Tagen.
OP #5088129
Lesenswert?

Stefan U. schrieb:
> Verteile das auf mehrere Mikrocontroller. Ohne großartige Klimmzüge sind
> 10 Servos an einem ATtiny möglich. Nimm vier davon und verbinde sie per
> I2C oder irgend einer anderen seriellen Schnittstelle mit deinem ATmega.
>
> Das Auslagern des zeitkritischen Teils auf separate Controller
> verinfacht auch die Programmierung ganz erheblich.
>
> Zum abgucken: http://stefanfrings.de/servocontroller/index.html

Die Lösung klingt nach einem wirklich interessanten Ansatz...
Ich glaube das werde ich mal probieren müssen ;)
Gast #5088137
Lesenswert?

> Dabei vergessen sie meist, daß das Kommunikationsprotokoll zwischen
> den µCs für diese Anfänger i.d.R. die viel grössere Hürde darstellt.

Warum hast du (Harry L) dann den PCA9685 vorgeschlagen?

> Tja, wenn man nicht programmieren kann, nimmt man eben für jede
> popelige Funktion einen eigenen µC

Na und, Hauptsache, die Bastelei funktioniert an Ende.

Ich denke, es wird auch schwierig sein, eine 1-Chip Lösung in Handarbeit 
zu verarbeiten. Da werden ziemlich viele Leitungen sternförmig zum µC 
zusammen laufen. Eine dezentrale Lösung mit I2C Bus und kleineren IC's 
lässt sich viel leichter handhaben.
#5088160
Lesenswert?

Stefan U. schrieb:
>> Dabei vergessen sie meist, daß das Kommunikationsprotokoll zwischen
>> den µCs für diese Anfänger i.d.R. die viel grössere Hürde darstellt.
>
> Warum hast du (Harry L) dann den PCA9685 vorgeschlagen?
>
>> Tja, wenn man nicht programmieren kann, nimmt man eben für jede
>> popelige Funktion einen eigenen µC
>
> Na und, Hauptsache, die Bastelei funktioniert an Ende.
>
> Ich denke, es wird auch schwierig sein, eine 1-Chip Lösung in Handarbeit
> zu verarbeiten. Da werden ziemlich viele Leitungen sternförmig zum µC
> zusammen laufen. Eine dezentrale Lösung mit I2C Bus und kleineren IC's
> lässt sich viel leichter handhaben.

Merkst du gar nicht, wie du dir widersprichst?
Oder kennst du einfach den PCA9685 nicht?

Aber Hauptsache man gibt seinen Senf dazu....
Gast #5088185
Lesenswert?

Der PCA9685 wird über eine I2C Schnittstelle angesteuert.

Du hast argumentiert, "daß das Kommunikationsprotokoll zwischen den
µCs für diese Anfänger i.d.R. die viel grössere Hürde darstellt."

Dann sollte man "diesem Anfänger" doch wohl eher nicht diesen Chip 
empfehlen, oder?

Ich würde ihm allerdings schon dazu raten, sich mit dem I2C Protokoll zu 
beschäftigen, denn ich finde den von Dir vorgeschlagenen Chip für diesen 
Fall sehr gut. Ich habe ja auch nicht von I2C abgeraten.
Gast #5088207
Lesenswert?

Stefan U. schrieb:
> Dann sollte man "diesem Anfänger" doch wohl eher nicht diesen Chip
> empfehlen, oder?

Warum? für den PCA gibt's das Kommunikationsprotokoll über I2C fix und 
fertig implementiert als Arduino-Bibliothek. Ziemlich Deppen- und 
Maker-Sicher.

https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library


Bei einer eigenen Multi-µC-Lösung muss er selber ein Protokoll entwerfen 
und implementieren.
Gast #5088246
Lesenswert?

> für den PCA gibt's das Kommunikationsprotokoll über I2C fix und
> fertig implementiert als Arduino-Bibliothek.

> Bei einer eigenen Multi-µC-Lösung muss er selber ein Protokoll
> entwerfen und implementieren.

Ich stimme Dir zu.
Gast #5088309
Lesenswert?

>Bei einer eigenen Multi-µC-Lösung muss er selber ein Protokoll entwerfen
>und implementieren.

Wahrscheinlich ein kaum zu lösende Aufgabe,
1
#include <Servo.h>
2

3
#define PIN_SERVO0 8
4
#define PIN_SERVO1 9
5
#define PIN_SERVO2 10
6

7
Servo servo0;
8
Servo servo1;
9
Servo servo2;
10

11
void setup()
12
{
13
  servo0.attach(PIN_SERVO0);
14
  servo1.attach(PIN_SERVO1);
15
  servo2.attach(PIN_SERVO2);
16
    
17
  Serial.begin(115200);
18
  while (!Serial) {
19
    ; // wait for serial port to connect. Needed for native USB port only
20
  }
21
}
22

23
uint8_t getChar()
24
{
25
  while (Serial.available() == 0);
26
  return Serial.read();
27
}
28

29
void loop()
30
{
31
  uint8_t c = 0;
32
  uint8_t servoNumber;
33
  uint8_t servoVal;
34

35
  do
36
  {
37
    c = getChar();
38
  } while (c != 's');
39
  
40
  servoNumber = getChar();
41
  servoVal    = getChar();
42

43
  if(servoNumber==0) servo0.write(servoVal);
44
  if(servoNumber==1) servo1.write(servoVal);
45
  if(servoNumber==2) servo2.write(servoVal);
46

47

48
}
Gast #5088405
Lesenswert?

Ähnlich simpel ist auch meine Lösung mit dem ATtiny, die wire Library 
von Arduino macht es einem leicht:
1
Wire.beginTransmission(0x1E); // adresse
2
Wire.write(94); // erster Servo in die Mitte
3
Wire.write(94); // zweiter Servo in die Mitte
4
Wire.write(125); // dritter Servo nach rechts
5
Wire.write(62); // vierter Servo nach links
6
Wire.endTransmission();
Gast #5088791
Lesenswert?

Eine Korrektur:
1
Wire.beginTransmission(0x0F); // 7bit Adresse
2
Wire.write(94); // erster Servo in die Mitte
3
Wire.write(94); // zweiter Servo in die Mitte
4
Wire.write(125); // dritter Servo nach rechts
5
Wire.write(62); // vierter Servo nach links
6
Wire.endTransmission();

Ich habe den Schaltplan schön gezeichnet:
http://stefanfrings.de/servocontroller/schematics.png

Die Firmware kannst du als hex File runterladen.
http://stefanfrings.de/servocontroller/ServoController.zip

Du wirst noch einen ISP Programmieradapter brauchen. Zur Not eignet sich 
dazu ein Arduino mit dem ISP Sketch.

Ich würde die Variante mit ATtiny2313 bevorzugen, da dieser mehr Pins 
zur Adress-Codierung bereit stellt.
#5190914
Lesenswert?

// hier mal das ganze über den Timer. es können alle Pins verwendet 
werden.
String cmd="";
String sservo="";
char fbuffer[100];
unsigned int xxx=123;
unsigned int yyy=00;

#define IndexMax 6                          // Anzahl der servos
#define ServoMIN 500                       // Mittelwert des Servos in 
usec
#define ServoMAX 2500                       // Mittelwert des Servos in 
usec
#define ServoHome 1500                      // Mittelwert des Servos in 
usec
#define INTERVAL 20000                      // 20ms
//int S[IndexMax]={A3,A2,A1,A0,A4,A5};          // Pins für die Servos;
int S[IndexMax]={A3,A2};          // Pins für die Servos;
volatile int W[IndexMax];                            // Impulswerte der 
Servos in usec
volatile int  Index=0;                      // Zeiger auf Servo/Daten
volatile int  Index1=0;                     // Zeiger auf Servo/Daten

void Servo_init(void)
{
  int i;
  for (i=0;i<IndexMax;i++)
    {
    pinMode(S[i], OUTPUT);    // Pins als Ausgang definieren
    W[i]=ServoHome;           // vorbelegung der Servos
    sprintf(fbuffer, "Pin %i ist Servo %i",S[i],i);
    Serial.println(fbuffer);
    }

// Timer 1
  noInterrupts();           // Alle Interrupts temporär abschalten
  TCCR1A = 0;               // Register löschen
  TCCR1B = 0;               // Register löschen
  TCNT1 = 100000;           // Timer vorbelegen, beliebig
  TCCR1B |= (1 << CS11);    // 8 als Prescale-Wert = 500nsec Takt
  TIMSK1 |= (1 << TOIE1);   // Timer Overflow Interrupt aktivieren
  interrupts();             // alle Interrupts scharf schalten
}

ISR(TIMER1_OVF_vect)               // Interrupt Timer/Counter1 OVERFLOW
{
int i,T1wert;
  digitalWrite(S[Index1],LOW);     // aktuellen Servo abschalten
  if (Index<IndexMax)
     {
     digitalWrite(S[Index],HIGH);  // neuen Servo anschalten
     T1wert=65535-(W[Index]*2);    // anzahl der Takte berechnen
     Index1=Index;
     Index++;
     }
  else
     {
     Index=0;
     T1wert=65535-(INTERVAL*2);
     for (i=0;i<IndexMax;i++) T1wert+=(W[i]*2);
     }
  TCNT1H = (uint8_t) (T1wert >>8 );  // Timer mit neuer Impulszeit 
starten
  TCNT1L = (uint8_t) (T1wert     );
}

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println("Starting...");
  Servo_init();
}


void loop() {

int i;

  if (Serial.available() > 0)
  {
    cmd = Serial.readStringUntil(10);
    Serial.println(cmd);




    //Serial.print("Moving Servo to: ");
    //Serial.println(cmd);

    //2-120
    sservo = cmd.substring(0,1);
    cmd = cmd.substring(2);
    yyy=sservo.toInt();
    xxx=cmd.toInt();

    if ((xxx >= ServoMIN) && (xxx <= ServoMAX) && (yyy < IndexMax))
      {
      sprintf(fbuffer, "Moving servo %2d to %3d test",yyy, xxx);
      Serial.println(fbuffer);
      W[yyy]=xxx;
      }
    else
      {
      Serial.println("Value out of range");
     }
  }
}
Gast #5190979
Lesenswert?

> hier mal das ganze über den Timer

Toll, noch eine Variante davon. Und so Zeitnah. Wie steuerst du damit 32 
Servos?

Mein vor 3 Monaten genannter Vorschlag konnte schon 10 Servos ansteuern 
und lässt sich immer noch ganz leicht auf 20 erweitern.

Wir waren aber schon längst bei der Erkenntnis, dass dieser PCA9685 die 
bessere Wahl ist.
#5191486
Lesenswert?

Jobst M. schrieb:
> Der 4017 ist der richtige Baustein.

Nö.
Der kann nur 9 Servos ansteuern und läßt sich nicht kaskadieren.

Besser nimmt man daher 4 Stück 74HC164 kaskadiert. Die brauchen 
insgesamt 2 Outputs des MCs, wovon der Takt ein Compare-Output ist, 
damit jitterfrei.
Der Timerinterrupt lädt dann den nächsten Servowert aus nem 32Byte-Array 
nach und schiebt bei jedem ersten Wert das 1Bit raus.
#5191871
Lesenswert?

Mit einem ATMega328 kannst Du keine 32 Servos direkt ansteuern, 8 via 
Multiplexing aber schon:
https://www.mikrocontroller.net/articles/Modellbauservo_Ansteuerung#Signalerzeugung_f.C3.BCr_mehrere_Servos_mittels_Timer_.28C.29

Das kannst Du dann mit einem 24 Channel Breakout-Board

https://eckstein-shop.de/Adafruit-24-Channel-12-bit-PWM-LED-Driver-SPI-Interface?curr=EUR&gclid=EAIaIQobChMIpd74g9WY1wIVyqgYCh0UQQCzEAQYAiABEgIm5_D_BwE

kombinieren, denn der 328 hat bei der Ansteuerung per SPI sowieso nix zu 
tun. Zusammen mit einem Arduino Nano Clone liegst Du dann bei unter 20 
EUR.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren