Как стать автором
Обновить
Selectel
IT-инфраструктура для бизнеса

Самодельная мобильная лаборатория из старого мультиметра и подручного телефона

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров8.1K

Эта история началась с того, что я допиливал свой пет-проект по обработке данных. В ходе работы мне попался старый, но надежный мультиметр. Я долго им пользовался ранее, изучая электронику. Сам прибор неплохой, но софт под него сильно устарел, да и заточен он только под Windows. Так я занялся реверс-инжинирингом, отладкой обмена сообщениями и сборкой библиотек для телефона. Подробности — под катом!

Для подобного проекта можно использовать любую платформу, для которой получится собрать Qt-приложение.

Используйте навигацию, если не хотите читать текст целиком:
Предыстория
О подключении по USB
Немного кода
Автоматизация
Заключение

Предыстория


На первых курсах вуза мне сильно хотелось иметь профессиональное оборудование для работы с электроникой, но стоимость для студента была заоблачной. К счастью, я мог использовать различные приборы на кафедре, но их не унести домой. Так я наткнулся на компактные измерительные устройства Hantek. Они представляли собой плату в металлическом корпусе, с одного торца которого были разъемы для подключения щупов, а с другого — USB-интерфейс.

Блоками Hantek365 и DSO-6204 я надолго оснастил свою домашнюю лабораторию. Оба устройства были все время подключены в ПК, а измерительные щупы находились в стакане для канцелярии. Это позволяло в любой момент открыть софт и провести нужные измерения. Во избежание помех в первый год пользования приобрел USB-изоляторы.

Забавный факт: китайские производители даже предлагали свой аналог NI PXI rack, но большого распространения они не получили.


Источник.

После переезда на Linux Mint в начале магистратуры мультиметр я практически забросил, т. к. не было софта под него. Заменил его обычным ручным UNI-T.

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




О подключении по USB


По запросу «hantek 365 source» буквально первая ссылка в поисковой выдаче привела меня в нужный репозиторий на GitHub.

При подключении по USB девайс работает по крайне простому протоколу. Как и USB/CDC-устройства, мастер посылает запрос на чтение и ожидает ответ от прибора. Если ответ получен в течение отведенного времени, его можно обработать. Ответ от устройства в виде набора байт выглядит следующим образом:

  • 0 байт — статус измерения,
  • 1 байт — содержит ASCII знак измерения (2B +, 2D -),
  • 2-5 байты — четыре ASCII символа числового значения,
  • 7 байт — позиция десятичной точки,
  • 8 байт — режима измерения,
  • 10 байт — множитель,
  • 11 байт — единица измерения,
  • 12 байт — шкала текущего измерения,
  • 13-15 байты — \r \n 0x00 — завершение пакета данных.

Анализируя формат сообщения в найденном примере кода, заметил, что выполняется занятная операция вывода:

for (i = 0; i < 4; i++)
          {
            fprintf(stdout, "%c", databuff[i + 2]);
            if ((dpos - 0x30) >> i == 1)
            {
              fprintf(stdout, ".");
            }
          }

То есть устройство содержит готовый ответ измерения в виде ASCII-кода, а разделительную точку — в виде позиции бита седьмого байта. Также биты множителя (10) и единицы измерения (11) хранят информацию в виде позиции бита. Например, так выглядят единицы измерения:

  • 0x01 = 0b00000001 — температура по Фаренгейту, °F;
  • 0x02 = 0b00000010 — температура по Цельсию, °C;
  • 0x04 = 0b00000100 — частота, Гц;
  • 0x20 = 0b00100000 — сопротивление, Ом;
  • 0x40 = 0b01000000 — ток, амперы;
  • 0x80 = 0b10000000 — напряжение, вольты.

А как получены эти значения? Одно из предположений — кто-то из разработчиков прибора выкладывал исходные коды. Или же просто проанализировали трафик интерфейса через Wireshark. Я нередко использовал такой метод поиска необходимых данных при доработке библиотеки для CDC. Таким же методом определил значение последних 4 байт.


Все байты как на ладони: +0,718 мВ (4=0b100 — три символа после запятой, 0x40 — милли, 0x80 Вольт).

Что внутри


Думаю, читателям интересно устройство прибора изнутри. Корпус представляет собой экструдированный алюминиевый каркас с полозьями для печатной платы, закрытый с торцов информационными пластинами с разъемами.


Простая, но эффективная конструкция.

Большой интерес представляет сама плата. На ней расположены разъемы щупов, аналоговый фронтенд, мощный шунт, набор предохранителей, микросхема обработки сигналов и конвертор интерфейсов. Плата сделана универсальной. Насколько понимаю, на ней предусмотрены посадочные места для модуля беспроводной связи, цепь подготовки питания от аккумулятора и кнопка разрыва питания.


В качестве конвертора интерфейсов на обратной стороне платы располагается STM32F103c8T6 (практически bluepill), который связан с микросхемой-мультиметром FS9922-DMM4 (даташит на эту микросхему) через оптопары. Контакты STM выведены как на интерфейс USB-B, так и на разъем USBXI.

В целом, там всего четыре контакта. При большом желании можно развести собственную плату для рэка из набора разъемов PCIe 1x. Однако несмотря на схожесть разъема и назначения, интерфейс PCIe и USBXI несовместимы! В лучшем случае — сожжете порт на STM32.


Немного кода


Основная логика работы, как и ранее, сделана на Qt C++ и доступна в репозитории. Помимо мультиметра думаю добавить интерфейс для аналогичного осциллографа, например DSO-6022.

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

QtHantek* QtHantek::createDevice(QObject *parent) {
    libusb_context *context = NULL;
    libusb_device **list = NULL;
    int rc = 0;
    size_t count = 0;
    rc = libusb_init(&context);
    if (rc != 0)
    {
        qWarning () << "USB not available!";
        return new QtHantek(0,parent);
    }
    count = libusb_get_device_list(context, &list);
    if (count < 1){
        qWarning () << "Devices not found!";
        return new QtHantek(0,parent);
    }
    if (count>0) {
        for (size_t idx = 0; idx < count; ++idx)
    {
        libusb_device *device = list[idx];
        libusb_device_descriptor desc = {0};
        rc = libusb_get_device_descriptor(device, &desc);
        if (rc != 0)
            continue; // skip if error
        if (desc.idVendor == availableDevices[0][0] && desc.idProduct == availableDevices[0][1]){
                    libusb_exit(context);
                    return new QtHantek365(parent);
         }
    }
    }
    libusb_free_device_list(list, 1);
    libusb_exit(context);
     return new QtHantek(0,parent);
}

Вся работа с мультиметром по USB заключаются в выборе режима работы и считывания буфера с обратным значением.

Так, если все ок, производим стандартную процедуру инициализации интерфейса, выбираем текущий режим (по умолчанию — 60V DC) и направляем его устройству для инициализации работы и старта обработки данных.

int init()
    {
        libusb_device **list = NULL;
        m_husb = libusb_open_device_with_vid_pid(m_ctx, 1155, 22306);
        if (m_husb == nullptr)
        {
            qDebug() << "Cannot open device!";
            return -2;
        }
        if (libusb_claim_interface(m_husb, 0) < 0)
            return -3;
        initTimer();
        emit exceptionSignal(0); // Успешная инициализация
        return 0;
    }

Как и в случае с CDC, запускаем таймер. Каждый тик (100 мс) проверяем значение интерфейса и, если первый байт 0xA0, выставляем сигнал, что данные готовы. Далее этот сигнал можно обрабатывать UI-интерфейсом. В связи с тем, что данные приходят сразу в готовом виде, я использую две переменные для хранения результата.

        if (databuff[0] == 0xA0 && actual == 15)
        {
            int value = 0;
            int afterPoint = 0;
            uint8_t dpos = databuff[7];  // десятичная точка
            uint8_t mult = databuff[10]; // Множитель
            uint8_t unit = databuff[11]; // Единица измерения
            // преобразование в число
            for (int i = 0; i < 4; i++)
            {
                afterPoint = afterPoint * 10 + (databuff[i + 2] - 0x30);
                if (dpos & 1 << (4 - i))
                {
                    value = afterPoint;
                    afterPoint = 0;
                }
            }
            if (databuff[1] == 0x2D)
                value *= -1;
            emit messageAvailable(value, afterPoint, getMultiplierString(mult), getUnitString(unit));

Для «связывания» интерфейса пользователя и подпрограммы обработки данных приборов используем setContextProperty.

На целевой странице с прибором добавляем обработку сигналов от прибора. Основной из них — onDataAvailable — сообщает о наличии новых данных. При срабатывании сигнала приложение определяет текущие максимальное и минимальное значение, форматирует и выводит на экран.

   Connections {
            target: hantekDevice
            onMessageAvailable:{
                valueDisplay.text = value+"."+afterPoint;
                dimensionLabel.text = mult + unit;
                if(modeSelector.sIndex>8){
                    if (acdcSwitch.checked)
                    {dimensionLabel.text += " AC";}
                    else {dimensionLabel.text += " DC";}
                }
            }
        }

QML-скевоморфизм


Пообщавшись со знакомыми разработчиками, мы создали публичный репозиторий с элементами QML. Актуальную версию селектора можно скачать оттуда.

Для реализации привычного интерфейса мультиметра решил сделать собственный элемент — селектор режимов. Он состоит из окружности, вокруг которой расположены части дуги с цветовыми метками режимов и названия каждого возможного режима работы.


Физическое и программное воплощение одного устройства.

Из интересного — добавил обратную связь через QtFeedback. Примеры применения можно найти в документации Ubuntu Phone. В целом, применение очень простое: создаете компонент и по необходимости его вызываете. Под капотом данный элемент передает необходимый запрос через DBus интерфейсу com.nokia.NonGraphicFeedback. Следующий пример по нажатию кнопки вызывает вибрацию на 100 мс:

import QtQuick 2.0
import Sailfish.Silica 1.0
import QtFeedback 5.0
Page {
   objectName: "mainPage"
   allowedOrientations: Orientation.Portrait
   // HapticsEffect для вибрации
   HapticsEffect {
       id: rumbleEffect
       duration: 100
   }
   Button {
       text: "DRILL!!!"
       width: parent.width
       height: parent.height
       onClicked: rumbleEffect.start();  // plays a rumble effect
   }
}

Интересный нюанс: если выключена вибрация клавиатуры, то и здесь она не сработает.

Для отображения графика на планшете в этот раз использовал ChartJs2QML из примеров ОМП. Этот вариант удобнее в плане интерфейса для разработки, но отображаются артефакты при обновлении графиков. Чтобы интерфейс с графиком отображался только на планшетах, создал новую qml-страницу и прописал условие вызова в главном файле:

initialPage: getWidthBasedPage() 
…
    function getWidthBasedPage() {
        if (Screen.width > 720) {return Qt.resolvedUrl("pages/TabletMainPage.qml");
        } else { // Для маленьких экранов
            return Qt.resolvedUrl("pages/MainPage.qml");
        }

Автоматизация


Инсайдерская информация! Компания ОМП готовит значительное обновление системы. В частности — компонентов управления USB-устройствами и доступам к ним. В скором времени при разработке ПО пропадет необходимость подключать статическую библиотеку libusb и прописывать ручками права на устройство в консоли. Вместо этого, начиная с Аврора ОС 5.2, появится возможность запросить разрешения на интерфейс в ОС после соответствующей настройки в проекте.

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


Эту проблему решил пересборкой библиотек статически. Чтобы не пересобирать их каждый раз, сделал пайплайн, который автоматически собирает libusb. В общем виде пайплайн скачивает исходный код библиотеки, конфигурирует проект с указанием «собирать статические библиотеки» и выполняет сборку проекта для каждой необходимой архитектуры. Все архитектуры прописаны в «матрице», переменной arch:

jobs:
 build:
   name: Сборка библиотеки
   runs-on: psdk
   strategy:
     matrix:
       arch: [aarch64, armv7hl]
   steps:
  …
   - name: Сборка для каждой архитектуры
     run: |
       cd libusb
       ${{ env.psdkPath }}/sdk-chroot sb2 -R -t ${{ env.aurora_tag }}-${{ matrix.arch }}.default make
…

Собранный пакет автоматически пушится на следующем этапе в общий публичный репозиторий пакетов и доступен всем по ссылке. При дальнейшей сборке достаточно выбрать нужную библиотеку, стянуть архив и распаковать решение для целевой архитектуры приложения.


Заключение


Так, за пару вечеров с помощью более современных инструментов я дал вторую жизнь устаревшему прибору. Теперь устройством можно снова пользоваться для быстрого анализа и оценки различных показателей. В дальнейшем планирую развить приложение, подключив аналогичный осциллограф и некоторые другие приборы (например, логический анализатор) и собрать вариант приложения для других используемых ОС.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+56
Комментарии2

Публикации

Информация

Сайт
slc.tl
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия
Представитель
Влад Ефименко