Как записать переменную по определенному адресу в Keil

Изредка возникает задача сохранить во flash памяти контрольную сумму, картинку, строчку текста, настройку. Иногда возникает задача сохранить не просто в ОЗУ, а в определенной области, чтобы для этой области например включить/выключить DCACHE. Или например иметь функцию, исполняемую из ОЗУ чтобы можно было присылать по UART и сразу исполнять новый код функции.

Рассмотрим задачу на примерах. В качестве испытуемого будет народный stm32f401ret6 со следующей адресацией flash памяти (страница 51 даташита):

#define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) //Sector 0, 16 Kbytes
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) //Sector 1, 16 Kbytes
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) //Sector 2, 16 Kbytes
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) //Sector 3, 16 Kbytes
#define ADDR_FLASH_SECTOR_4 ((uint32_t)0x08010000) //Sector 4, 64 Kbytes
#define ADDR_FLASH_SECTOR_5 ((uint32_t)0x08020000) //Sector 5, 128 Kbytes


В Keil это можно сделать разными путями. Нулевой путь — через GUI с помощью редактирования опций проекта — весьма ограниченный, не будем рассматривать.

Первый путь — ручной:
uint32_t keyFlash __attribute__((at(0x08004000))) = 0xAABBCCDD;
Здесь мы записали 32-битное число 0xAABBCCDD по адресу 0×08004000.

Иной путь заключается в использовании скеттер (scatter) файла.Теорию можно прочесть здесь. Также желательно понимать что такое объектный файл. Совсем в двух словах, после работы препроцессор→компилятор получается множество
файлов *.o
где звездочка значит любое имя. Например, из main.c получается файл main.o

Рассмотрим пример scatter файла. Данный файл был получен из дефолтного scatter файла, который генерирует сам Keil. Затем в него был добавлен execution region который я решил назвать MYREGION. В этом регионе есть секция mysection, которая ищется во всех объектных файлах. Если линкер найдёт синтаксическую единицу (читай переменную) из этой секции, то эта синтаксическая единица попадёт в регион MYREGION.
LR_IROM1 0x08000000 0x08001000 { ; load region size_region
ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}

RW_IRAM1 0×20000000 0×00000900 { ; RW data
.ANY (+RW +ZI)
}

MYREGION 0×0800C000 FIXED {
*.o (mysection)
}
}

А теперь собственно в файле main.c заведем нашу синтаксическую единицу для хранения в регионе:
const uint16_t ADC_Buf[7] __attribute__((section("mysection"))) = {0xDDDD, 0xDDDD, 0xDDDD, 0xDDDD, 0xDDDD, 0xDDDD, 0xDDDD};

И тут нас ждёт ловушка. Язык высокого уровня мы любим за оптимизацию. Включаем -O3 и наша неиспользуемая в программе константа исчезает. Тут случай, когда про toolchain можно сказать «слишком умный». Чтобы объяснить, что в секции mysection у нас всё только нужное, зайдём на вкладку Linker в Misc controls и пропишем
--keep=''main.o (mysection)''

Свойства проекта KeilСвойства проекта Keil

Изредка бывает ситуация, когда мы хотим ограничить распространения региона только на синтаксические единицы из определенного файла, пусть main.c тогда вместо
MYREGION 0x0800C000 FIXED {
*.o (mysection)
}

пишите
MYREGION 0x0800C000 FIXED {
main.o (mysection)
}

А ещё можно например все переменные из файла пусть main.c заставить жить в вашей секции вот так
MYREGION 0x0800C000 FIXED {
main.o(+RW +ZI)
}

При тестировании не забывайте делать Full chip erase, чтобы точно отчищать всю flash:

9c5a67f842fef6ce491324cd167ede81.png

Исходный код.

Кому оказалась интересна тема scatter файлов, предлагаю упражнение. Наверняка вы писали программу моргания светодиодом. Я перелагаю вам написать одну программу, которая работает верно: зажигает и тушит светодиод, А вторая программа будет зажигать и зажигать светодиод:

void blink(){
  while(1){
    on();
    delay();
    on();
    delay();
  }
}

После этого я предлагаю вам посмотреть содержимое памяти и глазами найти отличие. Далее blink () с помощью scatter файла поместите в ОЗУ, пусть оттуда выполняется. Затем перед вызовом функции blink () в main () попробуйте написать код, который починит blink (), чтобы она и зажигала и тушила. О своих (не) успехах пишите в лс или комментариях, может быть получится ещё одна заметка.

Также может быть полезно
https://radiohlam.ru/stm32_1/

Дополнительные материалы:
Как не инициализировать переменные в кейл?
Про слово FIXED

© Habrahabr.ru