Как записать переменную по определенному адресу в 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
Изредка бывает ситуация, когда мы хотим ограничить распространения региона только на синтаксические единицы из определенного файла, пусть 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:
Исходный код.
Кому оказалась интересна тема scatter файлов, предлагаю упражнение. Наверняка вы писали программу моргания светодиодом. Я перелагаю вам написать одну программу, которая работает верно: зажигает и тушит светодиод, А вторая программа будет зажигать и зажигать светодиод:
void blink(){
while(1){
on();
delay();
on();
delay();
}
}
После этого я предлагаю вам посмотреть содержимое памяти и глазами найти отличие. Далее blink () с помощью scatter файла поместите в ОЗУ, пусть оттуда выполняется. Затем перед вызовом функции blink () в main () попробуйте написать код, который починит blink (), чтобы она и зажигала и тушила. О своих (не) успехах пишите в лс или комментариях, может быть получится ещё одна заметка.
Также может быть полезно
https://radiohlam.ru/stm32_1/
Дополнительные материалы:
Как не инициализировать переменные в кейл?
Про слово FIXED