Forum: Mikrocontroller und Digitale Elektronik Wie Prozesse zeitlich gleichmäßig gestalten? Atmega 8


von Attila C. (attila)


Lesenswert?

Hallo!

Mein Programm soll:

-RC Signal eines Kanals auswerten
-I2C einlesen
-eine ADC Wandlung durchführen
-PID berechnen
-an ein LCD ausgeben
-Zeit vertrödeln (hierauf bezieht sich meine Frage)
-Ergebnis der PID Regelung an Servos ausgeben


Da die Vorgänge unterschiedlich lange dauern müsste der Punkt "Zeit 
vertrödeln" sich so verändern das ein "Durchlauf" immer gleich lange 
dauert und somit auch die Abtastrate der PID Regelung bildet.

Wie mache ich das am geschicktesten? Ich überlege alles in die ISR eines 
Timers zu packen bin aber nicht sicher ob es der richtige Ansatz ist?

Vielen Dank!

von Uwe S. (de0508)


Lesenswert?

Hallo Attila,

ich mache das meistens so, dass ich eine Zeitscheibe für die langsamen 
Prozesse definiere, so dass die Summe aller Ausführungszeiten unter 
meiner Zeitscheibe, sagen wie 5ms liegt.

Alles was schnell abgearbeitet werden muss, z.B. Uartdaten in eine 
Puffer schreiben und daraus lesen, wird über die zugehörigen 
Interruptroutinen realisiert.

Am langsamsten wird bei die die LCD Ausgabe sein, speziell, wenn man 
Clear Screan oder Cursor Home verwendet.
1
bool_t timer_tick = false;
2
3
int main(void) {
4
  // init timer
5
6
  while(1) // main - loop
7
  {
8
    // schnelle ereignisorientierte Prozesse
9
    if (event_uart) {
10
11
    }
12
13
    // zyklische Prozesse
14
    if (timer_tick) {
15
      timer_tick = 0
16
      // Prozess 1
17
      // Prozess 2
18
      // Prozess 3
19
      // usw.
20
    } // endif
21
  } // while
22
23
  return 0;
24
}

von Teo D. (teoderix)


Lesenswert?

Nach dem Schema:
ISR
  Set Flag xxmS
  Set Flag yymS
...

Main
  If Flag xxmS
    ...
  If Flag yymS
    ...
...

von Falk B. (falk)


Lesenswert?

Siehe Multitasking.

von Jobst M. (jobstens-de)


Lesenswert?

Attila Ciftci schrieb:
> Ich überlege alles in die ISR eines
> Timers zu packen

Jo. Wenn in einem Durchlauf für alle Funktionen nicht die Zeit eines 
IRQs überschritten wird.


Gruß

Jobst

von Uwe S. (de0508)


Lesenswert?

Naja Jobst,

das ist doch nicht gut, so kann man auf externe Ereignisse, die sich per 
Interrupt ankündigen, nicht mehr reagieren. Und man verliert evtl. auch 
einige Events.

Für Interrupt-Routinen gilt weiterhin, so wenig Code wie möglich, so 
viel wie absolut nötig.
Z.B. in einer Uartbearbeitung das neue Zeichen in ein FiFo zu speichern, 
incl. der Fifo-Index-/Zeigerverwaltung.

von user (Gast)


Lesenswert?

Hört sich nach einem Betriebsystem an was du suchst, oder wenigstens den 
scheduler davon

von Jobst M. (jobstens-de)


Lesenswert?

Uwe S. schrieb:
> so kann man auf externe Ereignisse, die sich per
> Interrupt ankündigen, nicht mehr reagieren.

Wieso sollte man nicht können? Ich mache sowas ...
Ich unterbreche Interrupts auch durch Interrupts - Wenn es nötig sein 
sollte.


Uwe S. schrieb:
> Für Interrupt-Routinen gilt weiterhin, so wenig Code wie möglich, so
> viel wie absolut nötig.

Naja, das ist aber nur eine grobe Faustformel. Ich kenne Projekte, wo 
das ganz anders ist ...


Gruß

Jobst

von Uwe S. (de0508)


Lesenswert?

Hallo Jobst,

klar kann man, wenn man alles über die AVRs und den AVR GCC weiss, aber 
der TO benötigt doch erst mal Starthilfe und keine Sonderfälle.
Oder wie sieht Du das ?

von Attila C. (attila)


Lesenswert?

Leute!

Vielen Dank für den Input. Ich fummel mal etwas damit herum und melde 
mich wenn ich nicht weiterkomme!

Da ich das Auslesen der RC Signale so gestaltet habe das solange gezählt 
wird wie die Pins high sind und somit im schlechtesten Fall 4,5 ms (3 
Servos) rumgetrödelt wird, muss ich mich erst mal darum kümmern bevor 
ich eure Ansätze durchprobieren kann.

Vielen Dank!

von Falk B. (falk)


Lesenswert?

Ein RC-Signal ist ein Puls mit 1,5-2,5ms Dauer. Den kann man SPIELEND 
nebenbei mit der Input Capture Funktion und dem dazugehörigen 
Interrupt messen.

von Attila C. (attila)


Lesenswert?

Hallo Falk,

ja sicher, wenn mein 16bit Timer nicht schon damit beschäftigt wäre 
Signale zum steuern von Servos zu bauen. Aber mal sehen, vielleicht 
schaffe ich es ja ihm zu erklären das er beides abwechselnd machen soll! 
:-)

von Falk B. (falk)


Lesenswert?

Mit etwas geschickter Programmierung kann der 16 Bit Timer beides 
gleichzeitg tun, zumal es ja in beiden Fällen um den gleichen Signaltyp 
geht (Servosignal).

von Attila C. (attila)


Lesenswert?

Ok es geht dann wohl nicht ohne Hilfe:

Wie erkläre ich meinem Timer das er erst das eine und dann das andere 
machen soll?

Im Moment macht er dies um 2 Servos (möglich sind 4) zu steuern:

ISR (TIMER1_COMPA_vect)
{
  static int channel=0;

  channel=channel+1;

  switch(channel)
  {
    case 1:
    PORTB=0b00000001;
    OCR1A=ocr[0];
    break;

    case 2:
    PORTB=0b00000010;
    OCR1A=ocr[1];
    break;

    case 3:
    PORTB=0b00000000;

    break;

    case 4:
    PORTB=0b00000000;
    channel=0;
    break;

    default:
    PORTB=0b00000000;
  }
}

von Falk B. (falk)


Lesenswert?

@ Attila Ciftci (attila)

>Wie erkläre ich meinem Timer das er erst das eine und dann das andere
>machen soll?

>Im Moment macht er dies um 2 Servos (möglich sind 4) zu steuern:

Was hast du denn für einen Prozessor? Die größeren haben 2 oder mehr OCR 
Einheiten am 16 Bit TImer, da kann man die Servosignale rein in Hardware 
generieren.

von Attila C. (attila)


Lesenswert?

Man soll ja laut Forenregeln den Prozessor im Betreff angeben ;-) ;-) 
;-)
Es ist ein Atmega 8

von Falk B. (falk)


Lesenswert?

Oh, da hab ich wohl den Wald vor lauter Bäumen nicht gesehen 8-0

Naja. Trotzdem die Frage, an welchen Pins deine Servos hängen? ZWEI 
Kanäle kann auch der ATmega8 per Hardware mit minimaler CPU-Last 
erzeugen, nämlcih OCR1A und OCR1B. Man muss nur den Vorteiler passend 
einstellen, damit das alles passt.

Wenn man das so macht, hat man auch den Timer für die ICP Funktion ganz 
normal zu Verfügung. Wie das dann geht, siehe hier.

High-Speed capture mit ATmega Timer

von Attila C. (attila)


Lesenswert?

Falk! Du siehst den Wald tatsächlich nicht vor lauter Bäumen! :-) :-) 
:-) Schau mal auf meinem Codeschnipsel :-) :-) :-) Es sind PB0 und PB1. 
Das werde ich jetzt ändern und dann an deinem Vorschlag arbeiten wie 
auch den Artikel lesen.

Habe ich das so richtig verstanden:

-Timer für die Servos configurieren
-Timer an
nach einem Durchlauf
-Timer aus

Timer für Input capture configurieren
-Timer an
nach einem Durchlauf
-Timer aus

Ist das so richtig? Und wie erkläre ich dem Timer das er bei steigender 
Flanke loszählen soll? Mit INT0? Am Port D hängt aber mein LCD dran :-(

Nichts für ungut mit dem Wald! :-)

von Falk B. (falk)


Lesenswert?

@ Attila Ciftci (attila)

>Falk! Du siehst den Wald tatsächlich nicht vor lauter Bäumen! :-) :-)
>:-) Schau mal auf meinem Codeschnipsel :-) :-) :-) Es sind PB0 und PB1.

Ja und? Ich hab irgendwann einfach keine Lust mehr auf Raten, Glaskugel 
lesen und Reverse Engineering. DU willst was von Forum, nicht umgekehrt. 
Siehe Netiquette.

>Habe ich das so richtig verstanden:

Nein.

>-Timer für die Servos configurieren
>-Timer an

Ja.

>nach einem Durchlauf
>-Timer aus

NEIN! Der läuft dauerhaft! OCR und ICP funktionieren PARALLEL!

>Ist das so richtig? Und wie erkläre ich dem Timer das er bei steigender
>Flanke loszählen soll?

Gar nicht. Bei ICP wird der Zählerstand automatisch von der Hardware in 
ein Register kopiert. Im Interrupt ein paar Mikrosekunden später kopiert 
man das in eine Variable und konfiguriert die aktive Flanke des ICP um. 
Beim nächsten Interrupt kann man die Zählerdifferenz bilden und hat die 
gesuchte Pulsbreite. Dann wieder ICP Flanke umkonfigurieren und das 
Spiel beginnt von vorn.

> Mit INT0? Am Port D hängt aber mein LCD dran :-(

Für ICP MUSST du zwingend den direkt dafür vorgesehenen Pin nutzen, das 
geht nicht anders. Das LCD kann man mit jeder beliebigen Pinkompbination 
ansteuern, selbst wenn die über alle Ports verstreut wären. Peter Fleury 
hat das vor Jahren vorgemacht.

von Attila C. (attila)


Lesenswert?

Vielen Dank Falk! Und sorry noch mal: War echt nicht bös gemeint! :-)

von Attila C. (attila)


Lesenswert?

Hallo Falk

Also ich habe jetzt herumprobiert und bin zu dem Schluss gekommen das 
das so nicht geht wie Du das vorschlägst. Wahrscheinlich ist das aber 
falsch und ich bin nur zu blöd.

Es kommt doch nur der normal mode des 16bit Timers in Frage, richtig?

von Falk B. (falk)


Lesenswert?

@Attila Ciftci (attila)

>Also ich habe jetzt herumprobiert und bin zu dem Schluss gekommen das
>das so nicht geht wie Du das vorschlägst.

Doch, aber es ist noch ganz trivial.

> Wahrscheinlich ist das aber falsch und ich bin nur zu blöd.

Das hast du gesagt ;-)

>Es kommt doch nur der normal mode des 16bit Timers in Frage, richtig?

Ja.

Man muss die beiden Servosignale über die Output Compare Funktion 
zeitversetzt erzeugen. Damit kann man nacheinander im OCRx Interrupt die 
Register jeweils neu laden. Parallel dazu läuft der ICP Interrupt und 
kümmert sich um die Pulsmessung.

In etwa so (nur als Skizze)

1
volatile uint16_t servo1, servo1_pause, servo1_gap;
2
volatile uint16_t servo2, servo2_pause, servo2_gap;
3
volatile uint16_t rc_input_width;
4
5
ISR (TIMER1_COMPA_vect) {
6
  if (TCCR1A & (1<<COM1A0)) {  // steigende Flanke aktiv
7
    OCR1A += servo1;
8
    TCCR1A &= ~(1<<COM1A0);    // Umschalten auf fallende Flanke
9
  } else {                     // fallende Flanke aktiv  
10
    OCR1A += servo1_pause;
11
    OCR1B  = OCR1A + servo2_gap;
12
    TCCR1A |= (1<<COM1A0);     // Umschalten auf steigende Flanke
13
  }
14
}
15
16
ISR (TIMER1_COMPB_vect) {
17
  if (TCCR1A & (1<<COM1B0)) {  // steigende Flanke aktiv
18
    OCR1B += servo2;
19
    TCCR1A &= ~(1<<COM1B0);    // Umschalten auf fallende Flanke
20
  } else {                     // fallende Flanke aktiv  
21
    OCR1B += servo2_pause;
22
    OCR1A  = OCR1B + servo1_gap;
23
    TCCR1A |= (1<<COM1B0);     // Umschalten auf steigende Flanke
24
  }
25
}
26
27
ISR (TIMER1_CAPT_vect) {
28
  static uint16_t time_rise;
29
30
  if (TCCR1B & (1<<ICES1)) {   // steigende Flanke aktiv
31
    time_rise = ICR1;
32
    TCCR1B &= ~(1<<ICES1);     // Umschalten auf fallende Flanke
33
  } else {                     // fallende Flanke aktiv  
34
    rc_input_width = ICR1-time_rise;
35
    TCCR1B |= (1<<ICES1);      // Umschalten auf steigende Flanke
36
  }
37
38
  TIFR = (1<<ICF1);            // Flag nochmal loeschen, wegen Umstellung der Flanke
39
}


Deine Aufgabe ist es jetzt, den zeitliche Ablauf der 
Servosignalerzeugung mal aufzuzeichnen und die Zeiten, welche oben im 
Quelltext verwendet werden, einzuzeichnen. Damit erkennst du dann auch, 
wie diese Zeiten berechnet werden müssen.

Wenn du fertig bist, solltest du ein Bild deines Zeitablaufs incl. 
Beschriftung hier senden. Viel Erfolg!

von Amateur (Gast)


Lesenswert?

Ein Zeiter kann bequem, mehrere Aktionen ausführen.

Ein einfaches Beispiel mit Einzelereignissen.

volatil int Aktion1 = 0;
volatil int Aktion2 = 0;
volatil int Aktion3 = 0;

ISR ZaehlRunter () {
  If Aktion1 {
    --Aktion1;
    If !Aktion1 {
      HauRein1();
      // Reload wenn zyklisch gearbeitet wird
    }
  }
  If Aktion2 {
    --Aktion2;
    If !Aktion2 {
      HauRein2();
    }
  }
  If Aktion3 {
    --Aktion3;
    If !Aktion3 {
      HauRein3();
    }
  } // von ISR()

In main () {
  ...
  Aktion1 = 50;   // Warte 50 Ticks und hau einen Drauf
      // one reload
}

von Attila C. (attila)


Lesenswert?

Hallo Falk,

dein Vorschlag klappt ausgezeichnet! Ich habe deinen Code für den Input 
Capture so übernommen und es funktioniert. Vielen Dank dafür! Hätte ich 
selber so nicht hinbekommen!

Die Steuerung der Servoc habe ich allerdings so gelöst und ich hoffe es 
spricht da nichts gegen. Funktionieren tut es auf jeden Fall:

ISR (TIMER1_OVF_vect)
{
  PORTB |=(1<<PB1)|(1<<PB2);
}

ISR (TIMER1_COMPA_vect)
{
  PORTB &= ~(1<<PB1);
}

ISR (TIMER1_COMPB_vect)
{
  PORTB &= ~(1<<PB2);
}

Es hat den Vorteil das ich mir die Pins an denen das Signal anliegt 
aussuchen kann und ich glaube mit etwas gefummel könnte man auch mehr 
als 2 Servos anfahren. Ist aber noch unausgegoren.

von Pandur S. (jetztnicht)


Lesenswert?

Ein LCD muss man nicht am Stueck ansteuern. Ich lass das auch im Main 
auf den Tick von 10ms machen. Ein byte aufs mal an den LCD. Gesteuert 
durch einen Zustandsmaschine.

von Falk B. (falk)


Lesenswert?

@ Attila Ciftci (attila)

>dein Vorschlag klappt ausgezeichnet! Ich habe deinen Code für den Input
>Capture so übernommen und es funktioniert.

Schön.

>Die Steuerung der Servoc habe ich allerdings so gelöst und ich hoffe es
>spricht da nichts gegen.

Doch.

> Funktionieren tut es auf jeden Fall:

Aber nicht reibungslos!

Denn das Problem ist hierbei, dass je nach Zufall und Signallage die 
Interrupts sich gegenseitig verzögern, und somit ein Jitter in deinen 
Servosignalen entsteht. Gute Servos folgen dem und zittern dann auch, 
mal mehr mal weniger.

>Es hat den Vorteil das ich mir die Pins an denen das Signal anliegt
>aussuchen kann

Sicher, aber 2 Pins mit Draht umlegen ist besser.

>und ich glaube mit etwas gefummel könnte man auch mehr
>als 2 Servos anfahren. Ist aber noch unausgegoren.

Kann man, aber dazu braucht es noch etwas mehr Grips. Der Trick ist, 
dass die Servopile zeitversetzt erzeugt werden. Dann reicht auch ein OCR 
Interrupt und etwas Logik. Is aber im Endeffekt der gleiche Ansatz wie 
du jetzt schon hast.

von Martin S. (led_martin)


Lesenswert?

Falk Brunner schrieb:
> Kann man, aber dazu braucht es noch etwas mehr Grips. Der Trick ist,
> dass die Servopile zeitversetzt erzeugt werden. Dann reicht auch ein OCR
> Interrupt und etwas Logik.

Wurde bei den Fernsteuerungen ursprünglich auch so gemacht, man hatte ja 
nur einen Funkkanal, da gingen die Pulse der x Fernsteuer-Kanäle 
nacheinender auf den Sender, und der Empfänger hat die dann nur 
nacheinender auf die einzelnen Ausgänge geschaltet, deshalb hat man den 
Impuls ja auch deutlich kürzer gemacht, als die Periodendauer. Bei 
manchen Empfängern kann man das als sogenanntes 'Summensignal' 
abgreifen, wenn man die Daten mehrerer Kanäle weiterverarbeiten möchte, 
z.B. in einem Qadrokopter.

Mit freundlichen Grüßen - Martin

von Attila C. (attila)


Lesenswert?

Hallo!

Ein kurzer Nachtrag:

Dank google und ebenda ein paar Freaks habe ich folgendes gefunden:

"Der Mega48 macht nur die Kanalbildung und der Mega169 die Aufbereitung 
des PCM Signals. Reden über Uart miteinander.
Format: 115200,8 N,1. LSB first   was fürn Zufall."

Und genau da habe ich meinem RC Empfänger angezapft und lese jetzt alles 
per UART ein.

Genial! Ich kann alle RC Kanäle einlesen UND mit meiner ursprünglichen 
Verwendung des 16bit Timers zig Servos ansteuern!

:-)

von Attila C. (attila)


Lesenswert?

Hallo!

Da habe ich mich deutlich zu früh gefreut und komme hier leider nicht 
weiter:

Jetzt ist es so das der RC Empfänger zwar ungefähr alle 20ms die Daten 
per UART raustut aber eben leider nicht gleichmäßig.

Meine einzige Idee ist dem Controller ein 30ms Zeitfenster zu geben um 
die Daten abzuholen und beim nächsten 30ms Zeitfenster den Rest 
abzufeiern. Da kann ich aber die Signale für die Servos auch "zu Fuss" 
mit delays bauen so uneffizient wie der Ansatz ist.

Wer hat einen Rat?

Danke!

von Attila C. (attila)


Lesenswert?

Da es höflich ist die threads auch "abzuschliessen":

Ich lese die UART Daten jetzt per Interrupt aus. Es war nicht schlau 2 
ms auf den Empfang zu warten. Jetzt bin ich "unabhängig" von der Taktung 
der Ausgabe beim RC Empfänger.

Ich schalte den 16bit Timer für die Servos im Overflow Interrupt des a 8 
bit Timers an und am Ende des 16 bit OCRA interrupts wieder aus.

Im Moment gebe ich also mit einem Konstanten Intervall Updates an die 
Servos und hoffe so eine Zeitkonstante für eine noch zu implementierende 
PID Regelung
zu haben!

Velen Dank an alle, ich habe mal wieder sehr viel gelernt!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.