Prozeda-Decoder/Der Datenstick

Aus Hobbyelektronik.org
Version vom 9. Februar 2021, 21:22 Uhr von Chris (Diskussion | Beiträge) (Seite erstellt)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

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

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
...

mehr Daten

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:

  1. 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
  2. Aktuelle Page auslesen
  3. 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.