Bitte um Hilfe mit Verständnis, ISR von Timer usw.

OP #7346212
Lesenswert?

Guten Tag! Ich lerne z.Z. mit einer Versuchsplatine auf Basis ATMega1284. Timer0 ist für ISR je 1 ms eingestimmt. in ISR wird ein Zähler inkrementiert. In Hauptprogramm wird Zähler geprüft und wenn 1000 erreicht, wird auf 0 gesetzt und einige Handlungen gemacht. Code:

1
// asm_timer0.S
2
#include <avr/io.h>
3

4
.extern flag_1ms
5
.global TIMER0_COMPA_vect
6

7
TIMER0_COMPA_vect:
8
  push r0
9
  in r0,0x3f
10
  push r24
11

12
  lds r24,flag_1ms
13
  subi r24,-1
14
  sts flag_1ms,r24
15
  lds r24,(flag_1ms + 1)
16
  sbci r24,-1
17
  sts (flag_1ms + 1),r24
18

19
  pop r24
20
  out 0x3f,r0
21
  pop r0
22

23
  reti
1
// timer0.c
2
#include "timer0.h"
3
volatile u16 flag_1ms = 0;
1
// timer0.h
2
#ifndef TIMER0_H
3
#define TIMER0_H
4

5
#include "../main.h"
6
#include <avr/interrupt.h>   // Fuer Interrupts
7

8
extern volatile u16 flag_1ms;  
9

10
#define PULS_FREQ 1000
11

12
#define INIT_PULS ((F_CPU/PULS_FREQ)/64 - 1)
13

14
static inline void timer_init_timer0(void){
15
  /* CTC-Mode */
16
  TCNT0 = 0;
17
  OCR0A = INIT_PULS;
18
  TCCR0A = (1<<WGM01);
19
  TCCR0B = ((1<<CS01) | (1<<CS00)); /* Start mit 1/64 Prescaler */
20
  TIMSK0 = (1<<OCIE0A);
21
}
22
#endif  /* TIMER0_H */

Hauptprogramm:

1
while(1){
2
      if(flag_1ms == 1000){
3
    flag_1ms = 0;
4
usw.

Diese Variante funktioniert wie erwartet

1
while(1){
2
      if(flag_1ms > 999){
3
    flag_1ms = 0;
4
usw.

Hier aber wird alles merkbar öfter als 1 Sek gemacht, so um 10%. Ich habe vermutet, das Problem kommt, da ISR gelegentlich zwischen Lesen und Schreiben von einzelnen Bytes von 16-bit-Variable kommt.

1
while(1){
2
  volatile u16 tmpflag;
3
  cli();
4
  tmpflag = flag_1ms;
5
  sei();
6
  tmpflag -= 1000;
7
if(!(SREG & (1<<0))){
8
    flag_1ms = 0;
9
usw.

Diese Variante arbeitet wie erwartet.

Die Frage: warum stören ISR bei dem Vergleich

1
if(flag_1ms > 999){

und nicht stören bei dem Vergleich

1
if(flag_1ms == 1000){

? In beiden Fällen wird doch 2-Bytes-Variable verarbeitet?

Viele Grüße!

#7346218
Lesenswert?

Maxim B. schrieb:

Guten Tag! Ich lerne z.Z. mit einer Versuchsplatine auf Basis ATMega1284. Timer0 ist für ISR je 1 ms eingestimmt. in ISR wird ein Zähler inkrementiert.

Und wozu die Assembleroutine? Kann man machen, ist bei 1kHz aber eher überflüssig.

In Hauptprogramm wird Zähler geprüft und wenn 1000 erreicht, wird auf 0 gesetzt und einige Handlungen gemacht.

Lies mal was zum Theme Interrupt, volatile und atomare Operationen.

OP #7346225
Lesenswert?

Falk B. schrieb:

Und wozu die Assembleroutine? Kann man machen, ist bei 1kHz aber eher überflüssig.

ISR sollte so schnell wie möglich gehen. Mit rein C braucht ISR 37 Cycle, mit Assembler nur 24. Grund: ich benutze r1 und r25 in ISR nicht. Auch SREG wird in r0 während ISR bleiben. Wozu macht C-Compiler diese überflüssigen push-pop ? Nur um alles langsamer zu machen?

Lies mal was zum Theme Interrupt, volatile und atomare Operationen.

Mich interessiert nicht die Problematik von ISR zwischen mehreren Bytes-Lesen-Schreiben, sondern warum bei dem Vergleich "==" ISR nicht stören und bei dem Vergleich ">" stören?

Was aber atomare Operationen betrifft: wie kann ich ISR nur innerhalb Vergleichs "if(flag_1ms > 999)" aussetzen, ohne bei dem ganzen {} ? Egal, benutze ich cli(); oder sei();, oder auch mit ISR_BLOCK - das gilt ja für ganze?

Moderator (Firma: Titel) Persönliche Seite #7346234
Lesenswert?

Maxim B. schrieb:

flag_1ms = 0;

Du darfst diese Variable nur in der ISR verändern/beschreiben. Niemals in der ISR und in der Mainloop!

Neim Tipp: mach aus dem flag_1ms einen uint32_t Zähler namens millis, zähle den jede ms um 1 hoch und mach es dann so, wie es beim Arduino mit den millis() geht.

Maxim B. schrieb:

ISR sollte so schnell wie möglich gehen.

Eine ISR soll so schnell wie nötig(!!) gehen.

Aber einfach mal mit Zahlen: wenn die ISR wegen der Verwendung von C-Code 50 Maschinentakte länger braucht, dann ist das verschwindend wenig Zeit, die du sparrt, denn in dieser 1 ms hat der µC bei 16 MHz immerhin 16000 Befehle ausgeführt. Und somit hättest du durch das Einsparen der 100 Takte gerade mal 50/16000 = 0,3% der Rechenleistung eingespart.

Wenn diese fehlende Rechenleistung von 0,3% der Gesamtrechenleistung zu Problemen führt, dann ist der Prozessor wohl eh' zu knapp bemessen.

Gast #7346242
Lesenswert?

Maxim B. schrieb:

warum stören ISR bei dem Vergleich if(flag_1ms > 999){ und nicht stören bei dem Vergleich if(flag_1ms == 1000){

Dazu müsste man einen Blick in Assembler Listing werfen. Es kann gut sein, dass beide Fälle fehlerhaft sind, aber das Auftreten des Fehlers unterschiedlich wahrscheinlich ist.

Du hast ja schon erkannt, dass man eigentlich bei Lesen des Zählers die Interrupts sperren muss damit die ISR nicht dazwischen stört.

Ich gebe dir mal ein Extrembeispiel. Stelle dir vor, die ISR erhöht den Zähler von 255 (0x00ff) nach 256 (0x0100). Stelle dir weiter vor, dass das Hauptprogramm im selben Moment den Zählerstand prüft, und zwar zuerst die oberen 8 Bit der Variable und dann die unteren. Dazwischen stört der Interrupt. Dann liest das Hauptprogramm den Wert 0 (0x0000), was völlig falsch ist.

Gast #7346243
Lesenswert?

Maxim B. schrieb:

warum bei dem Vergleich "==" ISR nicht stören und bei dem Vergleich ">" stören

999 ist 0x03E7, 1000 ist 0x03E8. Wenn die ISR während des Vergleichs 0xE7 auf 0xE8 ändert, bleibt aber 0x03 gleich, also entstehen nie üble Zwischenzustände die den ==1000-Vergleich betreffen könnten.

Beim Vergleich >999 ist es anders. Da kann es passieren, dass die ISR von 0x2FF nach 0x300 zählt, der Vergleich sich aber die 0xFF vor dem Inkrementieren und dann die 0x03 nsch dem Inkrementieren holt, und dann ist das Ergebnis >999.

LG, Sebastian

OP #7346248
Lesenswert?

Lothar M. schrieb:

Maxim B. schrieb:

flag_1ms = 0;

Du darfst diese Variable nur in der ISR verändern/beschreiben. Niemals in der ISR und in der Mainloop!

Neim Tipp: mach aus dem flag_1ms einen uint32_t Zähler namens millis, zähle den jede ms um 1 hoch und mach es dann so, wie es beim Arduino mit den millis() geht.

Danke für Tipp. Ich möchte aber die langsame Arduino-Variante lieber vermeiden.

Übrigens, Compiler macht aus dem

1
while(1){
2
  volatile u16 tmpflag;
3
  cli();
4
  tmpflag = flag_1ms;
5
  sei();
6
  tmpflag -= 1000;
7

8
if(!(SREG & (1<<0))){

auch zu viel Unnötiges:

1
    3d4e:  f8 94         cli
2
  tmpflag = flag_1ms;
3
    3d50:  80 91 1d 01   lds  r24, 0x011D  ; 0x80011d <flag_1ms>
4
    3d54:  90 91 1e 01   lds  r25, 0x011E  ; 0x80011e <flag_1ms+0x1>
5
    3d58:  9a 87         std  Y+10, r25  ; 0x0a
6
    3d5a:  89 87         std  Y+9, r24  ; 0x09
7
  sei();
8
    3d5c:  78 94         sei
9
  tmpflag -= 1000;
10
    3d5e:  89 85         ldd  r24, Y+9  ; 0x09
11
    3d60:  9a 85         ldd  r25, Y+10  ; 0x0a
12
    3d62:  88 5e         subi  r24, 0xE8  ; 232
13
    3d64:  93 40         sbci  r25, 0x03  ; 3
14
    3d66:  9a 87         std  Y+10, r25  ; 0x0a
15
    3d68:  89 87         std  Y+9, r24  ; 0x09
16
if(!(SREG & (1<<0))){
17
    3d6a:  0f b6         in  r0, 0x3f  ; 63
18
    3d6c:  00 fc         sbrc  r0, 0
19
    3d6e:  ef cf         rjmp  .-34       ; 0x3d4e <main+0x102>

Die Assembler-Variante macht den Job deutlich schneller und sparsamer:

1
while(1){
2
  asm volatile("cli");
3
  asm volatile("lds r24,flag_1ms");
4
  asm volatile("lds r25,(flag_1ms+1)");
5
  asm volatile("sei");
6
  asm volatile("subi r24,0xE8");
7
  asm volatile("sbci r25,0x03");
8
if(!(SREG & (1<<0))){

12 CPU-Cycle pro Schleife statt 24, gerade dort, wo es um die Zeit geht! Ohne "volatile" ging reine C-Variante leider nicht: "tmpflag -= 1000;" wurde von Compiler wegoptimiert.

Beitrag #7346249 wurde von einem Moderator gelöscht.
Gast #7346256
Lesenswert?

Maxim B. schrieb:

Dann wird "tmpflag -= 1000;" wegoptimiert - ich habe ausprobiert.

Offenbar benutzt du tmpflag für nichts. Dass "if(!(SREG & (1<<0)))" etwas damit zu tun hat, kann der C Compiler nicht erkennen. SO schlau ist er nicht. Schreibe einfach in C hin, was du machen willst, dann optimiert er es auch korrekt. Also

if tmpflag>1000 {...}

OP #7346258
Lesenswert?

Niemert schrieb:

Arduino braucht timer0 für delay und millis und sowas. Nimm timer1

Ich mache kein Arduino. Die Platine habe ich in China bestellt nach meinem Entwurf. Arduino kennt leider kein debug, und ich habe mir vor 4 Jahren JTAG ICE II gekauft. Dafür ist diese Platine bestimmt.

Das Spielzeug habe ich reaktiviert, da ich für einen Projekt MIDI-Sniffer brauche. Für solche Zwecke paßt diese Platine gut: für internen Aufgaben wird nur SPI benutzt, aber auch SPI kann extern benutzt werden. I2C und beide USARTs stehen frei. Für diese Platine habe ich auch MIDI-Modul gemacht.

Angehängte Dateien:
OP #7346289
Lesenswert?

Stefan F. schrieb:

Maxim B. schrieb:

Dann wird "tmpflag -= 1000;" wegoptimiert - ich habe ausprobiert.

Offenbar benutzt du tmpflag für nichts. Dass "if(!(SREG & (1<<0)))" etwas damit zu tun hat, kann der C Compiler nicht erkennen. SO schlau ist er nicht. Schreibe einfach in C hin, was du machen willst, dann optimiert er es auch korrekt. Also

if tmpflag>1000 {...}

Ich habe ausprobiert. Ja, das geht. Sogar 4 Bytes weniger, ansonsten ist Code nun sehr ähnlich. Ich habe vergeblich den Compiler beschimpft :)

Mit Asm:

1
  asm volatile("cli");
2
    3d4e:  f8 94         cli
3
  asm volatile("lds r24,flag_1ms");
4
    3d50:  80 91 1d 01   lds  r24, 0x011D  ; 0x80011d <flag_1ms>
5
  asm volatile("lds r25,(flag_1ms+1)");
6
    3d54:  90 91 1e 01   lds  r25, 0x011E  ; 0x80011e <flag_1ms+0x1>
7
  asm volatile("sei");
8
    3d58:  78 94         sei
9
  asm volatile("subi r24,0xE8");
10
    3d5a:  88 5e         subi  r24, 0xE8  ; 232
11
  asm volatile("sbci r25,0x03");
12
    3d5c:  93 40         sbci  r25, 0x03  ; 3
13

14
if(!(SREG & (1<<0))){
15
    3d5e:  0f b6         in  r0, 0x3f  ; 63
16
    3d60:  00 fc         sbrc  r0, 0
17
    3d62:  f5 cf         rjmp  .-22       ; 0x3d4e

nur C:

1
  cli();
2
    3d4e:  f8 94         cli
3
  tmpflag = flag_1ms;
4
    3d50:  80 91 1d 01   lds  r24, 0x011D  ; 0x80011d <flag_1ms>
5
    3d54:  90 91 1e 01   lds  r25, 0x011E  ; 0x80011e <flag_1ms+0x1>
6
  sei();
7
    3d58:  78 94         sei
8

9
// if(!(SREG & (1<<0))){
10
  if(tmpflag>999){
11
    3d5a:  88 3e         cpi  r24, 0xE8  ; 232
12
    3d5c:  93 40         sbci  r25, 0x03  ; 3
13
    3d5e:  b8 f3         brcs  .-18       ; 0x3d4e
OP #7346323
Lesenswert?

Lothar M. schrieb:

Aber einfach mal mit Zahlen: wenn die ISR wegen der Verwendung von C-Code 50 Maschinentakte länger braucht, dann ist das verschwindend wenig Zeit, die du sparrt, denn in dieser 1 ms hat der µC bei 16 MHz immerhin 16000 Befehle ausgeführt. Und somit hättest du durch das Einsparen der 100 Takte gerade mal 50/16000 = 0,3% der Rechenleistung eingespart.

Wenn diese fehlende Rechenleistung von 0,3% der Gesamtrechenleistung zu Problemen führt, dann ist der Prozessor wohl eh' zu knapp bemessen.

Problem: es werden auch andere ISR kommen, z.B. von USART. Je kürzer sind alle ISR, um so weniger wahrscheinlich, daß ich etwas verliere. Und Hauptschleife wird nicht immer auf eine Sekunde gesetzt. Es kann sein, daß sie auch mal 400 ms dauert... Natürlich ab 255 ms kein Problem, da Zähler-Variable nur 1 Bytes braucht.

Ich machte früher oft so, daß nach Timer-ISR mehrere Variablen geprüft werden, je nach Bedarf. Einige Aufgaben sollten jede 10 ms gemacht werden, anderen jede 100 ms, einige auch mal je 1000 ms... Jetzt geht es mir um Prinzip: ich möchte eine sicher funktionierende Hauptschleife, die auch dann stabil bleibt, wenn einige Aufgaben mehrere ms brauchen (so wie Arbeit mit LCD mit IL9341 zum Beispiel)...

Was IL9341 betrifft, habe ich übrigens eine Idee: separaten Mikrocontroller. Dann wird Haupt-Mikroconttroller nur gelegentlich Daten schicken, die LCD-Mikroconroller in FIFO nimmt und ohne Eile alle Zeichen auf dem Bildschirm macht... Aber das ist noch Zukunftsmusik. Die Platine hängt noch irgendwo in Hong Kong :) Die Platine hat mehrere Bod-Varianten (einschließlich synchron), wozu zwei Quarze, 14,7456 und 16 MHz, umgeschalet werden. Ich bin gespannt, was mir gelingt...

Gast #7346329
Lesenswert?

Maxim B. schrieb:

ich möchte eine sicher funktionierende Hauptschleife, die auch dann stabil bleibt, wenn einige Aufgaben mehrere ms brauchen

Dann würde ich in der Hauptschleife überhaupt nicht mit Zeiten hantieren, sondern das den einzelnen Tasks überlassen. Und ich würde den Millisekunden-Zähler niemals zurück setzen.

Lösungsansatz: http://stefanfrings.de/multithreading_arduino/index.html

OP #7346342
Lesenswert?

Stefan F. schrieb:

Maxim B. schrieb:

ich möchte eine sicher funktionierende Hauptschleife, die auch dann stabil bleibt, wenn einige Aufgaben mehrere ms brauchen

Dann würde ich in der Hauptschleife überhaupt nicht mit Zeiten hantieren, sondern das den einzelnen Tasks überlassen. Und ich würde den Millisekunden-Zähler niemals zurück setzen.

Lösungsansatz: http://stefanfrings.de/multithreading_arduino/index.html

Danke! Ich habe auch einen selbstgebackenen Planer, vor 4 Jahren gemacht... Nach einer Pause muß ich alles erinnern... Dort werden die Aufgaben in FIFO gestellt, zusammen mit Takt-Zahl, wann die Aufgabe zu machen ist. Auch Parameter für diese Funktionen werden in FIFO gespeichert (als u32, die auch jede Funktion nach ihr Vorlieben versteht, z.B. als 2x u16 oder als s8, u8 und s16 nach Bedarf), es gibt auch verschiedene Prioritäten... In Timer-ISR wird dort 1-Byte-Variable inkrementiert. Zuerst setzte ich in ISR NACKED nur ein Bit in GPIOR0, aber dann habe ich Gefahr erkannt, daß einige Aufgaben mehr Zeit brauchen können als Timer-ISR-Takt. Damit Verpaßte nachgeholt wird, wenn auch später, ist eine Variable besser.

Manchmal ist so was aber zu aufwendig. Als Beispiel: ich habe zwei Timer für UV-Platinenbelichter gemacht. Zuerst mit Hauptschleife, dann mit dem Planer. Beide arbeiten identisch, nur Code mit Planer deutlich größer.

OP #7347546
Lesenswert?

Ich möchte noch mal fragen.

Ich habe Logik anders gemacht. Nun wird alles in der Hauptschleife gezählt, Var. flag_1ms hat nun 1 Byte und dient dazu, verpaßte ISR (falls Bearbeitung in der Schleife mal zu lange dauert, z.B. eine Zeile auf LCD geschrieben) zu speichern. Deshalb wird in der Hauptschleife flag_1ms nicht mehr auf Null gesetzt, sondern decrementiert.

Problem: wenn ich C-Variante schreibe:

1
   if(flag_1ms){
2
   flag_1ms--;
3
   usw.

wird Variable zweimal gelesen: bei Vergleich und für Decrement. Und natürlich kann sie durch ISR zwischen zwei Lesen geändert werden.

Eine funktionierende Variante mit Inline-Assembler sieht so aus:

1
  asm volatile(
2
  "cli        \n\t"
3
  "lds r24,flag_1ms  \n\t"
4
  "tst r24      \n\t"
5
  "brne .+4      \n\t"
6
  "sei        \n\t"
7
  "rjmp .-14      \n\t"
8
  "dec r24      \n\t"
9
  "sts flag_1ms,r24  \n\t"
10
  "sei        \n\t"
11
  );

Die Frage: kann man so etwas mit C machen? Genau gesagt, nur 1x aus SRAM lesen und zwischen Lesen und Decrement mit cli ?

OP #7347562
Lesenswert?

Stefan F. schrieb:

Maxim B. schrieb:

Die Frage: kann man so etwas mit C machen?

cli() if(flag_1ms) { flag_1ms--; sei(); ... } else { sei(); }

Vielen Dank!!!

Zwar wird Variable hier doch zweimal gelesen.

1
+00001E9A:   9180011E    LDS       R24,0x011E     Load direct from data space
2
+00001E9C:   2388        TST       R24            Test for Zero or Minus
3
+00001E9D:   F409        BRNE      PC+0x02        Branch if not equal
4
+00001E9E:   C0BD        RJMP      PC+0x00BE      Relative jump
5
68:           flag_1ms--;
6
+00001E9F:   9180011E    LDS       R24,0x011E     Load direct from data space
7
+00001EA1:   5081        SUBI      R24,0x01       Subtract immediate
8
+00001EA2:   9380011E    STS       0x011E,R24     Store direct to data space
9
69:

deshalb 6 Bytes Code mehr. Ansonsten funktioniert identisch.

#7347565
Lesenswert?

Maxim B. schrieb:

(falls Bearbeitung in der Schleife mal zu lange dauert, z.B. eine Zeile auf LCD geschrieben) zu speichern. Deshalb wird in der Hauptschleife flag_1ms nicht mehr auf Null gesetzt, sondern decrementiert.

Warum, ist doch nur ne zusätzliche Fehlerquelle! Da eh nur für den Worst-Case gedacht, reicht es doch das Flag sofort am Anfang des "Task" zu löschen, das fängt auch EINEN IRQ ab.

Maxim B. schrieb:

wird Variable zweimal gelesen: bei Vergleich und für Decrement. Und natürlich kann sie durch ISR zwischen zwei Lesen geändert werden.

Und? Da geht doch nichts verloren! Ist doch egal, ob in der IRQ vor oder nach dem decrement, inkrementiert wird.

Gast #7347567
Lesenswert?

Maxim B. schrieb:

Zwar wird Variable hier doch zweimal gelesen.

Weil sie volatile ist. Vielleicht kannst du das volatile in diesem Fall weg lassen, weil direkt vorher ein cli() steht. Da bin ich nicht ganz sicher. Kannst ja mal ausprobieren und dann den Assembler Code prüfen.

Oder du schreibst es so:

1
    cli();
2
    uint8_t tmp=flag_1ms;
3
    if(tmp) 
4
    {
5
        flag_1ms=tmp-1;
6
        sei();
7
    } else {
8
        sei();
9
    }
1
  90:  f8 94         cli
2
  92:  80 91 00 01   lds  r24, 0x0100  ; 0x800100 <__DATA_REGION_ORIGIN__>
3
  96:  88 23         and  r24, r24
4
  98:  29 f0         breq  .+10       ; 0xa4 <main+0x14>
5
  9a:  81 50         subi  r24, 0x01  ; 1
6
  9c:  80 93 00 01   sts  0x0100, r24  ; 0x800100 <__DATA_REGION_ORIGIN__>
7
  a0:  78 94         sei
8
  a2:  01 c0         rjmp  .+2        ; 0xa6 <main+0x16>
9
  a4:  78 94         sei
OP #7347568
Lesenswert?

Teo D. schrieb:

Warum, ist doch nur ne zusätzliche Fehlerquelle! Da eh nur für den Worst-Case gedacht, reicht es doch das Flag sofort am Anfang des "Task" zu löschen, das fängt auch EINEN IRQ ab.

Wenn z.B. etwas gezählt wird und ein Zählen wird wegen einzige zu lange Schleife verpaßt, wird Zählen nachgeholt: zweimal gemacht, bis Variable wieder 0 ist. Äußerlich passiert dann nichts. Wird Variable einfach gelöscht, dann wird auf eins weniger gezählt. Auch wenn so was nur in einem von 100000 Schleifenzyklen passiert, wird das ärgerlich.

Und? Da geht doch nichts verloren! Ist doch egal, ob in der IRQ vor oder nach dem decrement, inkrementiert wird.

Perfection gibt es leider bei dem Compiler nicht: er versucht immer wieder überflüssige Befehle zu machen... Traurig...

Gast #7347572
Lesenswert?

Maxim B. schrieb:

Perfection gibt es leider bei dem Compiler nicht: er versucht immer wieder überflüssige Befehle zu machen... Traurig...

Ja sieht man, ich war auch gerade enttäuscht. Bei meinem obigen Beispiel liest der die Variable jetzt nur noch einmal, aber insgesamt ist der Code trotzdem nicht kürzer geworden.

OP #7347574
Lesenswert?

Stefan F. schrieb:

Maxim B. schrieb:

Zwar wird Variable hier doch zweimal gelesen.

Weil sie volatile ist. Vielleicht kannst du das volatile in diesem Fall weg lassen, weil direkt vorher ein cli() steht. Da bin ich nicht ganz sicher. Kannst ja mal ausprobieren und dann den Assembler Code prüfen.

Das hat funktioniert! Ich dachte, volatile ist für ISR auf Assembler notwendig, um Var zu sehen. Aber nicht wirklich.

1
67:         if(flag_1ms){
2
+00001E9A:   9180011E    LDS       R24,0x011E     Load direct from data space
3
+00001E9C:   2388        TST       R24            Test for Zero or Minus
4
+00001E9D:   F409        BRNE      PC+0x02        Branch if not equal
5
+00001E9E:   C0BB        RJMP      PC+0x00BC      Relative jump
6
68:           flag_1ms--;
7
+00001E9F:   5081        SUBI      R24,0x01       Subtract immediate
8
+00001EA0:   9380011E    STS       0x011E,R24     Store direct to data space
9
69:           sei();
10
+00001EA2:   9478        SEI                      Global Interrupt Enable
11
70:  
12
***
13
121:          sei();
14
+00001F5A:   9478        SEI                      Global Interrupt Enable
15
+00001F5B:   CF3D        RJMP      PC-0x00C2      Relative jump
16
121:

Nun noch 2 Bytes mehr als mit Inline-Assembler, wegen zusätzlichen Sprung bei else :)

Gast #7347575
Lesenswert?

Maxim B. schrieb:

Das hat funktioniert! Ich dachte, volatile ist für ISR auf Assembler notwendig, um Var zu sehen. Aber nicht wirklich.

Da habe ich noch ein bisschen Angst. Mache da mal eine Wiederholschleife drumherum und kontrolliere, ob die Variable dann immer noch bei jedem Schleifendurchlauf aus dem RAM gelesen wird.

1
#include <avr/interrupt.h>
2
#include <stdint.h>
3

4
uint8_t flag_1ms;
5

6
int main() {
7
    while (1) {
8
        cli();
9
        if(flag_1ms)
10
        {
11
            flag_1ms--;
12
            sei();
13
        } else {
14
            sei();
15
        }
16
    }
17
}

Moment ....

Scheint zu gehen. Glück gehabt.

1
00000090 <main>:
2
  90:  f8 94         cli
3
  92:  80 91 00 01   lds  r24, 0x0100  ; 0x800100 <__DATA_REGION_ORIGIN__>
4
  96:  88 23         and  r24, r24
5
  98:  29 f0         breq  .+10       ; 0xa4 <main+0x14>
6
  9a:  81 50         subi  r24, 0x01  ; 1
7
  9c:  80 93 00 01   sts  0x0100, r24  ; 0x800100 <__DATA_REGION_ORIGIN__>
8
  a0:  78 94         sei
9
  a2:  f6 cf         rjmp  .-20       ; 0x90 <main>
10
  a4:  78 94         sei
11
  a6:  f4 cf         rjmp  .-24       ; 0x90 <main>
OP #7347591
Lesenswert?

Stefan F. schrieb:

Maxim B. schrieb:

Das hat funktioniert! Ich dachte, volatile ist für ISR auf Assembler notwendig, um Var zu sehen. Aber nicht wirklich.

Da habe ich noch ein bisschen Angst. Mache da mal eine Wiederholschleife drumherum und kontrolliere, ob die Variable dann immer noch bei jedem Schleifendurchlauf Zugriff aus dem RAM gelesen wird.

Ich habe einfach schrittweise mit JTAGICE durch Assembler-Listing gekuckt. Bequem, diese Möglichkeit zu haben. Alles wirklich korrekt. Na, 2 Bytes kann ich dem Compiler verzeihen :) Für mich ist aber wichtig, beide Möglichkeiten zu kennen. Selten, aber passiert, daß man Assembler braucht. Bekannte Beispiel: für WS2812. Oder auch wenn man Bitfolge in Byte ändern muß, ist das mit Assembler um mehrfaches sparsamer als rein mit C.

Gast #7347594
Lesenswert?

Maxim B. schrieb:

Ich habe einfach schrittweise mit JTAGICE durch Assembler-Listing gekuckt. Bequem, diese Möglichkeit zu haben.

Ja. Wobei du das auch in gewissem Umfang mit dem Simulator machen kannst.

Für mich ist aber wichtig, beide Möglichkeiten zu kennen. Selten, aber passiert, daß man Assembler braucht.

Es ist gut, ein bisschen Assembler zu können, um genau solche Kontrollen machen zu können. Dann kann man im Fehlerfall verstehen, woran es scheitert und was man dagegen tun kann.

Moderator (Firma: Titel) Persönliche Seite #7347597
Lesenswert?

Maxim B. schrieb:

Deshalb wird in der Hauptschleife flag_1ms nicht mehr auf Null gesetzt, sondern decrementiert.

Was ist an "in der Hauptschleife nur lesen, nicht ändern" so schwer zu verstehen? Das Problem sind immer noch die Read-Modify-Write Zugriffe.

Teo D. schrieb:

Und? Da geht doch nichts verloren! Ist doch egal, ob in der IRQ vor oder nach dem decrement, inkrementiert wird.

Doch, es geht was verloren. Der Ablauf dazu: die Variable hat den Wert 5 und wird in der Hauptschleife (zum Dekrementieren) in ein lokales Register gelesen. Das Register hat nun den Wert 5. Und genau jetzt kommt der Interrupt, unterbricht die Haputschleife, liest die selbe Variable mit dem Wert 5, zählt sie um 1 auf 6 hoch und schreibt sie wieder zurück an ihren Speicherplatz. Damit ist der Interrupt fertig. Die unterbrochene Hauptschleife kommt wieder dran, zählt den Registerwert 5 um 1 runter und schreibt den Wert 4 an den selben Speicherplatz. Rumpeldipumpel, ein Interrupt ging flöten.

Deshalb wird in der Hauptschleife der Zählerstand nur gelesen. Und dann vergleicht man ihn mit einer lokalen Variable, die dann so lange hochgezählt wird, bis sie den selben Wert wie die Interruptzählervariable hat:

1
uint isrv, locv = 0;
2

3
ISR:
4
    isrv++;
5
    reti
6

7
MAINLOOP :
8
    if (isrv != locv) {
9
       machwas();
10
       locv++;
11
    }

Maxim B. schrieb:

Neim Tipp: mach aus dem flag_1ms einen uint32_t Zähler namens millis, zähle den jede ms um 1 hoch und mach es dann so, wie es beim Arduino mit den millis() geht.

Danke für Tipp. Ich möchte aber die langsame Arduino-Variante lieber vermeiden.

Du sollst nicht den Arduino-Code nehmen, sondern die Idee verstehen: der millis() Zähler wird nie zurückgesetzt.

OP #7347599
Lesenswert?

Simulator ist natürlich erste Wahl. Problem kommt, wenn am Anfang von Programm so etwas wie init von LCD steht, mit großen Verzögerungen. Bis Simulator durchkommt... JTAGICE kommt über solchen Stellen blitzschnell. Dafür aber keine Anzeige von verbrauchten CPU-Zyklen...

#7347606
Lesenswert?

Lothar M. schrieb:

Teo D. schrieb:

Und? Da geht doch nichts verloren! Ist doch egal, ob in der IRQ vor oder nach dem decrement, inkrementiert wird.

Doch, es geht was verloren. ....

Ja schon, .... nur der Fehler liegt doch schon darin, das die Abarbeitung länger dauert als der Time-Slot. Wenn da nicht das Abfangen eines IRQ reicht, wird das doch eh zum Irrer-Ivan! Eigentlich darf das NIEMALS eintreten!

OP #7347613
Lesenswert?

Teo D. schrieb:

Ja schon, .... nur der Fehler liegt doch schon darin, das die Abarbeitung länger dauert als der Time-Slot.

Manchmal ist das nicht zu vermeiden. Wichtig daß die Verarbeitung durchschnittlich weniger dauert. Dann können einige seltene Überstunden geduldet werden, wenn ISR nicht vergessen gehen. Hier kann man für ISR-Zähler sowiso nicht weniger als 1 Byte nehmen, deshalb bis ca. 255 ISR können nachgeholt werden. In der Praxis wird das selten über 2.

Ich habe übrigens schon vor einigen Zeit LCD-Programmmodul mit Möglichkeit über Puffer zu scheiben gemacht. Puffer aus dem Programm schnell gefüllt und dann automatisch ohne Eile, ein Zeichen oder Zeilenwechsel pro Millisekunde... Aber das kostet für 20x4 LCD schon satten 80 Bytes, für AVR nicht ganz wenig... Deshalb ist manchmal sinnvoll, einige längeren Verzögerungen dulden zu können.

#7347622
Lesenswert?

Maxim B. schrieb:

Einzges, was mir hier Gedanken macht: Var bei millis darf nicht unter 4 Bytes sein. Und 4 Bytes zu lesen kostet Zeit, gerade an der Stelle, wo cli möglichst kurz sein darf.

Du bist ein Schattenkämpfer. Du willst krampfhaft Probleme lösen, die noch gar nicht da sind und mötglicherweise gar nicht auftauchen.

https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Prinzipien_der_Optimierung

Du machst es genau falsch herum.

OP #7347624
Lesenswert?

Falk B. schrieb:

Du machst es genau falsch herum.

So lebe ich. Ich mache vieles falsch... Gestern habe ich bei Schluss-Amen eine falsche Taste gedruckt... Schande... Gut, daß die Menschen in Thüringen so viel Toleranz haben!

Stefan F. schrieb:

Danach wird er es wie alle anderen machen.

Das mache ich nie. Wie alle anderen. Das widerspricht meiner Persönlichkeit und meinem Beruf.

Moderator (Firma: Titel) Persönliche Seite #7347825
Lesenswert?

Maxim B. schrieb:

Var bei millis darf nicht unter 4 Bytes sein.

Wie schon wiederholt gesagt: mach deine eigenen "millis" mit einem einzigen Byte. Es geht nur um die Idee, die dahinter steckt.

Maxim B. schrieb:

Stefan F. schrieb:

es wie alle anderen machen.

Das mache ich nie.

Eigenartige Einstellung. Wenn ich erkenne, dass jemand die Aufgabe schon optimal gelöst hat, dann ist es doch nicht besonders vorteilhaft, nach der zweitbesten Lösung zu suchen.

EDIT: Maxim B. schrieb im Beitrag #7347574:

dachte, volatile ist für ISR auf Assembler notwendig, um Var zu sehen.

Volatile ist nötig, um dem Compiler mitzuteilen, dass er die Variable oder das Register bei jedem Lesezugriff erneut einlesen muss und eben nicht mit einer lokalen Kopie arbeiten darf. Und natürlich ist in einem Read-Modify-Write auch ein Lese-Zugriff.

Mal nur diesen einfachen Code genommen:

1
uint8_t nv; // nicht volatile globale Variable
2
:
3
nv = 0;
4
nv++;
5
nv++;
6
nv++;
7
nv++;
8
nv++;

Wel die Variable nicht volatil ist und sich deshalb nicht "im Hintergrund" ändern kann, kann der Compiler (wenn man es ihm in den Optimierungseinstellungen erlaubt) aus diesen 5 Zeilen einfach eine einzige machen:

1
nv = 5;

Daraus wird dann sowas:

1
LDI   R24,5   
2
STS   0x0111,R24

Wenn der Code jetzt aber so aussieht:

1
volatile uint8_t nv; // volatile globale Variable
2
:
3
nv = 0;
4
nv++;
5
nv++;
6
nv++;
7
nv++;
8
nv++;

Dann muss der Compiler zuerst den Wert am RAM-Speicherplatz auf 0 setzen, danach muss er diesen Wert wieder vom RAM lesen, ihn inkrementieren und wieder schrieben. Dieses vom RAM lesen, Inkrementieren und ins RAM schreiben muss er weitere 4 mal machen. Denn der Wert an dieser RAM-Speicherzelle (oder der Registerinhalt) kann sich inzwischen ja geändert haben.

Das Ganze wird also dank des volatilen Verhaltens von nv eher länglich:

1
LDI   R24,0   
2
STS   0x0111,R24
3
LDS   R24,0x0111
4
SUBI  R24,-1
5
STS   0x0111,R24
6
LDS   R24,0x0111
7
SUBI  R24,-1
8
STS   0x0111,R24
9
LDS   R24,0x0111
10
SUBI  R24,-1
11
STS   0x0111,R24
12
LDS   R24,0x0111
13
SUBI  R24,-1
14
STS   0x0111,R24
15
LDS   R24,0x0111
16
SUBI  R24,-1
17
STS   0x0111,R24
OP #7348524
Lesenswert?

Lothar M. schrieb:

Maxim B. schrieb:

Var bei millis darf nicht unter 4 Bytes sein.

Wie schon wiederholt gesagt: mach deine eigenen "millis" mit einem einzigen Byte. Es geht nur um die Idee, die dahinter steckt.

Wenn ich richtig verstehe, sollte Zähler von "millis" genug groß sein, um während der Arbeit von Gerät, von Einschalten bis zu Ausschalten, keine Wiederholung kommt? Wie kann man sonst Differenz eingeben? Wenn ich das Gerät z.B. bis zu 8 Stunden betreiben möchte, und Millisekunden als Takt, dann brauche ich bis zu 10006060*8=28800000 = 0x1B77400 zu zählen, schon 4 Bytes notwendig?

#7348527
Lesenswert?

Maxim B. schrieb:

Wenn ich richtig verstehe, sollte Zähler von "millis" genug groß sein, um während der Arbeit von Gerät, von Einschalten bis zu Ausschalten, keine Wiederholung kommt? Wie kann man sonst Differenz eingeben?

Nein. Es muss nur groß genug sein, dass die größte Zeitspanne, die du abmessen willst, reinpasst.

Wenn der Zähler überläuft und dann wieder bei 0 anfängt, ist das kein Problem. Wenn du die Zeitvergleiche richtig gemacht hast.

#7348554
Lesenswert?

Maxim B. schrieb:

Dann sollten 2 Bytes reichen. 1 Byte reicht aber in keinem Fall.

Für flag_1ms reicht doch auch 1 Byte. Dann sollte das auch für das Durchzählen reichen:

1
volatile uint8_t flag_1ms;
2

3
ISR(TIMER0_COMPA_vect) {
4
    flag_1ms++;
5
}
6

7
void main () {
8
    flag_1ms = 0;
9
    // Initialise TIMER0 to trigger COMPA every 1ms
10
    [...]
11
    while (1) {
12
        static uint8_t last_after5ms = 0;
13
        if (flag_1ms-last_after5ms>=5) {
14
            last_after5ms += 5;
15
            // Do something every 5ms
16
        }
17
        static uint8_t last_after15ms = 0;
18
        if (flag_1ms-last_after15ms>=15) {
19
            last_after15ms = flag_1ms;
20
            // Do something every 15ms
21
        }
22
        [...]
23
    }
24
}

Es muss hierbei nur garantiert sein, dass ein Durchlauf durch die while-Schleife nie länger als 255ms dauert.

Übrigens: Die Stelle "Do something every 5ms" versucht eventuelle Verzögerungen aufzuholen; die Stelle "Do something every 15ms" tut das nicht, sondern wartet immer mindestens 15ms.

LG, Sebastian

OP #7348569
Lesenswert?

Hier darf Schleife nie über 1 ms sein, sonst wird ISR verloren. Um ISR nicht zu verlieren, sollte Variable, die in ISR incrementiert wird, in der Hauptschleife decrementiert werden und nicht auf Null gesetzt.

Hätte mir eine Millisekunde gereicht, würde ich bei ISR keine Variable nehmen sondern ein Bit in GPIOR0, das ist von Natur aus atomar. Dann wird das etwa so aussehen:

1
ISR(TIMER3_COMPA_vect, ISR_NAKED){ // Timer 3 interrupt.
2

3
  FLAGG |= (1<<F_PULS);  // Flag fuer Puls einsetzen
4
  
5
  reti(); // da ISR_NAKED
6
}
Gast #7348582
Lesenswert?

Maxim B. schrieb:

Wenn ich richtig verstehe, sollte Zähler von "millis" genug groß sein, um während der Arbeit von Gerät, von Einschalten bis zu Ausschalten, keine Wiederholung kommt?

Nö. Der Zähler muss nur groß genug sein, um die größten Intervalle abzudecken, die man messen will. Ein einzelner Überlauf während der Messung stört nicht, wenn man die Zeitspanne mit einer Subtraktion berechnet. Vor zwei Tagen hatte ich auf einen Artikel verlinkt, der bereits darauf hinwies.

Gast #7348619
Lesenswert?

Maxim B. schrieb:

Hier darf Schleife nie über 1 ms sein, sonst wird ISR verloren. Um ISR nicht zu verlieren, sollte Variable, die in ISR incrementiert wird, in der Hauptschleife decrementiert werden und nicht auf Null gesetzt.

Nö, wieso? Weder auf Null setzen noch dekrementieren ist nötig. Stattdessen merkst du dir die früheren Zählerwerte, und subtrahierst diese vom jetzigen Wert. Und das funktioniert auch über die Überlaufgrenzen hinweg.

LG, Sebastian

Moderator (Firma: Titel) Persönliche Seite #7348745
Lesenswert?

Maxim B. schrieb:

1 Byte reicht aber in keinem Fall.

Was macht dein Programm 255 ms lang?

Maxim B. schrieb:

FLAGG |= (1<<F_PULS); // Flag fuer Puls einsetzen

Und dann das einzelne Bit in der Hauptschleife per Read-Modify-Write wieder zurücksetzen? Was hast du an meinem Tipp "In der Hauptschleife nur lesen!" nicht verstanden?

Du bist da offensichtlich viel zu sehr in deiner Assembler-Flag-Welt, als dass du die Einfachheit und die Geradlinigkeit des von mir vorgeschlagenen Weges sehen würdest. Mein Tipp: denk nochmal ausgiebig über den miilis() Mechanismus nach. Man muss das nicht am ersten Tag vollumfänglich erkennen. Wenn ich ehrlich bin: ich habe auch ein paar Irrwege/Umwege gemacht. Deine Versuche waren auch dabei.

OP #7349022
Lesenswert?

Lothar M. schrieb:

Und dann das einzelne Bit in der Hauptschleife per Read-Modify-Write wieder zurücksetzen?

Nein.

1
  // Bedienung von Puls 1 ms
2
  if( FLAGG & (1<<F_PULS) ){
3
    104c:  f0 9b         sbis  0x1e, 0  ; 30
4
    104e:  fe cf         rjmp  .-4        ; 0x104c <main+0x2a>
5
    FLAGG &= ~(1<<F_PULS); // Flag fuer Puls = 0    
6
    1050:  f0 98         cbi  0x1e, 0  ; 30

Auch ISR wird mit nur sbi auskommen. Deshalb braucht man dafür nichts pushen und popen. Und das ist von Natur aus atomar.

GPIOR0 paßt für Bearbitung von ISR in der Haupttschleife sehr gut. Leider sind GPIOR1 und GPIOR2 bei ATMega1284 nicht mehr in Bit-Bereich, aber mit GPIOR0 kann man das machen (es gibt aber Kontroller, wo alle GPIORs in Bit-Bereich stehen, so wie AT90PWM3B). So dürfen diese ISR (bis zu acht) NAKED sein und deshalb sehr kurz. Ich habe so auch Int2 bearbeitet: dort hängt DS3234. Für RTC sagt ISR nur, daß die Daten erneuert werden, und auch wenn 1x ISR verpaßt wird, passiert nichts.

#7349216
Lesenswert?

Wenn die Verkürzung der Timer-ISR das absolute Ziel ist:

Es geht auch ohne. Stell einen passenden Pre-Scaler ein, verwende direkt den TCNT1 als Zeitbasis... Der wird dann ganz von alleine hochgezählt, und auch beim Auslesen der 16-Bit hilft dir die Hardware, du kannst den Wert ohne IRQ-Sperre lesen.

Und statt Zeiträume/Intervalle mit einem in der ISR gesetzten Flag abzuwarten, könntest du in der main-loop z.B. auch stattdessen ein Output-Compare-Flag abfragen, und dir die Intervalle über die OCR-Register einstellen...

Also: Statt dass die Timer-Hardware ein Flag im Register setzt, die CPU als Reaktion darauf eine ISR aufruft, die ISR das Flag löscht und stattdessen ein Flag im RAM setzt, und dann das Hauptprogramm das Flag im RAM ausliest: Einfach direkt im Hauptprogramm das Hardware-Register-Flag auslesen...

#7349265
Lesenswert?

Εrnst B. schrieb:

Also: Statt dass die Timer-Hardware ein Flag im Register setzt, die CPU als Reaktion darauf eine ISR aufruft, die ISR das Flag löscht und stattdessen ein Flag im RAM setzt, und dann das Hauptprogramm das Flag im RAM ausliest: Einfach direkt im Hauptprogramm das Hardware-Register-Flag auslesen...

Da haste dich aber verrannt.... Egal, hier gibts ja eh nur Kraut und Rüben!

Moderator (Firma: Titel) Persönliche Seite #7349280
Lesenswert?

Maxim B. schrieb:

1050: f0 98 cbi 0x1e, 0 ; 30

Es stimmt zwar, das CBI an sich ist nicht unterbrechbar ist. Das Ganze funktioniert aber nur, wenn die Variable dauerhaft und garantiert in einem Register liegt. Das kannst du nur bei winzigen Mini- oder Demo-Progrämmchen mit wenig Spericherbedarf und wenig Funktionsumfang erwarten. Und das Verriegeln einer solchen Variablen in ein Register auf C-Ebene ist zumindest unschön.

Nochmal: du willst unbedingt die Assembler-Denkweise "Ich muss alles bis aufs letzte Bit selber unter Kontrolle haben!" auf eine höher abstrahierende Programmiersprache übertragen. Das ist der falsche Ansatz, denn dann musst du den Compiler zu Dingen zwingen, die er "von sich aus" anders gelöst hätte.

Aber auch die Compilerbauer sind nicht auf der Brennsuppe dahergeschwommen und haben solche alltäglichen Funktionen natürlich ebenfalls gelöst. Nur eben abstrakter, anders und portabler.

Mein (durchaus erprobter) Ansatz ist es, den Code möglichst ohne Verwendung von speziellen unportierbaren oder schlecht portierbaren Geschichten wie "nacked" ISR zu schreiben. Und wenn sich im Verlauf der Entwicklung herauskristallisiert, dass mir die Rechenzeit knapp wird, erst dann überlege ich, ob (auch im Hinblick auf Erweiterungen) eine händisch hingebastelte Optimierung oder ein größerer µC die bessere Lösung ist.

#7349283
Lesenswert?

Teo D. schrieb:

Εrnst B. schrieb:

Also: Statt dass die Timer-Hardware ein Flag im Register setzt, die CPU als Reaktion darauf eine ISR aufruft, die ISR das Flag löscht und stattdessen ein Flag im RAM setzt, und dann das Hauptprogramm das Flag im RAM ausliest: Einfach direkt im Hauptprogramm das Hardware-Register-Flag auslesen...

Da haste dich aber verrannt....

Nur in dem Sinne, dass der TO etwas über ISRs lernen wollte ;-) Ansonsten ist der Ansatz absolut richtig!

#7349288
Lesenswert?

Lothar M. schrieb:

1050: f0 98 cbi 0x1e, 0 ; 30

Es stimmt zwar, das CBI an sich ist nicht unterbrechbar ist. Das Ganze funktioniert aber nur, wenn die Variable dauerhaft und garantiert in einem Register liegt. Das kannst du nur bei winzigen Mini- oder Demo-Progrämmchen mit wenig Spericherbedarf und wenig Funktionsumfang erwarten.

Nein. Wenn man die GPIO Register oder ein sonstiges Register benutzt, das gerade frei ist und per sbi/cbi erreichber ist, ist das garantiert. Die ATXmega haben sogar extra 8 Stück davon!

Nochmal: du willst unbedingt die Assembler-Denkweise "Ich muss alles bis aufs letzte Bit selber unter Kontrolle haben!"

In der Tat. Ein Kontrollfetischist, der das Gesamtproblem bzw. Ziel längst aus den Augen verloren hat. Wenn er es denn je im Auge hatte. Vermutlich geht es hier nur um den Egotrip und nicht um die Lösung eines realen Problems.

Angehängte Dateien:
#7349296
Lesenswert?

Maxim B. schrieb:

GPIOR0 paßt für Bearbitung von ISR in der Haupttschleife sehr gut. Leider sind GPIOR1 und GPIOR2 bei ATMega1284 nicht mehr in Bit-Bereich, aber mit GPIOR0 kann man das machen (es gibt aber Kontroller, wo alle GPIORs in Bit-Bereich stehen, so wie AT90PWM3B).

Du solltest stattdessen auf einen moderneren wie etwa die AVR-DA/DB/DD Serie wechseln. Da gibt es auch eine RTC oder PIT für solche Aufgaben.

#7349299
Lesenswert?

Teo D. schrieb:

Nein, das löst das "Problem" des TOs nicht,

Warum? Es ging um eine Timer-ISR, die eine Variable für eine Zeitbasis hochzählt. Ob man die jetzt "millis", "systick", "time", "ticker" oder sonstwie nennt, ist ja egal. Und genau das stumpfe Hochzählen eines 16-Bit Wertes, der bei Überlauf wieder bei 0 beginnt, kann der Timer auch in Hardware, ganz ohne ISR.

#7349317
Lesenswert?

Εrnst B. schrieb:

nd genau das stumpfe Hochzählen eines 16-Bit Wertes, der bei Überlauf wieder bei 0 beginnt, kann der Timer auch in Hardware, ganz ohne ISR.

Nur so als Hinweis, auch wenn es nicht von Belang ist, weil der TO ja etwas über ISRs lernen will: verzichtet man auf ISRs und damit auf volatile / memory-barrier / Int-Sperren, ist für den Compiler das ganz Programm wieder zu einem schönen sequentiellen Programm geworden. Wenn dann noch alle Funktionen inline sind (z.B. templates) (gemeint ist hier, dass der Compiler in der TU den Code sieht, sofern nicht LTO angewendet wird), für das i.A. zu wesentlich kleinerem (und besser optimiertem Code), weil "über alles" optimiert werden kann.

#7349432
Lesenswert?

Steve van de Grens schrieb:

Aber Zugriffe auf den Timer Counter sind ohne volatile/cli/memory-barrier nicht atomar. Damit bekommt man wieder die oben demonstrierten Probleme beim Lesezugriff.

volatile-access/memory-barrier haben nichts mit Atomarität zu tun.

memory-barrier hat nichts mit volatile-access zu tun.

Es hilft nichts, wenn man alles planlos in eine Suppe wirft.

#7349475
Lesenswert?

Steve van de Grens schrieb:

Aber Zugriffe auf den Timer Counter sind ohne volatile/cli/memory-barrier nicht atomar. Damit bekommt man wieder die oben demonstrierten Probleme beim Lesezugriff.

Mmh. Auf dem ATMega1284 sind Zugriffe auf die 16-bit TCNTn-Register atomar: "16.3 Each 16-bit timer has a single 8-bit register for temporary storing of the high byte of the 16-bit access. [...] When the low byte of a 16-bit register is read by the CPU, the high byte of the 16-bit register is copied into the temporary register in the same clock cycle as the low byte is read."

LG, Sebastian

OP #7349520
Lesenswert?

Lothar M. schrieb:

Nochmal: du willst unbedingt die Assembler-Denkweise "Ich muss alles bis aufs letzte Bit selber unter Kontrolle haben!" auf eine höher abstrahierende Programmiersprache übertragen. Das ist der falsche Ansatz, denn dann musst du den Compiler zu Dingen zwingen, die er "von sich aus" anders gelöst hätte.

Das stimmt. In einem Orchester gibt es nur ein Dirigent, anders funktioniert es in der Musik kaum. Ich bin so erzogen: ich möchte alles unter Kontrolle haben :)

Persönliche Seite #7349528
Lesenswert?

Sebastian W. schrieb:

Mmh. Auf dem ATMega1284 sind Zugriffe auf die 16-bit TCNTn-Register atomar:

Nein, sind sie nicht. Das zitierte Feature dient nur dazu, auf Timer-Register Glitch-frei zugreifen zu können.

"16.3 Each 16-bit timer has a single 8-bit register for temporary storing of the high byte of the 16-bit access. [...] When the low byte of a 16-bit register is read by the CPU, the high byte of the 16-bit register is copied into the temporary register in the same clock cycle as the low byte is read."

Wenn allerdings zwischen den beiden Zugriffen eine ISR ausgeführt wird, die auf das gleiche Timer-Register zugreift, dann bekommt man Probleme wie bei "normalen" 16-Bit Variablen auch — es sei denn, man trifft entsprechende Vorkehrungen.

OP #7349537
Lesenswert?

Wilhelm M. schrieb:

Du solltest stattdessen auf einen moderneren wie etwa die AVR-DA/DB/DD Serie wechseln. Da gibt es auch eine RTC oder PIT für solche Aufgaben.

Ich habe gestern für Probe auf zweite Win7 letzte Atmel Studio installiert (obwohl ich weiß, daß das sehr schlechtes IDE ist), um zu kucken, ob ich dort bequem mit AVR-DA probieren könnte.

Leider habe ich festgestellt, das für diese AVR keinen Simulator existiert. Man muß immer mit einer physischen Platine arbeiten. Einfach Programmabschnitt ausprobieren, wie mit ATMega1284 u.Ä. üblich, kann man mit AVR-DA nicht. Vielleicht werde ich irgendwann in der Zukunft etwas mit AVR-DA machen, aber erst wenn ich viel sicherer mit C werde.

Im Moment überdeckt die Reihe ATMEGA324-644-1284 all mein Bedarf. Meine laufende Projekte drehen sich um MIDI, dafür reichen die Möglichkeiten von diesen Controller.

OP #7349554
Lesenswert?

Εrnst B. schrieb:

Also: Statt dass die Timer-Hardware ein Flag im Register setzt, die CPU als Reaktion darauf eine ISR aufruft, die ISR das Flag löscht und stattdessen ein Flag im RAM setzt, und dann das Hauptprogramm das Flag im RAM ausliest: Einfach direkt im Hauptprogramm das Hardware-Register-Flag auslesen...

Das hat Nachteil, daß Hardware-Register-Flag von Hardware gelöscht sein kann. Das enspricht meinem Lebenskonzept als "Kontrollfetischist" nicht :)

Persönliche Seite #7349622
Lesenswert?

Maxim B. schrieb:

Εrnst B. schrieb:

Einfach direkt im Hauptprogramm das Hardware-Register-Flag auslesen...

Das hat Nachteil, daß Hardware-Register-Flag von Hardware gelöscht sein kann.

Das Flag wird nur gelöscht, wenn:

  • Eine entsprechende IRQ ausgeführt wird (was du ja machst), oder

  • Das Flag von Hand gelöscht wird.

Du willst also nicht, dass die Hardware das Flag zurücksetzt, verwendest aber ein Schema, das genau das mnacht.

OP #7349670
Lesenswert?

Johann L. schrieb:

Du willst also nicht, dass die Hardware das Flag zurücksetzt, verwendest aber ein Schema, das genau das mnacht.

Ein bißchen anders. In lösche das Flag in der Hauptschleife, während auch Reaktion auf ISR stattfindet. Sinn: Hauptschleife darf schmerzlos von anderen ISR unterbrochen werden. ISR selbst bleibt möglichst kurz, um ISR einander am wenigstens störten...

Übrigens, ich habe die Variante mit GPIOR0 nur als eine Möglichkeit erwähnt. Für ISR, die für Zeit verantwortlich sind, favorisiere ich doch die Variante, wo ISRs gezählt werden, damit auch verpaßte nachgearbeitet werden. Für Anwendungen wie Ereignisse (so wie auf meiner Platine DS3234 angeschlossen wurde. Dort bedeutet ISR nur, daß die Zeitdaten abgelesen sein KÖNNTEN, d.h. einmal verpaßt macht gar nichts. Es sei denn, über ISR von DS3234 wird auch Systemtiming gemacht, das ist auch möglich) reicht Flag in GPIOR aus.

#7349716
Lesenswert?

Maxim B. schrieb:

Ein bißchen anders. In lösche das Flag in der Hauptschleife, während auch Reaktion auf ISR stattfindet. Sinn: Hauptschleife darf schmerzlos von anderen ISR unterbrochen werden. ISR selbst bleibt möglichst kurz, um ISR einander am wenigstens störten...

Genau das kannst du doch auch mit dem Flag im Timer-Register machen. Reagiere in der main-loop darauf, lösche es da. Dann hast du keine Timer-ISR, die irgendwas unterbrechen würde, und andere ISR stören das Ganze auch nicht...

Maxim B. schrieb:

einmal verpaßt macht gar nichts.

genau das Verhalten hast du mit dem "nativen" Flag auch... wenn die Loop nicht dazukommt es abzuarbeiten, bleibt es eben gesetzt, und ein zweiter Compare-Match ändert nichts daran.

Hilft dir aber, wenn wie oben vermutet dein Lernziel "möglichst viele ISR verwenden, egal wie sinnvoll" ist, nicht weiter.

Kannst es ja im Hinterkopf behalten, falls du mal in einer realen Anwendung in eine ähnliche Situation kommst.

OP #7349733
Lesenswert?

Εrnst B. schrieb:

Maxim B. schrieb:

Genau das kannst du doch auch mit dem Flag im Timer-Register machen. Reagiere in der main-loop darauf, lösche es da. Dann hast du keine Timer-ISR, die irgendwas unterbrechen würde, und andere ISR stören das Ganze auch nicht...

Und wenn Timerüberlauf zweimal oder dreimal kommt? Ich kann das wissen nur wenn ISR gezählt werden.

Maxim B. schrieb:

einmal verpaßt macht gar nichts.

genau das Verhalten hast du mit dem "nativen" Flag auch... wenn die Loop nicht dazukommt es abzuarbeiten, bleibt es eben gesetzt, und ein zweiter Compare-Match ändert nichts daran.

Bei externen ISR von DS3234 kommt dann ein Problem: ISR-Pin wird so lange tief gehalten, bis Kommunikation mit DS3234 stattfindet. Das könnte stören, besonders wenn andem gleichen ISR-Pin mehrere Slaaven sitzen.

#7349760
Lesenswert?

Maxim B. schrieb:

Und wenn Timerüberlauf zweimal oder dreimal kommt?

gerade eben hast du noch erzählt, dass das dann egal wäre?

Maxim B. schrieb:

ISR-Pin wird so lange tief gehalten, bis Kommunikation mit DS3234 stattfindet.

Ja, und? den Pin kannst du auch in deiner Main-Loop abfragen, ohne ISR... Vor allem wenn du die eigentliche Abfrage eh aus der Main-Loop heraus machst.

1
while (true) {
2
  ....
3
  if (bit_is_clear(DS_PIN, DS_PIN_NR)) query_ds3243()
4
  ....
5
}

Was bringt es dir, den Pin-Status zuerst in einer ISR in ein Flag umzukopieren, wenn Abfragen des IO-Registers im Hauptprogramm billiger wäre?

#7349762
Lesenswert?

Hallo Maxim,

Maxim B. schrieb:

In einem Orchester gibt es nur ein Dirigent, anders funktioniert es in der Musik kaum.

So weit, so gut.

Dein ursprüngliches Problem war ja, dass dir von Zeit zu Zeit Ereignisse verlorengingen bzw. (beim Vergleich auf >999) zu häufig ausgeführt wurden. Für dieses Problem wurden einige Lösungen genannt:

  1. Variablen volatile deklarieren (hattest du schon gemacht).

  2. Während des Zugriffs auf mit ISR geteilte Variablen größer als 1 Byte außerhalb der ISR die Interrupts sperren, sowohl beim Lesen als auch beim Schreiben. Alternativ 1-Byte-Variablen verwenden, falls ausreichend.

  3. Für Lese/Abfrage/Modifiziere-Konstrukte von mit ISR geteilten Variablen außerhalb der ISR die Interrupts von Anfang bis Ende sperren, falls ein zwischenzeitiger Interrupt (und entsprechende dortige Modifikation der gemeinsamen Variable) die Programmlogik zerstört. Gilt auch für 1-Byte-Variablen. Allerdings wird von Logik, bei der geteilte Variablen von zwei Stellen aus verändert werden, abgeraten.

  4. Anstelle von 1), 2) und/oder 3) einen TCNTn so laufen lassen, dass dessen TOVn in der Hauptschleife detektiert und zurückgesetzt werden kann. Dies ermöglich u.U. aber nur die Prüfung ob ein Überlauf aufgetreten ist, nicht wie häufig.

  5. Anstelle von 4) einen TCNTn ganz langsam laufen lassen, und die Erhöhung dieses TCNTn in der Hauptschleife detektieren (z.B. durch Vergleich mit einem gespeicherten vorherigen Wert von TCNTn). Hierbei können auch mehrfache Erhöhungen erkannt werden.

Allerdings befreit dich all dies nicht von der Notwendigkeit kooperativen Multitasking, die maximale Dauer eines Hauptschleifendurchlaufs (und also jeder einzelnen Aktion im Hauptschleifenkontext) so zu begrenzen, dass keine anderen eventuell notwendigen nebenläufigen Aktionen dadurch behindert werden. Nur so hast du "alles unter Kontrolle".

Und dazu muss man halt für jede einzelne Aktion deren maximale Dauer ausrechnen, und gegebenenfalls in kleinere Teile aufteilen, wie zum Beispiel die Bedienung des Displays. Denn selbst wenn du einen Koprozessor dafür nutzt, muss doch dennoch die Kommunikation mit diesem im Rahmen der genannten Maximalzeit vonstatten gehen.

LG, Sebastian

#7349770
Lesenswert?

Maxim B. schrieb:

Nein. DAS wäre nicht egal. Egal wäre das nur bei ISR, die für Systemtakt nicht verantwortlich sind.

Aber statt der Systemtakt-ISR kannst du direkt den Timer-Wert lesen. Per Prescaler den Timer in einer passenden Geschwindigkeit laufen lassen.

Dann passiert der Überlauf bei 2¹⁶, weiter oben meintest du, das wäre ausreichend (bei ~1kHz). Mit dem Prescaler kannst du dann zwischen besserer Auflösung oder höherer Maximal-Zeit zwischen den Überläufen wählen...

Das umrechnen von Minuten und Sekunden in Timer-Ticks ist dann nicht mehr ganz so einfach, aber da der Compiler nicht mit 10 Fingern geboren wurde, hat er garkein Problem damit auch mit schrägen Werten zu rechnen.

Beitrag #7349774 wurde von einem Moderator gelöscht.
OP #7349784
Lesenswert?

Εrnst B. schrieb:

Aber statt der Systemtakt-ISR kannst du direkt den Timer-Wert lesen. Per Prescaler den Timer in einer passenden Geschwindigkeit laufen lassen.

Ja, ich kann auch das... Nur 1-ms-Takt ist so bequem...

Außerdem möchte ich mit Timer-Wert Tempo bei MIDI wechseln.

Für mich ist interessant: reicht 5-ms-Takt, um die Zeit bei einem Sequenzer zwischen MIDI-Befehlen zu speichern? Oder wird bei 32-Passagen doch Unregelmäßigkeit zum Hören? 3 Bytes MIDI dauern etwa 1 ms, deshalb viel darunter zu gehen hat wenig Sinn. Aber darüber?

Das kann vielleicht nur Versuch zeigen.

Um klar zu machen: es geht mir hier um Urlaub. Damit mein Pfarrer mal ohne mich GDs machen kann.

Moderator (Firma: Titel) Persönliche Seite #7349805
Lesenswert?

Maxim B. schrieb:

Das könnte stören, besonders wenn...

Das erinnert mich an den "Schuh des Manitu", wo es heißt "Was, wenn einer von uns die Masern bekommt?"

Du zerrst hier"Probleme" an Land, die beim System- oder Softwaredesign ganz einfach berücksichtigt werden können.

Wenn das eine gemeinsame Interruptleitung ist, dann muss die Interruptroutine eben so oft durchlaufen werden, bis die Leitung inaktiv wird.

#7349840
Lesenswert?

Maxim B. schrieb:

Nur 1-ms-Takt ist so bequem...

dann bleib halt dabei.

Oben hast du den Eindruck gemacht, du musst unbedingt die letzten paar Taktzyklen aus deinen ISR-Routinen rausquetschen. Und weniger als 0 (in Worten: "Null") Takte bei Verzicht auf die ISRs geht halt nicht, deshalb der Vorschlag...

Musst du als Programmierer wissen, wo ein Interrupt Sinn macht, und wo ein simples Abfragen aus dem Hauptprogramm besser ist.

Gerade die INT-Leitung deines DS3234 wäre ein Paradebeispiel... nur weil das Datenblatt die so bezeichnet, muss der µC die noch lange nicht per ISR auswerten. Vor allem wenn du die Leitung eh erst später im Hauptprogramm zurücksetzt, wenn du den Chip ausliest.

Beitrag #7350205 wurde von einem Moderator gelöscht.
Beitrag #7350253 wurde von einem Moderator gelöscht.
Beitrag #7350264 wurde von einem Moderator gelöscht.

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