Здравствуйте, меня зовут Евгений, и мне надоело писать прошивки для микроконтроллеров. Как это это случилось и что с этим делать, давайте разберемся.


После того как поработаешь в большом программировании С++, Java, Python, и т. Д. Возвращаться к маленьким и пузатым микроконтроллерам совсем не хочется. К их скудным инструментам и библиотекам. Но делать иногда нечего, задачи real-time и автономности, не оставляют выбора. Но есть некоторые типы задач, которые просто выбешивает в этой области решать.


К примеру тестирование оборудования, что-либо более скучного и занудного занятия в embedded программировании, вряд ли можно придумать. Вообщем как и удобных инструментов для этого. Пишешь… Прошиваешь… моргаешь… светодиодиком (иногда логи по UART). Все ручками, без специализированных инструментов для тестирования.


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


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


Для таких экспериментов больше подходит что-то типа REPL, дабы можно было просто и безболезненно делать вот такие, хотя бы банальные, вещи:


\


Как к этому прийти, посвящен этот цикл статей.


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


Проект обещал хорошего тамаду и конкурсы интересные на месяца два так ( а скорее всего и больше).


Ладно, мы же тут не плакаться собрались. Надо либо снова окунуться в дебри С и бесконечных прошивок, либо отказаться либо что-то придумать, что бы облегчить себе это занятие. В конце концов лень да любопытство — двигатель прогресса.


В прошлый раз, когда разбирался с OpenOCD, наткнулся на такой интересный пункт в документации как


http://openocd.org/doc/html/General-Commands.html
15.4 Memory access commands
mdw, mdh, mdb — позволяют считывать значению по физическому адресу на микроконтроллере
mww, mwh, mwb — позволяют записывать по физическому адресу на микроконтроллере

Интересно…. А регистры периферии читать и писать с их помощью можно?.. оказывается можно, да к тому же эти команды можно выполнять удаленно через TCL сервер, который запускается при старте openOCD.


Вот пример моргания светодиодиком для stm32f103C8T6


// Step 1: Enable the clock to PORT B
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

// Step 2: Change PB0's mode to 0x3 (output) and cfg to 0x0 (push-pull)
GPIOC->CRH = GPIO_CRH_MODE13_0 | GPIO_CRH_MODE13_1;

// Step 3: Set PB0 high
GPIOC->BSRR = GPIO_BSRR_BS13;

// Step 4: Reset PB0 low
GPIOC->BSRR = GPIO_BSRR_BR13;

и аналогичный ему последовательность команд openOCD


mww 0x40021018 0x10
mww 0x40011004 0x300000
mww 0x40011010 0x2000
mww 0x40011010 0x20000000

А теперь, если задуматься о вечном и рассмотреть прошивки для МК… то основное предназначение этих программ это запись в регистры чипа; прошивка, которая будет просто что-то делать и работать только с процессорным ядром, не имеет никакого практического применения!


Примечание

Хотя конечно можно и крипту считать(=


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


И так, жизнь налаживается. В исходниках openOCD можно даже найти, интересный пример использования данного интерфейса.


Очень хорошая заготовочка на питоне.


Вполне можно конвертировать адреса регистров из заголовочных файлов, и начать писать на кошерном скриптовом языке. Уже можно готовить шампанское, но мне показалось этого мало, ведь хочется вместо возни с регистрами использовать Standard Peripherals Library или новый HAL для работы с периферией.


Портировать библиотеки на питон … в каком-нибудь страшном сне этим займемся. Значит надо как использовать эти библиотеки в С или … С++. А в плюсах же можно переопределить почти все операторы … для своих классов.


А базовые адреса в заголовочных файлах, подменить на объекты своих классов.


К примеру в файле stm32f10x.h


#define PERIPH_BB_BASE        ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */

Заменить на


class InterceptAddr;
InterceptAddr addr;
#define PERIPH_BB_BASE        (addr) /*!< Peripheral base address in the bit-band region */

Но игры с указателями в библиотеке, рубят на корню эту идею...


Вот к примеру файл stm32f10x_i2c.c :


FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG)
{
__IO uint32_t i2creg = 0, i2cxbase = 0;
    ….
  /* Get the I2Cx peripheral base address */
  i2cxbase = (uint32_t)I2Cx;
….

Значит надо как-то по другому перехватывать обращения к адресам. Как это делать наверно стоит посмотреть у Valgrind, не зря у него есть memchecker. Уж он то точно должен знать как перехватывать обращения по адресам.


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


Int * p = ...
*p = 0x123;

Перехватить адрес есть возможность, а вот перехватить записываемые данные уже не получалось. Только название внутреннего регистра в котором это значение лежит, но до которого не добраться из memcheck.


На самом деле Valgrind удивил меня, внутри используется древний монстр libVEX, о котором я вообще не нашел никакой информации в интернете. Хорошо что немного документации удалось найти в заголовочных файлах.


Потом были другие инструменты DBI.


Frida, Dynamic RIO, еще какой-то, и наконец попался Pintool.


У PinTool оказалась неплохая документация и примеры. Хотя мне их все равно не хватило, и с некоторыми вещами пришлось делать эксперименты. Инструмент оказался очень мощный, единственно огорчает закрытый код и ограничение только платформой intel (хотя в дальнейшем это можно будет обойти)


Итак, нам нужно перехватывать запись и чтение по определенным адресам. Посмотрим какие инструкции отвечают за это https://godbolt.org/z/nJS9ci.


Для х64 это будет MOV для обоих операций.


А для х86 это будет MOV для записи и MOVZ для чтения.


Примечание: лучше всего не включать оптимизацию, иначе могут повылазить другие инструкции.


Пишем перехватчики для этих инструкций
  INS_AddInstrumentFunction(EmulateLoad, 0);
  INS_AddInstrumentFunction(EmulateStore, 0);

.....

static VOID EmulateLoad(INS ins, VOID *v) {
  // Find the instructions that move a value from memory to a register
  if ((INS_Opcode(ins) == XED_ICLASS_MOV ||
       INS_Opcode(ins) == XED_ICLASS_MOVZX) &&
      INS_IsMemoryRead(ins) && INS_OperandIsReg(ins, 0) &&
      INS_OperandIsMemory(ins, 1)) {
    INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(loadAddr2Reg),
                   IARG_MEMORYREAD_EA, IARG_MEMORYREAD_SIZE, IARG_RETURN_REGS,
                   INS_OperandReg(ins, 0), IARG_END);

    // Delete the instruction
    INS_Delete(ins);
  }
}

static VOID EmulateStore(INS ins, VOID *v) {
  if (INS_Opcode(ins) == XED_ICLASS_MOV && INS_IsMemoryWrite(ins) &&
      INS_OperandIsMemory(ins, 0)) {
    if (INS_hasKnownMemorySize(ins)) {
      if (INS_OperandIsReg(ins, 1)) {
        INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(multiMemAccessStore),
                       IARG_MULTI_MEMORYACCESS_EA, IARG_REG_VALUE,
                       INS_OperandReg(ins, 1), IARG_END);
      } else if (INS_OperandIsImmediate(ins, 1)) {
        INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)multiMemAccessStore,
                       IARG_MULTI_MEMORYACCESS_EA, IARG_UINT64,
                       INS_OperandImmediate(ins, 1), IARG_END);
      }
    } else {
      if (INS_OperandIsReg(ins, 1)) {
        INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(storeReg2Addr),
                       IARG_MEMORYWRITE_EA, IARG_REG_VALUE,
                       INS_OperandReg(ins, 1), IARG_MEMORYWRITE_SIZE, IARG_END);
      } else if (INS_OperandIsImmediate(ins, 1)) {
        INS_InsertCall(ins, IPOINT_BEFORE, AFUNPTR(storeReg2Addr),
                       IARG_MEMORYWRITE_EA, IARG_UINT64,
                       INS_OperandImmediate(ins, 1), IARG_UINT32,
                       IARG_MEMORYWRITE_SIZE, IARG_END);
      }
    }
  }
}

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


С записью все сложнее… аргументы могут быть разных типов и к тому же передаваться по разному, поэтому приходится перед командой вызывать разные ф-ции. На 32-битной платформе multiMemAccessStore, а на 64 будет вызываться storeReg2Addr. Причем здесь инструкцию из конвеера не удаляем. Удалить проблем её нет, но вот сымитировать её действие в некоторых случаях не получается. Программа почему-то иногда валится в sigfault. Для нас это не критично, пусть себе пишет, главное что есть возможность перехвата аргументов.


Дальше надо посмотреть, а какие адреса нам надо перехватывать, посмотрим на Memory Map для нашего чипа stm32f103C8T6:



Нас интересуют адреса с SRAM и PERIPH_BASE, т.е с 0x20000000 по 0x20000000 + 128*1024 и с 0x40000000 по 0x40030000. Отлично, вернее не совсем, как помним инструкцию записи мы удалить не смогли. Поэтому запись по этим адресам будет вываливаться в sigfault. К тому же есть неиллюзорная вероятность того что на эти адреса будет приходится данные нашей программы, не у этого чипа так у другого. Поэтому однозначно надо их куда-то отремапить. Допустим на какой нибудь массив.


Создаем массивы нужного размера, и дальше их указатели подставляем в дефайны базовых адресов.


В нашей программе, в заголовчниках вместо


#define SRAM_BASE             ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */

Делаем


 #define SRAM_BASE             ((AddrType)pAddrSRAM)
 #define PERIPH_BASE           ((AddrType)pAddrPERIPH)

и где pAddrSRAM и pAddrPERIPH указатели на заранее выделенные массивы.


Теперь нашему PinTool клиенту надо как-то передать как мы отремапили необходимые адреса.
Самое простое что мне показалось, как сделать это перехват ф-ции, которая возвращает структуру массив с такого формата:


typedef struct
{
 addr_t start_addr; //адрес массива куда ремапятся нужные адреса
 addr_t end_addr;   //размер этого массива
 addr_t reference_addr; // отремапленные адрес
} memoryTranslate;

К примеру для нашего чипа это будет так заполняться


    map->start_addr = (addr_t)pAddrSRAM;
    map->end_addr = 96*1024;
    map->reference_addr = (addr_t)0x20000000U;

Перехватить ф-цию и взять из нее требуемые значения не составляет большого труда:


Перехватить ф-цию и взять из нее требуемые значения не составляет большого труда:
IMG_AddInstrumentFunction(ImageReplace, 0);
....

static memoryTranslate *replaceMemoryMapFun(CONTEXT *context,
                                            AFUNPTR orgFuncptr,
                                            sizeMemoryTranslate_t *size) {
  PIN_CallApplicationFunction(context, PIN_ThreadId(), CALLINGSTD_DEFAULT,
                              orgFuncptr, NULL, PIN_PARG(memoryTranslate *),
                              &addrMap, PIN_PARG(sizeMemoryTranslate_t *), size,
                              PIN_PARG_END());

  sizeMap = *size;

  return addrMap;
}

static VOID ImageReplace(IMG img, VOID *v) {
  RTN freeRtn = RTN_FindByName(img, NAME_MEMORY_MAP_FUNCTION);
  if (RTN_Valid(freeRtn)) {
    PROTO proto_free =
        PROTO_Allocate(PIN_PARG(memoryTranslate *), CALLINGSTD_DEFAULT,
                       NAME_MEMORY_MAP_FUNCTION,
                       PIN_PARG(sizeMemoryTranslate_t *), PIN_PARG_END());

    RTN_ReplaceSignature(freeRtn, AFUNPTR(replaceMemoryMapFun), IARG_PROTOTYPE,
                         proto_free, IARG_CONTEXT, IARG_ORIG_FUNCPTR,
                         IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_END);
  }
}

И нашу перехватываемую ф-цию сделать такого вида:
memoryTranslate * getMemoryMap(sizeMemoryTranslate_t * size){
...
return memoryMap;
}

Что же самая нетривиальная работа сделана, осталось сделать клиента к OpenOCD, в PinTool клиенте мне не хотелось его реализовать, поэтому я делал отдельным приложением, с которым наш PinTool клиент общается через named fifo.


Таким образом схема интерфейсов и коммуникаций получается такая:



А упрощенный workflow работы на примере перехвата адреса 0х123:



Давайте разберемся по порядку что же здесь происходит:


  1. запускается PinTool клиент, делает инициализацию наших перехватчиков, запускает программу
  2. Программа запускается, ей нужно отремапить адреса регистров на какой-нить массив, вызывается ф-ция getMemoryMap, которую перехватывает наш PinTool. Для примера один из регистров отрепамился на адрес 0х123, его будем отслеживать
  3. PinTool клиент сохраняет значения отремапленных адресов
  4. Передает управление обратно нашей программе
  5. Дальше где-то происходит запись по нашему отслеживаемому адресу 0x123. Ф-ция storeReg2Addr отслеживает это
  6. И передает запрос на запись в OpenOCD клиент
  7. Client возвращает ответ, тот парсится. Если все нормально, то возращается управление программе
  8. Дальше где-то в программе происходит чтение по отслеживаемому адресу 0x123.
  9. loadAddr2Reg отслеживает это и посылает запрос OpenOCD клиенту.
  10. OpenOCD клиент обрабатывает его и возвращает ответ
  11. Если все нормально, но в программу возвращается значение из регистра МК
  12. Программа продолжается.

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

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


  1. Amomum
    20.12.2018 12:35

    Ох какая классная наркомания! Круто!

    По-моему, более «традиционный» подход к такому (который исповедуют всякие микропитоны и эспруино) — это интерпретатор на самом проце, с написанием кода в терминале. Не берусь судить, какой подход проще или лучше.


    1. ser-mk Автор
      20.12.2018 13:13

      Можно и так, но тут считай сравнивать теплое с мягким.
      Потому как предназначения разные. Виртуальные машины / интерпретаторы на МК, могут исполнять скрипты автономно и вполне могут использоваться в продакшене. Но там мы упираемся в ограничение ресурсов МК, необходимость портирования вирутальной машины / интерпретатора, а так же еще написание библиотек переферии.
      В моем подходе программы/скрипты не могут исполняться автономно, но нам доступны все ресурсы нашего хостового ПК, все виды библиотек и все виды языков программирования. При этом при использовании С/С++ можно использовать стандартные библиотеки от производителя для работы с переферией.


      1. NetBUG
        20.12.2018 17:02

        … а также в надёжность, производительность, объём и соответствие этих библиотек/интерпретаторов условиям задачи (лицензия, железо)


    1. NetBUG
      21.12.2018 20:07

      ПРошу прошения, не в ту ветку.


  1. xfaetas
    20.12.2018 13:20

    А есть инструменты, в которых можно сразу такой workflow рисовать и сразу в C компилировать?


    1. ser-mk Автор
      20.12.2018 13:21

      Вот именно такой нет, для чего-нибудь попроще есть xod.io


  1. avf1906
    20.12.2018 13:49

    Абсолютно противоположное впечатление: «После того как поработаешь в большом программировании С++, Java, Python, и т. Д с микроконтроллерами, возвращаться к маленьким и пузатым микроконтроллерам к большому программированию С++, Java, Python совсем не хочется. К их скудным монстроузным инструментам и библиотекам. Но делать иногда нечего, задачи real-time и автономности UI, хранения и обработки данных, не оставляют выбора.»


    1. Amomum
      20.12.2018 14:03
      +2

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

      Все мы страдаем от несовершенства мира в своем углу.


      1. avf1906
        20.12.2018 20:44

        Скорее от недостатка понимания и/или отсутствия мотивации (неинтересно). А за статью спасибо, интересно, и, главное, применимо


      1. Vadimatorikda
        21.12.2018 08:23

        невозможности использовать динамическую память

        Запрет на предприятии? Если так, то да, больно. Если нет, то при использовании FreeRTOS все более чем шикарно. Работа с динамической памятью (при достаточном выделении ресурсов под ее дефрагментацию и прочее) очень удобна.
        кривой поддержки хотя бы С++11

        GCC адекватно может в C++14 (около года пишу так).
        от безумных аппаратных косяков в процах

        В самих ядрах косяков либо нет совсем, либо их не много. А вот периферия… Да.
        ошибок монтажа…

        С опытом такие вещи достаточно быстро раскрываются. Особенно если монтаж осуществлял не ты сам.
        Лично мне нравится под МК программировать. Как только ты доходишь до понимания того, что твоего кода 5-10 кб, а остальное библиотеки и ты закидываешь их в отдельное место и больше почти не шьешь, то прошивка занимает не более 2-3 секунд. Очень удобно. А остальные 200 кб BSP лежат себе тихонько в конце flash и все. Как-нибудь напишу статью на эту тему. Как организовать это правильно. Есть подводные камни.
        За статью спасибо. Приятно было почитать. Однако именно так я врят ли буду вопрос. Все же я когда-то давно написал универсальное средство вывода и отладки (да-да, на базе UART-а. Вернее USB-UART-а, но ни суть. Надо кстати задокументировать будет и описать...). Его мне хватает с головой.


        1. eternalego
          21.12.2018 11:19

          GCC безусловно может C++14, но, позвольте, всегда ли доступна соответствующая версия компилятора для определённой платформы? Лет пять назад, когда цивилизованный мир давно уже перелез на С++11 и линукс 3.х, приходилось страдать с ядром 2.6.х и дремучим GCC для одной из вариаций ARM, на поддержку которой производитель (Texas Instruments) забил.


          1. esaulenka
            21.12.2018 13:55

            А на что именно так крепко забил TI?
            И что, поддержка этого ядра исчезла из транка gcc?
            Или просто их Code Composer старый? Не велика беда, мне кажется…


            1. eternalego
              21.12.2018 14:28

              Их композером вообще не пользовался. Работа велась с DM3730, и последняя сборка под эту платформу была в конце 11-го года: www.ti.com/tool/linuxdvsdk-dm37x
              В ядрах третьей версии поддержки нужного чипа я тогда не нашёл, а делать порт самостоятельно было не в моих силах. Со сборкой более новых версий gcc для кросс-компилирования под эту платформу тоже были какие-то проблемы, сейчас уже не вспомню, какие.


        1. Amomum
          21.12.2018 12:20
          +1

          Запрет на предприятии? Если так, то да, больно. Если нет, то при использовании FreeRTOS все более чем шикарно. Работа с динамической памятью (при достаточном выделении ресурсов под ее дефрагментацию и прочее) очень удобна.

          Ну, прямого запрета нет, только рекомендации MISRA :) Но с фриртосом действительно удобно, хотя бы heap_1 можно себе позволить и стеки руками не создавать.

          В самих ядрах косяков либо нет совсем, либо их не много. А вот периферия… Да.

          Кхе-кхе… Миландр 1986ВЕ1… Кхе-кхе…
          Но да, ошибки в периферии существенно чаще попадаются.

          С опытом такие вещи достаточно быстро раскрываются. Особенно если монтаж осуществлял не ты сам.

          Ну, понятное дело, что раскрываются, просто каждый раз тратится уйма времени на поиски этих ошибок. А переделать печатную плату — это дело нескольких недель; пока оплата пройдет, пока ее изготовят, пока доставят…

          GCC адекватно может в C++14 (около года пишу так).

          Keil, к сожалению, только частично умеет в С++11, причем стандартная библиотека осталась от С++98. А их новый компилятор armclang пока сыроват.


        1. IRainman
          23.12.2018 18:36

          Ну, я просто оставлю это здесь.

          Страдаю с Analog Device SC-589 based платой. Там нет даже С++11 и попытка его включить на том говне мамонта той версии GCC приводит к внутренней ошибке линкера ибо линкер древний и забагованный в этом месте, а обновлять это никто не будет. Шёл 20189 год, а мне приходиться писать на C++03 под эту жопуелезку. И вот это дно происходит на железке которая даже не микроконтроллер, а трёх ядерная система ARM + 2 DSP с 2 ГБ ОЗУ. Всё очень плохо в этом королевстве :'(


      1. slonegd
        21.12.2018 12:55

        arm-none-eabi-gcc поддерживает последний 17 стандарт в полной мере.


    1. DrGluck07
      21.12.2018 16:50

      Вот да, с какой радостью я недавно клепал бутлоадер на xmega. Сидишь себе, пишешь, отлаживаешь, красота. Единственная проблема, мне надо было впихнуть 13кБ программы и библиотек в 8кБ бутлоадера. И от этого я получил ещё большее удовольствие. На ПК такое испытать можно, но только если делать что-то в ядре или типа того.


  1. drblez
    20.12.2018 14:30

    Форт. мне кажется, для микроконтроллеров с достаточным объемом ОЗУ (от 8Кб, скажем) и достаточным объемом FLASH (от 64Кб, скажем) форт будет хорошим решением для того, что бы сделать хоть прототип, хоть готовое приложение ))
    А еще можно делать кровавый патчинг через терминал, тоже очень мило ))

    Давеча попробовал mecrisp.sourceforge.net и остался доволен результатами. Как раз надо было сопрячь железку со странным протоколом с МК. Получилось хорошо и быстро.
    Прототип сделал. Теперь думаю, что может его не перекладывать на Си, а продолжать писать на форте.

    В этой, конкретной, реализации очень удобно разрабатывать тем, что можно слова создавать в ОЗУ, а потом пересоздавать их во FLASH. А если есть возможность подключить SD, то набросав редактор можно полностью вести процесс разработки на МК.


    1. Rigidus
      20.12.2018 16:57

      Очень бы хотелось развернутую статью об этом опыте ( mecrisp )


    1. FForth
      21.12.2018 01:34

      Mecrisp stellaris unoficial doc
      hightechdoc.net/mecrisp-stellaris/_build/html/index.html

      Ещё можно этого автора почитать и его директорию проекта с Форт кодом на Github
      jeelabs.org/2016/02/dive-into-forth

      P.S. Экспериментировал, в частности, с www.mpeforth.com/xc7.htm


    1. esaulenka
      21.12.2018 13:49
      +1

      Голову вывернуть полностью надо с этим вашим фортом…
      Когда-то давно коллега в «толстый» STM32 (тогда «толстым» был STM32F4) запихивал Lua.


  1. apro
    20.12.2018 14:59

    А почему просто не воспользоваться возможностями которые идут из коробки?
    С кросс-компилятором идет gdb, openocd умеет работать как gdb сервер,
    подключаемся gdb к openocd и вот у нас repl, gdb умеет и скрипты запускать,
    и интерпретироваться подмножество языка С, типа *(int *)0x4 = 5;,
    и можно даже вызывать отдельные функции из загруженного кода в память МК.
    Ну и скрипты на python он поддерживает.


    1. ser-mk Автор
      20.12.2018 15:28

      Хорошее замечание. Тоже об этом думал. Но давайе по порядку…
      У GDB нет REPL, это скорее консоль для исполнения уже встроенных команд и откомпилированных команд программы. Может интерпретровать простые констуркции С но и только.
      Проблематично вызывать ф-ции из программы, а допустим иницилизировать структуры и классы и передавать их в аргументы это будет жуткая боль.
      Скрипты и расширения на python отчасти спасают, но всеравно читаемость и масштабируемость будет хромать.
      Во второй части будет наглядный пример, как все довольно просто работать будет. В этой статье мы всего лишь готовим инструмент для этого.


  1. roboqueer
    20.12.2018 17:36

    BSDL? Не, не слышал!


    1. ser-mk Автор
      20.12.2018 17:38

      Я тоже не слышал. Может раскроете свою мысль?


      1. madprogrammer
        20.12.2018 18:05

        Boundary Scan Description Language — с помощью такого файла-описания чипа и JTAG Boundary Scan (который как раз был в первую очередь придуман с целью упрощения тестирования плат с большим количеством взаимосвязанных чипов, а не для отладки/прошивки чипов, как многие думают), можно дрыгнуть любой ногой любого чипа, «сидящего на JTAG-chain, считать состояние любого пина и т.д., и все это без знания регистров и внутреннего устройства чипа (т.к. JTAG Boundary Scan-функционал реализован независимо от остальных частей кристалла, включая CPU). Минусом технологии является низкая скорость, т.к. данные последовательно загоняются с первый чип, потом во второй, третий и т.д., и потом возвращаются обратно в JTAG-железку, через которую идет управление.


        1. ser-mk Автор
          20.12.2018 18:35

          Могу ошибаться, но еще наверно стоит к минусам отнести:
          то что у микросхемы должен быть обязательно JTAG интерфейс — на той тестируемой плате это не прокатывало, так как там была единственная stm32 и у нее был выведен SWD только
          стоимость такого решения — было бы интересно узнать у людей кто в теме
          Ну и BSDL решает только вопрос тестирования соединений микросхем ( на сколько я понял )
          Допустим более сложные тесты уже не провернуть — к примеру проверить работу трансформатора на определенной частоте, корректность работы микросхем (на случай их брака)


  1. r44083
    20.12.2018 18:07

    По-моему автор просто перестал ценить ту изюминку и детерминированность встраиваемых систем. Это целый мир. Бывает. Со временем само проходит.


    1. ser-mk Автор
      20.12.2018 20:41
      -1

      Все относительно. Сейчас могут набежать, те кто топит за функциональное программирование, кто за хайповые golang и Котлин и закидать вас монадами, коурутинами и прочими плюшками.
      Под каждую задачу свой инструмент. И что-то громоздкое городить на бедном мк, не всегда рационально.
      P.s. не холивара ради.


  1. yetanotherman
    21.12.2018 02:52

    У меня проект был попроще, электроника скорее моё хобби, и я наоборот решил попробовать применить приёмы из «большого» программирования прямо «в лоб». Сначала жестко разделил в коде логику и физику, хотя казалось, что логики было намного меньше — по факту оказалось наоборот. Логику тестировал прямо на x86, написал на неё юнит-тесты. Потом, написал что-то типа интеграционных тестов, но без физики — gpio и работа с uart/spi и прочим io — пробрасывалась программно на код, который работал с «той стороны» чипа (то есть на хостовой системе или в соседнем чипе). Это тоже гонялось какое-то время на x86, пока я доделывал устройство. Последним шагом создал тестовый стенд из готового устройства и raspberry pi, который своими gpio зацеплен за тест-поинты и может шить чипы устройства через бутлоадер по uart. На нем бегает агент от гитлаба, который по изменению кода в репозитории шьет новую версию и прогоняет ранее написанные тесты уже на железе с полноценной интеграцией всех компонент (тесты и код хостовой системы бегают на малине, в чипах прямо продакшн-прошивка без каких-либо тестовых инструментов живёт). Плюс к этому есть агент на x86, который гоняет ранее написанные юниты, дабы лишний раз не насиловать флеш контроллера, когда я делаю глупую ошибку.

    Может конечно будь у меня OpenOCD я бы пошел по пути близкому к вашему, но так через test driven development и автоматизацию деплоя удалось упростить большую часть экспериментов с освоением периферии чипа, бонусом получив дополнительное покрытие тестами.

    Пока у меня не до конца закрыт вопрос с аналоговым трактом, но его для успешного завершения проекта всё равно решать, лень мне руками несколько десятков модулей тестировать, поэтому, видимо, стенд обрастёт парочкой программируемых источников напряжения для генерации тестовых сигналов (хотя может хватит фильтрованного ШИМа с малины опять же).


  1. truebest
    21.12.2018 06:16

    Крик души.
    Как же меня достали эти микроконтроллеры. Я больше так жить не хочу. Я устал. Это супергеморой. Я устал от того количества проводов, и места, которое занимает устройство на столе. От того что иногда приходится подготавливаться, припаиваться, чтобы посмотреть события на шине, чтобы отладить периферию. Инструменты отладки и программирования есть разные, нужно разбираться в различных программаторах, методах, способах, и чего-там нового напридумывали разработчики этого софта. Самый ужасное это программирование периферии. Помимо хороших знаний схемотехники, конкретной схемы, особенностей, нужно вкуривать дотащит. Если у вас 5-10 различных периферийных устройств на разных шинах, в среднем вам нужно досконально знать и понимать как это устройство лучше использовать. Средний даташит до 100 страниц (ADE7878A), потом нужно написать под него LL драйвер, если у вас RTOS, на одной шине, реализовать атомарный доступ, драйвер ос по сути. Читать Datasheet, App note, Reference Manuals, Programming Manuals и понимать как лучше реализовывать те или иные фишки. И это только для конкретного проца или серии. Хороший проект, и вы прочитаете > 1000 страниц.
    Если вы более-менее сложную электронику разрабатываете, вам нужна техника, которую нужно переодически обновлять. За этот год только купил: анализатор спектра, осциллограф, лаб БП. Есть хороший мультиметер, анализатор логический. А мне еще генератор сигналов векторный нужен. Периодически RF занимаюсь. До кучи вы должны быть электронщиком-схемотехником.
    Прошивки могут быть достаточно большими, например текущий проект: 20к строк бутлоадер + 80к строк основная. Это без либ конечно. Недавно ступил и не поставил скобочки в условии у функции, как итог, прошивка неожиданно падала, а компилятор молчал. Или решил обновить freertos на свежий, поставил скомпилировал и устройство стало падать раз в день. От чего? Как найти? Вернул в зад. Отладка сложная, невозможна без таких подходов, как у автора например. В свое время создал для себя UART логгер на SD, тк устройство не для работы на столе предназначалась.
    Единственный критерий исправной прошивки — работа устройства без всяких ватчдогов в течении недели/месяца. Это значит что код должен быть качественным.
    Софт: Нормальный софт платный, я пользуюсь Visual Studio + VisualGDB + ReSharper. Две последних позиции более 10к в год. Не понимаю людей как можно пользоваться Keil, IAR, CooCox, Eclipse и тд. Пользовался всем, ужасный софт.
    Документация: Все нужно документировать, иначе никто кроме тебя не разберет, особенно те места, где пересекаются границы программистов.
    Иногда требуется софт на ПК для настройки, или сам пишешь если нет документации или программиста. Лучше делать его кроссплатформенный.

    Зарплата: разРаб на stm32 — 100к в СПБ, ну и чуть больше в МСК, а в регионах… Знать нужно ВООООО, опыта — ВООООО лет от 5-ти.
    Для меня большое счастье, когда нужно писать например на Go или Java. А SQL — это кайф. А mongoDB — еще больший кайф. А знаешь Java можно сказать что знаешь Android, несмотря на то что нужно в тренде быть, знать что нового в ОС напридумывали и как это правильно в коде реализовать.

    Все, кто ко мне приходят с целью узнать, как быть разрабом МК, отговариваю, показываю этот супергеморой, показываю ЗП на HH, говорю что лучше быть Android программистом. Go, Java, Kotlin.
    Один мои приятель, с появлением второй дочери, в регионе, денег перестало хватать, бросил МК, и ушел в Go. Можно быстро переучиться с C/C++. Спустя год зарабатывает хорошие деньги, и гораздо счастлив.
    Другие мои знакомые, кто хоть раз писали прошивки, сразу поняв что это геморой, ушли в системное C++/C# и БД.

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


    1. vvzvlad
      21.12.2018 10:25

      Угу-угу, все так, программировать можно и в более комфортных условиях. Только вот ни одна из этих областей не дает то самое чувство, когда берешь в руки железку, которую сделал и запрограммировал ты, и она работает. Когда видишь, что твоя работа — не просто биты в памяти и пиксели на экране.


      1. truebest
        21.12.2018 13:18

        Согласен, меня самого это и привлекает, когда бездушная железка начинает «дышать».
        Но с опытом, считаю железка это проблемы — ее нужно произвести, ее нужно продать. Иногда нужны компоненты, которые нужно еще и растаможить.
        Софт легко поправить и выслать новую версию, с железом так не получится, будешь рекламации собирать от заказчиков, и ответственность выше, потому что железка должна работать надежно.


    1. eternalego
      21.12.2018 10:52
      +2

      До кучи ещё можно вспомнить ПЛИС и VHDL, тоже зона отладочного кошмара. Четыре года назад ушёл с микроконтроллеров и микропроцессоров на более высокоуровневое программирование, и не жалею. Тоже конечно не везде можно нормально отлаживаться, не для всех языков и не для всех окружений удаётся построить удобную инфраструктуру. В общем, на самом деле всё это, конечно, вопрос в основном опыта и личных пристрастий: чем их больше в какой-то области, там и хорошо :)


      1. truebest
        21.12.2018 13:22

        В ПЛИС не имею большого опыта, пару раз с циклоном 4 в картусе поигрался, сделал аппаратный вывод на VGA монитор рядом параметром, еще что там много, но ничего серьезного. Даже понравилось. Но тут хотя-бы симулятор есть полноценный, как у xilinx так и alterra, можно побочно отлаживаться.


    1. Mih-mih
      21.12.2018 10:52

      Ваш пост читается как «разработка встраиваемых систем — это очень интересно, но не для всех» :). Настоящее удовольствие приходит тогда, когда ты еще и разработчик собственно железки, которую отом оживляешь. А самое-самое удовольствие, когда в проекте есть место и для легкого безумияПЛИС, программу/не программу для которой тоже пишешь ты.
      Полное слияние железа и софта для него.
      Ну да, это должно нравиться, иначе боль, тоска и в лучшем случае уход в чистые программисты. С зарплатами увы, есть такая проблема, которая усугубляется еще и тем, что работа начинает «держать». Уходить с нее, собственно, некуда, только на другую такую же.


      1. Mogwaika
        21.12.2018 10:54

        Так, на какой язык проще переучиваться после верилога?


        1. Mih-mih
          21.12.2018 11:20

          На VHDL ). Языки описания аппаратуры слишком сильно отличаются от языков программирования. Другой способ мышления, если хотите.


          1. Mogwaika
            21.12.2018 11:28

            А с нуля тогда что лучше изучать?


            1. Mih-mih
              21.12.2018 11:49

              Речь про HDL-языки? Вряд ли можно сказать, что «лучше». По ощущениям, на верилоге пишет бОльшее количество народа. С другой стороны, VHDL более структурированный, что ли, плюс есть некоторые возможности, которых нет у верилога. С третьей стороны, есть еще и SystemVerilog, с неимоверно более богатыми возможностями в плане верификации. С вменяемой литературой вот только по нему плоховато.
              По остальным языкам, впрочем, тоже. В свое время мне ветка по верилогу на forum.ixbt.com помогла больше, чем все имеющиеся на тот момент книги.


              1. truebest
                21.12.2018 13:26

                Я для себя их так классифицировал, они похож на Си другой на Паскаль. Мне паскаль с университета не нравился своими бесполезными begin и end.


                1. Mih-mih
                  21.12.2018 13:49

                  Вот только на Паскаль больше похож многословный VHDL, в то время как верилог с его «begin...end» явно С-подобный )


                  1. truebest
                    21.12.2018 14:31

                    Память уже подводит))


              1. Mogwaika
                21.12.2018 13:52

                Понятное дело, про не HDL-языки, выше же обсудили, что в среднем больше за них платят…


      1. Misaka10032
        21.12.2018 11:27

        «разработка встраиваемых систем — это очень интересно, но не для всех»

        Подписываюсь под каждым словом.
        И под каждым словом автора комментария в начале ветки (кроме SQL и Java, не приходилось разрабатывать на них).
        Уже несчётное число раз думал уйти нафиг из embedded. Но кайф от разработки железа, пайки, отладки удерживает. Плюс ЗП вроде вменяемая.
        Надеюсь, что всё же к 30 годам гормоны улягутся и я таки уйду в чистого программиста, ибо ситуация с соотношением ЗП\навыки в эмбеде и правда удручает.


        1. truebest
          21.12.2018 13:39

          К 30 годам станет только хуже, и желание и речь про что-то изменить сменится на какой в этом смысл.
          А с кайфом по разработке железа, нужно понять какого он типа — если это доминантность (делаешь то что другие не могут или что-то уникальное что никто другой не делал), я всегда вспоминаю профессоров в институте, некоторые из них академические ветки придумали, но при этом ходят в протертых штанах, получают копейки, и за это держаться, когда зав.каф. уже давно используют по полной индексы хирша, гранты, конференции и тд в том числе для прибыли.
          Наверное, в определенное время они не готовы меняться, что против логики жизни, и остаются делать то, что редко кому нужно.
          Это не значит что нужно талант на деньги менять, это значит что свое реальное время жизни надо менять на реальные ресурсы.


          1. Mih-mih
            21.12.2018 13:47

            А удовольствие от работы — это реальный ресурс? Или же реальный ресурс это всегда кэш? И как тогда быть с вещами, на которые тратишь свое реальное время и реальный кэш, взамен получая удовольствие?
            Цель/смысл жизни это штука такая… У всех разная.


            1. truebest
              21.12.2018 13:57

              Я считаю так, если ты настоящий программист, ты можешь писать на любом языке. Это подтверждают мои некоторые друзья, которые иногда шли устраиваться на backend, но собеседование было по frontend, их брали на работу, за месяц они вникали в тему, и начинали работу над проектом. И им по кайфу это стало делать.
              А все железо, они на хобби оставили, коптеры собирают, ну и что там AlexGyver делает на youtube. Просто не нужно бояться перемен.


              1. Mogwaika
                21.12.2018 14:02

                Тут вот обсудили, что с hdl на не-hdl сложно перескакивать или с асм-а на ооп…


              1. Mih-mih
                21.12.2018 14:08

                А что делать, если не «программист», а «инженер» (да не будет это никому в обиду сказано)? :) И тем более, если с хоббями (совершенно другими) и так нет никаких проблем? :)
                Просто вот для Вас, если я правильно понимаю, «эмбеддед»===«программированию контроллеров». Написание программ для них, не больше и не меньше. Работа с собственно самой железкой скорее неприятное дополнение, обязанность, нежели чем сколь-нибудь интересный процесс. Ещё и деньги, фиктически, отнимает. В таком случае на этом «эмбеддед» сидеть нет никакого смысла, это верно.


                1. truebest
                  21.12.2018 14:28

                  Я скорее сам инженер. Для меня нормально это и схемотехника и программирование.
                  Embedded я скорее называю разработка устройства, с процом и ОЗУ, где есть ОС, более менее полноценная типа Linux. Это сложная схема и плата, uboot, это настройка ядра, buildroot, это кросс-компиляция, ну и дальше, какой-либо твой софт.
                  Все что типа at91sam, stm32, nxp и тд. это микроконтроллеры, там интересные схемотехнические решение, и крайне сложный софт. Это иногда разработка DC/DC (тут недавно статья была от Nordic), иногда и трансформатор помотать руками приходится. Это про то как и чем bulk от boost отличаются, и что такое Sepic. А прошивка — вдохнуть жизнь в плату.

                  Я просто хочу сказать, что на берегу, где есть backend/frontend есть жизнь, она более интересная, более нужная людям и как результат более высокооплачиваемая. А драгоценное время жизни, вы можете потратить на родных и близки, на то, что действительно важно!


          1. Misaka10032
            21.12.2018 13:48

            ходят в протертых штанах

            Посмотрел на свои ботинки, в которых вместо замка — пластиковые стяжки для проводов…
            Чёрт, пора идти за новыми. Мне просто лень на другой конец города ехать.
            А насчёт разработки железа — в данный момент времени я просто понимаю, что не смогу променять то, чем мне нравится заниматься, на то, от чего меня спать тянет, пусть даже за вдвое большую ЗП.
            Очень надеюсь, что через несколько лет я всё же смогу найти направление в IT вне эмбеда, за которое смогу зацепиться и в которое смогу развиваться.


            1. CrashLogger
              21.12.2018 14:05

              Я вот смотрю в сторону Китая — там сейчас востребована разработка всяких девайсов, и железячники и программеры в цене. Но эта страна на любителя конечно, не всем подойдет.


            1. truebest
              21.12.2018 14:11

              Дело тут не только в ботинках, а скорее в семье и воспитании. И чаще негативном. Меня например в детстве, мамка на базар таскала, купить «серенький» свитерок под горлышко. А мне с утра так это не нравилось, а меня таскали и предлагали, ну вот посмотри, а этот, а тот. В итоге в 18-20 лет я ходил в чем угодно, лишь бы не идти в магазин за вещами. Что сыграло плохую штуку, барышням, которые мне нравились, я был не очень интересен. Помню, как появились деньги, насильно себя заставлял идти в магазин идти и выбирать, мерять, снимать. Сейчас с этим вроде хорошо. Теперь я за вещами в Стамбул езжу, там она дешевле стоит, ну и там вкусная еда еще.
              Я сейчас думаю что здорово быть, красивым (следить за собой, одеваться стильно и в спортзал регулярно ходить).

              Вам наверное по советую сделать большой и сложный проект embedded, значимый для вашей психики. Завершить что-то. Тогда вам захочется чего-то нового.


          1. uldashev
            21.12.2018 16:40

            В политике самый высокий курс обмена времени на блага, в финансах, но никак не в программировании.


    1. Whuthering
      21.12.2018 11:05
      +1

      Как с языка сняли. Именно такие же мысли и ощущения. Сам ушел из embedded в бэкенд, и не жалею.


      1. truebest
        21.12.2018 13:42

        Все кто из embedded ушел, все не жалеют. Из всех моих знакомых это точно. Я сам следующий. Тоже бекэнд нравится, хотя если честно, мне просто нравятся разные языки. А вас, сердечно поздравляю!


    1. truebest
      21.12.2018 15:21
      +1

      Определенно последнее, что мне не понравилось в том чтобы быть таким разработчиком.
      Меня перевели в новый отдел, нас стало 4 человека: схемотехник и три программиста. Знаете сколько было руководителей? 3 человека. На них весели еще обязанности, но главной конечно было драть с нас 3 шкуры. Нужно было делать то что нужно, а не то что ты хочешь, и не всегда этот проект или железка интересна. Конечно, нужно уметь работать над проектом, в независимости от того нравится или не нравится железка, но порой, когда запросов много ты просто превращается в некий обработчик чужих хотелок. А для работы того или иного устройства нужно вложить душу. Я понял, что в современном мире, имея убеждения, которые позволяют кайфовать от того что например железка заморгала как надо, более шустрые ребята будут попросту использовать вас, ваше время, мозги, меняя на ЗП в 65к (3 года назад была такая зп у меня). Таким поведением и взглядами вы заранее обрекаете себя на использование вас другими бабуинами.
      Душу можно вкладывать в свой проект, в свой бизнес, что не посредственно вам эквивалент затрачено частички души, времени и сил. А не пустые поздравления старших коллег, показывающих вашу значимость, признание в коллективе, или эмоции от заработавшей платы, по сути манипулируя вами.

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

      Я уволился, сейчас получается зарабатывать в разы больше, и заниматься своими проектами. Следующий шаг — уйти из программирования под МК и стать разработчиком на высокоуровневых языках. Мк, оборудование останутся только для хобби проектов. Жизнь с годами должна становиться проще.


  1. CrashLogger
    21.12.2018 10:05
    +1

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


    1. Whuthering
      21.12.2018 11:08
      +1

      Лихо вы большую часть современного IT вычеркнули :)
      … системное программирование, высоконагруженные сервисы, графика и геймдев, машинное зрение, обработка больших данных…
      Там хардкорные алгоритмы жизненно необходимы, и тщательная оптимизация процветает во все поля.


    1. truebest
      21.12.2018 13:51
      +1

      Из-за объема, бывало и такое что делили прошивку, и писали в несколько человек.
      Дело еще в том что кому-то проще читать даташиты и описывать LL функции, а кто-то, кто постоянно с абстракциями работает, он может над логикой поработать.
      Как не странно, си отлично поддерживает модульность, функции заглушки и тд, и позволяет работать в команде. Так вот, те, кто работает только с логикой, уже не знают что там внизу, хоть и могут посмотреть.
      «Фреймворк на фреймворке» — у меня так руководитель говорил, хотя на деле, для современных stm32 уже приходится использовать криптолибы, либы USB, и другие.
      Наверное он так говорил, тк он иногда на ассемблере писал под какой-нибудь pic10f200, да и вообще asm много времени посвятил и разным процам.


  1. FanatPHP
    21.12.2018 11:31

    1. PavelOsipov
      22.12.2018 00:49

      А я в минуты дебаг-уныния перечитываю старый пост на RSDN «Путь химика в программирование»: часть 1, часть 2, часть 3.


  1. Ryppka
    21.12.2018 12:16

    А не думали попробовать в qemu эмулировать устройство? А целом вроде на английском сейчас только ленивый не пишет/докладывает про off-target test driven development…


    1. ser-mk Автор
      21.12.2018 13:19

      Вы про тестирование программ на эмуляторе?
      Если так то у меня стояла другая задача.
      Мне требовалось проверять физические устройства/платы на ошибки монтажа, корректность установленных микросхем.
      А так же мне нужен был удобный инструмент для экспериментов и изучения работы с новыми устройствами.
      Поэтому qemu и off-target test driven development мне тут в помощь не будут.


  1. FForth
    21.12.2018 12:28

    Репозиторий Mecrisp Форт кода Jeelabs
    github.com/jeelabs/embello/tree/master/explore/1608-forth


  1. ripandtear
    21.12.2018 16:58
    +1

    Абсолютно похожие мысли. Начинал с программирования МК (STM32, PIC32MX), потом «перетек» в DSP (Analog Devices), сейчас докатился до того, что пишу целиком на ассемблере под DSP-ядро (вместе с оптимизацией), которые крупные вендоры встраивают в свои SoC. Производитель этого DSP-ядра не поставляет C-компилятор. Планирую в ближайшем будущем уходить в С++ или Rust, в свободное время изучаю эти языки, и наконец-то забыть весь этот фулл-таймовый ассемблер как страшный сон.

    Мое личное мнение — большинство психологических неудобств вытекает из того, что при работе с железом, плохо оборудованы рабочие места, и сам рабочий процесс не очень хорошо налажен. Ну вот банальщина — один осциллограф на отдел, две или три платы на 6 и более программистов, все испытательные стенды собраны из веточек из желудей (Вместо разъемов скрутки которые вылетают и т.п.) потому что «Зачем нам нормально все собирать, все равно этот стенд потом разбирать» и так далее. «Электротехника — это наука о контактах» — вот это точное попадание. Можно до посинения отлаживать код, а потом выяснить, что кто-то где-то вместо пайки засунул провод в разъем на стенде, капнул при пайке припоем и замкнул цепь, подал вместо 24V DC 24V AC, примеры можно до бесконечности приводить…

    В общем, если коротко о главном, в меньшинстве пишется сама полезная нагрузка (функционал и т.п.) а в большинстве — борьба со всем сопутствующим.


  1. RobbieKhan
    21.12.2018 18:51

    Белые люди тестируют проекты с помощью JTAG Boundary Scan


    1. ser-mk Автор
      21.12.2018 19:03

      Если вы к ним относитесь, то интересно будет узнать об этой технологии, особенно интересна цена.


      1. truebest
        22.12.2018 02:59

        EEVblog #499 — What is JTAG and Boundary Scan?


        1. ser-mk Автор
          22.12.2018 10:47

          Полно таких красивых видео и сайтов.
          Но нигде почему-то не говорится про цену внедрения.


          1. RobbieKhan
            22.12.2018 12:13

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