Hi zusammen !
Ich quäle mich gerade mit dem MCP2515 herum.
Ich habe kein Hard- oder Softwareproblem... eher ein
"glaubenstechnisches" und hoffe auf ein Paar Anregungen ;-)
Es geht um die Realisierung eines Hausbusses, nur damit ihr das
Datenaufkommen abschätzen könnt. Vorkommen sowohl Tastendrücke,
regelmässige Broadcasts an alle, flashen per Botloader über CAN.
Programmiert wird in Assembler. CAN Baudrate: 125k
Ich schwanke total in der Bearbeitung empfangener Nachrichten:
- Puffer für empfangene Nachrichten im µC SRAM einrichten ja/nein (der
MCP kann ja schon drei Nachrichten halten... andererseits wär ein Puffer
sicher nicht schlecht falls doch mal zufällig was gleichzeitig geschiet
- empfangene Nachricht im Interrupt des µC sofort auslesen und in SRAM
puffern (FIFO,FILO) damit das Empfangsfach des MCP schnell wieder frei
ist
- empfangene Nachricht im Interrupt des µC "abchecken" ob von Bedeutung,
erst dann in SRAM übernehmen
- oder nur im Interrupt schauen welcher Rx-Puffer eie Nachricht hat und
später abfragen
- viele, oder gar jeder, nutzen einen Sheduler um da ein bisschen
Ordnung reinzubekommen und nicht nur ereignisorientiert zu arbeiten. Den
in Assembler zu stricken ist wohl nich so einfach, daher habe ich auch
kaum Code-Beispiele dafür gefunden und somit immernoch nich richtig
geschnallt was mir das Teil bringt.
Ob ich nun eine immer wiederkehrende Schleife habe oder in definierten
Zeitabständen etwas "organisiere" ist doch relativ gleich, oder?
Ich finde überhaupt keinen vernünftigen Gedankenansatz. Bei 125kBaud und
einer Nachrichtenlänge von 128Bit können also theoretisch 1000
Nachrichten pro Sekunde anliegen (unrealistisch, aber es können ja mal
drei oder vier Ereignisse gleichzeitig eintreten).
Baudrate runter, ok. Aber ich will ja gleichzeitige Ereignisse auch
schnell wegkriegen.
Ist das denn ok wenn ich bei der Sache die "Gleichzeitigkeit" heranziehe
und einfachmal annehme das nicht mehr als z.B. fünf Dinge direkt
nacheinander auf dem Bus liegen?
So würde ich natürlich auch programmieren und nicht alle Broadcasts auf
einmal...
Wie geht ihr sowas an? Könnt ihr mich mal ein bissl anschubsen? :-)
thx & greetz
Danny
Hallo,
für mich stellt sich da meist die Frage: was ist "schnell".
In welchem Zeitraum muß ich maximal auf ein Ereignis reagieren können?
Ich weiß nicht was, Dein Hausbus alles handeln soll, aber als Beipiele
vielleicht: Temperatur messen muß ich nicht alle 10ms, so schnell ändert
die sich nicht, ob die Anzeige dann nochmal 1s hinterher hängt, merkt
keiner.
Licht schalten/dimmen per Fernbedienung sollte dagegen nie eine größere
Verzögerung von ca. 0,3s haben, sonst hat man das Gefühl, nicht richtig
auf die Taste gedrückt zu haben.
Ein Einbruchsalarm muß auch nicht innerhalb 10ms gemeldet werden usw.
Ich habe manchmal das Gefühl, es werden Datenmengen erzeugt,
Genauigkeiten gefordert, Reaktionszeiten eingebaut, nur weil es
technisch eben geht.
Letztlich sollte der gewünschte tatsächliche Nutzen entscheiden und der
dazu nötige Aufwand getriben werden, nicht der maximal technisch
mögliche.
Sind aber nur meine Gedanken dazu...
Gruß aus Berlin
Michael
Mit CANopen wird eine Abbildung zwischen CAN-Messages und
SRAM-Speicherobjekten realisiert, d.h. im Speicher steht der Zustand
eines Schalters/Sensors/Aktuators, und CAN-Messages aktualisieren diesen
(rein wie raus). Das korrespondiert recht gut mit den FullCAN
Message-Objekten grösser entwickelter CAN Controller wie im AT90CAN128
oder LPC2000 (hier mal ohne Bugs betrachtet), zumindest solange der
genug Message-Objekte vorgesehen hat.
Mit dem MCP2515 muss man sowas dann im MCP-Treiber erledigen. Generell
gilt aber für fast FIFO-freie Controller die den MCP: Sofort raus aus
dem Buffer. Ob du das dann bereits im Interrupt vgl. CANopen
entsprechend eintütest, oder erst einmal in einen FIFO-Buffer im
Speicher, das hängt nun stark davon ab, ob damit eher
Zustands/Aktivitätsinformationen übertragen werden, oder ob du den CAN
eher als Ethernet für Arme siehst und auch mal grösseren Datenmengen
sequentiellen Charakters dabei sind (z.B. Logfiles auslesen).
@Michael: Und ich versteh immer nich warum man sofort den "Datenhighway"
anprangert obwohl ich nur mal die Gleichzeitigkeit betrachte. :-)
Ich will nicht auf Dauer einen an der oberen Grenze befindlichen
Datendurchsatz erreichen weils technisch möglich ist. Aber der "normale
Zustand" sollte nicht schon in der Planung in einer
"arme-Schwein-Schleife" enden.
Aber: Einen µC über den BUS per Bootloader flashen, in der Zeit bewegt
sich jemand im Flur (Bewegungsmelder) oder es wir eine Temperatur
gebroadcastet und jemand zweites drückt im Keller nen Lichtschalter -
das kann wohl jeden Tag passieren.
Ich will also nicht dauerhaft viel - sondern nur einen Ansturm gut
bewältigen können ohne das mein Licht-Ein-Telegramm verloren geht.
Und in dem einen Moment ist es nun mal ein großer Unterschied ob ich
"nur" die zwei bzw. drei MCP-Speicher nutze oder sofort ins SRAM
kopiere/puffer.
@A.K.: Danke für dein Tip, dann werde ich mich mal ein bissl durch
CANOpen graben ;-)
Datenmengen sind nicht der Normalzustand, aber wenn Sie anfallen
(flashen per Bus oder wie du sagtest LogFiles) will ich keine spürbare
veränderung des Busses sehen (ich hab immer die sich ständig ändernden
Blinktakte einer S5 bei großer Auslastung vor meinem Inneren Auge :-) )
greetz
Danny
Hallo Danny,
>Datenmengen sind nicht der Normalzustand, aber wenn Sie anfallen>(flashen per Bus oder wie du sagtest LogFiles) will ich keine spürbare>veränderung des Busses sehen (ich hab immer die sich ständig ändernden>Blinktakte einer S5 bei großer Auslastung vor meinem Inneren Auge :-) )
das sollte kein Problem darstellen, wenn Du die Prioritäten der CAN-msgs
vernünftig planst. Ungefährer Anhaltspunkt:
Prio 1: sehr wichtige msgs
Prio 2: msgs mit geringem Datenaufkommen
Prio 3: msgs mit mittelgroßem Datenaufkommen
Prio 4: msgs mit sehr großem Datenaufkommen
In Prio 2 fallen alle "normalen" msgs, z.B. Tastendrücke,
Temperaturdaten, etc.
In Prio 4 fallen z.B. Log-Daten-Übermittlung, Bootload-Daten.
So kommen Tastendrücke auch noch an, wenn z.B. gerade größere Bootmengen
über den Bus gehen.
Ich habe bei mir Eingangs- und Ausgangs-Fifos im RAM angelegt. Warum
darauf verzichten? Ansonsten musst Du bei jeder Aktion, die Dein mc
macht, aufs Timing schielen.
Beispiel 2:
Ich habe Graphikdisplays mit 128*64 Pixel verwendet. Wenn ich das
komplette Display update, dann sind das > 1024 Schreibzugriffe über SPI.
Ich habe 4Mhz Taktfrequenz, also brauche ich Minimum 2ms für diese
Aktion.
>- empfangene Nachricht im Interrupt des µC "abchecken" ob von Bedeutung,>erst dann in SRAM übernehmen
Dann ist es sinnvoller, die Masken des MCP2515 zu benutzen. Dann
bekommst Du erst gar keine Interrups.
>- oder nur im Interrupt schauen welcher Rx-Puffer eie Nachricht hat und>später abfragen
Das bringt gar nichts. Was machst Du später mit der Info, dass da mal
eine CAN-msg war, aber leider Dein main zu spät gekommen ist und die msg
bereits überschrieben wurde?
Zur Geschwindigkeit:
Je langsamer die Datenübertragung, desto langsamer kann auch der mc
getaktet sein, ohne dass er Timingprobleme bekommt. Und je weniger Strom
verbraucht er dann. Das war für mich u.a. ein Grund, eine sehr langsame
Übertragungsrate zu wählen.
Warum willst Du alles in Assembler machen?
Meine Erfahrung: das Programm selbst bekommst Du locker in einen
ATmega16 rein - auch in C ist das kein Problem. Selbst ein ATmega8 wird
wohl gehen (den kann man nicht jtag-debuggen - deshalb kommt der für
mich nicht in Frage). Vom Flash-Bedarf wird es erst problematisch, wenn
Du texte und Graphiken benötigst. Und die brauchen in Assembler und C
denselben Platz.
Versteh mich nicht falsch: ich habe früher sehr viel in Assembler
gemacht. Irgendwann habe ich dann gemerkt, dass > 70% der Bugs typische
Assembler-Bugs waren. In C programmierst Du 1. schneller und 2.
wesentlich bugfreier.
Viele Grüße, Stefan
Ich betreibe so einen Bus in meinem Auto (komplett eigenständig,
Fahrzeug hat nichtmal CAN). Dabei haben fast alle Module einen Mega8 in
C programmiert, der Flash reicht da mehr als genug, wenn nicht gerade
ein LCD mit Menüführung dranhängt (wie Stefan ja auch schon sagte)
Ich habe den MCP2515 zwar an einem IRQ Pin, aber das benutze ich nur um
den Controller aufzuwecken. Die Nachrichten hole ich alle in der Main()
ab. Wenn man sie in einer ISR abholt, dann muss man meiner Meinung nach
auch einen sehr großer Buffer anlegen in die einige Nachrichten passen,
wenn man nur eine einzige Nachricht dort speichern kann bringt es IMHO
keinen Vorteil gegenüber dem holen aus der Main. Im Gegenteil, evtl.
leert man den Buffer aus dem MCP und wenn dann vor dem erneuten
durchlaufen der Main() eine neue Nachricht kommt ist die alte weg.
Ich würde mir einfach mal den Worstcast Fall durchrechnen. Also wielange
habe ich vom abholen der Nachrichten Zeit bis wieder 3 Nachrichten im
Buffer liegen können. Wenn es sein kann, dass die main() Schleife nicht
in dieser Zeit mindestens einmal wieder durchlaufen wurde, dann muss man
was am Timing ändern.
Ich weiß nicht, was du bei dir alles in den Knoten machst, aber bei mir
sind die Zeitaufwendigsten Dinge eigentlich AD Wandlungen, alles andere
wird ja eh nur angestossen und irgendwann wieder abgestellt unterbricht
die Main Schleife aber nicht wirklich.
Ich komme damit bisher auf jedenfall sehr gut klar. Auch ohne gleich so
große Dinge wie CANopen oder so zu implementieren.
Gruß Philipp
@Stefan: Danke für Deine ausführliche Antwort :-)
zur Priorisierung: Klar, auf CAN-Ebene problemlos durch den MCP
erledigt. Aber anschliessend habe ich immernoch 3 direkt aufeinander
folgende Nachrichten im µC. Oder hast du bei -jeder- deiner
µC-Aktionen,Berechnungen oder was auch immer sichergestellt das es auf
Grund der niedrigen Baudrate bis zum möglichen Eintreffen des nächsten
Datenpaketes abgearbeitet ist?
Wenn ich nun noch im eigentlich Programm Prioritäten unterscheiden
soll... mh ich weiss nich...
Die Messages per MCP zu filtern habt ihr mir vor nem Jahr ausgeredet ;-)
fand ich seit dem auch nich mehr so dolle, denn der kleine UP-Controller
soll evt doch mehr mitbekommen als nur Broadcast und eine Lampe.
Somit ist es glaub ich gradlinieger (vom Konzept her) alles einzulesen
als bei jedem Knoten andere Filter und Masken zu haben. (ja gefühlssache
;-) )
Zum Assembler: Du kennst den Aufwand den so ein Hausbus mit sich bringt
:-) würdest du den Weg gemeinsam mit einer dir fremden
Programmiersprache beschreiten wollen? Ich bin mir sicher das ich mehr
C-Anfängerfehler mache als deine "typischer Assemblerfehler" denn wenn
ich fertige C-Programme sehe streiche ich zur Zeit echt die Segel :)
Bin noch in der Umbauphase des Hauses und möchte wenn ich ma Zeit und
Lust hab nun nebenbei programmieren. Und ich möchte als erstes das
Flashes über CAN realisieren um schon von beginn an hier beim testen
nich immer wegen des umsteckens verrückt zu werden :-) und das trau ich
mir in C nicht zu momentan, bzw hab einfach keine Lust da auch noch mit
anzufangen
greetz
Danny
so viel Text zu einem so einfachen Thema :-)
CAN-Bus mit 125k - hihi, da lacht doch jeder Controller nur drüber.
Überlegt mal, eine Standardbotschaft dauert da ungefähr 1,2 ms (grob
gerechnet 150Bit). Eher kommt auch keine neue. Was bitte soll den mit
den Daten geschehen, dass die in 1ms nicht weggeschaufelt/verarbeitet
sind?
Einen struct anlegen (aus ID, data_length, remote-bit und 8
Datenbytes), in der ISR per aus dem CAN-Controller in den struct
schreiben, ein Bit setzen, dass der main mitteilt, dass neue Daten da
sind. Die kann dann ganz in Ruhe damit was anstellen, vielleicht sogar
erst mal in eine andere Variable kopieren, damit der neue Empfang nicht
die alten Daten überschreibt. Und wenn man dann noch die Filter im MCP
entsprechend setzt, hat der MC noch viel weniger zu tun.
Das ganze Programm besteht fast nur aus Langeweile/im Kreis hopsen oder
sleep des MC.
Genauso seh ich es auch crazy horse. Allerdings finde ich nicht, dass
man die Nachricht in der ISR abholen muss und dann ein Bit setzen. Was
bringt das?
Das Bit setzt der MCP eh indem er solange die IRQ Leitung auf low hält
bis die Nachricht abgeholt wurde, dass kann ich genauso in der Main
prüfen und da dann die Nachricht holen und gleich verarbeiten. Es bringt
ja keinen Vorteil die Nachricht in der ISR zu holen, wenn man eh nur
eine Puffert und diese erst in der Main verarbeitet.
Gruß Philipp
> das sollte kein Problem darstellen, wenn Du die Prioritäten der CAN-msgs> vernünftig planst.
Das hat meiner Meinung nach nichts mit den CanId-Prioritäten zu tun.
Dannys Problem war ja, dass innerhalb einer gewissen Zeit mehr
Telegramme kommen können, als er gerade_ _jetzt verarbeiten kann. (Im
Mittel müssen die aufgelaufenen Telegramme natürlich abgearbeitet sein,
sonst ist da konzeptionell was schief).
Wenn man von einer Hauptprogrammschleife von 10ms ausgeht, d.h. nur alle
10ms nachsieht, ob und welche Telegramme ankamen und diese auf einen
Schlag verarbeitet, und in der Zwischenzeit z.B. das LCD aktualisiert,
dann können ~16 CanMsgs eingehen, bevor erneut nachgesehen wird.
(Annahmen: 125kBit/s; kurze Botschaften: ~80Bit; -> ~16CanMsg pro 10ms).
Diese 16 müssen dann natürlich zwischengepuffert werden.
> In C programmierst Du 1. schneller und 2. wesentlich bugfreier.
Volle Zustimmung. Wenn du C erstmal etwas begriffen hast, dann wirst du
dich fragen, warum du dich solange mit Asm abgekämpft hast. :-)
> Die Messages per MCP zu filtern habt ihr mir vor nem Jahr ausgeredet ;-)> Somit ist es glaub ich gradlinieger (vom Konzept her) alles einzulesen> als bei jedem Knoten andere Filter und Masken zu haben. (ja gefühlssache> ;-) )
War damals nicht die Begründung, dass die Rechenzeit durch
Softwarefilter eher vernachlässigbar ist? :-) Ich überschlage nochmal
kurz:
CanInterrupt: 20µs (oder weniger; um zu erkennen, ob relevant und in den
Puffer holen) * 16Interrupts pro 10ms. => 320µs/10ms => 3.2%
Prozessorauslastung (das ist absolute "worst case"-Betrachtung. Bei
kürzeren Interruptlaufzeiten (z.B. weil das Telegramm nicht für den
eigenen Knoten bestimmt ist) entsprechend (deutlich) weniger; bei
kürzeren Taskzyklen entsprechend mehr...
Aber das hat ja crazy horse schon ausgeführt...
Naja 16 Nachrichten halte ich für ziemlich großzügig. 10ms sind ja
selbst bei mur 1MHz 10000 Takte. Ich denke man muss einfach schauen was
der Knoten macht und wielange die main benötigt. Schaltet er einfach nur
eine Pin auf High, wenn eine bestimmte Nachricht kommt (sowas wie
Schalter an -> Lampe an) und bei einer anderen wieder aus, dann braucht
man bestimmt keinen Puffer. Wenn er aber was kompliziertes macht und
wirklich 10ms für einen main() Zyklus braucht sieht das komplett anders
aus. Ich denke eine generelle perfekte Lösung gibt es da nicht.
Gruß Philipp
@and_ref: Nachdem ich so viel Anderes im Kopf hatte die letzte Zeit muß
man ersma wieder reinkommen ins Thema :-) Ich sag ja, ich brauch nun n
Anstoß ;-)
Ich hatte das folgendermaßen überschlagen vorhin: 1000Msg/Sek möglich.
Wie ich ja schon schrieb nicht auf Dauer aber das mal drei bis fünf
direkt aufeinanderfolgen sollte man denk ich einplanen.
Ihr rechnet in Zeit, ich in Takten. Das Abrufen einer kompletten Msg per
SPI inkl. schreiben ins SRAM, dauert ~250Takte.
Nun muß das ganze gelesen und geprüft werden. Sagen wir mal wieder
~250Takte. Dann haben wir bei einem Controller mit 1MHz bereits 50%
Auslastung (weil ja nur 1000Takte in 1ms). Wenn nun noch ne CAN-Antwort
generiert, über den SRAM und anschliessend per SPI raus muß liegen wir
bei geschätzten 100%.
Bei 2Mhz sind 50%, bei 4Mhz 25%... aber auf die berechneten 3% komm ich
nur schwer und mit einem zugedrückten Auge.
Das wäre bei direkter Abarbeitung vor der nächsten Nachricht so. Daher
denke ich das ein puffern (Ringpuffer ~8Nachrichten) nötig ist im
Gegensatz zu Phillips Auffassung. Oder hab ich mich irgendwie
verrechnet?
Klar, dannach habe ich Zeit den Stapel abzuarbeiten.
zum Thema C: Würdet ihr mir tatsächich raten jetzt bei diesem Projekt
mit C zu beginnen? Ihr könnts sicher beurteilen wie schnell ihr
reingekommen seid. Ich hab ein wenig Angst nach 2 Wochen son bissl die
Lust zu verlieren weil ich weiterkommen möchte aber mit der Sprache
nicht klar komm. Und denn mach ich nämlich alles nochmal in asm...
greetz
Danny
Hmm, das klingt doch plausibel was du sagst, wenn du wirklich 500Takte
benötigst um eine Nachricht zu verarbeiten, dann gehts aber nur (wie du
ja auch schon gesagt hast) mit Ringpuffer bzw. mit einem Puffer der mehr
als eine Nachricht speichern kann. Naja und 100% sind ja nicht verboten,
also wenn du deine 1000 Msg/s noch schaffst ist doch alles ok, auch wenn
der Controller dazu voll ausgelastet ist.
Ich wohl muss wohl dringend mal bei mir nachzählen... :)
@Philipp: wie gesgat is ja kein Dauerzustand, aber möchte ich
berücksichtigen. Denn irgendwann kommen mal n paar auf einmal und denn
möchte ich nich das die Außenbeleuchtung die ganze Nacht unbemerkt
brennt weil was verloren ging....
denn krempelt man doch wieder alles um :)
greetz
Danny
ps: nun habt ihr mich soweit... ich schnüffel wieder mal im C-Tutorial
:D
gut, wenn man die Taktfrequenz beliebig klein wählt, wird irgendwann die
einfache Addition zweier 8bit-Zahlen zu einem Zeitproblem...:-). Wer
schreibt dir denn 1MHz vor??
Ich habe mal eine CAN-Bridge gebaut, da hängen 2 MCP2515 an einer SPI,
Mega16.
Eine ziemliche Anzahl von Botschaften von beiden Seiten, viele davon im
10ms-Raster. Bus mit 500kHz. Es mussten ein Auto und ein
Motorsteuergerät miteinander verheiratet werden, die eignetlich gar
nicht zueiander passen. Also dem Motorsteuergerät das dazugehörige Auto,
dem Auto das entsprechende Steuergerät vorspielen. Konkret gelöst wurde
das so:
-Empfang beider Seiten per Interrupt, je ein 1-msg-Buffer
-alle für beide Seiten zu sendenden Botschaften wurden als RAM-Abbild
gehalten (ID und data-length fest)
-einen Sendetimer, der entsprechend der erforderlichen Intervalle die
Botschaften nach beiden Seiten verschickt, ganz stur.
-bei Empfang Daten nach diversen Umrechnungen und Bitschubsereien
passend in dei Matrix der Gegenseite eintütet
-nebenbei ein paar zus. Sensoren auswerten und dummy-Daten generieren
Da es ein einmaliges Projekt war und nicht allzuviel kosten sollte (und
auch nicht für den öffentlichen Strassenverkehr war), wurde nie eine
echte worst-case-Betrachtung gemacht. Der MC lief mit 16MHz und hatte
noch jede Menge Luft. Ausfälle sind mir nie berichtet worden.
@Philipp
> schaltet er einfach nur eine Pin auf High, wenn eine bestimmte Nachricht> kommt (sowas wie Schalter an -> Lampe an) und bei einer anderen wieder> aus, dann braucht man bestimmt keinen Puffer.
Die Notwendigkeit eines Puffers ergibt sich nicht daraus, was ein
empfangenes Telegramm im Knoten auslöst, sondern wie lange die
Abarbeitung der empfangenen Telegramme maximal verzögert werden kann.
Und darauf hat der Scheduler (Hauptprogrammschleife) einen großen
Einfluss.
> man muss einfach schauen was der Knoten macht und wielange die main> benötigt.
Ich würde unbedingt davon abraten die Hauptprogrammschleife sofort
wieder starten zu lassen, wenn der letzte Durchgang gerade_ _eben
beendet war. Damit jittert die Laufzeit des Programms und es ergeben
sich nichtdeterministische lustige Effekte. Das ist der Horror bei der
Fehlersuche und bei Erweiterungen.
Also: Ein zyklischer Task mit festen Aufrufabständen muss her! Bleibt
Laufzeit am Ende übrig, so wird dieser eben aktiv "verwartet".
In deinem Konzept (ohne Puffer) muss der Programmfluss mindestens alle
1ms bei der Can-Abfrage vorbeikommen, sonst könntest du Telegramme
verpassen. Wenn du jetzt deine Software erweiterst musst du das immer im
Hinterkopf behalten - und schonmal garnix mit LCD-Ansteuerung, die mal
eben 2ms braucht.
> Wenn nun noch ne CAN-Antwort generiert, über den SRAM und anschliessend> per SPI raus muß liegen wir bei geschätzten 100%.
Oh, stimmt. Hatte vergessen, dass die Kommunikation mit dem externen
CAN-Controller auch noch Zeit braucht. Deshalb verwende ich integrierte
CAN-Controller. :-)
> Das Abrufen einer kompletten Msg per SPI inkl. schreiben ins SRAM,> dauert ~250Takte.
Bei 16MHz bist du dann aber wieder im grünen Bereich... Oder du machst
für "Lichtschalterknoten" eine strengere CanMsg-Filterung und senkst
dadurch die Prozessorlast. Den Knoten für die PC-Anbindung kannst du
dann auch mit 16MHz laufen lassen.
> Würdet ihr mir tatsächich raten jetzt bei diesem Projekt mit C zu> beginnen?
Wenn nicht jetzt, wann dann? ;-)
Natürlich reisst du dir damit eine weitere große Baustelle auf. Zumal
zur C-Programmierung noch der formale Kram kommt, wie z.B. Compiler
einrichten, Makefile, usw.. Das sollte man auch nicht unterschätzen.
Irgendwann wird sich der zusätzliche Zeitaufwand amortisieren - das wann
hängt davon ab, wie schnell du in C reinkommst. ;-)
Ich meinte das mit dem nur Pin setzen ja bezogen auf die main() und
diese würde dann besonders kurz und auch schnell sein.
Wenn er einfach bei eintreffen der Nachricht "Schalter xy gedrückt" die
Lampe einschalten möchte (mal ganz vereinfacht jetzt alles). Dann wird
das sicher nicht mehr als 1ms in Anspruch nehmen.
Mit LCDs habe ich zB beim Einschalten usw nicht gewartet bis das Display
soweit ist sondern habe dann einfach x main durchläufe gewartet.
Dadurch, dass ich die main immer sofort wieder starte ist diese Zeit
zwar nicht exakt gleich, aber meist reicht es ja, wenn min. zB 2ms
eingehalten werden. Und das kann ich garantieren.
Wobei ergeben sich denn noch Probleme durch eine einfach endlosschleife
statt timergestütztem Neustart der main? Und hast du ein Konzept wie man
soetwas am besten angeht?
@and_ref: durch unsere Diskussion vor nem Jahr bin ich deinen Gedanken
gar nicht sooo fern ;-)
mh... aber mal ne Frage die ich beim letzten Mal schon nich ganz
verstanden hab. Du sprichst nun ja auch wieder von nem Sheduler.
Versteh ich das richtig: Das is im Grunde nur eine per Timerinterrupt
aufgerufene Schleife die in definierten Zeitabständen alles überprüft?
also-> SRAM auf neue Nachrichten, wenn vorhanden eine davon abarbeiten
-> Tasten einlesen und gemäß Entprellung weiterverarbeiten
-> Zeit hochzählen für Zeitrelaisfunktionen
-> nochwas machen
WARTEN und nach definierter Zeit von vorn.
Wo ist darin der Sinn? Ich lege mir zeitrelevante Dinge in ein TimerISR
(Tastenabfrage, Zeit zählen) und den Rest mache ich in einer
Hauptschleife (evt. sag ich ja auch bald main dazu :D ) im Grunde
ununterbochen. Wo genau ist nun der Vorteil ALLES streng rhytmisch zu
machen? Hast du ein greifbares beispiel für "nichtdeterministische
lustige Effekte"?
greetz
Danny
> Wobei ergeben sich denn noch Probleme durch eine einfach endlosschleife> statt timergestütztem Neustart der main?
Ein Beispiel gibst du ja selbst:
> Mit LCDs habe ich zB beim Einschalten usw nicht gewartet bis das Display> soweit ist sondern habe dann einfach x main durchläufe gewartet.
Dein x ist davon abhängig was noch so alles in main gemacht wird.
Irgendwann entfällt ein Programmteil oder läuft z.B. wegen
Compileroptimierung oder sonstigen Änderungen schneller als bisher und
schon verlierst du jedes 100.000ste CanTelegramm. Das merkst du erst,
wenn das Aussenlicht wieder die ganze Nacht gebrannt hat. :-) Und den
Fehler wirst du bestimmt nicht auf die Änderung von vor 2Monaten in
einem völlig unbeteiligt scheindenen Programmteil schieben...
> Und hast du ein Konzept wie man soetwas am besten angeht?
Ich leite alle Zeiten (z.B. Entprellzeiten für Taster; Timeoutzeiten;
Sekundentakt für Uhr...) von den 10ms ab indem ich in der entsprechenden
Routine einfach zähle wie oft diese durchlaufen wird. Ich brauche keine
weiteren Timerinterrupts mehr einzustellen.
Die 10ms erzeuge ich aus einem 1ms-Timer, der im Timerinterrupt eine
Variable hochzählt, die von Main dann laufend gepollt wird und bei
">=10" den Task10ms() aufruft und die Variable um 10 dekrementiert.
@Danny:
> Versteh ich das richtig: Das is im Grunde nur eine per Timerinterrupt> aufgerufene Schleife die in definierten Zeitabständen alles überprüft?
nicht im Interrupt, sondern in der Hauptschleife, damit Interrupts
dazwischen kommen können. (s.o.)
> Wo genau ist nun der Vorteil ALLES streng rhytmisch zu machen?
Es ist besser planbar. Wird die Laufzeit durch einen Programmteil für
einen Durchgang länger (z.B. komplexe Berechnung nur wenn "Uhrzeit <
7.00Uhr" ist oder bei bestimmtem eingegangenen CanTelegramm o.ä.), so
wird diese Zeit von der Reservezeit abgezogen, die am Ende sowieso
gewartet wird, bevor der nächste Durchgang beginnt.
Somit hat jeder Durchgang (in etwa) den gleichen zeitlichen Abstand zu
seinem Vorgängerdurchgang.
Beispiel: Schalten der Rolladenrelais; Vorgabe: zwischen dem Schalten
zweier Relais müssen z.B. 400ms vergehen.
Im 1. Durchlauf: Abschalten des ersten Relais
Im 40.Durchlauf: Einschalten des zweiten Relais
Dazwischen liegen genau 400ms.
In der "schnellen Hauptschleife" schwankt die Zeit sehr stark in
Abhängigkeit der Uhrzeit (siehe Beispiel oben; <7.00Uhr). Es ist
überhaupt nicht vorhersagbar, wie lange das wirklich dauern kann. 100ms
würden die Relaiskontakte stark belasten (Funkenbildung...) und 800ms
wird der Benutzer deutlich merken...
> Hast du ein greifbares beispiel für "nichtdeterministische lustige> Effekte"?
Ich konstruiere mal: Dein Programm entprellt einen Taster über 100x
"schnelle" Hauptschleifen. (angenommen das heisst, du musst für die
Dauer von 100x Hauptschleife die Taste gedrückt halten). Das
funktioniert auf dem Schreibtisch immer. Im Verbund mit anderen Knoten
steigt die CanInterruptLast und die Laufzeit von 100x Schleifen
verdoppelt sich. Du drückst die Taste aber gleichlang und wunderst dich,
dass der Tastendruck nicht erkannt wird bzw. die Lampe nicht angeht -
aber nicht jedesmal, sondern nur sporadisch. Falls dir das zu abstrakt
ist, dann nimm die 100x Schleifen zur Erkennung eines "langen"
Tastendrucks.
Oder andersrum: Der Tastendruck verschickt seit der letzten
Umkonfiguration nicht mehr die Information "Licht an", sondern "Licht
toggeln". Da deine Entprellung seit jeher nicht stabil funktioniert (da
laufzeitabhängig; hast du ja garnicht gemerkt) fällt dieses Problem erst
jetzt auf... und bevor du auf die Idee kommst, das alles mit
Hardwaretimern erschlagen zu wollen, soviele Timer gibts nicht. Und über
Softwaretimer ist es eigentlich genau das was ich schreibe.
Übrigens: In der Wartezeit bis zum nächsten Durchgang musst du natürlich
nicht ausschliesslich auf das "jetzt geht's los"-Flag pollen, sondern
kannst noch andere kurze Dinge tun (Zufallszahlen berechnen; naja
eigentlich fällt mir nichts sinnvolles ein :-). Nennt sich dann
Idle-Task und die läuft dann wieder quasi wie deine jetzige
Hauptprogrammschleife. Auch hier kann man wieder das beste aus beiden
Konzepten verwenden.
hmm, das klingt wirklich überzeugend. logische Verknüpfungen kann man
dann zB in dem Idle Task lassen.
So entfällt auch das genaue Abschätzen der Taktzyklen die benötigt
werden usw. man muss nur sichergehen das alles geschafft werden kann.
Ich bin überzeugt :)
Ich habe bei mir aber jetzt eine CanDispatch Funktion die eine CAN
Nachricht verarbeitet. Wenn ich diese Funktion jetzt aber nur noch
zyklisch aufrufe, dann gewinne ich ja durch den Buffer keine Zeit. Oder
sollte man diese Funktion dann so gestalten dass sie erst wiederkehrt,
wenn sie den ganzen Buffer abgearbeitet hat? (Momentan bekommt die immer
eine Nachricht und im nächsten main die zweite) Dann hängt der
Controller aber ggf. für eine ganze Zeit dortdrin und versäumt evtl.
etwas an den IO Pins, was vorher problemlos mit Polling abzufragen war.
(So ein Pin Polling würde ich auch in diesen Idle Task legen, oder
sollte man dann nur noch Dinge pollen die zB länger als 10ms anliegen
damit man sie mit dem Sheduler problemlos erkennn kann?)
Wie gestaltet man denn das abarbeiten des Puffers am geschicktesten?
PS: Was mir gerade noch einfällt. Man könnte sogar eine Überlast LED
ranmachen. Wenn alles normale läuft geht der ms Zähler ja nie über 10,
weil er bei 10 wieder die main anstösst und diese ihn zurücksetzt. Läuft
die main aber mal länger als 10ms ist der Zählerstand auch größer. So
hat man eine einfache Kontrolle, falls mal was nicht richtig klappt
edit2:
oder setzt du den Zähler gar nicht zurück und machst nur sowas wie:
if (!counter%10) task_10ms;
if (!counter%100) task_100ms;
usw?
@and_ref: es beruhigt mich das meine Art zu programmieren deiner nicht
so fern ist wie ich zunächst dachte.
Alles was zeitabhängig gezählt wird habe ich auch per TimerISR
hoch/runtergezählt und in einer Schleife ich sag mal wie viele einzelne
Statemachines abgefahren.
Zusammengefasst wird dein main regelmässig durchlaufen, springt so die
routinen regelmässig an in denen du zählst.
Und ich habe bisher immer im TimerISR gezählt und in ner Endlosschleife
ausgewertet.
Ich denke mal die Funktion ist gleich sicher, auch ohne das das
Aussenlicht ne Nacht durchbrennt ;-) aber deines scheint eleganter,
gerade was Übersicht und andere Interrupts angeht.
nun nochmal ne dusselige C-mal-sehn-Anfängerfrage ;-)
Du sagst du durchläufst die main regelmässig.
Wenn ich was versuche zu programmieren wird immer bei main gestartet,
das heisst ich muss in main mit den ganzen initialisierungen beginnen.
dann kommt ein label ab dem meine ganzen funktionsaufrufe stehen,
anschliessend nehme ich ein while oder was auch immer und bilde
innerhalb der main eine schleife die nur schaut ob die regelmässige zeit
um ist. wenn ja springt er wieder zum label.
Sieht so im Kern das aus was wir besprochen haben?
Denn wird ja im Grunde nicht die main regelmässig durchlaufen, sondern
nur sagen wir mal der letzte Teil der main, seh ich das richtig?
thx & greetz
Danny
ps: ich find es echt klasse sich hier so mit Gleichgesinnten zu
unterhalten. Auch wenns, man möge mir verzeihn, nicht mehr nur um die
eigentliche Frage geht. Macht echt Spaß mit Euch :-)
wow, jetzt ist es mal wieder soweit, dass man kaum mit dem Lesen
nachkommt. Hatten wir das nicht schonmal vor 2 Jahren ? :-))
Zu den Puffern:
Ich finde sie deshalb wichtig, weil ich mir mit ihnen gerade NICHT
ständig Gedanken um das Timing machen muss. Ob eine Aktion 10us dauert,
weil nur ein Port zu setzen ist oder mehrere ms, weil eine
Graphikausgabe stattfindet, ist damit letztendlich egal.
Wichtig finde ich übrigens nicht nur den Eingangspuffer. Der
Ausgangspuffer ist genausowichtig: wenn mhrere msgs gleichzeitig
verschickt werden, soll main() nicht warten müssen, bis die letzte msg
verschickt ist, sondern gleich nach dem Kopieren der msgs in den Puffer
weiterarbeiten können.
Zu den CAN-Mask-Registern:
Ich selbst benutze diese auch nicht. Alle msgs landen bei mir erstmal im
Puffer, überflüssige werden dann einfach weggeschmissen. Das fand ich
sicherer, weil dann keine msgs "irgendwie" verschwinden können (wenn man
bei der Programmierung der Mask-Register was falsch gemacht hat).
Ich finde sie nur dann eine Option, wenn man ansonsten Timingprobleme
befürchtet (sollte man "eigendlich" bei einem Hausbus aber nicht
bekommen).
Zum Scheduler:
Auf meiner Hardware laufen mehrere virtuelle "Module", die für einzelne
Funktionen zuständig sind, z.B. das Einlesen eines Tasters oder das
Schalten eines Relais. Jedes dieser Module besitzt 3 Funktionen:
on_init()
on_timer()
on_msg(msg)
Wenn eine msg eintrifft, prüft der Scheduler, für welches Modul diese
ist und leitet sie SOFORT an das Modul weiter - an die on_msg(msg)
Funktion.
Zusätzlich prüft der Scheduler im x-ms-Raster, ob der Timer abgelaufen
ist und ruft dann die zyklische Funktion on_timer() der Module auf.
Das hat folgenden Vorteil: Jede msg wird sofort verarbeitet.
Gleichzeitig gibt es für jedes Modul ein festes Zeitraster.
Die Programme für die Einzelfunktionen werden dadurch sehr einfach, und
können ziemlich unabhängig vom Restprogramm geschrieben werden.
Viele Grüße, Stefan
@Stefan: ja sorry, ich wollte es ja eigentlich allgemein halten weils
nur um die Bearbeitug des MCP2515 gehen sollte... und wieder
abgeschweift :-D
> Wenn eine msg eintrifft, prüft der Scheduler, für welches Modul diese> ist und leitet sie SOFORT an das Modul weiter - an die on_msg(msg)> Funktion.
mit "msg eintrifft" meinst du das sie im selbst gebastelten SRAM-Puffer
liegt? also das Auslesen des MCP und übertragen in den SRAM-Puffer ist
in diesem Beispiel schon geschehen?
würde bedeuten:
- main brummt vor sich hin
- Interrupt vom MCP wird erzeugt, kurz per ISR abgefragt und z.B. ein
Rx0-Flag in einer Globalvariable des ATMega gesetzt
- diese wird im main laufend gepollt und wenns da is wird das
MCP-Rx0-Register ausgelesen, in den ATMega-Puffer geschrieben und beide
(MCP- und ATMega-) Flags rückgesetzt
- nun merkt die main durch n anderes Flag das in den Puffern ne Msg.
liegt und überprüft diese, sollte sie von Belang sein wird die
zuständige Funktion aufgerufen
Hab ich das so richtig verstande? Ich hab schon so oft versucht dein
C-Code zu verstehen :-) (danke nochmal für die Veröffetlichung) immer
gescheitert - aber nicht an Dir sondern wegen fehlenden C-Kenntnissen...
thx & greetz
Danny
@Danny:
Nein:
Wenn ein IR vom MCP2515 kommt, lese ich die CAN-msg sofort (im IR) in
einen msg-Puffer aus. Der wird wie ein Fifo verwaltet.
In main() frage ich dann nur noch, ob im Puffer eine msg vorhanden ist.
Nur das Flag <CAN-msg received> aus dem MCP2515 zu holen, bringt Dir
nicht viel. Dann kannst Du den MCP2515 auch fast komplett ohne IR
pollen.
Wenn Du dagegen das msg-holen komplett in den MCP2515-Interrupttreiber
verlegst, dann wird der Zugriff auf die msgs aus dem Hauptprogramm
einfacher und timing-unkritischer.
>Hab ich das so richtig verstande? Ich hab schon so oft versucht dein>C-Code zu verstehen :-) (danke nochmal für die Veröffetlichung) immer>gescheitert - aber nicht an Dir sondern wegen fehlenden C-Kenntnissen...
Sehr gerne :-)
Ich bin leider immer noch nicht ganz fertig, im Moment schreibe ich
gerade die letzten Änderungen am Bootloader. Sobal alles einigermassen
vollständig und aufgeräumt ist, will ich es nochmal veröffentlichen
(gpl). Naja, zumindest der Bootloader wird nicht mehr lange dauern ...
Viele Grüße, Stefan
Wie arbeitet ihr den Puffer eigentlich ab?
Wenn man bei jedem Aufruf der main(9 nur eine Msg holt, dann wächst
evtl. der Puffer bis zum Überlauf und der Controller langweilt sich
trotzdem.
Wenn man den kompletten Puffer jedesmal leer arbeitet, dann kann es doch
wieder passieren, dass man die main() nicht in dem Zeitschlitz zuende
bekommt?
Oder seh ich da noch etwas völlig falsch?
Vielen Dank
Gruß Philipp
@Philipp: nein vom Grundsatz siehst Du es richtig.
Aber genau das ist ja der Punkt den ich schon mehrfach versucht habe zu
verdeutlichen:
Es geht mir nicht darum dauerhaft dieses hohe Message-Aufkommen auf
dem Bus bewältigen zu können. Dies Aufkommen wird schon auf Grund der
Anwendung nicht erreicht und wenns n Dauerzustand wäre hast du Recht muß
alles im Zeitraum bis zur nächsten CAN-Message abgefrühstückt sein.
Es geht mir lediglich darum einen Moment mit hohen Aufkommen ohne
Probleme verarbeiten zu können.
Nur um für den Fall gerüstet zu sein das mal drei bis fünf CAN_Messages
direkt aufeinander folgen. Kommt vermutlich sehr selten vor, ist aber
nicht auszuschliessen... (zum Beispiel parametrieren oder flashen per
CAN, laden von Logfiles und gleichzeitig drückt jemand nen Schalter)
greetz
Danny