18/02/2020

Web (1): RayCasting vs GPU Color Picking (на примере Three.js)

В определённый момент, перед разработчиком графических приложений с использованием OpenGL/WebGL встаёт задача взаимодействия пользователя с объектами. Будь то элементы двухмерного интерфейса (частный случай трёхмерного пространства) или объекты трёхмерной сцены, существует два подхода к решению этой задачи. Первый заключается в поиске объекта, на который указывает мышь математическим (тригонометрическим) расчётом.  Второй — поиск объекта по цвету пикселя, на который пользователь указал мышью. Первый кажется логичным, понятным и красивым, второй — на первый взгляд кажется странным, но у обоих методов есть свои плюсы и минусы и свои области применения.

В этой статье мы рассмотрим два метода выбора объекта на сцене (OpenGL/WebGL на примере Three.js) — RayCasting и GPU Color Pick.

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

В Three.js для этих целей имеется уже разработанный объект THREE.Raycaster. Пользоваться им достаточно просто, в примере, которым я завершу эту статью, этот метод будет представлен. Описывать его механику смысла не вижу — это обычная тригонометрия.

У RayCasting’а есть два минуса:

  1. Он работает медленнее, так как работает на CPU, и в случае, когда у нас много объектов, это может быть очень тяжёлой задачей для центрального процессора. А в современной графике с использованием актуальных методов создания карт (т.н. сцен) количество объектов может быть очень большим.
  2. Он не учитывает факторы текстуры — прозрачность, и просто «выбирает» объекты, которые встречаются лучу (в нашем случае — выбор объекта мышью, луч рассчитывается от камеры), то есть работает исключительно геометрия (математика).

Технология GPU Color Pick заключается в следующем. При создании сцены, все объекты дублируются в другую сцену, обычно называемую PickingScene и связываются некоторыми ID реальных объектов с цветами объектов в PickingScene. Для исключения излишней нагрузки на GPU, PickingScene, как правило, делается очень маленькой (выходной буфер рендеринга очень маленькой площади, это в некоторой степени сокращает время на рендеринг). В нашем случае, по наведению мыши, вызывается функция поиска объекта в PickingScene и затем выбирается ID объекта в реальной сцене. В силу того, что технология GPU Color Pick работает по цвету, она лишена второго минуса RayCasting’а — мы можем использовать альфа-канал в текстурах объектов. Это значит, что мы можем дать пользователю возможность «выбирать» объекты стоящие «за», сквозь «прозрачные» области стоящих «перед». В силу того, что технология GPU Color Pick работает на GPU, а он, в свою очередь очень «заточен» под такие задачи, эта технология работает гораздо быстрее и почти полностью разгружает CPU (на нём остаётся лишь задача соотнести цвета и ID объектов).

У GPU Color Pick (как ни странно) есть два минуса:

  1. Мы не можем получить координаты места «встречи» указателя (здесь это не луч и не вектор) и объекта — только сам объект, так как объект выбирается не геометрией, а цветом.
  2. Подгружается GPU. Это хорошо тем, что разгружается CPU, но может быть плохо, если вычислительные мощности GPU для нас критичны. Плюс, в момент процесса самого Picking’а, нам надо ждать на CPU пока GPU выполнит задачу поиска объекта по цвету.


Все эти технологии, конечно же применяются и на Bare WebGL и на чистом OpenGL.

Чтобы поиграться (работает без Web-сервера):

git clone https://gitlab.com/daftsoft/gpu-color-pick-vs-raycasting



12/12/2019

Системы сборки (1): GNU Make - Simple Makefile


Системы сборки (1): Make - Simple Makefile  простой Makefile для несложных программ
Когда проект растёт в размерах файлов, разработчики начинают делить его на несколько файлов, потом ещё и ещё делить. Компилировать один файл командой gcc (или иной другой) не сложно. Иногда, когда проект ещё не слишком большой, пишут скрипты типа build.sh, в которых записывают все действия для сборки. Но когда проект становится очень большим, и/или усугубляется использованием сторонних библиотек, возникает необходимость автоматизировать и «обрамить» процесс сборки. Для этих целей разработана система GNU Make.
С одной стороны, в этой статье описана лишь малая доля возможностей и функционала системы GNU Make, с другой — для начала работы, для большинства простых программ и для понимания того, что написано в других Makefile'ах вам этой информации хватит.
Настройка сборки происходит через задание переменных. Все переменные могут быть пустыми (как наши LDFLAGS и INCLUDE ниже). Более того, переменные могут быть неинициализированными вовсе — не объявлены в Makefile. В таком случае, они не будут приводить к ошибкам при попытке использовать их, а будут раскрываться как пустые. Обращение к переменной осуществляется через символ $ и скобки — $(CXXFLAGS). Начнём: Имена компиляторов (CC — для C, CXX — для C++):
CC=gcc
CXX=g++
Эти переменные в make-системе есть по-умолчанию — cc и g++ (на моей системе cc — это ссылка на gcc) для компиляторов C и C++ соответственно. То есть, можно опустить эти объявления, если вы согласны собирать проекты под родную систему и стандартными компиляторами. Возможность изменять эти переменные нужна для кросс-компиляции (сборки проектов под системы, отличные от той, на которой сборка производится) или для работы с разными версиями компиляторов на одной машине. Объявления этих переменных (как и некоторых других ниже) закомментированы. Я делаю это для того чтобы, описывая значения полей и структуру Makefile'а, заодно показать насколько маленьким может быть простой Makefile. Флаги компиляции. Сюда вы можете ставить оптимизацию (например, -O3), стандарт языка (-std=c99), параметры -Werror, -Wall, -pedantic и т.п.
CFLAGS=-Wall
CXXFLAGS=-Wall
Имя исполняемого файла, который мы хотим получить на выходе:
EXECUTABLE=simple
Эта команда собирает все файлы с расширением .cpp в переменную:
SOURCES=$(wildcard *.cpp)
Эта команда создаёт переменную с именами объектных файлов. Это достигается путём замены расширения .cpp на расширение .o:
OBJECTS=$(SOURCES:.cpp=.o)
Так как нам в проекте нужен только список объектных файлов (для чего будет нужен этот список и почему список имён файлов с исходниками не будет нужен — будет видно позже), две предыдущие команды можно объединить в одну:
OBJECTS=$(patsubst %.cpp,%.o, $(wildcard *.cpp))
Здесь описано как добавить к проекту исходные файлы на C:
OBJECTS+=$(patsubst %.c,%.o, $(wildcard *.c))
LDFLAGS — флаги для линковщика (пока оставляем пустыми):
LDFLAGS=
INCLUDE — пути к заголовочным файлам, включаемым в код нашего проекта (здесь оставляем пустыми, то есть будут использованы системные):
INCLUDE=
Далее идут правила. Вся система make построена на так называемых «правилах». Правило — это основной параметр, передаваемый команде make. В описании правила, за его именем указывают зависимости, необходимые для его выполнения. Зависимости — это файлы (имена файлов). После, на новой строке, идут команды, которые будут выполняться при вызове этого правила — строки с командами. Они должны начинаться с символа табуляции. Правило может не иметь команд, правило может не иметь зависимостей. Улитка перед командой является указанием не выводить команду (в противном случае make перед выполнением команды выведет её на консоль).
all — это общепринятое название правила, выполняющего сборку проекта. В нашем случае all завязан на значение имени исполняемого файла — EXECUTABLE, а оно в свою очередь является правилом, описанным ниже. Так же можно видеть, что само по себе правило all не имеет «тела» (команд для выполнения).
all: $(EXECUTABLE)
Правило с именем исполняемого файла, получаемого из значения переменной, выглядит следующим образом:
$(EXECUTABLE): $(OBJECTS)
        $(CXX) $(CXXFLAGS) -o $@ $^ $(INCLUDE) $(LDFLAGS)
Здесь написано следующее — для создания файла, записанного в значении переменной EXECUTABLE, нужны объектные файлы (записанные в созданной нами ранее переменной-списке OBJECTS). И описано само правило, являющееся, если присмотреться, командой линковки, в нашем случае раскрывающееся в следующую команду: g++ -Wall -o simple main.o module.o extern.o Что означает: слинковать перечисленные объектные файлы (main.o module.o extern.o) в исполняемый файл simple. Откуда берутся имена объектных файлов мы помним — выше описана команда создания списка путём замены расширения .c и .cpp на .o и записи их в переменную OBJECTS. Но вы можете спросить — а где команда (или правило) для построения компиляции) исходных файлов в объектные? А его нет. Дело в том, что в системе make есть набор стандартных неявно описанных правил. Все они описаны здесь: GNU Make Catalogue of Built-In Rules
Одно из стандартных правил make выглядит примерно так:
# .o: .c
#         $(CC) $(CPPFLAGS) $(CFLAGS) -c
Что означает скомпилировать (без линковки) все .c-файлы. Компиляция без линковки выдаёт объектный файл. Вот так получаются сами объектные файлы необходимые для линковки в исполняемый файл. Автоматизация make доходит до того, что можно дать команду:
make имя файла с исходным кодом даже без расширения и даже не имея Makefile! make main По этой команде make попытается скомпилировать main.c (в случае его отсутствия make попытается сделать то же с файлом main.cpp) в объектный файл main.o, затем слинковать его в исполняемый файл main. Неявное правило можно переписать. Например, правило, описанное ниже приведёт к ошибке сборки всего проекта. Оно гласит: для сборки объектного файла extern.o нужен файл с исходным кодом extern.c. Но само по себе не создаёт объектного файла нужного для сборки нашего проекта. Что приведёт к тому, что проект никогда не будет собран. Можете раскомментировать это правило сделать make clean all и посмотреть что будет.
#extern.o: extern.c
#        echo Rule to never make this project!
clean — правило для очистки. Сюда вписываем всё, что мы хотим делать при очистке проекта. В нашем случае мы удаляем объектные файлы, исполняемый файл и временные файлы. Это правило, в том виде, в котором оно описано, удалит только те объектные файлы, которые относятся к нашему проекту — то есть имеющие соответствующие файлы с исходным кодом. Вы можете проверить это, удалив какой-нибудь .с/.cpp-файл и выполнив команду make clean — вы увидите, что объектный файл соответствующий удалённому останется. Можно поставить rm *.o, но в приличных и крупных проектах принято удалять только своё. А вдруг вы (или пользователь вашего проекта) держите ещё какие-то файлы в каталоге с вашим Makefile (например, объектные файлы, которые вы получаете без исходного кода)?
clean:
        rm -rf $(OBJECTS) $(EXECUTABLE) *~
.PHONY — Это список имён правил, которые не являются файлами. Создаётся во избежание конфликтов между названиями этих правил и именами файлов. Если не указать clean и all в списке .PHONY, make запутается, потому что по умолчанию правила clean и all будут связаны с файлами с такими именами и make будет запускать их только тогда, когда файл не будет обновлен в отношении его зависимостей. Можно создать файл clean и попробовать сделать

make clean

с закомментированной строкой .PHONY, результат будет такой:

make: 'clean' is up to date.

То есть make увидит, что файл clean уже существует и не станет выполнять это правило вообще.
Список .PHONY может быть использован для форсированной компиляции каких либо файлов, вне зависимости от их состояния. Например форсированная перестройка модуля или всего проекта. Вообще считается хорошим тоном вписывать в список .PHONY все правила, не создающие файлов — clean, all, install, test, и пр.
.PHONY: clean all
Возможно вы обратили внимание, что выше мы использовали такие переменные как $@ и $^. Это название правила и список зависимостей соответственно. Ниже написано тестовое правило, которое выводит значение этих переменных. Вы можете поэкспериментировать — поменять его название и список зависимостей. И вопрос на внимательность — как сделать так, чтобы это правило выполнялось бесконечное количество раз?
test: Makefile main.cpp
        @echo "@ = " $@
        @echo "^ = " $^
        @touch $@

Для тех, кто впервые читает или встречается с GNU Make пару слов об этой системе. GNU Make — система сборки (компиляции) программного обеспечения. Работа с ней выглядит следующим образом — (вами или не вами) пишутся правила, затем выполняется команда формата

make -f Makefile -jJobs rule_1 ... rule_n 

где:
Makefile — имя Makefile'а из которого брать правила. Эту опцию можно опустить. По-умолчанию GNU Make будет искать Makefile'ы в текущем каталоге в следующем порядке: GNUmakefile, makefile и Makefile. Рекомендуемым авторами системы является имя — Makefile.
Jobs — количество потоков выполнения. Make умеет распараллеливать работу. Обычно эту цифру ставят равной количеству процессоров либо количеству процессоров умноженному на два. Если указать ключ -j без параметра, Make сам рассчитает оптимальное количество потоков для выполнения. Здесь стоит отметить два момента. Первое — этот ключ имеет смысл только для достаточно больших проектов. Второе — задавать слишком большое значение опасно — Make никак не проверяет производительность машины, на которой запущен и её способность вытянуть заданное количество «работ». В итоге можно перегрузить машину так, что вы даже нажав Ctrl-C будете ждать пол часа. Я попадал в такую ситуацию. Ещё бывает так что большие проекты не собираются в параллельном режиме — будет ошибка компиляции, которую можно обойти запустив make ещё раз, но без многопоточной сборки. Иногда можно «пройти» это место, остановить сборку и опять запустить в многопоточном режиме. Бывает такое, как я понимаю, из-за неправильно составленных Makefile'ов — получается так, что часть кода, требующая неких объектных файлов, уже собрана, а объектные файлы ещё нет. Своеобразный race condition получается.
rule_1 ... rule_n — список правил для выполнения. Да, правила можно передавать Make по несколько штук — они будут выполняться в очерёдности, в которой были заданы пользователем. Если этот параметр опустить — Make выполнит правило, описанное первым в Makefile.
Изначально GNU Make создавался как бы «в паре» с (или «под»GCC — отсюда такая простота и прозрачность в работе Make именно с процессом сборки программ при помощи этой коллекции компиляторов. Но несмотря на это, можно написать абсолютно любые правила в Makefile'е — настройка сети, запуск/остановка сервисов и пр.

На этом о make пока всё. К более сложным аспектам работы с этой системой мы ещё вернёмся позже.
В следующей статье мы рассмотрим как смешивать C и C++ код в одном проекте (в этом Makefile мы уже затронули это с точки зрения сборки), как происходит построение программы — как компилятор и линковщик создают исполняемый файл и при чём здесь ассемблер?

22/10/2019

Интересно о размерах (1): типы и разрядные сетки машин

Короткая заметка о том, как соотносятся размеры целочисленных типов с разрядностью машины (процессора, контроллера).

Лет 10-15 назад я почему-то думал, что размер int'a (sizeof (int)) равен разрядности машины. Не знаю откуда я это взял, может, я как-то по-своему интерпретировал слова Б.Кернигана и Д.Ритчи:

«int  целое число, обычно имеющее типовой размер для целых чисел в данной системе»

Но выглядела эта идея довольно красиво  представьте себе, что у вас int всегда имеет размер разрядности машины: 16 — для 286, 32 — для 386 и после, 64  для Itanium (или кто там был первым среди x86-64?). В последствии, на практике, я увидел, что это не так.
Недавно (неделю-две назад) я решил окончательно разобраться с этим вопросом. Опишу это здесь.

Мои изыскания привели к следующим результатам:
1. Размер int'а никак не связан с разрядностью машины. Стандарт языка C описывает только одно правило соотношения между базовыми типами:

sizeof (short) <= sizeof (int) <= sizeof (long)

по-русски гласящее, что тип short будет либо меньше, либо равен int, который, в свою очередь, либо меньше, либо равен long. На практике, мы видим, что чаще всего размер int'а равен 4 байтам. Но размеры этих типов не определяются стандартом.

2. Да, функцией sizeof () можно определить разрядность машины  только не через размер типа, а через размер указателя на тип (подойдёт любой тип). Именно размер указателя равен разрядности машины, так как разрядность  это не только количество бит, с которыми машина работает за один такт и/или размер инструкции, но и размер поддерживаемого объёма памяти, для адресации которого машина выделяет и оперирует типами соответствующего размера. То есть, чтобы узнать разрядность машины можно воспользоваться sizeof([любой тип]*), например sizeof(int*).

Так как функция sizeof () реализована так, что размерности типов (и прочих констант) подставляются в момент компиляции (compile-time), по сути дела информативность её достаточно мала в силу того, что разрядность процессора задаётся (и может быть ограничена) компилятором и/или операционной системой и результат выполнения её после компиляции будет одинаков при всех последующих запусках. Для определения разрядности машины есть макрос __LP64__ и __LP32__ соответственно (эти макросы означают модель памяти, о 64-ёх разрядной можно почитать здесь 64 bit data models). Макрос использованный в коде, на этапе компиляции развернётся и не будет занимать процессорного времени на этапе выполнения, при этом выполнит задачу sizeof (указатель на тип).

Завершим описанное демонстрацией простенькой программой:

#include "stdio.h"
int main ()
{
  printf ("Compiled on ");
#if defined(__LP64__)
  printf ("64-bit platform\n");
#endif
#if defined (__LP32__)
  printf ("32-bit platform\n");
#endif
  printf ("sizeof (char)\t= %d bytes\n", sizeof(char));
  printf ("sizeof (short)\t= %d bytes\n", sizeof(short));
  printf ("sizeof (int)\t= %d bytes\n", sizeof(int));
  printf ("sizeof (long)\t= %d bytes\n", sizeof(long));
  printf ("sizeof (float)\t= %d bytes\n", sizeof(float));
  printf ("sizeof (double)\t= %d bytes\n", sizeof(double));
  printf ("sizeof (int*)\t= %d bytes\n", sizeof(int*));
}

И проверяем:
gcc ./main.c && ./a.out 
Compiled on 64-bit platform
sizeof (char)   = 1 bytes
sizeof (short)  = 2 bytes
sizeof (int)    = 4 bytes
sizeof (long)   = 8 bytes
sizeof (float)  = 4 bytes
sizeof (double) = 8 bytes
sizeof (int*)   = 8 bytes
64-ёх битная платформа (определена макросом и вычислением sizeof(int*)), и правило

sizeof (short) <= sizeof (int) <= sizeof (long)

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

15/07/2019

STM32 (2): удобная среда разработки — SES + CubeMX: методика ускорения разработки (часть 1)


STM32 (2): удобная среда разработки 
— SES + CubeMX: методика ускорения разработки (часть 1)

В предыдущей статье мы рассмотрели и настроили среду разработки на основе
Segger Embedded Studio (SES).
В этой статье я опишу методику ускорения разработки ПО под STM32 путём подключения HAL (Hardware Abstraction Level, библиотека абстракции от аппаратуры) от ST.
В прошлый раз мы упоминали пакет CMSIS (Cortex-M Software Interface Standard) — это набор общих для всех Cortex-M контроллеров дефайнов (#define). Эта, условно говоря, библиотека разработана для стандартизации процесса разработки и повышения переносимости кода между всеми Cortex-M контроллерами. У неё есть очевидное положительное качество — её легковесность, так как дефайны, раскрываясь при компиляции, не занимают места. И, да — она облегчает работу на Cortex-M (в том числе и на STM32). Но если вы будете писать код с использованием лишь CMSIS, вам придётся заполнять все структуры (множество структур) самостоятельно. И, более того, о, ужас, писать непонятные вещи, подобные этому:
GPIOA->ODR &= ~GPIO_ODR_ODR0;

Компания STMicroelectronics пошла дальше и разработала продукт называемый STM32CubeMX (CubeMX). CubeMX — свободный, кроссплатформенный пакет для настройки HAL от ST в графической среде. Процесс работы с использованием CubeMX выглядит примерно так: выбираем тип контроллера, настраиваем его интерфейсы, выбираем IDE, под которую нужно создать проект, нажимаем «Сгенерировать проект», открываем проект в выбранной IDE, работаем над функционалом. К сожалению CubeMX не поддерживает SES. Как это компенсировать я и расскажу на простом примере.

Для начала работы, скачайте CubeMX:
https://www.st.com/en/development-tools/stm32cubemx.html
Нужно нажать Get Software и, к сожалению без регистрации/авторизации скачать не получится.

Устанавливаем, запускаем, создаём проект (New Project > ACCESS TO MCU SELECTOR):

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


Вверху вы увидите три вкладки — Pinout & Configuration, Clock Configuration и Project Manager.
Начнём с самого основного — настройки частоты на которой будет работать наш контроллер. Перейдите на вкладку Clock Configuration. Вы увидите следующее (картина будет отличаться в зависимости от выбранного контроллера, я для примера рассматриваю один из самых простых — STM32L011):


Видите как много параметров в настройке частоты? А теперь введите в поле HCLK (MHz) (обычно чуть выше и правее середины схемы) значение. CubeMX даже подскажет вам максимальную частоту, которую вы можете себе «позволить» на выбранном контроллере.


После нажатия Enter, CubeMX сам рассчитает всю цепь, а в случае некорректного значения сообщит об ошибке.

Далее во вкладке Project Manager введите название проекта, пути и прочее. В поле Toolchain / IDE выберите MDK-ARM V5. Последнее важно для импортирования проекта в SES. Теперь нажмите «GENERATE CODE» (находится справа вверху). CubeMX создаст проект, скопирует HAL под нужный контроллер и, что самое главное — сгенерирует высокоуровневый код (с использованием HAL'а), который выполнит инициализацию всего того, что мы настроили в графической среде.

Как я уже писал — под SES CubeMX не умеет делать проекты. Сейчас мы будем это исправлять. Открываем SES и создаём проект, выбираем тот же контроллер, что и в CubeMX. Здесь не повторяюсь — этот процесс описан в предыдущей статье.

Открываем оба каталога — с проектом CubeMX и с проектом SES. Далее будем копировать файлы из каталога с проектом CubeMX в корень каталога проекта SES (себе). Я буду показывать процесс на примере проекта под STM32L0xx. В случае использования другого контроллера, имена каталогов изменятся соответственно.
1. Удаляем из проекта SES файл main.c (нажать на нём правой кнопкой и выбрать «X Delete»).
2. Копируем себе каталоги Src и Inc.
3. Перемещаем файл Src/system_stm32l0xx.c в каталог STM32L0xx/CMSIS/Device/Source (с замещением).
4. Копируем каталог STM32L0xx_HAL_Driver (у CubeMX находится в каталоге Drivers). Я обычно копирую его в каталог STM32L0xx, который SES создаёт в корне проекта.
5. Добавляем новые файлы исходного кода: жмём правой кнопкой мыши по Source Files, выбираем Add Existing File... и указываем все файлы из каталога Src.
6. Повторяем эту процедуру, указывая все файлы из каталога STM32L0xx/STM32L0xx_HAL_Driver/Src
7. Открываем свойства проекта (правая кнопка на проекте —> Options...), ищем User Include Directories и указываем:
.
./Inc
STM32L0xx/STM32L0xx_HAL_Driver/Inc



На этом самая сложная часть импорта ST HAL'а в проект завершилась. На данном этапе ваш проект должен компилироваться, собираться, загружаться и работать.
Для проверки правильно ли выставилась частота контроллера, добавим в наш main следующий функционал. Найдите вызов функции SystemClock_Config() и добавьте до и после неё соответствующий вывод:

printf ("Unset clock speed is: %dMHz ", HAL_RCC_GetSysClockFreq () / (1000 * 1000));
SystemClock_Config(); printf ("Clock speed is set to: %dMHz ", HAL_RCC_GetSysClockFreq () / (1000 * 1000));

У меня выводит следующее:
Unset clock speed is: 2MHz Clock speed is set to: 32MHz

Завершающее эту статью замечание. В коде, созданном в CubeMX вы увидите множество комментариев, в том числе и вида
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */

По задумке разработчиков из ST CubeMX должен по этим меткам определять, где пользовательский код и при последующих изменениях настройки контроллера сохранять эти изменения. Так как с SES CubeMX не работает, эти комментарии нам уже не понадобятся. Лично я их удаляю все и сразу. Вы можете абсолютно без риска поступить так же.
Разумеется выставить частоту контроллера не единственная задача на наших проектах, поэтому в следующей статье я покажу что ещё можно делать в CubeMX и как добавлять в свой проект дальнейшие изменения если вы удалили комментарии USER CODE BEGIN/END.


24/05/2019

STM32 (1): удобная среда разработки — SES: обзор, подготовка к работе


STM32 (1): удобная среда разработки — SES: обзор, подготовка к работе

В этой статье я предлагаю рассмотреть и настроить среду разработки на основе
Segger Embedded Studio (SES).

В отличие от других IDE, используемых для разработки под STM32 (в большинстве своём, основанных на Eclipse), SES, являясь проприетарным продуктом, представляет собой консистентную (целостную) IDE. SES предоставляет следующие возможности:

  • Создание проектов под микроконтроллеры (через GUI-Wizard);
  • Группировку проектов в «решения» (Solution);
  • Массу настроек проекта (так же через GUI);
  • Сборку проектов в различные выходные форматы (ELF, bin, hex);
  • Загрузку прошивки в микроконтроллер через JTAG;
  • Пошаговую отладку программы;
  • Отладочный вывод (RTT — о нём ниже);
  • Многое другое ещё не освоенное мной.

SES является кроссплатформенным продуктом (Linux, Mac, Windows), производителем предоставляется возможность бесплатно (свободно) пользоваться ей в некоммерческих целях. На момент написания этой статьи (май 2019) скачивание даже не требовало регистрации (что по нынешним меркам граничит с фантастикой).

И дополнительный бонус  в SES есть возможность включить в проект Segger RTT (Real Time Transfer, иногда переводят как Real Time Terminal)  очень быстрая и легковесная реализация отладочного вывода (printf) с устройства на компьютер и даже ввода данных с компьютера на устройство (!). Последнее я не использовал и не проверял.

В других IDE отладочный вывод надо реализовывать в коде самому, а иногда и дорабатывать JTAG (допаивать SWO  Single Wire Output, например). А если говорить о Semihosting'е, то придётся ещё дополнительное окно открывать. В компании Segger это реализовали по трём проводам (режим подключения SWD  Single Wire Debug). А сам отладочный вывод отображается в самой SES, во включающейся в режиме отладки вкладке Debug Terminal.

Из недочётов SES можно указать отсутствие поддержки ST-Link, но сделать J-Link из китайского ST-Link'а  не проблема (возможно, напишу о том как это делается).

Отмечу, что SES вся такая целостная и проприетарная не страдает недостатками подобных IDE (Keil, например). В SES можно экспортировать Makefile из проекта (правда вы скорее всего захотите его доработать, если решите работать с проектом на чистом Makefile). Можно открыть файл разметки секций и карты памяти в самом редакторе и править их руками, затем пересобирать проект с новым расположением секций. SES поставляется и работает на обычном GCC (и clang, чем компилировать  можно выбирать). Отсюда вытекают все положительные последствия — привычные настройки, ключи и поведение компилятора.

Для тех, кого я убедил попробовать SES, ссылка для скачивания: скачать SES

SES построена по принципу модульной системы — поддержка всего и вся здесь осуществляется модулями. Для того чтобы создать наш первый проект, нужно установить модуль для интересующего нас микроконтроллера и CMSIS (если, конечно вы не хотите писать код под Cortex-M, не отрываясь от документации). Модули легко устанавливаются (скачиваются из интернета) через GUI. Откройте диалоговое окно Tools —> Package Manager. 

Как видно из снимков этого диалогового окна — у меня уже установлены модули поддержки STM32L0, STM32F4 и CMSIS.

Найдите нужный вам тип контроллера и установите поддержку его. Зависимости устанавливаются автоматически. Например, для STM32 будет установлен CMSIS.
После откройте окно создания проекта (File —> New Project ... ), выберите проект под нужный тип контроллера (установленные вами пакеты поддержки контроллеров будет доступны здесь). Нажмите Next и в следующем окне укажите конкретный процессор (Target Processor) из выбранного семейства.


Если вы здесь забудете это сделать — ничего страшного, это можно будет поменять в последствии в свойствах проекта сколько угодно раз. Дальше Next, Next до упора, Finish. Всё, проект создан, вы можете подключить J-Link к компьютеру и отлаживаться.

В следующей статье я расскажу о том как можно путём некоторых изощрений (или извращений) облегчить себе жизнь. Речь пойдёт о HAL (Hardware Abstraction Level — библиотека абстракции от аппаратуры, облегчающая работу с ней).

17/04/2019

Git: базовые функции или как не потерять лицо перед коллегами (создание репозиториев)



Git.3: создание репозиториев
Git работает как с удалёнными репозиториями, так и с локальными. Скачивая себе репозиторий с сервера, вы создаёте у себя локальную копию этого репозитория. Именно поэтому эта операция называется «клонированием». Клонирование мы рассматривали в первой статье этого цикла. Скорее всего это тот метод, которым вы в своей профессиональной деятельности будете пользоваться чаще всего.
GitLab, который мы использовали в примерах из первой заметки является удалённым сервером, со своими пользователями и их правами. В примерах этих статей мы будем заливать изменения в репозиторий. У вас это не получится сделать с моим репозиторием. Для этого нам понадобилось бы учиться создавать пользователей на GitLab'е, мне давать вам права разработчика и/или публиковать свой ssh-ключ и пр. В будущих заметках мы, возможно, будем учиться как этому так и прочему функционалу GitLab'а. Пока наша задача быстро начать работать на равных с другими коллегами (и не потерять лицо перед ними). 
Для отработки примеров из заметок этого цикла мы рассмотрим как создать свой репозиторий, локальный, но позволяющий в полной мере тренироваться с функционалом Git'а. Кроме того, по работе это может вам пригодиться  возможно, когда-то вы станете настолько большим человеком, что будете создавать проекты и соответственно репозитории для них.
Репозиторий создаётся следующим образом. Создайте каталог с названием вашего репозитория, добавив к нему «.git» и перейдите в него, например:

mkdir GitTutor.git
cd GitTutor.git

Здесь расширение .git говорит о том, что создаваемый каталог будет Git-директорией, той, о которой мы говорили в первой статье. Здесь важно понять  созданный выше каталог не является каталогом с вашим проектом, в него не нужно класть ваши файлы (!).  Далее создайте пустой репозиторий следующей командой (в вашей Git-директории GitTutor.git):

git init --bare (здесь два минуса перед bare)

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

cd WorkingDirectory
git clone /Path/To/RepositoryName.git
cd RepositoryName

Путь к каталогу, в котором лежит репозиторий можно указать как полный так и относительный. Эта команда склонирует репозиторий в каталог RepositoryName.

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

05/04/2019

Git: базовые функции или как не потерять лицо перед коллегами (работа с изменениями)


Git.2: работа с изменениями
Вы скачали проект, к которому вас подключили, начали работать с ним и, как результат, наделали изменений в файлах, а может быть, уже создали новые файлы. В какой-то момент настаёт время сохранять свои изменения, то есть «залить» их в репозиторий. Рассмотрим как это делается в логичной очерёдности, которая скорее всего у вас будет возникать.
Первое  зафиксировать изменения в системе контроля версий (коммит):

git commit -am "Описание коммита"

Эта команда запишет ваши изменения в локальную базу Git'a. Ключ -a означает что нужно записать изменения всех файлов. Следует пользоваться этим ключом с осторожностью  если вы не хотели сохранять в базе изменения в некоторых файлах (например, те файлы, которые не компилируются на данный момент), то ключ -a запишет и эти файлы. В таком случае нужно указать имена файлов, изменения которых нужно зафиксировать. Ключ -m - означает указать сообщение. Если не указывать ключ -m (и последующее описание коммита, соответственно), то Git запустит редактор, в котором вам будет предложено ввести описание коммита. Пример коммита файлов поштучно:

git commit -m "Добавлены файлы main.c, main.h" main.c main.h

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

Повторю то, что уже тысячи раз писалось  несмотря на то, что Git позволяет ставить пустую строку в комментарий, не забывайте описывать свои изменения (коммиты). Когда-то их наличие может спасти вас от часов головной боли.

Теперь рассмотрим ситуацию когда вам нужно добавить новый файл в репозиторий. Если вы просто создадите файл в каталоге репозитория и сделаете commit -a, Git не запишет его в систему контроля версий (более того  вас даже не предупредят о том, что файлы были проигнорированы). А если вы сделаете commit с указанием конкретного имени нового файла, Git и вовсе выдаст ошибку. Для того чтобы Git начал считать этот файл частью репозитория, нужно сделать следующее:

git add список_имён_файлов

Если нужно добавить несколько новых файлов, можно использовать ключ -A. Что ещё более замечательно  этот ключ не только добавляет файлы, но и удаляет и перемещает (помечает к удалению или перемещению). Если быть точнее  этот ключ приводит репозиторий в соответствие с существующим деревом каталогов, что удобнее, чем добавлять, удалять и перемещать файлы поштучно вручную отдельными командами Git'a.

git add -A

NB!
Имейте в виду, что, если вы привыкли хранить какие-то файлы в каталоге репозитория, но держать их вне репозитория, ключ -A вам не подойдёт, так как добавит все файлы в репозиторий.

Здесь нужно сделать commit (без ключа -a). В ситуации когда было изменение репозитория (добавлены или удалёны файлы) нужно зафиксировать изменения:

git commit -m "Добавлены файлы ... удалены файлы... перемещены файлы..."

Если вы не закоммитите изменения репозитория (добавленные файлы), то в последствии, когда вы измените содержание файла(-ов) и попытаетесь коммитить и добавление файла и его изменение, будут проблемы. Git закоммитит файл в том состоянии, в котором он был на момент выполнения git add. Это нормально и является следствием концепции Git'а, вглубь которой мы сейчас не полезем.

NB! Важно.
О частоте коммитов и их названиях. Не ленитесь делать коммиты в базу и осмысленно их комментировать. Лично я рекомендую фиксировать изменения являющиеся какими-то этапами  например, созданы файлы, добавлены основные шаблоны, завершена сложная функция. Называть их рекомендую соответственно  «добавлены файлы main.c, main.h», «добавлен отладочный вывод». В случае, если «сложная функция» оказалась очень большой, прогресс в процессе работы над ней можно коммитить соответственно подфункциям. Когда я совсем никак не могу найти оправдание коммита, я называю его «Operating commit» (что означает «операционный коммит»).

Итог
На данный момент вы умеете скачивать (клонировать) репозиторий, записывать изменения в локальную базу (вести базу версий), и добавлять в неё новые файлы.

В следующей заметке мы рассмотрим как работать с удалённым сервером Git, что вам, практически 100%-но понадобится в вашей профессиональной деятельности.