Schreiben eines Decoders für Sigrok

Bild



Wenn Sie mit digitaler Technologie arbeiten, ist früher oder später ein logischer Analysator erforderlich. Einer der für Funkamateure verfügbaren ist der DSLogic Logic Analyzer von DreamSourceLab. Er wurde auf der Website wiederholt erwähnt, mindestens: eins , zwei und drei .



Seine Funktion ist Open Source und auch die Tatsache, dass die Open-Source- Sigrok- Bibliothek für die Decodierung von Signalen verantwortlich ist . Zusammen mit einer beeindruckenden Liste vorhandener Signaldecoder bietet diese Bibliothek eine API zum Schreiben eigener Signale. Das werden wir tun.



Demo stehen



, . TTP229-BSF. 8- 16- . Arduino, DSLogic.



Bild





, , . , . , , .



Jeder Decoder in sigrok ist ein separates Paket, das in Python 3 geschrieben wurde und ein eigenes Verzeichnis unter dem Decoderordner hat. Wie jedes Python-Paket enthält der Decoder __init__.py. Gemäß der Namenskonvention in sigrok ist pd.py eine Datei, die die eigentliche Implementierung enthält.



Bild



Der Code in __init__.py ist Standard und enthält eine Dokumentzeichenfolge, die das Protokoll und den Decoderimport beschreibt ( d28dee9 ):



'''
Protocol description.
'''

from .pd import Decoder


Die Datei pd.py enthält die Decoder-Implementierung ( d28dee9 ):



class Decoder(srd.Decoder):
    api_version = 3
    id = 'empty'
    name = 'empty'
    longname = 'empty decoder'
    desc = 'Empty decoder that can be loaded by sigrok.'
    license = 'mit'
    inputs = ['logic']
    outputs = ['empty']
    tags = ['Embedded/industrial']
    channels = (
        {'id': 'scl', 'name': 'SCL', 'desc': 'Clock'},
        {'id': 'sdo', 'name': 'SDO', 'desc': 'Data'},
    )

    def start(self):
        pass

    def reset(self):
        pass

    def decode(self):
        pass


Dies ist eine minimale Implementierung, die die Bibliothek laden kann, aber nichts dekodiert. Werfen wir einen Blick auf die erforderlichen Eigenschaften:



  • api_version – Protocol decoder API, . libsigrokdecode 3- Protocol decoder API. .
  • id – , . , .
  • name, longname, desc – , , . .
  • inputs, outputs – . 'logic' . . , , SPI. SPI . , SPI . 'spi'.
  • license, tag – . DSView 1.1.1 + libsigrokdecode tags - .
  • Kanäle - Eine Liste der vom Decoder verwendeten Signalleitungen. Diese Eigenschaft ist für Decoder erforderlich, für die das Eingabedatenformat als Logik definiert ist.


und erforderliche Methoden:



  • start () - Methode, die vor dem Start der Dekodierung aufgerufen wird. Bei dieser Methode sollten Einstellungen für die aktuelle Dekodierungssitzung vorgenommen werden.
  • reset () - Die Methode, die aufgerufen wird, wenn die Dekodierung beendet wird. Sollte den Decoder in seinen Ausgangszustand zurückversetzen.
  • decode () ist eine Methode zum Decodieren eines Signals.


Nachdem Sie sich mit der minimalen Implementierung des Decoders befasst haben, können Sie mit der Decodierung des realen Signals beginnen.



Voll ausgestatteter Decoder



Schauen wir uns zunächst das Zeitdiagramm des Datensignals an. Der TTP229-BSF verfügt über mehrere Betriebsmodi, und ich zeige nur das Zeitdiagramm für den Modus, der unten verwendet wird. Nähere Informationen zu allen Betriebsarten der Mikroschaltung finden Sie in der Dokumentation dazu.



Bild



In erster Linie muss der Satz von Pflichtleitungen beschrieben werden, mit denen der Decoder arbeiten wird. In diesem Fall gibt es zwei davon, eine Taktleitung (SCL) und eine Datenleitung (SDO).



class Decoder(srd.Decoder):
    ...
    inputs = ['logic']
    channels = (
        {'id': 'scl', 'name': 'SCL', 'desc': 'Clock'},
        {'id': 'sdo', 'name': 'SDO', 'desc': 'Data'},
    )


Wenn der Mikrokreis einen Tastendruck erkennt, setzt er das Data Valid (DV) -Signal auf der SDO-Leitung, gemäß dem der Empfänger mit dem Lesen von Daten beginnen soll. Lassen Sie uns dieses Signal finden und dekodieren.



sigrok , . . , , . Protocol decoder API . . wait(). . , self.samplenum .



, , – , :



  • 'l' - niedriger Pegel, logische 0;
  • 'h' - hohes Niveau, logische 1;
  • 'r' - Signalanstieg, Übergang vom niedrigen zum hohen Zustand;
  • 'f' - Signalabfall, Übergang vom hohen zum niedrigen Zustand;
  • 'e' - willkürliche Signaländerung, Anstieg oder Abfall;
  • 's' ist stabiler Zustand, 0 oder 1.


Um den Anfang des DV-Signals zu finden, ist daher eine Bedingung erforderlich, die beschreibt, dass die SCL-Leitung hoch ist und dass das Signal auf der Datenleitung (SDO) abfällt. Rufen wir die Funktion wait () mit der vorbereiteten Bedingung auf und speichern die Probennummer:



        self.wait({0: 'h', 1: 'f'})
        self.dv_block_ss = self.samplenum


Um das Ende des DV-Signals zu finden, muss eine Bedingung festgelegt werden, wenn die SCL-Leitung hoch bleibt und die Datenleitung hoch geht:

        self.wait({0: 'h', 1: 'r'})


Nach Abschluss des letzten Aufrufs der Funktion wait () sind die Abtastnummern des Beginns und des Endes des DV-Signals bekannt. Es ist Zeit, eine Anmerkung dafür zu erstellen. Fügen Sie dazu eine Beschreibung der Anmerkungen und ihrer Gruppierung (annotation_rows) in den Decoder ein:



class Decoder(srd.Decoder):
    ...
    annotations = (
        ('dv', 'Data valid'),
    )
    annotation_rows = (
        ('fields', 'Fields', (0,)),
    )


Dabei ist 0 der Index der Annotation im self.annotations-Tupel in dieser Gruppe. Sie müssen auch die Ausgabe von Anmerkungen registrieren:



    def start(self):
        self.out_ann = self.register(srd.OUTPUT_ANN)


Jetzt ist alles bereit, um die Anmerkung zum DV-Signal zu platzieren. Dies erfolgt durch Aufrufen der Funktion put () ( f613b83 ):



        self.put(self.dv_block_ss, self.samplenum,
                 self.out_ann, [0, ['Data valid', 'DV']])


Funktionsparameter: Annotation Start Sample Number (self.dv_block_ss), Annotation End Sample Number (self.samplenum), Annotation Output Identifier (self.out_ann) und Daten für Annotation. Die Daten werden als Liste des Anmerkungsindex (0) und als verschachtelte Liste der Zeichenfolgen vom längsten zum kürzesten zur Anzeige in der Beschreibung dargestellt. Wenn mehr als eine Zeile angegeben ist, kann die Schnittstelle die angezeigte Zeile unabhängig auswählen, z. B. abhängig von der verwendeten Skala:







In ähnlicher Weise fügen wir eine Anmerkung für die Verzögerung Tw zwischen dem Ende des DV-Signals und dem Beginn des Lesens von Daten vom Mikrocontroller hinzu. Als nächstes können Sie die Daten beim Drücken der Taste dekodieren.



TTP229-BSF kann je nach ausgewähltem Modus mit 8 oder 16 Touch-Tasten arbeiten. In diesem Fall enthalten die übertragenen Daten keine Informationen über den Betriebsmodus der Mikroschaltung. Daher lohnt es sich, für den Decoder eine Option hinzuzufügen, die den Modus angibt, in dem die Mikroschaltung arbeitet.



class Decoder(srd.Decoder):
    ...
    options = (
        {'id': 'key_num', 'desc': 'Key number', 'default': 8,
         'values': (8, 16)},
    )
    def start(self):
        ...
        self.key_num = self.options['key_num']


Diese Option ist verfügbar, um einen Wert in der Benutzeroberfläche festzulegen, wenn ein Decoder ausgewählt wird.



Wie Sie dem Zeitdiagramm entnehmen können, werden die Daten auf der SDO-Leitung angezeigt, wenn die SCL auf den aktiven (niedrigen) Pegel wechselt, und werden gespeichert, wenn das Signal auf den passiven Pegel zurückkehrt. In diesem Moment können sowohl der Mikrocontroller als auch der Decoder den Datensatz auf der SDL-Leitung fixieren. Der Übergang von SCL zurück zur aktiven Ebene kann als Beginn des nächsten Datenversands betrachtet werden. In diesem Fall sieht die Decodierungsfunktion folgendermaßen aus ( ca9a370 ):



    def decode(self):
        self.wait({0: 'h', 1: 'f'})
        self.dv_block_ss = self.samplenum

        self.wait({0: 'h', 1: 'r'})
        self.put(self.dv_block_ss, self.samplenum,
                 self.out_ann, [0, ['Data valid', 'DV']])
        self.tw_block_ss = self.samplenum

        self.wait([{0: 'f', 1: 'h'}, {0: 'f', 1: 'f'}])
        self.put(self.tw_block_ss, self.samplenum,
                 self.out_ann, [1, ['Tw', 'Tw']])
        self.bt_block_ss = self.samplenum

        for i in range(self.key_num):
            (scl, sdo) = self.wait({0: 'r'})
            sdo = 0 if sdo else 1

            self.wait({0: 'f'})
            self.put(self.bt_block_ss, self.samplenum,
                     self.out_ann, [2, ['Bit: %d' % sdo, '%d' % sdo]])
            self.bt_block_ss = self.samplenum


Dieser Ansatz des Platzierens von Annotationen hat jedoch einen Nachteil: Die Annotation für das letzte Bit wird bis zu den nächsten vom Mikrocontroller gelesenen Daten fortgesetzt.







. . , SCL . , SCL 2 ., . 'skip', , , . , . metadata(). Hz.



    def metadata(self, key, value):
        if key == srd.SRD_CONF_SAMPLERATE:
            self.timeout_samples_num = int(2 * (value / 1000.0))


Dann wird die Bedingung in der Decodierungsfunktion unter Verwendung von Überspringen in der folgenden Form sowie einer zusätzlichen Überprüfung geschrieben, dass die Mikroschaltung beim Lesen der Daten über die gedrückte Taste nicht in ihren Ausgangszustand zurückgekehrt ist ( 6a0422d ).



    def decode(self):
        ...
        for i in range(self.key_num):
            ...
            self.wait([{0: 'f'}, {'skip': self.timeout_samples_num}])
            self.put(self.bt_block_ss, self.samplenum,
                     self.out_ann, [2, ['Bit: %d' % sdo, '%d' % sdo]])
            if (self.matched & 0b10) and i != (self.key_num - 1):
                break


Der Decoder kann nun eine vollständige Datenübertragung durchführen. Und es ist praktisch, wenn zusätzlich zu den Informationen über einzelne Bits eine Anmerkung hinzugefügt wird, welche Taste gedrückt wurde. Fügen Sie dazu eine Beschreibung einer anderen Anmerkung hinzu. Da die Anmerkung zum Drücken einer Schaltfläche für das gesamte Senden von Daten gilt und sich mit zuvor hinzugefügten Anmerkungen überschneidet, sollte sie in einer separaten Gruppe platziert werden. Lassen Sie uns für sie eine neue Gruppe von Anmerkungen 'Schlüsselbotschaft' erstellen. ( 91c64e6 ).



class Decoder(srd.Decoder):
    ...
    annotations = (
        ('dv', 'Data valid'),
        ('tw', 'Tw'),
        ('bit', 'Bit'),
        ('key', 'Key press status'),
    )
    annotation_rows = (
        ('fields', 'Fields', (0, 1, 2)),
        ('keymsg', 'Key message', (3,)),
    )
    def decode(self):
        ...
        keys_pressed = list()

        for i in range(self.key_num):
            ...
        else:
            key_msg = \
                'Key: %s' % (','.join(keys_pressed)) if keys_pressed else 'Key unpressed'
            key_msg_short = \
                'K: %s' % (','.join(keys_pressed)) if keys_pressed else 'KU'

            self.put(self.dv_block_ss, self.samplenum,
                     self.out_ann, [3, [key_msg, key_msg_short]])






Bis zu diesem Punkt funktionierte der gesamte Code nur mit der ersten Prämisse. Haben Sie 19% neben dem Decodernamen bemerkt? Dies ist der Prozentsatz der Proben, die vor dem Beenden der Funktion decode () verarbeitet wurden. Um alle Samples zu verarbeiten, muss noch eine Endlosschleife um den Code zum Decodieren eines separaten Daten- Sends ( 48f95fb ) hinzugefügt werden .



    def decode(self):
        ...
        while True:
            self.wait({Pin.SCL: self.passive_signal, Pin.SDO: self.front_edge})
            self.dv_block_ss = self.samplenum
            ...


Weil die Dekodierung automatisch beendet wird, wenn die Funktion wait () bei der Suche nach dem nächsten Sample alle durchlaufen. Infolge dieser Änderung werden alle Proben und alle Datenübertragungen wie auf dem KDPV dargestellt verarbeitet .



Der letzte Schliff bleibt, um die Möglichkeit hinzuzufügen, den Pegel des aktiven Signals auszuwählen, und ein vollwertiger Decoder für TTP229-BSF ist bereit. Der endgültige Quellcode ist auch auf GitHub verfügbar .




All Articles