/*
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;
}
}
}
Vorgefundene Kodierung: UTF-8 | 0
|