Reverse Engineering eines unbekannten Mikrocontrollers





Komplizierte Krawatte



Hintergrund ...



Im Rahmen meiner Arbeit zum  Reverse Engineering elektronischer eInk-Preisschilder  stieß ich auf ein interessantes Problem. Ein bestimmtes Unternehmen (Samsung Electro Mechanics / SoluM) wechselte von Chips von Drittanbietern, deren Ursprung ich identifizieren konnte (Marvell 88MZ100), zu einem neuen Chip, den es mit seinen Preisschildern der nächsten Generation zu verwenden begann.



Es schien, dass dies ihr eigener Chip ist, der von der Firma genau für diesen Zweck entwickelt wurde. Das Reverse Engineering von so etwas zu übernehmen, ist ein totes Thema. Ein Freund gab mir ein paar Preisschilder mit solchen Chips - zum basteln. Es stellte sich heraus, dass es sich um zwei Typen handelt: einen mit einer segmentierten Anzeige auf E-Ink und einen mit einer herkömmlichen grafischen Anzeige auf E-Ink. Der Hauptchip in beiden Modellen ist der gleiche, daher habe ich als erstes ein segmentiertes Anzeigegerät verwendet, da es einfacher ist und es einfacher ist, mit einem unbekannten System umzugehen, das es verwendet. Es war nicht ganz klar, wo ich anfangen sollte, aber das sind natürlich die Aufgaben, die immer am interessantesten sind! 



Studie





Es ist dumm zu versuchen, ein Kreuzworträtsel zu lösen, ohne die Fragen dazu zu lesen. Es ist genauso dumm, ein Gerät zurückzuentwickeln, ohne zuerst alle Informationen zu sammeln, die bereits darüber verfügbar sind. Was wissen wir also zunächst? Das Protokoll für die drahtlose Datenübertragung ist wahrscheinlich das gleiche wie üblich, da kein Unternehmen auf ein neues migrieren oder zwei Protokolle für seine Kunden gleichzeitig unterstützen möchte, wobei die Migration langsam durchgeführt wird. Das alte Protokoll war 2,4 GHz ZigBee-ähnlich, daher ist das neue wahrscheinlich dasselbe. Hier ist ein Foto der Tafel von beiden Seiten.





Was sehen wir also? Zunächst ein cooles Beispiel für Kostenoptimierung. Sie haben den E-Ink-Bildschirm direkt auf die Leiterplatte laminiert! Wer braucht eine leitfähige Glasrückwand, wenn es eine Leiterplatte gibt? Die Frontplatte besteht aus leitfähigem Kunststoff. Aber es ist nicht wichtig.



Nach ihrer Größe sind zwei Antennen sichtbar - bei 2,4 GHz. Wie erwartet hatten Geräte der vorherigen Generation auch zwei 2,4-GHz-Antennen. Wir sehen zwei Chips. Groß und Klein. Der große (mit "SEM9010" bezeichnete) hat anscheinend viele Kontakte zum Display und keine zu den Antennen. Offensichtlich ist dies ein Display-Controller.



Das kleine (mit "SEM9110" bezeichnete) scheint das Gehirn zu sein, das für alle Operationen verantwortlich ist. Es ist mit den Antennen, dem Zeitsteuerungskristall und den wichtigsten Punkten verbunden, die hier für die werkseitige Programmierung offensichtlich sind.



Hier gibt es 12 Pads: eines ist mit dem Pluspol der Batterie verbunden, eines mit Masse, der Zweck der anderen 10 ist ein Rätsel. Wenn ich online nach dem Namen des Chips suche, finde ich nichts Nützliches - definitiv ihre eigene Entwicklung. Aber wer entwirft einen eigenen Chip für eine so einfache Anwendung? Vielleicht nur ein Rebranding? Geschnallt arbeiten wir!



Seltsamerweise hat die Google Bildsuche hier geholfen. Es kommt vor, dass dieses Tool für das Reverse Engineering nützlich ist. In diesem Fall bringt er uns zu diesem Nugget. (archivierte Kopie  hier  für die Nachwelt). Dies ist eine Frage von StackExchange, in der Sie sich fragen, wie diese elektronischen Regaletiketten funktionieren. Die Frage ist interessant, weil auf dem hier veröffentlichten Foto die Leiterplatte fast identisch mit  unserer aussieht  . Die Chips sind auch genau gleich, aber die Etiketten auf ihnen sind unterschiedlich! Das Board wurde wahrscheinlich hergestellt, bevor SoluM begann, diese Chips umzubenennen.



Der Chip, von dem ich angenommen habe, dass er der Display-Controller ist, ist beschriftet  SSD1623L2



. In der Tat handelt es sich um einen segmentierten E-Ink-Display-Controller, der bis zu 96 Segmente unterstützt. Wenn ich online suche, finde ich das Datenblatt der  Vorabversion Version 0.1 (archivierte Kopie  hier für die Nachwelt). Das ist gut! Wenn sie wüssten, wie sie das erreichen können, könnten sie einen Code aufnehmen, den sie verstehen, und sobald wir diesen Code sehen, ist das alles!



Es stellt sich heraus, dass der Hauptmikrocontroller ist ZBS242



. Okay. Ich bin mit diesem Mikrocontroller nicht vertraut. Lassen Sie uns das Internet etwas genauer durchsuchen - und die Suche führt uns zum Link  (Archivkopie  hier für die Nachwelt), in der auch die gleiche Antwort von StackExchange erwähnt wird. Die Seite ist koreanisch, zeigt jedoch, dass dieser Chip über einen 8051-Kern sowie eine ziemlich vorhersehbare Peripherie verfügt: UART, SPI, I2C, ADC, DAC, Komparator, Temperatursensor, 5-Kanal-PWM, 3-Kanal-Triac-Controller , IR-Sender, Key-Scan-Funktion, RF-Wake-Funktion, Antennenabstand, ZigBiee-kompatibles Radio und MAC. Das Bild zeigt, dass es auch einen internen 32-kHz-RC-Oszillator gibt, der, wie angegeben, im Schlafmodus nur 1 uA verbrauchen kann. Ich denke, es war diese Firma, die unseren Chip für Samsung hergestellt hat. Interessant ...



Schauen wir uns die Bilder an und stellen fest, dass der SEM9110-Kristall, der uns verwirrte, ebenfalls aus nächster Nähe aufgenommen wurde   (Archivkopie)  hier  für die Nachwelt). Es wird angegeben, ZBS243 zu sein. Ich denke, das bedeutet, dass es hier eine ganze Familie von Chips gibt: den ZBS24x. Wirklich interessant.



Wir haben einen Thread!





Nachdem wir ein weiteres Segment-Tag geöffnet haben, freuen wir uns weiterhin über die Neuigkeiten: Der Programmkopf ist in klaren, lesbaren goldenen Buchstaben signiert! Der Kopf scheint einen SPI, einen UART, einen Reset-Pin, eine Stromversorgung, Masse und einen Pin namens „Test“ zu haben, der wahrscheinlich verwendet wird, um in den werkseitigen Testmodus zu wechseln. Alles ist neugieriger und neugieriger.





Es ist logisch, dass der älteste Vertreter der hypothetischen ZBS24x-Familie als "ZBS240" bezeichnet wird. Vielleicht gibt uns eine Suche nach einer solchen Abfrage etwas Interessantes? Wenn wir nach "ZBS240" suchen und die Schlacke herausfiltern, finden wir eine weitere interessante Seite auf Koreanisch  (archivierte Kopie  hier  für die Nachwelt). Es sieht so aus, als ob dieses Unternehmen kundenspezifische On-Demand-Gruppenprogrammierer herstellt. Nachdem wir uns auf ihrer Website umgesehen haben, finden wir ein Handbuch  (Archivkopie  hier für die Nachwelt) auf ihrem Programmiergerät, und wir können sogar ein Dienstprogramm für einen PC herunterladen, um mit einem solchen Gerät zu arbeiten. Dieses Dienstprogramm verfügt sogar über ein Tool zum Aktualisieren der Firmware auf dem Gerät. Ich habe nachgesehen, ob es möglich ist, anhand dieser Informationen zu erraten, wie das Gerät programmiert werden soll, aber die Firmware hat sich als verschlüsselt herausgestellt. Anscheinend sendet das PC-seitige Dienstprogramm nur Daten über die serielle USB-Schnittstelle, daher gibt es auch hier keine nützlichen Informationen. Traurig ... 



Nachdem wir ein bisschen mehr gesucht haben, finden wir eine noch interessantere Seite  (archivierte Kopie  hier für die Nachwelt). Was ist das? Ist es zum Verkauf?!? Auf keinen Fall mehr, oder? Ich habe gerade für alle Fälle an diese Firma geschrieben, um Seife zu bekommen. Schweigen ... Als Geste der Verzweiflung fragte ich einen Freund aus Hongkong, ob er jemanden in Korea kenne, der diese Leute kontaktieren könne, da ihre Website zeigt, dass sie nur eine Überweisung von einer koreanischen Bank als Zahlungsmittel akzeptieren. Ich war nur erstaunt, als er zurückstieß und sagte, er könne mir dieses Gerät über einen in Korea gefundenen Vermittler besorgen! Einige Tage später wurde das Gerät von DHL ausgeliefert!



Du kannst ihn erreichen!



Wie man ihn kontaktiert



Funktioniert! Ich kann den Chip lesen und darauf schreiben. Ich habe eine Weile gebraucht, um das Programmiertool zu recherchieren. Anscheinend hat der Chip 64 KB Flash-Speicher und einen 1 KB "Informationsblock", der meiner Meinung nach zum Speichern von Kalibrierungswerten, MAC-Adressen und dergleichen verwendet wird. Ich konnte einige der Spuren abfangen, bewaffnet mit dem wunderbaren Saleae Logic Logic Analyzer  , und beobachtete, wie der Programmierer seine Arbeit erledigte . Sie können meine Ergebnisse hier herunterladen . In diesem Archiv finden Sie Spuren des Lesens, Löschens und Schreibens in den Bereichen INFOBLOCK und CODE. In der Tat ist das Protokoll sehr  einfach! Die Taktfrequenz kann zwischen 100 kHz und 8 MHz liegen.



ISP-Protokoll: auf den Knochen geschnitten 



Alles beginnt mit dem Einstellen der Zeilen in den gewünschten Zustand: SCLK unten, MOSI oben, RESET oben, SS oben. Dieser Zustand wird 20 ms lang aufrechterhalten. Dann wird RESET um 32 ms abgesenkt. Dann werden mindestens 4 Prozessortakte mit 500 kHz an die SCK-Leitung gesendet. Dann gibt es eine weitere Verzögerung von 10 ms, bis RESET nach oben gedrückt wird. Sie können jetzt eine Verzögerung von 100 ms einstellen, bevor Sie mit der Kommunikation beginnen. Danach können beliebig viele Transaktionen durchgeführt werden. Ein paar Grundregeln: Zwischen dem Herunterfahren der SS und dem Senden eines Bytes müssen mindestens 5us liegen, zwischen dem Ende des Bytes und dem Hochfahren der SS mindestens 2us, und der kürzeste Zeitraum, den die SS ausgeben kann, beträgt 2,5us. Daher wird jedes Byte im Status gesendet: SS ist inaktiv, ein Byte wird im SPI-Modus 0 gesendet, SS ist in Betrieb. Ja natürlich,SS kippt für jedes Byte.



Alle Transaktionen sind drei bis vier Bytes lang. Das erste Byte gibt den Transaktionstyp an, das niedrigste Bit gibt die Richtung der Transaktion an: Null bedeutet Schreiben auf das Gerät, eins bedeutet Lesen vom Gerät. Mit den Befehlen 0x02



/ 0x03



 werden Kommunikationssitzungen initiiert. Der Programmierer sendet einen Drei-Byte-Schreibvorgang:  02 BA A5



und liest dann, wobei er zuerst den Lesebefehl und die "Adresse" 03 BA



sendet . Der  Master sendet FF



während des Empfangs A5



. Wenn dies funktioniert, wird die Kommunikation hergestellt.



Befehle  0x12



/ 0x13



 werden zum Lesen / Schreiben von SFRs (Special Purpose Register) in der CPU verwendet (ich fand das schwieriger, aber in diesem Fall ist die Reihenfolge nicht so wichtig). Um INFOBLOCK auszuwählen, 0xD8



 muss SFR  auf eingestellt sein 0x80



, um den Hauptblitzbereich auszuwählen, muss er auf eingestellt sein 0x00



. Um den Wert von vv in das Register rr zu schreiben, werden SPI-Daten benötigt  12 rr vv



. Um sicherzustellen, dass der Wert gelesen wurde, kann er zurückgelesen werden, indem zuerst ein Lesebefehl und eine "Adresse"  gesendet werden . 13 rr



Danach sendet der Master  FF



während des Empfangs  vv



.



Der Flash-Speicher ist leicht zu lesen. Bewerben Sie sich dazu 0x09



, ein Vier-Byte-Befehl. Nach dem Befehlsbyte wird die Adresse gesendet, zuerst das High-Byte, dann das Low-Byte. Der Master sendet dann  FF



und empfängt in der Zwischenzeit das gelesene Byte. Nun ja. Zum Lesen jedes Bytes ist ein separater Befehl erforderlich. Schreiben ist auch einfach. Hierzu wird der Befehl verwendet 0x08



. Dies ist ein Vier-Byte-Befehl. Nach dem Befehlsbyte wird die Adresse gesendet, zuerst das High-Byte, dann das Low-Byte und dann das zu schreibende Byte. Zum Schreiben jedes Bytes ist außerdem ein separater Befehl erforderlich. Stellen Sie sicher, dass Sie vor der Aufnahme löschen. Zum Löschen von INFOBLOCK ist nur eine 4-Byte-Sequenz erforderlich :  48 00 00 00



. Das Löschen im Haupt-Flash-Speicher erfolgt mit dem Befehl 88 00 00 00



.



Jetzt wissen Sie genug, um Ihren ZBS24x trivial zu programmieren!



Mach dich an die Arbeit!







Grundierung für 8051



Wenn Sie bereits mit dem 8051 vertraut sind, können Sie  diesen Abschnitt sicher  überspringen .



Der 8051  ist ein  alter Mikrocontroller, der in der Antike von Intel entwickelt wurde . Es ist ein schrecklicher Aufwand, damit zu arbeiten, aber es wird immer noch ziemlich oft verwendet, weil es billig zu lizenzieren ist (tatsächlich kostenlos). Wo liegt das Problem? Der 8051 verfügt über mehrere separate Speicherplätze. CODE



 Ist der Speicherbereich für den Code reserviert? Die maximale Größe beträgt 64 KB (16-Bit-Adresse). In den modernsten Designs ist dies ein Flash-Speicher. Der Code kann von hier aus Bytes mit einer speziellen Anweisung movc



 ("MOVe from Code") lesen XRAM



 Ist "externer" Speicher. Das heißt, außerhalb des Kerns. Sie können verschiedene Dinge darin speichern, aber es ist für alles andere fast nutzlos. So: Die einzigen Operationen, die in diesem Speicher ausgeführt werden können, sind Schreiben und Lesen. Die maximale Größe beträgt 64 KB (16-Bit-Adresse). Wie funktioniert der Adressenspeicher einer 8-Bit-Adresse mit einer 16-Bit-breiten Adresse? Es stellt sich als sehr langsam heraus. Der Befehl movx



 ("MOVe to / from eXternal") greift auf diesen Speichertyp zu. Wie geben Sie jedoch eine 16-Bit-Adresse an? Hierzu wird ein spezielles Register  DPTR



 ("Data PoinTeR") sowie zum Arbeiten mit einer Anweisung verwendet movc



DPTR



 besteht aus einem oberen  DPH



 und einem unteren Register  DPL



... Folglich können Sie durch Schreiben der Hälfte der Adresse in jeden von ihnen den externen Speicher und den Codespeicher adressieren. Wie Sie vielleicht erraten haben, beginnt dieser Prozess schnell zu verrutschen, da Sie beispielsweise zum Kopieren eines Abschnitts vom externen Speicher in den externen Speicher die Werte zwischen DPL



 und  wiederholt mischen müssen DPH



. Aus diesem Grund haben einige der fortgeschritteneren Versionen des 8051 viele Register  DPTR



, aber nicht alle, und nicht alle werden auf die gleiche Weise implementiert.



Intel hat eine schnellere Möglichkeit hinzugefügt, auf eine Teilmenge des externen Speichers zuzugreifen. In diesem Fall besteht die Idee darin, Register R0



 und zu verwenden  R1



 als Zeigerregister. Aber sie sind 8 Bit groß. Woher kommen die anderen 8 Bit in der Adresse? Sie stammen aus einem Register P2



 (das auch Port 2 für die GPIO-Pins steuert). Offensichtlich behindert diese Praxis die Verwendung von Port 2 für ... Sie wissen ... GPIO. Es gibt Möglichkeiten, diese Situation auszugleichen, aber darüber spreche ich jetzt nicht. Daher ist die uns zur Verfügung stehende Speichermenge auf 256 Byte begrenzt (es sei denn, Sie ändern Port 2 dynamisch, was Sie wahrscheinlich nicht möchten). Normalerweise wird dieser Speicher aufgerufen PDATA



. Ähnliche Speicherzugriffe werden auch unter Verwendung einer Anweisung durchgeführt  movx



. Als nächstes haben wir SFR



- verschiedene Konfigurationsregister, mit denen Peripheriegeräte konfiguriert sind. Auf diesen Speicherbereich kann nur direkt zugegriffen werden. Dies ist die Situation: Die Adresse muss direkt in der Anweisung codiert werden, es erfolgt kein Zugriff über ein Zeigerregister. Es gibt 128 Bytes SFR



. Die folgende Tabelle zeigt die SFR



gemäß dem 8051-Standard verfügbaren Listen . Die grauen Kästchen enthalten, auf SFR



welche Bits mit bitweisen Befehlen einzeln zugegriffen werden kann. Dies ist nützlich, wenn Sie Port-Pins atomar zuweisen, Interrupt-Quellen aktivieren / deaktivieren oder einige Status überprüfen.



Der interne Speicher des 8051 ist etwas knifflig. Bei allen modernen 8051 sind es 256 Bytes. Die letzten 128 Bytes  0x80-0xff



 sind verfügbar  nur  indirekt über die Register  R0



 und  R1



, aber im Gegensatz zu der Situation mit externen Speichern, jetzt nicht nur uns zur Verfügung lesen und schreiben sind. Wir können eine Erhöhung um eins ( inc



Rement), eine dec



Verringerung um eins ( Rement), Addition ( add



) und die meisten anderen erwarteten Operationen durchführen. Tatsächlich  wird über diese Zeigerregister indirekt auf ALLEN internen RAM zugegriffen. Niedrigste 128 Bytes 0x00-0x7f



 auch direkt verfügbar (die Adresse wird direkt in der Anweisung selbst codiert, genau wie beim Arbeiten mit SFR



. 16 Byte Speicher im Bereich können 0x20-0x2f



 auch mithilfe von Bitverarbeitungsanweisungen bitadressiert werden. Es ist praktisch, Variablen für boolesche Werte in zu speichern Dieser Teil. Die niedrigsten 32 Bytes  0x00-0x1f



 bilden 4 Bankregister  R0



... R7



Im Statusregister  PSW



 gibt es Bits, mit denen Sie auswählen können, welche Bank gerade verwendet wird. In Wirklichkeit gibt es jedoch einen Mangel im internen Speicherbereich. Der Code verwendet meist nur eine Speicherbank.



Der 8051 ist eine Maschine, die hauptsächlich für die Arbeit mit einem einzelnen Operanden ausgelegt ist. Das heißt: In den meisten Vorgängen wird die Batterie als eine der Quellen und möglicherweise als Ziel verwendet. Register können auch für viele (aber nicht alle) Operationen verwendet werden, und einige Operationen ermöglichen den indirekten Zugriff auf den internen RAM, wie oben beschrieben. Der Stapel ist ein leerer Upstream, adressierbar SFR



, wird aufgerufen  sp



 und befindet sich nur im internen RAM, seine maximale Größe ist auf 256 Bytes begrenzt, aber in Wirklichkeit ist er viel kleiner. 



Jedes 8051-ROM-Image beginnt mit einer Vektortabelle, die Sprünge zum ursprünglichen Code, den Sie ausführen möchten, sowie zu den Interrupt-Handlern enthält. In 8051 befindet sich historisch gesehen der Rücksetzvektor bei 0x0000



und Interrupt-Handler beginnen an der Adresse 0x0003



 und dann alle 8 Bytes. Da der Befehl reti



 nur zur Rückkehr von Interrupts verwendet wird, kann er verwendet werden, um leicht zu erkennen, ob eine bestimmte Funktion ein Interrupt-Handler ist.



Füllen Sie Ihren C-Compiler-Kanal mit all dem und machen Sie einen Zug! 



Für diese Architektur gibt es einen geeigneten C-Compiler: Keils C51. Aber es ist nicht billig. Es gibt auch einen Open Source Compiler: SDCC . Es ist so lala, aber kostenlos. Während dieses Projekts habe ich nur zwei große Fehler gefunden, die nur durch Umgehen behoben werden konnten. Für ein Open Source-Projekt ist das überhaupt nicht schlecht.



Beginnen wir mit der Analyse



void prvTxBitbang(u8 val)
                  __naked {
  __asm__(
    "  setb  PSW.5       \n"
    "  jbc   _EA, 00004$ \n"
    "  clr   PSW.5       \n"
    "00004$:             \n"
    "  clr   C           \n"
    "  mov   A, DPL      \n"
    "  rlc   A           \n"
    "  mov   DPL, A      \n"
    "  mov   A, #0xff    \n"
    "  rlc   A           \n"
    "  mov   DPH, A      \n"
    "  mov   B, #11      \n"
    "00001$:             \n"
    "  mov   A, DPH      \n"
    "  rrc   A           \n"
    "  mov   DPH, A      \n"
    "  mov   A, DPL      \n"
    "  rrc   A           \n"
    "  mov   DPL, A      \n"
    "  jnc   00002$      \n"
    "  setb  _P1_0       \n"
    "  sjmp  00003$      \n"
    "00002$:             \n"
    "  clr   _P1_0       \n"
    "  nop               \n"
    "  nop               \n"
    "00003$:             \n" 
    "  nop               \n"
    "  nop               \n"
    "  nop               \n"
    "  djnz  B, 00001$   \n"
    "  mov   C, PSW.5    \n"
    "  mov   _EA, C      \n"
    "  ret               \n"
  );  }

      
      





Es ist einfach, mit der GPIO-Konfiguration zu beginnen. In der Regel werden Sie auf mehrere übereinstimmende Bits stoßen, die in mehreren Registern hintereinander gesetzt oder gelöscht werden. Dies ist logisch, da Sie beim Aktivieren oder Deaktivieren normalerweise den Pin als Funktion (vom GPIO) verwenden, ihn als Ein- oder Ausgang einstellen und seinen Wert einstellen oder lesen müssen. Sie sollten zu Beginn der Arbeit auf diese Art von Code stoßen. Mal sehen , was es gibt ... wir feststellen , dass die Standard - Register P0



P1



und  P2



 tatsächlich verwendet auf diese Weise, wie mit den Registern GPIO zu beschäftigen. Wenn wir uns ansehen, welche Register um sie herum geschrieben sind und was dann mit den Bits in ihnen passiert (ob sie gelesen (Eingabe) oder geschrieben (Ausgabe) werden), können wir davon ausgehen, dass die Register  AD



AE



AF



 Auf „die Funktion“ entwickelt - und es scheint , dass GPIO, die Bits gesetzt sind entsprechend nicht als gpio verwendet werden, und alle GPIO, tatsächlich als GPIO verwendet, starten Sie so erst nach einem entsprechenden Bit in einem dieser Register Arbeits wird gelöscht. Ich habe sie benannt, PxFUNC



wobei x die Portnummer ist. Dann haben wir den Schluss ziehen können  B9



BA



BB



 steuern die Richtung. Immer wenn in einem von ihnen ein Bit gesetzt ist, wird das entsprechende GPIO nur gelesen, und wenn das Bit gelöscht wird, ist das entsprechende GPIO schreibgeschützt. Daher verstehen wir, dass diese Register die Richtung des GPIO steuern. Ich habe sie benannt  PxDIR



Dabei ist x die Portnummer. Theoretisch könnte ich jetzt den GPIO steuern. Wenn ich nur wüsste, welche von ihnen was tun ... 



Ich habe beschlossen, alle nacheinander auszuprobieren, bis ich die finde, die das "TEST-Pad" am Programmierkopf steuert, oder vielleicht die URX- und UTX-Pads. Eigentlich ... Ich fand, dass Port 1 Pin 0 ( P1.0



) "TEST" ist,  P0.6



 dies ist "UTX" und  P0.7



 dies ist "URX". Mit einem kontrollierten GPIO können Sie Ihr Leben vereinfachen, aber nur solange Sie das Debuggen durch Wechseln verschiedener GPIOs bewältigen können und bis Sie es satt haben. Ich hatte Zeit das zu üben! 



Wir haben printf!



Ich habe diese Funktion verwendet, um das "TEST" -Pad mithilfe der Bit-Bang-Methode in eine normale serielle 8n1-Schnittstelle umzuwandeln, und die Ausgabe mit meinem Logikanalysator gesammelt. Ich spielte damit herum, bis die Baudrate erreicht war, die mein USB-zu-Seriell-Adapterkabel verarbeiten konnte. Ich hatte bereits eine 8051-Implementierung von printf in Assembler. Eine Stunde lang übte ich die Ausgabe komplexer Debug-Zeilen von dieser spontanen seriellen Schnittstelle. Kein schlechter Start, definitiv, dies ist die einzige Möglichkeit, um effektiv voranzukommen! 



Zu diesem Zeitpunkt habe ich im Fenster die Werte aller angezeigt SFR



, um zumindest zu navigieren, was diese Werte sind. Es gab noch einige Probleme mit der weiteren Forschung. Zunächst schien der Watchdog-Timer (WDT) nur standardmäßig eingestellt zu sein und den Chip nach einer Sekunde der Ausführung zurückzusetzen, sodass alle meine Experimente in eine Sekunde oder weniger passen mussten. Ich wusste noch nicht, wie man WDT bedient, also habe ich diese Einschränkung für eine Weile in Kauf genommen. Wie dem auch sei, eine Sekunde ist viele Zyklen! 



Zugriff erweitern



Nachdem ich den Code zuverlässig ausführen und die Ergebnisse ausgeben konnte, entschied ich mich herauszufinden, wo sich die Tick-Steuerelemente befinden. Fast alle Register haben mindestens ein Register, das unterschiedliche Geschwindigkeiten (mindestens die Geschwindigkeit der CPU) steuert, und ein anderes Register, das die Taktrate (oder das Zurücksetzen) verschiedener Module steuert. Sie werden normalerweise so gefunden: Die erste wird normalerweise SEHR  früh beim ersten Laden aufgezeichnet und danach wird sie (wenn überhaupt) kaum berührt. Die Sekunde hat normalerweise ein Bit gesetzt (Taktzyklen) oder ein Bit gelöscht, bevor wir mit der Konfiguration eines Peripheriegeräts beginnen. Wir wissen nicht, wo die verschiedenen Peripheriegeräte konfiguriert sind, aber normalerweise das Set  SFR



mit ähnlichen Nummern entspricht einem Peripheriegerät. Also mal sehen. Auf jeden Fall gibt es einen Fall, passen Sie diese Beschreibung : B7



. Wir sehen, dass es bitweise gesetzt wird, bevor mehrere SFR



mit ähnlichen Nummern geschrieben werden  , und die darin enthaltenen Bits werden gelöscht, nachdem Anrufe an mehrere SFR



mit ähnlichen Nummern gestoppt werden. Wir sehen auch, dass es anfänglich als aufgezeichnet wird  0x2F



, also haben wir es hier mit Peripheriegeräten zu tun, die im Voraus enthalten sind. Da die Bits vor dem zu setzen scheinen,  was wir als Initialisierung von Peripheriegeräten betrachten, werde ich dieses Register aufrufen CLKEN



... Ich habe mit dem Ändern der Bits in diesem Register herumgespielt, und es schien, als wäre nichts passiert, als sie gelöscht wurden. Im Prinzip ist dies logisch, da ich keine Peripheriegeräte benutze.



Ein anderes Register in der Nähe (Literate-Code initialisiert normalerweise alle Taktoperationen zusammen), das dann nicht neu geschrieben wird, ist dieses 8E



. Er schreibt an  0x21



. Ich schlug vor, dass es mit der Geschwindigkeit zusammenhängen könnte. Ich habe experimentiert. Anscheinend werden die 4 niedrigstwertigen Bits bei der Arbeit in keiner Weise wiedergegeben, daher habe ich keine Ahnung, warum sie eingesetzt werden  0b0001



, aber die nächsten drei Bits ändern wahrscheinlich die CPU-Geschwindigkeit erheblich (soweit ich anhand der Geschwindigkeit meines UART beurteilen kann, der der Drift ausgesetzt ist). Das höchstwertige Bit schien die Frequenz ein wenig zu verändern. Ich nahm an, dass es für das Umschalten zwischen der internen RC-Schaltung und dem externen Kristall verantwortlich ist. Drei Bits, von denen ich annahm, dass sie als Frequenzteiler funktionieren, setzen die Taktrate so, dass sie gleich erscheint 16M / (1 + )



. Ich habe dieses Register benannt CLKSPEED



. Folglich wird die höchste Geschwindigkeit bei dem Wert 0x01



und die niedrigste bei erreicht  0xf1







Timer zum Laufen bringen



Viele Hersteller bauen im 8051 auf allerlei Dingen auf, daher gibt es hier nur sehr wenig Standardisierung. Die meisten berühren jedoch nicht die normalen Geräte des 8051, wie z. B. Timer 0 und Timer 1. Bitte beachten Sie: Dies ist keine Faustregel. Zum Beispiel ändert TI die Timer in seinen Chips der CC-Serie erheblich. Ich habe festgestellt, dass in diesem Chip die Register, die normalerweise Standard-8051-Timer konfigurieren sollen, nahe beieinander liegen, und der Interrupt-Handler Nr. 1 scheint sie ebenfalls zu beeinflussen. Ist es möglich? Standard-Timer? Ich habe es versucht und ... es hat funktioniert. Völlig Standard, scheinbar genau so wie die ursprüngliche Spezifikation. Ich habe das Register überprüft  CLKEN



 und das Bit 0 (Maske) gefunden  0x01



) damit die Timer funktionieren. Bestätigt, dass das Standardregister IEN0



 auch wie erwartet funktioniert und dass die Nummern 1 und 3 tatsächlich Interrupts für Timer 0 und Timer 1 auslösen! Die Timer scheinen mit genau 1/12 von 16 MHz zu laufen, genau wie es bei einem Standard 8051 mit 16 MHz zu erwarten wäre. Bisher habe ich nicht gefunden, wie ich diese Frequenz ändern kann. Was wir wissen jetzt enthüllt Register  TL0



TH0



TL1



TH1



TMOD



TCON



! Wir haben jetzt funktionierende Präzisions-Timer!



Ich war nicht zu faul zu prüfen, ob Timer 2 tatsächlich im Standard 8052 (Fortsetzung von 8051) implementiert ist. Nein, das ist es nicht. 



Oder vielleicht UART?



void uartInit(void) {
    // 
    CLKEN |= 0x20;
 
    //  
    P0FUNC |= (1 << 6) | (1 << 7);
    P0DIR &=~ (1 << 6);
    P0DIR |= (1 << 7);
 
    // 
    UARTBRGH = 0x00;
    UARTBRGL = 0x89;
    UARTSTA = 0x12;
}
 
void uartTx(u8 ch) {
    while (UARTSTA_1));
    UARTSTA_1 = 0;
    UARTBUF = ch;
}

      
      





Das OTA-Modul enthielt mehrere Zeilen. Es macht Sinn, dass sie sich auf etwas beziehen sollten, oder? Vielleicht eine serielle Debug-Schnittstelle? Dies passt gut zu einem Board mit den Schlüsselpunkten "UTX" und "URX". Dieser Code war ein wenig verworren, aber es sah so aus, als würde er Bytes in einer Art Puffer speichern. Der Code sah definitiv wie ein Standard-Ringpuffer aus. Ich habe nachgesehen, wo dieser Puffer gelesen wird. Es stellte sich heraus, dass es im Handler für Interrupt # 0 war. Oooh, interessant. Könnte es ein UART-Interrupt-Handler sein? Der Code schien Bit Nr. 1 in einem Bereich zu prüfen, der einem Statusregister (Register 98



) ähnelte  , und wenn er gesetzt war, las er ein Byte aus unserem Ringpuffer und schrieb es in ein Register 99



... Wenn ein anderes Bit (# 0) in das oben genannte Statusregister gesetzt wurde, dann las es das Register  99



 und fügte das Ergebnis in ... einen anderen Ringpuffer ein. Nun, das stimmt verdammt gut mit dem überein, was ich von einem UART-Interrupt-Handler erwarten würde! Was machen wir als Nächstes? 



Jeder Kreispuffer hat zwei Zeiger, einen zum Lesen und einen zum Schreiben. Es ist sinnvoll, dass sie initialisiert werden, bevor der Puffer für irgendetwas verwendet wird. Wenn wir also herausfinden, wo diese Indizes initialisiert sind, werden wir wahrscheinlich herausfinden, wo der UART installiert ist, oder? Sieht auf jeden Fall so aus. In dieser Funktion, die den UART initialisiert, sehen wir diesen GPIO  P0.6



 und  P0.7



setzen ihn in den Funktionsmodus.  P0.7



 wird auf Eingang und  P0.6



 - auf Ausgang gesetzt. Zwei weitere Register:  9A



 und  9B



 sind geschrieben mit  0x00



 und  0x89



 jeweils. Das Register, das gemäß meiner Version mit Zuständen (Register  98



) arbeitet, wird als geschrieben  0x10



, und dann werden die Bits 0 und 1 darin gelöscht. Dann wird  CLKEN



 Bit 5 und IEN0



 Bit 0 gesetzt. Das ist im Prinzip alles, was wir brauchen! 



Also benennen wir das Register  und das Register  wird  . Wir wissen das  99



  UARTBUF



98



UARTSTA



UARTSTA



 muss auf 0x10 gesetzt werden, damit dieser Block funktioniert, und wir wissen, dass Bit 0 bedeutet, dass der UART ein freies Byte in der TX-FIFO-Warteschlange hat, und Bit 1 bedeutet, dass der UART ein Byte in der RX-FIFO-Warteschlange für uns hat. Wir wissen, dass CLKEN



 Bit 5 die Uhr für den UART aktiviert hat und dass die Interrupt-Nummer 0 dem UART-Interrupt-Handler entspricht. Es ist nur ein Schatz an Informationen. In diesem Wissen konnte ich einen funktionierenden UART-Treiber in meinem Code erstellen und eine ausgehende Nachricht an den gewünschten "UTX" -Pin senden, der sich, wie wir jetzt wissen, an Port 0, Pin 6 ( P0.6



) befindet. Wir haben auch erfahren, dass der "URX" -Schlüsselpunkt mit P0.7



diesem verbunden  ist und dass dies die RX-Leitung im UART ist. Der UART sendete Daten mit 115.200 Bit / s (8n1) und war in keiner Weise vom Register betroffen CLKSPEED



... Also, was sind diese beiden anderen mysteriösen Register, die diese magischen Bedeutungen geben? 



Ich habe versucht, an den beiden verbleibenden Registern zu basteln,  9A



 und  9B



. Es wurde schnell klar, wofür sie waren. Dies sind Frequenzteiler. Ich habe einige Werte eingegeben, um zu sehen, wie sie sich auf die Baudrate auswirken. Es stellte sich als einfach heraus.  9A



 (im Folgenden als bezeichnet  UARTBRGL



) war das niedrige Byte und 9B



 (im Folgenden bezeichnet  UARTBRGH



) war das hohe Byte (die oberen 4 Bits werden anscheinend ignoriert). Die Baudrate wird einfach berechnet als  16M / (UARTBRGH:UARTBRGL + 1)



. Dies erklärt perfekt die Werte, die magisch erschienen - sie entsprechen 115.200 Baud.



Anscheinend hängt ein kleiner Fehler damit zusammen, dass die Statusbits programmgesteuert gelöscht werden können, ohne das FIFO zu beeinflussen. Wenn Sie also versehentlich das Bit löschen, das bedeutet, dass im TX-FIFO freier Speicherplatz vorhanden ist ( UARTSTA



.1), dann den Interrupt wird niemals auftreten und das Bit wird niedrig bleiben.



Seltsamerweise stimmen diese Speicherorte mit den korrekten 8051-Adressen für SCON



 und  überein  SBUF



, bei denen es sich um die Register der seriellen 8051-Schnittstelle handelt. Die Bits 0, 1 und 2  UARTSTA



 passen wirklich zu den Beschreibungen  SCON



von 8051, aber hier ist die Ähnlichkeit vorbei. Für UART von 8051 müssen die Bits 7 und 6 gesetzt werden  SCON



in 0 und 1 wird es nur auf diese Weise zu einem normalen UART. Dieser Chip benötigt in diesem Fall 0 und 0. Darüber hinaus verfügt der 8051 UART normalerweise nicht über einen Baudteiler, anstelle dessen Timer 1 verwendet wird.



Watchdog Timer und "Look!"



Zu diesem Zeitpunkt begann mich das 1-Sekunden-Ausführungslimit, das durch die Standard-Watchdog-Konfiguration garantiert wurde, zu ärgern. Ich habe mich entschlossen herauszufinden, wo und wie der Watchdog konfiguriert ist. Normalerweise wird der Watchdog-Timer als Teil seiner eigenen Funktion konfiguriert und ist klein. Natürlich werde ich nicht sagen, dass dies immer passiert, aber meistens sieht es so aus. Ich hatte mehrere Kandidaten und versuchte, die Schreibvorgänge der Register nacheinander in mein Testprogramm zu kopieren, aber der Wachhund gab nicht nach. Ich musste den Chip jede Sekunde richtig zurücksetzen.



Dabei bemerkte ich eine sehr merkwürdige Funktion. Anscheinend hat sie das Register unter der Nummer gelesen FF



, dort etwas geschrieben und dann zurückgesetzt P1DIR



, schrieb in ein anderes Register und stellte dann den ursprünglichen Wert im Register wieder her  FF



. Die Seltsamkeit war, dass ALLE  Pins von Port 1 auf Pin gesetzt wurden. Das ist Unsinn. Bei anderen Modellen sind an Port 1 mehrere Pins als Eingang konfiguriert. Außerdem werden solche Register normalerweise Stück für Stück unter Verwendung von Anweisungen  anl



(logisches UND) und  orl



(logisches ODER) betrieben. Solch ein grobes Schreiben in das gesamte Register sah auf einmal abstoßend aus. Was ist mit dem Register FF



, das gesichert und wiederhergestellt werden muss? Es sah sehr seltsam aus! 



Ich beschloss, nachzuforschen. Beim Speichern des Registerwerts auf der Konsole FF



Es stellte sich heraus, dass es Null war, was natürlich nicht zu mir passte. Ich habe die gesamte Firmware durchsucht und festgestellt, dass fast überall darin eine Aufzeichnung, dann eine Sicherung und dann der ursprüngliche Wert wiederhergestellt ist. Mir ist auch aufgefallen, dass das Schreiben fast immer mit einem Wert 0x04



und selten mit  einem Wert geschieht  0x00



... Dieses Register wurde während der Sicherung zur weiteren Wiederherstellung schreibgeschützt, für diesen Wert wurden keine weiteren Aktionen ausgeführt. Welche Funktionalität zeigt dies an? Grundsätzlich funktionieren Memory-Banking-Steuerelemente normalerweise so! Wenn Sie mehr Informationen haben, als Sie in Ihren Adressraum passen, müssen Sie wechseln. Dieses Zugriffsmuster (Sicherung vor Änderung und anschließende Wiederherstellung) ist typisch für solche praktischen Situationen. Aber was können sie aufbewahren? Kann das sein? Überlasten diese Verrückten den Speicherplatz selbst SFR



?!



Ich habe ein Programm geschrieben, das die Werte von allen SFR



, allen 128 anzeigen kann.  Dann habe ich das Bit 0x04



 in  verwandelt  FF



  SFR



und nahm wieder den ganzen Raum heraus SFR



. Dann wickelte das Programm dieses Bit zurück und zeigte erneut alle Werte an. Gott, der Allmächtige! So ist das! Bit 2 im Register  FF



 spart wirklich Platz SFR



. Ich habe ohne Zweifel gesehen, dass sich die angezeigten Werte ändern, wenn dieses Bit gesetzt ist. Anscheinend betraf dies nicht ALLE Adressen  SFR



, sondern viele. Ich habe dieses Register benannt CFGPAGE



.



Jetzt, wo  CFGPAGE



ich dachte, ich wäre erledigt, kehrte ich zu meiner mysteriösen Funktion zurück, die sich auf Null stellte P1DIR



. Ich weiß bereits, dass es in diesem Fall NICHT  auf Null zurückgesetzt wird P1DIR



, sondern sein seltsamer Cousin auf einer anderen Seite SFR



Ich habe versucht, diesen Code in mein Programm zu kopieren. Ob Sie es glauben oder nicht, ich bin versehentlich auf einen Code gestoßen, der WDT deaktiviert !!!



Untersuchte den Code, der diese Funktion umgibt, da sich normalerweise verwandte Funktionen in Binärdateien nebeneinander befinden. Es gab tatsächlich mehrere Funktionen in der Nähe, die auch CFGPAGE



 auf die benachbarte Adresse zugegriffen haben und auf diese zugegriffen haben P1DIR



. Nach ein paar Stunden Versuch und Irrtum verstand ich die Details der Funktionsweise des Watchdogs vollständig. Auf der 4. Seite der Konfigurationen wird die Adresse BF



angezeigt, um das Aktivieren und Zurücksetzen des Watchdog-Timers zu steuern. Das höchstwertige Bit dieses Registers aktiviert oder deaktiviert die Chip-Reset-Funktion im Watchdog-Timer. Ich habe es genannt WDTCONF



. Adresse  BA



 (befindet sich  P1DIR



 auf Konfigurationsseite 0) ist das Watchdog-Aktivierungsregister. Bit 0 aktiviert oder deaktiviert hier den Watchdog-Timer selbst. Ich habe es genannt WDTENA



.



Bis zu diesem Zeitpunkt habe ich noch herausgefunden, wie ich den Watchdog-Timer zähmen kann. Es hat eine Weile gedauert, aber am Ende habe ich es herausgefunden. Ein Register  BB



 (jetzt benannt  WDTPET



) kann auf Null geschrieben werden, um den Watchdog-Timer zu zähmen. Ich brauchte noch ein paar Minuten, um herauszufinden, wie die Verzögerung im Watchdog-Timer konfiguriert werden sollte, da der Adressraum zwischen BB



 und  eindeutig eine Lücke aufwies BF



... Der Zähler ist 24 Bit lang und bei Zähmung überlastet. Es kann nicht gelesen werden. Reload - Wert gespeichert in WDTRSTVALH



: WDTRSTVALM



: WDTRSTVALL



zumin befand BE



BD



BC



 jeweils auf die Konfigurationsseite 4. Der Zähler zählt  hoch  bei einer Frequenz von etwa 62 kHz und einem Überlauf ausgelöst wird . Um eine erhöhte Verzögerung zu liefern, muss daher ein kleinerer Wert in diese Rücksetzregister geschrieben werden.



Subtilere Möglichkeiten



Flash-Speicherprogrammierung



//    irqs 
voif flashDo(void) {
    TRIGGER |= 8;
    while (!(TCON2 & 0x08));
    
    TCON2 &=~ 0x48;
    SETTINGS &=~ 0x10;
}
 
void flashWrite(u8 pgNo, u16 ofst,
              void *src, u16 len) {
    u8 cfgPg, speed;
    
    speed = CLKSPEED;
    CLKSPEED = 0x21;
    cfgPg = CFGPAGE;
    CFGPAGE = 4;
    
    SETTINGS = 0x18;
    FWRTHREE = 3;
    FPGNO = pgNo;
    FWRDSTL = ofst;
    FWRDSTH = ofst >> 8;
    FWRLENL = len - 1;
    FWRLENH = (len - 1) >> 8;
    FWRSRCL = (u8)src;
    FWRSRCH = ((u16)src) >> 8;
    flashDo();
    
    CFGPAGE = cfgPg;
    CLKSPEED = speed;
}
void flashRead(u8 pgNo, u16 ofst,
    void __xdata *dst, u16 len) {
    u8 pgNo, cfgPg, speed;
    
    speed = CLKSPEED;
    CLKSPEED = 0x21;
    cfgPg = CFGPAGE;
    CFGPAGE = 4;
    
    SETTINGS = 0x8;
    FWRTHREE = 3;
    FPGNO = pgNo;
    FWRDSTL = (u8)dst;
    FWRDSTH = ((u16)dst) >> 8;
    FWRSRCL = ofst;
    FWRSRCH = ofst >> 8;
    FWRLENL = len - 1;
    FWRLENH = (len - 1) >> 8;
    flashDo();
    
    CFGPAGE = cfgPg;
    CLKSPEED = speed;
}
void flashErase(u8 pgNo) {
    u8 __xdata dummy = 0xff;
    u8 cfgPg, speed;
    
    speed = CLKSPEED;
    CLKSPEED = 0x21;
    cfgPg = CFGPAGE;
    CFGPAGE = 4;
    
    SETTINGS |= 0x38;
    FWRTHREE = 3;
    FPGNO = pgNo;
    FWRDSTL = 0;
    FWRDSTH = 0;
    FWRLENL = 0;
    FWRLENH = 0;
    FWRSRCL = (u8)&dummy;
    FWRSRCH = ((u16)&dummy) >> 8;
    flashDo();
    
    CFGPAGE = cfgPg;
    CLKSPEED = speed;
}

      
      





Ich habe mich auf das OTA-Image konzentriert, da es kleiner als die Hauptfirmware ist. Ein Detail, das im OTA-Image unbedingt benötigt wird, ist die Fähigkeit, in den Flash-Speicher zu schreiben. Wie sieht es aus? Es wird angenommen, dass wir eine Funktion benötigen, die den Blitz löscht, da der Blitz in Blöcken gelöscht wird. Sie benötigen außerdem eine Schreibfunktion, mit der eine Datenseite oder weniger geschrieben werden kann. Wir brauchen eine Art Überprüfung der aufgezeichneten Daten. Das einzige Detail, das sich in den Implementierungen unterscheidet, ist, wie wir die Daten, die zum Schreiben bestimmt sind, in den Flash-Controller einspeisen. Ich wusste nicht, wie es aussehen sollte, aber der Rest war leicht zu finden. Die Überprüfung würde sich wahrscheinlich darauf beschränken, nur anzurufen memcmp



oder Zyklus. Durch Flash-Löschvorgänge wird der Flash-Speicher abgenutzt. Daher muss die Seite vor dem Löschen überprüft und anschließend ausgeführt werden. 



Auf der Suche nach einer Überprüfung vor dem Löschen fand ich schnell eine Funktion, die einen  0x400



 Byte-zu-  XRAM



Voll-Bereich von Bytes erstellt 0xFF



. Dann wird der Speicherbereich  CODE



mit diesem Puffer verglichen, und wenn sie nicht gleich sind, werden Interrupts deaktiviert und einige werden SFR



auf der Konfigurationsseite 4 berührt. Die Seitengröße im Flash-Speicher beträgt eindeutig 1024 Bytes. Überprüfen, welche anderen Orte davon betroffen sind SFR



finden wir den restlichen Flash-Code. Aus dem Kontext geht hervor, was diese Register tun und wie. In diesem Fall ist es interessant, wie die Daten der Flash-Speichersteuereinheit zugeführt werden. Dieser Steuerblock enthält eindeutig einen DMA-Block. Der Flash-Speichersteuereinheit wird eine Adresse zugeführt, XDATA



und die Daten werden direkt von dort absorbiert. Wie cool!



Zu diesem Zeitpunkt war ich mir noch nicht sicher, wie ich INFOBLOCK lesen sollte. Anscheinend ging ihn der OTA-Code nichts an, aber irgendwo MUSS  er gelesen werden - schließlich sind Daten darin. Ich überprüfte das Hauptbild und bemerkte ein Code-Snippet, das dasselbe betraf  SFR



aus dem Flash-Speicher, aber auf andere Weise. Mit etwas mehr Analyse konnte ich den korrekten Messwert von INFOBLOCK reproduzieren. Es ist merkwürdig, dass dieselbe Methode zum Lesen jedes anderen Flash-Speicherblocks verwendet werden kann, dies ist jedoch nicht erforderlich, da Sie zum Lesen des Flash-Speichers lediglich den Speicherbereich lesen müssen  CODE



. INFOBLOCK ist nur über die Flash-Speichersteuereinheit zugänglich. Sowohl für das Schreiben als auch für das Lesen aus dem Flash-Speicher verwendet der Steuerblock den direkten Speicherzugriff (DMA) und schreibt in  XDATA



.



Ein Register  DF



 ( FWRTHREE



) widersetzte sich allen Erklärungsversuchen. Es hatte immer einen Rekord mit dem Wert 0x03



, ich weiß nicht warum. Mein Flash-Zugangscode macht dasselbe. Register  D8



 ( FPGNO



) wird mit der Flash-Seitennummer geschrieben. Die Hauptseite des Flash - Speichers von 0 bis 63 nummeriert sind, hat Infoblock - Nummer 128  DA



:. D9



 ( FWRSRCH



:) FWRSRCL



ist die Quelle des DMA - Blockes in dem Steuerblock - Flash - Speicher. Zum Schreiben in Flash enthält es die Adresse, XDATA



an der wir die zu schreibenden Daten finden. Um den Flash zu lesen, wird nach einem Byte-Offset auf der Originalseite gesucht, und das Lesen beginnt bei diesem Offset.  DC



: DB



 ( FWRDSTH



: FWRDSTL



) Ist die Zuordnung für DMA im Flash-Speicherverwaltungsblock. Damit das Schreiben auf Flash blinkt, enthält es den Byte-Offset auf der Zielseite, und das Schreiben beginnt an diesem Punkt. Zum Lesen des Flashs wird die Adresse verwendet,  XDATA



an die die beim Lesen empfangenen Daten geschrieben werden.  DE



: DD



 ( FWRLENH



:) FWRLENL



Ist die Länge der Daten, die der DMA-Block übertragen soll, minus eins.



Das Schreiben in den Flash-Speicher als solcher wird ausgelöst, indem ein Bit in einem weiteren gesetzt wird SFR



. Verschiedene Bits darin sind auch gesetzt, um anderen Code zu steuern, der anscheinend nicht mit dem Flash-Speicher zusammenhängt. Daher kam ich zu dem Schluss, dass dieses Register wahrscheinlich verschiedene Aktionen auslösen würde. Ich habe dieses Register benannt D7



 auf der Konfigurationsseite 4  TRIGGER



. Der Abschlussstatus wird auch in einem Register überprüft, das anscheinend auch von anderen Codes gemeinsam genutzt wird. CF



 Ich habe  dieses Register auf der Konfigurationsseite 4 benannt  TCON2



. Warum nicht? Es gab auch ein Register C7



, das auch in Verbindung mit anderem Code verwendet wurde und anscheinend konfigurierte, welche Operation ausgeführt werden sollte. Ich habe es genannt SETTINGS



0x30



 wurde mit einem logischen ODER darauf geschrieben, 0x18



 um zu löschen + schreiben,  einen Blitz zu schreiben, einen Blitz  0x08



 zu lesen. Ich vermutete, dass das Bit 0x08



 "Datenübertragung ausstehend" 0x10



 bedeutet "in Flash" und  0x20



 "Löschen". Dies ist sinnvoll, wenn man bedenkt, welche Werte wir sehen und welche Operationen hier ausgeführt werden.



Das Lesen und Schreiben im Flash hat wunderbar funktioniert, aber das Löschen hat anscheinend nicht funktioniert. Anstatt die Seite mit dem angegebenen Code zu löschen, wurde aus irgendeinem Grund die Seite, auf der sich der Code befand, der das Löschen anforderte, ständig gelöscht. Offensichtlich war dieses Problem nicht in dem Code enthalten, der auf diesem Gerät enthalten war. Ich habe etwas falsch gemacht. Überprüft, überprüft und erneut überprüft, um sicherzustellen, dass mein Code mit dem Werkscode übereinstimmt. Passend dazu. Was ist los mit dir? Ich habe mehrere Tage gearbeitet, bis mir klar wurde, dass der Werkscode bei 4 MHz und meiner bei 16 MHz funktioniert. Könnte dies der Punkt sein? Es stellte sich genau so heraus! Ich habe meinen Flash-Löschcode geändert, um den aktuellen Frequenzteiler beizubehalten, und den Takt für die Dauer des Flash-Löschvorgangs auf 4 MHz verlangsamt. Ging in Ordnung, da dieser Code bereits mit deaktivierten Interrupts ausgeführt wird.



Eine weitere Feinheit dieser Flash-Speichersteuereinheit besteht darin, dass sie anscheinend keine einfache "Lösch" -Operation vorsieht. Ich dachte darüber nach, die entsprechenden if-Bits im Register zuzuweisen  SETTINGS



, und dann schien es mir logisch, dass beim Setzen auf 0x20



 oder  0x30



 ein einfaches Löschen erfolgen sollte. Die einzige Möglichkeit, dies zu löschen, besteht darin, eine Lösch- + Schreiboperation auszuführen, die mindestens ein Byte schreibt (da es keine Möglichkeit gibt, eine Länge von Null in FWRLENH



: FWRLENL



darzustellen . Um ein einfaches Löschen durchzuführen, bitte ich einfach, ein einzelnes Byte zu schreiben 0xFF



. Es klappt



SPI



Grundsätzlich sind alle SPI-Treiber gleich. Am Eingang wird ein Byte empfangen, am Ausgang ein Byte. Natürlich haben einige DMA und einige sind Interrupt-gesteuert, aber 99% von ihnen in kleinen Systemen sind softwaregesteuert, und irgendwo gibt es eine einfache Funktion u8 spiByte(u8 byte);



.



Es war logisch, sich eingehender mit SPI zu befassen. Da wir wissen, dass es SSD1623L2



 mit SPI kommuniziert, und wir auch die Details der Organisation einer solchen Kommunikation kennen, müssen wir uns nur den Code ansehen und herausfinden, welcher Teil davon diese Operation ausführen soll. Genau wie Sudoku wird diese Suche nicht schwierig sein, wenn man bedenkt, wie viel wir bereits wissen. Betrachten Sie das Datenblatt SSD1623L2



 wir sehen, dass die Registernummer des ersten gesendeten Bytes in die Bits 1..6 geschrieben ist und sich das "Schreib" -Bit an Position 7 befindet. Alle Register sind 24 Bit lang. Es ist logisch, dass der Programmierer einen Code schreibt, der die Registernummer als Parameter verwendet, sie um eins nach links verschiebt, möglicherweise logisch oder in 0x80



, wenn ein Schreibvorgang angefordert wird, und dann drei Bytes überträgt. Nicht alle Programmierer handeln logisch, aber diese Annahme hilft unermesslich beim Reverse Engineering. Wenn man sich den Code ansieht, ist es leicht zu erkennen, welche Funktionen genau so aussehen. Einige fügen hinzu 0x80



, andere nicht. Sie alle nennen dieselbe mysteriöse Funktion für jedes Byte. Wir gehen also davon aus, dass einige Text auf dem Bildschirm anzeigen, andere lesen. Lassen Sie uns die mysteriöse Funktion selbst angehen.



Tatsächlich ist hier alles so einfach wie das Schälen von Birnen. Es schaltet  CFGPAGE



 auf 4 um, schreibt dann den ED



 Wert  in das Register  0x81



, schreibt das zu sendende Byte EE



, schreibt  0xA0



 in  EC



, verzögert 12 Mikrosekunden, setzt Bit 3 auf  EB



, liest das empfangene Byte aus  EF



, speichert  0x80



 in  ED



. Das ist alles. Wie kann man das alles verstehen? Verlassen Sie sich nach wie vor auf das, was bereits bekannt ist.



0x80



 und  0x81



 unterscheiden sich nur in einem Bit, und wir setzen es vor dem Starten der SPI-Operation, und nach dem Ende der Arbeit setzen wir es zurück, so dass dies anscheinend eine Art "aktivierendes" Bit ist. Andererseits klingt die Bedeutung  0xA0



 buchstäblich  wie eine  Konfiguration. Das Register  EB



 ist immer noch ein Rätsel. Wenn ich diesen Code jedoch reproduziere, ohne darauf zu schreiben, funktioniert alles, sodass ich zu dem Schluss komme, dass nicht viel von diesem Register abhängt. Auf jeden Fall EE



 dies  SPITX



und  EF



 das  SPIRX



. Ich rief an  ED



 -  SPIENA



 und  EC



 -  SPICFG



.



Es bleibt zu charakterisieren, was die Beats tun SPICFG



... Ich habe ein bisschen Versuch und Irrtum gemacht, bewaffnet mit einem Logikanalysator. Bit 7 muss gesetzt sein, Bit 6 muss gelöscht werden. Bit 5 startet die Übertragung des SPI-Bytes und löscht sich, wenn es damit fertig ist. Die Bits 3 und 4 stellen die Taktfrequenz ein. Sie können zwischen folgenden Werten wählen: 500 kHz, 1 MHz, 2 MHz, 4 MHz. 2 ist das Standardkonfigurationsbit CPHA



 für SPI, Bit 1 ist  CPOL



. Bit 0 scheint RX zu verletzen. Ich gehe davon aus, dass er den Block für Halbduplex (in Reihe MOSI



) konfigurieren kann  . Im Allgemeinen ist es nicht so schwierig.



Pin für Pin, finden Sie schnell die GPIO-Konfiguration und sehen Sie, was P0.0



 dies  SCLK



P0.1



 dies  MOSI



 und  P0.2



 das ist  MISO



... Wenn wir suchen, wo diese GPIOs konfiguriert sind, sehen wir auch, wie das CLKEN



 SPI- Bit benötigt wird  : das ist Bit 3. Großartig - wir haben jetzt ein funktionierendes SPI!



Bestimmen Sie die Temperatur 



volatile u8 __xdata mTempRet[2];
 
void TEMP_ISR(void) __interrupt (10)
{
  uint8_t i;
  
  i = CFGPAGE;
  CFGPAGE = 4;
  mTempRet[0] = TEMPRETH;
  mTempRet[1] = TEMPRETL;
  CFGPAGE = i;
  IEN1 &=~ 0x10;
}
 
int16_t tempGet(void)
{
  u16 temp, sum = 0;
  u8 i;
  
  CLKEN |= 0x80;
  
  i = CFGPAGE;
  CFGPAGE = 4;
  TEMPCFG = 0x81;
  TEMPCAL2 = 0x22;
  TEMPCAL1 = 0x55;
  TEMPCAL4 = 0;
  TEMPCAL3 = 0;
  TEMPCAL6 = 3;
  TEMPCAL5 = 0xff;
  TEMPCFG &=~ 0x08;
  CFGPAGE = i;
  IEN1 &=~ 0x10;
  
  for (i = 0; i < 9; i++) {
    
    // 
    IEN1 |= 0x10;
  
    // 
    while (IEN1 & 0x10);
    
    if (i) {  //  
      
      sum += u8Bitswap(mTempRet[0]) << 2;
      if (mTempRet[1] & 1)
        sum += 2;
      if (mTempRet[1] & 2)
        sum += 1;
    }
    
    timerDelay(TICKS_PER_S / 1000);
  }
  // 
  CLKEN &=~ 0x80;
  
  return sum / 8;
}

      
      





E-Ink-Anzeigen werden je nach aktueller Temperatur unterschiedlich aktualisiert. Daher ist es wichtig, die Umgebungstemperatur zu kennen, um sie korrekt zu aktualisieren. Die richtigen Wellenformen werden abhängig von der Temperatur ausgewählt. Hier wird Wissen von außen nützlich sein. Wenn wir also herausfinden können, wo die Wellenformen in den Display-Controller geladen werden, können wir herausfinden, wo die Auswahl getroffen wird. Von hier aus können Sie direkt zu dem Punkt gehen, an dem die Temperatur gemessen wird, oder? Nachdem wir dies getan haben, gehen wir zu genau einer Funktion, deren Ausgabe bestimmt, welche Wellenform verwendet wird. Das muss es sein! Übrigens: Normalerweise sind Temperatursensoren am ADC angebracht - fast niemand stellt sie in einer separaten Version her. Aber es spielt [noch] keine Rolle.



Alles beginnt mit dem Setzen von Bit 7 auf  CLKEN



und endet mit dem Zurücksetzen, so dass wir zumindest wissen, dass wir auf diese Weise den Temperatursensor (oder ADC) ein- und ausschalten. Die Funktion wechselt  CFGPAGE



 zu 4 und schreibt dann eine Reihe von Werten in eine Reihe von Registern. Alle Werte sind konstant. 0x81



 -> reg.  F7



0x22



 -> reg.  E7



0x55



 -> reg.  E6



0x00



 -> reg.  FC



0x00



 -> reg.  FB



0x03



 -> reg.  FE



0xFF



 -> reg.  FD



, dann werden die Bits auf  0x81



 gespült  F7



. Danach  CFGPAGE



 stellt Bit 4 im Register wieder her und löscht es dann A1



. Dies scheint die anfängliche Einrichtung zu sein. Nachdem eine bestimmte Prozedur fünfmal ausgeführt wurde, werden die Ergebnisse aller Operationen mit Ausnahme der ersten gemittelt. Danach wird eine Menge Mathematik für den auf diese Weise erhaltenen Durchschnitt durchgeführt, insbesondere unter Verwendung der Werte von INFOBLOCK - wahrscheinlich sind dies Kalibrierungswerte. Das Ergebnis wird dann zurückgegeben. Schauen wir uns die Details genauer an.



Dabei wurde einfach Bit 4 im Register gesetzt A1



wurde das globale Bit gesetzt und dann verbringen wir im aktiven Standby-Modus Zeit, bis das Bit gelöscht ist. Die spezifischen gemittelten Werte stammen anscheinend von einem globalen Wert. Das ist komisch ... Ich habe gesucht, wo es geschrieben ist, und es im Interrupt # 10-Handler gefunden. Offenbar war dies , wie Bit 4 im Register gelöscht wurde A1



, dann wird der Schalter auf Konfigurationsseite 4 stattfand, wurden die Werte aus den Registern gelesen F8



 und  F9



, und einige seltsame Dinge mit ihnen gemacht, und dann wurde diese globale Wert geschrieben . Aber was wird mit diesen Werten gemacht? 



Ich war gerade in den Augen sticht Konstanten  0x55



0xAA



0xCC



und  0x33



... Ist das möglich? Könnte jemand so stumpf sein, dass ... na ja. Dies sind Konstanten für eine clevere Möglichkeit, die Reihenfolge der Bits in einem Byte umzukehren. Tricky, aber nur auf fortgeschritteneren Prozessoren. Auf 8051 ist dieser Ansatz sehr ineffektiv. Aber warum? Es scheint, dass unabhängig von der IP (Befehlszeiger), die sie zur Messung der Temperatur lizenzieren, ein Ergebnis erzeugt wird, bei dem die Bits in umgekehrter Reihenfolge sind. Warum dieses Problem auf der Softwareebene eines proprietären Chips gelöst werden sollte, ist eine große Frage. Schließlich ist es nicht schwieriger, die Reihenfolge der Bits in der Hardware umzukehren, als ein paar Drähte neu zu ordnen ... Was macht es? Weiß nicht. Tatsächlich habe ich es nie verstanden. 



Fast niemand entwirft einen speziellen Befehlszähler für einen Temperatursensor. Dieses Ding wird einfach in den ADC eingesteckt. Nachdem ich diesen Code erneut implementieren und sicherstellen konnte, dass er sehr gut funktioniert, habe ich versucht, alle diese Register zu ändern. Die meisten von ihnen beeinflussten die Verstärkung des Temperatursensors, einige hatten keine Auswirkung. Wenn dies ein normaler ADC wäre, würden wir erwarten, dass einige Bits ihn auf eine andere Art von Eingang umschalten und einen völlig anderen Wert ergeben. Dies ist leider nicht geschehen. Es sah wirklich aus wie ein normaler Temperatursensor. Dies wird auch bestätigt, weil diese Register nirgendwo anders berührt werden. Seltsam wie die Hölle, aber okay ... 



Da fast alle diese Register nur einmal geschrieben werden und es sich um diese Werte handelt und deren Änderung sich auf den gemessenen Wert auswirkt, habe ich beschlossen, sie einfach alle Temperaturkalibrierungswerte zu nennen. Deshalb lernen wir TEMPCAL1



 (reg.  E6



),  TEMPCAL2



 (Reg.  E7



),  TEMPCAL3



 (Reg.  FB



),  TEMPCAL4



 (Reg.  FC



),  TEMPCAL5



 (Reg.  FD



) Und  TEMPCAL6



 (reg.  FE



) Bekannt werden. Ich habe es benannt  , da es mehrmals verwendet wird und das Laden des Kalibrierungswerts tatsächlich zu verwalten scheint. Die Ergebnisse werden in   (reg.  F7



  TEMPCFG



TEMPRETH



F8



) und  TEMPRETL



 (reg.  F9



). Die Ergebnisse sind 10 Bit lang und am umgekehrten Ende eines 16-Bit-Ergebnisregisters mit umgekehrter Bitreihenfolge ausgerichtet.  



Mir ist auch aufgefallen, dass Bit 3 in gesetzt ist  TEMPCFG



 , wenn das Sample fertig erstellt ist. Seltsamerweise überprüft der Factory-Code dies nicht und stützt sich stattdessen auf den Interrupt. Tatsächlich hat es sich jedoch  als nützlich erwiesen, den Zweck des Registers zu entschlüsseln A1



. Wie Sie sehen können, ist der klassische 8051 auf 7 Interruptquellen beschränkt, da wir 8 Bits im Register haben  IEN



und Bit 7 ist reserviert, um einen globalen Interrupt zu aktivieren. Wie gehen Sie mit Interrupts ab Nummer 7 um? Tatsächlich ist es wie im wilden Westen. Was Sie wollen, ist das, was Sie tun. Aber hier haben wir eine Hardware, die Interrupt Nummer 10 auslöst, und mit ein wenig können wir erkennen, wann es fertig war. Dies ist ideal zum Experimentieren. in dem wir wissen wollen, wie Interrupts über 7 aktiviert und deaktiviert werden. Es war nur notwendig, an diesem Code zu basteln, bis Sie den Interrupt loswerden, aber das Beispiel wird erstellt . Die Suche dauerte nicht lange. Es muss es sein A1



! Ich habe ihn genannt  IEN1



... Ich bin nicht sicher, welche Funktion Bit 0 hier hat, aber Bits 1 und höher steuern die Aktivierung der Interrupts Nummer 7 und höher. Das konnte ich später bestätigen. Okay, fertig - wir haben noch ein weiteres Peripheriegerät dokumentiert und so noch mehr Kuriositäten entdeckt ...



I2C



Zu diesem Zeitpunkt öffnete ich ein größeres E-Ink-Preisschild, das mit demselben Chip ausgestattet war. Es war ein 2,9-Zoll-Modell mit einem E-Ink-Grafikdisplay und NFC !!! Auch hier ist das Wissen von Drittanbietern nützlich. Die meisten NFC-Geräte sagen Ihnen genau, was sie sind, wenn Sie höflich fragen. Dies ist eine gute Sache, da der NFC-Chip auf der Platine zu klein war, um ordnungsgemäß beschriftet zu werden. Nachdem wir es mit NFC gescannt und die Geräte-ID überprüft haben, stellen wir fest, dass es sich um NXP NT3H1101 handelt (archivierte Kopie  hier  für die Nachwelt). Von dieser sehr praktischen Seite können Sie das Datenblatt herunterladen  - und es wird sofort klar, wie die Kommunikation mit diesem Chip ablaufen soll. Eine nützliche Information! (Alle Informationen sind hier nützlich). Das einzig ärgerliche ist, dass die I2C-Adresse dieses Geräts nicht festgelegt ist, sondern auf einen beliebigen Wert eingestellt werden kann. Es wird jedoch ein Standardwert angegeben. Das Alphabet des Reverse Engineering: In 99,9% der Fälle ändern sich die Standardwerte nicht. Ich wette, die Standard-I2C-Adresse hat sich auch nicht geändert!



Ein binäres Analogon für zu finden ist  0x55



 ziemlich einfach - dieser Wert ist nicht so häufig. Anscheinend werden sie alle vor dem Aufruf einer der beiden Funktionen erstellt. Es ist sinnvoll, dass sie an I2C angeschlossen werden. Darüber hinaus wird in allen Fällen vor diesen Aufrufen Bit 4 gesetzt CLKEN



welches dann verworfen wird. Wir wissen jetzt, dass I2C durch dieses Bit aktiviert wird. Schauen wir uns an, was diese Funktionen bewirken. Einige kopieren die Daten aus dem angegebenen Parameter ganz am Anfang, andere am Ende. In der Mitte schreiben sie alle etwas Globales, setzen das globale Bit, löschen Bit 4 und setzen Bit 5 im Register 95



und warten, bis es gelöscht ist. Hmm, funktioniert wie ein Temperatursensor. Anscheinend wird Bit 2 im IEN1



 Interrupt aktiviert.



Mal sehen, wo sich der Interrupt-Handler befindet, der diese globalen Werte beeinflusst. In der Tat ist seine Interrupt-Nummer erwartungsgemäß 8. Es setzt CFGPAGE



 auf 0 und liest dann das Register 91



... Die niedrigstwertigen 3 Bits werden ignoriert und die verbleibenden Bits werden im Schaltfall verwendet, um zu entscheiden, was zu tun ist. Dieser Code stellte sich als etwas verwirrend heraus, also beschloss ich zu experimentieren. Befestigte den Logikanalysator an den Leitungen zum NFC-Chip und fand schnell heraus, wo SDA



und wo  SCL



. Es war einfach, weil es ein Datenblatt für diesen Chip gibt.



Es scheint, dass das Löschen von Bit 4 im Register  95



 nichts beeinflusst, aber das Setzen von Bit 5 bewirkt, dass die START-Bedingung auf dem Bus wahr ist. Ein Interrupt wird ausgelöst. Wenn Sie dasselbe mit dem eingebauten Handler tun und die 5 höchstwertigen Bits im Register lesen 91



, sehen wir, dass sie einen Wert haben 0x08



... Das Adressbyte wird dann mit dem R / W- 94



Bit (Lesen / Schreiben) im Register gespeichert  , und Bit 3 im Register wird gelöscht  95



. Es sollte auch beachtet werden, dass ALLE Pfade durch diesen Interrupt-Handler dazu führen, dass Bit 3 im Register gelöscht wird 95



. Ich denke, dies ist das "Bit, das unterbrochen werden muss". Ich habe es noch nicht herausgefunden, aber wir können bereits einige Register benennen. Es scheint, dass sich alle I2C-Register auf der Konfigurationsseite 0 befinden.



Ich werde anrufen,  weil es I2C ist, das es enthält und aus keinem anderen Grund gelesen wird. Ich habe noch nie gesehen, dass sich die niedrigstwertigen drei Bits geändert oder in irgendeiner Weise verwendet haben.  - Also werde ich anrufen  91



  I2CSTATE



I2CBUF



94



, da die Daten entlang des Förderers durch sie gepumpt werden und  95



 in Zukunft benannt werden  I2CCTL



, da etwas getan werden muss, damit etwas getan werden kann.



Wir graben weiter und stellen fest, dass beim Senden des Adressbytes einer von vier Statuswerten erhalten werden kann. Wenn für das von uns gesendete Adressbyte Schreibzugriff erforderlich ist, lautet der Status,  0x18



ob es bestätigt wurde (ACK), und  0x20



wenn nicht. Wenn das von uns gesendete Adressbyte Lesezugriff erfordert, lautet der Status,  0x40



ob es bestätigt wurde (ACK), und  0x48



wenn nicht. Die Handhabung von NAK (Byte unbestätigt) ist recht einfach. Wenn Bit 5 auf gesetzt ist  I2CCTL



 Die STOP-Bedingung am Bus ist wahr.



Das Senden von Daten im Schreibmodus ist einfach. Das Byte wird einfach in geschrieben  I2CBUF



. Wenn das gesendete Byte bestätigt wird (ACK), wird der Status 0x28



und wenn nicht, dann  0x30



. Um einen Neustart zu provozieren, setzen Sie Bit 4 auf  I2CCTL



 - es funktioniert. Wenn die Ausführung des Befehls RESTART auf dem Bus abgeschlossen ist, wird der Status  0x10



.



Wenn wir die Informationen lesen möchten, können wir nach dem Senden des Neustartbits und des Adressbytes im Lesemodus, sobald wir den Status sehen 0x40



, entscheiden, wie auf das nächste empfangene Byte reagiert werden soll - ACK oder NAK. Um dies zu bestätigen (ACK), setzen Sie Bit 2 auf  I2CCTL



, und um nicht zu bestätigen (NAK) - wir löschen dieses Bit. Mit der Rückgabe des Handlers wird das Byte empfangen. Wenn dies erledigt ist, wird der Status 0x50



angezeigt, wenn das Byte bestätigt wurde und 0x58



wenn es nicht bestätigt wurde. Auf die eine oder andere Weise wird das I2CBUF



 empfangene Byte in enthalten sein. 



Nachdem wir den Initialisierungscode überprüft und an unserer Kopie herumgebastelt haben, finden wir, dass Bit 7 in  I2CCTL



 steuert, ob das Peripheriegerät Interrupts auslöst. Wenn nicht, wird dieses Register auf initialisiert  0x43



... Ich gehe davon aus, dass der Block so konfiguriert ist, dass er im Master-Modus arbeitet. Da ich keinen Beispielcode für den Slave-Modus habe, habe ich diese Frage nicht weiter untersucht, bin mir aber sicher, dass der Slave-Modus unterstützt wird. Es kann gemacht werden, aber ich bin faul :).



Das Register  96



 zeichnete auch Informationen in der Initialisierungszeit auf und ändert sich dann nicht mehr. Dies korreliert gut mit einer Information, die uns noch fehlt - und zeigt an, wie die Taktrate eingestellt ist. Nachdem I2CSPEED



wir mit diesem Register experimentiert haben (das jetzt genannt wird  ), sehen wir, dass es eine komplexe Interdependenz mit der Taktfrequenz hat, aber nach mehreren Dutzend Versuchen bin ich zu folgendem gekommen:  rate = 16MHz / ((dividerB ? 10 * (1 + dividerB) : 12) << dividerA)



wobei dividerA die drei niedrigstwertigen Bits sind I2CSPEED



und dividerB ist das nächste 4. Das höchstwertige Bit wird anscheinend nicht verwendet.



Die Tatsache, dass die anfängliche GPIO-Einrichtung in der Nähe des Initialisierungspunkts des Peripheriegeräts erfolgt, scheint zu implizieren, dass Pins P1.4



 und  in diesem Fall wichtig sind P1.5



.



Alles hat funktioniert, aber es gab ein Geheimnis. Als der Interrupt für diesen Block aktiviert wurde (c  IEN1



), wurde auch Bit 2 im Register gesetzt A2



. Da es  IEN1



 sich an der Adresse befindet  A1



, habe ich den Verdacht, dass es sich um einen Interrupt handelt. Ich habe immer noch nicht genau herausgefunden, was es tut, und kein anderer Code als der ursprüngliche I2C-Setup-Code verwendet es. Ich habe es vorher benannt I2CUNKNOWN



obwohl es wahrscheinlicher ist, dass es Interrupt-bezogen ist als I2C-bezogen. Wie auch immer, mein Code kann jetzt I2C-Transaktionen als Master ausführen!



Pinwechselerkennung



Die Preisschild-Firmware wurde aktiviert, als sie von einem NFC-fähigen Gerät gescannt wurde. Der integrierte NFC-Chip verfügt über einen "Felderkennungs" -Pin, der mit dem Hauptmikrocontroller verbunden ist. Zufall? NichtÜberlegen! Es muss eine Möglichkeit geben, Änderungen am Pin zu erkennen. Es weckt sogar den Chip aus dem Ruhemodus (Energiesparen). Außerdem dauert das Zeichnen mit elektronischer Tinte einige Zeit, und während dieser Wartezeit sollte der Chip wahrscheinlich weiter schlafen. Das Display signalisiert das Ende des Zeichnens durch Ändern des Signals "BUSY". Wir haben also zwei Fälle, in denen die CPU eine Änderung an einem Pin erkennen muss, und höchstwahrscheinlich handelt es sich nicht um einen aktiven Wartezyklus. Es wäre schwierig, den zuerst beschriebenen Fall zu finden - ich wusste immer noch nicht genau, wo sich dieser Ruhezustand befindet. Der zweite Fall war im Gegenteil sehr leicht zu finden - ich meine, es war leicht, den Code zum Zeichnen auf dem Bildschirm zu finden. Auch hier ist es hilfreich, auf vorhandenem Wissen aufzubauen. Ich wusste,Welches Team ist für die "Aktualisierung des Bildschirms" auf praktisch allen vorhandenen E-Ink-Display-Chips verantwortlich? Ich habe es gerade betreten und gesehen, was passieren würde. Es gab viel Code, viele wurden berührt  SFR



... Ich begann mit den wenigen zu experimentieren, die ich sah. Vermutete Vermutungen angestellt: Alle Pins sollten in der Lage sein, die Änderungserkennung auszulösen. Dies ist nicht immer der Fall, aber normalerweise wird eine fundierte Vermutung gezogen. Ich ging davon aus, dass alle Konfigurationsregister, über die wir gesprochen haben, sequentiell sind und mit drei Ports funktionieren. Ich ging auch davon aus, dass das Ändern des Pins einen Interrupt verursachen und nicht nur das Gerät aufwecken sollte. Es ist sinnvoll, dass die Anzahl der Konfigurationsregister ziemlich vorhersehbar ist. Für jeden Pin benötigen wir ENABLE, STATUS und höchstwahrscheinlich DIRECTION. Darüber hinaus befinden sich Register, die sich auf die Erkennung von GPIO-Änderungen beziehen, wahrscheinlich in der Nähe anderer GPIO-Konfigurationsregister.



Auf dieser Grundlage habe ich einige Experimente durchgeführt, da ich zumindest einige der Pins leicht wechseln konnte (zum Beispiel TEST). Es hat auch einige Zeit gedauert, um zu sehen, wie sich meine aktuelle Karte entwickelt SFR



. Ich habe nicht in den Registern suchen vergessen BC



BD



und  BE



 auf der Konfigurationsseite 0. Mehrere Experimente haben gezeigt , dass sie das Pull - up eines jedes Stiftes steuern. Es stimmt, ich habe noch nie Konfigurationen gesehen, die es erlauben würden, "den Stift nach unten zu ziehen". Ich habe sie benannt PxPULL



.



Nach mehreren Experimenten wurde klar, dass es drei Register pro Port gibt, die Interrupts steuern, wenn sich der Pin ändert.  PxLVLSEL



( A3



A4



A4



) wählt den gewünschten Pegel (0 = hoch, 1 = niedrig).  PxINTEN



( A6



A7



A9



) Bietet Änderungsverfolgung Stift auf Hardware - Ebene.  PxCHSTA



( AA



AB



AC



) Speichert den Erfassungsstatus (Bit gesetzt = etwas geändert hat). Andere Experimente haben gezeigt, dass die Interrupt-Nummer beim Pin-Wechsel 11 ist. Funktioniert gut, und ich habe es sogar geschafft, den Chip aus dem Energiesparmodus aufzuwecken (mehr dazu weiter unten).



Zweiter DPTR



Registriert  84



 und  85



 spart auf mysteriöse Weise bei allen Swap-Transaktionen CFGPAGE



 und speichert alle 8 Bits darin. In vielen Varianten des 8051 sollte sich hier das zweite Register befinden DPTR



. Aber wenn ja, wie wechseln Sie dorthin  ? Jeder macht es anders. Ich habe beschlossen, es zu versuchen. Schrieb ein Programm in Assembler, um jedes Bit in jedem Register der Reihe nach umzukehren und zu prüfen, ob das Schreiben einer ganzen Zahl DPTR



 (spezielle Anweisung) mit dem nachfolgenden Lesen DPL



 und  DPH



 (normaler Zugriff auf  ) übereinstimmt SFR



). Es ist vorhersehbar, dass viele dieser Dinge nicht so einfach umgeschaltet werden können, ohne das Programm zum Absturz zu bringen. Aber nachdem ich geübt hatte, das eine oder andere sorgfältig zu überspringen, isolierte ich Bit 0 in  92



. Nun ja ... das macht er. Wie bei vielen 8051 habe ich dieses Register benannt  DPS



, was "Datenzeigerauswahl" bedeutet. Register 84



 und  85



 ich nannten natürlich  DPL1



 und  DPH1



.



Andere Experimente.



Einige Experimente haben gezeigt, dass die beiden niedrigstwertigen Bits in PCON



(Standby und Schlaf) im Schlafmodus des 8051 wie erwartet funktionieren (obwohl der Schlaf im Energiesparmodus ebenfalls konfiguriert werden kann). Ich habe auch festgestellt, dass das Setzen von Bit 4 deaktiviert ist  XRAM



. Das spart im Schlafmodus etwas mehr Energie! 



Register im Bereich B2



.. sind interessant  B6



. Sie scheinen abhängig von den Anweisungen an ihrem Standort zu variieren. Nachdem ich alles sorgfältig überlegt hatte, wurde mir klar B4



: B5



 Es ist immer auf dem neuesten Stand  PC



!!! Warum jemand es brauchen könnte - ich weiß es nicht. Benannte sie  PCH



 und  PCL



... Sie sind schreibgeschützt. Aber was ist mit den anderen Registern in diesem Bereich? B2



 und  B3



scheint mit bedingten Sprüngen verbunden zu sein. Auf einem Weitsprung (zum Beispiel beim Laufen ljmp



lcall



oder  ret



), scheinen sie das Ziel des Sprungs zu speichern. Bei kurzen Übergängen (wie z. B.  sjmp



B2



 scheint es die Verschiebung herauszufinden. Seltsame Dinge, aber nutzlos, also ging ich nicht weiter darauf ein. Ich habe den Rest der Register benannt  PERFMONx



.



Schlafen Sie im Energiesparmodus



Menschen sind Menschen, und nichts Menschliches ist ihnen fremd. Die Leute lieben runde Zahlen. Ich mag Genauigkeit, auch wenn ich sie nicht brauche. Dies hilft sehr beim Reverse Engineering. Wie reagieren Sie beispielsweise auf eine Konstante  0x380000



? Keiner? Vielleicht. Wie wäre es 0x36EE80



? Die Augen klammern sich schon an sie. Was zur Hölle bedeutet das? Übersetzen Sie es in ein Dezimalsystem und Sie sehen: 3.600.000.  Nun , dies ist eine Stunde, ausgedrückt in Millisekunden. Dieser Wert kann möglicherweise nur bei einem langen Schlaf im Energiesparmodus nützlich sein. Ich bin es leid zu zählen, wie viele Dinge ich "rückentwickelt" habe, indem ich mich auf Konstanten dieser Art stütze, die Aufschluss darüber geben, wo der Traum verwirklicht wird! 



Hier sind die Konstanten auf diesem Gerät, die an die für mich interessante Funktion übergeben wurden: 1 5000 2 000 5 000 10 000 3 600 000 1 800 000 0xffffffff. Es ist durchaus verständlich, dass dies ein Hinweis auf die Dauer in Millisekunden ist. Letzteres ist wahrscheinlich ein Stummel für "für immer oder fast für immer". 



Es gab fast keine Chance zu verstehen, was die meisten Register hier tun, da sie von Code fast ausschließlich im Schlafmodus verwendet werden. Einige SFR



waren im Raum und einige im Weltraum  MMIO



... Ich konnte den Code kopieren und reproduzieren. Insbesondere hat mich interessiert, dass der Sleep-Timer mit zwei Geschwindigkeiten arbeiten kann: mit einer Frequenz von 32 kHz und 1 Hz. Dies ist ein 24-Bit-Timer, mit dem der kürzestmögliche Schlaf ungefähr 30 ms dauert und der längste ungefähr 194 Tage dauern kann! Lesen Sie mehr im SDK.



Radio



Radio erfordert normalerweise eine umfangreiche Konfiguration, sodass SFR



 es auf engstem Raum zu voll ist . Die meisten mit Funkgeräten ausgestatteten 8051 werden verwendet, um dieses Problem zu lösen MMIO



. Speicherzugeordnete E / A im 8051 werden normalerweise nur dem Adressraum zugeordnet  XRAM



. Als ich diagonal durch den Code schaute, stellte ich fest, dass das Radio auf diesem Chip eingeschaltet ist  MMIO:df00 — MMIO:dfff



.



Empfangspfad



Wieder entschied ich mich, mit dem OTA-Image zu beginnen. Es ist klein genug, um die Analyse zu vereinfachen. Es wurde schnell klar, dass das OTA-Image keine Funkpakete sendet, sondern nur empfängt (Bestätigungen werden automatisch auf Hardwareebene gesendet, was für die meisten ZigBee-Chips typisch ist). Aber es ist gut! Dank dessen reicht es für uns aus, nur die Hälfte des Fahrers zu analysieren, was bedeutet, dass die Aufgabe doppelt so einfach wie möglich ist! 



Als ich anfing zu suchen, woher der OTA-Code die Daten bezieht, schien es eine Pufferwarteschlange zu geben. Was es ist: Es ist eine Warteschlange, die einzelne Bytes enthält, von denen jedes ein Zeiger auf eine Liste von Puffern ist. Der Code, der anscheinend Pakete empfing und die empfangenen Pakete verarbeitete, nahm den Puffer aus der Warteschlange, verarbeitete ihn und stellte ihn dann in eine andere Warteschlange. Ein sehr einfaches Schema. Eine Warteschlange speichert Puffer voller empfangener Daten, eine andere Warteschlange speichert leere Puffer, die zum Empfang neuer empfangener Daten bereit sind. Klar genug.



Wenn wir uns ein wenig umschauen, stellen wir schnell fest, wo auf die Warteschlangen auf andere Weise zugegriffen wird: Entfernen des Puffers aus der "leeren" Warteschlange und Einreihen der vollständigen Warteschlangen. Dies ist der Handler für Interrupt Nr. 5! Der Interrupt - Handler selbst war recht einfach, vorausgesetzt , dass das Bit gesetzt wurde TCON2.2



, es gespeichert  0xC6



 in  MMIO:df48



, aus der Warteschlange entfernt den Puffer, Bytes in sie kopiert und es in einer anderen Warteschlange gestellt. Aber woher hat er die Bytes kopiert? Woher hast du die Länge der Kopie? Beide wurden aus dem Puffer genommen, XRAM



in den er nicht schrieb! Ich habe dieses Rätsel nie lösen können.



Die Suche endete nicht dort. Interrupt 4 spielte eine Schlüsselrolle. Sein Handler erwies sich als noch einfacher. Er hat Bit 5 getestet  MMIO:dfad



 (ich werde es nennen  RADIO_IRQ4_pending



und wenn gesetzt, ruft es eine Prozedur auf, die nirgendwo anders aufgerufen wird. Diese Prozedur las , überprüfte, ob der Wert darin kleiner oder gleich 128 war, las  , überprüfte, dass sie mit einer Erhöhung um eins gleich dem vorherigen Wert werden würde. Wenn eine der oben genannten nicht erfüllt wurde, dann gespeichert es   in  , sonst Konfigurationsseite 4 wurde ausgewählt, der erste Lesewert in einer globalen Variablen gespeichert wurde, was die Länge bezeichnet. Dieser Wert minus eins blieb bestehen  , und der Zeiger auf den Puffer, aus dem anschließend die Daten kopiert wurden, wurde gespeichert in : . Dann wurde Bit 2 gesetzt  . SFR



  FA



MMIO:df98



0xC6



MMIO:df48



D5



D4



D3



TRIGGER







Auch hier hilft es, den Kontext zu kennen. 127 ist der Maximalwert, den ein gültiges 802.15.4-Paket haben kann, und diese Länge enthält eine zyklische 2-Byte-Redundanzprüfung (CRC), jedoch nicht die Länge des Bytes selbst. Daher vermute ich, dass FA



 dies die resultierende Länge ist (unter Berücksichtigung der Bytelänge und der CRC). Ich habe es genannt  RADIO_GOTLEN



. In einem solchen Fall ist es sinnvoll, dass das  MMIO:df48



 (jetzt benannte  RADIO_rxFirstByte



) das erste empfangene Byte (Längenbyte) sein könnte. Bei allen verbleibenden Registern ist klar:  D5



 Es ist die Länge des DMA für RX DMA (jetzt aufgerufen  RADIO_RXLEN



D4



: D3



 Es wird in Teilezeiger auf den Ziel-RX DMA zerlegt ( RADIO_RXPTRH



 und  RADIO_RXPTRL



 beziehungsweise).



Dann hat alles geklappt. Interrupt Nummer 4 wird ausgelöst, sobald das Funkgerät ein Paket in den internen Puffer empfängt. Bit 5 auf  RADIO_IRQ4_pending



 (dies wird jetzt genannt  RADIO_IRQ4_pending



) sagt uns, dass dies passiert ist. Wir fahren mit der Erstinspektion des Pakets fort (stellen Sie sicher, dass seine Länge innerhalb angemessener Grenzen liegt) und führen dann den DMA vom internen Puffer bis aus XRAM



, wenn alles in Ordnung ist. Wenn nicht, dann schreiben wir 0xC6



 in  MMIO:df48



. Logischerweise kann dies mit "Leeren des Empfangs-FIFO" verglichen werden, daher wird dieses Register jetzt aufgerufen  RADIO_command



. Wenn mit dem Paket alles in Ordnung war und die DMA-Operation abgeschlossen ist, wird Bit 2 gesetzt  TCON2



und Interrupt 5 wird ausgelöst. Auch hier schreiben wir "RX FIFO leeren" an  RADIO_command



. Dies ist nützlich, da wir die Daten bereits mit der DMA-Methode abgerufen haben. Dann werden die Daten kopiert und die Arbeit erledigt! 



In den meisten Funkgeräten wird der empfangene zyklische Redundanzcode auf den höheren Ebenen nicht bereitgestellt - er wird einfach überprüft und mit einem einzelnen Statusbit mit einem Ja- oder Nein-Wert zurückgegeben. Wie üblich ist davon auszugehen, dass alles "normal" funktioniert. Sie überprüfen - es ist wirklich regelmäßig. Die meisten ZigBee-Funkgeräte melden stattdessen in diesen beiden Bytes einen LQI (Radio Link Quality Indicator) und einen RSSI (Received Signal Strength Indicator) anstelle eines CRC. In diesem Modell funktioniert das Radio ähnlich. Fast. Anscheinend ist das erste Byte immer 0xD0



Die zweite scheint jedoch tatsächlich den LQI (in den niedrigstwertigen 7 Bits) und den CRC-Status (in Bit 7) zu enthalten. Tatsächlich ist es funktional der Funktionsweise des Chipcon-Radios sehr ähnlich. Der Befehl 0xC6



 bedeutet auch "leeres Empfangs-FIFO" für Chipcon-Funkgeräte (jetzt TI)! Viele andere Dinge sind nicht gleich, aber die Befehle sind GEGENÜBER und es hat mir geholfen, durch die anderen Elemente dieses Funkstapels zu navigieren!



Mehr zum Radio



Wenn Sie überlegen, wie der OTA-Code das Radio initiiert, können Sie sehen, dass VIELE Register nur einmal betroffen sind. Einige Werte sind darin geschrieben, die völlig zufällig zu sein scheinen. Höchstwahrscheinlich sind viele von ihnen Messgeräte. Jedes Register, in das einmal (oder wiederholt, aber der gleiche Wert eingegeben wird) geschrieben wird, ist ein Kalibrierungsregister. Ich werde die langweiligen Details der beteiligten Register überspringen, aber ich werde über den funktionierenden Initiationscode sprechen, der im SDK enthalten ist.



Auch hier beobachten wir, wie viele Werte in das Register geschrieben werden  RADIO_command



... Die aufgezeichneten Werte stimmen mit denen überein, die wir erwarten würden, wenn wir mit den Werten der Chipcon-Befehle arbeiten würden, obwohl wir einige Werte sehen können, die nicht in den Chipcon-Funkmodulen enthalten sind. Entweder ist dieses Radio ein seltener Chipcon-Bastard, oder beide stammen von einem gemeinsamen Vorfahren ab. In jedem Fall hilft diese Situation, einige weitere von ihnen ausgegebene Befehle zu verstehen.



Das Reproduzieren des Initiationscodes und das Schreiben von Interrupt-Handlern, wie sie im Chip eingebaut sind, ergibt eine funktionierende Binärdatei, die für den Empfang geeignet ist und Experimenten förderlich ist. Als ich einige weitere Register bemerkte, in die die Hauptfirmware schreibt, stellte ich schnell fest, dass MMIO:df88 — MMIO:df8f



 dies "meine lange MAC-Adresse" ist, die auf Hardwareebene zum Filtern eingehender Pakete verwendet wird. Ähnlich,  MMIO:df90 — MMIO:df91



 Legt die "eigene PAN-ID" für den Empfangsfilter fest. A  MMIO:df92 — MMIO:df93



 setzt "eigene Kurzadresse". Dieses Gerät akzeptiert und bestätigt (ACK) alle Pakete, die an unsere Broadcast-Adressen gesendet werden.  MMIO:dfc0



 Stellt den Funkkanal auf die Standardnummerierung nach 802.15.4 (11..26) ein.



Da das Funkgerät die Pakete bestätigt, konnte ich auch feststellen, dass die Sendestärke beim Einstellen angepasst wird  MMIO:dfc9



 . Ich denke, es geht um das Register, das die Sendeleistung einstellt. Mir ist auch aufgefallen, dass beim Einstellen eines Kanals in der werkseitigen Hauptfirmware zwei weitere Register mit Werten pro Kanal geschrieben werden. Es gibt nur ein solches Register in der OTA-Firmware. Der auf RX bezogene wird aufgerufen MMIO:dfcb



, und der auf TX bezogene wird aufgerufen MMIO:dffd



... Einfach genug zu replizieren und zu verstehen. Dann ist es Zeit, TX herauszufinden!



Senden wir ein paar Bytes!



Nachdem ich den Datenpfad entschlüsselt hatte, übertrug ich die Funktions- und Registernamen in mein zerlegtes Master-Image. Wenn wir uns ansehen, was noch nicht markiert ist, können wir sehen, wo der Pfad von TX liegt. In der Tat gibt es hier zwei weitere Pufferwarteschlangen: eine mit leeren TX-Puffern, die zur Verwendung bereit sind, und die andere mit "verbrauchten" TX-Puffern, die zum Senden bereit sind. Ich habe die Übertragungsfunktion sehr schnell gefunden.



In 802.15.4 ist es üblich, vor dem Senden den Funkkanal zu hören. Dieser Vorgang wird als CCA (Channel Idle Assessment) bezeichnet. Bevor wir etwas mit den Daten tun, die wir senden möchten, betrachten Sie eine Schleife, die liest  MMIO:df98



 und Überprüfen von Bit 0. Wenn es gesetzt ist, schlägt die Funktion fehl und der Timer wird so eingestellt, dass er es erneut versucht. Ich denke, das ist der CCA-Pfad. Wenn wir in diesem Bit 128 Mal Null sehen, betrachten wir den Kanal als frei. 



Die Übertragungsfunktion selbst erwies sich als bedrückend einfach: Sie wählen Konfigurationsseite 4, die gewünschte Länge (ohne Längenbyte oder CRC) und alles wird beschrieben CD



. Ein Zeiger auf einen Puffer in  XRAM



 geschrieben in CA



: C9



. Der Puffer beginnt mit einem Längenbyte.  RADIO_command



 mit Wert geladen  0xCB



. Es gibt keinen solchen Befehl in Chipcon-Funkgeräten, aber ich denke, es bedeutet "TX-FIFO laden". Dann wird Bit 1 gesetzt  TRIGGER



... Ich nehme an, auf diese Weise wird der DMA-Zugriff auf die interne TX FIFO-Funkwarteschlange gestartet. Dann  MMIO:dfc8



 auf  0xFF



255 Versuche gemacht werden , für TX zu Ende zu warten, das Bit 7 bei der Überprüfung   MMIO:df9b



 (jetzt genannt  RADIO_curRfState



) festgelegt ist. Dann wird es nach einer kurzen Verzögerung auf  MMIO:dfc8



 eingestellt 0x7F



. Seltsamerweise habe ich keine Ahnung, warum es aufgenommen wird MMIO:dfc8



. In meinem Code habe ich versucht, darauf zu verzichten, und alles hat gut funktioniert.



Schwänze 



Nachdem ich ein wenig experimentiert hatte, entdeckte ich einige Tricks, die die werkseitige Firmware nicht kann. Bit 6 in RADIO_IRQ4_pending



 wird gesetzt, nachdem wir das Paket "gesendet" haben und die Bestätigungsverzögerung (ACK) abläuft. Wenn wir tatsächlich eine ACK empfangen, wird auch Bit 4 gesetzt. Daher ist es einfach zu bestimmen, (1) wann wir tatsächlich ein Paket gesendet haben und (2) ob wir eine ACK empfangen haben. Cool!



Wenn Bit 4 in  RADIO_IRQ4_pending



gesetzt ist und Bit 5 in RADIO_curRfState



nicht belegt ist, bedeutet dies, dass wir gerade ein Paket empfangen. Wir müssen den RSSI manuell auswählen, für den wir MMIO:df84



 (jetzt  RADIO_currentRSSI



) lesen . Es hat einen Offset von ca. 56 dBm.



Ich habe auch das Bit 1 in bemerkt TCON2



Wird nach Abschluss von TX DMA festgelegt (jedoch nicht unbedingt der TX-Prozess selbst). Bit 0 in wird  TCON2



gesetzt, wenn die Funkinitialisierung endet.



Ungeklärte Mysterien



ADC / Batteriemessung und AES Encryption Engine 



Es ist sinnvoll, dass es eine Möglichkeit gibt, die Batteriespannung zu messen, aber ich habe keine Spur eines ähnlichen Codes gefunden. Ohne Code, der den ADC auf diese Weise verwendet, sind die Chancen gering, diese verschwindende Methode zu finden. Der AES-Block ist im Prinzip der gleiche wie der ADC. Ich weiß, dass der Chip einen AES-Beschleunigungsblock enthält (für ZigBee erforderlich). Da der eigentliche Code ihn jedoch nicht verwendet, sehe ich keinen Weg, ihn zu finden.



Verschiedenes



Dinge, die wir nicht finden können, die uns aber nicht wirklich interessieren, da wir diesen Chip nicht kaufen können: IR-LED-Controller, PWM-Einheit, DAC. Ich überlasse diese Dinge dem Leser, um sie selbst zu üben.



ZBS242 / 3 Pinbelegung, Funktionen, SFR, Downloads 



Laden Sie das ZBS24x SDK herunter  .







  • Die schattierten Zellen zeigen bitweise adressierbare Register an 
  • Die diagonal schattierten Register, die nicht in der Bank vorrätig sind CFGPAGE



  • Vertikal schattierte Register, die anscheinend überhaupt auf keiner der Seiten erscheinen. 
  • Leere Zellen sind unbekannte Register
  • Namen von RADIO-Registern beginnen mit dem Buchstaben "r" 






Lektionen für einen Anfänger Reverse Engineer



  • Lesen Sie die Materialien mindestens einige Stunden oder Tage durch, bevor Sie mit der Arbeit beginnen.
  • - . -, . 
  • . SPI , I2C , . .
  • , – ( ). 
  • : , . , , .  
  • , , . 
  • . - , - . , .
  • - . , , . 
  • , , . , , .
  • - . . 
  • - , , - . 







Cloud- Server von Macleod eignen sich hervorragend zum Hosten von Websites.



Registrieren Sie sich über den obigen Link oder indem Sie auf das Banner klicken und erhalten Sie 10% Rabatt für den ersten Monat der Anmietung eines Servers einer beliebigen Konfiguration!






All Articles