Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Микропроцессорные системы. Средства разработки программного обеспечения для микроконтроллеров семейства AVR. Учебное пособие
.pdf
31
Микроконтроллеры AVR имеют расширенную гарвардскую архи-
тектуру. Это означает, что память программ доступна для чтения специальными командами. Обычно в памяти программ хранят, помимо программного кода, неизменяемые в процессе выполнения программы данные – константы, таблицы, изображения символов, данные для началь-
ной инициализации переменных и т. п.
Количество адресов в адресном пространстве памяти зависит от
того, какого размера слова адресуются. Например, память можно адресовать битами, байтами (по 8 бит в каждом), словами (по 16 бит в каждом), двойными словами (по 32 бита в каждом). В зависимости от принятой системы адресации изменяется количество адресов одного и того
же объема памяти.
В подавляющем большинстве современных микропроцессорных
систем, в том числе и в микроконтроллерах семейства AVR, принято ад-
ресовать все ячейки памяти и порты ввода-вывода в 8-битных байтах.
Следует различать физическую и логическую организацию памяти.
Физическая организация памяти определяется аппаратными
возможностями микропроцессорной системы.
Логическая организация – это возможность адресации тех или
иных программных объектов, хранящихся в памяти.
Рассмотрим разницу между физической и логической организаци-
ей памяти на примере памяти программ микроконтроллера ATMega169
семейства AVR (см. рис. 6).
Память программ данного микроконтроллера имеет объем
16 Кбайт (16 384 байт).
Физически память программ организована в виде слов, размером
16 бит (2 байта). Это означает, что ядро микроконтроллера имеет возможность считывания из памяти программ одного слова размером
2 байта за один цикл чтения. Любая команда микроконтроллера AVR
имеет размер, кратный 2 байтам – либо 2, либо 4 байта. Ядро микроконтроллера, при выборке команд, адресует память программ в словах
по 16 бит. С этой точки зрения адресное пространство памяти команд
имеет 8192 слова по 2 байта каждое.
Как упоминалось ранее, в микроконтроллерах семейства AVR
имеется возможность считывания данных из памяти программ специальными командами. Команды, которые считывают содержимое памяти
программ, адресуют пространство памяти побайтно, т. е. можно считать
любой произвольный восьмибитный байт памяти программ состоящей
из 16 384 байт.

32
Следовательно, одно и то же адресное пространство памяти программ микроконтроллера (размером 16 384 байта) рассматривается од-
новременно и как адресное пространство, состоящее из 8192 слов по 2
байта (при считывании команд), и как состоящее из 16 384 байтов (при
считывании данных из памяти программ специальными командами).
Таким образом, одна и та же физическая организация памяти
может быть представлена по-разному с точки зрения логической орга-
низации той же памяти.
В нашем примере памяти программ микроконтроллера
ATMega169:
физическая организация памяти программ – 8192 слова по 2 байта
(16 бит) каждое;
логическая организация памяти при выборке команд – 8192 слова
по 2 байта (16 бит) каждое;
логическая организация памяти при чтении данных из памяти
программ – 16 384 байта по 8 бит каждый.
Разработчику, как правило, неудобно применять различные спо-
собы адресации к одному и тому же адресному пространству, поскольку
каждый раз надо указывать, какой способ адресации применяется –
в байтах, словах, двойных словах. Несложно показать, что имеется од-
нозначное соответствие между адресами памяти в байтах, словах и
двойных словах: первый байт каждого 16-битного слова – это байт с
четным адресом; первый байт каждого 32-битного двойного слова – это
байт с адресом, кратным четырем, и т. п.
Как уже было сказано, в современных микропроцессорных систе-
мах обычно всевозможные адресные пространства памяти адресуются в
8-битных байтах. Чтобы учесть другие способы адресации, применяется
выравнивание данных в памяти.
Выравнивание данных – расположение данных в памяти с уче-
том физической организации микропроцессорной системы.
В приведенном примере памяти программ микроконтроллера
ATMega169 каждая команда имеет размер, кратный 2 байтам. Следовательно, с точки зрения адресации в байтах – каждая команда начинается
с выровненного четного адреса.
В некоторых типах микроконтроллеров обращение машинными
командами к любым многобайтным данным в памяти возможно только
по выровненному адресу.
Например, в микроконтроллерах с архитектурой ARM [7] обраще-
ние к байтам памяти возможно по произвольному адресу; обращение к
16-битным словам – только по адресу памяти, кратному двум (т. е. четно-
му); обращение к 32-битным двойным словам возможно только по адресу,

33
кратному четырем. Если попытаться осуществить чтение или запись данных по невыровненному адресу, то микроконтроллер с архитектурой
ARM запишет или считает неверные данные. Такое ограничение, конечно, не очень удобно, с точки зрения программиста, но оно позволяет
упростить схемотехнические решения и повысить быстродействие микроконтроллера. Кроме того, ЯВУ учитывают необходимость выравнива-
ния данных и располагают данные по корректным выровненным адресам.
В скриптах линкера, описывающих распределение памяти микро-
процессорной системы (см. ниже), имеется возможность указать вырав-
нивание любой секции памяти по любому произвольному значению.
7.1. Секции памяти
Компиляторы ЯВУ генерируют объектный код, который не привя-
зан к конкретным физическим адресам. Привязка программы к конкретным физическим адресам – это задача линкера. Для того чтобы
линкер «знал» о том, какую часть кода располагать, в каком адресном
пространстве и по каким адресам, компиляторы включают в объектный
файл информацию о секциях.
При трансляции исходного кода весь объектный код и данные по-
мещаются в следующие основные секции – секцию кода (.text), секцию
инициализированных данных (.data) и секцию неинициализированных
данных (.bss). Секции могут располагаться в общем случае в одном или
разных адресных пространствах, но никогда не могут пересекаться
(т. е. адрес одной секции не может совпадать с каким-либо адресом другой секции).
В каждой из этих секций могут быть различные подсекции, как за-
данные компилятором, так и определенные программистом. Например,
в микроконтроллерах семейства AVR особо выделена секция .vectors,
являющаяся частью секции .text и содержащая вектора прерываний.
Также отдельно выделяются подсекции, содержащие ссылки на проце-
дуры инициализации переменных и статических классов.
Привязка символов из различных секций к абсолютным физическим адресам памяти программ или данных осуществляется линкером.
Без использования специальных директив языков С и С++, компи-
лятор помещает в секцию .text весь исполняемый программный код,
ссылки на процедуры инициализации переменных и статических классов. В секцию .data помещаются статические переменные, которые
инициализированы в момент создания. И, наконец, в секцию .bss помещаются не инициализированные при создании переменные, стек возвра-
тов и программный стек.

34
Линкер, обрабатывая один или несколько файлов объектного кода
(*.o), объединяет секции с одинаковыми именами из различных файлов.
Затем, согласно специальному файлу-описателю распределения памяти,
осуществляется привязка каждой секции к физическим адресам памяти.
Рассмотрим для примера файл-описатель распределения памяти
для процессора семейства AVR ATMega169. Этот файл предназначен
для линкера avr-ld, входящего в пакет AVR-GCC. Приведем содержимое
этого файла полностью.
OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr")
OUTPUT_ARCH(avr:5)
MEMORY
{
text (rx) : ORIGIN = 0, LENGTH = 16K
data (rw!x) : ORIGIN = 0x800100, LENGTH = 1K
eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = 512
fuse (rw!x) : ORIGIN = 0x820000, LENGTH = 1K
lock (rw!x) : ORIGIN = 0x830000, LENGTH = 1K
}
SECTIONS
{
/* Read-only sections, merged into text segment: */
.hash : { *(.hash) }
.dynsym: { *(.dynsym) }
.dynstr: { *(.dynstr) }
.gnu.version: { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r: { *(.gnu.version_r) }
.rel.init : { *(.rel.init) }
.rela.init : { *(.rela.init) }
.rel.text : {
*(.rel.text)
*(.rel.text.*)
*(.rel.gnu.linkonce.t*)
}
.rela.text :{
*(.rela.text)
*(.rela.text.*)
*(.rela.gnu.linkonce.t*)

35
}
.rel.fini : { *(.rel.fini) }
.rela.fini : { *(.rela.fini) }
.rel.rodata :{
*(.rel.rodata)
*(.rel.rodata.*)
*(.rel.gnu.linkonce.r*)
}
.rela.rodata :{
*(.rela.rodata)
*(.rela.rodata.*)
*(.rela.gnu.linkonce.r*)
}
.rel.data:{
*(.rel.data)
*(.rel.data.*)
*(.rel.gnu.linkonce.d*)
}
.rela.data:{
*(.rela.data)
*(.rela.data.*)
*(.rela.gnu.linkonce.d*)
}
.rel.ctors : { *(.rel.ctors) }
.rela.ctors: { *(.rela.ctors) }
.rel.dtors: { *(.rel.dtors) }
.rela.dtors: { *(.rela.dtors) }
.rel.got: { *(.rel.got) }
.rela.got: { *(.rela.got) }
.rel.bss: { *(.rel.bss) }
.rela.bss: { *(.rela.bss) }
.rel.plt: { *(.rel.plt) }
.rela.plt: { *(.rela.plt) }
/* Internal text space or external memory. */
.text:{
*(.vectors)
KEEP(*(.vectors))
/* For data that needs to reside in the lower 64k of progmem. */
*(.progmem.gcc*)
/* Placing the trampolines here gives a better chance

36
that they will be in range of the code that uses them. */
. = ALIGN(2);
__trampolines_start =. ;
/* The jump trampolines for the 16-bit limited relocs will reside here. */
*(.trampolines)
*(.trampolines*)
__trampolines_end = . ;
*(.progmem*)
. = ALIGN(2);
/* For future tablejump instruction arrays for 3 byte pc devices.
We don't relax jump/call instructions within these sections. */
*(.jumptables)
*(.jumptables*)
/* For code that needs to reside in the lower 128k progmem. */
*(.lowtext)
*(.lowtext*)
__ctors_start = . ;
*(.ctors)
__ctors_end = . ;
__dtors_start = . ;
*(.dtors)
__dtors_end = . ;
KEEP(SORT(*)(.ctors))
KEEP(SORT(*)(.dtors))
/* From this point on, we don't bother about wether the insns are
below or above the 16 bits boundary. */
*(.init0) /* Start here after reset. */
KEEP (*(.init0))
*(.init1)
KEEP (*(.init1))
*(.init2) /* Clear __zero_reg__, set up stack pointer. */
KEEP (*(.init2))
*(.init3)
KEEP (*(.init3))
*(.init4) /* Initialize data and BSS. */
KEEP (*(.init4))
*(.init5)
KEEP (*(.init5))
*(.init6) /* C++ constructors. */
KEEP (*(.init6))

37
*(.init7)
KEEP (*(.init7))
*(.init8)
KEEP (*(.init8))
*(.init9) /* Call main(). */
KEEP (*(.init9))
*(.text)
. = ALIGN(2);
*(.text.*)
. = ALIGN(2);
*(.fini9) /* _exit() starts here. */
KEEP (*(.fini9))
*(.fini8)
KEEP (*(.fini8))
*(.fini7)
KEEP (*(.fini7))
*(.fini6) /* C++ destructors. */
KEEP (*(.fini6))
*(.fini5)
KEEP (*(.fini5))
*(.fini4)
KEEP (*(.fini4))
*(.fini3)
KEEP (*(.fini3))
*(.fini2)
KEEP (*(.fini2))
*(.fini1)
KEEP (*(.fini1))
*(.fini0) /* Infinite loop after program termination. */
KEEP (*(.fini0))
*(.rodata) /* We need to include .rodata here if gcc is used */
*(.rodata*) /* with -fdata-sections. */
etext = . ;
} > text
.data:{
PROVIDE (__data_start = .) ;
*(.data)
*(.data*)
*(.gnu.linkonce.d*)

38
. = ALIGN(2);
_edata =. ;
PROVIDE (__data_end = .) ;
} > data AT> text
.bss ADDR(.data) + SIZEOF (.data) : AT (ADDR (.bss)){
PROVIDE (__bss_start = .) ;
*(.bss)
*(.bss*)
*(COMMON)
PROVIDE (__bss_end = .) ;
} > data
__data_load_start = LOADADDR(.data);
__data_load_end = __data_load_start + SIZEOF(.data);
/* Global data not cleared after reset. */
.noinit :{
PROVIDE (__noinit_start = .) ;
*(.noinit*)
PROVIDE (__noinit_end = .) ;
_end = . ;
PROVIDE (__heap_start = .) ;
} > data
.eeprom :{
/* See .data above... */
KEEP(*(.eeprom*))
__eeprom_end = . ;
} > eeprom
.fuse :{
KEEP(*(.fuse))
KEEP(*(.lfuse))
KEEP(*(.hfuse))
KEEP(*(.efuse))
} > fuse
.lock:{
KEEP(*(.lock*))
} > lock

39
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the be-
ginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* DWARF 3 */

40
.debug_pubtypes 0 : { *(.debug_pubtypes) }
.debug_ranges 0 : { *(.debug_ranges) }
/* DWARF Extension. */
.debug_macro 0 : { *(.debug_macro) }
}
Мы не будем рассматривать подробно все директивы файла-
описателя распределения памяти, подробное их описание можно полу-
чить в [8]. Рассмотрим лишь основные директивы и секции.
Строки с директивами OUTPUT_FORMAT и OUTPUT_ARCH
задают формат выходного файла и тип архитектуры.
Директива MEMORY задает общее распределение памяти,
а именно – имя секции, ее начальный адрес и максимальный размер
секции. Заметим, что файл-описатель распределения памяти всегда
предполагает единое адресное пространство. Поэтому для разделения
адресных нескольких пространств введены формальные адреса.
Адресное пространство памяти программ начинается с адреса
0x000000. Секция .text находится в памяти данных и имеет максималь-
ный размер – 16 Кбайт.
Привязка секции .text к адресному пространству памяти программ
микроконтроллера ATMega169 показана на рис. 6.
Память программ микроконтроллеров семейства AVR организова-
на в виде последовательности 16-битных (двухбайтовых) слов. Это объ-
ясняется тем, что машинная команда AVR имеет размер, кратный двум
байтам. Таким образом, память программ микроконтроллера
ATMega169 имеет размер 16 Кбайт, или 8 Кслов, по 16 бит каждое. Последний адрес ячейки памяти равен 0x3FFF (в байтах), или 0x1FFF
(в 16-битных словах).
Секция .text занимает адреса памяти программ микроконтроллера,
начиная с нулевого, и в ней выделены несколько подсекций, для определенных целей. Например, секция .vectors является частью (подсекцией)
секции .text и занимает первые 0x2E байт памяти программ (адреса
0x0000 – 0x002D). Размер секции .text определяется объемом программы, поэтому секция .text занимает обычно только часть памяти программ. На рис. 6. показан вариант, когда секция .text занимает всю па-
мять программ целиком.
Адресное пространство памяти данных формально начинается с
адреса 0x800000. Формально это значит, что физического адреса
0x800000 в памяти данных микроконтроллера не существует. Физически
адреса памяти данных начинаются, как и адреса памяти программ, с ну-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
