Rust Embedded - Разработка под процессоры Cortex-M3 на примере отладочной платы STM32F103C8T6 (Black Pill)
Rust Lang Сообщество - Николай КалугинОглавление
- Вступление
- Версия компилятора Rust
- Установка компонентов
- Создание и настройка проекта
- Пример
- Компиляция
- Подключение
- Прошивка
- Заключение
Вступление
Привет! Хочу познакомить вас с проектом Rust Embedded. Он позволяет нам использовать язык программирования Rust для разработки под встроенные платформы (Embedded Linux / RTOS / Bare Metal).
В этой статье, мы рассмотрим компоненты, которые необходимы для начала разработки под микропроцессоры Cortex-M3. После этого, напишем простой пример - моргание встроенным светодиодом, с использованием доступной и дешевой китайской отладочной платы STM32F103C8T6 или Black Pill, из за черного цвета и небольшого размера. Существует также версия платы в синем цвете - Blue Pill. Её я не рекомендую, так как я слышал что она имеет неправильный резистор, который вызывает проблемы при использовании порта USB. Второе её отличие от Black Pill в расположении пинов (выводов). Но об этом позже.
Также нам будет необходим программатор для отладочных плат STM32. В этой статье, мы будем использовать дешевый и доступный, китайский программатор ST-Link V2.
![](/file/3baa04850042567a51ae8.jpg)
![](/file/dd1b2a241e39b4c6922df.jpg)
Версия компилятора 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 = [ # LLD (shipped with the Rust toolchain) is used as the default linker "-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 файл с данными командами, и запускать его из консоли.
Благодарю за внимание! Если вас интересуют подобные статьи, рекомендую обратить внимание на Telegram канал - @rust_lang_ru. На котором вы можете найти статьи, переводы, новости и другие интересные материалы по языку Rust, там же вы можете обсудить всё это с единомышленниками.
Также имеется YouTube канал.
Подписывайтесь!