Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Скрипт компоновщика

Программа теперь собирается успешно. Однако при попытке прошить её на Pico может появиться ошибка такого вида:

ERROR: File to load contained an invalid memory range 0x00010000-0x000100aa

Сравнение нашего проекта с проектом быстрого старта

Чтобы понять, почему прошивка не удаётся, посмотрим на скомпилированную программу с помощью инструмента arm-none-eabi-readelf. Он показывает, как компилятор и компоновщик разместили программу в памяти.

Я взял бинарный файл из проекта быстрого старта и сравнил его с бинарным файлом, который наш проект выдаёт в текущем состоянии.

Быстрый старт и наш проект
Быстрый старт и наш проект

Не нужно понимать каждую деталь этого вывода. Важно лишь заметить, что два бинарных файла выглядят очень по-разному, хотя наш код на Rust почти такой же.

Главное отличие в том, что в нашем проекте не хватает некоторых важных секций: .text, .rodata, .data и .bss. Обычно их создаёт компоновщик:

  • .text: здесь находятся сами инструкции программы (код)
  • .rodata: данные только для чтения, например константы
  • .data: инициализированные глобальные и статические переменные
  • .bss: неинициализированные глобальные и статические переменные

Сравнить их можно и командой cargo size из набора инструментов cargo-binutils.

Вывод cargo size для быстрого старта и нашего проекта
cargo size: быстрый старт и наш проект

Компоновщик (linker):

Обычно этим занимается компоновщик. Его задача — взять все части нашей программы, например скомпилированный код, код библиотек, стартовый код и данные, и объединить их в один исполняемый файл, который устройство может действительно запустить. Он также решает, куда помещать каждую часть программы в памяти: где будет код и где — глобальные переменные.

Однако компоновщик не знает автоматически структуру памяти RP2350. Нужно сообщить ему, как расположены флеш-память и ОЗУ. Это делается с помощью скрипта компоновщика. Если скрипт отсутствует или неверен, компоновщик не разместит наш код в правильных областях памяти, что и приводит к ошибке прошивки, которую мы видим.

Скрипт компоновщика

Скрипт компоновщика писать самим мы не будем. Крейт cortex-m-rt уже предоставляет основной скрипт (link.x), но он знает только о ядре Cortex-M. Он ничего не знает о конкретном микроконтроллере, который мы используем. У каждого микроконтроллера свой объём флеш-памяти, свой объём ОЗУ и своя структура памяти, а cortex-m-rt не может угадать эти значения.

Поэтому cortex-m-rt ожидает, что пользователь или крейт поддержки платы (board support crate) предоставит небольшой скрипт компоновщика под названием memory.x. Этот файл описывает структуру памяти целевого устройства.

В memory.x мы должны определить области памяти, которые есть у устройства. Минимум нужны две области: одна с именем FLASH и одна с именем RAM. Секции .text и .rodata программы размещаются в области FLASH. Секции .bss и .data, а также куча (heap) размещаются в области RAM.

Для RP2350 даташит (глава 2.2, Карта адресов) указывает, что флеш-память начинается с адреса 0x10000000, а SRAM — с адреса 0x20000000. Поэтому наш файл memory.x будет выглядеть примерно так:

MEMORY {
    FLASH : ORIGIN = 0x10000000, LENGTH = 2048K

    RAM : ORIGIN = 0x20000000, LENGTH = 512K
    SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K
    SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K
    ...
    ...
}
...
...

Для RP2350 в memory.x нужны и другие настройки. Писать их вручную не нужно. Вместо этого возьмём файл из репозитория примеров embassy-rp и поместим его в корень проекта.

Опция кодогенерации для компоновщика

Одного файла memory.x в папке проекта недостаточно. Также нужно убедиться, что компоновщик действительно использует скрипт, предоставляемый cortex-m-rt.

Для этого мы говорим Cargo передать компоновщику скрипт link.x. Передать аргумент в rustc можно несколькими способами: через .cargo/config.toml или через файл сборки build.rs. В быстром старте мы использовали build.rs, а сейчас применим подход с .cargo/config.toml. В этом файле обновите секцию target следующим образом:

[target.thumbv8m.main-none-eabihf]
runner = "picotool load -u -v -x -t elf" # Эту строку мы уже добавили
rustflags = ["-C", "link-arg=-Tlink.x"]  # Это новая строка

Запускаем на Pico

Когда всё настроено, можно прошить программу на Pico:

cargo run --release

Фух… Мы взяли обычный проект на Rust и превратили его в прошивку no_std для Pico. Наконец-то можно увидеть, как мигает светодиод.

Ресурсы