sigrok-protocol-decoders/

Если вы читали пост Знакомимся с Sigrok и логическим анализатором DSLogic , то помните, что для Sigrok можно писать декодеры протоколов (protocol decoders, в документации к Sigrok часто используется сокращение PD) на Python. Однако в посте ничего не говорится о том, как их, собственно, писать. Пришло время заполнить этот пробел.

Примечание: Если вы пропустили статью Как стать контрибьютором в open source проект — идеи для первого патча и прочие рекомендации , может быть не лишено смысла ознакомиться с ней.

Основные сведения

В этом контексте нельзя не сказать пару слов о процессе разработки Sigrok. Каким образом новые декодеры (или патчи к существующим) попадают в него? Для этого нужно предложить патчи в рассылке sigrok-devel . Притом мейнтейнеры предпочитают патчи в виде веток на GitHub, а не в файлов .patch. В качестве конкретного примера рассмотрим написанный мной декодер протокола TFT-дисплеев на базе ST7735 . Соответствующее письмо в рассылку можно найти здесь .

Вы обратили внимание, что я сказал патчи , во множественном числе? Патчей действительно нужно несколько:

  • Патч для репозитория sigrok-dumps, добавляющий .sr файл с примером декодируемого протокола. Пример моего патча: 1ea7b9af .
  • Патч для репозитория libsigrokdecode, добавляющий сам декодер на Python. Пример: f62e32bc .
  • Наконец, патч для sigrok-test, добавляющий регрессионные тесты на написанный декодер. Пример: 4aa3a4fd .

Получить файл .sr не сложно, это делается банально с помощью sigrok-cli или PulseView. Заметьте, что пример следует сократить до минимального. Обрезать лишнее можно в PulseView с помощью курсоров. Также стоит иметь ввиду, что в идеале записанный пример должен покрывать все, или хотя бы большинство возможностей декодера (например, все декодируемые команды). В связи с этим может потребоваться написать кастомную прошивку для вашей любимой отладочной платы.

В общем, эта часть работы тривиальна. Поэтому далее мы сосредоточимся на написании декодера и тестов к нему.

Разработка декодера

На время разработки декодера нужно как-то заставить sigrok-cli и PulseView его видеть. Проще всего сделать это с помощью символьной ссылки:

cd / usr / share / libsigrokdecode / decoders
sudo ln -s / path / to / libsigrokdecode / decoders / st7735 . / st7735

Декодер состоит из двух файлов. Файл __init__.py в основном содержит лицензию и краткое описание декодера:

##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2018 Aleksander Alekseev <afiskon@gmail.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, see <http://www.gnu.org/licenses/>.
##

»’
This decoder decodes the ST7735 TFT controller protocol.

Details:
http://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf
»’

from . pd import Decoder

Самое же интересное содержится в файле pd.py. Рассмотрим его по частям.

import sigrokdecode as srd

MAX_DATA_LEN = 128

# Command ID -> name, short description
META = {
0x00 : { ‘name’ : ‘NOP    ‘ , ‘desc’ : ‘No operation’ } ,
0x01 : { ‘name’ : ‘SWRESET’ , ‘desc’ : ‘Software reset’ } ,
0x04 : { ‘name’ : ‘RDDID  ‘ , ‘desc’ : ‘Read display ID’ } ,
# … (ПРОПУЩЕНО) …
}

Здесь просто объявляются константы, используемые далее по коду. Переменная META , как не сложно понять, служит для отображения кода команды в ее название и краткое описание. Эта информация была получена из даташита ST7735.

class Ann:
BITS , CMD , DATA , DESC = range ( 4 )

class Decoder ( srd. Decoder ) :
api_version = 3
id = ‘st7735’
name = ‘ST7735’
longname = ‘Sitronix ST7735’
desc = ‘Sitronix ST7735 TFT controller protocol.’
license = ‘gplv2+’
inputs = [ ‘logic’ ]
outputs = [ ‘st7735’ ]
channels = (
{ ‘id’ : ‘cs’ , ‘name’ : ‘CS#’ , ‘desc’ : ‘Chip-select’ } ,
{ ‘id’ : ‘clk’ , ‘name’ : ‘CLK’ , ‘desc’ : ‘Clock’ } ,
{ ‘id’ : ‘mosi’ , ‘name’ : ‘MOSI’ , ‘desc’ : ‘Master out, slave in’ } ,
{ ‘id’ : ‘dc’ , ‘name’ : ‘DC’ , ‘desc’ : ‘Data or command’ }
)
annotations = (
( ‘bit’ , ‘Bit’ ) ,
( ‘command’ , ‘Command’ ) ,
( ‘data’ , ‘Data’ ) ,
( ‘description’ , ‘Description’ ) ,
)
annotation_rows = (
( ‘bits’ , ‘Bits’ , ( Ann. BITS , ) ) ,
( ‘fields’ , ‘Fields’ , ( Ann. CMD , Ann. DATA ) ) ,
( ‘description’ , ‘Description’ , ( Ann. DESC , ) ) ,
)

Класс Decoder представляет собой непосредственно наш декодер. Класс должен иметь несколько обязательных полей, содержащие уникальный идентификатор протокола, его имя, краткое описание, информацию о лицензии. Поля inputs и outputs определяют, какие данные декодер принимает на вход и выдает на выход. Это нужно по той причине, что Sigrok позволяет писать декодеры, работающие поверх других декодеров. Например, нередко нужно написать декодер, работающий поверх декодера SPI или I2C. В данном примере декодер работает непосредственно с логическими сигналами, не полагаясь на другие декодеры. Поле channels определяет, какие сигналы нужны декодеру на вход. Поля annotations и annotation_rows говорят, какие данные выводит наш декодер, притом последнее поле нужно для объединения этих данных в группы.

def __init__ ( self ) :
self . reset ( )

def reset ( self ) :
self . accum_byte = 0
self . accum_bits_num = 0
self . bit_ss = 1
self . byte_ss = 1
self . current_bit = 1

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

def put_desc ( self , ss , es , cmd , data ) :
if cmd == 1 :
return
if META [ cmd ] :
self . put ( ss , es , self . out_ann , [ Ann. DESC ,
[ ‘%s: %s’ % ( META [ cmd ] [ ‘name’ ] . strip ( ) ,
META [ cmd ] [ ‘desc’ ] ) ] ] )
else :
# Default description:
dots = »
if len ( data ) == MAX_DATA_LEN:
data = data [ :- 1 ]
dots = ‘…’
data_str = ‘(none)’
if len ( data ) > 0 :
data_str = ‘ ‘ . join ( [ ‘%02X’ % b for b in data ] )
self . put ( ss , es , self . out_ann , [ Ann. DESC ,
[ ‘Unknown command: %02X. Data: %s%s’ % ( cmd , data_str ,
dots ) ] ] )

Имена методов __init__ , reset и start говорят сами за себя. Метод put_desc , используемый далее по коду, присваивает диапазону входных данных текстовое описание. Делается это через вызов унаследованного от родительского класса srd.Decoder метода put . Для идентификации начала и конца входных данных, которым мы хотим сопоставить какой-то выход декодера, используются целые числа, называемые start sample и end sample. В коде их часто сокращают до ss и es .

Наконец, основным методом является decode :

def decode ( self ) :
current_cmd = 1
current_data = [ ]
desc_ss = 1
desc_es = 1
self . reset ( )
while True :
# Check data on both CLK edges.
( cs , clk , mosi , dc ) = self . wait ( { 1 : ‘e’ } )

if cs == 1 : # Wait for CS = low, ignore the rest.
self . reset ( )
continue

if clk == 1 :
# Read one bit.
self . bit_ss = self . samplenum
if self . accum_bits_num == 0 :
self . byte_ss = self . samplenum
self . current_bit = mosi

if ( clk == 0 ) and ( self . current_bit >= 0 ) :
# Process one bit.
self . put ( self . bit_ss , self . samplenum , self . out_ann ,
[ Ann. BITS , [ str ( self . current_bit ) ] ] )
# MSB-first
self . accum_byte = ( self . accum_byte << 1 ) | self . current_bit
self . accum_bits_num + = 1
if self . accum_bits_num == 8 :
# Process one byte.
# DC = low for commands.
ann = Ann. DATA if dc else Ann. CMD
self . put ( self . byte_ss , self . samplenum , self . out_ann ,
[ ann , [ ‘%02X’ % self . accum_byte ] ] )
if ann == Ann. CMD :
self . put_desc ( desc_ss , desc_es , current_cmd ,
current_data )
desc_ss = self . byte_ss
desc_es = self . samplenum # For cmds without data.
current_cmd = self . accum_byte
current_data = [ ]
else :
if len ( current_data ) < MAX_DATA_LEN:
current_data + = [ self . accum_byte ]
desc_es = self . samplenum

self . accum_bits_num = 0
self . accum_byte = 0
self . byte_ss = 1
self . current_bit = 1
self . bit_ss = 1

Данные для декодирования приходят через вызов унаследованного метода wait . Притом, передав этому методу аргумент {1: 'e'} , мы говорим, что хотим получать данные только по переднему или заднему фронту на первом канале, которому у нас соответствует CLK (нумерация каналов идет с нуля). Также помимо e (edge) можно указать r (rising edge) или f (falling edge), если нас интересует не любой фронт, а только передний или только задний. В принципе, можно и не указывать ничего, получая вообще все входные данные, какие есть. Но в этом случае скорость работы декодера будет оставлять желать лучшего.

Так или иначе, благодаря вызову wait переменным cs , clk , mosi и dc присваиваются единички и нолики в соответствии со значениями на каналах. Понять, по какому смещению относительно начала данных мы находимся, можно благодаря унаследованному полю samplenump . Остальное — дело техники. Единички и нолики выводятся с аннотацией Ann.BITS , из них собираются байты, выводимые с аннотациями Ann.CMD или Ann.DATA , а байты декодируются в текстовое описание, выводимое при помощи объявленного выше метода put_desc .

Напомню, как выглядит результат:

На приведенной картинке фиолетовым цветом изображены биты (Ann.BITS). Под ними идут байты, зеленые соответствуют командам (Ann.CMD), а синие — аргументам команд (Ann.DATA). Наконец, под байтами выводится текстовое описание команды (Ann.DESC).

Вот и весь код! Стоит, впрочем, сказать пару слов об отладке. Иной раз трудно понять, какие данные приходят в тот или иной метод, особенно если вы пишете декодер, работающий поверх другого декодера. Вот, к примеру, шаблон ничего не делающего декодера, работающего поверх декодера SPI:

import sigrokdecode as srd

class Ann:
CMD , DATA , DESC = range ( 3 )

class Decoder ( srd. Decoder ) :
# see https://sigrok.org/wiki/Protocol_decoder_API
api_version = 3
id = ‘st7735’ # this is shown in `sigrok-cli -L`
name = ‘ST7735’ # this is shown in PulseView
longname = ‘ST7735 TFT controller’ # shown in `sigrok-cli -L`
desc = ‘ST7735 TFT controller protocol decoder’
license = ‘gplv2+’
inputs = [ ‘spi’ ]
outputs = [ ‘st7735’ ]
channels = ( )
optional_channels = ( )
annotations = (
( ‘command’ , ‘Command’ ) , # Ann.CMD
( ‘data’ , ‘Data’ ) , # Ann.DATA
( ‘description’ , ‘Description’ ) , # Ann.DESC
)
annotation_rows = (
( ‘fields’ , ‘Fields’ , ( Ann. CMD , Ann. DATA , ) ) ,
( ‘description’ , ‘Description’ , ( Ann. DESC , ) ) ,
)
options = ( ) # see examples in spiflash decoder

def __init__ ( self ) :
self . reset ( )

def reset ( self ) :
pass # do nothing, yet

# This function is called before the beginning of the decoding.
# This is the place to register() the output types, check the
# user-supplied PD options for validity, and so on
def start ( self ) :
self . out_ann = self . register ( srd. OUTPUT_ANN )

def putx ( self , data ) :
self . put ( self . ss , self . es , self . out_ann , data )

# This is a function that is called by the libsigrokdecode
# backend whenever it has a chunk of data for the protocol
# decoder to handle
# Arguments:
# ss =   startsample, the absolute samplenumber of the
#        first sample in this chunk of data
# es =   endsample, the absolute samplenumber of the
#        last sample in this chunk of data
# data = a list containing the data to decode. Depends on
#        whether the decoder decodes raw samples or is
#        stacked onto another decoder
def decode ( self , ss , es , data ) :
print ( «decode: ss = %s, es = %s, data = %s» % ( ss , es , data ) )
ptype , mosi , miso = data

self . ss , self . es = ss , es

if ptype == ‘CS-CHANGE’ :
# end_current_transaction()
pass

if ptype != ‘DATA’ :
return

# Handle commands here
self . putx ( [ Ann. DESC , [ ‘Unknown command: 0x%02X’ % mosi ] ] )

Обратите внимание на отладочный вывод с помощью print , а также на то, что данные здесь приходят не через вызов wait , а напрямую в метод decode , имеющий другую сигнатуру. Теперь можно сказать:

pulseview —log-to-stdout ~ / temp / st7735-debug.sr | tee ~ / temp / pv.log

Отладочный вывод декодера будет виден в консоли.

Покрываем код текстами

Итак, будем считать, что с декодером мы разобрались. Осталось добавить тесты на него. На этом шаге лучше не полагаться на установленные бинарные пакеты Sigrok, а честно собрать их из веток master. Тем более, что половину репозиториев мы и так же склонировали. Делается это не сложно и занимает пару минут:

mkidir -p / home / eax / sigrok-dev / src
cd / home / eax / sigrok-dev / src

git clone ‘git://sigrok.org/libsigrok’
git clone ‘git://sigrok.org/sigrok-cli’
git clone ‘git://sigrok.org/libsigrokdecode’

cd libsigrok
. / autogen.sh
CFLAGS = ‘-O0 -g’ CXXFLAGS = ‘-O0 -g’ . / configure
—prefix = / home / eax / sigrok-dev / install
—disable-java —disable-python
make
make install

cd .. / libsigrokdecode
. / autogen.sh
CFLAGS = ‘-O0 -g’ CXXFLAGS = ‘-O0 -g’ . / configure
—prefix = / home / eax / sigrok-dev / install
make
make install

cd .. / sigrok-cli
. / autogen.sh
CFLAGS = ‘-O0 -g’ CXXFLAGS = ‘-O0 -g’ . / configure
—prefix = / home / eax / sigrok-dev / install
make
make install

cd ..

Fun fact! Все зависимости я лично подтянул очень просто — собрал пакеты из AUR , но не стал их устанавливать. Даже если вы пользуетесь дистрибутивом, отличным от Arch Linux, вы можете подсмотреть список зависимостей в AUR.

Собирать PulseView при разработке декодеров, строго говоря, не требуется, но если очень хочется:

git clone git: // sigrok.org / pulseview

cd pulseview
mkdir build
cd build
PKG_CONFIG_PATH = / home / eax / sigrok-dev / install / lib / pkgconfig /
cmake -G Ninja
-DCMAKE_INSTALL_PREFIX: PATH = / home / eax / sigrok-dev / install
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_CXX_FLAGS= ‘-O0 -g -fext-numeric-literals’ ..
ninja
ninja install

Если вы хотите использовать Sigrok и PulseView из веток master, допишите в файл ~/.bashrc:

export LD_LIBRARY_PATH = » $LD_LIBRARY_PATH :~/sigrok-dev/install/lib/»
export PATH = «~/sigrok-dev/install/bin: $PATH »

Возвращаемся к тестами:

git clone git: // sigrok.org / sigrok-test
cd sigrok-test

. / autogen.sh
PKG_CONFIG_PATH = $HOME / sigrok-dev / install / lib / pkgconfig
. / configure
—with-decodersdir = $HOME / sigrok-dev / libsigrokdecode / decoders
make

Проверяем, что тесты проходят:

LD_LIBRARY_PATH = $HOME / sigrok-dev / install / lib
. / decoder / pdtest -r -v -a

Чтобы сгенерировать ожидаемый вывод для нашего нового декодера, пишем в decoder/test/st7735/test.conf:

test st7735_basic
protocol-decoder st7735 channel cs=0 channel dc=2 channel mosi=3 ?
channel clk=4
input display/st7735/st7735.sr
output st7735 annotation match st7735_basic.output

Говорим:

LD_LIBRARY_PATH = $HOME / sigrok-dev / install / lib
. / decoder / pdtest -f st7735

Будет получен файл decoder/test/st7735/st7735_basic.output с ожидаемым выводом. Для запуска отдельного теста говорим:

LD_LIBRARY_PATH = $HOME / sigrok-dev / install / lib
. / decoder / pdtest -r st7735

Эксперимента ради можно отредактировать файл .output и убедиться, что в этом случае тесты не пройдут:

Testcase: st7735/st7735_basic/annotation
Test output mismatch:
— 24003758-24003780 st7735: command: «11»
+ 24003758-24003780 st7735: command: «01»

Заинтересованные читатели могут использовать информацию из этого раздела не только для разработки декодеров, но и для разработки кишок Sigrok и PulseView. Здесь вам также помогут заметки Памятка по отладке при помощи GDB , Краткий обзор статических анализаторов кода на C/C++ , Профилирование кода на C/C++ в Linux и FreeBSD и далее по ссылкам.

Заключение

Кое-какие дополнительные сведения можно найти на официальной wiki проекта:

Также не побрезгуйте почитать код других декодеров из libsigrokdecode , там полно примеров. За помощью всегда можно обратиться в уже упомянутую рассылку sigrok-devel . А еще сообщество разработчиков Sigrok довольно активно в IRC , на канале #sigrok во FreeNode.

Как видите, Sigrok имеет относительно невысокий порог вхождения. Стать контрибьютором в него очень просто — берете случайную железку и пишите для используемого ею протокола декодер на Python. А когда и если это перестанет быть интересным, можно заняться разработкой PulseView или драйверов для новых логических анализаторов, мультиметров и осциллографов. В общем, если вы искали открытый проект для самореализации, рекомендую.

admin

Share
Published by
admin

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

1 месяц ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

1 месяц ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

1 месяц ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

1 месяц ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

1 месяц ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

1 месяц ago