Как стать автором
Обновить

Как мы МИК32 «Амур» подружили с Engee

Уровень сложностиПростой
Время на прочтение10 мин
Количество просмотров2.7K

В 2024 году в продаже появился первый российский микроконтроллер с RISC-V архитектурой – МИК32 Амур (К1948ВК018). Наша команда не могла пройти мимо такой новинки, учитывая интерес профессиональной общественности к RISC-V. Мы поучаствовали и в программе раннего доступа к RISC-V на отладочной плате MIK32 Nuke, и в техническом тренинге от АО «Микрон», чтобы в контакте с производителем наладить программирование контроллера кодом, сгенерированным из среды модельно-ориентированного проектирования Engee.

Меня зовут Алексей Евсеев, я инженер Экспоненты, и я хочу поделиться с вами опытом разработки моделей в Engee для МИК32, показать наш типовой workflow, а также осветить некоторые фишки и особенности работы с генератором кода Engee. Надеюсь, материал будет интересен и разработчикам встраиваемого ПО, и специалистам в моделировании.

Общие принципы разработки

В парадигме модельно-ориентированного проектирования (МОП) разработка ведётся над одной обобщенной моделью – «единым источником правды», объединяющего модели конечного устройства, процесса и алгоритма.

В первую очередь, мы упрощаем задачу, «декомпозируем» её на функциональные и архитектурные части, далее итеративно наращиваем нашу модель, тестируя её на каждом шаге. В процесс тестирования мы стараемся включить необходимый минимум – имитационное моделирование и проверку работы кода, полученного из модели, на целевом устройстве. Если все сделать правильно, то автоматическое тестирование кода должно происходить уже при генерации Си кода. Однако здесь наше участие минимально – в Engee часть проверок происходит по одному нажатию на кнопку «Сгенерировать Си код».

При необходимости в процесс тестирования можно включить и верификацию кода в модельном окружении, но на целевом процессоре (PIL-тестирование), и отладку в режиме реального времени с оператором (HIL-тестирование). Последняя, нужно заметить, в Engee доступна (пока что) только для комплекса полунатурного моделирования КПМ РИТМ. Теперь пойдём по порядку выполнения шагов.

Программирование по-русски

(+ немного профессионального жаргона)

Рассмотрим простейшую модель (Рисунок 1): блок Repeating Sequence Stair циклически передаёт заданную последовательность нулей и единиц на выходной порт. Выбранный в модели решатель – дискретный с фиксированным шагом, длительность шага циклического расчёта модели – 100 мс. Результат работы модели – наблюдаемый график изменения переменной signal (Рисунок 2).

Рисунок 1. Модель Engee для циклического формирования нулей и единиц
Рисунок 1. Модель Engee для циклического формирования нулей и единиц
Рисунок 1. Модель Engee для циклического формирования нулей и единиц
Рисунок 2. График циклического формирования нулей и единиц

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

Теперь нужно сгенерировать Си код. Если в собранной модели нет ошибок, то в той же директории файлового браузера (ФБ) Engee, в которой расположена модель, создаётся папка <имя модели>_code (Рисунок 3). В этой папке создаются файлы – шаблон main.c, заголовочный файл <имя модели>.h и файл источника кода <имя модели>.c. Последние два содержат в себе полноценный и независимый код из модели. 

Работу нашей модели описывают три функции

  • инициализация <имя модели>_init(),

  • пошаговый расчёт <имя модели>_step() ,

  • терминация <имя модели>_term().

Их мы и будем вызывать в теле основной программы main.c.

Рисунок 3. Автоматическая генерация кода из модели
Рисунок 3. Автоматическая генерация кода из модели

В модели сейчас нет специфических блоков для целевого устройства, которые сконфигурируют его периферию и передадут в неё нашу последовательность. Следовательно, работу с периферией пока что организуем в виде текста, кодом в файле main.c. Вот пример работы с цифровым выходом (мигающий светодиод), основанный на коде, который дают производители МИК32:

#include "stdint.h"
#include "mik32_hal_pcc.h"
#include "mik32_hal_gpio.h"

const uint32_t tickInMs = 3200; 	// константа числа тактов в миллисекунде

void delay(uint16_t ms);        	// прототип функции задержки

int main()                      	// основная программа
{
  // Настройка подсистемы тактирования и монитора частоты МК
  PCC_InitTypeDef PCC_OscInit = {0};
  PCC_OscInit.OscillatorEnable = PCC_OSCILLATORTYPE_ALL;
  PCC_OscInit.FreqMon.OscillatorSystem = PCC_OSCILLATORTYPE_OSC32M;
  PCC_OscInit.FreqMon.ForceOscSys = PCC_FORCE_OSC_SYS_UNFIXED;
  PCC_OscInit.FreqMon.Force32KClk = PCC_FREQ_MONITOR_SOURCE_OSC32K;
  PCC_OscInit.AHBDivider = 0;
  PCC_OscInit.APBMDivider = 0;
  PCC_OscInit.APBPDivider = 0;
  PCC_OscInit.HSI32MCalibrationValue = 128;
  PCC_OscInit.LSI32KCalibrationValue = 128;
  PCC_OscInit.RTCClockSelection = PCC_RTC_CLOCK_SOURCE_AUTO;
  PCC_OscInit.RTCClockCPUSelection = PCC_CPU_RTC_CLOCK_SOURCE_OSC32K;

  HAL_PCC_Config(&PCC_OscInit);
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  GPIO_InitStruct.Pull = HAL_GPIO_PULL_NONE;
  GPIO_InitStruct.Mode = HAL_GPIO_MODE_GPIO_OUTPUT;

  // включение тактирования GPIO_0
  __HAL_PCC_GPIO_0_CLK_ENABLE();
  GPIO_InitStruct.Pin = GPIO_PIN_9;
  HAL_GPIO_Init(GPIO_0, &GPIO_InitStruct);

  uint8_t state = true; // объявление переменной состояния светодиода

  while (1)   // бесконечный цикл
  {
    HAL_GPIO_WritePin(GPIO_0, GPIO_PIN_9, state); // вкл/выкл светодиода
   	state=!state;	// инвертируем переменную состояния светодиода
   	delay(500);	// формируем задержку 500 мс
  }
}

void delay(uint16_t ms) // функция задержки в миллисекундах
{       	
  for (volatile uint32_t i = 0; i < (tickInMs * ms); i++);
}

Итог работы этой программы – мигание встроенного светодиода МИК32 с частотой ~1 Гц.

 Модифицируем этот пример, встраивая сгенерированный из Engee код. Для этого:

  • подключим сгенерированный заголовочный файл: #include "mik32_test.h"

  • перед бесконечным циклом инициализируем модель: mik32_test_init();

  • сам же бесконечный цикл перепишем таким образом:

while (1)                       // бесконечный цикл
{
  mik32_test_step();
  HAL_GPIO_WritePin(GPIO_0, GPIO_PIN_9, mik32_test_Y.Out1);
  delay(100);
}

Здесь mik32_test_Y.Out1 – переменная, несущая результат вычисления нашей модели, который оказывается в выходном порте (блок Out1). В неё в итоге передаётся текущее состояние на выходе блока-формирователя повторяющейся последовательности.

Вызывая delay(100);, мы имитируем шаг расчёта модели. Почему имитируем? Ну, потому, что цикл выполнения программы здесь будет явно больше шага моделирования. Так что для лучшего соответствия программы и модели стоит рассчитывать и контролировать внутри цикла его длительность, а в идеале – вообще запустить FreeRTOS и включить функцию расчёта шага модели в таск.

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

Сгенерированный код включён в основную программу, а значит, осталось собрать проект и загрузить его в МК. В Engee пока нет готовых решений для полноценной коммуникации с внешними устройствами. Поэтому сгенерированный код и основную программу можно, например, добавить в проект во внешней IDE. Рекомендованная для МИК32 среда разработки – MikronIDE на базе Eclipse. Кроме того, в свободном доступе находится пакет поддержки МИК32 для VSCode+PlatformIO. Последним мы и воспользуемся в этом примере.

После сборки проекта, компиляции и загрузки кода в МК можно наблюдать мигание светодиода по смоделированной ранее последовательности. На рисунке 4 показана осциллограмма на цифровом пине 9.

Рисунок 4. Осциллограмма того, что и требовалось сделать
Рисунок 4. Осциллограмма того, что и требовалось сделать

 Тестирование на устройстве первой итерации выполнено, что дальше?

Переносим код периферии МК в Engee

При желании и необходимости мы можем теперь сократить количество кода в main.c, перенеся код для работы с периферией в блоки с кодом Си в модели Engee.

Со временем можно собрать целую библиотеку блоков специально для периферии МИК32 в Engee. Так мы избавим себя от необходимости писать или переносить из прошлых проектов код для работы с периферией.

Чтобы это провернуть, мы воспользуемся классным блоком из библиотеки Engee, он называется C Function. Суть работы с ним проста: мы представляем операции/функцию/набор функций на Си в виде функционального блока, который связывает получаемые набором функций переменные с возвращаемыми. Такие переменные, соответственно, становятся входами и выходами данного блока. Кроме этого, в блоке могут быть реализованы сложные механизмы параметризации, статические рабочие переменные, можно подключить файлы Си с готовым кодом, статические и динамические библиотеки.

Внутри самого блока – 4 вкладки для добавления кода. Три из них соответствуют трём функциям, генерируемым из модели – инициализация, пошаговый расчёт и терминация. Во вкладку инициализации вставляем код для конфигурирования и инициализации периферии, во вкладку пошагового расчёта – функции для циклической работы с периферией. Если в программе требуется ещё и терминация модели, во вкладку терминации C Function можно, например, добавить функции для отключения периферии. Четвертая вкладка блока используется для кода, который будет добавлен в файл сгенерированного кода, но не будет включен ни в одну из функций, получаемых из предыдущих вкладок. Здесь будет удобно, например, объявить глобальные переменные, определить имена структур или функции.

По итогу блок в Engee с кодом цифрового выхода может выглядеть так, как показано на рисунке 5.

Рисунок 5. Вкладки блока C Function для цифрового выхода МИК32. Слева направо: настройка портов блока, подключение файлов библиотеки HAL МИК32, вкладка Start code, вкладка Output code
Рисунок 5. Вкладки блока C Function для цифрового выхода МИК32. Слева направо: настройка портов блока, подключение файлов библиотеки HAL МИК32, вкладка Start code, вкладка Output code

Обращу внимание на несколько деталей, о которых я не успел ещё упомянуть.

  1. Имя порта блока – это не обязательно имя переменной, используемой в коде.

  2. Блок имеет 2 входа: seq – порт, переменная которого во встраиваемом коде не используется. Он применяется только для последовательного соединения блоков в Engee и, таким образом, достоверного определения последовательности вхождения кода из блоков C Function в результирующий код модели.

  3. Эта модель работает и в Engee, и на целевой платформе. То есть код блока будет не только встраиваться в сгенерированный код, но и исполняться в Engee. Чтобы использовать добавленный код только для встраивания в код целевой платформы, можно ограничить его конструкцией с условными директивами #ifdef MIK32V2 ... #else ... #end. Макрос MIK32V2, как правило, уже определён внутри библиотеки HAL МИК32, и встраиваемый код будет компилироваться в IDE, но не в Engee.

  4. Во вкладке Build options нужно указать путь расположения в ФБ Engee и имена заголовочных файлов из библиотеки HAL МИК32 для работы с данной периферией, а также имена заголовочных файлов используемой стандартной библиотеки. Это позволяет нам в результирующем коде получить такие строки:

#include "stdint.h"
#include "math.h"
#include "mik32_hal_pcc.h"
#include "mik32_hal_gpio.h"

Итак, собрав функции для работы с GPIO в один блок, а для инициализации библиотеки HAL МИК32, настройки подсистемы тактирования и монитора частоты МК – в другой, мы получим модель, как на рисунке 6.

Рисунок 6. Модель Engee  с периферийными блоками
Рисунок 6. Модель Engee с периферийными блоками

Моделирование проходит аналогично предыдущему шагу, так как добавленные блоки не вносят выполняемых операций в процессе моделирования. После успешной генерации кода можно перейти к работе с пользовательской программой. Теперь наш main.c будет выглядеть так:

#include "mik32_test.h" // подключение сгенерированного файла

int main()                          // основная программа
{
    mik32_test_init();     // функция инициализации модели
    while (1)                       // бесконечный цикл
    {
        mik32_test_step(); // функция пошагового расчёта модели
        delay(100);     // «шаг» расчёта модели
    }
}

Такое сокращение main.c позволяет нам масштабировать модель, не возвращаясь больше к редактированию пользовательской программы в сторонней IDE. Результат работы программы на этой итерации аналогичен предыдущему, как на рисунке 4.

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

Автоматизируем расчёт последовательности

Следующая небольшая ознакомительная задача – задать более сложную, например, кодирующую последовательность. Мы не будем вручную вписывать нули и единицы в блок Repeating Sequence Stair. В него вместо последовательности мы впишем переменную Код_Морзе. В эту переменную далее передадим последовательность, которую сейчас с вами сформируем.

Для технических расчётов в Engee есть несколько удобных инструментов – терминал, редактор скриптов, область переменных и область функций, обратные вызовы моделей. Язык среды вычислений – Julia, очень удобный и понятный в том числе, и для пользователей MATLAB и Python. Кстати говоря, в Julia можно встраивать вычисления на других языках – С, Python, MATLAB и некоторых других. Но вернёмся к нашей задаче.

Закодируем приветствие для радиопередачи "ЗДР, ENGEE! ". Принципы кодирования описаны в примере из сообщества Engee . Следующая часть кода на Julia описывает функцию посимвольного кодирования сообщения по азбуке Морзе.

Символы = collect("ЗДР, ENGEE! ")
Код_Морзе = (Int64)[];

for Итерация in 1:size(Символы, 1)    
  if (Символы[Итерация] == 'D')||(Символы[Итерация] == 'Д')        
    Код_символа = [1,1,1,0,1,0,1,0,0,0];    
  elseif (Символы[Итерация] == 'E')||(Символы[Итерация] == 'Е')        
    Код_символа = [1,0,0,0];    
  elseif (Символы[Итерация] == 'G')||(Символы[Итерация] == 'Г')        
    Код_символа = [1,1,1,0,1,1,1,0,1,0,0,0];    
  elseif (Символы[Итерация] == 'N')||(Символы[Итерация] == 'Н')        
    Код_символа = [1,1,1,0,1,0,0,0];    
  elseif (Символы[Итерация] == 'R')||(Символы[Итерация] == 'Р')        
    Код_символа = [1,0,1,1,1,0,1,0,0,0];    
  elseif (Символы[Итерация] == 'Z')||(Символы[Итерация] == 'З')        
    Код_символа = [1,1,1,0,1,1,1,0,1,0,1,0,0,0];    
  elseif Символы[Итерация] == ' '        
    Код_символа = [0,0,0,0];    
  elseif Символы[Итерация] == ','        
    Код_символа = [1,0,1,1,1,0,1,0,1,1,1,0,1,0,1,1,1,0,0,0];    
  elseif Символы[Итерация] == '!'        
    Код_символа = [1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,0,0,0];    
  end
  Код_Морзе = vcat(Код_Морзе, Код_символа);
end

Выполнив этот код в Engee, мы получим вектор из 116 значений. После запуска модели с новой последовательностью можно увидеть желаемую последовательность из нулей и единиц (Рисунок 7).

Рисунок 7. График закодированного сообщения
Рисунок 7. График закодированного сообщения

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

Рисунок 8. МИК32 явно хочет нам что-то сказать
Рисунок 8. МИК32 явно хочет нам что-то сказать

Следующие шаги

Мы обещали обсудить фишки генератора кода. То есть, конечно, следующие шаги состоят в том, чтобы сделать более крутую модель или реализовать настоящее реальное время. Но пока обсудим оптимизацию.

Для улучшения модели и программы, оптимизации их под свои задачи можно воспользоваться следующими возможностями Engee:

  • собрать пользовательскую библиотеку периферийных блоков МИК32;

  • добавить маски периферийных блоков – интерактивные (например, для задания номера порта прямо из маски) или неинтерактивные (просто с красивыми картинками на лицевой стороне блоков);

  • добавить в процесс разработки этап верификации сгенерированного кода;

  • использовать программное управление моделью для автоматизации конфигурирования модели, моделирования, генерации кода и его верификации и прочего;

  • автоматизировать конфигурирование модели при помощи обратных вызовов;

  • подключить специализированные библиотеки Julia реализовать с их помощью автотесты или генерацию части кода ;

  • и многое другое.

Сейчас на платформе и в Cообществе выложены несколько интересных примеров для работы с цифровыми и аналоговыми входами/выходами МИК32. А еще совсем скоро в открытом доступе появится и пример с тестированием нечёткого регулятора (НР) (Рисунки 9, 10).

Рисунок 9. Модель Engee и график тестирования НР
Рисунок 9. Модель Engee и график тестирования НР
Рисунок 10. Осциллограмма на аналоговом выходе МИК32 в ходе тестирования НР
Рисунок 10. Осциллограмма на аналоговом выходе МИК32 в ходе тестирования НР

Резюмируем

В целом, мы прошлись по процессу разработки программ для МИК32 в Engee и рассмотрели некоторые особенности процесса проектирования модели. Этот пример уже может служить отправной точкой для моделирования в Engee вашей модели с последующим встраиванием кода на внешнюю аппаратную платформу, в том числе МИК32.

Давайте пообщаемся в комментариях, а еще  можем продолжить диалог вживую (хоть и онлайн) на бесплатном вебинаре от ЦИТМ Экспонента. Уже 3 декабря с 10:00 я буду рад рассказать всем желающим о работе с генератором кода в Engee для МК МIK32V2 и STM32F4.

Спасибо за внимание, до встречи! 

И ещё обязательно подписывайтесь на телеграм-канал Engee, чтобы быть в курсе обновлений этой среды.

Теги:
Хабы:
Всего голосов 10: ↑9 и ↓1+9
Комментарии25

Публикации

Информация

Сайт
exponenta.ru
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
MaksimSidorov

Истории