-
Notifications
You must be signed in to change notification settings - Fork 4
CMD_Messenger_Explanation
Der Messenger hat zwei Buffer, streambuffer und commandbuffer. Zur Zeit sind die beim Mega streambuffer[96] und commandbuffer[96]. Das ist definiert über MAXSTREAMBUFFER=96 und MESSENGERBUFFERSIZE=96. Beim beim ProMicro und Uno ist MESSENGERBUFFERSIZE=64, für den ProMicro hatte ich das im PullRequest schon erhöht da das LCD nciht funktionierte (siehe unten).
Pro loop() wird feedinSerialData() aufgerufen. Damit wird der Inhalt vom serialBuffer in den streambuffer kopiert, und zwar beginnend an der ersten Stelle [0]. Danach wird der streambuffer Byte für Byte bis ans Ende an processLine() übergeben und dann "geleert".
Innerhalb von processline() wird jedes Byte in den commandbuffer geschrieben, beginnend an der ersten Stelle. Zusätzlich wird jedes empfangene Byte auf EndOfMessage geprüft. Sollte das vorliegen, dann wird innerhalb von processline() die entsprechende Funktion aufgerufen. Nachdem die abgearbeitet ist wird der commandBuffer "gelöscht" und es werden die nächsten Bytes empfangen (im serielBuffer kann ja noch etwas stehen) und ebenfalls jedes Byte geprüft, kopiert und auf Ende geprüft. Kommt kein EndOfMessage bleibt der commandBuffer mit einer Anzahl Bytes gefüllt, der stremBuffer ist dann leer und die Funtion feedinSerialData() wird verlassen.
In der nächsten loop() können dann wieder UART Daten vorhanden sein. Dann wiederholt sich (fast) das Spiel. Die Daten kommen in den streamBuffer (ist ja vom Durchgang vorher leer) und werden Byte für Byte an processline() übergeben. Da im commandBuffer aber noch etwas steht (vorher wurde ja kein EndOfMessage detektiert), werden die ankommenden Daten Byte für Byte hinten angehängt. Und jedes Byte wird wieder auf EndofMessage geprüft. Kommt eines, dann wird die entsprechende Funktion aufgerufen, der commandBuffer gelöscht und weitere Daten wieder von der ersten Stelle an hinein kopiert. Solange bis der streamBuffer (der ja da Abbild des serialBuffer ist) abgearbeitet ist.
Was folgt daraus:
- da der streamBuffer eine "Kopie" des seriellen Buffers ist, braucht der streamBuffer auch nicht größer zu sein. Bevor der serialBuffer neu abgefragt wird ist dieser ja komplett abgearbeitet. MAXSTREAMBUFFER braucht nicht größer zu sein als der serialBuffer. Beim Mega/ProMicro/Uno ist dieser 64Byte groß, MAXSTREAMBUFFER kann also auf 64 reduziert werden. Wahrscheinlich kann er noch kleiner sein, der er ja nicht überlaufen kann (wird abgfragt) und unvollständige Kommandos dann in zwei (oder mehr) Durchläufen der loop()/feedinSerial() abgearbeitet werden. Allerdings kann sich dadurch ein Timimg Problem ergeben (siehe unten).
- der commandBuffer muss mindestens so groß sein wie das längste Kommando. Dies ist in diesem Fall das beschreiben eines 20x4 LCD's. Das Kommando setzt sich aus der FunktionsID (2 Byte da in ASCII übertragen wird), dem Komma, der LCD Nummer (wieder zwei Byte), einem weiteren Komma, 80Byte Daten und abschliessend ein Semikolon zusammen. Das macht dann in Summe 87 Byte. Die jetzige Größe von 96 Byte passt, könnte sogar leicht reduziert werden. Das habe ich mit ändern der Buffergröße auch so verifiziert.
Beim Timing wird es interessanter. Bei 115200 Baud braucht kommen 11.520 Byte pro Sekunde (8 Bit plus Start-/Stopp Bit = 115200/10). 1 Byte braucht damit 0,086ms. 64 Byte passen in den serialBuffer, also nach 5,5ms ist der serialBuffer voll. D.h. eine loop() darf nicht länger dauern! Und mehr als 64 Byte können ja beim LCD kommen.
Bei den Inputs habe ich ja schon vorgesehen, dass diese nicht in einer loop() gelesen werden (u.a. lastEncoderRead = millis() +2;). Ausnahme sind die Encoder, die müssen jede loop() gelesen werden. Tja, wie lange dauert das? Bei kleinen/mittleren Configs auf jeden Fall kleiner als 1ms, das habe ich schon mal beobachtet. Wohin es bei großen Configs geht fällt mir schwer abzuschätzen, ggf. packe ich meinen LA mal aus und lasse einen Pin jede loop() toggeln.
Bei den Outputs sehe ich bis auf das LCD kein Problem, die anderen sollten schnell genug gehen. Beim LCD wird es aber Interessant. Alles was ich lese und im Code nach verfolge führt auf eine I2C Frequenz von 100kHz und einem I2C Sendebuffer von 32 Byte. Ist vom CMDMessenger ein Kommando für das LCD empfangen worden, dann liegen ja alle Daten vor (EndOfMessage wurde detektiert, s.o.) Diese werden an die lcd Funktion übergeben und diese wiederum bedient in einer Schleife den I2C Bus. Dort gehen die Daten aber nur mit 100kHz raus, "schlimmer" ist aber dass das LCD ja im 4Bit Modus läuft. Für jedes "Datenbyte" müssen zwei Byte über den I2C gesendet werden. Bei 100kHz und ~90 Byte Daten (80 Byte Inhalt plus etwas Overhead) sind das 1000 Bit (Start-/Stopp gibt es so zwar nicht, aber es gibt glaube ich auch Anfangs-/End Zustände) braucht man 9ms. Ich habe aber momentan keine Idee wie ein Buffer Overflow vom vom I2C Buffer vermieden wird. Aber das scheint ja zu funktionieren :) Also die 9ms gehen so gerade für ein LCD aus. Die ersten 5,5ms = 64 Byte sind ja "nur" kopieren von Byte. Dann kommen die restlichen 23 Bytes die dann zum verarbeiten der Daten kommen. D.h. die 9ms laufen jetzt los, und in diesen 9ms können nur noch 64-23=41 Byte empfangen werden. Setzen eines Outputs passt passt sicherlich, aber ein zweites LCD direkt hinterher führt zum Overflow des serialBuffer!
Was folgt hieraus:
- Verstehen warum das LCD bei 100kHz funktioniert
- Das kann man "vermeiden", wenn man den I2C Bus auf 400kHz stellt. Dann ist der I2C Bus bezogen auf das LCD (Doppleung der Bytes) doppelt schnell wie die serielle Schnittstelle. Das habe ich mit Wire.setClock(400000); nach _lcdDisplay->init(); in oid MFLCDDisplay::attach(byte address, byte cols, byte lines) gemacht. In _lcdDisplay->init(); wird Wire.begin(); aufgerufen, das setzt alles auf Standard. Muss also danach sein. Mangels eines zweiten LCD's zur Zeit kann ich das aber nicht testen.
- Ein "Acknowledge" aus dem CMDMessenger heraus nach dem Abarbeiten eines Kommandos wäre vorteilhaft. Dann kann kein Buffer überlaufen.
- 9ms für das LCD hat Einfluß auf die Encoder Routine, die ja eigentlich alle 1ms aufgerufen werden sollte um alle Impulse zu detektieren. Es können Schritte verloren gehen, ggf. wird sogar die falsche Richtung erkannt (muss ich noch mal drüber nachdenken ob das so ist). 400kHz auf dem I2C Bus hilft hier auch ungemein, da wäre ich nur noch bei 2,25ms.