Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Микропроцессорные системы. Средства разработки программного обеспечения для микроконтроллеров семейства AVR. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
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 в памяти данных микроконтроллера не существует. Физически адреса памяти данных начинаются, как и адреса памяти программ, с ну-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]