Ошибка линковщика вроде L6220E: Execution region RW_IRAM1 size exceeds limit при сборке проекта в Keil MDK почти всегда указывает на проблему с scatter-файлом — либо регионы памяти описаны неверно, либо программа действительно не помещается в выделенный объём. Первое действие в такой ситуации — открыть вкладку Linker в настройках проекта и проверить, какой scatter-файл используется: автоматически сгенерированный или пользовательский.
Scatter-файл (файл рассеяния, scatter loading file) — это текстовое описание карты памяти микроконтроллера для компоновщика armlink. Он определяет, по каким адресам размещаются код, данные, стек и куча, какие секции копируются из Flash в RAM при старте и как инициализируются переменные. Без корректного scatter-файла прошивка либо не соберётся, либо соберётся, но будет падать при запуске.
Зачем нужен scatter-файл и как он работает
Микроконтроллеры семейства ARM Cortex-M имеют разделённую память: Flash для хранения кода и SRAM для переменных. Линковщику нужно явно указать, где физически находится каждая область и какого она размера. Именно эту задачу решает scatter-файл — он связывает логические секции программы (код, константы, инициализированные и неинициализированные данные) с реальными адресами конкретного чипа.
При сборке armlink читает scatter-файл и формирует образ прошивки: код и константы остаются во Flash, а инициализированные переменные получают два адреса — load address (где хранится начальное значение во Flash) и execution address (куда значение копируется в RAM). Копирование выполняет код инициализации из startup-файла до вызова main().
Если scatter-файл не задан вручную, Keil генерирует его автоматически на основе полей IROM и IRAM на вкладке Target. Для простых проектов этого достаточно, но при работе с несколькими банками RAM, внешней памятью или загрузчиками потребуется ручное описание.
Структура и синтаксис scatter-файла
Файл состоит из двух типов регионов. Load region описывает, где образ хранится до запуска (обычно Flash), а execution region — где секции исполняются или находятся во время работы. Минимальный файл для типичного Cortex-M выглядит так:
LR_IROM1 0x08000000 0x00080000 {
ER_IROM1 0x08000000 0x00080000 {
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 {
.ANY (+RW +ZI)
}
}
Разберём ключевые элементы синтаксиса:
- 📌
LR_IROM1 0x08000000 0x00080000— регион загрузки: стартовый адрес и максимальный размер (здесь — 512 КБ Flash, типично для STM32F4). - 📌
*.o (RESET, +First)— таблица векторов прерываний размещается первой, это обязательное требование архитектуры. - 📌
.ANY (+RO)— все секции только для чтения (код и константы) из любых объектных файлов. - 📌
RW_IRAM1 0x20000000— исполняемый регион в SRAM: сюда попадают данные+RW(инициализированные) и+ZI(обнуляемые при старте).
Адреса и размеры должны точно соответствовать документации на конкретный микроконтроллер — их нужно брать из reference manual и datasheet производителя, а не копировать из чужих проектов. У разных моделей одного семейства объёмы и даже стартовые адресы памяти могут отличаться.
Как подключить scatter-файл в проекте Keil
Настройка выполняется в диалоге Options for Target (кнопка с «волшебной палочкой» или Project → Options for Target). Порядок действий следующий: на вкладке Linker снимите галочку Use Memory Layout from Target Dialog — после этого станет активным поле Scatter File, где указывается путь к вашему файлу. Кнопка Edit открывает файл прямо из диалога.
☑️ Проверка scatter-файла перед сборкой
После сборки результат размещения удобно проверить в map-файле (он создаётся в папке вывода проекта). В разделе Memory Map of the image видно, куда линковщик реально поместил каждую секцию. Если адреса не совпадают с ожиданиями — значит, scatter-файл не подключён или содержит ошибку.
⚠️ Внимание: если галочка Use Memory Layout from Target Dialog оставлена включённой, Keil проигнорирует ваш scatter-файл и сгенерирует собственный. Это одна из самых частых причин ситуации «я изменил файл, но ничего не поменялось».
Размещение кода и данных в нестандартных областях
Ручная правка scatter-файла чаще всего нужна в трёх сценариях: исполнение кода из RAM, использование нескольких банков памяти и работа с загрузчиком. Для размещения конкретной функции или модуля в RAM в исходнике переменной или функции присваивается именованная секция, которая затем явно направляется в нужный регион:
RW_IRAM2 0x10000000 0x00010000 {
*.o (RAMCODE)
.ANY (+RW +ZI)
}
В коде такая секция указывается через атрибут компилятора, например __attribute__((section("RAMCODE"))) для ARM Compiler 6. Точный синтаксис атрибута зависит от версии компилятора (ARMCC пятой или шестой версии), поэтому сверяйтесь с документацией на используемый тулчейн.
При наличии загрузчика (bootloader) стартовый адрес приложения смещается: например, если загрузчик занимает первые сектора Flash, load region приложения начинается не с базового адреса, а со смещения. При этом важно, чтобы таблица векторов приложения оказалась по новому адресу, а вектор прерываний корректно переназначался — обычно через регистр VTOR в startup-коде или в самом загрузчике.
Типичные ошибки линковщика и их причины
Большинство проблем со scatter-файлом проявляется на этапе линковки в виде ошибок серии L6xxx. Ниже — наиболее встречающиеся из них с возможными причинами. Точная формулировка и номер ошибки могут отличаться в зависимости от версии armlink.
| Ошибка | Вероятная причина | Что проверить |
|---|---|---|
| L6220E: region size exceeds limit | Секция не помещается в заявленный размер региона | Размер региона в scatter-файле против фактического объёма в map-файле |
| L6406E: no space in execution region | Исчерпано место в RAM или Flash-регионе | Оптимизация кода, уменьшение буферов, перенос данных в другой банк |
| L6236E: no section matches selector | Селектор секции в scatter-файле не нашёл ни одной секции | Имя секции в атрибуте и в scatter-файле должны совпадать |
| L6002U / ошибки парсинга | Синтаксическая ошибка: скобки, адреса, опечатки | Структура фигурных скобок и шестнадцатеричный формат адресов |
Отдельный случай — проект собирается, но устройство зависает сразу после старта. Возможная причина — неверный стартовый адрес load region: код прошивается не туда, откуда микроконтроллер ожидает таблицу векторов. Проверьте, что адрес в scatter-файле совпадает с реальным началом Flash и с настройками алгоритма программирования на вкладке Debug → Settings → Flash Download.
⚠️ Внимание: при смещении приложения под загрузчик недостаточно изменить scatter-файл — нужно также настроить адрес прошивки во Flash и переназначение таблицы векторов через SCB->VTOR. Пропуск любого из этих шагов приводит к незапуску прошивки или падению в HardFault при первом же прерывании.
Стек, куча и специальные секции
В проектах Keil стек и куча обычно описываются символами ARM_LIB_STACK и ARM_LIB_HEAP непосредственно в scatter-файле, что позволяет явно задать их расположение и размер. Если эти регионы не описаны, линковщик размещает стек и кучу по умолчанию внутри региона RW_IRAM1, и их размеры берутся из startup-файла.
ARM_LIB_STACK 0x20020000 EMPTY -0x00001000 {
}
ARM_LIB_HEAP +0 EMPTY 0x00001000 {
}
Атрибут EMPTY резервирует область без секций, а отрицательный размер у стека означает, что он растёт вниз от указанного адреса. Размещение стека в конце RAM с ростом вниз — стандартный приём: при переполнении стек «упирается» в границу региона, что упрощает обнаружение проблемы.
Чем scatter-файл Keil отличается от linker script в GCC
В GCC-проектах (например, STM32CubeIDE) используется linker script на языке ld с секциями MEMORY и SECTIONS. Смысл тот же — описание регионов памяти и размещение секций, — но синтаксис другой. Scatter-файл armlink компактнее: регионы описываются адресом и размером, а секции выбираются селекторами вроде .ANY (+RO). При переносе проекта между тулчейнами карту памяти придётся переписать вручную, автоматической конвертации нет.
Для неинициализированных данных, которые должны переживать сброс (например, флаги причин перезагрузки), используется регион с атрибутом UNINIT. Секции в таком регионе не обнуляются стартап-кодом:
RW_NOINIT 0x2001F000 0x00001000 UNINIT {
*.o (NO_INIT)
}
Проверка результата: map-файл и отладка
После правки scatter-файла полезно пройти короткий цикл верификации. Сначала соберите проект и откройте map-файл: убедитесь, что каждая секция попала в ожидаемый регион и суммарные размеры не превышают лимитов. Затем прошейте устройство и проверьте запуск под отладчиком — программа должна дойти до main() без попадания в обработчики fault.
Если прошивка стартует нестабильно, проверьте в отладчике содержимое регистров MSP и PC сразу после сброса: они загружаются из первых двух слов таблицы векторов, и их значения должны указывать в RAM и Flash соответственно. Невалидные значения здесь почти всегда означают, что образ лежит не по тому адресу.
Часто задаваемые вопросы
Где находится scatter-файл в проекте Keil по умолчанию?
Если используется автогенерация, файл создаётся в папке вывода проекта (обычно Objects или Listings) с расширением .sct. Пользовательский файл можно хранить в любом месте — путь задаётся на вкладке Linker.
Можно ли использовать один scatter-файл для разных микроконтроллеров?
Только если у них полностью совпадают адреса и объёмы памяти. Даже внутри одного семейства (например, STM32F103 разных модификаций) объём Flash и RAM различается, поэтому файл нужно адаптировать под конкретный чип по его datasheet.
Что означает запись .ANY (+RO) в scatter-файле?
Селектор .ANY выбирает секции из всех объектных файлов проекта, а +RO фильтрует только секции чтения — код и константы. Так одна строка размещает весь исполняемый код без перечисления модулей по именам.
Почему после правки scatter-файла изменения не применяются?
Наиболее вероятная причина — включённая опция Use Memory Layout from Target Dialog на вкладке Linker: тогда Keil игнорирует внешний файл. Также проверьте, что редактируете именно тот файл, который указан в настройках проекта.
Нужен ли scatter-файл для простого учебного проекта?
Нет, для базовых проектов достаточно автоматической генерации на основе полей IROM/IRAM на вкладке Target. Ручной scatter-файл понадобится при работе с загрузчиками, несколькими областями RAM, размещением кода в нестандартных регионах или тонкой настройкой стека и кучи.