Rust embedded. Stopwatch.
Rust embedded. Spi и embedded-graphics.
Rust embedded. Gpio.
Rust embedded. Сиквел.
Rust embedded.
Изучение на примерах очень важно для разработчика, с помощью примеров разработчик может прицениться к новому инструменту и понять, зачем ему этот инструмент. Какие преимущества даёт этот инструмент, почему нужно его использовать и т.д. Сегодня в статье у нас будет два примера для двух отладочных плат, один пример полностью на Rust, второй пример это композиция C и Rust.
Rust — это системный язык программирования, внимание которого сосредоточено на трёх задачах: безопасность, скорость и параллелизм. Он решает эти задачи без сборщика мусора, что делает его полезным в ряде случаев, когда использование других языков было бы нецелесообразно: при встраивании в другие языки, при написании программ с особыми пространственными и временными требованиями, при написании низкоуровневого кода, такого как драйверы устройств и операционные системы. Во время компиляции Rust делает ряд проверок безопасности. За счёт этого не возникает накладных расходов во время выполнения приложения и устраняются все гонки данных. Это даёт Rust преимущество над другими языками программирования, имеющими аналогичную направленность. Rust также направлен на достижение «абстракции с нулевой стоимостью». Хотя некоторые из этих абстракций и ведут себя как в языках высокого уровня, но даже тогда Rust по-прежнему обеспечивает точный контроль, как делал бы язык низкого уровня.
У Rust много плюсов:
- Отличная статическая типизация;
- Отсутствие сборщика мусора и возможность контролировать место размещения данных в памяти;
- Отличный встроенный статический анализатор кода, который позволяет избегать ошибок, связанных с управлением памятью и многопоточностью;
- Понятный синтаксис;
- Компилятор от разработчиков Rust со встроенным менеджером и сборщиком пакетов, системой тестов и генератором документации;
- Безопасная работа с памятью;
- Возможность применять абстракции;
- Для многих ошибок во время компиляции приводятся варианты исправления;
- Указатели можно использовать только в небезопасном коде, в безопасном коде применяются исключительно ссылки на гарантированно существующие объекты.
- Очень строгий компилятор кода;
- Предсказуемое выделение и освобождение памяти;
- Замыкания;
- Сопоставление с образцом;
- Алгебраические типы данных и т.п.
Первый пример полностью на Rust (сетевая часть тоже на Rust, можно использовать вместо LwIP) для отладочной платы stm32f746g-disco.
git clone https://github.com/mcuby/stm32f7-discovery.git
Я не разработчик этого проекта, только форкнул его и адаптировал его для запуска в VS code, оригинальные исходники здесь.
Давайте посмотрим какие пакеты включены в этот проект:
authors = ["Roman Shulenkov postmaster@mcu.by"] categories = ["embedded", "no-std"] license = "MIT OR Apache-2.0" name = "stm32f7_discovery" version = "0.1.0" edition = "2018" default-run = "polling" [dependencies] cortex-m = "0.5.2" cortex-m-rt = "0.6.4" cortex-m-semihosting = "0.3.0" alloc-cortex-m = "0.3.4" spin = "0.4.8" bitflags = "1.0.3" volatile = "0.2.4" bit_field = "0.9.0" bare-metal = "0.2.3" embedded-hal = "0.2.1" pin-utils = "0.1.0-alpha" core = {path = "core"} [dependencies.stm32f7] version = "0.3.2" features = ["stm32f7x6", "rt"] [dependencies.arrayvec] version = "0.4.7" default-features = false [dependencies.byteorder] version = "1.0" default-features = false [dependencies.smoltcp] #version = "0.5.0" git = "https://github.com/oli-obk/smoltcp.git" branch = "patch-2" default-features = false features = ["alloc", "socket-raw", "socket-udp", "socket-tcp", "socket-icmp", "proto-ipv4", "proto-dhcpv4"] [dependencies.font8x8] version = "0.2.4" default-features = false features = ["unicode"] [dependencies.futures-preview] git = "https://github.com/rust-lang-nursery/futures-rs.git" default-features = false features = ["alloc", "nightly"] [profile.release] codegen-units = 1 # better optimizations debug = true lto = true # better optimizations incremental = false # TODO: remove after https://github.com/rust-lang/cargo/pull/6610 lands in nightly [patch.crates-io.cortex-m-rt] # TODO: replace with crates.io version when new version is released git = "https://github.com/rust-embedded/cortex-m-rt.git" [dependencies.interrupture-stm32f7x6] path = "interrupture-stm32f7x6" version = "0.1.0"
Хочу отметить тут smoltcp – это автономный стек TCP/IP, управляемый событиями, который предназначен для систем реального времени с минимальным набором ресурсов. Цели его дизайна – простота и надежность. Его проектные антицелевые задачи включают сложные вычисления во время компиляции, такие как макросы или трюки типов, даже за счет снижения производительности. (Как раз его можно взять на замену дырявому LwIP, кто работает с LwIP, поймет меня, что проблем у LwIP много).
Кроме всего прочего проект для stm32f7-discovery содержит множество примеров для работы с периферией отладочной платы.
drwxrwxr-x 10 mcuby mcuby 4096 кра 14 20:10 . drwxrwxr-x 12 mcuby mcuby 4096 кра 14 08:44 .. drwxrwxr-x 2 mcuby mcuby 4096 кра 10 20:58 bin drwxrwxr-x 2 mcuby mcuby 4096 кра 14 20:06 ethernet -rw-rw-r-- 1 mcuby mcuby 2147 кра 10 20:58 future_mutex.rs drwxrwxr-x 2 mcuby mcuby 4096 кра 10 20:58 gpio -rw-rw-r-- 1 mcuby mcuby 14223 кра 10 20:58 i2c.rs drwxrwxr-x 2 mcuby mcuby 4096 кра 10 20:58 init drwxrwxr-x 2 mcuby mcuby 4096 кра 10 20:58 interrupts drwxrwxr-x 2 mcuby mcuby 4096 кра 10 20:58 lcd -rw-rw-r-- 1 mcuby mcuby 1319 кра 10 20:58 lib.rs -rw-rw-r-- 1 mcuby mcuby 4471 кра 10 20:58 mpsc_queue.rs -rw-rw-r-- 1 mcuby mcuby 4519 кра 10 20:58 random.rs drwxrwxr-x 2 mcuby mcuby 4096 кра 10 20:58 sd -rw-rw-r-- 1 mcuby mcuby 4031 кра 10 20:58 system_clock.rs drwxrwxr-x 3 mcuby mcuby 4096 кра 10 20:58 task_runtime -rw-rw-r-- 1 mcuby mcuby 6433 кра 10 20:58 task_runtime.rs -rw-rw-r-- 1 mcuby mcuby 1902 кра 10 20:58 touch.rs
По умолчанию работает проект polling.rs, как раз это пример на картинке ниже.
Существует несколько областей, где многие языки программирования слабы в плане производительности выполнения программ. Часто компромисс заключается в том, чтобы использовать более медленный язык, который взамен способствует повышению производительности программиста. Чтобы решить эту проблему, часть кода системы можно написать на C, а затем вызвать этот код, написанный на C, как если бы он был написан на языке высокого уровня. Это называется «интерфейс внешних функций» (foreign function interface), часто сокращается до FFI.
Rust включает поддержку FFI в обоих направлениях: он легко может вызвать C код, и он так же легко, как и C код, может быть вызван извне. Rust сочетает в себе отсутствие сборщика мусора и низкие требования к среде исполнения, что делает Rust отличным кандидатом на роль вызываемого из других языков, когда нужны некоторые дополнительные возможности.
Узнать больше про FFI можно здесь.
Конечно Rust прекрасен, но еще не все есть у Rust чтобы полностью отказаться в embedded от С/С++ (FatFS, EmWin, иные микроконтроллеры не stm32 и другие вещи), но написание асинхронных приложений (многопоточность) можно делать на Rust, писать бизнес логику тоже можно Rust. Мой совет, начинайте применять Rust сегодня, за ним будущее в embedded и во всем системном программировании.
Хочу отметить проект rust-anywhere для отладочной платы stm32f429i-disco. К сожалению данный проект у меня не запустился с коробки, я его немного изменил.
git clone https://github.com/mcuby/rust-anywhere.git
Убрал в lv_misc/lv_area.h включение файла string.h (нету этого изменения в моем коммите, поправьте руками данный файл).
Перешел на nightly-2019-04-26 в rust-toolchain
Изменил файлы buld.rs для своего gcc, выглядит это так:
builder = builder.clang_arg("--sysroot=/home/mcuby/Downloads/gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi");
Всё, что нужно сделать, это запустить make из каталога rust-anywhere/device. В каталоге rust-anywhere/device/build/bin будет находиться необходимый нам elf файл rust-anywhere.elf, его можно преобразовать в bin файл с помощью команды arm-none-eabi-objcopy -O binary rust-anywhere.elf rust-anywhere.bin и после прошить с помощью команды openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c “init” -c “reset init” -c “program rust-anywhere.bin exit 0x08000000”. Итог нашей работы будет, как на фото ниже.
В этом проекте композиция Rust и С, в проекте используется FFI. Сборочная система make. Процесс получения исполняемого файла выглядит следующим образом.
MAKEFILE_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) CARGO := cargo AS := arm-none-eabi-as CC := arm-none-eabi-gcc ASFLAGS := -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -g CFLAGS := -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -g -Os LDFLAGS := -flto -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 # Build directory for artifacts. BUILD_DIR := $(MAKEFILE_DIR)/build # Where compiled objects are stored. BINDIR := $(BUILD_DIR)/bin OBJDIR := $(BUILD_DIR)/obj # The Rust static library. RUST_DIR := $(MAKEFILE_DIR)/../crates/target/thumbv7em-none-eabihf/release RUST_PATH := $(RUST_DIR)/libdevice.a # The binary is the main target for this Makefile. BIN_NAME := rust-anywhere.elf BIN_PATH := $(BINDIR)/$(BIN_NAME) # Configure device HAL library. DEFINES := -DSTM32F429ZITx -DSTM32F429I_DISCO -DSTM32F4 -DSTM32 -DUSE_HAL_DRIVER -DSTM32F429xx # Include directories. INCLUDES := \ -I$(MAKEFILE_DIR) \ -I$(MAKEFILE_DIR)/include \ -I$(MAKEFILE_DIR)/CMSIS/core \ -I$(MAKEFILE_DIR)/CMSIS/device \ -I$(MAKEFILE_DIR)/HAL_Driver/Inc \ -I$(MAKEFILE_DIR)/../crates/api/include \ -I$(MAKEFILE_DIR)/../crates/lvgl/lvgl-sys # External libraries. LIBS := \ -L$(RUST_DIR) \ -llibdevice # Linker script. LINKER_SCRIPT := LinkerScript.ld # C Sources. CSRCS := \ src/main.c \ src/stm32f4xx_it.c \ src/syscalls.c \ src/system_stm32f4xx.c \ HAL_Driver/Src/stm32f4xx_hal.c \ HAL_Driver/Src/stm32f4xx_hal_cortex.c \ HAL_Driver/Src/stm32f4xx_hal_dma.c \ HAL_Driver/Src/stm32f4xx_hal_gpio.c \ HAL_Driver/Src/stm32f4xx_hal_i2c.c \ HAL_Driver/Src/stm32f4xx_hal_ltdc.c \ HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c \ HAL_Driver/Src/stm32f4xx_hal_rcc.c \ HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c \ HAL_Driver/Src/stm32f4xx_hal_sdram.c \ HAL_Driver/Src/stm32f4xx_hal_spi.c \ HAL_Driver/Src/stm32f4xx_hal_tim.c \ HAL_Driver/Src/stm32f4xx_hal_tim_ex.c \ HAL_Driver/Src/stm32f4xx_ll_fmc.c \ hal_stm_lvgl/stm32f429i_discovery.c \ hal_stm_lvgl/tft/ili9341.c \ hal_stm_lvgl/tft/tft.c \ hal_stm_lvgl/touchpad/stmpe811.c \ hal_stm_lvgl/touchpad/touchpad.c \ # Assembler sources. ASM_SRCS := startup/startup_stm32f429xx.s ALL_SRCS := $(CSRCS) $(ASM_SRCS) BIN_OBJS := $(addprefix $(OBJDIR), \ $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(patsubst %.s,%.o, $(ALL_SRCS))))) $(OBJDIR)%.o: %.c @mkdir -p $(dir $@) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -c $< -o $@ $(OBJDIR)%.o: %.s @mkdir -p $(dir $@) $(AS) $(ASFLAGS) -c $< -o $@ $(BIN_PATH): $(BIN_OBJS) $(RUST_PATH) $(LINKER_SCRIPT) @mkdir -p $(dir $@) $(CC) $(LDFLAGS) -T$(LINKER_SCRIPT) -o $@ $(BIN_OBJS) $(LIBS) # The Rust static library. $(RUST_PATH): cd $(MAKEFILE_DIR)/../crates/libdevice && $(CARGO) build --release --target=thumbv7em-none-eabihf # The target that's compiled if there's no command line arguments. all: $(BIN_PATH) # Other targets. clean: cd $(MAKEFILE_DIR)/../crates/libdevice && $(CARGO) clean rm -rf $(BUILD_DIR) .PHONY: all clean
Rust составляющая проекта расположена в rust-anywhere/crates/ имеем следующую структуру файлов:
drwxrwxr-x 4 mcuby mcuby 4096 кра 13 21:07 api drwxrwxr-x 3 mcuby mcuby 4096 кра 13 20:31 board -rw-rw-r-- 1 mcuby mcuby 29842 кра 13 20:47 Cargo.lock -rw-rw-r-- 1 mcuby mcuby 179 кра 13 20:31 Cargo.toml drwxrwxr-x 3 mcuby mcuby 4096 кра 13 20:31 drivers drwxrwxr-x 3 mcuby mcuby 4096 кра 13 20:31 ffi -rw-rw-r-- 1 mcuby mcuby 32 кра 13 20:31 .gitignore drwxrwxr-x 3 mcuby mcuby 4096 кра 13 20:31 hmi drwxrwxr-x 3 mcuby mcuby 4096 кра 13 20:31 libdevice drwxrwxr-x 3 mcuby mcuby 4096 кра 13 20:31 libsimulator drwxrwxr-x 4 mcuby mcuby 4096 кра 13 20:31 lvgl -rw-rw-r-- 1 mcuby mcuby 19 кра 13 21:01 rust-toolchain drwxrwxr-x 4 mcuby mcuby 4096 кра 13 21:08 target