Компания Saleae производит логические анализаторы и предоставляет программу Saleae Logic 2 для отображения, анализа и логгирования данных. Logic 2 поддерживает разнообразные интерфейсы и протоколы и их декодирование.
Для расширения функций анализатора в Logic 2 появилась возможность создания своих обработчиков и декодеров протоколов.

В представляемом материале рассматривается создание своего декодера - анализатора верхнего уровня (HLA).

КПВ, единственная здесь созданная бездушным ИИ
КПВ, единственная здесь созданная бездушным ИИ

Зачем?
Например, есть последовательность передаваемых по SPI байт. Стандартно, при правильной настройке, вы увидите значения этих байт. Но, может возникнуть вопрос удобной интерпретации полученных данных.
Декодер может помочь в выводе данных в удобном виде и/или упростить анализ (reverse engineering) неизвестного протокола.

Как?
Saleae Logic 2 позволяет подключать программные расширения - написанные на Python модули, которые добавляют возможностей по отладке и анализу данных. В настоящий момент поддерживается три типа расширений:
- Высокоуровневый анализатор (High-Level Analyzers, HLA)
- Аналоговые измерения (Analog Measurements)
- Цифровые измерения (Digital Measurements)

Далее рассмотрим создание HLA, который может анализировать данные, получаемые от низкоуровневых протоколов (USART, I2C, SPI, CAN, LIN, Manchester и др.) В нашем примере будем декодировать передаваемые данные в ЖК-индикатор TIC33 и ТDO905. Оба индикатора имеют идентичный SPI-подобный интерфейс, разница заключается в разводке сегментов, а значит в декодере. Поэтому наш HLA-анализатор будет иметь функцию выбора декодера. Декодированные данные будем собирать в строку, признак новой строки - таймаут в передаваемых данных.
Для создания Расширения нажимаем на иконку Extension и далее нажимаем на Create Extension

Либо в опциях расширений выбрать Create New Extension (тут же можно загрузить кастомные расширения)

Вам предложат задать имя расширению, описание и авторство, а так же, выбрать путь и название к создаваемому Расширению. Logic 2 создаст несколько файлов-шаблонов: Extension.json HighLevelAnalyzer.py README.md

extension.json: { "name": "TIC33 & ТDO905 decoder", "apiVersion": "1.0.0", "author": "my hided name", "version": "0.0.1", "description": "SPI байтики декодируем в символы, отображаемые на дисплее TIC32 или ТDO905", "extensions": { "TIC LCD decoder": { "type": "HighLevelAnalyzer", "entryPoint": "HighLevelAnalyzer.Hla" } } }

Тут можно изменить имя, добавить описание и тогда наше расширение будет выглядеть примерно так:

Рассмотрим содержимое модифицированного файла HighLevelAnalyzer.py

Первым делом импортируются необходимые данные:

from saleae.analyzers import HighLevelAnalyzer, AnalyzerFrame, ChoicesSetting, NumberSetting
from saleae.data import GraphTimeDelta


Далее создается декодер - какому значению байта в SPI потоке соответствует отображаемый декодером символ:

# декодеры-словари
spitolcdDecoder_TIC33 = { # charset decoder for TIC33
0xFA: '0', 0x0A: '1', 0xB6: '2', 0x9E: '3',
0x4E: '4', 0xDC: '5', 0xFC: '6', 0x8A: '7',
0xFE: '8', 0xDE: '9', 0xEE: 'A', 0x7C: 'b',
0xF0: 'C', 0x3E: 'd', 0xF4: 'E', 0xE4: 'F',
0x00: ' ', 0x04: '-', 0x10: '_', 0x80: '¯',
0xC6: '⁰', 0x34: 'c', 0xC4: 'ᶜ', 0x14: '₌',
0x84: '⁼', 0x94: '≡', 0x3C: 'o', 0x6C: 'h',
0x2C: 'n', 0x38: 'u', 0x74: 't', 0xE6: 'P',
}

spitolcdDecoder_TD0905 = { # charset decoder for ТDO905
0x5F: '0', 0x50: '1', 0x6D: '2', 0x79: '3',
0x72: '4', 0x3B: '5', 0x3F: '6', 0x51: '7',
0x7F: '8', 0x7B: '9', 0x77: 'A', 0x3E: 'b',
0x0F: 'C', 0x7C: 'd', 0x2F: 'E', 0x27: 'F',
0x00: ' ', 0x20: '-', 0x08: '_', 0x01: '¯',
0x63: '⁰', 0x2C: 'c', 0x23: 'ᶜ', 0x28: '₌',
0x21: '⁼', 0x29: '≡', 0x3C: 'o',
}


При добавлении расширения нам нужно будет выбрать опции: тип ЖКИ (интерфейса, декодера), линию SPI с данными для декодера и таймаут - минимальное время между байтами которое будет идентифицировать новую порцию данных на ЖКИ.
N.B.: Вообще, драйвер ЖКИ имеет сигнал LOAD и в качестве разделителя фреймов можно использовать его.

class Hla(HighLevelAnalyzer):
my_choices_setting = ChoicesSetting(choices=('TIC33', 'ТDO905'))
spiLine_Choise = ChoicesSetting(label='SPI Line', choices=('MOSI', 'MISO'))
packet_timeout = NumberSetting(label='Packet Timeout [s]', min_value=1e-6, max_value=1e4)


Результатом работы декодера будет выводимая в анализатор строка

result_types = {
'LCD': {
'format': '{{data.string}}',
}
}


Инициализация. В зависимости от выбранного интерфейса используем соответствующую таблицу декодера. В каждом байте, помимо информации о 7-и сегментах, присутствует информация о разделительной точке, поэтому задаем шаблон выявления точки

def init(self):
self.current_string = ''
self.start_time = None
self.last_time = None
self.patterns = spitolcdDecoder_TIC33 if self.my_choices_setting == 'TIC33' else spitolcdDecoder_ТDO905
self.dotmask = 0x01 if self.my_choices_setting == 'TIC33' else 0x80

def reset_state(self):
self.current_start_time = None
self.last_end_time = None

def decode(self, frame: AnalyzerFrame):
result_frames = []
if frame.data['mosi' if self.spiLine_Choise == 'MOSI' else 'miso'] is None:
return

maximum_delay = GraphTimeDelta(second=self.packet_timeout or 0.5E-3)


Декодируем текущий символ, данные берем с выбранной линии MOSI/MISO интерфейса SPI. Если в байте присутствует бит разделительной точки на дисплее - добавляем её в вывод.

ch = int.from_bytes(frame.data['mosi' if self.spiLine_Choise == 'MOSI' else 'miso'], 'big')
symbol = self.patterns.get(ch & 0xFE, '?') + '.' if ch & self.dotmask else self.patterns.get(ch & 0xFE, '?')

Далее складываем символы в строку и выводим результат:

# Если это первый символ
if not self.current_string:
self.current_start_time = frame.start_time
self.current_string = symbol
self.last_end_time = frame.end_time
return

# Проверяем превышение таймаута
if self.last_end_time + maximum_delay < frame.start_time:
# Сохраняем предыдущую строку
result_frames.append(AnalyzerFrame(
'LCD',
self.current_start_time,
self.last_end_time,
{'string': self.current_string}
))
# Начинаем новую строку
self.current_string = symbol
self.current_start_time = frame.start_time
else:
# Продолжаем текущую строку
self.current_string += symbol

self.last_end_time = frame.end_time
return result_frames

def finalize(self):
if not self.current_string:
return
return AnalyzerFrame(
'LCD',
self.current_start_time,
self.last_end_time,
{'string': self.current_string}
)


На этом все, остается добавить/обновить наше расширение в Saleae Logic 2.
При добавлении декодера будет предложено выбрать источник данных - данные из SPI, декодируемый интерфейс (тип индикатора), линию данных SPI и разделяющий пакеты интервал времени.

Настройки нашего декодера
Настройки нашего декодера

Теперь наши данные получили "лицо" и, средствами логического анализатора Saleae, могут быть представлены в декодированном, более читаемом (user-friendly) виде.

Данные SPI с подключенным декодером
Данные SPI с подключенным декодером
Данные в табличном представлении вывода декодера
Данные в табличном представлении вывода декодера
Изображение на дисплее ЖКИ
Изображение на дисплее ЖКИ

Итоги
Наверное, читатель может сказать: «а зачем смотреть анализатором то, что и так видно на индикаторе?». Соглашусь, но данный пример служит лишь демонстрацией возможностей. На сайте Saleae можно найти более полезные примеры декодеров: для работы с различными I2C, SPI, NFC микросхемами, радиотрансиверами и специфическими протоколами. Даже если вы не найдете нужный для себя декодер, то сможете создать собственный под конкретную микросхему, периферию или при исследовании и проведении reverse engineering.

Полезные ссылки для интересующихся:

Saleae Support. Software Extensions: https://support.saleae.com/extensions

Пользовательские, комьюнити расширения. Shared High Level Analyzers (HLAs): https://support.saleae.com/extensions/high-level-analyzer-extensions/shared-high-level-analyzers-hlas

Комментарии (6)


  1. checkpoint
    22.05.2025 21:48

    А почему не сделали свой плагин для PulseView (sigrok) ? Почему завязались на проприетарное решение ?


    1. ariz0na Автор
      22.05.2025 21:48

      Основная причина - исторически больше привык к Saleae, хотя у меня есть и DSLogic и с DSView и универсальной PulseView давно знаком, но вот как-то не задумывался про создание плагина для него.
      Второй резон - посмотрел поиском и нашел подробную статью на Хабре про написание декодера под sigrok, так что нового тут рассказать нечего ))


      1. checkpoint
        22.05.2025 21:48

        Если бы Вы сделали плагин для PulseView от этого было бы больше пользы. У меня тоже есть прибор от Saleae, но использую его исключительно с PulseView.


        1. ariz0na Автор
          22.05.2025 21:48

          Для PulseView получается вот так:

          или нужно именно описание как добавлять свой плагин для PulseView?


          1. checkpoint
            22.05.2025 21:48

            или нужно именно описание как добавлять свой плагин для PulseView?

            Я не имел опыта добавления своих плагинов в PulseView, только пользовался тем что уже есть. Лишяя инструкция с живым примером никогда не помешает.


  1. NutsUnderline
    22.05.2025 21:48

    для анализа протоколов можно прикрутить wireshark, в нем довольно удобно сделано сделан разбор пакетов данных по структурам а данные в него можно тянуть самыми разными путями, а верхний уровень можно реализовать скрипnом на .LUA https://stackoverflow.com/questions/44965474/creating-lua-dissector-in-wireshark-for-non-ethernet-data

    более того можно использовать и куда как более дешевое железо, есть вообще готовое решение https://github.com/frank-zago/ch341-i2c-spi-gpio