Где хранится программа в микроконтроллере

Разбираемся как устроена память микроконтроллера на примере STM32F205VGT6.

Так же мы используем CubeMX для подготовки проекта.

Где хранятся данные , где стек, где код программы?

Генерим сначала пустой проект, оставляем только пустой main().

читаем ld файл

Linker script for STM32F205VG Device with ** 1024KByte FLASH, 128KByte RAM.

ld файл это основа, здесь CubeMx прописывает все исходные параметры памяти, стека, кода. Именно его надо спокойно/внимательно изучить, здесь все очевидно на самом деле.

/* Specify the memory areas */ MEMORY < RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K >

Стек в микроконтроллерах на ядрах ARM растёт сверху вниз.

_estack = 0x20020000; /* end of RAM */ // 0x20000=131072

RAM по даташиту Up to 128 4 Kbytes of SRAM. То есть есть еще какие-то 4Kb SRAM в конце.

Стек располагается отдельно от остальных блоков памяти, в конце ОЗУ. Конец ОЗУ по мнению CubeMX это 0x20020000=..131072 примерно (128 4Kb).

Программирование микроконтроллеров: Урок 2. Организация памяти данных (регистры)

Для контроля минимальных запасов стека и кучи в CubeMX есть такие установки

_Min_Heap_Size = 0x200; /* required amount of heap */ _Min_Stack_Size = 0x400; /* required amount of stack */

(_user_heap_stack = _Min_Heap_Size _Min_Stack_Size )

list полезный по информации файл на выходе сборки

.text — /* The program code and other data goes into FLASH */ , это скомпилированный машинный код — помещается во FLASH;
.data — /* Initialized data sections goes into RAM, load LMA copy after code */ — Переменные, это помещается в RAM; и зачем-то копия помещается еще во FLASH.
.rodata — /* Constant data goes into FLASH */ — аналог .data для неизменяемых данных, но помещается во FLASH;
.bss — /* Uninitialized data section */ , глобальные и статические переменные, которые при старте содержат нулевое значение — помещаются в RAM.

Момент истины

FLASH может содержать только неизменяемые данные , т.е. их нельзя в процессе исполнения перезаписать. (FLASH (rx) ) rx = read execute . Отсюда и идет весь этот винигред.

Sections: Idx Name Size VMA LMA File off Algn 0 .isr_vector 00000184 08000000 08000000 00010000 2**0 CONTENTS, ALLOC, LOAD, READONLY, DATA 1 .text 0000015c 08000184 08000184 00010184 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 2 .rodata 00000000 080002e0 080002e8 000102e8 2**0 CONTENTS, ALLOC, LOAD, DATA 3 .init_array 00000004 080002e0 080002e0 000102e0 2**2 CONTENTS, ALLOC, LOAD, DATA 4 .fini_array 00000004 080002e4 080002e4 000102e4 2**2 CONTENTS, ALLOC, LOAD, DATA 5 .data 00000000 20000000 20000000 000102e8 2**0 CONTENTS, ALLOC, LOAD, DATA 6 .bss 00000020 20000000 080002e8 00020000 2**2 ALLOC 7 ._user_heap_stack 00000600 20000020 080002e8 00020020 2**0 ALLOC

ld файл прописывает где и куда какие данные сохранить

.bss : < /* This is used by the startup in order to initialize the .bss secion */ _sbss = .; /* define a global symbol at bss start */ __bss_start__ = _sbss; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); _ebss = .; /* define a global symbol at bss end */ __bss_end__ = _ebss; > >RAM

>RAM — это помещается в RAM !

Микроконтроллеры это просто.#2 Память

файл startup_stm32f205xx.s

Этот файл генерируется CubeMX. Он на ассемблере.

Смысл его в том, чтобы начать выполнение программы: для этого надо сначала установить указатель стека на начало стека :

Reset_Handler: ldr sp, =_estack /* set stack pointer */

Теперь для закрепления понимания поэкспериментируем

char buf[10000]; main()

эффекта изменения распределения памяти не произойдет

сделаем так : char buf[10000]=; — ничего не меняется

теперь в main сделаем так :

for(int i=0; i < sizeof(buf); i ) buf[i]=1;

и .bss увеличивается с 00000020 до 00002730 (10032!) . То есть ушло в RAM!

теперь делаем так :

const char buf[10000]=0>; main < char ch=0; for(int i=0; i < sizeof(buf); i ) char ch=buf[i]; printf(«%c»,ch);

И .rodata увеличивается с 10 до 00002784 (10116) ! То есть ушло во FLASH (кстати только при условии явной инициализации=).

Выводы такие :

Побольше const

Если RAM памяти не хватает или не хватает стека (он всегда есть часть RAM), то можно попробовать все что не меняется обозвать const , инициализировать обязательно и тогда это попадет не в RAM , а в FLASH.

section `._user_heap_stack’ will not fit in region `RAM’

Вот такая бяка появляется, когда RAM не хватает, например добавили нового кода , сторонние библиотеки .

У меня (на самом деле) эта ошибки ушла после удаления файла startupxxx.s и перегенерации заново проекта(и файла startup) из CubeMx.

Файлы для скачивания

* STM32F205VGT6_MemoryUsed [zip]
пустышка для изучения распределения памяти в контроллере STM32

Читайте также:
Какие дисциплины входят в программу сноубординга

Источник: kkmspb.ru

Память контроллера

Практически любая программа затрагивает память: там хранятся переменные, состояние программы, даже сама программа. Давайте разберёмся, как устроена память в микроконтроллере, что где хранится и как используется.

Обзор

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

Вторая, очень важная и обязательная часть — оперативная память. То место, где хранятся данные времени выполнения: данные с датчиков, сформированные тексты, картинки для экрана частично или полностью, промежуточные данные расчётов, стек и куча, всё, что угодно, что создаётся во время работы. Данные там хранятся только пока есть питание. После перезагрузки всё пропадёт.

Эта память обычно инициализируется нулями в процессе старта программы и глобальными переменными, если они есть. Это обычно скрыто от программиста.

У очень маленьких контроллеров (вроде attiny10) её может и не быть вовсе.

Иногда встречается память для хранения настроек (обычно типа EEPROM), очень небольшого размера. Все значения, которые туда записаны, сохраняются и без питания. Обычно запись может идти по одному байту, без необходимости стирания целого блока. В контроллерах серии AVR, например, она почти всегда есть, а в STM32 её нет. Ресурс перезаписей ограничен.

Иногда встречается другой тип памяти: встроенный в блок часов реального времени, которые питаются от отдельной батарейки. Обычно объём там тоже очень незначительный, десяток-другой байт, хотя бывает и больше. Пока батарейка есть, данные там будут присутствовать. Перезаписывать можно сколько угодно.

Это вся память, которая доступна ядру напрямую без каких-то особых ухищрений. В серьёзных контроллерах встречается ещё контроллер памяти, с помощью которого можно подключать внешние микросхемы разных типов: параллельный NOR, параллельный NAND, последовательный spi nand, QuadSPI, SRAM, SDRAM и дальше в зависимости от крутости контроллера.

Бывают ещё микросхемы памяти с неспециализированными под память интерфейсами: I2C, SPI и т.д., но к ним надо обращаться в общем порядке для используемого протокола. Программно передавать все команды. Впрочем, это часто используемый способ подключения дополнительной памяти для хранения настроек.

И ещё один момент: память MMC/SD, для которой, бывает, предусматривают специальный модуль с аппаратной реализацией протокола SDIO.

Архитектура контроллера

Первый важный момент, который стоит понимать, какая используется в контроллере архитектура:

1. Архитектура фон Неймана. Память программ и данных находятся в одном адресном пространстве и имеют идентичные методы доступа к ним. То есть прочитать какую-нибудь таблицу из памяти программ и из оперативной памяти можно одним и тем же кодом, просто поменяв там адрес. Это все контроллеры ARM, stm8, MSP430 и др.

2. Гарвардская архитектура. Память программ и память данных имеют свои адресные пространства и свои методы доступа к ним. Нельзя одним и тем же кодом прочитать данные и из памяти программ, и из оперативной памяти. Требуется их вычитывать с помощью специальных инструкций и механизмов ядра. Это контроллеры AVR, PIC, 8051 и др.

Второй важный момент: разрядность ядра и памяти. Восьмибитные контроллеры (AVR, PIC) для оперативной памяти имеют 8-битную шину. То есть чтобы вычитать 32-битное число, нужно четыре операции чтения. В том же арме на это уйдёт всего лишь одна операция.

Память программ

Память программ может быть внешней, может быть и внутренней. Внешняя — это или для мамонтов тридцати-сорокалетней давности, или для контроллеров и процессоров, где память должна быть огромной, какая-нибудь параллельная NOR/NAND на десятки-сотни мегабайт или даже гигабайты, как во всяких мобильных процессорах. Впрочем, процессоры могут запускаться и со всяких экзотических устройств типа MMC/SD-карт.

Внутренняя память более обычное дело. Объём небольшой: от какого-нибудь килобайта до пары мегабайт.

У современных контроллеров бывает встроенный механизм её программной перезаписи изнутри. Всякие армы, авры и прочие похожие могут обновлять свою программу или какие-то данные в ней самостоятельно. В таких контроллерах, бывает, предусматривают дополнительный блок памяти или регион памяти для специальной программы-бутлоадера, которая может запустится вместо основной и записать основную программу по командам снаружи, без необходимости использовать программатор. И это даже может быть выполнено через USB, если, конечно, этот протокол поддерживается =) Бутлоадер, в общем-то, от обычной программы ничем не отличается, кроме разве что специального назначения и возможности запуститься вместо обычной.

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

Это механизм можно использовать и для записи настроек, хотя это и не очень удобно: память программ обычно стирается большими блоками, размером в несколько килобайт, и чтобы поменять один байт надо сохранить и перезаписать весь блок. А ресурс перезаписей тратится у всего блока! Да ещё и может записаться не так. Всегда надо проверять, то ли записалось, что хотелось.

Читайте также:
Какая программа для создания таблицы на компьютере

Скорость доступа к ней обычно невелика. Но у контроллеров с низкой тактовой частотой это незаметно, всё успевается. У контроллеров с частотой в сотню-другую мегагерц, чтение может тормозить выполнение программы.

Ресурс обычно 10-100 тысяч перезаписей, хотя в дешёвых сериях встречается и ресурс всего в сотню гарантированных перезаписей.

Для контроллеров с архитектурой фон Неймана чтение будет выглядеть так:

uint8_t from_pgm = *(uint8_t)pointer_to_program_data;

const char text[] = «Hello world»;

puts( // это сработает!

В фон-нейманоской архитектуре константа text будет записана, скорее всего, в память программ и прочитана оттуда же.

Для гарвардской архитектуры такой же код сработает, но это будет трюком компилятора. Он просто подгрузит при запуске этот текст из памяти программ в оперативную память. А если памяти мало и надо экономить. то тогда используем специальные функции, которые берут данные напрямую из памяти программ. Например, в AVR это будет выглядеть так:

#include
PROGMEM const char text[] = «Hello world»;
puts_P( // Это сработает!
puts( // А это нет!

Внутри же там, если делать самостоятельно, будет что-то вроде:

void puts_P(PROGMEM const char * text) char C = pgm_read_byte(text++); // специальная функция из avr/pgmspace.h, которая умеет читать память программ
while(C) putc(C);
C = pgm_read_byte(text++);
>
>

Это очень неудобно с точки зрения программиста, но приходится выкручиваться.

Запись в эту память происходит очень по-разному. В STM32 надо в специальном модуле настроить режим записи: по байту ли, по два ли байта, или по четыре сразу, скорость доступа и разрешить запись. а дальше запись идёт так же, как в оперативку, пока не будет снят флаг разрешения записи. А модуль будет перехватывать команды записи и менять содержимое памяти.

При этом, при записи, можно записывать нули (как если бы запись была такой: *olddata = *olddata // читаем байт из адреса 12 памяти настроек

eeprom_write_byte( (uint8_t*) 0x12, 0x55); // обновим

Внешняя память по интерфейсам памяти

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

Объём обычно велик, десятки и сотни мегабайт, иначе нет смысла заморачиваться с этим, скорость большая, но всё же до скорости ядра обычно не дотягивает. Выполнение программ из этой памяти будет не самым быстрым действом.

С точки зрения программы, такая оперативная память ничем не будет отличаться от оперативной, разве что адресом и скоростью доступа. Ну или память программ, в зависимости от типа памяти.

Она не доступна с момента запуска и требуется настраивать контроллер памяти, чтобы этот доступ всё же появился: какой тип памяти, ширина линий адреса/данных, отдельные ли они, как синхронизировать и т.д.

Работа с динамической памятью (SRAM/SDRAM) после подключения не отличается от работы с оперативной. Работа со статической памятью (NAND/NOR) не отличается на чтение от обычной памяти программ, но с записью надо соблюсти ритуал, прописанный в даташите на микросхему памяти. Например, запись в NOR (условно):

#define BASE_NOR_ADDRESS 0x60000000
// Перед этим память стёрли
// Сначала пляски с бубном
*( BASE_NOR_ADDRESS + 0xAAA) = 0xAA;
*(BASE_NOR_ADDRESS + 0x555) = 0x55;
*(BASE_NOR_ADDRESS + 0xAAA) = 0xA0;
// А потом пишем!
for(size_t i = 0; i < size; i++) *(BASE_NOR_ADDRESS + address + i) = data[i];
>

Внешняя память по стандартным интерфейсам

Обычно микросхемы памяти для настроек имеют интерфейс или SPI, или I2C. В микросхемах SPI общение выглядит как «команда-данные (чтение или запись)», протокол надо уточнять в даташите, то в I2C это просто чтение или запись штатными средствами протокола. То есть регистры памяти замаплены на регистры I2C. Из различий может быть только размер адреса, который записывается перед операцией чтения/записи: один, два или три байта.

Довольно часто используется как память настроек в контроллерах, где нет встроенной памяти.

Обычно такая память выполняется по технологии EEPROM, то есть имеет все те же недостатки: ресурс 100-1000 записей, низкую скорость записи, блочное стирание. В еепром-микросхемах с индивидуальной перезаписью байт, запись одного байта может занимать несколько миллисекунд. Жуть как долго.

В SPI это может быть и побыстрее, за это время можно успеть записать целый блок, но и стирать там надо тоже блоками, нельзя взять и перезаписать один байт. Хотя подробности лучше смотреть для каждой микросхемы отдельно. В общем, медленно и печально, но зато стоит копейки. То есть можно найти микросхемы за пару-тройку рублей.

Есть серия памяти по технологии FRAM, там ресурс в несколько миллиардов перезаписей, высокая надёжность, индивидуальная перезапись каждого байта и быстрота. Правда, объём и цена там такие себе.

Естественно, такая память ядру не видна, код из неё не может выполняться, да и указатель на такую память не получишь. Надо байт за байтом её копировать с помощью приёмопередатчика в память. Или обратно.

Пример записи данных в SPI-память (условно):

void write_memory(uint32_t address, const uint8_t * data, uint32_t length) MemCS.Low(); // Опустим линию выбора микросхемы
spi_write(MEM_CODE_WRITE);
spi_write(address // младший байт адреса
spi_write((address >> 8) // средний байт адреса
spi_write((address >> 16) // старший байт адреса
for(size_t i = 0; i < length; i++) < // а теперь данные
spi_write(*data++);
>
MemCS.High(); // Сеанс окончен, линию выбора микросхемы в высокий уровень
>

Регистры периферии

Раз уж затронули тему памяти, то рассмотрим и ещё один момент: обычно все модули, какие есть в процессоре или контроллере, имеют выход в память, через которую они и управляются. То есть каждый модуль может иметь несколько десятков байт в адресном пространстве, где можно прочитать его состояние и записать какие-то команды.

Читайте также:
Абонент временно недоступен программа

Например, запись в регистр данных приёмопередатчика инициирует процесс отправки этого байта по протоколу с заданными настройками. Или же данные появятся в приёмном регистре после получения посылки, откуда их можно прочитать.

Например, условный модуль контроля выводов микросхемы:

Регистр GPDIR, адрес 0x41000000, 16 бит, отвечает за направление работы выводов номерами с 0 до 15. Бит в состоянии 0 означает вход, состояние 1 означает выход.

Регистр GPSET, адрес 0x41000002, 16 бит, отвечает за работу выводов в режиме выхода. Состояние 0 означает низкий уровень на ножке, состояние 1 высокий. Для выводов в режиме входа значение игнорируется.

Регистр GPGET, адрес 0x41000004, 16 бит, отвечает за работу выводов в режиме входа, показывает логический уровень на ножке. Установлен в 0, если на ножке низкий уровень, установлен в 1, если высокий. Запись игнорирует. Для ножек в режиме вывода дублирует значение из регистра GPSET.

И примерный код для такого описания:

// Перевели в режим выхода вывод номер 7.
*((volatile uint16_t*)0x41000000) |= 1

// Установили его состояние в низкий уровень
*((volatile uint16_t*)0x41000002) (1

Обратите внимание на использование ключевого слова volatile при работе с регистрами периферии. Оно запрещает буферизацию значения, так как значение может поменяться в любой момент.

Модуль прямой работы с памятью

В контроллерах посерьёзнее часто включается специальный модуль, имеющий часто свой выделенный доступ к разным шинам памяти, который умеет память копировать. Из одного места в другое. Он может копировать тупо от забора и до обеда, может копировать по сигналу: пришёл байт, отсчитал таймер нужное время, и т.д.

И тем самым может освобождать ядро от кучи задач, особенно, если на них стоит реагировать сразу и выдерживать точное время. Например, хотим мы измерить напряжение в сети через делитель. Говорим модулю копирования: доставай данные из модуля АЦП и складывай их по сигналу таймера (с шагом 10 мкс) в участок памяти такой-то с такой-то длиной. И всё, что остаётся сделать после запуска модуля — дождаться завершения, занимаясь своими делами. Данные будут лежать и ждать обработки.

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

Постарайтесь понять особенности работы с каждой доступной памятью вашего микроконтроллера, это недолго, и сбережёт вам много времени и нервных клеток в перспективе.

Источник: dzen.ru

Память

Организация FLASH памяти программ ATmega8

Коды программ микроконтроллера размещаются в энергонезависимом ПЗУ, выполненной по технологии FLASH. При нормальных условиях эксплуатации, FLASH-память позволяет сохранять свое содержимое в неизменном виде в течение 40 лет и допускает как минимум 10000 циклов стирания/записи.
Рис.5 Организация FLASH памяти программ ATmega8 Организация памяти программ, на примере ATmega8, приведена на рис.5. Размер FLASH-памяти этой модели составляет 8192 байт. Но, поскольку каждая команда занимает 2 или 4 байта, то по отношению к AVR точнее будет говорить об объеме в 4096 слов.

Такая размерность определяет максимально возможное число слов команд (кодов операций), доступное при написании программы. Для адресации памяти программ используется программный счетчик PC (Program Counter) (другое название: регистр или счетчик команд). Он представляет собой регистр, в котором находится текущей адрес команды во FLASH-памяти.

Таким образом, счетчик команд адресует 16-разрядные слова, а не байты, и имеет переменную разрядность, которая зависит от размера FLASH. Так у ATmega8 (4096 слов), PC имеет разрядность 12 бит, у ATmega16 (8192 слова) — 13 бит и т.д. В архитектуре AVR, PC является недоступным для программиста регистром.

Помимо своего основного назначения (хранение команд), FLASH-память AVR-микроконтроллеров также позволяет хранить пользовательские данные произвольного типа: константы, таблицы, постоянные коэффициенты и т.д. Минимальный адресуемый элемент в этом случае 1 байт. Доступ к таким данным из прикладной программы производится различными модификациями инструкции lpm.

Как правило, алгоритмы работы микроконтроллера, записанные в памяти программ, не должны изменяться во времени. Однако у AVR все-таки имеется возможность модифицировать содержимое FLASH посредством собственного программного обеспечения. Обсуждению этого вопроса посвящена глава «Самопрограммирование микроконтроллеров AVR».

Там же показан и другой способ разделения FLASH на секцию прикладной программы (Application Section) и секцию загрузчика (Boot Loader Section). Изменять содержимое памяти программ можно только из области загрузчика. В процессе программирования запись во FLASH-память происходит постранично. Размер страницы зависит от объема памяти конкретной модели и может быть равен 32, 64 или 128 слов. Перейти к следующей части: Память данных SRAM

Теги:

Котов Игорь Юрьевич Опубликована: 2012 г. 0

Вознаградить Я собрал 0 0

Оценить статью

  • Техническая грамотность

Источник: cxem.net

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru