Rust Embedded. Разработка под процессоры Cortex-M3 на примере отладочной платы STM32F103C8T6 (Black Pill)

Привет! Хочу познакомить вас с проектом Rust Embedded. Он позволяет нам использовать язык программирования Rust для разработки под встроенные платформы (Embedded Linux / RTOS / Bare Metal).


В этой статье, мы рассмотрим компоненты, которые необходимы для начала разработки под микропроцессоры Cortex-M3. После этого напишем простой пример — моргание встроенным светодиодом.

Для этого нам понадобится доступная и дешевая, китайская отладочная плата STM32F103C8T6 или Black Pill (из за черного цвета и небольшого размера). Существует также версия платы в синем цвете — Blue Pill. Её я не рекомендую, так как я слышал, что она имеет неправильный резистор, который вызывает проблемы при использовании порта USB. Второе её отличие от Black Pill в расположении пинов (выводов). Но об этом позже.

Также нам будет необходим программатор для отладочных плат STM32. В этой статье мы будем использовать дешевый и доступный китайский программатор ST-Link V2.


Версия компилятора Rust


Для начала, вам необходимо убедится что версия вашего компилятора 1.31 или более свежая. Проверить вашу версию компилятора можно введя команду:

> rustc --version

Установка компонентов


Теперь мы можем приступить к установке необходимых компонентов.

GNU Arm Embedded Toolchain


Нам будет необходим отладчик для чипов ARM — arm-none-eabi-gdb. Запустите установщик и следуйте инструкциям. В конце установки не забудьте отметить опцию "Add path to environment variable".

Ссылка на скачивание

После установки, вы можете проверить, всё ли в порядке, введя в командной строке:

> arm-none-eabi-gdb -v

Если всё в порядке, то вы увидите версию установленного компонента.

Драйвер ST-Link


Перейдём к установке драйвера для программатора ST-Link.

Следуйте инструкциям установщика и убедитесь что вы устанавливаете правильную (шестидесяти-четырёх битную или тридцати-двух битную) версию драйвера в зависимости от разрядности вашей операционной системы.

Ссылка на скачивание

ST-Link Tools


Следующий шаг — установка инструментов, необходимых для прошивки — ST-Link Tools. Скачайте архив и распакуйте в любое удобное место, также необходимо указать путь к вложенной папке bin (stlink-1.3.0\bin) в переменную среды "PATH".

Ссылка на скачивание

cargo-binutils


И наконец, установим пакет cargo-binutils, это делается двумя командами в консоли.

> cargo install cargo-binutils
> rustup component add llvm-tools-preview

На этом установка компонентов окончена.

Создание и настройка проекта


Чтобы продолжить, нам необходимо создать новый проект с именем "stm32f103c8t6". Напомню, делается это командой:

> cargo new stm32f103c8t6

Зависимости проекта и оптимизации


Подключим необходимые библиотеки в файле Cargo.toml:

[package]
name = "stm32f103c8t6"
version = "0.1.0"
authors = ["Nick"]
edition = "2018"

# Зависимости для разработки под процессор Cortex-M3
[dependencies]
cortex-m = "*"
cortex-m-rt = "*"
cortex-m-semihosting = "*"
panic-halt = "*"
nb = "0.1.2"
embedded-hal = "0.2.3"

# Пакет для разработки под отладочные платы stm32f1
[dependencies.stm32f1xx-hal]
version = "0.5.2"
features = ["stm32f100", "rt"]

# Позволяет использовать `cargo fix`!
[[bin]]
name = "stm32f103c8t6"
test = false
bench = false

# Включение оптимизации кода
[profile.release]
codegen-units = 1 # Лучшая оптимизация
debug = true # Нормальные символы, не увеличивающие размер на Flash памяти
lto = true # Лучшая оптимизация

Также, вы можете видеть что, в конце файла, была включена оптимизация кода.

Библиотека cortex-m-rt требует от нас создать файл в корневом каталоге проекта, его необходимо назвать "memory.x". В нём указывается сколько памяти имеет наше устройство и её адрес:

MEMORY
{
 FLASH : ORIGIN = 0x08000000, LENGTH = 64K
 RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

Установка целевой платформы и цели компиляции по-умолчанию


Необходимо установить целевую платформу для компилятора, это делается командой:

> rustup target add thumbv7m-none-eabi

После этого, создайте папку ".cargo", в корневом каталоге, а в ней файл "config".

Содержимое файла config:

[target.thumbv7m-none-eabi]

[target.'cfg(all(target_arch = "arm", target_os = "none"))']

rustflags = ["-C", "link-arg=-Tlink.x"]

[build]
target = "thumbv7m-none-eabi"  # Cortex-M3

В нём мы обозначили цель компиляции по умолчанию, это позволяет компилировать наш код, в ARM .elf файл, простой и привычной командой:

> cargo build --release

Пример


Чтобы убедиться в работоспособности, рассмотрим простейший пример — моргание светодиодом.

Содержимое файла main.rs:

#![deny(unsafe_code)]
#![no_std]
#![no_main]

use panic_halt as _;

use nb::block;

use stm32f1xx_hal::{
    prelude::*,
    pac,
    timer::Timer,
};
use cortex_m_rt::entry;
use embedded_hal::digital::v2::OutputPin;

// Определяем входную функцию.
#[entry]
fn main() -> ! {
    
    // Получаем управление над аппаратными средствами
    let cp = cortex_m::Peripherals::take().unwrap();
    let dp = pac::Peripherals::take().unwrap();
    let mut flash = dp.FLASH.constrain();
    let mut rcc = dp.RCC.constrain();

    let clocks = rcc.cfgr.freeze(&mut flash.acr);
    let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);

    // Конфигурируем пин b12 как двухтактный выход.
    // Регистр "crh" передаётся в функцию для настройки порта.
    // Для пинов 0-7, необходимо передавать регистр "crl".
    let mut led = gpiob.pb12.into_push_pull_output(&mut gpiob.crh);
    // Конфигурируем системный таймер на запуск обновления каждую секунду.
    let mut timer = Timer::syst(cp.SYST, &clocks)
    .start_count_down(1.hz());

    // Ждём пока таймер запустит обновление
    // и изменит состояние светодиода.
    loop {
        block!(timer.wait()).unwrap();
        led.set_high().unwrap();
        block!(timer.wait()).unwrap();
        led.set_low().unwrap();
    }
}

В первой строке, с помощью атрибута — #![deny(unsafe_code)], мы убираем возможность использования небезопасного кода.

Во второй строке, мы разместили атрибут #![no_std], он требуется, потому что мы создаём приложение для голого железа, а стандартной библиотеке необходима операционная система.

Далее идёт атрибут #![no_main], который сообщает компилятору, что мы не используем основную функцию по умолчанию с вектором аргументов и типом возвращаемого значения. Это не имеет смысла, поскольку у нас нет операционной системы или другой среды выполнения, которая вызывала бы функцию и обрабатывала возвращаемое значение.

После атрибутов, мы подключаем необходимые модули в область видимости.

Далее следует функция, в нашем случае мы, по привычке, назвали её — main(). Это точка входа в программу, она определяется атрибутом #[entry].

Внутри основной функции мы создаем дескриптор периферийного объекта, который «владеет» всеми периферийными устройствами.

После этого мы можем установить пин, отвечающий за встроенный в плату светодиод, на выход. Так как в моём случае, это плата Black Pill, светодиод в ней подключен к пину B12. Если у вас плата Blue Pill, то в ней за встроенный светодиод отвечает пин C13. Поэтому мы устанавливаем пин B12 как двухтактный выход и присваиваем его переменной led.

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

Затем, в бесконечном цикле, мы можем описать логику работы нашей программы. Она очень проста, сначала timer блокирует выполнение программы, пока не пройдёт указанное в нём время. Следующим шагом, на пин встроенного светодиода подаётся значение high, соответственно он загорается. После чего выполнение программы снова блокируется тем же таймером, на одну секунду. И пин светодиода устанавливается в значение low, следовательно он гаснет. Далее программа циклически повторяется.

Компиляция


Теперь, когда мы разобрались с кодом и логикой программы, мы можем скомпилировать её в .elf файл командой:

> cargo build --release

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

Но, для запуска программы на голом железе, нам нужен не файл .elf, а файл .bin. Файл .bin это образ программного обеспечения, которое может быть записано побайтно в память микроконтроллера. Файл .elf может быть конвертирован в файл .bin с помощью команды:

> cargo objcopy --bin stm32f103c8t6 --target thumbv7m-none-eabi --release -- -O binary stm32f103c8t6.bin

Теперь наш бинарный файл готов для прошивки.

Подключение


Прошивать плату мы будем с помощью программатора ST-Link V2. Для этого нужно сначала подсоединить его к плате.

На корпусе программатора есть схема расположения пинов разъема. Необходимо обратить внимание на вырез в разъёме, он также отображен на схеме, что позволяет понять как производить подключение.

Подключите пины 3.3V и GND программатора с соответствующими пинами на плате. Пин SWDIO подключите к пину DIO, а пин SWCLK к пину CLK.

После подключения платы к программатору, мы можем подключить ST-Link к компьютеру.

Внимание! Перед подключением отключите все другие источники питания от платы, если таковые имеются, в противном случае это может привести к повреждению платы или компьютера.

Теперь мы можем проверить подключение:

> st-info --descr

В консоли, мы должны увидеть строку "F1 Medium-density device".

Прошивка


Опционально, перед прошивкой, можно очистить память платы, если на ней что то записано:

> st-flash erase

И наконец, мы можем начать прошивку:

> st-flash write stm32f1.bin 0x8000000

Если всё выполнено правильно, вы должны увидеть мигающий светодиод. Поздравляю!

Заключение


Итак, чтобы прошить плату, необходимо использовать данный набор команд введённых последовательно:

> cargo build --release
> cargo objcopy --bin stm32f103c8t6 --target thumbv7m-none-eabi --release -- -O binary stm32f103c8t6.bin
> st-flash erase
> st-flash write stm32f1.bin 0x8000000

Чтобы упростить себе жизнь, мы можем создать .bat файл с данными командами, и запускать его из консоли.

От автора


Если вас интересуют подобные руководства, добро пожаловать на мой YouTube канал.
Также, недавно, я завёл Telegram канал, на котором публикую различные переводы руководств и книг, новости, юмор и другие вещи связанные с языком программирования Rust. Там вы можете найти ссылки на несколько чатрумов для того, чтобы задать свои вопросы и получить помощь, представить свои проекты или просто пообщаться с единомышленниками.

Спасибо за внимание!
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 37

    +4

    Можно сильно сократить размер бинарника, если отказаться от использования .unwrap(). Он использует стандартные средства форматирования, которые не оптимизированы для генерации короткого кода.


    Подробнее можно посмотреть здесь: https://jamesmunns.com/blog/fmt-unreasonably-expensive/

      +1

      Как-то сложно вы прошиваете, можно же прописать runner = "arm-none-eabi-gdb -q -x openocd.gdb" в .cargo/config и прошивать с помощью gdb командой cargo run --release, без всякой конвертации в bin. Пример этого есть в https://github.com/rust-embedded/cortex-m-quickstart


      И ещё: почему в features указан stm32f100, а не stm32f103?

        0

        Интересно.
        А есть листинг, посмотреть что получилось в ассемблере?

          –2
          Makefile руками написать — не так-то и сложно. Зато удобно. И стандарт какой-никакой…
            +3

            А он-то тут зачем?

            0
            плата «черного цвета… небольшого размера… в синем цвете… я слышал… неправильный резистор» — возник вопрос, перекраска, подгонка размера, и изменение правильности резистора изменит ситуацию?
            +1
            чтобы не плодить сущности, я бы называл это «черной blue pill». Просто с black pill я связываю почти такую же черную платку, но уже на STM32F411. Не сильно дороже, но сильно мощнее. (хотя конкретно сейчас использую именно blue pill)
              +2

              Подозреваю, это просто китайские гении маркетинга придумали "дешёвую блэк пилл", впаяв на новую плату старый контроллер. О таком странном гибриде только сейчас узнал.


              UPD. извините, соврал. Картинки "black pill STM32F411" и "black pill STM32F103" заметно различаются.

                0
                а есть еще Black Pill STM32F401CCU6. Тоже с Type-C на борту
                +1
                Было бы хорошо обновить статью рекомендациями из комментариев и выложить обновленную версию в гитхаб, которую можно было бы использовать как Quick Start.
                  0

                  Я правильно понимаю что функционал xargo теперь доступен для ARM прямо из дефолтного cargo?

                    0

                    Да. Xargo теперь нужен в очень редких случаях.

                      0

                      Строго говоря, небольшое подмножество, но если для большинства случаев его за глаза. IIRC, Jorge Aparicio (автор xargo и большого количества крейтов для embedded) хотел втащить ещё больше функций, связанных с пересборкой libcore, liballoc, libcollections etc, в cargo.

                        0

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

                      +4

                      Для себя вижу 3 проблемы в эмбеддед расте:


                      1. Отвратительная документация и оформление репозиториев пакетов. Для запуска проекта используется несколько пакетов, у которых есть зависимости. У всех этих пакетов абсолютно одинаковый ридми на гитхабе (под копирку) и совсем непонятно что и как использовать.


                      2. Нет поддержки CAN-шины. Было уже 3 попытки написать поддержку CAN в embedded hal, но пока безуспешно. Было большое обсуждение, почему rust embedded еще нельзя использовать в боевых системах и отсутствие CAN — это один из пунктов.


                      3. Группа разработчиков из embedded rust — это как закрытый тайный орден, который общается в matrix. Хочешь что-то обсудить — разбирайся с matrix и кривыми клиентами.


                      4. Нет приличной RTOS, tockOS и RTFM не в счет.



                      НО, все 4 недостатка говорят только о текущем состоянии. Зато уже изначально есть terminal workflow (привет CI, автоматизация релизов и прочие вещи, полезные в IoT), который прекрасно работает на windows, mac и linux. И работать с такой инфраструктурой гораздо приятнее, чем с родными инструментами STM.


                      Вообще, остается добавить дефолтных файлов конфигурации для дебага в VS Code в cortex-m-quickstart + документацию и будет вообще отлично!

                        0
                        Так сколько проблем вы видите?)
                        Было большое обсуждение, почему...

                        Где об этом почитать?
                        –4
                        в моём понимании, развращённом windows-инструментарии, всех вот этих 9-ти шагов быть не должно

                        Должно быть — написал код, нажал кнопочку, сбилдилось, залилось в кристалл, далее отлаживаешься через тот же ST Link

                        А тут только на подготовку надо потратить полдня…

                        Я пользуюсь средой разработки Mikroe Pascal for ARM (можно и MiroE C++ for ARM), там как раз всё так и есть, как я описал — пишешь код, F9, отладка.

                        ПыСы. Начинайте минусить
                          +1

                          Что, и даже Mikroe Pascal for ARM отдельным шагом устанавливать не пришлось?

                            0
                            его — пришлось, но одной кнопкой. Далее просто работаешь
                            Но можно не устанавливать, а просто распаковать ZIP-дистрибутив в папку

                            Линуксоиды помешаны на миллионах зависимостях, и не знают, как это — работать с комфортом
                              0

                              в зависимостях нет ничего ужасного, есть есть надежный и удобный менеджер зависимостей. В расте он есть, в линусе/мак ос х он есть, а вот в винде — нет.

                                –1
                                а в винде он не нужен, потому что обычно всё, что нужно, уже входит в дистрибутив
                                  0
                                  а что вы минусуете-то, парни? Тот же IAR, например, из коробки имеет всё и сразу, и не требует для своей работы доставить ещё стотыщ пакетов и прочего мусора
                                    –1

                                    Это работает только до тех пор, пока вам не понадобится сторонняя библиотека, которую "забыли" включить в дистрибутив.

                                    0

                                    Что именно входит в дистрибутив? В дистрибутив чего?


                                    Помимо libc и самого языка Си в проекте могут использоваться библиотеки. Например, RTOS, FS и пр. Каким образом они подключаются и компилируются в C? Да как придётся. Хорошо, если есть CMake или Make в репозитории. Иначе бери, копируй файлы, компилируй всё сам. Так что непонятно что вас смутило в системе, которая значительно упрощает управление зависимостями проекта.

                                      0
                                      меня не смутило ничего
                                        0
                                        Например, RTOS, FS и пр. Каким образом они подключаются и компилируются в C?

                                        Они как правило в SDK идут, у stm32 в stm32cubeide, у esp32 в esp idf.
                                          0

                                          Ну, нельзя же запихать все зависимость внутрь IDE. Что-то да останется снаружи. Плюс, если они "идут", это значит что внутри IDE сделан такой же, только неявный, package manager, который всё это скачивает и встраивает в структуру.

                                            –1
                                            нельзя, но основное можно.
                                            Я не фанат настраивать окружение по пол дня для каждого маленького проекта.
                                              0

                                              А что из перечисленного тут следует настраивать полдня?

                                                –3
                                                Всё то что перечислено выше, от
                                                Версия компилятора Rust

                                                И, как минимум, до
                                                Пример


                                                Плюс к этому ещё какую то IDE бы желательно, с отладкой, ведь уже 2020 год.

                                                П.С. И после всего оказывается что ещё и CAN не доступен)
                                                  0
                                                  И после всего оказывается что ещё и CAN не доступен
                                                  Что значит недоступен? То, что готового драйвера нет, и придётся самому с регистрами работать?
                                                  Ну так это статья "пишем хелловорлд". Для нормальных боевых проектов всё равно придётся писать свой код со своими азартными играми и продажными женщинами.
                                                    0

                                                    Да, как то не очень хочется с регистрами работать) ну вот совсем. И если этого можно избегать, я избегаю. И да, ещё на vhdl пишу, работы с регистрами мне там хватает.

                                                0

                                                Маленький проект проще скопипастить с другого маленького проекта со всеми его настройками.
                                                Ну и в кейлах-иарах в package'ах, мягко говоря, далеко не все возможные библиотеки представлены.

                                                  +1

                                                  Хорошо конечно так, если просто скопировать. У меня как не проект, так новый микроконтроллер. Из последних за 2019год esp8266, esp32, stm32, stm8, n76e003, kendryte k210

                                +1
                                очень сложно описано, думаю, можно проще все сделать
                                  –4
                                  не читал, но осуждаю.
                                  на самом деле, прочитал всё, но не понял — зачем? я знаю, что еще и форт есть и фортран для STM32. но зачем???

                                  Only users with full accounts can post comments. Log in, please.