Prozeda-Decoder/Der Datenstick
Wie bereits im Hauptartikel erwähnt, verbirgt sich im Stick nichts mehr als ein Atmel AT45DB081D - ein 8 MBit großer Flash-Speicher mit SPI als Interface.
Dieser wird über einen 8-poligen Mini-DIN-Stecker mit dem Regler bzw. dem Adapter zum Auslesen am PC verbunden.
Pin | Bezeichnung |
---|---|
1 | 17 V |
2 | unbekannt |
3 | MISO |
4 | GND |
5 | 3,3 V |
6 | MOSI |
7 | SCK |
8 | !CS |
Inhaltsverzeichnis
Datenformat
Mit der PC-Software lassen sich die Daten vom Logger sowohl als Rohdaten als auch im CSV-Format auf dem PC speichern. Beide Datensätze habe ich bekommen:
aa5540052401ffffffffffffffffff0020205761676e6572202620436f2000000000000000000000000020536f6c617220536f6c6172746563686e696b20...
;Datalogging ; Wagner & Co ; ; Solartechnik XXXXXXXXXXXXXXXX ; System-Nr. XXXX ; System-Ver. XXX Datum Uhrzeit Kollektor Speicher unten Speicher oben Rücklaufanh. Rücklaufanh. T T Primär Vorlauf Kaltwasser T Frischwasser Ausgang 1 Ausgang 2 Ausgang 3 Ausgang 4 Ausgang 5 Ausgang 6 Ausgang 7 unused Speicher Speicher Funktion aktiv Funktion aktiv Funktion aktiv Funktion aktiv Durchfluss Zapfung 8 9 1 1 1 1 1 1 1 1 1 1 1 10 10 10 10 10 10 10 10 7 15 11 11 11 11 19 27 09.11.16 18:44:00 -2.100 45.800 48.000 225.100 225.100 225.100 225.100 63.200 46.800 150.700 54.300 0.000 0.000 0.000 0.000 100.000 8.000 0.000 0.000 0 2249 0.100 0.200 0.100 0.200 0.000 -0.400 09.11.16 18:44:00 -2.100 45.700 48.000 228.300 228.300 228.300 228.300 63.300 46.200 200.400 54.400 0.000 0.000 0.000 0.000 100.000 9.000 0.000 0.000 0 2249 0.100 0.200 0.100 0.200 0.000 -0.200 09.11.16 18:44:00 -2.000 45.600 47.900 228.300 228.300 228.300 228.300 63.300 45.800 200.400 54.300 0.000 0.000 0.000 0.000 100.000 7.000 0.000 0.000 0 2249 0.100 0.200 0.100 0.200 0.000 -0.500 09.11.16 18:44:00 -2.000 45.600 47.900 231.000 231.000 231.000 231.000 63.300 45.700 225.200 54.300 0.000 0.000 0.000 0.000 100.000 6.000 0.000 0.000 0 2249 0.100 0.200 0.100 0.200 0.000 -0.600 09.11.16 18:44:00 -2.100 45.500 47.900 231.000 231.000 231.000 231.000 63.300 45.500 225.200 54.500 0.000 0.000 0.000 0.000 100.000 7.000 0.000 0.000 0 2249 0.100 0.200 0.100 0.200 0.000 -0.500 09.11.16 18:44:00 -2.000 45.500 47.800 233.300 233.300 233.300 233.300 63.300 45.500 237.600 54.500 0.000 0.000 0.000 0.000 100.000 8.000 0.000 0.000 0 2249 0.100 0.200 0.100 0.200 0.000 -0.400
Die Systemkennziffern (Seriennummer, System-Nr. und -Version) habe ich durch "X" ersetzt.
Die Binärdatei gehört eindeutig zur CSV, nur wie ist der Zusammenhang? Einen Schritt zurück - erstere ist genau 2048 KiB groß, der verwendete Flash-Speicher aber nur 1 MiB. Beim Blick in die Datei: das sieht alles ziemlich hexadezimal aus.
Um schnell zum Ziel zu kommen und die Codeeffizient erst einmal egal ist, habe ich PHP verwendet um die Daten umzuwandeln:
function convert($infile, $outfile)
{
$if = fopen($infile, "rb");
$of = fopen($outfile, "wb");
while(!feof($if))
{
$str = fread($if, 2);
fwrite($of, hex2bin($str));
}
fclose($if);
fclose($of);
}
Nach ein paar Sekunden konnte ich das Ergebnis in HxD bewundern:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00000000 AA 55 40 05 24 01 FF FF FF FF FF FF FF FF FF 00 ªU@.$.ÿÿÿÿÿÿÿÿÿ. 00000010 20 20 57 61 67 6E 65 72 20 26 20 43 6F 20 00 00 Wagner & Co .. 00000020 00 00 00 00 00 00 00 00 00 00 20 53 6F 6C 61 72 .......... Solar 00000030 20 53 6F 6C 61 72 74 65 63 68 6E 69 6B 20 XX XX Solartechnik XX 00000040 XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XXXXXXXXXXXXXXXX 00000050 20 20 53 79 73 74 65 6D 2D 4E 72 2E 20 20 XX XX System-Nr. XX 00000060 20 53 79 73 74 65 6D 2D 56 65 72 2E 20 20 XX XX System-Ver. XX ...
Das sieht doch gut aus!
Speichersegmente
Die nun sichtbaren Daten erzeugen den Eindruck von 3 Datenbereichen:
Start-Adresse | Ende-Adresse | Länge | Inhalt |
---|---|---|---|
0x00000 | 0x0006F | 0x0006F | Systeminfo |
0x00200 | 0x0043F | 0x00240 | Spaltenbeschreibung |
0x00500 | 0xFFFFF | 0xFFB00 | Loggingdaten |
Systeminfo
Die ersten beiden Byte auf dem Flash lauten 0xAA 0x55, die schon beim Initialisieren des Sticks geschrieben werden. Gleiches gilt für das dritte Byte (Adresse 0x00002), das vermutlich das Logging-Intervall repräsentiert. Leider konnte ich das bis jetzt noch nicht so richtig nachvollziehen.
Ab Offset 0x00010 befindet sich der Herstellername der Solaranlage. Am Anschluss - ab Offset 0x0003E steht die 18-stellige Seriennummer und ab 0x00050 die Systemnummer, wobei der eigentliche Wert bei 0x0005E mit Länge 2 liegt. Gleiches gilt für die Systemversion, die ab Offset 0x00060 beschrieben und der eigentliche Wert bei 0x0006E - ebenfalls mit Länge 2 liegt.
Die beiden letzteren Werte für letztere Bezeichner sind binär Little-Endian abgespeichert, aus 0x12 0x34 würde also 0x3412 bzw. 13330 werden.
Spaltenbeschreibung
Die Spaltenbeschreibungen sind wie folgt aufgebaut: 16 Zeichen lang, 15 davon sind NULL-terminierte ASCII-Strings gefolgt von einem weiteren Zeichen:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ... 00000200 20 20 20 20 44 61 74 75 6D 20 20 20 20 20 00 08 Datum .. 00000210 20 20 20 55 68 72 7A 65 69 74 20 20 20 20 00 09 Uhrzeit .. 00000220 20 53 65 6B 75 6E 64 65 6E 20 00 00 00 00 00 10 Sekunden ...... 00000230 20 20 4B 6F 6C 6C 65 6B 74 6F 72 20 20 20 00 01 Kollektor .. 00000240 20 20 53 70 65 69 63 68 65 72 20 B9 20 20 00 01 Speicher ¹ .. ...
Eine Besonderheit stellen offenbar die ASCII-Zeichen 0xB9 und 0xB3 dar - diese werden beim Auslesen nach "unten" und "oben" übersetzt.
Dieses letzte Zeichen spiegelt sich in der Zeile nach den Spaltenköpfen in der CSV-Datei wider. Nach genauerer Betrachtung ergibt sich folgende (mutmaßliche) Zuordnung:
Wert (Hex) | Datentyp | Länge |
---|---|---|
00 | Dummy | 2? |
01 | Temperatur | 2 |
07 | Speicher | 4? |
08 | Datum | 2 |
09 | Uhrzeit | 2 |
0A | Ausgang | 1 |
0D | Fehler | 1? |
0E | Fehler | 2? |
0F | Speicher | 2? |
10 | Sekunden | 2 |
13 | Durchfluss | 2 |
1B | Zapfung | 2 |
Zur Länge später mehr.
Loggingdaten
Ab Adresse 0x00500 wird es interessant, hier befindet sich die Payload. Als "Trick" um die Datenlänge besser ermitteln zu können hat sich bewährt, die Anzeigebreite des Hex-Editors auf Fenstergröße anpassen zu lassen und dann die Breite des Fensters zu variieren. Dadurch lassen sich Wiederholungen besser erkennen. So bei 64 Byte Breite:
Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C ... 00000500 55 04 64 04 2F 00 EB FF CA 01 E0 01 CB 08 CB 08 CB 08 CB 08 78 02 D4 01 E3 05 1F 02 00 00 00 00 64 08 00 00 00 55 55 00 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FC FF 00 00 U.d./.ëÿÊ.à.Ë.Ë.Ë.Ë.x.Ô.ã.......d....UU.......É.............üÿ.. 00000540 55 04 64 04 30 00 EB FF C9 01 E0 01 EB 08 EB 08 EB 08 EB 08 79 02 CE 01 D4 07 20 02 00 00 00 00 64 09 00 00 00 55 55 00 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FE FF 00 00 U.d.0.ëÿÉ.à.ë.ë.ë.ë.y.Î.Ô. .....d....UU.......É.............þÿ.. 00000580 55 04 64 04 31 00 EC FF C8 01 DF 01 EB 08 EB 08 EB 08 EB 08 79 02 CA 01 D4 07 1F 02 00 00 00 00 64 07 00 00 00 55 55 00 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FB FF 00 00 U.d.1.ìÿÈ.ß.ë.ë.ë.ë.y.Ê.Ô.......d....UU.......É.............ûÿ.. 000005C0 55 04 64 04 32 00 EC FF C8 01 DF 01 06 09 06 09 06 09 06 09 79 02 C9 01 CC 08 1F 02 00 00 00 00 64 06 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FA FF 00 00 U.d.2.ìÿÈ.ß.........y.É.Ì.......d....UU.......É.............úÿ.. 00000600 55 04 64 04 33 00 EB FF C7 01 DF 01 06 09 06 09 06 09 06 09 79 02 C7 01 CC 08 21 02 00 00 00 00 64 07 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FB FF 00 00 U.d.3.ëÿÇ.ß.........y.Ç.Ì.!.....d....UU.......É.............ûÿ.. 00000640 55 04 64 04 34 00 EC FF C7 01 DE 01 1D 09 1D 09 1D 09 1D 09 79 02 C7 01 48 09 21 02 00 00 00 00 64 08 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FC FF 00 00 U.d.4.ìÿÇ.Þ.........y.Ç.H.!.....d....UU.......É.............üÿ.. 00000680 55 04 64 04 35 00 EC FF C6 01 DE 01 1D 09 1D 09 1D 09 1D 09 79 02 C8 01 48 09 20 02 00 00 00 00 64 07 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FB FF 00 00 U.d.5.ìÿÆ.Þ.........y.È.H. .....d....UU.......É.............ûÿ.. 000006C0 55 04 64 04 36 00 EC FF C6 01 DE 01 32 09 32 09 32 09 32 09 79 02 C8 01 86 09 20 02 00 00 00 00 64 07 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FB FF 00 00 U.d.6.ìÿÆ.Þ.2.2.2.2.y.È.?. .....d....UU.......É.............ûÿ.. 00000700 55 04 64 04 37 00 EB FF C6 01 DE 01 32 09 32 09 32 09 32 09 79 02 C7 01 A5 09 21 02 00 00 00 00 64 06 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FA FF 00 00 U.d.7.ëÿÆ.Þ.2.2.2.2.y.Ç.¥.!.....d....UU.......É.............úÿ.. 00000740 55 04 64 04 38 00 EB FF C5 01 DE 01 45 09 45 09 45 09 45 09 79 02 C7 01 B4 09 21 02 00 00 00 00 64 07 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FB FF 00 00 U.d.8.ëÿÅ.Þ.E.E.E.E.y.Ç.´.!.....d....UU.......É.............ûÿ.. 00000780 55 04 64 04 39 00 EB FF C5 01 DD 01 45 09 45 09 45 09 45 09 79 02 C6 01 BC 09 22 02 00 00 00 00 64 08 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FC FF 00 00 U.d.9.ëÿÅ.Ý.E.E.E.E.y.Æ.¼.".....d....UU.......É.............üÿ.. 000007C0 55 04 64 04 3A 00 EB FF C5 01 DD 01 54 09 54 09 54 09 54 09 78 02 C6 01 C0 09 23 02 00 00 00 00 64 07 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FB FF 00 00 U.d.:.ëÿÅ.Ý.T.T.T.T.x.Æ.À.#.....d....UU.......É.............ûÿ.. 00000800 55 04 65 04 00 00 EB FF C5 01 DD 01 54 09 54 09 54 09 54 09 7A 02 C7 01 C2 09 23 02 00 00 00 00 64 06 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FA FF 00 00 U.e...ëÿÅ.Ý.T.T.T.T.z.Ç.Â.#.....d....UU.......É.............úÿ.. 00000840 55 04 65 04 01 00 EB FF C4 01 DD 01 62 09 62 09 62 09 62 09 79 02 C7 01 C3 09 21 02 00 00 00 00 64 07 00 00 00 55 55 10 00 00 00 00 00 00 C9 08 00 00 01 00 02 00 01 00 02 00 00 00 FB FF 00 00 U.e...ëÿÄ.Ý.b.b.b.b.y.Ç.Ã.!.....d....UU.......É.............ûÿ.. ...
Zieht man die Spaltenbeschreibungen zu Rate, müssen die ersten Bytes das Datum und die Uhrzeit sein. Und tatsächlich, bei Offset 0x04 sieht man, dass der Wert hochzählt. Angekommen bei 0x3A nullt der Wert und an Offset 0x02 wird hochgezählt. 0x3A ist allerdings nicht 59, sondern 58. Vielleicht eine leichte Asynchronität zwischen Uhr und Aufzeichnung?
Scrollt man weiter nach unten, ändern sich auch die Bytes davor. Interessant ist hierbei, dass sich die geraden Bytes öfter ändern als die ungeraden. Oder: die "ersteren" scheinen das kleine Ende der entsprechenden Zahl zu repräsentieren. Unser System scheint Little Endian zu verwenden. Interessant ist auch, dass bei Offset 0x05 konsequent eine Null steht. Wenn es sich tatsächlich um Sekunden handelt, warum haben die Entwickler hier sinnlos Platz verschwendet?
Ungeduldig wie ich bin (das da vorne _muss_ ein Timestamp sein), habe ich mir die nächsten Bytes angesehen. Offset 0x06 eiert um 0xEB herum, Offset 0x07 ist für länger konstant 0xFF, "schnappt" aber irgendwann auf 0x00 um. Da es unwahrscheinlich ist, dass sich ein Wert sehr stark ändert, tippe ich 0xFFEB in den Taschenrechner ein, stelle ihn auf Word-Breite ein und bekomme direkt -21 ausgespuckt (der Windows-Taschenrechner verwendet signed Datentypen). Ein Blick nach oben in die Daten der CSV-Datei verrät -2,1 °C. Die nächsten zwei Byte (bei Offset 0x08 und 0x09) lauten 0xCA 0x01, der Taschenrechner macht aus 0x01CA die dezimale Entsprechung 458. Die CSV-Tabelle sagt in der Spalte der ersten Zeile 45,8 °C. Das war einfach!
Um die Daten besser untersuchen zu können, habe ich das Swiss-Army-Knife aller BWLer und das Hacking-Tool schlechthin verwendet: Excel.
Mit Den Spaltenköpfen und der ersten Logzeile aus der CSV-Datei und der ersten Zeile (Hex-Codiert) aus der Binärdatei mache ich mich ans Werk.
Spalte F dient dazu den Offset für einen Wert vorzugeben, Spalte G bestimmt die Länge der in Spalte H separierten Hex-Werte. In Spalte I werden die ersten beiden Teile getauscht (ok, das funktioniert nur für maximal 2 Byte lange Werte) und von Hexadezimal nach Dezimal umgerechnet.
In Spalte J wird von Unsigned nach Signed umgewandelt (was allerdings nur für 2 Byte lange Datensätze funktioniert). Nach etwas Schätzen, Daten vorwärts und rückwärts durchgehen, ergab sich die Tabelle für die Spaltenbeschreibung von oben. Bei der Länge der Datenfelder für Fehler bin ich mir nicht 100 % sicher aber es sieht sehr plausibel aus.
Zurück zum Datum und der Uhrzeit. Da steht bei dem Satz 1109 und 1124, was dem 09.11.2016 und 18:44 entsprechen soll. Jetzt kann man lang überlegen, mit dem Tag seit Datum X rechnen oder einfach einen Strich zwischen 11 und 09 packen. Eine Jahresangabe sucht man vergebens. Bei der Uhrzeit ist es naheliegend, die Sekunden oder Minuten seit Mitternacht zu zählen - also einfach mal geteilt durch 60, ergibt: 18,73 - 18 Uhr passt, 0,73 entspricht den 44 Minuten. Haken dran. Die Sekunden werden von der PC-Software interessanterweise nicht ausgegeben, durch die Spaltenbeschreibung im Flash muss die dritte Spalte aber den Sekunden-Anteil repräsentieren.
Ein Datensatz aufgedröselt sieht dann wie folgt aus:
Kommunikation mit dem Datastick
Wie die Daten im Speicher aussehen ist nun bekannt, aber wie kommen sie drauf?
Frank war so freundlich und hat mir Traces geschickt. Leider zunächst nur Exports, in denen ich das genaue Timing und "alles andere" nicht sehen konnte.
Wie André Lampe in seinem 33C3-Vortrag "Es sind die kleinen Dinge im Leben" sagte: Rohdaten sind geil.
Später habe ich noch vollständige Datensätze bzw. die Rohdaten bekommen. Leider kann/möchte ich diese an dieser Stelle zum aktuellen Zeitpunkt nicht veröffentlichen, da es momentan keinen Weg gibt, die Dateistruktur von Saleae Logic zu verändern und ich somit die im Trace vorkommenden Seriennummern nicht entfernen kann.
Die Konfiguration für den SPI kann man größtenteils aus dem Datenblatt des Flashs ermitteln:
- MSB first
- 8 Bit je Datenwort
- SPI-Mode 0 oder 3
- CS low-aktiv
Im vorliegenden Fall wird Mode 0 verwendet.
Der Export des SPI-Analyzers aus Logic sieht wie folgt aus:
Time [s],Packet ID,MOSI,MISO 0.941889000000000,0,0xE8,0x00 0.941953000000000,0,0x00,0x00 0.942017416666667,0,0x00,0x00 0.942081583333333,0,0x00,0x00 0.942149166666667,0,0xAA,0x00 0.942213333333333,0,0xAA,0x00 0.942277583333333,0,0xAA,0x00 0.942341750000000,0,0xAA,0x00 0.942409833333333,0,0xAA,0x00 0.943974750000000,1,0xE8,0x00 0.944038833333333,1,0x00,0x00 0.944103083333333,1,0x00,0x00 0.944169166666667,1,0x00,0x00
...und ist damit ziemlich unleserlich. Neben den übertragenen Daten lässt sich auch die Bitrate grob abschätzen - jede Zeile entspricht einem übertragenen Byte und dauert im ersten Paket im Schnitt 65,1 µs. Mit 8 Bit je Datenwort kann ein Bit höchstens 8,138 µs dauern. Die Bitrate muss also etwas höher als 123 kHz sein, abhängig davon wie groß die Pausen zwischen den Bytes sind.
Die Wahrheit sieht natürlich etwas anders aus
233 kHz Clock-Frequenz und etwas um 32 µs zwischen den Bytes.
Traces lesbarer machen
Die Exporte von Logic sind gut, aber nicht für jede Anwendung. Um die übertragenen Daten zu verstehen ist mir das Timing nicht ganz so wichtig, aber der Inhalt der Nachrichten muss gut lesbar sein.
Also habe ich ein kleines Programm geschrieben (siehe unten), um die Pakete zu komprimieren, was dann wie folgt aussieht:
Time | Packet | Sent data | Received data |
---|---|---|---|
0,941889000000000 | 0 | E8 00 00 00 AA AA AA AA AA | 00 00 00 00 00 00 00 00 00 |
0,943974750000000 | 1 | E8 00 00 00 AA AA AA AA AA | 00 00 00 00 00 00 00 00 00 |
... | |||
7,979580416666670 | 14 | E8 00 00 00 AA AA AA AA AA | 00 00 00 00 00 00 00 00 AA |
7,981663083333330 | 15 | E8 00 00 01 AA AA AA AA AA | 00 00 00 00 00 00 00 00 55 |
7,983659916666670 | 16 | E8 00 00 02 AA AA AA AA AA | 00 00 00 00 00 00 00 00 01 |
8,011756000000000 | 17 | E8 00 0A 00 AA AA AA AA AA | 00 00 00 00 00 00 00 00 FF |
8,014754250000000 | 18 | E8 00 00 00 AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA ... | ... |
8,051748750000000 | 19 | 82 00 00 00 AA 55 01 05 24 01 FF FF FF FF FF FF FF FF FF 00 20 20 57 61 67 6E 65 72 20 26 20 43 6F 20 00 ... | ... |
Zugleich wird der Timestamp angepasst, damit die Datei direkt von Excel eingelesen werden kann. Die Spalten sind zudem per Tab getrennt, was beim Import etwas angenehmer sein kann
Schaut man im Datenblatt nach den verschiedenen Befehlen, findet man unter 0xE8 "Continuous Array Read" und unter 0x82 "Main Memory Page Program through Buffer 1". Tabelle 4 im Dokument den Aufbau der zu den Befehlen gehörenden Sequenzen.
Demnach wird in Paket 0 der Zeiger auf Page 0 und Buffer Address Bit 0 gesetzt und ein Byte gelesen, welches mit 0x00 beantwortet wird. Ignoriert man Paket 1 geschieht dies im Sekundentakt (nicht oben gezeigt), bis in Paket 14 0xAA zurückgegeben wird.
Jetzt kann man die Kommunikation schon ziemlich gut verfolgen, allerdings: warum sollte man es sich unnötig schwer machen, wenn man den PC für sich arbeiten lassen kann? Also habe ich das Programm zum Komprimieren der Daten erweitert - es kann auch die im Protokoll verwendeten Befehle interpretieren:
Time | Packet | Opcode | Page | Address from/to | Data length | Data |
---|---|---|---|---|---|---|
0,941889000000000 | 0 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
0,943974750000000 | 1 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
... | ||||||
7,979580416666670 | 14 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | AA |
7,981663083333330 | 15 | E8H Cont Array Read | 0x0000 | 0x000001 ... 0x000001 | 1 | 55 |
7,983659916666670 | 16 | E8H Cont Array Read | 0x0000 | 0x000002 ... 0x000002 | 1 | 01 |
8,011756000000000 | 17 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x000500 | 1 | FF |
8,014754250000000 | 18 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x0000FF | 256 | AA 55 01 00 FF FF FF FF FF FF FF FF FF FF FF FF FF ... |
8,051748750000000 | 19 | 82H Write through Buff1 | 0x0000 | 0x000000 ... 0x0000FF | 256 | AA 55 01 05 24 01 FF FF FF FF FF FF FF FF FF 00 20 ... |
Kommunikation zum Speicher
Erkennung
Um den Datastick zu erkennen, wird im Sekundentakt versucht die Adresse 0x00000 zu lesen. Beinhaltet diese den Wert 0x00 wird davon ausgegangen, dass kein Stick gesteckt ist (keine Antwort). Wird das "Magic Byte" 0xAA erkannt, wird anschließend Adresse 0x00001 gelesen, die bei einem korrekt initialisierten Stick 0x55 beinhaltet:
Time | Packet | Opcode | Page | Address from/to | Data length | Data |
---|---|---|---|---|---|---|
5,970892583333330 | 11 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
6,974197916666670 | 12 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
6,976281000000000 | 13 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
7,979580416666670 | 14 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | AA |
7,981663083333330 | 15 | E8H Cont Array Read | 0x0000 | 0x000001 ... 0x000001 | 1 | 55 |
Diese Erkennung läuft auch nachdem ein Stick erfolgreich erkannt wurde. Es ist der einfachste Weg, ein Entfernen des Loggers festzustellen, auch wenn es über die anderen Kommunikationsinhalte ebenfalls möglich wäre.
Anschließend gibt es eine Fallunterscheidung: Entweder der Stick kommt frisch initialisiert vom PC oder er war schon einmal an der Anlage.
Dazu wird an Adresse 0x000500 geprüft, ob 0xFF (mutmaßlich unbeschrieben) oder ein anderer Wert steht. Handelt es sich um ersteren, wird von einem frisch initialisierten Stick ausgegangen.
Frisch initialisierter Stick
Time | Packet | Opcode | Page | Address from/to | Data length | Data |
---|---|---|---|---|---|---|
5,970892583333330 | 11 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
6,974197916666670 | 12 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
6,976281000000000 | 13 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
7,979580416666670 | 14 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | AA |
7,981663083333330 | 15 | E8H Cont Array Read | 0x0000 | 0x000001 ... 0x000001 | 1 | 55 |
7,983659916666670 | 16 | E8H Cont Array Read | 0x0000 | 0x000002 ... 0x000002 | 1 | 01 |
8,011756000000000 | 17 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x000500 | 1 | FF |
8,014754250000000 | 18 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x0000FF | 256 | AA 55 01 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ... |
8,051748750000000 | 19 | 82H Write through Buff1 | 0x0000 | 0x000000 ... 0x0000FF | 256 | AA 55 01 05 24 01 FF FF FF FF FF FF FF FF FF 00 20 20 57 ... |
8,095399500000000 | 20 | 82H Write through Buff1 | 0x0002 | 0x000200 ... 0x0002FF | 256 | 20 20 20 20 44 61 74 75 6D 20 20 20 20 20 00 08 20 20 20 ... |
8,139579833333330 | 21 | 82H Write through Buff1 | 0x0003 | 0x000300 ... 0x0003FF | 256 | 20 20 41 75 73 67 61 6E 67 20 33 20 20 20 00 0A 20 20 41 ... |
8,182810250000000 | 22 | 82H Write through Buff1 | 0x0004 | 0x000400 ... 0x0004FF | 256 | 46 75 6E 6B 74 69 6F 6E 20 61 6B 74 69 76 00 0B 20 20 44 ... |
8,224643666666670 | 23 | E8H Cont Array Read | 0x0000 | 0x000003 ... 0x000003 | 1 | 05 |
8,226721500000000 | 24 | E8H Cont Array Read | 0x0000 | 0x000003 ... 0x000003 | 1 | 05 |
8,229152833333330 | 25 | 81H Erase Page | 0x0005 | 0x000500 ... 0x0005FF | 0 | |
8,239133916666670 | 26 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | AA |
8,980954250000000 | 27 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x0005FF | 256 | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ... |
9,018144083333330 | 28 | 82H Write through Buff1 | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 ... |
9,156107750000000 | 29 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | AA |
9,158207083333330 | 30 | E8H Cont Array Read | 0x0000 | 0x000001 ... 0x000001 | 1 | 55 |
9,160199750000000 | 31 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | AA |
9,991345666666670 | 32 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 ... |
10,028507166666700 | 33 | 82H Write through Buff1 | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 ... |
In diesem Fall steht an Adresse 0x000500 der Wert 0xFF (Paket 17), was auf noch nicht vorhandene Loggingdaten deutet.
In Paket 18 wird augenscheinlich geprüft, ob Kopfdaten vorhanden sind, anschließend werden die Systeminfo (Paket 19) und die Spaltenbeschreibungen (Paket 20, 21 und 22) geschrieben.
Die Bedeutung der zusätzlich geschriebenen Bytes (0x05 0x24 0x01) hat sich mir leider noch nicht erschlossen.
Warum Byte 0x000003 hier noch einmal und vor allem zweimal (Paket 23 und 24) abgefragt wird, ist mir ebenfalls etwas rätselhaft.
Ab Paket 25 beginnt der normale Schreibzyklus (siehe weiter unten).
Bereits verwendeter Stick
Time | Packet | Opcode | Page | Address from/to | Data length | Data |
---|---|---|---|---|---|---|
8,586632916666670 | 17 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
9,592280083333330 | 18 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
9,594365416666670 | 19 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | 00 |
10,597658333333300 | 20 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | AA |
10,599741083333300 | 21 | E8H Cont Array Read | 0x0000 | 0x000001 ... 0x000001 | 1 | 55 |
10,601737750000000 | 22 | E8H Cont Array Read | 0x0000 | 0x000002 ... 0x000002 | 1 | 0F |
10,629873166666700 | 23 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x000500 | 1 | 6F |
Gleicher Lesevorgang und Antwort in Paket 24 ... 50 für die Adressen
0x000540, 0x000580, 0x0005C0, 0x000600, 0x000640, 0x000680, 0x0006C0, 0x000700, 0x000740, 0x000780, 0x0007C0, 0x000800, 0x000840, 0x000880, 0x0008C0, 0x000900, 0x000940, 0x000980, 0x0009C0, 0x000A00, 0x000A40, 0x000A80, 0x000AC0, 0x000B00, 0x000B40, 0x000B80, 0x000BC0 | ||||||
10,692716666666700 | 51 | E8H Cont Array Read | 0x000C | 0x000C00 ... 0x000C00 | 1 | 6F |
10,694810333333300 | 52 | E8H Cont Array Read | 0x000C | 0x000C40 ... 0x000C40 | 1 | FF |
10,696810750000000 | 53 | E8H Cont Array Read | 0x0000 | 0x000000 ... 0x000000 | 1 | AA |
11,599035166666700 | 54 | E8H Cont Array Read | 0x000C | 0x000C00 ... 0x000CFF | 256 | 6F 00 46 04 33 00 24 00 B0 01 D3 01 C4 09 C4 09 C4 09 C4 ... |
Nach dem Lesen der Kopfdaten wird Adresse 0x000500, also der Beginn der Loggingdaten angesprungen und ein Byte gelesen (Paket 23). Ist dieses nicht 0xFF, wird so lange um 64 Byte weitergesprungen (Paket 24 ... 51), bis der erste nicht vorhandene Datensatz mit 0xFF am Anfang gefunden wird (Paket 53). An dieser Stelle kann der Schreibzyklus begonnen werden.
Schreibzyklus
Das Schreiben der Daten hätte ich mir eigenwilliger nicht ausmalen können. Aufgrund der Gegebenheiten ging ich von zwei Möglichkeiten aus:
- Im Regler werden 4 Einträge gecached und anschließend als komplette Page geschrieben
- Jeder Eintrag wird einzeln geschrieben und dabei der Pointer auf die jeweilige Adresse gesetzt
Stattdessen ist die Vorgehensweise wie folgt:
- Prüfen ob es der nächste zu schreibende Satz der letzte innerhalb der aktuellen Page ist und wenn ja: nächste Page löschen
- Aktuelle Page auslesen
- Aktuelle Page mit angehängten Daten wieder schreiben
Die folgende Tabelle ist leider etwas groß, aber hier benötigt man zum Verständnis die gelesenen/geschriebenen Daten. Zum besseren Übersicht habe ich die zyklischen Abfragen zur Erkennung des Sticks entfernt.
Time | Packet | Opcode | Page | Address from/to | Data length | Data |
---|---|---|---|---|---|---|
8,980954250000000 | 27 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x0005FF | 256 | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
9,018144083333330 | 28 | 82H Write through Buff1 | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 09 42 02 3D 01 C4 09 C4 01 00 00 00 00 00 00 00 00 00 55 55 10 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
9,991345666666670 | 32 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 09 42 02 3D 01 C4 09 C4 01 00 00 00 00 00 00 00 00 00 55 55 10 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
10,028507166666700 | 33 | 82H Write through Buff1 | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 09 42 02 3D 01 C4 09 C4 01 00 00 00 00 00 00 00 00 00 55 55 10 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2A 00 57 00 B6 01 DC 01 CB 08 CB 08 CB 08 CB 08 31 02 A0 01 E3 05 C3 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
10,994477083333300 | 37 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 09 42 02 3D 01 C4 09 C4 01 00 00 00 00 00 00 00 00 00 55 55 10 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2A 00 57 00 B6 01 DC 01 CB 08 CB 08 CB 08 CB 08 31 02 A0 01 E3 05 C3 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
11,031559916666700 | 38 | 82H Write through Buff1 | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 09 42 02 3D 01 C4 09 C4 01 00 00 00 00 00 00 00 00 00 55 55 10 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2A 00 57 00 B6 01 DC 01 CB 08 CB 08 CB 08 CB 08 31 02 A0 01 E3 05 C3 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2B 00 51 00 B5 01 DC 01 EB 08 EB 08 EB 08 EB 08 38 02 6D 01 D4 07 C3 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
12,001553583333300 | 42 | 81H Erase Page | 0x0006 | 0x000C00 ... 0x000CFF | 0 | |
12,012532583333300 | 43 | E8H Cont Array Read | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 09 42 02 3D 01 C4 09 C4 01 00 00 00 00 00 00 00 00 00 55 55 10 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2A 00 57 00 B6 01 DC 01 CB 08 CB 08 CB 08 CB 08 31 02 A0 01 E3 05 C3 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2B 00 51 00 B5 01 DC 01 EB 08 EB 08 EB 08 EB 08 38 02 6D 01 D4 07 C3 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
12,049725916666700 | 44 | 82H Write through Buff1 | 0x0005 | 0x000500 ... 0x0005FF | 256 | 6F 00 45 04 29 00 23 00 AD 01 D8 01 C4 09 C4 09 C4 09 C4 09 42 02 3D 01 C4 09 C4 01 00 00 00 00 00 00 00 00 00 55 55 10 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2A 00 57 00 B6 01 DC 01 CB 08 CB 08 CB 08 CB 08 31 02 A0 01 E3 05 C3 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2B 00 51 00 B5 01 DC 01 EB 08 EB 08 EB 08 EB 08 38 02 6D 01 D4 07 C3 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 6F 00 45 04 2C 00 4C 00 B4 01 DB 01 EB 08 EB 08 EB 08 EB 08 3D 02 55 01 D4 07 C2 01 00 00 00 00 00 00 00 00 00 55 55 00 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 |
13,007502916666700 | 48 | E8H Cont Array Read | 0x0006 | 0x000600 ... 0x0006FF | 256 | FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
13,044694416666700 | 49 | 82H Write through Buff1 | 0x0006 | 0x000600 ... 0x0006FF | 256 | 6F 00 45 04 2D 00 47 00 B3 01 DB 01 06 09 06 09 06 09 06 09 3F 02 47 01 CC 08 C2 01 00 00 00 00 00 00 00 00 00 55 55 10 00 00 00 00 00 00 F8 08 00 00 01 00 01 00 01 00 02 00 00 00 F0 FF 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF |
Das ist auch schon so ziemlich alles.
Erwähnenswert ist an dieser Stelle noch, dass das Logging-Intervall am Regler während der Laufzeit verändert werden kann. In diesem Fall wird die erste Page neu geschrieben.
Flash-Emulator
Nun könnte man einfach dauerhaft mit eingestecktem Stick mitschnüffeln und die Daten so extrahieren. Leider hört der Solarregler (wahrscheinlich) mit dem Logging auf, wenn der Speicher voll ist. Besser ist da eine Lösung, die man anstecken und vergessen kann.
Eine Möglichkeit wäre, den SPI-Flash über Multiplexer an einen Mikrocontroller (oder direkt den SBC) zu hängen und immer wieder auszulesen. Geht, ist aber unnötig viel Aufwand und irgendwann, in 100 Jahren ist der Speicher totgeschrieben.
Die meisten Mikrocontroller besitzen einen SPI-Controller, der auch Slave kann. Mit ein bisschen Software lässt sich doch sicher der Speicher emulieren. Der komplette Befehlssatz ist nicht nötig, genauso wenig muss man den kompletten Speicherplatz des Originalbausteins vorhalten.
Also habe ich mir also einen ATmega328P (anfänglich ein ATmega8, der aufgrund der 3,3 V allerdings nicht stabil lief) auf ein Breadboard gesteckt und losgelegt - bzw. erst einmal blöd aus der Wäsche geschaut.
Zwar wird der Slave-Select-Pin zum aktivieren des SPI-Moduls verwendet, aber es gibt (zumindest beim ATmega8) keinen direkt assoziierten Interrupt auf Kommunikationsstart. Der wird aber zwingend benötigt, da man zur Synchronisation des Protokolls wissen muss, wann ein Paket angefangen hat (zurücksetzen des Byte-Zählers). Ungläubig und sehr irritiert habe ich beim ATmega8 Slave Select mit INT0 verbunden. Beim ATmega328P ist dies nicht notwendig, da dieser über Pin Change Interrupts verfügt, die auf nahezu allen Port-Pins verfügbar sind.
Für die Flash-Emulation werden also zwei Interrupts benötigt: SPI_STC (Serial Transfer Complete) und PCINT0 (dem der Slave-Select-Pin zugeordnet ist).
Der Programmablauf ist grob wie folgt:
Bei fallender Flanke am Slave Select wird, neben dem völlig autark ablaufenden Aktivieren des SPI-Moduls, der Byte-Zähler und der Op-Code zurückgesetzt und das SPDR-Datenregister mit 0x00 vorgeladen. Zusätzlich wird ein Busy-Flag gesetzt um - wenn die Firmware weiter wächst - Aufgaben niedrigerer Prioritäten zurückzustellen.
Bei steigender Flanke am Slave Select wird geprüft, welche Aktion im letzten Paket angefordert wurde, was vor allem beim Löschen der Page oder nach dem Schreiben der Loggingdaten - auf die wir es schließlich abgesehen haben - wichtig ist.
Im Serial SPI_STC-Interrupt passiert die eigentliche Arbeit: Wird ein Byte über SPI empfangen, schlägt es hier auf. Dank der Variable spi_bytenum weiß das Programm, wo es sich innerhalb des Pakets befindet.
Folglich wird das erste Byte in die Variable für den Op-Code geladen, die nächsten drei Byte landen im Adresszähler.
Um nicht unnötig Laufzeit zu verschwenden (Interrupt-Zeit ist als "teuer" zu betrachten), habe ich für die Behandlung der Adresse einen Trick (wenn man es so nennen will) angewandt: Der Wert ist 20 bzw. 21 Bit breit. Der nächsthöhere Standardtyp wäre uint32_t, der bei Rechenoperationen jedoch relativ viel Speicher frisst - schließlich handelt es sich um eine 8-Bit-CPU. Da größtenteils eh nur die unteren 16 Bit der Adresse wichtig sind, habe ich eine Union (also eine Verschmelzung mehrerer Datentypen) erstellt, die die Adresse etwas angenehmer speichern lässt:
volatile union
{
struct
{
uint16_t lower;
uint8_t upper;
} val24;
uint8_t val8[3];
} flash_address;
Das Schreiben der Werte sieht zwar etwas ungewöhnlich aus, orientiert die Bytes allerdings gleich richtig herum an und berücksichtigt auch den Offset von spi_bytenum (die Adresse kommt für spi_bytenum > 0 und < 4):
flash_address.val8[sizeof(flash_address) - spi_bytenum] = d;
Im Anschluss sind wir schon eine Ebene höher: Für den Op-Code 0xE8 (Read Continuous Array) wird es nach dem 7. Byte interessant, bei Write through Buffer 1 ab dem 4. Byte, was dann in etwa wie folgt aussieht:
ISR(SPI_STC_vect)
{
IOPIN_H(DBGO);
uint8_t d = SPDR;
if(spi_bytenum == 0)
{
// byte 0 in transfer is the opcode
SPDR = 0x00;
flash_opcode = (Flash_Opcode_t)d;
}
else if(spi_bytenum < 4)
{
SPDR = 0x00;
// byte 1 ... 3 are address bytes
flash_address.val8[sizeof(flash_address) - spi_bytenum] = d;
if(spi_bytenum == 3)
{
// command completely received, now do what needs to be done
//uart_debug_write("%2x:%4x", flash_opcode, flash_address.val24.lower);
}
}
else if(flash_opcode == Flash_Opcode_ReadContArray && spi_bytenum >= 7)
{
// when reading from the flash, we have to start to transmit
// the data already one byte earlier due to delay of MISO
if(flash_address.val24.lower <= 0x0003)
{
// hello bytes
SPDR = flash_data_hello[flash_address.val24.lower & 0x03];
}
else if(flash_address.val24.lower >= 0x0500)
{
// logging data section
SPDR = flash_data_log[flash_address.val24.lower & 0xFF];
}
flash_address.val24.lower++;
}
else if(flash_opcode == Flash_Opcode_WriteBuff1 && spi_bytenum >= 4)
{
if(flash_address.val24.lower >= 0x0500)
{
flash_data_log[flash_address.val24.lower & 0xFF] = d;
}
flash_address.val24.lower++;
}
else
{
// when there's no specific handler for the transmission, reply 0x00
SPDR = 0x00;
}
// byte number is only important for the first n bytes, after that
// there are anyway only data bytes (which are handled separately)
if(spi_bytenum < 0xFF)
spi_bytenum++;
IOPIN_L(DBGO);
}
ISR(PCINT0_vect)
{
if(IOPIN_V(SPI_SS) == 0)
{
spi_bytenum = 0;
flash_opcode = 0;
// preload SPI data register
SPDR = 0x00;
trx_flags |= TrxFlags_SpiBusy;
}
//...
}
Um die Laufzeit im Blick zu haben, wird zusätzlich ein Debug-Pin zu Beginn des Interrupts auf High und am Ende auf Low gesetzt. Wenn ich mich richtig erinnere, waren es bei 8 MHz CPU-Takt um die 8 µs, also reichlich Luft zu den knapp 30 µs, die zur Verfügung stehen. Allerdings muss dazu gesagt werden, dass in dem Codeschnipsel das Schreiben der Daten in den RAM noch nicht implementiert ist.