Иногда возникает ситуация, когда надо что‑то посчитать согласно сложному алгоритму прямо на LapTop/NetTop/DeskTop PC. При этом этот алгоритм написан на Си. Это может быть цифровой фильтр, дискретное преобразование Фурье, генератор QR кода, кусок линейной алгебры с векторами, какое‑то тригонометрическое вычисление, программный модулятор, статистическая обработка случайной величины. Да всё, что угодно! То есть Вы хотите использовать язык Си как гибкий и быстрый калькулятор в Windows. Тут надо написать программу на Си.
Компьютер — это универсальный вычислитель
Или, например, Вы программируете микроконтроллеры на Си и хотите сделать симулятор прошивки в виде консольного приложения. Надо вам, например, прогнать модульные тесты платформа‑независимого кода на «большом компьютере». Потом Вам 80% вероятность, что понадобится конфигуратор прошивки по UART. Потом Вам понадобится консольное приложение Loader для загрузки по UART самой прошивки через BootLoader.
Потом понадобится крохотная PC‑утилита синхронизации для часов реального времени с PC.
Почему именно Си?
Эту LapTop утилиту стоит писать на том же языке, что и прошивку хотя бы по той причине, что можно пере использовать кодовую базу из микроконтроллеров для программирования на DeskTop(е).
Дело в том что язык программирования Си — это самый простой язык программирования из тех, что всё еще более или менее используется в промышленной разработке. По факту в Си только функции и переменные. Тут нет никаких виртуальных функций, шаблонов, делегатов и прочих концепций. В Си всё предельно просто и конкретно.
Разработка под PC это не сross компиляция, как в случае со сборкой артефактов для микроконтроллера, и тут в PC всё в какой‑то степени проще. При сборке Си приложения не надо думать о файле конфигурации компоновщика, как мы это привыкли делать для cross компиляции артефактов для микроконтроллеров (файлы *.ld).
Сперва определимся для какого Target(а) надо собрать бинарь. Надо узнать какой у нас на материнской плате установлен микропроцессор. Такую информацию может показать утилита CPUZ.
В данном случае у меня на одном компьютере Intel Celeron J4125 2Ghz, 4x cores, L1 32kByte, L2 4MByte, 10W. На другом компьютере установлен 64 разрядный микропроцессор AMD Ryzen 5 PRO 3400GE 3.30 GHz.
Но это даже не так важно. Важно какой у нас Instruction Set. В данном случае — x86–64. Это значит, что у нас 64-битный процессор. Получается, что у нас есть выбор: либо ставить 64-битный компилятор Си, либо накатывать 32-битный компилятор Си.
Когда мы пишем программу в Windows мы пишем программу не для микропроцессора. Мы пишем программу для операционной системы. Это главное отличие от программирования для микроконтроллера. Там мы писали монолитную программу для конкретного микропроцессорного ядра (ARM-Cortex M33 или PowerPC). Тут же как правило, нам приходится работать на разных процессорах, однако мы в OS Windows этого даже не замечаем.
Units: | text | bit | N | N | kByte | kByte | MByte |
№ | Процессор | bitness | cores | Threads | L1 | L2 | L3 |
1 | AMD Ryzen 5 PRO 3400GE | 64 | 4 | 8 | 32 | 512 | 4 |
2 | Intel Celeron J4125 | ? | 4 | 4 | 32 | ? | ? |
3 | Intel Core i7 8550U | 64 | 4 | 8 | 32 | 256 | 8 |
Одновременно с этим наш имитатор прошивки должен собираться и запускаться на всех окружениях настольных компьютеров: на работе, дома, в гараже.
Как и в любом деле сначала надо определится с терминологией.
Терминология
Компилятор — программа, переводящая написанный на языке программирования читаемый понятный человеком текст, в набор машинных кодов (человеко‑нечитаемый бинарный код). Программисты это люди, которые как никто приближены к абстракциям. Любой язык программирования, в частности Си — это уровень на коротком можно не думать о наборе команд данного микропроцессора. У процессора нет никаких переменных и функций. Переменные существуют только в сознании программиста. Поэтому нам нужен компилятор Си. Компилятор для каждого Cи файлика производит *.o файлик с родным для данного процессора машинным кодом.
Компоновщик (Linker)— утилита, которая склеивает бинарные файлы *.o в один монолитный исполняемый бинарный файл программы. В нашем случае это *.exe.
Артефакт — результат работы ToolChain(а). В нашем случае это *.exe файл с бинарным файлом программы.
Что надо из софтвера?
Текстовый редактор (Text Editor)
Прежде всего нужен какой‑то текстовый редактор, чтобы написать этот самый исходный текст программы на Си. Тут вариантов масса. NotePad++, Eclipse, MS VS Code.
Сборщик (Build Tools)
В компьютерах ничего само собой не происходит. Компьютеры - это самые ленивые и безынициативные сущности. Им всё надо объяснять максимально подробно и понятно. Поэтому надо явно указать из каких *.с файлов мы хотим собирать программу. Эти файлы надо как-то перечислить проиндексировать. Для этого была создана специальная утилита называемая make. Идея проста. Создается текстовый файл (Makefile) и в нем прописывается правильная последовательность вызова консольных утилит, которая приведет к тому, что на жестком диске появится исполняемый файл с программой.
Препроцессор (Preprocessor)
Это такая консольная утилита (cpp.exe), которая вставляет и заменяет куски текста. Нужна чисто ради удобства написание текста. Препроцессор позволяет полностью исключить такое нехорошее явление как дублирование программного кода. При этом препроцессору абсолютно всё равно с каким языком программирования работать (Cи, C++, DeviceTree, Graphviz, скрипты компоновки и т. п.). Для препроцессора любой язык программирования - это просто текст.
Теперь рассмотрим практические аспекты.
Какой выбрать компилятор Си кода?
Тут есть несколько бесплатных вариантов на выбор.
№ | Компилятор | разрядность генерируемого кода |
1 | СygWin | 64 |
2 | MinGW | 32 |
3 | Mingw-w64 | 64 |
4 | clang | 64 |
5 | Tiny C Compiler | 32/64 |
Для программистов микроконтроллеров я настоятельно рекомендую выбрать именно MinGW. Дело в том, что MinGW генерирует 32-битный код. Это как раз соответствует тому, что большинство микроконтроллеров (например ARM Cortex Mx) как раз 32-битные. И Вы так достигните большей совместимости между кодом прошивки микроконтроллера и консольным приложением в Windows.
Вторая причина по которой надо использовать компилятор C:\MinGW\bin\gcc.exe заключается в том, что в окружении MinGW есть заголовочный файл conio.h, который определяет функцию kbhit(). Это нам понадобится для имитации на PC текстового терминала UART-CLI консоли в stdout/stdin.
>C:\MinGW\bin\gcc.exe --version
gcc.exe (MinGW.org GCC-6.3.0-1) 6.3.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Набор утилит MinGW преспокойно скачивается и устанавливается как любая другая программа под OS Windows.
Компоновщик (Linker)
Компоновкой занимается утилита ld, которая вызывает collect2. Именно утилита collect2.exe будет выдавать ошибки, если Вы будите вызывать функции без определения их тела.
Что вообще надо из софтвера?
Вот минимальный джентельменский набор для того чтобы собрать программу на Си.
№ | Назначение утилиты | Название утилиты |
1 | Текстовый редактор | NotePad++.exe |
2 | Препроцессор | cpp.exe |
3 | Компилятор | gcc.exe |
4 | Консольная утилита для удаления файлов или папок | rm |
5 | Компоновщик | ld.exe |
6 | Утилита управления ToolChain(ом). Она решает что и в какой последовательности собирать, чтобы получить артефакты | make.exe |
7 | Утилита анализа получившегося бинаря. Аналог readelf.exe из мира программирования микроконтроллеров | PE explore.exe |
Традиционно программы на Си собирают из make скриптов. Вот минималистический makefile для сборки много файлового Си‑проекта на Windows
MK_PATH:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
#@echo $(error MK_PATH=$(MK_PATH))
INCDIR += -I$(MK_PATH)
WORKSPACE_LOC:= $(MK_PATH)../../
$(info WORKSPACE_LOC= $(WORKSPACE_LOC))
INCDIR += -I$(WORKSPACE_LOC)
BUILDDIR := $(MK_PATH)/Build
SRC_PATH := $(dir $(abspath $(dir $$PWD) ))
#@echo $(error SRC_PATH=$(SRC_PATH))
OBJDIR := $(SRC_PATH)obj
# the compiler to use
OPT += -DHAS_GCC
CC = C:\MinGW\bin\gcc.exe
# compiler flags:
# -g adds debugging information to the executable file
# -Wall turns on most, but not all, compiler warnings
CFLAGS += -g
#Generate code for 32-bit ABI
CFLAGS += -m32
CFLAGS += -std=c11 -fshort-enums
#CFLAGS += -Og
CFLAGS += -O0
#CFLAGS += -Wall
#CFLAGS +=-pedantic
#CFLAGS += -ftime-report
#files to link:
LFLAGS += -static
#LFLAGS += -lm
EXECUTABLE=firmware_simulator_x86_m
include $(MK_PATH)config.mk
ifeq ($(CLI),Y)
include $(MK_PATH)cli_config.mk
endif
ifeq ($(UNIT_TEST),Y)
include $(MK_PATH)test_config.mk
endif
ifeq ($(UNIT_TEST),Y)
include $(MK_PATH)diag_config.mk
endif
include $(WORKSPACE_LOC)code_base.mk
#@echo $(error SOURCES_C= $(SOURCES_C))
INCDIR := $(subst /cygdrive/c/,C:/, $(INCDIR))
#@echo $(error INCDIR= $(INCDIR))
OBJ := $(patsubst %.c, %.o, $(SOURCES_C))
OBJ := $(subst /cygdrive/c/,C:/, $(OBJ))
#@echo $(error OBJ= $(OBJ))
.PHONY:all
all:$(OBJ) $(EXECUTABLE)
$(EXECUTABLE): $(OBJ)
$(CC) $(CFLAGS) $(OBJ) $(LFLAGS) -o $(EXECUTABLE).exe
%.o: %.c
$(CC) $(CFLAGS) $(INCDIR) $(OPT) -c $< -o $@
clean:
rm -r $(EXECUTABLE) $(OBJ)
Тут файлы config.mk, cli_config.mk, test_config.mk и diag_config.mk это просто файлы с перечислением набора переменных окружения для выборочной сборки конкретных исходников из общей кодовой базы. Вот корневой makefile для подключения разных программных компонентов code_base.mk
ifneq ($(CODE_BASE_MK),Y)
CODE_BASE_MK=Y
$(info CodeBase Config)
#@echo $(error WORKSPACE_LOC=$(WORKSPACE_LOC))
INCDIR += -I$(WORKSPACE_LOC)
ifeq ($(THIRD_PARTY),Y)
include $(WORKSPACE_LOC)/third_party/third_party.mk
endif
ifeq ($(APPLICATIONS),Y)
include $(WORKSPACE_LOC)/applications/applications.mk
endif
ifeq ($(CONNECTIVITY),Y)
include $(WORKSPACE_LOC)/connectivity/connectivity.mk
endif
ifeq ($(CONTROL),Y)
include $(WORKSPACE_LOC)/control/control.mk
endif
ifeq ($(COMPUTING),Y)
#@echo $(error COMPUTING=$(COMPUTING))
include $(WORKSPACE_LOC)/computing/computing.mk
endif
ifeq ($(SENSITIVITY),Y)
#@echo $(error SENSITIVITY=$(SENSITIVITY))
include $(WORKSPACE_LOC)/sensitivity/sensitivity.mk
endif
ifeq ($(STORAGE),Y)
#@echo $(error STORAGE=$(STORAGE))
include $(WORKSPACE_LOC)/storage/storage.mk
endif
ifeq ($(UNIT_TEST),Y)
include $(WORKSPACE_LOC)/unit_tests/unit_test.mk
endif
endif
Конечный make файл может выглядеть например так. Вот тут и индексируют конечные *.c файлы и определяют ключевые слова для препроцессора (начинаются на HAS_XXXXX).
$(info SCHMITT_TRIGGER_MK_INC=$(SCHMITT_TRIGGER_MK_INC))
ifneq ($(SCHMITT_TRIGGER_MK_INC),Y)
SCHMITT_TRIGGER_MK_INC=Y
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
$(info Build $(mkfile_path) )
SCHMITT_TRIGGER_DIR = $(COMPUTING_DIR)/schmitt_trigger
INCDIR += -I$(SCHMITT_TRIGGER_DIR)
SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger.c
SCHMITT_TRIGGER=Y
OPT += -DHAS_SCHMITT_TRIGGER
ifeq ($(DIAG),Y)
OPT += -DHAS_SCHMITT_TRIGGER_DIAG
SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger_diag.c
endif
ifeq ($(CLI),Y)
ifeq ($(SCHMITT_TRIGGER_COMMANDS),Y)
OPT += -DHAS_SCHMITT_TRIGGER_COMMANDS
SOURCES_C += $(SCHMITT_TRIGGER_DIR)/schmitt_trigger_commands.c
endif
endif
endif
Если говорить метафорично, то Make - это как механическая коробка передач, только для сборки программ. C Make Вы можете буквально контролировать каждую опцию компилятора. Плюс Make в том, что за 60 лет своего существования это теперь самая разобранная и надежная технология из всего Computer Science.
При первой сборке проекта скорее всего выскочит вот эта ошибка. Это значит, что надо переустановить MinGW.
C:\Users\username\AppData\Local\Temp\ccT9XWou.s:54: Error: invalid instruction suffix for `push'
C:\Users\username\AppData\Local\Temp\ccfvWBon.s:19: Error: invalid instruction suffix for `pop'
Надо чтобы команда gcc.exe -dumpmachine после установки показывала mingw32
>C:\MinGW\bin\gcc.exe -dumpmachine
mingw32
Содержимое того Makefile можно представить в виде вот этой простенькой блок схемы ToolChain(а). Тут можно визуально проследить какой путь проходит *.с файл с момента написания до исполнения в Windows.
В остальном сборка на PC не отличается от сборки для микроконтроллера. В этом и достоинство сборки из Make. При работе с make cборка под любые процессоры выглядит плюс/минус одинаково. Просто по другому определенные переменные окружения: CC LD и т. п. Все те же *.mk файлы подтянутся что и в кодовой базе для прошивок.
Про то как происходит сборка прошивок из-под скриптов можно почитать в этом тексте:
Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB.
Когда собралась вся кодовая база бинарь получился всего 1,9MByte
Отладка имитатора прошивки
Артефактом сборки является файл *.exe. Его можно запустить вызвав его из командной строки cmd.
Тут происходит имитация UART-CLI терминала только вместо UART выступают файлы stdin (аналог UART-RX)/stdout (аналог UART-TX).
Можно заметить, что скорость исполнения приложения просто бешеная. За одну секунду супер-цикл успевает прокрутиться аж 11 240 590 раз! В микроконтроллерах это значение обычно было порядка 7588 раз.
Получается, что на PC приложение исполняется быстрее в 1481 раз. На три порядка быстрее!
Вывод
Сборка кода на Cи для DeskTop это весьма полезный навык при отладке микроконтроллерных прошивок. Можно отладить огромные куски платформа независимого кода: CRC, обработку строчек, триггер Шмитта, бинарные протоколы, и т.п.
Также можно собрать проект разными компиляторами: GCC, Clang. И тем самым найти и устранить больше ошибок в кодовой базе.
Как видите, в сборке Си программ на PC, ровным счетом, нет ничего сложного. Надеюсь этот текст поможет большему количеству программистов-микроконтроллеров отлаживать свои приложения на DeskTop PC и создавать, тем самым, отличные программные продукты.
Links
Компилятор GCC. Первая программа на Windows
EclipseIDE.Установка, настройка, программирование на С/Installation, configuration, programming in C
Eclipse и MinGW: как указать IDE путь к makefile и файлам проекта; разделение mingw32-make и ключей
MinGW - Minimalist GNU for Windows
Генерация зависимостей внутри программы
Сборка firmware для CC2652 из Makefile
Почему Важно Собирать Код из Скриптов
Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB
Tiny C Compiler - Summary https://savannah.nongnu.org/projects/tinycc
The LLVM Compiler Infrastructure https://llvm.org/
Эффективное использование GNU Make http://citforum.ru/operating_systems/gnumake/
Вопросы
Какой путь проходит с файл с момента написания до момента исполнения?
Что происходит между нажатием на Enter при запуске консольной утилиты в cmd и запуском функции main()?