Servosignal von Timer1 KanalA und ~B zeigt Ausfälle
Hallo C-Spezialisten,
heute kommt wieder (m)ein C-Spezialproblem, das ich seit vier Tagen nicht lösen kann. Tests auf zwei verschiedenen Targets und Controllern (Steckbrett mit mega328/20MHz und RNControl mit m1284/20MHz) bringen gleiche Ergebnisse - soweit ein eher zufällig aussehender Fehler dieses Urteil zulässt. Recherchen zum Stackpointer bei AVR-GCC sagten mir, dass ich den SP nicht anfassen muss/müsste. Die Dokumentationen der Controller zu den Timern brachten mir auch keinen Fortschritt. Nun bleibt mir nur noch diese Anfrage.
Aufgabenstellung (Fernziel):
i) Ein Controller soll sechs bis sieben Servos treiben, maximal 8
ii) Normale Servos (bzw. die billigen Analogmodelle)
iii) Die Verstellgeschwindigkeit MUSS variabel sein
iv) Die Servos müssen unabhängig voneinander fahren
v) Mein Standardtimer 20kHz toggelt eine "Herzschlag"-LED
Lösungsweg:
v) Timer1 (16 bit) erzeugt mit Kanal-A/OCR1A ein Achtel (1/ 8 der Servoperiode
OCR1A ist üblicherweise 800 - ergibt insgesamt dann ca. 20 ms Periode
vi) Timer1 erzeugt mit Kanal-B/OCR1B die Servorampe (=Verstellwinkel)
OCR1B wird zwischen 300 und 600 schwanken - ca. 1 bis 2 ms,
getestet wurde erfolgreich Werte zwischen 280 und 620
vii) Ein Feld mit 8 int16-Werten enthält die gewünschten Rampenlängen
viii) Die Rampenlänge OCR1B ist deutlich kürzer als OCR1A - s.o.
ix) AVR-STudio4
Realisierung (derzeit vorwiegend mit konstantem Schaltpin = Kontroll-LED)
x) Timer1_A startet im ersten Durchlauf den Servo-Pin/LED und
startet Timer1_B = enable CompareB Match : TIMSK1 |= (1<<OCIE1B)
xi) Timer1_B löscht in seinem ersten Interrupt den Servopin
und disabled den eigenen Interrupt : TIMSK1 &= ~(1<<OCIE1B)
xii) Testweise wird OCR1B in den Grenzen rauf- und runtergefahren.
xiii) Ein Test mit OCR1A = 6400 <=> der Timer1_A erzeugt die 20-ms-Periode in einem Durchlauf und startet NUR einmal den Timer1_B für eine auf-und-ab-gehenden OCR1B-Rampe - läuft bisher störungsfrei. Der zugehörige Servo lässt sich mit Stellgeschwindigkeit von "normal" bis ca. 1/4 der üblichen Geschwindigkeit praktisch ruckelfrei fahren. Langsamere Schwenkgeschwindigkeiten erzeugen mehr oder weniger deutliches Ruckeln. Dabei dreht der angeschlossene Servo mustergültig. Es scheinen nach längerer Laufzeit - mehreren Minuten - auch Fehler aufzutreten.
xiv) Das "Servosignal" wird im Oszilloskop beobachtet und durch das Leuchten der LED angezeigt.
xv) Controller und Servo werden getrennt versorgt, GND´s sind verbunden.
Später soll ein umlaufender Pointer die verschiedenen Werte der Rampenlänge als OCR1B nehmen und damit die zugehörigen Pinne schalten. Derzeit wird NUR die Kontroll-LED geschaltet.
Beobachteter Fehler:
xxi) Beim Betrieb des Timer1_A mit 20ms und nur einer Rampe - also das Servo-Standardsignal läufts problemlos.
xxii) Der Betrieb mit dem Timer1_A mit 2ms und der darin erzeugten Rampe von 1 bis <2 ms läuft teilweise minutenlang.
xxiii) Die erzeugte Rampe läuft programmgemäß rauf und runter.
xxiv) Meist fällt die erzeugte Rampe nach (dem ersten) Auf-Ab-Zyklus praktisch aus, es ist nur ein mikrosekundenlanger Peak im 2ms-Takt zu sehen.
xxv) Seltsameweise kommt die Rampe programmgerecht nach einiger Zeit wieder . . .
xxvi) Wiederholen der Initialisierung des Timer1 - am Ende eines Auf-Ab-Zyklus brachte weniger Störungen - aber keine Störungsfreiheit.
Wer es bisher gelesen hat - danke für die Aufmerksamkeit.
Frage: Wo könnte der Fehler (noch) liegen?
Danke für Eure Geduld, Aufmerksamkeit und Hilfe.
Zur Erläuterung noch der Code (einige Kommentarzeilen, Portinitialisierungen etc. rausgekürzt)
Auszug Hauptprogramm
Code:
// ============================================================================== =
// === HAUPTProgramm =========================================================== =
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int main(void)
// ......
// - - - - - - - - - - - - - - -
// Ports+Pins als Ein- (0) oder Ausgänge (1) konfigurieren, Pull Ups (1) aktivieren
// A = Ausgang, E = Eingang ohne , EU = Eingang MIT PullUp
DDRB = 0b11111111; // siehe aktuell oben
PORTB = 0b00000000; // und Port/Pull Ups (1) aktivieren
//
DDRC = 0b11111111; // PC0 .. 6 , kein PC7-Pin bei m168/328 in THT
PORTC = 0b00000000; // PC0 und ~1 sind ADC-Eingänge ##>> OHNE Pullup !!
//
DDRD = 0b11111110; // -> siehe unter DDRB
PORTD = 0b00001001; // Pull Ups aktivieren, NICHT bei extINT0/~1
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ......
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TC1TMR_init(); // Init Tmr/Cntr1 für PWMs/Servo ~tmr~
// - - - - - - - - - - - - - - -
Izeit_1 = 20000; // Der ZeitHorizont für ISR(TIMER2_COMPA_vect)
Izthrznt = 20000; // Der ZeitHorizont für ISR(TIMER2_COMPA_vect)
Isecundn = 1; // Sekundenzähler, max 9 Stunden - NUR hier nullen
TC2TMR_init(); // Init Timer/Cntr2-Interrupt 20 kHz/50 µsec ~tmr~
// ISR gibt auf PC3 ein Taktsignal aus
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sei(); //Globalen Interrupt freigeben
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
init_USART0(MYUBRR); // USART0 initialisieren m wählbarer Baudrate (s.o.) ~inf~
// ......
// ============================================================================== =
// Testabschnitt(e).
// ============================================================================== =
// ......
uart_puts("\tEs folgt Aufruf I2CTST01()\r\n");
I2CTST01(); //
// ......
// ===== Ende main
// ================================================================================
/* Es folgt der aktuelle Übersetzungskommentar:
Build started 19.10.2012 at 09:51:33
avr-gcc -mmcu=atmega328p -Wall -gdwarf-2 -std=gnu99
-DF_CPU=20000000UL -Os -funsigned-char -funsigned-bitfields
-fpack-struct -fshort-enums -MD -MP -MT R5Sv1.o
-MF dep/R5Sv1.o.d -c ../R5Sv1.c
avr-gcc -mmcu=atmega328p -Wl,-Map=R5Sv1.map R5Sv1.o -o R5Sv1.elf
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature
R5Sv1.elf R5Sv1.hex
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load"
--change-section-lma .eeprom=0 --no-change-warnings
-O ihex R5Sv1.elf R5Sv1.eep || exit 0
avr-objdump -h -S R5Sv1.elf > R5Sv1.lss
AVR Memory Usage
----------------
Device: atmega328p
Program: 1566 bytes (4.8% Full)
(.text + .data + .bootloader)
Data: 408 bytes (19.9% Full)
(.data + .bss + .noinit)
Build succeeded with 0 Warnings...
============================================================ */
Auszug header
Code:
// ============================================================================== =
// ============================================================================== =
// ##### Variablenliste, global #####
// ============================================================================== =
// ============================================================================== =
//
#define SetBit(ADDR,BIT) ((ADDR) |= (1<<(BIT))) // Setzt Bit
#define ClrBit(ADDR,BIT) ((ADDR) &= ~(1<<(BIT))) // Löscht Bit
#define ToggleBit(ADDR,BIT) ((ADDR) ^= (1<<(BIT))) // Toogelt Bit
// - - - - - - - - - - - - - - - -
// ......
volatile int16_t Izeit_1; // Timer mit 20 kHz Wertbereich int16: 32.767
volatile int16_t Izthrznt; // Der zeitliche Horizont, z.B. 20000 für 1 sec
volatile int16_t Isecundn; // Sekunden Programmlaufzeit, 32.767 sec sind ...
// ============================================================================== =
// ============================================================================== =
Auszug Testroutine:
Code:
// ============================================================================== =
// == Routine zum "Normalbetrieb"
// ============================================================================== =
void SrvTST_03(void) // Servo Testbetrieb, übernommen aus :
// war SrvTST_01 in ~r1n01d
{ //
uint16_t ilng; // Siehe for-Loops in RCBB == 25 und RCBB == 6
uint16_t nochnl; // for-Loop "nochnloop"
uint16_t PWMmin, PWMmax; // PWM-Grenzen
uint16_t srvwt; // wait-Millisekunden für Servoloop
// - - - - - - - - - - - - - - -
PWMmin = 300; //
PWMmax = 640; //
srvwt = 25; //
// - - - - - - - - - - - - - - -
uart_puts ("\tStart Servo-Test03 ~r1n01\r"); //
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
while (1) //
{ //
for (ilng = PWMmin; ilng <= PWMmax; ilng++)
{ //
cli(); // ###
OCR1B = ilng; //
sei(); // ###
waitms ( srvwt); //
} // ### Ende for (ilng = ...
// - - - - - - - - - - - - - - -
for (ilng = PWMmax; ilng >= PWMmin; ilng--)
{ //
cli(); // ###
OCR1B = ilng; //
sei(); // ###
waitms ( srvwt); //
} // ### Ende for (ilng = ...
} // ### Ende while (1)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uart_puts ("\tEnde Servo-Test03 ~r1n01\r"); //
return; // ### Ende von void SrvTST_03(void)
} //
// ============================================================================== =
Auszug Timer, Initialisierung-en und ISR
Code:
// ============================================================================== =
// == Timer Aufgabe: Servo mit Soft-PWM ansteuern auf wählbarem Port
// - - - - - - - - - - - - - - - -
void TC1TMR_init(void) // Init Timer/Counter 1 für 2 ms Servoperiode
{ //
TCCR1B |= (1<<WGM12); // WGM12 => CTC, TOP = OCR1A S133
TCCR1B |= (1<<CS11)|(1<<CS10); // CS11/10 <=> clk/64 => 312 500 Hz S134
OCR1A = 800; // Mit OCR1A = 6250 => alle 20 ms ein Interrupt
TIMSK1 |= (1<<OCIE1A); // Tmr/Cntr1 Oput CompA Mtch intrrpt enabled
}
// ============================================================================== =
// ============================================================================== =
// === Nicht unterbrechbare ISR für TIMER1_COMPA_vect ====================== =
ISR(TIMER1_COMPA_vect) // VECTOR 8 S 65
{ //
SetBit (PORTB, 2); // LED/PB2 high, wird von Timer1_COMPB gelöscht
TIMSK1 |= (1<<OCIE1B); // Tmr/Cntr1 CompB Match interrupt enabled
} //
// ============================================================================== =
// ============================================================================== =
// === Nicht unterbrechbare ISR für TIMER1_COMPB_vect ====================== =
ISR(TIMER1_COMPB_vect) // VECTOR 9 S 65
{ //
ClrBit (PORTB, 2); // LED/PB2 gelöscht
TIMSK1 &= ~(1<<OCIE1B); // Tmr/Cntr1 CompB Match interrupt disabled
} //
// ============================================================================== =
// ============================================================================== =
// === Initialisierung fuer Timer2 mega168 ===================================== =
void TC2TMR_init(void) // Init Tmr/Cntr 2, 8-Bit auf 20 kHz = 50 µs
{ //
TCCR2A |= (1<<WGM21); // Timer im CTC-Mode, Top=OCR2A doc S 157
TCCR2B |= (1<<CS21); // Prescaler 1/8 / Clock <- CPU doc S 158
OCR2A = 124; // Preset 124 für 50µs bei 20Mhz
TIMSK2 |= (1<<OCIE2A); // Tmr/Cntr2 CompareA interrupt enabled
} //
// ============================================================================== =
// ============================================================================== =
// === Nicht unterbrechbare ISR für timer2 ===================================== =
// Routine zählt hoch im Takt 20 kHz = 50 µs. Der Zählerwert wird von den ISR für
// EXT_INT0 und -INT1 ausgelesen und den Werten Iz_yseci zugewiesen
ISR(TIMER2_COMPA_vect) // Vektor 7
{ //
if (Izeit_1) //Interrupt-Timer = 1 ... 20 000 ... (1 sec blink)
{ //
Izeit_1 --; // ###>>> Izeit_1 ist aktuell int16_t ==>>
// Izeit_1 bleibt bis 32000 in der int16-Grenze
} //
else // Eine Sekunde ist voll =>
{ //
Izeit_1 = 20000; // Rückstellen auf 20000
Isecundn ++; // Sekundenzähler hochtackern, max 9 Std
ToggleBit (PORTC, L1g); // LED/PB2 gelöscht
} // Ende if (Izeit_1 < Izthrznt)
return; //
} //
// ============================================================================== =
// ============================================================================== =
// ============================================================================== =
//### Programm pausieren lassen !! Der Pausenwert ist nur experimentell !
void waitms(uint16_t ms)
{
for(; ms>0; ms--)
{
uint16_t __c = 4000;
__asm__ volatile (
"1: sbiw %0,1" "\n\t"
"brne 1b"
: "=w" (__c)
: "0" (__c)
);
}
}
// ============================================================================== =
// ============================================================================== =
// ===== ENDE Subroutinen ================================================== =
// ============================================================================== =
Zehn Servos auf einen Streich
Hallo Alle,
nochmal danke für eure Hilfen. Das Servoprogramm läuft bei der Erprobung einwandfrei mit den zehn Servos (Anm.: teils getestet mit realen Servos, teils mit Oszilloskop - es fehlt seit Tagen eine Hardwarelieferung). Die aktuell erzielbare Auflösung ist hoch und ...
Zitat:
Zitat von
Klebwax
... Bei einem Servo ist, in Grenzen natürlich, das Puls/Pause Verhältnis unerheblich ...
... im aktuell geschalteten Fall sieht es so aus, dass eine Periodenzeit von 22 ms im Vergleich mit einer Periode von 20 ms bei gleicher Rampenzeit die gleiche Servostellung bewirkt. Geprüft einfach durch Augenschein mit Peilung über den Servohebel gegen markierten Untergrund.
Mal ein paar Eckpunkte:
Timer1 auf 312,5 kHz (20 MHz mit Prescaler 64)
ISR (TIMER1_COMPA_vect) durch OCR1A = 800 alle 2,56 ms. Ein umlaufender Servopointer {1 bis 10} setzt den entsprechenden Servopin.
ISR (TIMER1_COMPB_vect) wird in TC1_CMPA mit OCR1B gestartet und erzeugt die Rampe, der Servopointer dient zum Löschen der Rampe.
Die zuständigen Rampenwerte liegen in einem Feld Srv_tm [12] (*ggg* ich weiß, das sind zu viele Elemente, aber ich nehme der Übersichtlichkeit halber nur die Elemente ~[1] bis ~[10] für Servo1 bis Servo10 und habe das zwölfte für "sonstige" Zwecke" frei).
Der Schwenkbereich liegt bei (m)einem Billigservo bei fast 180° - mit OCR1B-Werten zwischen 120 und 780. Für die "allgemeine" Anwendung wirds auf 200 bis 700 beschränkt. Das entspricht einer theoretischen Auflösung von weniger als einem Drittel Grad (rund 17 Gradminuten).
Zitat:
Zitat von
Klebwax
... Ich würde sogar versuchen, die Periodendauer soweit als möglich zu reduzieren ...
Nun habe ich ja ein reproduzierbar getaktetes/taktbares Modul, da kann ich mich demnächst damit spielen. Die ersten Ergebnisse mit 8 oder zehn Servos und entsprechend acht oder zehn TC1_CMPA-Schleifen sehen schon gut aus. Das "... demnächst damit spielen ..." wird etwas dauern, weil ich ziemlich hinter meinem Zeitplan herhinke.
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
... "... demnächst damit spielen ..." wird etwas dauern, ... ziemlich hinter meinem Zeitplan ...
Trotzdem habe ich eine neue Platine gebaut (bauen müssen) - einige Funktionen fehlten mir. Ich glaube Projekte mit dem Flag "erledigt" halten sich manchmal besser als andere . . .
......Anhang 24353
Kurz ein paar Details zum Bild :
1 Energieeinspeisung VBATT 8 V .. 20 V mit Verpolungschutz
die grüne LED zeigt Spannung > ca. 2V auf Vcc
SpannungsIC ist ein 78S05 mit etwas ELKO und KerKo
2 PORTB mit GND (9) und Vcc (10)
3 ISP, Vcc rechts oben über Jumper wählbar
4 2x2 – GND (rechts) und Vcc (links)
5 UART0 – nur RX, GND, TX
6 ISP, links daneben Pegelanzeige mit LED, leuchtet wenn GND an /RES
7 UART1 wie 5
8 PORTA0 .. ~A3, dazu GND und Vcc
9 Energieeinspeisung Servos Vs
10 Jumper zur Strommessung von 9 zu Servosteckern (15)
zur Verbindung von 9 zu den Servosteckern MUSS ein Jumper rechts
sitzen, Jumper links verbindet die Controllerversorgung
nach dem 78S05 mit Servo-Vcc
11 Heartbeat (gnLED, Timer2, 1 Hz) und Info-LED rt
12 Resettaster
13 78S05
14 2 Taster zur freien Verfügung, auch interruptfähig PD6, PD7
15 10 Servostecker, von links: SIG, Vs, GND
16 Spannungsanzeige > 2V auf Vs
Die internen Leitungen sind meist Kupferlackdraht mit „lötbarer“ Beschichtung. Der Quarz und die 100nF-Abblockungen liegen unter dem Controller, L 10µH für AVcc liegt mit anderem SMD-Hühnerfutter – z.B. 1k für LED bei (1) – auf der Lötseite. Leer – ohne Controller – Stromaufnahme mit LED (1) und gnLED (für Heartbeat) auf (4) 20,7 mA, ohne „Heartbeat“ etwas über 10 mA. Mit m1284 ungeflasht ca. 35 mA, Anfahr-Anzeige auf dem DMM >> 70 mA.
Nach Setzen der Fuses und disablen der JTAGEN-Fuses wurden ca. 48 mA gezogen.
Nun sieht die Hardware (für mich) praxisgerecht aus - und mit den zwei, drei Details, die eine breite Spielwiese bieten (na ja, der Baustil l heißt eher Plattenbau und nicht Platinenherstellung. Rein vom Stil und der Fertigungstechnologie her).