Escribir un decodificador para sigrok

imagen



Si trabaja con tecnología digital, tarde o temprano se necesita un analizador lógico. Uno de los jamones disponibles es el analizador lógico DSLogic de DreamSourceLab. Fue mencionado más de una vez en el sitio, al menos: uno , dos y tres .



Su característica es de código abierto , y también el hecho de que la biblioteca sigrok de código abierto es responsable de decodificar las señales . Junto con una impresionante lista de decodificadores de señal existentes, esta biblioteca proporciona API para escribir la suya. Esto es lo que haremos.



Soporte de demostración



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



imagen





, , . , . , , .



Cada decodificador en sigrok es un paquete separado escrito en Python 3 y tiene su propio directorio debajo de la carpeta de decodificadores. Al igual que cualquier paquete de Python, el decodificador contiene __init__.py y, de acuerdo con las convenciones de nomenclatura adoptadas por sigrok, un archivo pd.py que contiene la implementación en sí.



imagen



El código en __init__.py es estándar e incluye una cadena de documentos que describe el protocolo y una importación de decodificador ( d28dee9 ):



'''
Protocol description.
'''

from .pd import Decoder


El archivo pd.py contiene la implementación del decodificador ( 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


Esta es una implementación mínima que la biblioteca puede cargar, pero no decodifica nada. Echemos un vistazo a las propiedades requeridas:



  • 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 - .
  • canales : lista de líneas de señal utilizadas por el decodificador. Esta propiedad es necesaria para los decodificadores cuyo formato de datos de entrada es lógico.


y métodos requeridos:



  • start () : método llamado antes de que comience la decodificación. En este método, se deben realizar los ajustes para la sesión de decodificación actual.
  • reset () : el método llamado cuando se detiene la decodificación. Debe devolver el decodificador a su estado inicial.
  • decode () es un método llamado para decodificar una señal.


Habiendo tratado con la implementación mínima del decodificador, puede comenzar a decodificar la señal real.



Decodificador completo



Primero, veamos el diagrama de tiempo de la señal de datos. El TTP229-BSF tiene varios modos de operación, y le doy un diagrama de tiempo para el modo que se utilizará más adelante. Se puede encontrar información más detallada sobre todos los modos de operación del microcircuito en la documentación correspondiente.



imagen



En primer lugar, es necesario describir el conjunto de líneas obligatorias con las que trabajará el decodificador. En este caso, hay dos de ellos, una línea de reloj (SCL) y una línea de datos (SDO).



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


Cuando el microcircuito detecta que se presiona un botón, establece la señal de datos válidos (DV) en la línea SDO, según la cual el receptor debe comenzar a leer los datos. Busquemos y decodifiquemos esta señal.



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



, , – , :



  • 'l' - nivel bajo, 0 lógico;
  • 'h' - nivel alto, lógico 1;
  • 'r' - aumento de la señal, transición del estado bajo al alto;
  • 'f' : disminución de la señal, transición del estado alto al bajo;
  • 'e' - cambio arbitrario en la señal, subida o bajada;
  • 's' - estado estable, 0 o 1.


Por lo tanto, para encontrar el comienzo de la señal DV, una condición que describe que la línea SCL es alta y la caída de la señal ocurre en la línea de datos (SDO). Llamamos a la función wait () con la condición y guardamos el número de muestra:



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


Para encontrar el final de la señal DV, es necesario establecer una condición en la que la línea SCL permanezca alta y la línea de datos sea alta:

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


Al finalizar la última llamada a la función wait (), se conocerán los números de muestra del principio y el final de la señal DV. Es hora de crear una anotación para él. Para hacer esto, agregue anotaciones al decodificador y agrúpelas juntas (annotation_rows):



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


donde 0 es el índice de la anotación en la tupla self.annotations que pertenece a este grupo. También deberá registrar la salida de las anotaciones:



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


Ahora todo está listo para colocar la anotación en la señal DV. Esto se hace llamando a la función put () ( f613b83 ):



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


Parámetros de función: número de muestra de inicio de anotación (self.dv_block_ss), número de muestra de finalización de anotación (self.samplenum), identificador de salida de anotación (self.out_ann) y datos para la anotación. Los datos se presentan como una lista de índice de anotación (0) y una lista anidada de cadenas, de la más larga a la más corta, para mostrar en la descripción. Si se especifica más de una línea, la interfaz puede seleccionar independientemente la línea mostrada, por ejemplo, dependiendo de la escala utilizada:







Del mismo modo, agregamos una anotación para el retraso Tw entre el final de la señal DV y el inicio de la lectura de datos desde el microcontrolador. A continuación, puede comenzar a decodificar los datos de presión del botón.



TTP229-BSF, dependiendo del modo seleccionado, puede funcionar con 8 o 16 botones táctiles. En este caso, los datos transmitidos no contienen información sobre el modo de funcionamiento del chip. Por lo tanto, para el decodificador, vale la pena agregar una opción que especifique el modo en que opera el microcircuito.



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']


Esta opción estará disponible para establecer un valor en la interfaz de usuario cuando se selecciona un decodificador.



Como puede ver en el diagrama de tiempos, los datos en la línea SDO se establecen cuando el SCL pasa al nivel activo (bajo) y se guardan cuando la señal vuelve al nivel pasivo. En este momento, tanto el microcontrolador como el decodificador pueden grabar el conjunto de datos en la línea SDL. La transición de SCL de regreso a la capa activa puede considerarse como el comienzo de la próxima transmisión de datos. En este caso, la función de decodificación se verá así ( 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


Pero este enfoque de colocar anotaciones tiene un inconveniente, la anotación para el último bit continuará hasta la próxima lectura de datos por parte del microcontrolador.







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


Luego, la condición en la función de decodificación se escribirá usando skip en el siguiente formulario, más una verificación adicional de que, al leer los datos sobre el botón presionado, el microcircuito no volvió a su estado inicial ( 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


Ahora el decodificador puede manejar el envío completo de datos. Y será conveniente si, además de la información sobre bits individuales, se agrega una anotación sobre qué botón se presionó. Para hacer esto, agregue una descripción de una anotación más. Dado que la anotación para presionar el botón se refiere a toda la transmisión de datos y se cruza con las anotaciones agregadas anteriormente, debe colocarse en un grupo separado. Creemos un nuevo grupo de anotaciones 'Mensaje clave' para él. ( 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]])






Hasta este punto, todo el código funcionaba solo con la primera premisa. ¿Ya has notado el 19% al lado del nombre del decodificador? Este es el porcentaje de muestras que se procesaron antes de que salga la función decode (). Para procesar todas las muestras, queda agregar un bucle sin fin alrededor del código para decodificar un envío de datos por separado ( 48f95fb ).



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


Dado que la decodificación finalizará automáticamente si la función wait () itera sobre todos ellos al buscar la siguiente muestra. Como resultado de este cambio, todas las muestras y todas las transmisiones de datos se procesarán como se muestra en el KDPV .



El toque final permanece para agregar la capacidad de seleccionar el nivel de señal activa y un decodificador completo para TTP229-BSF estará listo. El código fuente final también está disponible en GitHub .




All Articles