Bytes to int mit Bitwise Operators

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend,

ich habe gerade folgenden Code im Netz gefunden:
https://raw.githubusercontent.com/adamj ... mpu6050.py

da gibt es eine Funktion:

Code: Alles auswählen

    def bytes_toint(self, firstbyte, secondbyte):
        if not firstbyte & 0x80:
            return firstbyte << 8 | secondbyte
        return - (((firstbyte ^ 255) << 8) | (secondbyte ^ 255) + 1)
Ich begreife nicht so ganz was da gemacht wird, bzw. wieso das so gemacht wird. Ich beschreibe mal was ich denke was bis zum "ersten" return passiert.
Mit '&' wird von 'firstbyte' und '0x80' jedes Bit verglichen. Wenn die gleich sind, dann ist das Ergebnis an der Stelle eine eins, ansonsten eine 0. Dann wird da abgefragt ob 'firstbyte' gleich 0x80 ist. Wenn 'firstbyte' nicht 0x80 ist, dann kommt das eingerückte 'return'.
Wenn ich mir 'firstbyte' als Binärzahl vorstelle dann werden jetzt von rechts 8 Nullen aufgefüllt und links fallen 8 Bits weg. Dass ist das Gleiche wie wenn ich 'firstbyte' als Dezimalzahl mit 256 multipliziere. Nach der Rechnung wird das Ergebnis mit 'secondbyte' verglichen, wieder jedes einzelne Bit, nur dieses mal wird eine 1 geschrieben wenn eins von beiden Bits eine 1 ist, ansonsten eine 0.
Das ist mein Erkenntnisstand nach dem ich den Code soweit gelesen habe, aber mir fehlt das wieso. Und zwar in allen Bereichen. Wenn ich die Umrechnung verstehe, dann verstehe ich vielleicht auch, wieso die 'if'-Abfrage notwendig ist, bzw. wieso es zwei verschiedene Umrechnungen gibt.

Wir hatten schon mal so ein ähnliches Thema mit den Bitwise Operatoren bei der RGB656-Umrechnung, aber irgendwie steige ich da nicht durch.

Der Code ist dazu da um ein MPU5060 auszulesen und ich habe ihn aus diesem Tutorial:
https://microcontrollerslab.com/micropy ... 2-esp8266/

Könnt ihr mir bitte erklären, was das genau geschieht oder mir Hinweise geben, wie ich selbst drauf komme?

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

Eigentlich hätte ich in Sachen Hinweise hier ja print() geschrieben, aber da ich hier in den letzten Tagen zum ersten mal von snoop gelesen hatte, werfe ich jetzt eben mal snoop in den Raum.

https://pypi.org/project/snoop/#basic-snoop-usage

Das Beispiel hier drauf angepasst:

Code: Alles auswählen

import snoop

@snoop
def bytes_toint(self, firstbyte, secondbyte):
        if not firstbyte & 0x80:
            return firstbyte << 8 | secondbyte
        return - (((firstbyte ^ 255) << 8) | (secondbyte ^ 255) + 1)

bytes_toint(127,127)
bytes_toint(127,128)

bytes_toint(128,127)
bytes_toint(128,128)
Ich habe snoop hier nicht installiert, kann daher nichts über die Debugausgaben sagen. Bis eben hatte ich da noch andere Aufrufe von bytes_toint stehen, aber bei den Werten, die jetzt da stehen, passiert hoffnungsweise genügend interessantes.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Antwort.

Erst mal habe ich raus gefunden, das meine Annahme mit dem &-Operator falsch war. Also der Bit-Vergleich wie beschrieben müsste schon stimmen, aber der Rest was ich darüber geschrieben habe passt so nicht ganz. Der liefert nur False, wenn kein Bit mit dem anderen übereinstimmt. Aber warum wird das abgefragt? Welche Eigenschaften haben die zwei Zahlen, dass die gesondert behandelt werden müssen?

'snoop' habe ich hier neulich auch entdeckt.
Ich habe mir auch schon einiges ausgeben lassen, aber es wurde mir nicht klar, wieso man da 8 Bits verschieben muss und wieso der Oder-Vergleich notwendig ist.
Vielleicht muss ich auch noch mal das Datenblatt genau studieren, was denn die Bytes aussagen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13068
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Also erst einmal vergleichen ``&`` und ``|`` nichts, das sind bitweise Verknüpfungen, das heisst die Bits der beiden Operanden werden „und“- oder „oder“-verknüpft. Bei ``&`` ist das Ergebnisbit 1 an einer Stelle 1 wenn beide Operanden an der Stelle 1 sind. Und bei ``|`` reicht es wenn mindestens einer der beiden Operanden eine 1 an der jeweiligen Stelle hat.

0x80 in binär ist 0b10000000, also das oberste Bit bei einem Byte. Und eine „und“-Veknpüfung mit einem anderen Bytewert ist 0 falls das oberste Bit in diesem Byte 0 ist und 0x80 falls das oberste Bit 1 ist, denn nur dann sind ja bei beiden Operanden die Bits dort 1. Und weil alle anderen Bits in 0x80 den Wert 0 haben, sind die auch im Ergebnis immer 0. Da wird also nicht mit 0x80 verglichen, sondern geprüft ob das oberste Bit gesetzt ist oder nicht. Falls es *nicht* gesetzt ist wird der erste Bytewert um 8 Bits nach rechts verschoben, wodurch die untersten 8 Bit 0 werden. Links fallen allerdings keine 8 bits weg. Das ist ein Python-Integer, die können beliebig gross werden. Und wenn da 8 Bits wegfallen würden, dann wären das ja die 8 die man gerade nach links geschoben hätte und der Teilausdruck wäre immer 0, was ein bisschen sinnfrei wäre.

Mit dem ``|`` werden dann die unteren 8 Bit auf den Wert gesetzt den `secondbyte` hat. Denn durch das verschieben sind die beim ersten Operanden alle garantiert 0 und somit können nur noch die gesetzten Bits in `secondbyte` etwas zum Ergebnis der Verknüpfung beitragen.

Falls das oberste Bit gesetzt ist, interpretiert der Code bei zweiten ``return`` die beiden Bytes als negative 16 Bit-Zahl im Zweierkomplement. Und rechnet das entsprechend um.

Ich hätte es wahrscheinlich eher so ausgedrückt:

Code: Alles auswählen

def bytes_to_int(high_byte, low_byte):
    result = high_byte << 8 | low_byte
    return -((result ^ 0xFFFF) + 1) if result & 0x8000 else result
Alternativ:

Code: Alles auswählen

def bytes_to_int(high_byte, low_byte):
    result = high_byte << 8 | low_byte
    return ~result + 0xffff if result & 0x8000 else result
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist grober Unfug. Da wird voellig unnoetig das Zweierkomplement selbst ausgerechnet. Dafuer benutzt man einfach http://docs.micropython.org/en/v1.8/pyb ... uct.unpack, mit '<h' also Format.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

vielen Dank für die Erklärungen.
Im Datenblatt auf Seite 29 steht, dass die Werte die ich da auslesen will 16 Bit-Zweierkomplement-Werte sind.
Zweierkomplement bedeutet dass das Bit das ganz links ist, das Vorzeichen angibt, weil ein '-' - Zeichen ja nur für uns Menschen lesbar/verwertbar ist.
Damit ich meinen 16-Bit-Wert bekomme, muss ich die zwei Bytes miteinander verknüpfen. Wenn das Bit ganz links eine 1 ist, dann bekomme ich eine negative Zahl und wenn es eine 0 ist, dann ist die Zahl positiv. Das heißt auch, das ich 15 Bits habe um eine Dezimalzahl darzustellen.
Ich hoffe mal dass das soweit passt?

Ich muss dass dann noch mal mit Zahlen durchspielen und dann auch die Codebeispiele von __blackjack__ testen und wenn ich verstanden habe was da gemacht wird, dann ersetze ich das gegen die fertige Funktion.


Grüße
Dennis

Edit: Ich meine mittlerweile "Guten Mittag" ich habe mit dem Beitrag heute früh gestartet, dann kam aber noch was dazwischen :)
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13068
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Das stimmt nicht so ganz. Du hast nicht nur 15 Bit für die Dezimalzahl. Zweierkomplement bedeutet nicht, dass das oberste Bit das Vorzeichen darstellt und die restlichen die Zahl. Dann hätte man ja zweimal die 0, einmal +0 und einmal -0. Die 0 gibt's im Zweierkomplement aber nur einmal.

Edit: 0x0000 ist 0 und 0x8000, also gesetztes oberstes Bit und sonst 0en, ist -32768.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

@Dennis89: hast du meinen Beitrag gesehen? Man kann einfach ustruct verwenden, und gut ist.

Code: Alles auswählen

12:09 $ python3
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import struct
>>> z = -12345
>>> struct.pack("<h", z)
b'\xc7\xcf'
>>> struct.unpack("<h", b'\xc7\xcf')
(-12345,)
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und noch ein Nachtrag zum 2er-Komplement: wenn man im obersten Bit eine 1 vorfindet, die Zahl also negativ ist, dann erhaelt man die entsprechende positive Zahl durch

- invertierung der Bits
- Addition von 1

Also die -1, dargestellt in 2er-Komplement als $ffff, wird dann

- $0000
- $0001

Und genau diese Formel benutzt er da auch, und negiert dann wieder das Ergebnis.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Der Code ist ziemlich umständlich. Die Rechnungen macht man nicht selbst, sondern nutzt das fertige struct-Modul.

Code: Alles auswählen

import machine
import struct

class Accelerator():
    def __init__(self, i2c, addr=0x68):
        self.i2c = i2c
        self.addr = addr
        self.buffer = bytearray(14)
        self.i2c.start()
        self.i2c.writeto(self.addr, b"\x6b\0")
        self.i2c.stop()

    def fill_buffer(self):
        self.i2c.start()
        self.iic.readfrom_mem_into(self.addr, 0x3B, self.buffer)
        self.i2c.stop()

    def get_values(self):
        self.fill_buffer()
        return dict(zip(
            ["AcX", "AcY", "AcZ", "Tmp", "GyX", "GyY", "GyZ"],
            struct.unpack('<7h', self.buffer)))
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für eure weiteren Antworten.

@__blackjack__ und @__deets__ Die Rechnung habe ich jetzt mit euren Beispielen und Rechenvorgänge verstanden. Parallel dazu habe ich auch noch über das Zweierkomplement gelesen und habe gesehen das es noch eine Schreibweise gibt: Least Significant Bit. Da nimmt man das rechte Bit als Vorzeichen. Woher weis ich jetzt wenn ich zum Beispiel 0b10000111 sehe was das ist. Das könnte ja 135 sein oder -121 oder noch was anderes wenn man die Least Significant Bit - Schreibweise verwendet.
Wenn ich jetzt irgendwoher ein paar Zahlen bekomme, dann muss irgendwie mitgeteilt werden, dass ich die Zahl umrechnen muss oder nicht? Wenn ich die Zahl 135 brauche, dann ist das erste Bit eine 1, daran kann ich ja nichts ändern? Kann man verstehen welche Gedanken in meinem Kopf schwirren?

Die Beispielzahl habe ich so umgerechnet:

Code: Alles auswählen

0b10000111 -> invertieren:
0b01111000 + 0b00000001
0b01111001
=  -121
@Dennis89: hast du meinen Beitrag gesehen? Man kann einfach ustruct verwenden, und gut ist.
Ja habe ich gesehen und mit meinem Satz:
...wenn ich verstanden habe was da gemacht wird, dann ersetze ich das gegen die fertige Funktion.[/quote

habe ich die fertigen Funktionen aus deinem Link gemeint. Da das was wir gerade durchkauen ja in der fertigen Funktion auch gemacht wird, würde ich es gerne verstehen, damit ich auch weis warum ich die Funktion anwenden muss.

@Sirius Danke für das Umschreiben. Das war war mein langfristiges Ziel. Also ich wollte verstehen was da gemacht wird und schauen ob es etwas gibt, das ich mit meinem Wissen verbessern könnte. Wenn ich soweit bin kann ich ihn jetzt mit deiner Musterlösung vergleichen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich habe noch nicht einmal in meinem Leben eine LSB Implementierung des 2er-Komplement gesehen. Damit musst du dich nicht beschaeftigen. Was man wissen muss ist, welche Byte-order deine Daten haben. Da gibt es little endian und big endian. Das beschreibt, in welcher Reihenfolge mehr-byte Zahlen abgelegt werden. Kommt das LSB zuerst (little endian), oder das MSB (big endian). Ja, LSB und MSB ist aergerlicherweise auch fuer Bytes gleich benannt wie fuer Bits.
Benutzeravatar
__blackjack__
User
Beiträge: 13068
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Was die Bits bedeuten musst Du wissen. Das ist ja das schöne an Bytes: Man kann damit alles mögliche darstellen. Zahlen, Zeichen, Farben, Töne, …

Was die Bits in einem Byte bedeuten und ob das Byte überhaupt für sich alleine steht oder oder noch Bits aus anderen Bytes braucht um einen sinnvollen Wert zu ergeben, muss irgendwo dokumentiert sein. Den Bytes/Bits selbst sieht man das nicht an.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das entnimmt man in diesem Fall der Dokumentation der IMU https://invensense.tdk.com/wp-content/u ... r-Map1.pdf auf Seite 31, da stehen die Register. Das ist dann MSB first, also Big Endian. Sieht man dem Code ja aber auch an.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Okay, vielen Dank.

Vorerst habe ich keine Fragen mehr. Ich beschäftige mich jetzt mal etwas mit dem Datenblatt und schau ob ich den vollständigen Code als Text ausfindig machen kann. Ich bin schon lang daran interessiert, wie man anhand eines Datenblattes einen Code schreibt. Vielleicht ist der recht übersichtliche Code hier ein guter Einstieg.

Viele Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Ohje ich verstehe das nicht wirklich.
Der originale Code startet so

Code: Alles auswählen

    def __init__(self, i2c, address=0x68):
        self.i2c = i2c
        self.address = address
        self.i2c.start()
        self.i2c.writeto(self.address, bytearray([107, 0]))
Setze ich hier im Register 107, CKLSEL auf 0? Seite 40. Oder was mache ich da? Ich habe halt in dem Zusammenhang nur die Zahlen 107 und 0 gefunden.

Dann hier

Code: Alles auswählen

    def get_raw_values(self):
        self.i2c.start()
        values = self.i2c.readfrom_mem(self.address, 0x3B, 14)
        self.i2c.stop()
        return values
Wird das Register 59 ausgelesen, aber das gibt nur X_OUT (Seite 29) raus, was ist denn mit den anderen Achsen, die haben andere Register und werden im Code nicht angesprochen. Das ist für mich alles irgendwie noch ganz undurchsichtig.

Vielleicht kommt auch über Nacht die Erleuchtung :D

Danke im voraus für euer Gedult und Mühe.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Das Register 107 besteht ja aus vielen Bits. Entscheidend ist hier, dass nach dem Start das Register den Wert 0x40 hat, also das SLEEP-Bit gesetzt ist, und das wird auf 0 gesetzt.

Beim Lesen von Speichern, wird automatisch zur nächsten Speicherstelle weitergegangen, hier also Register 59 bis 72.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Antwort.

Okay, wenn ich eine 0 sende ist dass gleich mit 0b0000000. Wenn ich jetzt zum Beispiel den CYCLE (nur als zufälliges Beispiel) aktivieren wollen würde, dann könnte ich 0b00100000 oder 0x20 senden.

Das mit lesen verstehe ich jetzt auch, es werden ja 14 Bytes gelesen und nicht 14 Bits.

Während ich versucht habe das zu verstehen, ist mir aufgefallen, dass du in deinem Code einen Buffer eingebaut hast, den der originale Code nicht hat. Welchen Vorteil habe ich damit?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dr buffer vermindert die Menge an allocations. Verbraucht also etwas weniger CPU und macht das System deterministischer.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Super, vielen Dank.

War mal wieder sehr interessant 👍🏼

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten