January 15, 2020

Разгоняем микроконтроллер. Как я выжал 620 МГц из совместимой с Arduino платы

Источник: t.me/Bureau121

Содержание статьи

  • Аппаратная часть
  • Новая IDE
  • Настраиваем проект
  • Бенчмарки
  • Краткая история происхождения попугаев
  • Портируем код
  • Поехали!
  • Анализ результатов
  • Итоги

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

Конечно, если речь идет о разгоне игрового ПК, то зачастую все тривиально. Многие производители процессоров и видеокарт выпускают собственные утилиты для повышения напряжения и такт��вых частот, так что в итоге все сводится к перемещению ползунков в окошке программы. После чего остается только запустить встроенный в игру бенчмарк и наблюдать рост циферок (FPS имеет значение, ага).


Но задумывался ли ты, как это все работает на самом деле? И что нужно знать, чтобы достичь по-настоящему выдающихся результатов? Попробую продемонстрировать на примере.


Аппаратная часть

Низкоуровневый разгон железа проще всего будет показать на обычном микроконтроллере. Наверняка ты знаком с Arduino — эта крохотная плата перевернула мир мейкеров, хакеров и всех сопричастных. В ней минимум компонентов: кварцевый резонатор, микросхема ATmega328P и регулятор питания. И все же это почти компьютер. А значит, ее тоже можно разогнать!

Однако сегодня я настроен на максимальный результат, и какие-то жалкие 16 МГц тактовой частоты меня совершенно не интересуют. Отложим Arduino в сторонку и возьмем плату Nucleo-144 на основе STM32H743.

Если пытаться подобрать подходящую аналогию из мира персональных компьютеров, то перед нами Core i9, вне всяких сомнений. Ядро ARM (Cortex-M7) тут работает на ошеломительной частоте 480 МГц — это ровно в 15 раз быстрее оригинальной Arduino Uno. Мало того, здесь размещено два мегабайта постоянной флеш-памяти и целый мегабайт ОЗУ. Производительности добавляет кеш программ и кеш данных (по 16 Кбайт), а также встроенный ускоритель исполнения кода ART.

Сегодня существуют две версии такой отладочной платы. Изначально в модельной линейке Nucleo-144 появилась H743ZI с программатором ST-Link V2-1 и микроконтроллером ревизии Y. Она поддерживала штатную работу «всего лишь» на 400 МГц. Но уже через несколько месяцев производитель сумел оптимизировать схему кристалла и начал выпускать микроконтроллеры Н7 новой ревизии V с базовой частотой в 480 МГц. Именно они легли в основу платы H743ZI2. Кроме того, обновили и программатор — теперь это ST-Link V3E с возможностью внутрисхемной эмуляции.

Так что, если будешь выбирать себе такую же плату, будь предельно внимателен, внешне они очень похожи и выполнены на одинаково белом текстолите. Да и по другим параметрам особых отличий нет: разъемы Arduino, Morpho, Ethernet и интерфейс USB присутствуют на обеих платах.


Новая IDE

Даже очень хорошее железо может оказаться бесполезным, если для него не будет подходящего ПО. К счастью, это понимают и в компании ST Microelectronics, поэтому сравнительно недавно там сделали собственную CubeIDE. Однако совсем новой ее назвать трудно. По сути, это надстройка над Eclipse, что может обрадовать пользователей, знакомых со средами Atollic TrueStudio и AC6 Workbench.

Интегрированная среда разработки не только бесплатна и доступна на всех основных платформах (поддерживаются Windows, Linux и macOS), но еще и позволяет «из коробки» пользоваться современными средствами отладки и трассировки (OpenOCD, GDB), а также настраивать периферию микроконтроллера буквально в пару кликов мыши.

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

Настраиваем проект

После создания проекта для отладочной платы Nucleo-H743ZI2 нам нужно изменить несколько настроек по умолчанию.

Окно настройки периферии имеет две полезные области (на самом деле их гораздо больше, но нас сейчас интересуют конкретно эти): Pinout & Configuration и Clock Configuration. Функции периферии можно указать из меню, выпадающего по правому щелчку мыши. Более тонкая настройка производится в окне System view. Графическое представление тактового дерева можно найти во вкладке Clock Configuration.

Обрати внимание, что для дальнейшей настройки системной частоты в качестве основного источника будет использоваться ФАПЧ (PLL), тактируемый от внешнего кварцевого резонатора HSE. В нашем случае он уже находится на отладочной плате и имеет номинал 8 МГц.

Максимально возможная тактовая частота ядра и системной шины при этом может быть обеспечена установкой делителей DIVM1 = 1 и DIVP1 = 7. Значение множителя DIVN1 предстоит увеличивать до тех пор, пока микроконтроллер будет работать стабильно. Делители HPRE, D1PRE, D2PPRE1, DRPPRE2 и D3PPRE установим равными двум, остальные оставим по умолчанию.

Нельзя забывать и о питании. Блок ФАПЧ работает от линейного регулятора, режим которого [VOS0...VOS5] программно задает уровень напряжения от 1,35 до 0,7 В. Кроме того, при необходимости можно подать внешнее питание Vcore, но только если оно не превышает 1,4 В.

Как ты понимаешь, повышение напряжения позволяет добиваться больших тактовых частот. Это связано с тем, что транзисторы в схеме начинают быстрее менять свое состояние (сокращается длительность переднего и заднего фронта сигнала).

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

Теперь предстоит указать компилятор и компоновщик. Это можно произвести в меню Project → Properties → C/C++ Build → MCU Settings (MCU GCC Compiler, MCU GCC Linker). Причем для чисто академических целей предлагаю сперва оставить оптимизации на уровне -O0 (оптимизация отключена).

На следующем шаге нам нужно обеспечить корректную работу функции printf. Для этого подключаем заголовочный файл <stdio.h> и добавляем ключик компиляции -u _printf_float для компоновщика. Остается только реализовать функцию вывода символов:

int __io_putchar(int ch) {
  /* Use Instrumentation Trace Macrocell */
  ITM_SendChar((uint32_t) ch);
  return ch;
}

Кроме того, переопределим функцию установки тактовой частоты:

SystemClock_Config(uint32_t* FinalCoreClock) {
  ...
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
  ...
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 120; /* @480 MHz */
  RCC_OscInitStruct.PLL.PLLP = 2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  RCC_OscInitStruct.PLL.PLLR = 2;
  RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3;
  RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
  RCC_OscInitStruct.PLL.PLLFRACN = 0;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    Error_Handler();
  }
  ...
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_14) != HAL_OK) {
    Error_Handler();
  }
  ...
  *FinalCoreClock = HSE_CLOCK / 2 * RCC_OscInitStruct.PLL.PLLN;
}

В листинге выше особый интерес представляет настройка множителя PLLN, который влияет на тактовую частоту ядра (что самым непосредственным образом сказывается на производительности). Также обрати внимание на процедуру выбора источника питания для ФАПЧ __HAL_PWR_VOLTAGESCALING_CONFIG и установку таймингов для флеш-памяти HAL_RCC_ClockConfig.

Функцию настройки таймера TIM2 можно реализовать следующим образом:

static void MX_TIM2_Init(uint32_t CoreClock) {
  ...
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = (CoreClock / (2 * 1000000)) - 1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 0xFFFFFFFF; /* timer period */
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  ...
}

Теперь в любой точке нашего кода мы сможем узнать количество микросекунд, прошедших с запуска таймера. Для этого достаточно будет просто обратиться к регистру TIM2 → CNT.

Бенчмарки

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

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

Краткая история происхождения попугаев

Первым широко распространенным тестом производительности принято считать Whetstone, который вышел в ноябре 1972 года. В качестве эталонной единицы измерения в нем использовался 1 MWIPS, соответствующий одному миллиону операций в секунду на архитектуре DEC VAX-11/780. Тест включал в себя 150 простых выражений, разделенных на восемь блоков, которые исполнялись в основном цикле.

Предполагалось, что малый размер теста позволит полностью уместить его в кеш L1 вычислительных машин и игнорировать преимущества L2. Whetstone не был рассчитан на использование оптимизационных компиляторов, что в итоге привело к путанице в расчете результатов. Появившиеся в 1980-х «продвинутые» компиляторы научились оптимизировать адресные переходы, виртуально увеличивая производительность в тестах в два раза.

Если тест Whetstone не был привязан к какой-либо операционной системе (и исполнялся в том числе на barebone-платформах), то разработанный в 1984 году тест Dhrystone предназначался для оценки производительности вычислительных систем на ядре Unix и использовал, например, системные библиотечные функции работы со временем.

В отличие от Whetstone, делающего упор на работу с числами с плавающей точкой, Dhrystone специализировался на работе с целочисленными и строковыми переменными. В настоящее время для оценки производительности достаточно широко используется версия 2.1, которая научилась больно бить компиляторы по рукам за любые попытки излишней оптимизации.

Синтетический тест CoreMark пришел на смену Dhrystone в 2009 году. Он включает в себя вычисление контрольной суммы, работу со связанными списками, сортировку и матричные операции. Таким об��азом, он максимально приближен к исполняемому коду современных проектов. CoreMark не использует системные библиотеки (его код полностью доступен в виде исходников) и тоже достаточно эффективно борется с оптимизирующей функцией компиляторов.

На сегодняшний день консорциум EEMBC (создатель CoreMark) — один из наиболее авторитетных разработчиков синтетических тестов для микроконтроллеров и устройств IoT. Предоставляемые на рассмотрение результаты проходят проверку и добавляются в рейтинг. Ознакомиться с ним ты можешь на сайте EEMBC в разделе CoreMark Scores.

Портируем код

Актуальная версия бенчмарка Whetstone включает в себя один файл whetstone.c, совместимый c ANSI C89, и щедро приправлена метками и операторами безусловного перехода goto. Да, вот такая занятная древность! Портирование заключается в переименовании функции теста c main на другое имя (например, whetstone). Кроме того, нужно не забыть передать ей аргументы ls (количество повторений теста в рамках одного прогона) и total_LOOPS (общее количество прогонов за весь тест).

Реализация бенчмарка Dhrystone 2.1 состоит из трех файлов: dhry_1.c, dhry_2.c и dhry.h. Так как тест был разработан для операционных систем семейства Unix, помимо переименовывания функции main необходимо реализовать функцию time (с использованием макроса #define TIME). Также нужно переопределить #define Mic_secs_Per_Second 1000000.0 (так как используемый нами системный таймер работает на частоте 1 МГц и один отсчет этого таймера соответствует одной микросекунде).

Бенчмарк CoreMark реализован в нескольких файлах, но настройка и портирование теста происходит в заголовочном файле с говорящим названием core_portme.h. Для корректного расчета времени необходимо установить макроопределения ITERATIONS (разработчики архитектуры ARM рекомендуют подобрать такое количество итераций, чтобы длительность исполнения теста была не менее 30 с) и CLOCKS_PER_SEC (тактовая частота считающего время таймера, в нашем случае 1 000 000).

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

#include <stdio.h> 
#define TOTALCYCLES 10

int main(void) {
  HAL_Init();
  SystemClock_Config(&coreClock);
  printf("System clock: %lu \n\r", coreClock);
  MX_GPIO_Init(); /* Initialize all configured peripherals */
  MX_TIM2_Init(coreClock);
  for (int c = 0; c < TOTALCYCLES; c++) {
    htim2.Instance->CNT = 0; /* Reset 32-bit timer value */
    HAL_TIM_Base_Start(&htim2); /* Start timer */
    printf("======================================\n\r");
    printf("Cycle run: %d\n\r", c);

    printf("Whetstone test ... beginning \n\r");
    whetstone(1000, 10); /* Run Whetstone test */
    printf("Whetstone test ... end \n\r");

    printf("Dhrystone test ... beginning \n\r");
    dhrystone21(10000000); /* Run Dhrystone test */
    printf("Dhrystone test ... end \n\r");

    printf("Coremark test ... beginning \n\r");
    core_main(); /* Run Coremark test */
    printf("Coremark test ... end \n\r");

    HAL_TIM_Base_Stop(&htim2); /* Stop timer */
  }
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);

  while (1);
}

Поехали!

Перед запуском микроконтроллера в настройках отладчика ST-Link необходимо включить поддержку Serial Wire Viewer (SWV) и указать текущую тактовую частоту ядра микроконтроллера. Кроме того, нужно включить окно вывода SWV «Window → Show View → SWV → SWV ITM Data Console» и разрешить вывод нажатием на кнопку Start Trace.

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

Есть альтернативный способ: выбери режим under reset в параметрах отладчика и зажми кнопку сброса. Тогда на линии nRST окажется логический ноль и микросхема перейдёт в режим программирования.

Предположим, все прошло удачно и программа выдала результат. Но выглядит он удивительно: 8 DMIPS и 69,75 CoreMark абсолютно не соответствуют заявленным характеристикам и уж тем более нашим амбициям.

Что же мы упустили? Во-первых, стоит включить кеш программ и данных, добавив во время инициализации функции SCB_EnableICache и SCB_EnableDCache. Либо ты можешь активировать их в графическом меню через настройки периферии (даже несмотря на то, что они напрямую относятся к ядру): CORTEX M7 → CPU Icache → Enabled / CPU Dcache → Enabled. Во-вторых, можно поиграть с ключами оптимизации компилятора -O3 и -Ofast. Ну и наконец, мы же можем разогнать микроконтроллер и попытаться полностью раскрыть потенциал железа!

Анализ результатов

Не буду долго ходить вокруг да около: после череды попыток и последовательного увеличения множителя частоты PLLN до 155 (соответствует 620 МГц) мне удалось добиться стабильной работы H743 и без проблем пройти все тесты. Результаты приведены на графике ниже.

Более медленное исполнение кода из ОЗУ в нашем случае объясняется конкурентным доступом к памяти данных/программ и независимым доступом в случае расположения исполняемого кода в области флеш-памяти.

Как видно из результатов, тест Whetstone наиболее подвержен влиянию настроек компилятора и является наименее репрезентативным из представленных. А при разгоне с максимальными настройками оптимизации компилятора GCC получен результат, который уверенно превзошел заявленные производителем характеристики (2400 CoreMark / 1027 DMIPS).

Итоги

Если при слове «микроконтроллеры» тебе представляется ардуинка на AVR, мигающая светодиодом как заведенная, то спешу тебя обрадовать — ее современные аналоги способны на куда более интересные вещи. Некоторые даже запускают на них нейронные сети и используют для машинного зрения. Что неудивительно — с такими-то характеристиками.

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

Удачного разгона!

ПОДПИСАТЬСЯ - Бюро121