Escrevendo um decodificador para sigrok

imagem



Se você trabalha com tecnologia digital, mais cedo ou mais tarde é necessário um analisador lógico. Um dos disponíveis para radioamadores é o analisador lógico DSLogic da DreamSourceLab. Ele foi mencionado repetidamente no site, pelo menos: um , dois e três .



Seu recurso é de código aberto e também o fato de que a biblioteca sigrok de código aberto é responsável pela decodificação de sinais . Juntamente com uma lista impressionante de decodificadores de sinal existentes, esta biblioteca fornece uma API para você criar seus próprios. É isso que faremos.



Suporte de demonstração



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



imagem





, , . , . , , .



Cada decodificador no sigrok é um pacote separado escrito em Python 3 e possui seu próprio diretório na pasta decodificadores. Como qualquer pacote Python, o decodificador contém __init__.py e, de acordo com a convenção de nomenclatura do sigrok, pd.py é um arquivo que contém a implementação real.



imagem



O código em __init__.py é padrão e inclui uma documentação que descreve o protocolo e a importação do decodificador ( d28dee9 ):



'''
Protocol description.
'''

from .pd import Decoder


O arquivo pd.py contém a implementação do 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 é uma implementação mínima que a biblioteca pode carregar, mas não decodifica nada. Vamos dar uma olhada nas propriedades necessárias:



  • 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 - .
  • canais - uma lista de linhas de sinal usadas pelo decodificador. Esta propriedade é necessária para decodificadores cujo formato de dados de entrada é lógico.


e métodos necessários:



  • start () - o método chamado antes do início da decodificação. Nesse método, as configurações devem ser feitas para a sessão de decodificação atual.
  • reset () - o método chamado quando a decodificação é interrompida. Deve retornar o decodificador ao seu estado inicial.
  • decode () - o método chamado para decodificar o sinal.


Depois de lidar com a implementação mínima do decodificador, você pode começar a decodificar o sinal real.



Decodificador completo



Primeiro, vejamos o diagrama de tempo do sinal de dados. O TTP229-BSF possui vários modos de operação, e forneço um diagrama de tempo para o modo que será usado posteriormente. Informações mais detalhadas sobre todos os modos de operação do microcircuito podem ser encontradas na documentação do mesmo.



imagem



Em primeiro lugar, é necessário descrever o conjunto de linhas obrigatórias com as quais o decodificador funcionará. Nesse caso, existem dois deles, uma linha de relógio (SCL) e uma linha de dados (SDO).



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


Quando o microcircuito detecta o pressionamento de um botão, ele define o sinal Data Valid (DV) na linha SDO, segundo a qual o receptor deve começar a ler os dados. Vamos encontrar e decodificar esse sinal.



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



, , – , :



  • 'l' - nível baixo, 0 lógico;
  • 'h' - nível alto, lógico 1;
  • 'r' - aumento do sinal, transição do estado baixo para o alto;
  • 'f' - decaimento do sinal, transição do estado alto para o baixo;
  • 'e' - mudança arbitrária, ascensão ou queda de sinal;
  • 's' - estado estável, 0 ou 1.


Assim, para encontrar o início do sinal DV, é necessária uma condição que descreva que a linha SCL está alta e que o sinal na linha de dados (SDO) cai. Vamos chamar a função wait () com a condição preparada e salvar o número da amostra:



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


Para encontrar o final do sinal DV, é necessário definir a condição quando a linha SCL permanecer em um estado alto e a linha de dados entrar em um estado alto:

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


Após a conclusão da última chamada para a função wait (), os números de amostra do início e do fim do sinal DV serão conhecidos. É hora de criar uma anotação para ela. Para fazer isso, adicione anotações ao decodificador e agrupe-as (annotation_rows):



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


onde 0 é o índice da anotação na tupla self.annotations neste grupo. Você também precisará registrar a saída das anotações:



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


Agora tudo está pronto para colocar a anotação no sinal DV. Isso é feito chamando a função put () ( f613b83 ):



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


Parâmetros de função: número da amostra inicial da anotação (self.dv_block_ss), número da amostra final da anotação (self.samplenum), identificador de saída da anotação (self.out_ann) e dados para anotação. Os dados são apresentados como uma lista do índice de anotação (0) e uma lista aninhada de cadeias, do maior para o menor, para exibição na descrição. Se mais de uma linha for especificada, a interface poderá selecionar independentemente a linha exibida, por exemplo, dependendo da escala usada:







Da mesma forma, adicionamos uma anotação para o atraso Tw entre o final do sinal DV e o início da leitura dos dados do microcontrolador. Em seguida, você pode começar a decodificar os dados pressionando o botão.



O TTP229-BSF, dependendo do modo selecionado, pode funcionar com 8 ou 16 botões de toque. Nesse caso, os dados transmitidos não contêm informações sobre o modo de operação do microcircuito. Portanto, para o decodificador, vale a pena adicionar uma opção que especifique o modo em que o microcircuito opera.



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 opção estará disponível para definir um valor na interface do usuário quando um decodificador for selecionado.



Como você pode ver no diagrama de tempo, os dados na linha SDO são expostos quando o SCL passa para o nível ativo (baixo) e são salvos quando o sinal retorna ao nível passivo. Nesse momento, o microcontrolador e o decodificador podem gravar os dados configurados na linha SDL. A transição do SCL de volta para a camada ativa pode ser considerada como o início da próxima transmissão de dados. Nesse caso, a função de decodificação será semelhante a ( 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


Mas essa abordagem de colocação de anotações tem uma desvantagem, a anotação do último bit continuará até os próximos dados lidos pelo 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))


Em seguida, a condição na função de decodificação é gravada usando skip no seguinte formato, além de uma verificação adicional de que o chip não retornou ao seu estado inicial ao ler dados sobre o botão pressionado ( 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


Agora, o decodificador pode lidar com o envio completo de dados. E será conveniente se, além das informações sobre bits individuais, for adicionada uma anotação sobre qual botão foi pressionado. Para fazer isso, adicione uma descrição de mais uma anotação. Como a anotação para pressionar um botão se aplica a todo o envio de dados e cruza com as anotações adicionadas anteriormente, ela deve ser colocada em um grupo separado. Vamos criar um novo grupo de anotações 'Mensagem-chave' para ele. ( 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]])






Até aquele momento, todo o código funcionava apenas com o primeiro pacote. Você notou 19% ao lado do nome do decodificador? Essa é a porcentagem de amostras que foram processadas antes da saída da função decode (). Para processar todas as amostras, resta adicionar um loop infinito ao redor do código para decodificar um envio de dados 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
            ...


Como a decodificação será encerrada automaticamente se a função wait () iterar sobre todos eles ao procurar o próximo exemplo. Como resultado dessa alteração, todas as amostras e todas as transmissões de dados serão processadas conforme mostrado no KDPV .



O toque final permanece para adicionar a capacidade de selecionar o nível do sinal ativo e um decodificador completo para o TTP229-BSF estará pronto. O código fonte da versão final também está disponível no GitHub .




All Articles