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

в 14:34, , рубрики: embedded, Rust, rust embedded, st-link v2, stm32, stm32f103, STM32F103C8T6, программирование микроконтроллеров

Привет! Хочу познакомить вас с проектом Rust Embedded. Он позволяет нам использовать язык программирования Rust для разработки под встроенные платформы (Embedded Linux / RTOS / Bare Metal).
Rust Embedded. Разработка под процессоры Cortex-M3 на примере отладочной платы STM32F103C8T6 (Black Pill) - 1

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

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

Также нам будет необходим программатор для отладочных плат STM32. В этой статье, мы будем использовать дешевый и доступный, китайский программатор ST-Link V2.
Rust Embedded. Разработка под процессоры Cortex-M3 на примере отладочной платы STM32F103C8T6 (Black Pill) - 2

Версия компилятора 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.0bin) в переменную среды "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. Там вы можете найти ссылки на несколько чатрумов для того, чтобы задать свои вопросы и получить помощь, представить свои проекты или просто пообщаться с единомышленниками.

Спасибо за внимание!

Автор: Николай Калугин

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js