Source file: /~heha/basteln/m/servo/servo.zip/servo2/main.cpp

/*
Quasianaloge Servosteuerung für zwei Türen
mit analog einstellbaren Anschlägen
und Nachführung der Türposition im EEPROM

Hardware: ATtiny13
Pin	Port	Funk.
1	PB5	!RESET
2	PB3		Betätigung B
3	PB4		Betätigung A
4	GND
5	PB0	OC0A	Anschluss Servo A
6	PB1	OC0B	Anschluss Servo B
7	PB2	ADC1	Einstellregler
8	Ucc

Beschreibung Servo MG90S:
Periodendauer 20 ms (50 Hz)
Puls: positiv 1 ms (Linksanschlag), 2 ms (Rechtsanschlag), Totbereich 5 µs
Anschluss: PWM = oragne, Ucc = 5 V = rot, GND = braun
Stellgeschwindigkeit: 0,1s/60°, Drehmoment 0,2 Nm
DIE PULSANGABEN SIND NACHWEISLICH FALSCH!
Richtig ist: Standard-Servo mit 0,5 .. 1,5 .. 2,5 ms Pulsdauer bei 50 Hz

Funktion:
Der Tastendruck (nach Low) schließt oder öffnet die Tür.
Betätigung während der Bewegung stoppt die Tür
und dreht die nächste Bewegungsrichtung um.
Mit einigermaßen Beschleunigungs- und Bremsfunktion.
Vorbild: Eintasten-Rollladensteuerung oder Garagentorsteuerung.

Einsatzerfahrung:
Die Inbetriebnahme mit dem Oszi ist erforderlich, da die Servos
nicht ratiometrisch zur Pulsfrequenz arbeiten, sie messen nur die Pulslänge.
Beim Einschalten springen sie Servos, obwohl die Pulsausgabe des Controllers
sofort korrekt erscheint.
Wie zu erwarten arbeiten die Servos ziemlich ruckelig und geräuschvoll.
Das Absenken der Speisespannung (um das Motordrehmoment zu senken)
bringt bloß noch die Servos noch mehr durcheinander.
Stromaufnahme der Servos bei lastfreier Verstellung: 200 mA.
Die Vorgabewerte für a, e, vmax und amax ergeben eine Durchlaufzeit von 3 s.

Um nur /ein/ Potenziometer zur Endwertkalibrierung verwenden zu können,
wird dieses wie folgt abgefragt:
* Die jeweils zuletzt gedrückte Taste weist den Servo zu
* Ein „gültiger“ Analogwert wird erst nach Durchkreuzen der Mittelstellung angenommen
  (Ein Inkrementalgeber wäre zweifellos besser, lohnt sich aber erst bei noch mehr PWM-Ausgängen)
* Während der Bewegung: Einstellen der Geschwindigkeit
* Stehend: Stetiges Einstellen der Endlage (je nach „innerer Laufrichtung“)
* Nur nach dem Flashen des EEPROM (noch kein Tastendruck erfolgt)
  bewirkt das Poti die Einstellung der PWM-Frequenz
Die Beschleunigung ist nicht einstellbar.
(Das könnte man realisieren, wenn der Servo zwischen den Endlagen steht.
Das Feedback ist dabei schwierig zu gestalten, hm.)
Zeitversetztes Betätigen einer Doppeltür mit /einer/ Taste erfordert Software-Änderung.

Stellpräzision: 203 Schritte; PWM-Stufung: 10 µs; PWM-Jitter: 0 (Hardware-PWM)
Interne Positionsdarstellung: 27 = Anfang, 128 = Mitte, 229 = Ende
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <avr/fuse.h>
#include <avr/signature.h>	// gepatcht: "const" weg!

#define A 27	// Anfang Stellweg
#define M 128	// Mitte Stellweg
#define E 229	// Ende Stellweg

FUSES={
 0x69,	// EESAVE aktivieren? („make flash“ löscht EEPROM nicht), Oszillator 4,8 MHz
 0xFF,	// unverändert (SPI-Programmiermöglichkeit bleibt erhalten)
};

typedef unsigned char byte;
#define NOINIT __attribute__((section(".noinit")))

struct servo{
 byte a,e;	// Anfangs- und Endlage, 50 ≤ a < e ≤ 255
 byte x;	// Position, 27 = 0,5 ms, 229 = 2,5 ms, Periode 20 ms (typ. für 8 Servos am 4017)
 byte vmax;	// Geschwindigkeitsvorgabe (einstellbar)
 byte amax;	// Beschleunigungsvorgabe (nicht mit Poti einstellbar)
 void key();		// Aufruf bei festgestelltem Tastendruck
 void change(char);	// Bei festgestellter Wertänderung, vom A/D-Wandler oder Inkrementalgeber
 void tick();		// nach 20 ms (50 Hz) neue Position ermitteln
 byte f;	// Bit 0: Richtung der Verfahrbewegung, Bit 1: In Bewegung
 byte v;	// aktuelle Geschwindigkeit
 byte xl;	// Bruchteile der Position
private:
 void calc_v(unsigned);
};

struct eedata{	// Vom EEPROM „gebackupte“ Daten
 servo s[2];
 byte i;	// „aktiver“ Kanal (für die Wirkung des Potenziometers an ADC1)
 byte cal;	// Oszillator-Kalibrierbyte
};

EEMEM eedata e={	// Vorgabewerte nach dem Programmieren des Chips
 {{A,E,M,128,4},
  {A,E,M,128,4}},
 0xFF,
 0xFF		// Kode für unprogrammiertes OSCCAL, Testwert hier: 0x45
};

eedata d NOINIT;

register byte adcprev asm("r2");	// vorhergehender A/D-Wert
register bool adcactive asm("r3");	// A/D-Wert verstellt irgendetwas

// nominell mit 50 Hz × 8 = 400 Hz
EMPTY_INTERRUPT(TIM0_OVF_vect)

// Asynchrones Update: Wartet nicht sondern stößt EEPROM-Schreibvorgang nur an
static void eeprom_update(const void*ram, void*eeprom, size_t len) {
 if (EECR&0x02) return;
 const byte*src=(const byte*)ram;
 byte*dst=(byte*)eeprom;
 for (;len;src++,dst++,len--) if (eeprom_read_byte(dst)!=*src) {
  EEDR=*src;	// Adresse steht noch in EEAR
  cli();
  EECR|=0x04;
  sei();
  EECR|=0x02;	// immer noch mit gesperrten Interrupts (wird zu SBI compiliert)
  return;
 }
} 

int main() {
 CLKPR =0x80;
 CLKPR =0x02;	// 4,8 MHz / 4 = 1,2 MHz, per OSCCAL 819,2 kHz
 MCUCR =0x20;	// Sleep aktivieren
 DDRB  =0x03;	// PWM als Ausgänge
 PORTB =0x18;	// Pullups für Tasten
 TCCR0B=0x02;	// loszählen mit Vorteiler 8
 TIMSK0=0x02;	// Interrupt bei Überlauf
 DIDR0 =0x24;	// Analogeingang (und !RESET) nicht abfragen
 ACSR  =0x80;	// Analogvergleicher aus
 ADMUX =0x21;	// 8 Bit, ADC1 = PB2
 adcactive=false;
 eeprom_read_block(&d,&e,sizeof d);
 if ((byte)~d.cal) OSCCAL=d.cal; else d.cal=OSCCAL;
 byte keys=PINB;
 sei();
 for(;;) {
  sleep_cpu();	// der einzige Unterbrecher ist der 400-Hz-Zeitgeber
  OCR0A=d.s[0].x+255-E;
  TCCR0A=0x83;	// nach dem nächsten sleep_cpu() erscheint die Startflanke an A
  
  sleep_cpu();
  TCCR0A=0x80;	// Nur noch Endflanke, keine weitere Startflanke generieren

  sleep_cpu();
  OCR0B=d.s[1].x+255-E;
  TCCR0A=0x23;
  
  sleep_cpu();
  TCCR0A=0x20;
  if (!((d.s[0].f|d.s[1].f)&2)) eeprom_update(&d,&e,sizeof d);

  sleep_cpu();
  d.s[0].tick();

  sleep_cpu();
  d.s[1].tick();

  sleep_cpu();
  ADCSRA=0xC1;		// A/D-Wandler starten
  
  byte adc=ADCH;	// Analogwert abholen
//  ADCSRA=0;		// A/D-Wandler stoppen
  if (adcactive) {
   char delta=adc-adcprev;
   if (delta>char(24)) delta=24;	// eingrenzen auf ±24
   if (delta<char(-24)) delta=-24;
   if (d.i<2) d.s[d.i].change(delta);	// Endlagen oder Geschwindigkeit verstellen
   else{
    byte b=d.cal+delta;
    if (b>=128) b=0;	// Unterlauf
    if (b>100) b=100;	// Überlauf
    OSCCAL=d.cal=b;	// OSCCAL und damit PWM-Frequenz einstellen (mit Oszi auf 50 Hz einstellen!)
   }
   adcprev+=delta;
  }else if (128-4<=adc && adc<128+4) {
   adcactive=true;
   adcprev=adc;
  }

  sleep_cpu();
  byte b=PINB;	// Tasten abfragen
  byte c=~b&keys;	// High-Low-Übergänge erfassen
  keys=b;
  if (c&0x10) {adcactive=false; d.s[d.i=0].key();}
  if (c&0x08) {adcactive=false; d.s[d.i=1].key();}
 }
} 
 
/* Objekt-Funktionen */

void servo::calc_v(unsigned d) {	// d = Entfernung zum Ziel
// Bremsweg nach Gaußscher Summenformel s = (v²+v)/2a
// Die Geschwindigkeit wird als 4*v verwendet.
// Daher wird die vorhandene Distanz d durch 2 geteilt.
 if (d>>1<(unsigned(v*v)+v)/amax) {	// abbremsen
  if (v>amax) v-=amax;		// minimal 1 belassen
 }
 else if (v<vmax) {		// beschleunigen
  if (vmax-v<amax) v=vmax;
  else v+=amax;
 }
}

void servo::tick() {
 if (f&2) {	// in Bewegung?
  unsigned pos=x<<8|xl;
  if (f&1) {	// rückwärts
   calc_v(pos-(a<<8));	// je nach Entfernung zum Ziel
   pos-=(unsigned)v<<2;
   if (byte(pos>>8)<=a) {
    pos=a<<8;
    f=0;			// Stop, nächste Richtung: vorwärts
    v=0;
    adcactive=false;
   }
  }else{	// vorwärts
   calc_v((e<<8)-pos);
   pos+=(unsigned)v<<2;
   if (byte(pos>>8)>=e) {
    pos=e<<8;
    f=1;			// Stop, nächste Richtung: rückwärts
    v=0;
    adcactive=false;
   }
  }
  x=pos>>8;	// ablegen
  xl=pos;
 }
}
  
void servo::key() {
 if (f&2) {	// in Bewegung?
  v=0;		// Vollbremsung (abrupt)
  xl=0;		// keine Subschritte
  f=f&1^1;	// Stop, nächste Richtung umkehren
 }else{
  f|=2;		// Bewegung starten
 }
 adcactive=false;
}

// delta nicht größer als ±24
void servo::change(char delta) {
 if (f&2) {	// in Bewegung?
  byte b=vmax;
  if (delta<0) {	// langsamer
   delta=-delta;
   if (b>byte(delta)) b-=delta; else b=1;	// nicht 0 oder negativ werden lassen
   if (v>b) v=b;	// anpassen
  }else{		// schneller
   if (255-b>delta) b+=delta; else b=255;	// nicht überlaufen lassen
   // v passt sich automatisch an, in calc_v()
  }
  vmax=b;
 }else{		// Stillstand
  if (f&1) {	// x sollte auf e stehen
   byte b=e+delta;
   if (b<=a) b=a+1;
   if (b>E) b=E;
   x=e=b;	// Position nachführen zur Kontrolle
  }else{
   byte b=a+delta;
   if (b<A) b=A;
   if (b>=e) b=e-1;
   x=a=b;
  }
 }
}
Detected encoding: UTF-80