Portfolio Обо мне Блог
14 July 2021
STM32 HARDWARE

Сегодня я расскажу вам о таком прекрасном продукте как Cube Monitor от компании ST. Это приложение, которое позволяет визуализировать и изменять переменные в процессе исполнения программы на микроконтроллере. Для визуализации данных можно создавать графические приложения доступные через браузер.

Для начала предлагаю разобраться в том что это такое и как это работает, а потом перейти к практике. ST в своей презентации предлагает вот какую схему:

cubemon_shim

С левой частью схемы все понятно, подключаем STLink к своему устройству, а программатор к ПК. А вот дальше интереснее. Gateway, это пк с подключенным STLink и на который установленно программное обеспечение от ST. Оно разворачивает на пк сервер, к которому может подключиться любое устройства с браузером и на нем станет доступен дашборд. С дашбордом все просто, его можно увидеть запустив просмотрщик из конструктора или из браузера на той же самой машине обратившись к локал хосту, или с другой введя на ней в браузере ip адрес гетвея.

На гетвее я бы остановился по подробнее. На самом деле внутри данного программного пакета находится Node-RED и компания ST по сути просто добавила туда несколько своих нод которые позволяют работать с STLink server а значит и с самим линком. К сожалению, из этого следует, что никакой другой программатор кроме STLink использовать не получится. Однако нет поводов для печали потому что компания ST далеко не единственная кто додумался добавить свои компоненты в Node-RED и для работы с любыми контроллерами с помощью Jlink можно использовать программный пакет FreeMASTER Lite (вообще он только для контроллеров NXP, но никто вам не мешает в меню выбора контроллера, просто выбрать ядро Cortex M не привязанное к конкретному контроллеру). Вероятно, я когда ни будь тоже про него напишу.

Суть Node-RED состоит в том что мы соединяем некоторые ноды между собой, которые общаются перекидыванием json объектов, по каким то событием. Событием может быть приход новых данных в какой, то интерфейс или изменения состояния какого то объекта в gui. Сами же ноды позволяют принимать или передавать данные по каким-то интерфейсам, а также принимать и передавать данные на дашборд. Также система позволяет построить этот самый дашборд. Но хватит рассуждений приступим к делу.

Для начала напишем простенькую программу под STM32 на которой будем тестировать функционал отладчика, просто мигалку светодиодом. Я возьму Nucleo-F411RE вы можете взять любую другую. Пишем следующий код (в main):

uint32_t i,x = 0;
uint32_t delay = 10;
...
  while (1)
  {
      HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
      if (++i == 100) i = 0;
      x = sqrt(i);

      HAL_Delay(delay);
 ...

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

Далее прошиваем контроллер и запускаем CubeMonitor и видим следующее:

cubemon_default

Компания ST позаботилась о нас и сделала простенький пример, который позволит строить графики переменных в реальном времени. Для его запуска необходимо настроить 2 ноды. В первой (под номером 1) необходимо выбрать наш STLink, для этого делаем двойной щелчок по ноде и открывается меню настроек. Там нам предложат добавить новый probe, нажимаем не карандаш справа (везде в этом ПО карандаш значит создание нового элемента) и выбираем там наш программатор. После чего можно будет выбрать протокол связи и частоту работы (у STLink V3, кстати, она в 19 раз выше). После подтверждения над изменяемой нодой пропадет красный квадрат, что значит отсутствие ошибок в настройках нашей ноды.

Вообще у меня нет желания также подробно писывать каждый шаг, если по какой-то причине это вам необходимо то это можно посмотреть тут и тут, я же буду более краток. Далее необходимо настроить ноду под номером 2. Для начала нужно выбрать elf файл в меню Executable, а переменные за которыми мы хотим следить. После чего на данной ноде тоже пропадет красный треугольник. После этого нужно еще выбрать программатор в ноде myProbe_In (которую я к сожалению забыл пронумеровать).

После этого над всеми нодами появятся синии кружки, а значит ошибок негде нет. Нажимаем DEPLOY (3) (это нужно делать после каждого изменения) и DASHBOARD (4) после чего увидим приложение которое будет строить нам графики. На нем отображается все переменные за которыми мы хотели следить - i и корень из i.

defoult_ploter

Далее опишу несколько интересных моментов:

  • При нажатии на название переменной график данной переменной перестанет отображаться, а окно авто масштабируется по высоте под оставшиеся графики. Однако оно подстроится не под видимую часть, а под весь график как бы давно он не строился.
  • Если остановить чтение (нажать на STOP ACQUISITION) то график можно будет масштабировать.
  • Если вы хотите пере прошить контроллер и что-то поменять в коде, то обязательно освободить программатор (остановить чтение), а также после пере сборки измененного кода обязательно в ноде с выбором переменных обновить их адреса (для этого достаточно просто открыть ее настройки и согласится на апдейт). Это нужно потому что адреса переменных могут меняться от сборке к сборке.
  • Числа с плавающей точкой тоже хорошо отображаются.
  • На графике можно отобразить сохраненные данные если нажать IMPORT DATA.
  • В ноде Processing которая на скриншоте выше называется myVariables можно создавать дополнительные переменные которые будут вычисляться по определенной формуле от текущих.
  • Для доступа к дашборду из браузера используйте URL: http://localhost:1880/ui/, кстати сам редактор ноды можно открыть из браузера по адресу: http://localhost:1880/.

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

Для начала было-бы не плохо разобраться с тем как в принципе работать с Node-RED. Этот можно посмотреть тут, или в официальной документации, я же изложу самое важное для понимая.

Любая нода отправляет сообщение по какому-то событию. Это может быть нажатие на кнопку на дашборде или приход пакета по tcp. Важно то, что эти сообщения могут иметь 2 свойства (мне очень не нравится этот термин, но он используется в интерфейсе программы) payload и topic. Мы конечно можем создать любое их количество, но по умолчанию используются только эти. Первый из них - payload содержит в себе полезную нагрузку сообщения, и может быть любым типом. Второй же - topic чаще всего строка. Топик может передать сообщение с одним из этих свойств.

Для минимальной работы системы, просто отрисовки графика, нужно построить следующий поток:

min_flow

Наш поток состоит из 2х веток, верхней и нижней. Первая нужна для отправки данных на STLink server, а вторая на прием. Для того чтобы нода STLink in начала принимать данные с контроллера, сначала нужно передать данные для работы STLink server через ноду STLink out. Она ждет payload содержащий настройки, которые генерируется в блоке variables, которые мы задаем через настройку данного блока, а также topic который может содержать строку read, start и stop. Они нужны для того чтобы считать один набор значений из отладчика, включить непрерывное чтение и прекратить это чтение. Таки образом вместо кнопок мы можем использовать любой стандартный элемент который сможет записать строку в topic (в примере ниже используются кнопки), но настройки для программатора можно создать только в ноде Variables.

Нижний же блок устроен по проще, нода STLink in передает сырые данные (через свойство payload) в ноду Variables, по сути ноду процессинга, в которой по адресам считанных переменных им присваиваются имена. Далее после процессинга json с данными и значениями попадает в ноду Chart, которая отрисовывает график на дашборде, интересные момент заключается в том что если использовать стандартный топик Chart вместо фирменного, то приложение упадет из-за слишком большого количества данных.

В принципе нам может потребоваться еще ровно 2 функции чтобы полноценно взаимодействовать со стандартными нодами, это чтение произвольной переменной и запись. С первым все проще компания ST позаботилась о нас и сделала специальный под поток Single value который позволяет выделить одну переменную после процессинга. Но иногда бывает такое что необходимо преобразовать тип данных, например переменная может иметь значения от 1 до 100, и мы хотим чтобы на дашборде загоралась кнопка если значение больше 80ти. Для этого нужно будет использовать ноду function которая позволит выполнить произвольный код Js при приходе сообщению. В итоге поток будет выглядеть так:

cubemon_fun

А код функции:

if (msg.payload < 80) 
    return { payload: true };
else 
    return { payload: false };

С записью же переменных дела обстоят хуже. ST дает нам ноду (write panel) которая позволит нам писать значения в переменные, но представляет из себя панель, в которую мы можем руками забивать значения. Выглядит это так:

write_panel

А подключить никакие стандартные элементы интерфейса мы не можем. Но есть выход, мы можем с помощью ноды debug узнать что шлет нода write panel в STLink out и сформировать такое-же сообщение. Выглядит это будет так:

cubemon_slider1

Слайдер отправляет значения из определенного диапазона в свойство payload, после чего нода Write оборачивает его в шаблон который мы подсмотрели в write panel, а нода set пишет в свойство topic строку wire т.к. без этого значение не отправится в дебагер. Шаблон внутри ноды Write должен выглядеть так:

{
    "variablelist": [
        {
            "address": "0x20000000",
            "name": "PWM",
            "type": 6,
            "value": "{{payload}}"
        }
        ],
        "accesspoint": 0
}

Вместо {{payload}} будет подставлено значение со слайдера. Однако в этом методе есть проблема. Каждый раз когда адреса переменных будет меняться при пере сборке кода, придется снова их смотреть с помощью дебагера, и прописать их руками в шаблон (а так-же тип). Вот если бы это можно было автоматизировать, и у нас есть для этого средства - функции и под потоки. Создадим под поток который решит все наши проблемы, в параметрах которого мы запишем название переменной и он сам сможет определять адрес данной переменной.

mem_writer

Внутри же под потока создадим всего одну ноду с функцией Js. На самом деле можно было бы обойтись без отдельного под потока, только функцией, но тогда мы не смогли бы указать название переменной точно так-же как это работает в под потоке Single value.

function

Самое интересное происходит внутри ноды функции:

let message_send = {
    "variablelist": [
        {
            "address": "",
            "name": "",
            "type": 0,
            "value": ""
        }
    ],
    "accesspoint": 0
};

if (msg.topic == "start" || msg.topic == "stop" || msg.topic == "read") 
{
    context.set("save_payload", msg.payload);
}

else 
{
    var save_payload = context.get("save_payload");

    if (!save_payload){
        node.error("Not data for wariable !!!");
        return;
    }

    for (var i = 0; i < save_payload.variablelist.length; i++){
        if (save_payload.variablelist[i].name == env.get("variable")) {
            message_send.variablelist[0].address = save_payload.variablelist[i].address;
            message_send.variablelist[0].name = env.get("variable");
            message_send.variablelist[0].type = save_payload.variablelist[i].type;
            message_send.variablelist[0].value = String(msg.payload);
            message_send.accesspoint = save_payload.accesspoint;
            return { payload: message_send, topic: "write" };
        }
    }

    node.error("Wariable \"" + env.get("variable") + "\" not found !!!");
}

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

Если поле контекста пустое это значит что пришло число которое нам нужно записать в переменную, указанную в конфиге ноды.

Данная функция имеет ряд ограничений, например нельзя писать значение до такого как включится плоттер (это свойственно и под потоку Single value), так-же в наш под поток Mem writer можно писать только числа. И обязательно должно быть указанно название переменной. Впрочем никто не запрещает вам его улучшать.

Пример проекта для CubeIDE и потока CubeMonitor можно найти тут.

Интересные ссылки: