Version 2 des 1-Wire Simulators
Durch die vielen Erfahrungen mit dem 1-Wire Simulator mit Atmel AVR-Mikrocontrollern ist die Idee für eine grundlegene neue Programierung gekommen. Wichtige Funktionen sollen dabei in einer Relativ statischen Bibliothek enthalten sein. Durch die Programmierung in Assembler könne diese Routinen, in Größe und Geschwindigkeit einmal optimiert, für alle 1-Wire Geräte anwendung finden.
Motivation für die Version 2
Schon lange plante ich den Quellcode für die 1-Wire Simulation mit Atmel AVR-Mikrocontrollern grundlegend zu überarbeiten. Folgendes sollte dabei besser werden:
- Trennung von grundlegenden 1-Wire Funktionen und gerätespezifischen Funktionen
- Einfaches ändern zwischen verschiedenen AVRs
- Implementation von Power Down Modi zur Energieeinsparung und damit eine Verringerung der Wärmeabstrahlung (Wichtig bei Temperaturmessung)
- Nach Möglichkeit: Lauffähig auch mit 4 MHz
Die Optimierung der grundlegenden 1-Wire Funktionalität und der ROM-Funktionen (die ja für alle 1-Wire Geräte gleich sind) soll auch gleich bei allen bisher erstellten Gerätesimulationen gelten. Das bedeutet, dass es keine einzelne Datei mehr geben kann, sondern dass es eine Bibliothek gibt, in der alles Grundlegende gespeichert ist.
Da ich in letzter Zeit sehr viel mit dem AVR in Assembler programmiert habe, habe ich auch diese Grundbibliothek in Assembler geschrieben. Die spezifische Datei für des 1-Wire Gerät ist weiterhin in C. Dadurch lassen sich recht komplizierte Berechnungen wie z.B. der Luftdruck beim BMP085 / BMP280 recht schnell implementieren.
Dateien in der Bibliothek
Folgende Dateien sind in der Bibliothek:
Datei | Beschreibung |
---|---|
OWPin |
Interrupt für Pegeländerungen am 1-Wire Pin |
OWTimer |
Timer Interrupt für Zeitmessungen |
OWConfig.s | Allgemeine Konfiguration (Register und Speicher Belegung, Auswahl der Konfiguration für den speziellen AVR, Macros) |
OWSet_ |
Spezielle Konfiguration für ATTINY24 - ATTINY84 |
OWSet_ |
Spezielle Konfiguration für ATTINY25 - ATTINY85 |
OWCRC8.s | CRC8 Berechnung (Wird je nach Gerät ausgewählt) |
OWCRC16.s | CRC16 Berechnung (Wird je nach Gerät ausgewählt) |
OWRom |
Alle ROM-Funktionen, sowie die Funktionalität zur Änderung der OW-Pin |
Diese Dateien stehen im Unterverzeichnis "common". Für jedes 1-Wire Gerät wird ein weiteres Unterverzeichnis angelegt und dort ist mindestens jeweils eine .c Datei mit dem Hauptprogramm und eine .s Datei mit den spezifischen Assemblerroutinen für das Gerät.
Register und Variablen
Alle Registerbezeichnungen beginnen mit "r_", so dass sie nicht mit Variablen verwechselt werden können. Wenn es dazugehörige Variablen im SRAM gibt, dann stehen sie gleich dahinter.
Bezeichnung | Nr. | Hauptfunktion |
---|---|---|
r_temp | 16 | temporäre Zwischenspeicherung |
r_rwbyte / rwbyte | 17 | aktuelles Byte welches gelesen oder geschrieben wird |
r_temp2 | 18 | temporäres Zusatzregister, wenn eins nicht ausreicht |
r_bcount / bcount | 19 | Bit, welches gerade behandelt wird ist 1, Bit wird druchgeschoben. r_bcount=0 bedeutet: nächstes Byte muss behandelt werden |
r_mode / mode | 20 | Aktueller Zustand/Funktion, OW_SLEEP->Lehrlauf, Definition der ROM Commands in OWRomFunctions.s, gerätespezifische Konstanten in der .s Datei im Geräteverzeichnis. Nach dem Wert in mode werden über die Sprungtabelle (handle_stable) die entsprechenden Routinen ausgewählt. |
r_sendflag / sendflag | 21 | 0 => Slave empfängt Daten vom Master, 1=> Slave sendet Daten zum Master |
r_bytep / bytep | 22 | Pointer auf das nächste (od. aktuelle) Byte |
r_crc | 23 | Hilfsvariable für die CRC-Berechnung |
srbyte | Aktuelles Byte für SEARCH_ROM | |
alarmflag | Für das Suchen von Geräten im Alarmmodus (z.B. beim DS18B20) | |
owid | Speicher für die 1-Wire-ID | |
zl / zh | 30/31 | Speicherzugriff |
reset_ |
Zeigt Hauptprogramm, dass ein Resetimpuls empfangen wurde | |
gcontrol | Kommunikation mit dem Programmteilen die nicht in der Interruptroutine laufen |
Einige Register werden in der SEARCH_ROM-Routine anders verwendet, um zusätzliche Push und Pop Befehle einzusparen.
Die vom Timing aus betrachtet kritischste Sitation ist wenn der Slave eine "0" an den Master sendet. Der Master zieht die Leitung auf Low und der Slave muss möglist in 6 µs die Leitung auch auf Low ziehen. Der DS9490 lässt die Leitung unter Umständen noch schneller wieder auf High (5V) springen. Nach der 1-Wire Spezifikation muss der Slave die Leitung nach 15 µs auf Low sein, denn dann liest der Master aus. Das ist kein Problem für den AVR. Es ist aber mein Ziel, dass zwischen dem Master-Low und dem Slave-Low keine Pause ist (die Leitung nicht auf High springt).
Es muss also in der Interruptroutine schnell entschieden werden ob eine "0" gesendet werden muss. Eine normal Variable in einem SRAM-Bereich müsste dazu erst in ein Register geladen werden. Das Register müsste vorher gesichert werden. Ein schnellerer Weg ist es, wenn ein ungenutztes Bit in einem I/O-Register des AVR für die Entscheidnugn genutzt wird.
Aus diesem Grund wurde das DDR Bit des Reset Pins für diese Entscheidung zweckentfremdet. Die Reset Leitung wird normalerweise nur zum Programmieren genutzt und da stört diese Anwendung nicht. Um das Reset-Pin als vollwertiges I/O-Pin zu nutzen muss erst eine FUSE gesetzt werden. Dann geht das Programmieren aber nur noch über die Hochvoltprogrammierung.
Es gibt also noch eine weiter Variable (ZEROMAKER), die nicht direkt im Quelltext auftaucht.
Folgende Abbildungen verdeutlicht diesen Zusammenhang nocheinmal:
Variable im SRAM -> 9,3 µs bis Bus Low
Variable im I/O Register -> Leitung nicht zwischendurch auf High
Die beiden Verläufe sind von einem Test mit einem MOSFET (2N7000) als Pegelwandler von 5V am 1-Wirebus und 3V am ATTINY84.
Programmablauf
Herzstück der Simulation ist die Interruptroutine die bei einer fallenden Flanke am 1-Wire Bus aufgerufen wird. In der neuen Software gibt es hier keinen unterschied zwischen den verschiedenen Zuständen wie Reset, Searchrom usw. Bei einem Reset wird ersteinmal ganz normal ein Bit gelesen. Da keine weitere fallende Flanke kommt, wird irgend wann der Timer Interrupt aufgrufen und Prüft ob die Leitung noch Low ist. Der Timer-Interrupt gibt dann entsprechend den Presents Impuls aus und schaltet auf OWM_READ_COMMAND.
Die Eigentlichen Funktionen werden bei handle_byte ausgeführt. Dort Springt das Programm anhand von einer Sprungtabelle und dem Wert in mode sehr effizient an die Stelle, wo der aktuelle Zustand bearbeitet wird.
Bei dem Searchrom-Algorithmus, wo jeweils zwei Bits gesendet (vom Slave gesndet) werden und ein Bit empfangen wird, bekommt der Bitzähler bcount nur entsprechend kleinere Werte und die handle_byte Funktion wird öfterer aufgerufen.
Beim Senden erfolgen die Berechnungen für das nächste Byte zwischen der fallenden Flanke vom Master bis zu der Zeit, in dem der Slave die Leitung wieder frei gibt, wenn denn eine 0 gesendet wird. Beim Empfangen erfolgen die Berechnungen nachdem die Leitung ausgelesen wurde bis maximal zum nächsten Low-Impuls vom Master (etwas eher sollte es schon sein).
Wichtig ist, dass nach einem handle_byte beim Empfangen das sendflag kontroliert wird. Wenn das nächste Byte gesendet wird muss der ZEROMARKER entsprechend gesetzt werden.
Im bei einem Timer Interupt wird geprüft, ob die Leitung Lange genug Low ist, damit der Impuls als Reset Impuls erkannt wird. Dazu gibt es noch die zweite Zeit OWT_RESET2. Die Zeiten wie auch OWT_READ und OWT_WRITE durch Polling eingehalten. Der Timer-Interrupt wird nur bei einem "Timeout" nach einer Fallenden Flanke ausgelöst.
Übersicht über die Zustände bei handle_byte
In der Datei OWRomFunctions.s sind die grundlegenden Zustände für die 1-Wire Simulation definiert. Diese sind bei jedem Gerät gleich. Deshalb gibt es hier eine kurze Beschreibung dazu:
Zustand (mode) | Beschreibung |
---|---|
OW_ |
Warten auf einen Resetimpuls. Alle fallenden Flanken auf dem Bus müssen dazu überprüft werden |
OW_READ_ |
Nach erfolgreichen Reset wird ein Befehl zur Zugriffskontrolle gelesen. |
OW_ |
Empfangen einer ID und prüfen ob es die Eigene ist |
OW_ |
Search-Rom-Algorithmus: Senden eines Bits aus der ID und danach die Negation (Complement) des Bits |
OW_ |
Search-Rom-Algorithmus: Empfangen des Steuerbits vom Master |
OW_ |
Wenn nur ein 1-Wire Gerät am Bus ist kann die ID ausgelesen werden. |
OW_ |
Schreiben einer neuen ID in den ID-Zwischenspeicher |
OW_ |
Kontrolieren der ID aus den Zwischenspeicher |
OW_ |
Übernehmen der ID aus den Zwischenspeicher in den ID-Speicher (EEPROM) |
OW_FIRST_COMMAND | Kein Zustand, enthällt die Nummer des nächsten Zustandes für die gerätespezifischen Zustände (Befehle) |
1 Nur wenn die Möglichkeit zur ID-Veränderung eingeschaltet ist
Die Zustände ensprechen nicht unbedingt immer den 1-Wire Command-Codes die behandelt werden. Diese sind deshalb nocheinmal in der folgenden Tabelle angegeben:
Code | Beschreibung |
---|---|
0x55 | MATCH ROM: selektieren eines Gerätes anhand einer ID |
0xF0 | SEARCH ROM: Suchalgorithmus für die angeschlossenen Geräte |
0xCC | SKIP ROM: Überspringen von MATCH ROM wenn nur ein Gerät am Bus |
0x33 | READ ROM: Lesen der ID wenn nur ein Gerät am Bus |
0xEC | ALARM SEARCH: SEARCH ROM für Geräte bei denen das Alarm-Flag gesetzt ist |
0x75 | WRITE NEWID: Schreiben einer neuen ID in den Zwischenspeicher |
0xA7 | READ NEWID: Lesen der ID aus dem Zwischenspeicher |
0x79 | SET NEWID: Übernehmen der neuen ID |
Nach OW_READ_ROM_COMMAND bzw. nach OW_READ_COMMAND werden die Codes gebrüft. Dafür gibt es in der Datei OWRomFunctions.s zwei Makros. Wenn das Sprungziel im bereich von -63 und +64 Befehlen liegt kann das Makro cjmp verwendet werden. Alternativ, wenn der Linker fehler bringt, muss das aufwändigere Makro cljmp verwendt werden. Dabei wird mehr Speicher benötigt und die Ausführung dauert länger.
In der Regel wird zunächst zu einer Initialisierungsroutinge (hrc_set_[befehl]) gesprungen, die den neuen Zustand und alle anderen Parameter einstellt. In der Sprungtabelle (handle_stable) steht der Sprungbefehl zu der Routine, die den aktuellen Zustand behandlet (rjmp h_[Zustand]).
Kommentare
danke fürs Checken. Schön, dass es auch andere Assemblerkundige gibt ;-). Ich habe die Änderungen eingepflegt. Beim nächsten Checkout werde sie online sein.
Mit „Zero-Polling“ wird es wahrscheinlich auch beim DS2490 oder DS2480 als Master keine High-Spitzen mehr zischen Master-Low und Slave-Low geben. Bei 4 MHz ist ja auch ein betrieb mit 1,8V möglich. Das spart nochmal Energie. Aber auch so ist der AttinyX4A einer der sparsamsten Mikrocontroller den ich kenne.
Hallo Tobias,
vielen Dank für die schnelle Anpassung, im Moment scheint es mit 4Mhz stabil zu laufen und ist unglaublich energiesparend (ca. 60% von der 8MHz Variante)
Out-of-the-Box lief es leider nicht, ich glaube du hast bei den Timings einen Tippfehler gemacht: OWT_present habe ich auf 60 geändert, dann läuft es. Das Oszi sagt dann 90us present-pulse.
OWT_reset2 kommt mir zwar auch etwas kurz vor, aber da es läuft habe ich es lieber nicht verändert.
Außerdem ist mir aufgefallen, dass in der h_read_memory am Ende das temp2 Register (Hi-Addresse) geschrieben werden muss statt temp: sts pack+1,r_temp2
Das ist keine Kritik, bitte nicht falsch verstehen, der Code ist toll geschrieben, beeindruckend effektiv. Ich möchte nur meine Erfahrung zurückgeben, evtl. hilft es einem anderen Nutzer.
Grüße
ich habe lange nichts mehr mit 4 Mhz gemacht. So habe ich gleich mal den Quelltext angepasst. Die Änderungen stehen jetzt unter "Sourcecode" im Git. Wenn du in der entsprechenden OW****.S Datei
#define __4MHZ__
einfügst, dann läuft die Simulation mit 4 Mhz.
(Es geht natürlich auch als Compiler-Option unter Projekteigenschaften -> Toolchain -> AVR/GNU C-Compiler -> Symbols)
Ich hoffe es hilft weiter.
Ich denke aber, sehr viele Devices lassen sich nicht parallel an einem Bus betreiben.
danke, dass du uns deinen Code zur Verfügung stellst.
Er hat bei mir sofort funktioniert, als ich dann das zusätzliche Fuse Bit nachträglich gesetzt hatte.
Auch wenn die neue Version durch den Assembler Code schwieriger zu verstehen ist, freue ich mich, dass du ihn weiterentwickelt hast.
Ich würde gerne den Controller Bus-powered verwenden. Ist es schon möglich mit 4Mhz zu arbeiten? Welche Änderungen muss ich im Code vornehmen, ich denke ich muss die Timingzeiten halbieren?
Grüße