PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] Verschiedene Taktraten im Atmega?



Don Simon
02.09.2018, 23:15
Hallo miteinander,

ich werke am "Robot Arm RA1-PRO V3" von Arexx mit eingebautem Atmega64 und externem 16 MHz Quarz, programmiere mit Bascom/Ponyprog/ISP.

Das Problem ist, dass das Programm mit 7,7 MHz läuft (über LED-Geblinke und "Wait" ermittelt),
die UART-Schnittstelle (Baud 9600 im Programm und im Bascom-Terminal) jedoch mit 16 MHz.
Im Programm kann ich nur eine Frequenz angeben, daher funktioniert das so nicht.

Wie kann das sein? Fuses sind auf externen Quarz gestellt.
Im Datenblatt hab ich gelesen, dass es eine "AVR Clock Control Unit" gibt, die wohl unterschiedliche (?) Taktraten weiterleiten kann.
In den Fusebits finde ich keine Einstellung für ein solches Register.
Wie komme ich auf den vollen Quarz-Takt innerhalb des Programms?

Danke und viele Grüße
Simon

Ceos
03.09.2018, 06:51
Eins vorweg, ich kenne mich mit Bascom nicht aus

aber was du beschreibst klingt extrem unwahrscheinlich, mir ist nicht bekannt dass man im atmega interne und externe clock gleichzeitig einsetzen könnte

wie genau hast du denn mit "geblinke und wait" die Geschwindigkeit gemessen?

Zumindest vom avr-gcc weis ich, dass wenn man die "F_CPU" definition nicht richtig setzt der wait nur mist macht.

Hier ein Artikel über F_CPU

https://rn-wissen.de/wiki/index.php?title=Hallo_Welt_f%C3%BCr_AVR_(LED_blink en)


Das Programm ist ausgelegt für eine Taktrate von 1 MHz. Wenn dein Controller (https://rn-wissen.de/wiki/index.php?title=Controller) mit einem anderen Takt läuft, dann hast du zwei Möglichkeiten:


Du lässt das Programm so, wie es ist. Dann blinkt die LED entsprechend schneller bzw. langsamer. Hast du deinen AVR zB mit 16 MHz getaktet, dann blinkt die LED mit 8 Hz.
Du passt die Taktfrequenz in der Quelle oder per Kommandozeile an. Für 8 MHz:


Das erklärt glaube ich ganz gut dein beschriebenes Erlebnis in dem Zusammenhang

oberallgeier
03.09.2018, 10:35
.. ich werke am "Robot Arm RA1-PRO V3" von Arexx mit eingebautem Atmega64 und externem 16 MHz Quarz, programmiere mit Bascom/Ponyprog/ISP .. Das Problem ist, dass das Programm mit 7,7 MHz läuft (über LED-Geblinke und "Wait" ermittelt) .. die UART-Schnittstelle (Baud 9600 im Programm und im Bascom-Terminal) jedoch mit 16 MHz. .. Im Programm kann ich nur eine Frequenz angeben, daher funktioniert das so nicht ..Dein ".. daher funktioniert das so nicht .." ist ja ne stolze aussage, leider völlig nichtssagend und daher unverständlich. WAS GENAU funktioniert nun nicht? Dazu kann ich mit der simplen Aussagen kein Urteil abgeben. WAS ist "das Programm" und welche Frequenz gibst Du (wo???) ein? Aber, eben ganz wichtig, WAS GENAU funktioniert nicht ?

Wie Ceos schon schrieb, kann man mit einer "wait"-Routine prächtig daneben liegen mit der Aussage zum Prozessortakt. Wenn da parallel noch ISRn laufen gibt das keine vernünftige Aussage über den tatsächlichen CPU-Takt. Nach der Anleitung V3-1113 zu Deinem Roboter sehe ich, dass der Controllertakt mit CPU=16000000UL angegeben ist. Der sollte also wirklich mit dem Quarztakt 16 MHz tickern.

Anmerkung, ohne Garantie, kein Regress möglich:
Bei den Fuses würde ich (ohne Garantie, kein Regress möglich) SUT-CKSEL (Start Up Table, ClocK SELect) setzen/controllieren auf
Ext. Crystal/Resonator High Freq.; Start-up time: 1K CK + 4 ms
Damit dürfte ein sauberes Arbeiten möglich sein.


.. Im Datenblatt hab ich gelesen, dass es eine "AVR Clock Control Unit" gibt, die wohl unterschiedliche (?) Taktraten weiterleiten kann ..Stimmt. Man kann für verschiedene, controllerinterne Baugruppen verschiedene Taktraten einstellen, für ADC, UART, etc, meinst Du das ?


.. In den Fusebits finde ich keine Einstellung für ein solches Register. ..Vielleicht musst Du dazu nochn bisschen AVR-Programmierung lernen? Für ADC, für UART etc zB, auch für Timer-Interrupts und so stellt man unterschiedliche Taktraten ein - aber dazu lies Dich mal in den entsprechenen Tutorials selber ein.


.. Wie komme ich auf den vollen Quarz-Takt innerhalb des Programms? ..DU ! nicht! (Na ja, Frequenz mit Messgerät direkt am Resonator abgreifen *gg*). Aber wenn Du die obige Einstellung für SUT-CKSEL benutzt dann können die verschiedenen Sektionen mit dieser Grundfrequenz arbeiten.


.. Das Problem ist, dass das Programm mit 7,7 MHz läuft (über LED-Geblinke und "Wait" ermittelt) ..Das scheint mir Dein wahrer Fehlschluss zu sein. Abgesehen davon - was meinst Du mit "..das Programm .. läuft mit 7,7 MHz.."? Nur mal Beispiele:
- Der direkte Call einer Subroutine, ein Maschinenbefehl, braucht auf Maschinenebene 1 Cyklus, bei Deinem mega64 also 1/16tel Millionstel Sekunde.
- Der relative Jump braucht schon 2 Cyklen.
- Der Return einer Subroutine oder einer Interruptsubroutine braucht vier Maschinenzyklen.
Will sagen: je nach der zum Ablauf benötigten oder gewünschten Operation werden die Befehle unterschiedlich schnell abgearbeitet, nicht jeder Maschinenbefehl ist in nur einem einzigen Maschinenzyklus erledigt. Trotzdem: das ist dann die maximal mögliche Geschwindigkeit.

Fazit: Lern mal gründlich die Programmierung des Controllers. Datenblatt, Tutorials und so. Und Zeit.

Don Simon
03.09.2018, 13:13
Erstmal vielen Dank für eure Hilfe!


.. wie genau hast du denn mit "geblinke und wait" die Geschwindigkeit gemessen? ..
In etwa so: "LED an, warte 10 Sekunden, LED aus". Dann messe ich die Zeit, während die LED leuchtet und rechne die ungefähre "wirkliche" Taktrate aus. Diese Frequenz gebe ich dann im Programm an.
Bei Bedarf wiederhole ich dasselbe noch mit 60 Sekunden. Die Prozedur mache ich oft bei Verwendung des internen Taktes, weil dieser sehr ungenau ist.

Danke für den Link. Bei mir ist es genau so. Ich setze vorher eine "F_CPU" Definition, damit das Programm "weiß", mit welcher Frequenz es später arbeitet. Allerdings macht bei mir die Wait-Funktion keinen Mist, sondern nur vorhersehbar andere Zeiten entsprechend der selbst vorgegebenen Quarz-Definition.




Dein ".. daher funktioniert das so nicht .." ist ja ne stolze aussage, leider völlig nichtssagend und daher unverständlich. WAS GENAU funktioniert nun nicht? Dazu kann ich mit der simplen Aussagen kein Urteil abgeben. WAS ist "das Programm" und welche Frequenz gibst Du (wo???) ein? Aber, eben ganz wichtig, WAS GENAU funktioniert nicht ? ..
Wie beschrieben habe ich festgestellt, dass der Takt des Programms ein anderer ist als der Takt der UART. Natürlich kann ich im Programm nur EINE Frequenz angeben, mit der das Programm arbeiten soll. Gebe ich 16 MHz an, bekomme ich von der seriellen Schnittstelle korrekte Zeichen am Terminal, gebe ich 7,7 MHz an, stimmen die "Wait"-Zeiten im Programm.
Mit "Programm" meine ich meistens den auszuführenden Code.


.. Wie Ceos schon schrieb, kann man mit einer "wait"-Routine prächtig daneben liegen mit der Aussage zum Prozessortakt. Wenn da parallel noch ISRn laufen gibt das keine vernünftige Aussage über den tatsächlichen CPU-Takt. ..
Dann muss das mein Fehler sein.


.. Bei den Fuses würde ich (ohne Garantie, kein Regress möglich) SUT-CKSEL (Start Up Table, ClocK SELect) setzen/controllieren auf
Ext. Crystal/Resonator High Freq.; Start-up time: 1K CK + 4 ms ..
Alle sechs Bits sind "nicht programmiert" (1en) und CKOPT ist programmiert (0), was laut Datenblatt dem externen Quarz mit hoher Frequenz und 1K CK + 4,1 ms Startzeit entspricht.


.. Man kann für verschiedene, controllerinterne Baugruppen verschiedene Taktraten einstellen, für ADC, UART, etc, meinst Du das ? ..
Ok, alles klar. Hab das nur vom Blockschaltbild her falsch interpretiert.


.. was meinst Du mit "..das Programm .. läuft mit 7,7 MHz.."? ..
Ich meine, dass der Programmcode mit der Taktrate 7,7 MHz abgearbeitet wird. Mir ist klar, dass einzelne Sprünge mehr Zyklen brauchen als andere.


Gedankenexperiment:
Nehmen wir an, ich definiere im Programmcode die 16 MHz als Taktfrequenz. Dann lasse ich alle Subs und Interrupts weg. Dann sollte ich doch korrekte Zeiten für eine allein stehende Wait-Routine bekommen?!

Ceos
03.09.2018, 13:31
LED an, warte 10 Sekunden, LED aus

Hört sich zumindest gut an, so sollten Fehler durch die Ausführungsgeschwindigkeit wie oberallgeier erklärt hat schonmal nicht ins Gewicht fallen ... definiere dein F_CPU doch mal mit 16000000 und poste hier ein stück des code und berichte mal die Zeit wie lange die LED leuchtet. Wenn da jetzt keine 10Skunden bei rauskommen, kann das nur 2 Gründe haben:

1.) du verwendest irgend eine Art von Bilbiothek die das Verhalten deiner Wait Funktion stört
2.) du verwendest eine Wait Funktion die nciht über Atmel Code gestuert wird

bei 1. würde ich behaupten, dass es entweder eine "loop() funktion ist die nicht oft genug aufgerufen wird, wobei cih keine Ahnung hätte warum oder eventuell in der Bibliothek der F_CPU Wert irgendwo ein 2tes Mal falsch definiert ist (Achte auf den Compiler Output)
bei 2. würde mich mal die quelle der wait funktion interessieren

oberallgeier
03.09.2018, 17:18
.. Gedankenexperiment .. Nehmen wir an, ich definiere im Programmcode die 16 MHz als Taktfrequenz .. lasse ich alle Subs und Interrupts weg .. ?!So ungefähr.

Vorschlag für nen Test, hier nur als Pseudocode

Quellprogrammstart:
- Initialisiere Ports (siehe Beispiel unten); es werden noch KEINE Interrupts initialisiert und schon garnicht freigegeben.

DAnach:
Schleife 1 (etwa zehn bis 20 mal)
- schalte Test-LED auf aus
- starte waitms 1000
- schalte Test-LED auf ein
- starte waitms
Schleife 1 Ende

Starte den Programmrest mit initialisierunge wie UART etc., Interrupts können freigegeben werden.

Gib mal einen TEsttext auf Uart aus.

Schleife 2 (etwa zehn bis 20 mal)
- schalte Test-LED auf aus
- starte waitms 1000
- schalte Test-LED auf ein
- starte waitms
Schleife 2 Ende

Vergleiche die Laufzeiten . . .

Hinterher allenfalls ärgern, nicht heulen. (hmmm - hoffentlich musste nicht auf mich schimpfen . . .)

Codebeispiel (wie es bei mir auf praktisch allen Controllern läuft)

// ================================================== =========================== =
// === HAUPTProgramm ================================================== ======== =
// Initialisierungen, Ausgabe des Identifizierungsstrings per UART
// Start des aktuellen Programms
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int main(void) //
{ //
uint8_t i; //
char abc[12]; // Übersetzungsfeld für Werteausgabe
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// === Grundlegende Initialisierungen der Hardware, Portdefinition -
// PCINT8, XCK0, T0, PB0 1 40 PA0, ADC0, PCINT0
// PCINT9, CLKO, T1, PB1 2 39 PA1, ADC1, PCINT1
// PCINT10, INT2, AIN0, PB2 3 38 PA2, ADC2, PCINT2
// PCINT11, OC0A, AIN1, PB3 4 37 PA3, ADC3, PCINT3
// PCINT12, OC0B, /SS, PB4 5 36 PA4, ADC4, PCINT4
// PCINT13, ICP3, MOSI, PB5 6 35 PA5, ADC5, PCINT5
// PCINT14, OC3A, MISO, PB6 7 34 PA6, ADC6, PCINT6
// PCINT15, OC3B, SCK, PB7 8 33 PA7, ADC7, PCINT7
// /RESET 9 32 AREF
// Vcc 10 31 GND
// GND 11 30 AVcc
// XTAL2 12 29 PC7, TOSC2, PCINT23
// XTAL1 13 28 PC6, TOSC1, PCINT22
// PCINT24, RXD0, T3, PD0 14 27 PC5, TDI, PCINT21
// PCINT25, TXD0, PD1 15 26 PC4, TDO, PCINT20
// PCINT26, RXD1, INT0, PD2 16 25 PC3, TMS, PCINT19
// PCINT27, TXD1, INT1, PD3 17 24 PC2, TCK, PCINT18
// PCINT28, XCK1, OC1B, PD4 18 23 PC1, SDA, PCINT17
// PCINT29, OC1A, PD5 19 22 PC0, SCL, PCINT16
// PCINT30, OC2B, ICP PD6 20 21 PD7, OC2A, PCINT31
// ================================================== =
// ####>>>> Initialisierung der Anschlüsse für R5M auf mega1284: <<<<####
// ( PB0 1 A A 40 PA0 (CN 11-1 -Audi-Busy)
// CN_12 ( PB1 2 A A 39 PA1 (CN 11-3 -Audi-Clock)
// PORTB ( PB2 3 A A 38 PA2 (CN 11-5 -Audi-Data)
// (CIR od. / PB3 4 A? E 37 PA3 (CN 11-4 -Audi)
// LCD ... \ PB4 5 A A 36 PA4, Servo10
// siehe ( MOSI, PB5 6 A A 35 PA5, Servo9
// unten ( MISO, PB6 7 A A 34 PA6, Servo8
// TasteC=3 ( SCK, PB7 8 EU A 33 PA7, Servo7
// Taste /RES, /RESET 9 32 AREF ref Vcc, aktuell
// Vcc 10 31 GND
// GND 11 30 AVcc
// XTAL2 12 A 29 PC7, Servo6
// XTAL1 13 A 28 PC6, Servo5
// RXD0, PD0 14 EU A 27 PC5, Servo4
// TXD0, PD1 15 EU A 26 PC4, Servo3
// RXD1, PD2 16 EU A 25 PC3, Servo2
// TXD1, PD3 17 A A 24 PC2, Servo1
// L1g PD4 18 A E 23 PC1, SDA
// L1r PD5 19 A E 22 PC0, SCL
// TasteA=1, PD6 20 EU EU 21 PD7, TasteB=2, PCINT31, (Sound)
// - - - - - - - - - - - - - - -
// ####>>>> PB3 ist evtl. Source für IR-DME-LED !! = CIR-LED
// - - - - - - - - - - - - - - -
// ####>>>> Initialisierung/Anschlüsse von PORT B für LCD DEM 16x2
// data bit 4 PB0 0 A WS Pin1 |
// data bit 5 PB1 1 A Pin2 | -- Der 10-polige Wannenstecker
// data bit 6 PB2 2 A Pin3 | ist an die Belegung wie beim
// data bit 7 SCK, PB3 3 A Pin4 | Transistortester angepasst
// RS line PB4 RS Pin5 | es kommen noch
// ENABLE line MOSI, PB5 EN1 Pin6 | Pin 9 GND und
// R/W (offen) MISO, PB6 R/W Pin7 | Pin 10 Vcc dazu
// NC (TasteC) SCK, PB7 NC Pin8 |___________________________
// GND Pin9
// Vcc Pn10 | Anmerkg: ENABLE line !
// - - - - - - - - - - - - - - -
// Ports+Pins als Ein- (0) od Ausgänge (1) konfigurieren, Pull Ups (1) aktivieren
// A = Ausgang, E = Eingang ohne , EU = Eingang MIT PullUp
DDRA = 0b11110110; // PA0..2 WTV Bsy/Clk/Dto, PA3=Eingang f Sharp
PORTA = 0b00000001; // und Port/Pull Ups (1) aktivieren
//
DDRB = 0b01111111; // siehe aktuell oben
PORTB = 0b10000000; // und Port/Pull Ups (1) aktivieren
//
DDRC = 0b11110111; // PC0..7 (mega1284), PC0 + PC1 = I2C
PORTC = 0b00000000; // bis auf i2c : Alle OHNE Pullup !!
//
DDRD = 0b00111100; // -> siehe Schaltplan m-32-plus
PORTD = 0b11000011; // Pull Ups aktivieren, NICHT bei extINT0/~1

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// <<<=== Etwa hier könnte Deine erste Testschleife stehen
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
for(i=0; i<10; i++) // gLED1(PC3) blinken lassen bevor Interrupts erlaubt sind,
{ // um ungewollte Resets u.ä. besser erkennen zu können
SetBit(PORTD, L1g); // gnLED/PD4 schalten EIN, HELL
wms(3); // ###>>> HeartbeatLEDs schalten Aode -<|- Portpin <<<###
ClrBit(PORTD, L1g); // gnLED/PD4 aus
wms(97); //
SetBit(PORTD, L1r); // rtLED/PD5 schalten EIN, HELL
wms(3); // ###>>> HeartbeatLEDs schalten Aode -<|- Portpin <<<###
if ( TAaus) ClrBit(PORTD, L1r); // rtLED/PD5 aus WENN T1 nicht gedrückt
wms(97); //
} // Ende von for(i=0; i<10; i++)
ClrBit(PORTD, L1r); // rtLED/PD5 auf JEDEN Fall aus
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SrvCHK = 1; // Flag setzen für Extremwertprüfung aktiv tmr
TC1TMR_init(); // Init Tmr/Cntr1 für ServoPWMs tmr
// - - - - - - - - - - - - - - -
Izeit_1 = 20000; // Der ZeitHorizont für ISR(TIMER2_COMPA_vect)
Izthrznt = 20000; // Der ZeitHorizont für ISR(TIMER2_COMPA_vect)
Isecundn = 0; // Sekundenzähler, max 9 Stunden - NUR hier nullen
TC2TMR_init(); // Init Timer/Cntr2-Interrupt 20 kHz/50 µsec tmr
// ISR gibt auf PD4/L1g (gnLED) ein Taktsignal aus
kal_0(); // Initialisieren aus KOnstanten-Liste = div. Vorgaben kal

// - - - - - - - - - - - - - - -
lcd_init(); // lib
lcd_01 ( ); // Info über LCD inf
// Anmerkung zu LCD_01 Diese Routine muss früh laufen, vor Allem
// VOR der UART1-Initialisierung. Denn die schnelle UART1 verzögert
// die Routine wms dramatisch ! ! !
//
STSi2c = 0; // Statusbyte I2C auf "nicht initialisiert" setzen
I2C_init (); // I2C-Buffer initialisieren, I2C-Slave-Init i2c

//init_uart0(MYUBRR); // USART0 initialisieren m wählbarer Baudrate (s.o.) inf
init_uart0 ( (u16)(F_CPU / BAUD / 16 - 0.5) ); // Init UART0 nach PDannegger
// Die ##-routinen für LCD-Betrieb wieder aktivieren - stören dann den IR-LED-Takt

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

sei(); // Globalen Interrupt freigeben
info01 (); // Startinfo über USART ausgeben main
// . . .
// . . .
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// <<<=== Etwa hier könnte Deine zweite Testschleife stehen
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Don Simon
04.09.2018, 01:20
.. Codebeispiel (wie es bei mir auf praktisch allen Controllern läuft) ..
Nun hab ich ein ähnliches Programm erstellt, einmal mit den Interrupts und einmal ohne Schnickschnack.

Ergebnis: Das Programm wird doch tatsächlich so stark gebremst, dass es nur circa halb so schnell läuft! Erstaunlich!
Ohne die Hintergrund-Routinen stimmen dann die "Wait"-Zeiten ziemlich gut.


--> Im Hintergrund läuft im normalen Programm die Servo-Bibliothek, die mit den sechs Servos des Roboter Arms wohl recht beschäftigt ist.



.. Hinterher allenfalls ärgern, nicht heulen. (hmmm - hoffentlich musste nicht auf mich schimpfen . . .) ..
Alles gut ;-)

Vielen Dank für eure Hilfe.
Offtopic: Wo passen denn zukünftige Beiträge rein, die den Roboterarm betreffen? Es gibt viele Unterforen zu "Asuro" usw., allerdings finde ich hierfür nichts.

Ceos
04.09.2018, 08:29
Die Servo Lib benutzt meines Wissens nach Software PWM, schaltet die Pins also "manuell" an und aus

Du könntest dich evtl. mal mit den Timern für Atmegas beschäftigen, die haben i.d.R. mind 2 Timer mit jeweils 2 PWM Ausgängen, die deine Servos, ohne auch nur ein einziges Stück Code (ausgenommen Initialisierung und bei Änderung der Position), ansteuern können.

Der Timer zählt dabei einfach nur hoch und wenn der Timer dabei den WErt des Compare REgister des Ausgangs erreicht schaltet er den zugehörigen Pin an oder aus, je nachdem wie der Timer und die PWM konfigurtiert ist.

Du musst dann praktisch nur das Compare Register mit 2-3 Prozessorzyklen ändern um den Servo passend zu verstellen (ausgenommen die Berechnung deiner Zielposition)

PS leider ist das Pin Mapping limitiert und du musst vermutlich einige Pins umplanen und die Progammierung wird dann auch schwer in Richtung "Bare Metal" kippen, also andere Librarys verwenden wird dann evtl. Probleamtischer ... oder du findest heraus mit welcher anderen SErvo Lib oder wie du mit der bestehenden Servo Lib die Hardware PWM nutzen kannt um das Problem zu minimieren.

oberallgeier
04.09.2018, 08:46
.. Programm .. einmal mit den Interrupts und einmal ohne Schnickschnack .. Programm .. so stark gebremst, dass es nur circa halb so schnell läuft ..Na ja, pingelig gesagt läuft "das Programm" immer gleichschnell, nur wird der Ablauf durch die Interruptroutinen zu einem guten Teil belegt, der Rest muss sich mit der von den Interruptroutinen übrig gelassenen Zeit begnügen. Und daher ist - sorry dass ich das so ausdrücke - Deine Interpretation, dass Dein Programm ".. nur circa halb so schnell läuft.. " falsch. Ohne Interrupt läuft Dein Programm sozusagen alleine, hat alle Zeit NUR für sich - und sieht daher schnelllaufend aus. Mi[ISRläuft]t Inte[ISRläuft]rrupt b[ISRläuft]leibt [ISRläuft]halt ni[ISRläuft]cht me[ISRläuft]hr all[ISRläuft]e Zei[ISRläuft]t für d[ISRläuft]en Re[ISRläuft]st ü[ISRläuft]rig. Anmerkung: die Interruptserviceroutinen sind üblicherweise nie gleich lang! Die Länge kann aus der "meinfile.lls"-Datei abgeleitet werden.

Hauptsatz: der Controller kann vom üblichen Programmablauf nur jeweils einen Schritt abarbeiten, nie zwei Schritte gleichzeitig. Ausnahmen bilden hardwareimplantierte Dinge wie einige Zähler, Timer etc.


.. Im Hintergrund läuft .. die Servo-Bibliothek, die mit den sechs Servos (http://rn-wissen.de/wiki/index.php?title=Servos) des Roboter Arms wohl recht beschäftigt ist ..Ach ja, soo schlimm ist es nicht - aber ich kenne leider Deine Bibliothek nicht. Ich habe bei den Armen (und im Kopf) von meinem archie (https://www.youtube.com/watch?v=LrG-PrdZXO0) jeweils einen Kontroller der maximal zehn Servos bedient. Zu diesen - interruptgetriebenen Servoroutinen - kommen noch andere Dienste. Mit Interrupt z.B. Boardtimer, UART, I²C und solcher Spass. Beim Motorcontroller ists ähnlich, aber ohne Servoroutinen, dort läuft durch ein Flag (von einem Timer-Interrupt gesetzt) im "normalen" Programmablauf z.B. die Regelroutine für die beiden Motoren. Das wird alles einigermassen gut verschachtelt.

Verschachtelt? Beispiel Servos. Meine analogen wollen ja alle 20 ms einen Puls von beliebiger Länge 0,5 bis 2 ms. Also läuft der "Servotimer" - nennen wir ihn ST0 so, dass alle 2 (!!!) ms EIN einziger Servo mit der ID "Servopointer" eingeschaltet wird <=> Puls wird high gesetzt, dazu ein Timer ST1, der die Länge des Pulses begrenzt. Wenn ST1 abgelaufen ist, schaltet der den Puls ab UND setzt den Servopointer eins rauf. Wenn der Servopointer auf 11 ist, wird er auf 1 gesetzt. Nach dem Ablauf der 2 (!!!) ms von ST0 wird also der nächste Servo . . . usw. Diese 2 ms sind bei meinen 20Mhz-getakteten Controllern eine kleine Ewigkeit, da kann man vielerlei andere Ding zwischendurch erledigen. Und nach dem zehnten Servo sind 20 ms vorbei und der erste kommt wieder dran.

Du siehst oben, dass Deine Interruptserviceroutinen dem Ablauf z.B. Deiner waitms-Schleife etliche Zeit wegnehmen, dadurch kann die nicht glatt, ungestört, durchlaufen. Übrigens sind diese waitms- oder delayxx-Routinen böse Bremsen, weil die ausser interruptgetriebenen Abläufen keine weiteren Aktionen zulassen. Daher erledige ich so etwas üblicherweise mit einem Flag, das im Boardtimer gesetzt wird. Dieser Boardtimer ist bei mir eine Interruptroutine, die alle 50 µs die boardinterne Zeit eins höher setzt. Also kann ich z.B. nen tmrxy1 machen, der in der ISR fallweise runtergetickert wird.

Fallweise? Klar, dort heißt es dann
....if ( tmrxy1 ) tmrxy1 --; // einfach tickern bis Null; Allzwecktimer s. ~com~
und bedeutet dass, solagen tmrxy1 ungleich null (bedeutet true ! *gg*) ist, wird er runtergezählt. Und ich kann ihn in der Zwischenzeit auf Null prüfen und ne Aktion machen sobald er wieder auf Null ist. Manche Abläufe laufen ähnlich, aber mit hochgetickerten Timer.

Ende, viel zu viel - TL;DR. Aber vielleicht wird Dir damit der Programmablauf im Controller klar(er).

Don Simon
07.09.2018, 14:34
Die Servo Lib benutzt meines Wissens nach Software PWM, schaltet die Pins also "manuell" an und aus ..
Davon gehe ich auch aus.


.. Du könntest dich evtl. mal mit den Timern für Atmegas beschäftigen ..
Hatte noch nicht die Zeit, die Servo-Bibliothek mit eigener Timer-Interrupt Steuerung zu ersetzen, hab das aber noch vor, da mir die Lib. sowieso zu ungenau unterteilt.


.. PS leider ist das Pin Mapping limitiert und du musst vermutlich einige Pins umplanen ..
Das hatte ich schon befürchtet.






Na ja, pingelig gesagt läuft "das Programm" immer gleichschnell ..
Klar, das "gesamte" Programm.


.. Deine Interpretation, dass Dein Programm ".. nur circa halb so schnell läuft.. " [ist] falsch.
Ja. Ich denke, wir können uns darauf einigen, dass durch die Unterbrechungen der ISR die Befehle in der Hauptschleife (bzw. "Main") weniger oft ausgeführt werden (können).


.. Die Länge [der ISR] kann aus der "meinfile.lls"-Datei abgeleitet werden. ..
Das wusste ich nicht. Wo finde ich die Datei?


.. der Controller kann vom üblichen Programmablauf nur jeweils einen Schritt abarbeiten ..
Klar, Multithreading muss mit nur einem Prozessorkern hintereinander ablaufen.






.. einen Kontroller der maximal zehn Servos bedient ..
Sollte auch kein Problem darstellen, wenn man bedenkt, dass die ISR hier nur jeweils einen Pin setzt oder rücksetzt. Es sollten sogar mehr als zehn Servos möglich sein, da die 50 Hz normalerweise nicht so kritisch sind.


.. "Servotimer" - nennen wir ihn ST0 [...] dazu ein Timer ST1, der die Länge des Pulses begrenzt. ..
Es sollte auch möglich sein, beides mit einem Timer abzuarbeiten, also Pulse und Pausen - ich hab mir das für meine zukünftige Ansteuerung überlegt. Das System ist ansonsten gleich:
Puls1, Pause1 (ausrechnen), Puls2, Pause2 (ausrechnen),... die Pause muss so berechnet werden, dass (Puls+Pause)*6=20 ms ergibt, da ich momentan maximal sechs Servos verwende.

Man könnte die Restzeit alternativ auch hinten dran hängen, dann sollte Puls+Pause jeweils 2 ms lang sein, wobei - wie schon angemerkt - die angestrebte Frequenz nicht so wichtig ist.
Interessant wäre daher auch noch folgender Versuch (ebenfalls mit einem Timer):
Puls1, Puls2, ...Puls6, 11 ms Pause.
Nach meiner Rechnung schwankt die Frequenz dann zwischen 43,5 und 58,8 Hz. Ich denke, das sollte noch problemlos funktionieren. Hat sowas schon einmal jemand versucht?


.. Übrigens sind diese waitms- oder delayxx-Routinen böse Bremsen ..
Das ist richtig. Die Routinen nutze ich normalerweise nur für unwichtige Sachen in der Hauptschleife. Für zeitkritische Anwendungen nutze ich auch gerne Timer, die Variablen hochzählen, um genauso eine Art Boardtimer zu erhalten, mit dem man wiederum durch Flags Teile im Hauptprogramm starten kann, um die ISR möglichst kurz zu halten.


Nebenbei, Archie sieht nach einem sehr interessanten Projekt aus!

oberallgeier
07.09.2018, 16:02
.. ich werke .. mit eingebautem Atmega64 und externem 16 MHz Quarz, programmiere mit Bascom/Ponyprog/ISP ..
.. Das wusste ich nicht. Wo finde ich die Datei? ..Oh weh, böses Eigentor - ich hatte nicht daran gedacht, dass Du mit Bascom arbeitest. Ich programmiere meine Atmels mit C (kaum C++) und AVRStudio4 oder Studio7. Da finde ich diese Datei im Projektordner, Subdirectory "default". Sieht etwa wie folgt aus; im Beispiel heißt die Datei ARCo.lss, hier nur Anfang, danach stark gekürzt :

ARCo.elf: file format elf32-avr

Sections:
Idx Name Size VMA LMA File off Algn
0 .data 00000d9e 00800100 00005922 000059d6 2**0
CONTENTS, ALLOC, LOAD, DATA
1 .text 00005922 00000000 00000000 000000b4 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .bss 000003b7 00800e9e 00800e9e 00006774 2**0
ALLOC
3 .eeprom 000001ea 00810000 00810000 00006774 2**0
CONTENTS, ALLOC, LOAD, DATA
4 .debug_aranges 00000020 00000000 00000000 0000695e 2**0
CONTENTS, READONLY, DEBUGGING
5 .debug_pubnames 000008ef 00000000 00000000 0000697e 2**0
CONTENTS, READONLY, DEBUGGING
6 .debug_info 00003355 00000000 00000000 0000726d 2**0
CONTENTS, READONLY, DEBUGGING
7 .debug_abbrev 000003eb 00000000 00000000 0000a5c2 2**0
CONTENTS, READONLY, DEBUGGING
8 .debug_line 000045d7 00000000 00000000 0000a9ad 2**0
CONTENTS, READONLY, DEBUGGING
9 .debug_frame 00000600 00000000 00000000 0000ef84 2**2
CONTENTS, READONLY, DEBUGGING
10 .debug_str 00000897 00000000 00000000 0000f584 2**0
CONTENTS, READONLY, DEBUGGING
11 .debug_loc 00001f04 00000000 00000000 0000fe1b 2**0
CONTENTS, READONLY, DEBUGGING
12 .debug_ranges 00000270 00000000 00000000 00011d1f 2**0
CONTENTS, READONLY, DEBUGGING

Disassembly of section .text:

00000000 <__vectors>:
0: 0c 94 46 00 jmp 0x8c ; 0x8c <__ctors_end>
4: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
8: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
c: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
10: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
14: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
18: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
1c: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
20: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
24: 0c 94 32 0b jmp 0x1664 ; 0x1664 <__vector_9>
28: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
2c: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
30: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
34: 0c 94 ce 07 jmp 0xf9c ; 0xf9c <__vector_13>
38: 0c 94 b3 0a jmp 0x1566 ; 0x1566 <__vector_14>
3c: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
40: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
44: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
48: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
4c: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
50: 0c 94 a4 00 jmp 0x148 ; 0x148 <__vector_20>
54: 0c 94 d3 00 jmp 0x1a6 ; 0x1a6 <__vector_21>
58: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
5c: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
60: 0c 94 14 0c jmp 0x1828 ; 0x1828 <__vector_24>
64: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
68: 0c 94 1d 02 jmp 0x43a ; 0x43a <__vector_26>
6c: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
70: 0c 94 76 01 jmp 0x2ec ; 0x2ec <__vector_28>
74: 0c 94 a6 01 jmp 0x34c ; 0x34c <__vector_29>
78: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
7c: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
80: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
84: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>
88: 0c 94 65 00 jmp 0xca ; 0xca <__bad_interrupt>

0000008c <__ctors_end>:
8c: 11 24 eor r1, r1
8e: 1f be out 0x3f, r1 ; 63
90: cf ef ldi r28, 0xFF ; 255
92: d0 e4 ldi r29, 0x40 ; 64
94: de bf out 0x3e, r29 ; 62
96: cd bf out 0x3d, r28 ; 61

00000098 <__do_copy_data>:
98: 1e e0 ldi r17, 0x0E ; 14
9a: a0 e0 ldi r26, 0x00 ; 0
9c: b1 e0 ldi r27, 0x01 ; 1
9e: e2 e2 ldi r30, 0x22 ; 34
a0: f9 e5 ldi r31, 0x59 ; 89
a2: 00 e0 ldi r16, 0x00 ; 0
a4: 0b bf out 0x3b, r16 ; 59
a6: 02 c0 rjmp .+4 ; 0xac <__do_copy_data+0x14>
a8: 07 90 elpm r0, Z+
aa: 0d 92 st X+, r0
ac: ae 39 cpi r26, 0x9E ; 158
ae: b1 07 cpc r27, r17
b0: d9 f7 brne .-10 ; 0xa8 <__do_copy_data+0x10>

000000b2 <__do_clear_bss>:
b2: 12 e1 ldi r17, 0x12 ; 18
b4: ae e9 ldi r26, 0x9E ; 158
b6: be e0 ldi r27, 0x0E ; 14
b8: 01 c0 rjmp .+2 ; 0xbc <.do_clear_bss_start>

000000ba <.do_clear_bss_loop>:
ba: 1d 92 st X+, r1

000000bc <.do_clear_bss_start>:
bc: a5 35 cpi r26, 0x55 ; 85
be: b1 07 cpc r27, r17
c0: e1 f7 brne .-8 ; 0xba <.do_clear_bss_loop>
c2: 0e 94 06 2a call 0x540c ; 0x540c <main>
c6: 0c 94 8f 2c jmp 0x591e ; 0x591e <_exit>

000000ca <__bad_interrupt>:
ca: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>

000000ce <init_uart0>:
// count up and wrap around

// ================================================== ============================ =
void init_uart0( u16 bauddivider )
{
UBRR0H = bauddivider >> 8;
ce: 90 93 c5 00 sts 0x00C5, r25
UBRR0L = bauddivider; // set baud rate
d2: 80 93 c4 00 sts 0x00C4, r24
UCSR0A = 0; // no U2X, MPCM
d6: 10 92 c0 00 sts 0x00C0, r1
UCSR0C = 1<<UCSZ01^1<<UCSZ00 // 8 Bit
da: 86 e0 ldi r24, 0x06 ; 6
dc: 80 93 c2 00 sts 0x00C2, r24
#ifdef URSEL0
^1<<URSEL0 // if UCSR0C shared with UBRR0H
#endif
;
UCSR0B = 1<<RXEN0^1<<TXEN0^ // enable RX, TX
e0: 88 e9 ldi r24, 0x98 ; 152
e2: 80 93 c1 00 sts 0x00C1, r24
1<<RXCIE0; // enable RX interrupt
rx_in = rx_out; // set buffer empty
e6: 80 91 21 0f lds r24, 0x0F21
ea: 80 93 20 0f sts 0x0F20, r24
tx_in = tx_out;
ee: 80 91 a3 0f lds r24, 0x0FA3
f2: 80 93 a2 0f sts 0x0FA2, r24
}
f6: 08 95 ret

000000f8 <ukbhit0>:


// ================================================== ============================ =
u8 ukbhit0( void )
{
return rx_out ^ vu8(rx_in); // rx_in modified by interrupt !
f8: 90 91 21 0f lds r25, 0x0F21
fc: 80 91 20 0f lds r24, 0x0F20
}
100: 89 27 eor r24, r25
102: 08 95 ret

00000104 <ugetchar0>:


// ================================================== ============================ =
u8 ukbhit0( void )
{
return rx_out ^ vu8(rx_in); // rx_in modified by interrupt !
104: 90 91 21 0f lds r25, 0x0F21
108: 80 91 20 0f lds r24, 0x0F20
// ================================================== ============================ =
u8 ugetchar0( void )
{
u8 data;

while( !ukbhit0() ); // until at least one byte in
10c: 98 17 cp r25, r24
10e: d1 f3 breq .-12 ; 0x104 <ugetchar0>
data = rx_buff[rx_out]; // get byte
110: e0 91 21 0f lds r30, 0x0F21
114: f0 e0 ldi r31, 0x00 ; 0
116: e0 56 subi r30, 0x60 ; 96
118: f1 4f sbci r31, 0xF1 ; 241
11a: e0 81 ld r30, Z
ROLLOVER( rx_out, RX0_SIZE );
11c: 80 91 21 0f lds r24, 0x0F21
120: 8f 5f subi r24, 0xFF ; 255
122: 80 93 21 0f sts 0x0F21, r24
126: 80 91 21 0f lds r24, 0x0F21
12a: 87 ff sbrs r24, 7
12c: 02 c0 rjmp .+4 ; 0x132 <ugetchar0+0x2e>
12e: 80 e0 ldi r24, 0x00 ; 0
130: 02 c0 rjmp .+4 ; 0x136 <ugetchar0+0x32>
132: 80 91 21 0f lds r24, 0x0F21
136: 80 93 21 0f sts 0x0F21, r24
URX0_IEN = 1; // enable RX interrupt
13a: 80 91 c1 00 lds r24, 0x00C1
13e: 80 68 ori r24, 0x80 ; 128
140: 80 93 c1 00 sts 0x00C1, r24
return data;
}
144: 8e 2f mov r24, r30
146: 08 95 ret

00000148 <__vector_20>:
...
...
und so weiter
// ================================================== ============================ =

Hier könnte man (wenn man viel Zeit hat - und Lust, oder wenn man es nötig findet) die Befehle jmp, ldi, out oder alle andern MAschinenbefehle im Datenblatt suchen und deren Anzahl von CPU-Takten notieren. Diese Taktanzahl für die jeweils interessierenden Abschnitte (Subroutinen, ISR etc) zusammenzählen und mit der Taktdauer multiplizieren . . .


.. Nebenbei, Archie sieht nach einem sehr interessanten Projekt aus!Danke. Ja, schon etwas zeitintensiv, ich lass mir halt Zeit.

Don Simon
10.10.2018, 21:46
Hatte leider in den letzten Wochen keine Zeit.
Aber danke für die Ausführung! Den Teil kannte ich noch garnicht. Das ist natürlich praktisch, wenn man so die einzelnen Zeiten von Routinen bekommen kann.

Steht jeweils die letzte Zahl für die Anzahl der Takte?
Also zB. fast ganz unten:
"13e: 80 68 ori r24, 0x80 ; 128"
Ist hier für ori mit r24 die "128" die Taktanzahl? Hierfür würde ich mit meinen 16 MHz rechnerisch auf 8 µs kommen.

oberallgeier
10.10.2018, 23:10
Hi Simon,
das mit der Zeit für die einzelnen Schritte ist etwas aufwendiger, die müsste man sich für jeden OpCode separat raussuchen. Die Anzahl der Maschinenzyklen steht als #Clocks in der Dokumentation/Datenblatt des Controllers, üblicherweise unter Instruction Set Summary irgendwo ziemlich weit hinten.


.. Steht jeweils die letzte Zahl für die Anzahl der Takte? .. Also zB. fast ganz unten:
"13e: 80 68 ori r24, 0x80 ; 128"
Ist hier für ori mit r24 die "128" die Taktanzahl? Hierfür würde ich mit meinen 16 MHz rechnerisch auf 8 µs kommen.

Nein, 128 ist der Wert 0x80 (hex 80, also dezimal 128 .) mit dem ein logisches OR des Inhaltes vom Register r24 durchgeführt wird. Ausführlich ist das z.B. im Studio4 in der Assemblerhilfe ( siehe [Help][Assembler Help] ) zu finden. Da kommt - wenn man zu ORI runterscrollt die entsprechende Beschreibung, siehe hier:

https://dl.dropbox.com/s/x1twjoi5ao1xa0p/xx.jpg?dl=0

.. und >>hier - Bild oben<< steht dann unten Cycles: 1. Und das ist die Aussage, dass genau diese einzelne Operation in einem Prozessortakt durchgeführt wird.

Die Befehlszeile ORI steht in der Dokumentation für den mega64 so (die Zeile unter der blau markierten):

https://dl.dropbox.com/s/c5e93j6pktri0dw/InstSet.jpg?dl=0

Übrigens steht da dann nicht "Cycles" sonder "#Clocks". Is aber dasselbe *gg*.

Ist das so nachvollziehbar?

Don Simon
11.10.2018, 19:16
Sehr gut nachvollziehbar, danke! Die einzelnen Befehle brauchen erstaunlich wenig Zeit. Hier hatte ich mit mehreren Takten gerechnet.
Vielleicht lerne ich doch noch irgendwann Assembler. Hatte vor ein paar Jahren mal ein bisschen damit angefangen mich einzuarbeiten, es dann aber nach 150 Seiten Theorie vorerst wieder aufgegeben.

oberallgeier
12.10.2018, 08:42
.. Assembler. Hatte vor ein paar Jahren mal ein bisschen damit angefangen .. nach 150 Seiten Theorie vorerst wieder aufgegeben.Das sind sicher hundert Seiten mehr als meine Geduld (und Auffassungsgabe) gereicht hätte.

Ich hatte jahrzehntealten Intel-8080-Assembler (also vorzugsweise alles vergessen) aufgefrischt mit dem Experimentierkasten (https://www.amazon.de/Franzis-Lernpaket-Mikrocontroller/dp/3645650180) von B.Kainka. Dort ist der Controller ein ATtiny13 - SEHR übersichtlicher kleiner Controller. Kainka hat dazu noch hier ne Beschreibung (http://www.b-kainka.de/lpmikros.htm). Es sind ne ganze Reihe kleiner Experimente, fast durchwegs in Assembler beschrieben. Das ganze Buch hat weniger als 150 Seiten *gg* - und danach war ich ein ganzes Stück "in" den Atmels drinnen.

oberallgeier
12.10.2018, 22:44
.. Die einzelnen Befehle brauchen erstaunlich wenig Zeit. Hier hatte ich mit mehreren Takten gerechnet ..Ach jeee. Schon wieder jemand der das Datenblatt schlecht/wenig/kaum/nicht liest.


..
130 Powerful Instructions – Most Single Clock Cycle Execution ..Erste Seite, vierte Zeile ! Weiter oben gehts ja kaum. => Ohne das zugehörige Datenblatt zu lesen ist der Umgang mit Mikrocontrollern eins der letzten großen Abenteuer unserer Tage.