Keil C51. Расположение переменных по абсолютному адресу (на примере строк в памяти программ)

Александр Бельченко
25 июня 2004

Известная проблема для Си-программистов встраиваемых систем — это расположение некоторых переменных по фиксированному адресу в памяти. Это может быть нужно как для доступа к портам ввода-вывода отображенным в память, так и для работы с энергонезависимой памятью.

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

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

Рассмотрим как это сделать средствами Keil C51.

Как справедливо замечает документация по Keil C51: «Язык программирования Си не поддерживает методов для явного указания адреса размещения в памяти статических или глобальных переменных». Однако при программировании встраиваемых систем такая задача возникает до неприличия часто. Поэтому создатели компилятора C51 ввели дополнительное ключевое слово _at_, при помощи которого можно явно задавать адрес размещения переменных. Однако, это ключевое слово можно использовать только совместно с переменными, размещаемыми в памяти данных. Для константных переменных в памяти программ этот трюк не работает.

Казалось бы — выхода нет? Есть! Просто проблему нужно решать с другой стороны. Размещением переменных и участков кода (т.н. сегментов программы) занимается линкер. Документация на Keil C51 рекомендует указывать самому линкеру, где нужно разместить тот или иной сегмент [1]. Для этого нужно только знать две вещи: как называется нужный сегмент и как указать линкеру нужный адрес.

Именование сегментов в Keil C51 подчиняется простому правилу: имя сегмента состоит из имени модуля и префикса, соответствующего типу данных [2]. Для сегмента переменных в памяти программ этот префикс будет ?CO?. Имя модуля совпадает с именем исходного Си-файла без расширения [3].

Таким образом, из соображений удобства, размещаемые переменные (в нашем случае строку с серийным номером прошивки) необходимо выделить в отдельный файл (например, snumber.c). Тогда сегмент со строкой будет иметь имя ?CO?SNUMBER.

Теперь укажем линкеру адрес размещения сегмента.

В настройках проекта Project — Options for Target... выбираем закладку BL51 Locate (если используется линкер BL51) либо закладку LX51 Locate (для линкера LX51). На этой закладке мы укажем адрес размещения сегмента. Пусть мы хотим разместить наш сегмент по адресу 0x07E0.

Для линкера BL51 в поле Code необходимо указать следующее:

 ?CO?SNUMBER(7E0h) 

Для линкера Lx51 в поле User Segments необходимо указать:

 ?CO?SNUMBER(C:0x7E0) 

В архиве code_at.zip (4,5 КБ) содержится пример проекта.


1. «Linker Location Controls», стр. 185 документа Cx51 Compiler (c51.pdf), из комплекта документации на Keil C51 и µVision2.
2. «Segment Naming Conventions», стр. 159 документа Cx51 Compiler (c51.pdf), из комплекта документации на Keil C51 и µVision2.
3. «Module Names», стр. 118 документа Getting Started with µVision2 (gs51.pdf), из комплекта документации на Keil C51 и µVision2. (см. перевод).