Вы находитесь на странице: 1из 270

УДК 621.

398
В 858

Утверждено учебным управлением МЭИ (ТУ) в качестве учебного пособия


для студентов и слушателей факультета повышения квалификации
специалистов промышленности и преподавателей вузов

Подготовлено на кафедре автоматизированного электропривода


в учебно-научно-консультационном центре «Texas Instruments-МЭИ»

Рецензенты:
докт. техн. наук, проф. А.Б. Красовский (МГТУ им. Н.Э. Баумана);
канд. техн. наук, доц. Т.В. Ремизевич (МЭИ) (ТУ)

Авторы:
А.С. Анучин, Д.И. Алямкин, А.В. Дроздов, В.Ф. Козаченко, А.С. Тарасов

Встраиваемые высокопроизводительные цифровые системы управления.


В 858 Практический курс разработки и отладки программного обеспечения сигнальных
микроконтроллеров TMS320x28xxx в интегрированной среде Code Composer
Studio: учеб. пособие / А.С. Анучин, Д.И. Алямкин, А.В. Дроздов и др.; под
общ. ред. В.Ф. Козаченко. — М.: Издательский дом МЭИ, 2010. — 270 с.

ISBN 978-5-383-00471-5

Курс практического освоения технологии разработки и отладки программного обеспечения


высокопроизводительных систем управления на базе цифровых сигнальных микроконтроллеров
со встроенной специализированной периферией на языке высокого уровня С/С++ в интегрирован-
ной компьютерной среде разработки Code Composer Studio. Раcсчитан на самостоятельную работу
или работу в лаборатории со стандартными средствами фирмы Texas Instruments, такими как оце-
TM
ночные платы eZdsp F2812/28335. Может быть использован для обучения студентов и слушате-
лей курсов повышения квалификации специалистов промышленности и преподавателей вузов.
Ориентирован на разработчиков встраиваемых систем управления комплектных электропри-
водов, силовых преобразователей и систем питания. Предназначен для студентов, обучающихся
по специальностям: «Электропривод и автоматика ПУ и ТК»; «Электрический транспорт»; «Элек-
трооборудование автономных объектов»; «Промышленная электроника».

© Московский энергетический институт


ISBN 978-5-383-00471-5 (технический университет), 2010
ПРЕДИСЛОВИЕ

Последние годы характеризуются бурным развитием цифровых техноло-


гий во всех областях техники. Микроконтроллеры стали адаптированными
под преимущественную область применения за счет интеграции на кристалл
большого числа специальных периферийных устройств, способных авто-
номно решать типовые задачи приложения с минимальными затратами
ресурсов центрального процессора. Специализированные микроконтроллеры
для применения в электроприводе, энергетике и силовой электронике полу-
чили общепринятые названия Motor Control (управление двигателями),
Motion Control (управление движениями), Power Control (управление мощ-
ностью). Усложнение алгоритмов цифрового управления электрооборудова-
нием потребовало перехода к более производительным архитектурам пост-
роения центрального процессора — к специализированным сигнальным
микроконтроллерам высокой производительности (вплоть до 300 млн оп/с)
с большим объемом встроенной флэш-памяти программ (до 128 К слов),
памяти данных (до 34 К слов), с рядом интегрированных на кристалл пери-
ферийных устройств (аналого-цифровые преобразователи, менеджеры собы-
тий, квадратурные декодеры, контроллеры различных интерфейсов и т.п.),
возможностью программирования и отладки непосредственно на языке
высокого уровня С/С++.
Разработка современных систем встроенного управления комплектными
электроприводами, силовыми преобразователями и источниками питания
требует знания основ модульного проектирования и отладки программного
обеспечения (ПО) на Ассемблере и языке высокого уровня С/С++ с исполь-
зованием интегрированных компьютерных систем разработки, допускаю-
щих отладку в реальном времени, в том числе непосредственно в изделии.
Роль языка С/С++ возрастает из-за сложности системы команд современных
микроконтроллеров, на изучение которой тратится значительное время.
Использование языка С/С++ с большим количеством уже существующих
стандартных и прикладных библиотек позволяет значительно ускорить раз-
работку, сократить время выхода новых изделий на рынок. Это особенно
важно в условиях современной России, пытающейся активно развивать
энергосберегающие, обрабатывающие и перерабатывающие технологии,
машиностроение.
Настоящее учебное пособие представляет собой практический курс основ
программирования встроенных систем управления на языке высокого
уровня С/С++, ориентированное на подготовку и переподготовку специалис-
тов в области современного электрооборудования и силовой электроники.
Курс базируется на одних из лучших в отрасли специализированных сиг-
нальных микроконтроллерах TMS320x28xxx фирмы Texas Instruments, явля-
3
ющейся мировым лидером в области сигнальных процессоров (более 50 %
всего мирового рынка). Он построен таким образом, что позволяет в течение
ограниченного времени (до 100 ч самостоятельной работы или работы в
лаборатории) получить начальные навыки программирования на языке
С/С++ в интегрированной среде разработки и отладки программного обеспе-
чения Code Composer Studio (CCS) с учетом особенностей архитектуры,
встроенной памяти и периферии современных микроконтроллеров, принци-
пов организации системы прерываний.
Студенты и слушатели курсов переподготовки получают навыки програм-
мной реализации типовых задач управления, таких как цифровые регуля-
торы и фильтры, блоки преобразования координат, навыки создания и
отладки полноценных структур подчиненного регулирования координат при-
водов постоянного тока и векторного управления приводами переменного
тока. Осваивают способы подключения специальных библиотек (таких как
IQmath), обеспечивающих эффективную реализацию систем управления
реального времени.
Изучение собственно языка С/С++ не является главной целью книги (для
этого существует много хороших учебников) — оно ведется на фоне изуче-
ния архитектуры специализированных микроконтроллеров, принципов
работы и функциональных возможностей встроенных периферийных уст-
ройств, методов их программирования, современной технологии модульной
разработки и отладки проектов, в которых часть задач может быть реализо-
вана на Ассемблере, а часть — на языке С/С++, методов отладки проектов в
реальном времени с развитыми встроенными возможностями визуализации
динамических процессов вплоть до построения графиков, фазовых портре-
тов и гармонического анализа выходных сигналов. Все перечисленные воп-
росы относятся к теоретической части и рассматриваются постепенно в про-
цессе освоения студентами и слушателями курсов технологии разработки
программ в среде CCS. Это первая книга на русском языке, которая отвечает
на вопрос: как разрабатывать программное обеспечение для специализиро-
ванных сигнальных микроконтроллеров Motor Control, являясь поэтапным
руководством для начинающих.
Курс состоит из девяти глав, каждая из которых представляет собой отдель-
ное исследование. Теоретический материал чередуется с контрольными воп-
росами и практической самостоятельной работой на оборудовании.
В книге использованы богатый международный опыт и методика прове-
дения фирмой Texas Instruments семинаров по семейству микроконтроллеров
‘C2000, опыт обучения студентов по курсу «Микропроцессорные системы
управления» на кафедре Автоматизированного электропривода МЭИ, опыт
переподготовки специалистов промышленности по курсу «Проектирование
и эксплуатация систем прямого микроконтроллерного управления двигате-
лями и технологическим оборудованием» в Учебно-научно-консультацион-
ном центре «Texas Instruments — МЭИ», многолетний опыт разработки
заказных цифровых систем управления для отечественных серий преобразо-
вателей частоты и комплектных электроприводов фирмы «НПФ Вектор».
4
В книге применяются следующие соглашения по умолчанию:
• все вновь вводимые термины из области процессорной техники выде-
лены курсивом;
• ключевые слова и директивы Ассемблера и языка С++ выделены жир-
ным шрифтом;
• наиболее важные моменты, на которые следует обратить особое внима-
ние, подчеркнуты.
Главы 1, 2 написаны В.Ф. Козаченко, главы 3, 4 — Д.И. Алямкиным,
главы 5, 7 — А.С. Тарасовым, глава 6 — А.С. Анучиным, главы 8, 9 —
А.С. Анучиным, А.С. Тарасовым, А.В. Дроздовым. Концепция построения
курса и общая редакция — В.Ф. Козаченко.
Авторы благодарят студентов 5-го и 6-го курсов кафедры АЭП МЭИ,
которые в порядке лабораторного практикума изучили материал книги и сде-
лали ряд ценных замечаний, особенно М.М. Лашкевича, который тщательно
оттестировал большинство приведенных в книге примеров программ. При-
меры программ можно скопировать с официального сайта кафедры АЭП
www.aep.mpei.ac.ru.

Авторы

5
Глава 1
УСТРОЙСТВО И ФУНКЦИОНАЛЬНЫЕ ВОЗМОЖНОСТИ
TM
ОЦЕНОЧНОЙ1 ПЛАТЫ eZdsp F2812
ДЛЯ РАЗРАБОТКИ И ОТЛАДКИ ПРОГРАММНОГО
ОБЕСПЕЧЕНИЯ В ИНТЕГРИРОВАННОЙ СРЕДЕ
CODE COMPOSER STUDIO. ВВЕДЕНИЕ В СРЕДУ CCS.
ТЕХНОЛОГИЯ РАСПРЕДЕЛЕНИЯ ПАМЯТИ

1.1. ОЦЕНОЧНАЯ ПЛАТА eZdspTM F2812


Устройство, назначение и основные технические
характеристики оценочной платы
Оценочная (отладочная) плата eZdspTM F2812 представляет собой авто-
номное процессорное устройство, созданное для быстрого освоения аппа-
ратных и программных возможностей новейших высокопроизводительных
сигнальных микроконтроллеров семейства ‘C2000 фирмы Texas Instruments
(TI). Микроконтроллеры ‘C2000 являются специализированными устрой-
ствами, архитектура и встроенная периферия которых максимально адапти-
рованы для эффективного решения задач прямого цифрового управления
двигателями и силовыми преобразователями энергии, решения задач комп-
лексной автоматизации производства.
Оценочная плата — это также удобное аппаратное средство, позволяю-
щее быстро изучить архитектуру современных сигнальных процессоров,
систему их команд, устройство и режимы работы встроенных периферийных
устройств. Она является базовым аппаратным средством для выполнения
всех работ курса.
Плата поставляется вместе с интегрированной средой Code Composer
Studio (CCS) для компьютерной разработки и отладки программного обеспе-
чения на Ассемблере и на языке высокого уровня С/С++. В дословном пере-
воде интегрированная среда CCS называется «Студией Композитора Кодов»,
так как предоставляет широчайший спектр самых современных возможно-
стей по модульной разработке и отладке программного обеспечения сигналь-
ных микроконтроллеров, на порядок облегчая труд программиста.
Несмотря на то что плата eZdspTM F2812 предназначена в первую оче-
редь для обучения студентов и специалистов промышленности, она может
применяться в проектных и конструкторских организациях для предвари-
тельной оценки возможностей реализации любого реального проекта на
6
базе цифровых сигнальных процессоров TMS320F2812. Плата поставляется
с микроконтроллером TMS320F2812, работающим на максимальной частоте
(150 МГц), имеющим максимальный объем встроенной памяти и богатый
набор встроенных периферийных устройств. Разъемы расширения позво-
ляют подключить к плате любую дополнительную периферию в соответ-
ствии со спецификой конкретного приложения. При этом для конкретного
приложения оцениваются не только необходимая производительность цент-
рального процессора, нужный объем памяти программ и данных, но и необ-
ходимость использования дополнительных периферийных устройств. Такой
подход существенно сокращает сроки проектирования новых микропроцес-
сорных устройств управления.
Плата eZdspTM F2812 снабжается драйвером сопряжения с интегрирован-
ной средой Code Composer Studio через параллельный порт персонального
компьютера. На ней также дополнительно устанавливается стандартный
разъем для подключения по интерфейсу JTAG внутрисхемного эмулятора и
выполнения «профессиональной» отладки (без ограничений).
Архитектура и система команд новейших микроконтроллеров Texas
Instruments ‘C283xx с двумя параллельно работающими конвейерами для
вычислений с фиксированной и плавающей точками (начало поставок 2008),
ориентированных на самые сложные задачи управления, полностью совмес-
тима с архитектурой центрального процессора ‘C281x, установленного на
плате eZdspTM F2812. Совместимость поддерживается также для микроконт-
роллеров ‘C280x, ориентированных на управление силовыми источниками
питания. Таким образом, оценочная плата является хорошей начальной
базой для всех специалистов, занятых разработкой современных систем
встроенного цифрового управления двигателями, источниками вторичного
питания, системами комплексной автоматизации производства.
Основные технические характеристики платы:
• центральный процессор — TMS320F2812;
• скорость выполнения операций — 150 млн оп/с (150 MIPS);
• объем встроенной на кристалл памяти данных, оперативное запомина-
ющее устройство — ОЗУ (RAM) — 18К 16-разрядных слов;
• объем встроенной на кристалл памяти программ, флэш-памяти — 128К
16-разрядных слов;
• объем внешней по отношению к микроконтроллеру (расширенной) ста-
тической памяти СОЗУ (SRAM), расположенной на плате, — 64К 16-разряд-
ных слова;
• частота тактового генератора 30 МГц. Коэффициент внутреннего умно-
жения тактовой частоты — 5;
• разъемы расширения для ввода аналоговых сигналов и ввода/вывода
цифровых сигналов, в том числе для сопряжения со встроенными перифе-
рийными устройствами;
• встроенный контроллер JTAG-интерфейса в соответствии со стандар-
том IEEE 1149.1 для подключения внутрисхемных эмуляторов;
7
• встроенный на плату внутрисхемный эмулятор JTAG-интерфейса IEEE
1149.1 с выходом на параллельный порт персонального компьютера;
TM
• драйвер оценочной платы eZdsp F2812 для работы с интегрирован-
ным пакетом TI Code Composer Studio для разработки и отладки програм-
много обеспечения;
• питание от одного внешнего источника питания +5 В. Преобразователь
переменного тока 220 В в постоянный ток напряжением +5 В (сетевой адап-
тер) входит в комплект поставки.
TM
Функциональная схема оценочной платы eZdsp F2812 показана на
рис. 1.1, а ее конструкция — на рис. 1.2. Главный интерфейс платы — это
JTAG, через него обеспечивается как загрузка, так и отладка программного
обеспечения. Имеются также разъемы расширения для подключения вне-
шних устройств пользователя.
Оценочная плата имеет габариты 13,5×7,6 см и выполнена по многослой-
ной технологии.
Для подключения питания +5 В служит разъем P6. Максимальный потреб-
ляемый от сетевого адаптера ток не должен превышать 500 мА. Если при
подключении к плате дополнительных устройств сопряжения потребляемый
ток может превысить это значение, то сетевой адаптер должен быть заменен
более мощным.
Оценочная плата имеет минимально необходимый набор оборудования,
достаточный для разработки и отладки программного обеспечения с исполь-
зованием интегрированной среды Code Composer Studio. Для работы с пери-

eZdspТМ F2812
аналоговых входов
Порт расширения
Параллельный

Аналого-цифровой
порт

преобразователь
параллельный порт
JTAG-интерфейс/
Контроллер

JTAG

Статическое
XZCS6AND7n ОЗУ
внешний

64К*16
JTAG

Микро-
контроллер
Порт расширения

TMS320F2812
ввода/вывода
цифрового

Дискретные
порты
Тактовый ввода/
XTAL1/OSCIN
генератор вывода

TM
Рис. 1.1. Блок-схема оценочной платы eZdsp F2812

8
TM
Рис. 1.2. Конструкция оценочной платы eZdsp F2812

ферией обязательно потребуется подключение к плате дополнительных уст-


ройств, в том числе преобразователей уровней сигналов и специальных
средств защиты входов процессора от недопустимых напряжений.
На плате eZdspTM F2812 установлены два планарных зеленых светоизлу-
чающих диода. Первый из них DS1 индицирует наличие на плате напряже-
ния питания 5 В, а второй DS2 — состояние битового выхода процессора XF,
который может программно модифицироваться. Вы можете управлять этим
светодиодом по своему усмотрению для индикации хода выполнения про-
граммы.
Для удобства подключения цифрового осциллографа на плате имеются
две тестовые точки — цифровой TP1 и аналоговой TP2 земли, которыми
будем пользоваться при исследовании работы периферийных устройств.

Практическая работа

1. Найдите на оценочной плате: центральный процессор, расширенную


статическую память, контроллер JTAG-интерфейса в параллельный порт.
2. Определите число слоев печатной платы.
3. Найдите разъем для подключения питания, разъем для подключения
компьютера, разъем для подключения внутрисхемного эмулятора.
4. Найдите светодиоды DS1 (питание) и DS2 (вывод XF микроконтрол-
лера).
5. Найдите тестовые точки цифровой TP1 и аналоговой TP2 земли.
9
Спецификация разъемов оценочной платы eZdspTM F2812
Оценочная плата имеет пять соединителей для расширения ее функцио-
нальных возможностей с помощью дополнительно подключаемых уст-
ройств. Первый вывод каждого соединителя имеет квадратную позолочен-
ную окантовку, в отличие от остальных выводов, имеющих круглую
окантовку. Большинство соединителей представляет собой поле отверстий-
контактов, в которые вставляется первичная часть разъема и пропаивается с
обратной стороны платы. Эта операция должна выполняться квалифициро-
ванным монтажником. Модуль расширения пользователя подключается к
ответной части одного или нескольких разъемов.
В процессе выполнения практических работ будете неоднократно обра-
щаться к приведенной справочной информации для того, чтобы определить,
куда выведены те или иные сигналы микроконтроллера (например, для
осциллографирования). Назначение соединителей представлено в табл. 1.1.
Табл и ц а 1.1
Назначение соединителей в оценочной плате eZdspTM F2812

Соединитель Назначение
P1 JTAG-интерфейс для отладки с помощью стандартного внутрисхемного эмулятора
P2 Разъем расширения памяти и периферии
P3 Параллельный порт контроллера JTAG-интерфейса для отладки от компьютера
через стандартный параллельный порт
P4/P8/P7 Интерфейс сопряжения со встроенными периферийными устройствами
P5/P9 Интерфейс аналогового ввода/вывода
P6 Разъем подключения источника питания

JTAG-интерфейс, P1
Отладочная плата, изображенная на рис. 1.3, имеет 14-выводной JTAG-
интерфейс P1 для подключения внутрисхемного эмулятора и выполнения
профессиональной отладки программного обеспечения без каких-либо огра-
ничений. Спецификация сигналов на разъеме — стандартная для всех сиг-
нальных процессоров Texas Instruments и приведена в табл. 1.2. Отсутствую-
щий шестой вывод выполняет функцию ключа.

P1
13 11 9 7 5 3 1 1

14 12 10 8 4 2 2

JTAG

Рис. 1.3. Расположение выводов JTAG-интерфейса (вид платы сверху)

10
Табл и ц а 1.2
Спецификация выводов JTAG-интерфейса (разъем P1)

Вывод 13 11 9 7 5 3 1
Сигнал EMU0 TCK TCK-RET TDO PD (+5V) TDI TMS
Вывод 14 12 10 8 6 4 2
Сигнал EMU1 GND GND GND Нет вывода GND TRST-

Интерфейс расширения памяти и периферии, P2


Интерфейс имеет 60 выводов, показанных на рис. 1.4, назначение кото-
рых приведено в табл. 1.3.
На разъем P2 выведены сигналы 16-разрядной двунаправленной шины дан-
ных (XD0-XD15), 19-разрядной однонаправленной шины адреса (XA0-XA18),
шины управления (XWE, XRD# и др.), шины питания и цифровой земли,
сигналы выборки кристаллов в зонах 0 и 1 внешней памяти (XZCS0AND1#),
а также зоне 2 (XZCS2#). Это позволяет подключить к микроконтроллеру
дополнительную память или периферийные устройства.

P2
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 41 43 45 47 49 51 54 56 58 60
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 42 44 46 48 50 52 53 55 57 59

Рис. 1.4. Расположение выводов на разъеме расширения P2 (вид платы сверху)

Та бл и ца 1.3
Спецификация сигналов разъема расширения P2

Вывод 1 2 3 4 5 6 7 8 9 10
Сигнал +5V +5V XD0 XD1 XD2 XD3 XD4 XD5 XD6 XD7
Вывод 11 12 13 14 15 16 17 18 19 20
Сигнал XD8 XD9 XD10 XD11 XD12 XD13 XD14 XD15 XA0 XA1
Вывод 21 22 23 24 25 26 27 28 29 30
Сигнал XA2 XA3 XA4 XA5 XA6 XA7 XA8 XA9 XA10 XA11
Вывод 31 32 33 34 35 36 37 38 39 40
Сигнал XA12 XA13 XA14 XA15 GND GND XZCS0AND1# XZCS2# XREADY 10K
Вывод 41 42 43 44 45 46 47 48 49 50
Сигнал XR#W 10K Pullup XWE XRD# +3.3V XNMI/INT13 XRS#/RS# Не подкл. GND GGND
Вывод 51 52 53 54 55 56 57 58 59 60
Сигнал GND GND A16 A17 A18 XHOLD# XHOLDA# Не подкл. Не подкл. Не подкл.

11
Параллельный порт/JTAG-интерфейс, P3
TM
Оценочная плата eZdsp F2812 имеет обычный параллельный порт для
подключения к персональному компьютеру (стандарты ECP, EPP и SPP8 дву-
направленной передачи данных). Со стороны оценочной платы параллель-
ный интерфейс преобразуется в JTAG-интерфейс, обеспечивая доступ к про-
цессору со стороны компьютера при отладке. Со стороны компьютера
необходимо установить соответствующий драйвер, который будет позволять
интегрированной среде Code Composer Studio загружать и отлаживать про-
граммы в реальном времени, используя параллельный интерфейс. Драйвер
поставляется вместе с отладочной платой.

Интерфейс встроенных периферийных устройств, P4/P8/P7


Интерфейс обеспечивает сопряжение внешних устройств со встроенными
на кристалл микроконтроллера периферийными устройствами. Схема распо-
ложения выводов на разъемах P4, P8, P7 представлена на рис. 1.5.
Назначение сигналов на разъемах P4, P8 показано в табл. 1.4. На разъем
P8 выведены сигналы наиболее часто используемых периферийных уст-
ройств:
• двух модулей полного сравнения менеджеров событий А и В, обеспечи-
вающих управление силовыми ключами двух трехфазных мостовых инвер-
торов напряжения (PWM1—PWM6 и PWM7—PWM12), в том числе в
режиме широтно-импульсной модуляции базовых векторов;
• двух дополнительных ШИМ-генераторов (модулей сравнения), реали-
зованных на базе таймеров 1 и 2 (T1PWM/T1CMP и T2PWM/T2CMP);
• модуля аппаратной блокировки ШИМ-сигналов, формируемых менед-
жерами событий А и В по сигналам внешних аварий (T1CTRIP/PDPINTA# и
T3CTRIP/PDPINTB#);
• трехканального модуля захвата внешних событий, например, для
сопряжения с потенциальными датчиками положения на элементах Холла
(CAP1, CAP2, CAP3) и «квадратурного» декодирования, например, сигналов
с импульсных датчиков положения (QEP1, QEP2, QEPI1);
• таймера/счетчика в режиме внешнего тактирования (TCLKINA,
TDIRA);
• синхронного периферийного интерфейса (SPISIMOA, SPISOMIA,
SPICLKA, SPISTEA), предназначенного для расширения дискретного

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 P4
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 P8
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39
1 2 3 4 5 6 7 8 9 10 P7

Рис. 1.5. Расположение выводов разъемов P4, P8, P7 (вид сверху)

12
ввода/вывода, организации межпроцессорных коммуникаций, подключения
дополнительных периферийных устройств (ЦАП, АЦП и т.д.);
• последовательного коммуникационного интерфейса A (SCITXDA,
SCIRXDA) для поддержки стандартных последовательных каналов связи
(RS-232, RS-422, RS-485);
• контроллера промышленной шины CAN (CANTXA, CANRXA), пред-
назначенного для организации локальных промышленных сетей на базе
CAN-интерфейса, построения распределенных мультимикропроцессорных
систем, расширения ввода/вывода.
Кроме того, на этот разъем выведен сигнал XCLKOUT внутреннего так-
тового генератора процессора, позволяющий не только убедиться в работо-
TM
способности платы eZdsp F2812, но и тактировать внешние устройства.

Табли ц а 1 . 4
Спецификация сигналов на разъемах P4 и P8
P4 P4 P8 P8 P8 P8
Вывод Сигнал Вывод Сигнал Вывод Сигнал
1 +5 V 1 +5 V 2 +5 V
2 XINT2/ADCSOC 3 SCITXDA 4 SCIRXDA
3 MCLKXA 5 XINT1#/XBIO# 6 CAP1/QEP1
4 MCLKRA 7 CAP2/QEP2 8 CAP3/QEPI1
5 MFSXA 9 PWM1 10 PWM2
6 MFSRA 11 PWM3 12 PWM4
7 MDXA 13 PWM5 14 PWM6
8 MDRA 15 T1PWM/T1CMP 16 T2PWM/T2CMP
9 NC 17 TDIRA 18 TCLKINA
10 GND 19 GND 20 GND
11 CAP5/QEP4 21 NC 22 XINT1N/XBIO#
12 CAP6/QEPI2 23 SPISIMOA 24 SPISOMIA
13 T3PWM/T3CMP 25 SPICLKA 26 SPISTEA
14 T4PWM/T4CMP 27 CANTXA 28 CANRXA
15 TDIRB 29 XCLKOUT 30 PWM7
16 TCLKINB 31 PWM8 32 PWM9
17 XF/XPLLDIS# 33 PWM10 34 PWM11
18 SCITXDB 35 PWM12 36 CAP4/QEP3
19 SCIRXDB 37 T1CTRIP/PDPINTA# 38 T3CTRIP/PDPINTB#
20 GND 39 GND 40 GND

13
На разъем P4 выведены сигналы менее часто используемых периферий-
ных устройств:
• многоканального последовательного буферизированного порта
(MCLKXA, MCLKRA, MFSXA, MFSRA, MDXA, MDRA), обеспечивающего
как синхронную, так и асинхронную передачу и предназначенного для рас-
ширения ввода/вывода и организации межпроцессорных коммуникаций;
• модуля захвата второго менеджера событий B (CAP5/QEP4,
CAP6/QEPI2);
• второго последовательного коммуникационного интерфейса B
(SCITXDB, SCIRXDB);
• дополнительных ШИМ-генераторов (модулей сравнения), реализован-
ных на базе таймеров 3 и 4 (T3PWM/T3CMP и T4PWM/T4CMP);
• второго таймера/счетчика в режиме внешнего тактирования
(TCLKINB, TDIRB).
Кроме того, на этот разъем выведен сигнал XF — битового флага, управ-
ляемого непосредственно программой пользователя.
На разъем P7 (табл. 1.5) выведен ряд специальных входов менеджеров
событий A и B, которые могут использоваться в качестве сигналов блоки-
ровки выходов ШИМ-генераторов: либо при приёме сигнала аварии в сило-
вом преобразователе, либо при приёме сигнала превышения выходным
током заданного порогового значения (функция автоматического токоограни-
чения). При этом сигналы C1TRIP#, C2TRIP#, C3TRIP# могут блокировать
соответственно стойки 1, 2 и 3 первого инвертора, а сигналы С4TRIP#,
C5TRIP#, C6TRIP# — стойки 4, 5, 6 второго инвертора.
Возможен также независимый приём сигналов аварий или блокировки
ШИМ для всех четырех дополнительных каналов ШИМ-генератора, реали-
зованных на базе таймеров T1, T2, T3, T4 — сигналы T1CTRIP—T4CTRIP.
Это обеспечивает идентификацию аварии вплоть до конкретного силового
ключа.
Кроме того, имеется возможность сборки по «монтажному ИЛИ» всех
аварийных сигналов в один сигнал PDPINTA# или PDPINTB# и одновремен-
ной блокировки сразу всех ШИМ-выходов менеджера событий А или B соот-
ветственно.

Табл и ц а 1.5
Спецификация сигналов на разъеме P7
Вывод 1 2 3 4 5
Сигнал С1TRIP# C2TRIP# C3TRIP# T2CTRIP#/EVASOC# C4TRIP#
Вывод 6 7 8 9 10
Сигнал C5TRIP# C6TRIP# T4CTRIP#/EVBSOC# NC GND

14
Аналоговый интерфейс, P5/P9
Для сопряжения с источниками аналоговых сигналов используется
30-выводной аналоговый интерфейс. Схема расположения выводов на разъ-
емах P5/P9 представлена на рис. 1.6.
Встроенный в микроконтроллеры аналого-цифровой преобразователь
(ADC) является 12-разрядным последовательным АЦП с устройством
выборки-хранения и двумя входными 8-канальными мультиплексорами А и
В, входы которых выведены на разъемы P9 (ADCINA0-7) и P5 (ADCINB0-7),
представленные в табл. 1.6.
Общее число аналоговых входов — 16. Формат ввода аналоговых сигналов
0—3 В. Дополнительно на разъем выведены сигналы встроенных в аналого-
цифровой преобразователь опорных источников питания ADCREFP (2 В),
ADCREFM (1 В) и сигналы аналоговой земли (GND). Все входы АЦП явля-
ются «голыми» — они не содержат ни входных фильтров низкой частоты для
защиты от помех, ни схем преобразования уровней, ни схем защиты от пере-
полюсовки и превышения допустимого входного напряжения. Соблюдайте
осторожность при подключениях!

ANALOG
P5
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
1 3 5 7 9 11 13 15 17 19
P9

Рис. 1.6. Схема расположения выводов аналогового интерфейса (вид сверху)

Табли ц а 1 . 6
Спецификация сигналов на разъемах P5, P9

P5 P9 P9
Сигнал Сигнал Сигнал
Вывод Вывод Вывод
1 ADCINB0 1 GND 2 ADCINA0
2 ADCINB1 3 GND 4 ADCINA1
3 ADCINB2 5 GND 6 ADCINA2
4 ADCINB3 7 GND 8 ADCINA3
5 ADCINB4 9 GND 10 ADCINA4
6 ADCINB5 11 GND 12 ADCINA5
7 ADCINB6 13 GND 14 ADCINA6
8 ADCINB7 15 GND 16 ADCINA7
9 ADCREFM 17 GND 18 VREFLO
10 ADCREFP 19 GND 20 NC

15
Контрольные вопросы
1. К каким точкам на плате можно подключить осциллограф, чтобы проверить, рабо-
тает ли центральный процессор, а именно генерируется ли выходная тактовая частота?
2. К каким точкам на плате нужно подключить осциллограф, чтобы проверить пра-
вильность формирования программой пользователя выходного битового сигнала XF?
3. Можно ли подключить к оценочной плате переменный резистор и сигнал с его
движка подать на вход АЦП? В каком диапазоне напряжений сможете менять уровень
входного аналогового сигнала?

Режимы работы оценочной платы eZdspTM F2812


На плате имеется семь переключателей, которые позволяют установить
нужный режим работы устройства. Все переключатели для удобства пользо-
вателя расположены в одном компактном месте, схема их размещения пред-
ставлена на рис. 1.7.
Переключатели JP1 и JP2 являются основными. Они позволят задать
режим работы центрального процессора с точки зрения использования
памяти, а также разрешить программирование встроенной флэш-памяти.
Переключатель JP9 рекомендуется использовать только квалифицированным
пользователям:
JP1 Режим работы процессора:
1-2 Режим микропроцессора (используется внешняя память);
2-3 Режим микроконтроллера (используется внутренняя встроенная
память).
По умолчанию используется режим микроконтроллера.
JP2 Напряжение программирования флэш-памяти:
1-2 Подается (всегда в этой позиции — вместо переключателя пере-
мычка);
2-3 Не подается.
JP9 Запрещение фазосдвигающей логики — умножителя частоты (PLL)
1-2 Работа PLL разрешена (положение по умолчанию);
2-3 Работа PLL запрещена.

JP7 – Boot Mode 3 JP1- XMP/MC#


1 2 3 1 2 3
JP8 – Boot Mode 2 JP11 – Boot Mode 1
1 2 3 1 2 3
JP9 – PLL Disable JP12 – Boot Mode 0
1 2 3 1 2 3
JP2 – Flash Power Supply
1 2 3

Рис. 1.7. Схема расположения переключателей и их положение по умолчанию

16
Сигнал (PLL) защелкивается при сбросе системы и далее может исполь-
зоваться как битовый выход XF. Фазосдвигающая логика обеспечивает
умножение частоты внешнего тактового генератора на программно заданный
коэффициент масштабирования.
Четыре других переключателя JP7, JP8, JP11 и JP12 задают режим работы
процессора при включении питания — режим начальной загрузки.
Микроконтроллеры ‘C28 имеют встроенное, запрограммированное на
заводе-изготовителе ПЗУ, в котором находится программа начальной
загрузки процессора (boot-loading software). Эта программа автоматически
выполняется сразу после сброса процессора (включения питания) и тести-
рует состояние нескольких выводов процессора (битовых портов
ввода/вывода GPIO), чтобы определить, какой режим начальной загрузки
аппаратно установил пользователь. Состояние этих выводов связано с
положением переключателей JP7, JP8, JP11, JP12 на оценочной плате, что
позволяет задать один из нескольких возможных режимов начальной
загрузки. Выбор режима представлен в табл. 1.7.

Табл и ц а 1.7
Выбор режима начальной загрузки переключателями JP7, JP8, JP11, JP12

Аппаратно-заданная конфигурация выводов

Режим начальной GPIOF4 GPIOF12 GPIOF3 GPIOF2


загрузки
JP7 JP8 JP11 JP12
BOOT3 BOOT2 BOOT1 BOOT0
(SCITXDA) (MDXA) (SPISTEA) (SPICLK)
Запуск предварительно загруженной про- Установлен Х Х Х
граммы во флэш-памяти: FLASH. Переход
по адресу 0x3F 7FF6. В этом месте до сброса
процессора должна быть записана команда
перехода на программу пользователя
Вызов загрузчика по последовательному Снят Установлен Х Х
синхронному интерфейсу SPI: SPI_BOOT.
Программа загружается из внешнего последо-
вательного ПЗУ (SPI EEPROM)
Вызов загрузчика по последовательному асин- Снят Снят Установлен Установлен
хронному интерфейсу SCI-A: SCI_BOOT.
Программа загружается по коммуникацион-
ному каналу связи, например, от ПК по интер-
фейсу RS-232
Запуск программы, ранее загруженной во Снят Снят Установлен Снят
встроенное ОЗУ (банк H0): H0. Передача
управления по адресу 0х3F 8000
Запуск программы, записанной в одно- Снят Снят Снят Установлен
кратно-программируемое ПЗУ: OTP. Пере-
дача управления по адресу 0x3D 7800
Вызов программы загрузчика по параллель- Снят Снят Снят Снят
ному порту Port B: PARALLEL_BOOT

17
Так, пользователь может задать режим выполнения программы, уже запи-
санной ранее во флэш-память (режим FLASH), например, с помощью внут-
рисхемного эмулятора по интерфейсу JTAG. В этом случае собственно
загрузка программы не производится и управление сразу передается про-
грамме пользователя. Можно задать и режим выполнения программы, ранее
загруженной в оперативную память микроконтроллера (режим H0). Эта
опция используется по умолчанию при выполнении практических работ.
Имеются и другие варианты, в частности можно передать управление загруз-
чику программы из компьютера в микроконтроллер по последовательному
коммуникационному интерфейсу RS-232 (режим SCI_BOOT).
Дополнительно загрузочное ПЗУ микроконтроллера содержит таблицы
стандартных функций, таких как SIN, COS, предназначенные для ускорения
математических вычислений.

Практическая работа
1. Проверьте состояние всех переключателей на оценочной плате. Убеди-
тесь, что оно соответствует положению по умолчанию.

Контрольные вопросы
1. Какой режим начальной загрузки выберете для «перепрошивки» рабочей про-
граммы во флэш-память микропроцессорной системы управления преобразователем час-
тоты после отладки в ОЗУ, если система управления имеет выход на интерфейс RS-232, а
внутрисхемный эмулятор недоступен?
2. Понадобится ли для этой операции специальное программное обеспечение на ком-
пьютере?
3. В какое положение необходимо будет установить переключатели режима управле-
ния начальной загрузкой после завершения «перепрошивки», чтобы запустить модифи-
цированную рабочую программу в реальном времени?

1.2. ОРГАНИЗАЦИЯ ПАМЯТИ.


КАРТА ПАМЯТИ ОЦЕНОЧНОЙ ПЛАТЫ eZdspTM F2812

Память и карта памяти оценочной платы


Основной объем программной памяти и памяти данных расположен на
кристалле микроконтроллера:
• 128 К слов программной флэш-памяти;
• два блока ОЗУ однократного доступа (SARAM) по 4К слова каждый;
• один блок ОЗУ однократного доступа (SARAM) объемом 8К слов;
• два блока ОЗУ однократного доступа (SARAM) по 1К слову каждый.
Таким образом, общий объем встроенной на кристалл процессора памяти
программ составляет 128К слов, а памяти данных — 18К слов.
Дополнительно на плате установлена внешняя статическая память (стати-
ческое ОЗУ — SRAM) объемом 64К слова, т.е. память данных аппаратно рас-
ширена до 82К слов, что позволяет разрабатывать сложные проекты.
18
Табл и ц а 1.8
TM
Карта памяти оценочной платы eZdsp F2812

Началь- Встроенная на кристалл память Внешняя память (расширенная)


Началь-
ный On-Chip Memory External Memory (XINTF) ный
адрес Память данных Память программ Память данных Память программ адрес
блока Data Space Prog Space Data Space Prog Space блока

0x00 Перифер.фрейм 1 0х00


6000 Peripheral Frame 1 (4К×16, 6000
Protected) Защищен
Зарезервировано
0x00 Перифер.фрейм 2
7000 Peripheral Frame 2 (4К×16,
Protected) Защищен
Зарезервировано
0x00 Банк L0 статического ОЗУ L0 — SARAM
8000 (4К×16, Secure Block) Секретный блок
0x00 Банк L1 статического ОЗУ L1 — SARAM
9000 (4К×16, Secure Block) Секретный блок
0x00
A000
Зона 2 расширенной памяти 0х08
XINTF Zone 2 (0,5М×16, XZCS2/) 0000
Зарезервировано
Зона 6 расширенной памяти 0x10
XINTF Zone 6 (0,5М×16, XZCS6AND7/) 0000
0x18
0000
0x3D Однократно-программируемое или масочно-
7800 программируемое ПЗУ OTP (ROM) (1К×16,
Secure Block) Секретный блок
0x3D Зарезервировано (1К×16)
7C00
0x3D Флэш-память или ПЗУ Flash (ROM) (128К×16, Зарезервировано
8000 Secure Block) Секретный блок
0x3F 128-битовый код секретности (Password)
7FF8
0x3F Банк H0 статического ОЗУ H0 — SARAM
8000 (8К×16)
0x3F
A000
Зарезервировано
Зона 7 расширенной памяти XINTF 0x3F
Zone 7 (16К×16, XZCS6AND7/) C000
Разрешена, если MP/(MC#)=1
0x3F Загрузочное ПЗУ Boot ROM (4К×16)
F000 Разрешено, если MP/(MC#)=0
0x3F Таблица векторов прерываний в загрузочном Таблица векторов прерываний в рас- 0x3F
FFFF ПЗУ BROM Vector — ROM (32×32) ширенной памяти XINTF Vector — FFFF
Разрешена, если VMAP=1, MP/(MC#)=0, RAM (32×32) Разрешена, если
ENPIE=0 VMAP=1, MP/(MC#)=1, ENPIE=0

19
Окончание табл. 1.8

Началь- Встроенная на кристалл память Внешняя память (расширенная)


Началь-
ный On-Chip Memory External Memory (XINTF) ный
адрес Память данных Память программ Память данных Память программ адрес
блока Data Space Prog Space Data Space Prog Space блока

0x00 Таблица векторов прерываний M0-Vector — 0x00


0000 RAM (32×32) Разрешено, если VMAP=0 0000
0x00 Банк М0 статического ОЗУ M0 — SARAM
0040 (1К×16)
0x00 Банк М1 статического ОЗУ M1 — SARAM
0400 (1К×16)
0x00 Перифер. фрейм 0
0800 Peripheral Frame 0 Зарезервировано
(2К×16)
0х00 Таблица векторов периф.
0D00 прерываний PIE Vector —
Зарезервировано
RAM (256×16)
Разрешено, если
VMAP=1, ENPIE=1
0x00 Зарезервировано
0E00
0х00 Зона 0 расширенной памяти XINTF 0x00
2000 Zone 0 (8К×16, XZCS0AND1/) 2000
Зарезервировано Зона 1 расширенной памяти XINTF 0x00
Zone 1 (8К×16, XZCS0AND1/) 4000
Защищена (Protected)

Программа для отладки может быть загружена и выполнена как в опера-


тивном запоминающем устройстве, так и во встроенной флэш-памяти. В
процессе выполнения практических работ рекомендуется использовать
только встроенное ОЗУ во избежание «непреднамеренного запароливания»
процессора. При этом плата должна аппаратно конфигурироваться в режим
работы без начальной загрузки (non boot-loader mode), когда управление
автоматически передается рабочей программе пользователя, расположенной
в оперативном запоминающем устройстве.

Замечания
1. Размеры блоков памяти фиксированы и не могут изменяться пользова-
телем.
2. Области памяти, помеченные как резервные, зарезервированы для
будущих применений. Приложения пользователя не должны обращаться к
этим областям памяти.

20
Порядок доступа к памяти
Вся память делится на внутреннюю (встроенную на кристалл микроконт-
роллера) и внешнюю (расширенную) память. При обращении к памяти реа-
лизуется логика единого непрерывного адресного пространства, в котором
одну часть адресов занимает встроенная память, а другую — расширенная.
В том случае, когда адресные зоны областей встроенной и расширенной
памяти перекрываются (например, зона 0x3F F000 — 0x3F FFFF), для иден-
тификации конкретной области памяти используется текущее состояние
входа MP/MC# микроконтроллера. В режиме микропроцессора (MP/MC# = 1)
обеспечивается доступ к внешней памяти, а в режиме микроконтроллера
(MP/MC# = 0) — к внутренней памяти. Управление состоянием входа
MP/MC# реализуется с помощью переключателя JP1. Например, области
загрузочного постоянного запоминающего устройства (ПЗУ) и внешней
памяти в зоне 7 не могут использоваться одновременно. В режиме микрокон-
троллера (MP/MC#=0) обращение будет автоматически производиться к
встроенному загрузочному ПЗУ, а в режиме микропроцессора (MP/MC#=1) —
к зоне 7 внешней памяти.
С одной стороны, в микроконтроллерах ‘C28xx память унифицирована —
она может использоваться (за одним исключением) и как память данных, и
как память программ. Так, в ОЗУ обычно находятся переменные пользова-
теля (память данных), а в ПЗУ или во флэш-памяти — программы пользова-
теля. Однако оперативное запоминающее устройство можно использовать и
в качестве кодовой памяти, загружая в него программу, подлежащую
отладке, и передавая управление этой программе. Эта операция выполняется
в ходе практических работ. С другой стороны, в процессе загрузки флэш-
памяти программа «загрузчик» рассматривает флэш-память как память дан-
ных, обеспечивая пересылку новых данных — выполняет программирование
флэш-памяти. Сама программа «загрузчик» при этом будет располагаться в
кодовом ОЗУ процессора, и никаких специальных устройств — программа-
торов для этой операции не требуется.
Как видно из табл. 1.8, исключениями являются области памяти данных,
отведенные под регистры встроенных периферийных устройств. Регистры
периферийных устройств отображены на память данных — каждый из них
имеет свой адрес в памяти данных, по которому к нему можно обратиться.
Эти области памяти называются периферийными фреймами 1, 2 и 3 и не
могут использоваться в качестве памяти программ.
Унификация памяти существенно облегчает программирование, особенно
на языке высокого уровня, так как пользователь может наиболее рацио-
нально разделить память на память программ и данных в соответствии с кон-
кретной задачей. Разделение выполняется с помощью компоновщика.
Термины «защищен» (периферийные фреймы 1 и 2) и «защищена» (зона
расширенной памяти 1) относятся к конвейеру команд и означают, что аппа-
ратно исключается ситуация, когда данные из периферийного регистра/
памяти считываются текущей командой еще до того, как предыдущая
команда смогла их модифицировать. На практике это означает, что операции
с регистрами периферийных устройств памятью будут выполняться в стро-
гом соответствии с порядком, заданным программой пользователя.
21
Флэш-память и однократно-программируемое
постоянное запоминающее устройство
Область флэш-памяти, представленная в табл. 1.9, разбита на отдельные
сектора по 8 или по 16К слов каждый. Последние восемь слов флэш-памяти
отведены для программирования кода секретности (8×16=128 бит). Если
код секретности используется, то в область памяти 0x3F 7F80 — 0x3F 7FF5
должны быть записаны нули (ключ задания режима секретности). Два слова
непосредственно перед кодом секретности предназначены для программиро-
вания команды передачи управления на точку входа в программу, которая
может быть либо собственно программой пользователя, либо процедурой
начальной загрузки процессора, расположенной во флэш-памяти.

Табл и ц а 1.9
Таблица адресов секторов флэш-памяти

Диапазон
Сектор программ и данных, объем
адресов
0x3D 8000
Сектор J, 8К×16
0x3D 9FFF
0x3D A000
Сектор I, 8К×16
0x3D BFFF
0x3D C000
Сектор H, 16К×16
0x3D FFFF
0x3E 0000
Сектор G, 16К×16
0x3E 3FFF
0x3E 4000
Сектор F, 16К×16
0x3E 7FFF
0x3E 8000
Сектор E, 16К×16
0x3E BFFF
0x3E C000
Сектор D, 16К×16
0x3E FFFF
0x3F 0000
Сектор C, 16К×16
0x3F 3FFF
0x3F 4000
Сектор B, 8К×16
0x3F 5FFF
0x3F 6000 Сектор A, 8К×16
0x3F 7F80 Эта область памяти должна быть запрограммирована нулями (0x0000), если исполь-
0x3F 7FF5 зуется Модуль Секретности Кода (Code Security Module), в противном случае может
содержать любой код
0x3F 7FF6 Точка входа загрузчика во флэш-памяти (или ПЗУ)
0x3F 7FF7 Boot-to-Flash (or ROM) Entry Point
Здесь должна быть запрограммирована команда перехода на программу загрузчика
(branch instruction)
0x3F 7FF8 Код секретности (128 бит) — Security Password
0x3F 7FFF Биты кода секретности не должны быть все нулевыми

22
В процессорах ‘F2811, ‘F2812 общий объем флэш-памяти равен 128К сло-
вам: четыре сектора 8К×16 и шесть секторов 16К×16. В процессорах ‘F2810
— 64К слова: два банка по 8К×8 и три банка по 16К×16.
Все микроконтроллеры семейства ‘28x дополнительно имеют одно-
кратно-программируемое ПЗУ объемом 1К×16, расположенное в диапазоне
адресов 0x3D 7800 — 0x3D 7BFF. Операция записи в это ПЗУ может быть
выполнена только один раз.
Пользователь имеет возможность индивидуально стирать, программиро-
вать и верифицировать любой сектор флэш-памяти, оставляя информацию
в других секторах нетронутой. Однако нельзя использовать какой-либо сек-
тор флэш-памяти или однократно-программируемое ПЗУ для работы с дру-
гим сектором флэш-памяти. Это означает, что программа загрузки флэш-
памяти должна выполняться исключительно из кодового ОЗУ.
Флэш-память и однократно-программируемая память отображены одно-
временно и на память программ, и на память данных. Поэтому их можно
использовать для хранения как программного кода, так и данных, например
таблиц функций и коэффициентов.
В процессорах ‘C28 число тактов ожидания для работы с флэш-памятью и
однократно-программируемым ПЗУ может программироваться в приложе-
нии пользователя. Эффективная производительность флэш-памяти может
быть повышена за счет разрешения работы специального конвейера флэш-
памяти. Это можно сделать в регистре опций флэш-памяти (flash option
register). Точное значение производительности процессора при работе с
флэш-памятью зависит от конкретной задачи, а также от числа установленных
тактов ожидания и режима работы конвейера флэш-памяти. Режим конвейер-
ного доступа не используется при работе с однократно-программируемым ПЗУ.
Все микроконтроллеры семейства ‘28xx выпускаются в двух исполнениях:
‘F28xx с флэш-памятью и ‘C28xx с масочно-программируемым на заводе-
изготовителе ПЗУ того же объема. В процессе разработки новых изделий и в
мелкосерийном производстве используются микроконтроллеры ‘F28xx, а в
крупносерийном производстве — ‘C28xx (с уже не требующей отладки про-
граммой, «прошитой» в ПЗУ).

Оперативное запоминающее устройство


однократного доступа SARAM (банки M0, M1, L0, L1, H0)
Все микроконтроллеры семейства ‘x28xx имеют два банка ОЗУ однократ-
ного доступа М0 и М1 объемом по 1К слову каждый. Банк М0 — общего
назначения, а банк М1 обычно используется в качестве стека. Указатель
стека после сброса процессора автоматически устанавливается на начало
банка М1 (на адрес 0x00 0400).
Все микроконтроллеры ‘x281x имеют дополнительно 16К слов ОЗУ одно-
кратного доступа, которое состоит из трех банков L0, L1, H0 (4К + 4К + 8К).
Каждый из них имеет независимые аппаратные средства минимизации
задержек на конвейере для обеспечения заявленной высокой производитель-
ности процессора (150 млн оп/с).
23
Все банки оперативного запоминающего устройства, подобно всем дру-
гим блокам памяти в процессорах ‘C28, одновременно отображены и на
память данных, и на память программ. Таким образом, программист может
использовать оперативную память как для загрузки и выполнения програм-
много кода, так и для хранения данных.

Расширенная память
Микроконтроллер TMS320F2812 автоматически формирует сигналы
выборки кристалла при обращении к расширенной памяти или периферии —
сигналы селектирования внешних устройств (CS#). Каждый сигнал соот-
ветствует определенной зоне адресов внешней расширенной памяти/перифе-
рии (XZ). Интерфейс внешней памяти поддерживает пять независимых зон
внешней памяти. Одна из них имеет свой собственный сигнал выборки
кристалла, а четыре другие — попарно общие сигналы «чип-селекта». Для
каждой зоны пользователь может запрограммировать свое собственное
число тактов ожидания (wait states), а также режим работы с сигналом готов-
ности памяти или без него. Это облегчает организацию интерфейса с вне-
шней памятью. Расширение периферии через XINTF поддерживается только
при отображении периферийных устройств на внешнюю память.
На плате eZdspTM F2812 один из сигналов выборки кристалла
XZCS6AND7# уже подключен к внешнему ОЗУ, а два других сигнала
(ZXCS0AND1# и XZCS2#) выведены на разъем расширения. Это позволяет
при необходимости еще больше расширить объем используемой памяти или
периферии.
При доступе к внешней статической памяти, установленной на плате,
микроконтроллер автоматически формирует строб XZCS6AND7#. Общий
объем этой зоны памяти 0,5М слова, начиная с адреса 0x10 0000 по адрес
0x18 0000. Реально используется только 64К слова в диапазоне адресов от
0x10 0000 до 0x10 FFFF.

Размещение таблицы векторов прерываний


Место размещения таблицы векторов прерываний в памяти микропроцес-
сорной системы определяется в процедуре инициализации системы. Значе-
ние имеет режим работы, установленный аппаратно MP/MC# (микропроцес-
сора или микроконтроллера), а также состояние двух битовых флагов VMAP
и ENPIE, устанавливаемых программно. Первый флаг VMAP задает разме-
щение таблицы векторов либо в начальной, либо в конечной области памяти.
Второй флаг ENPIE разрешает работу с таблицей векторов периферийных
прерываний, расположенной в памяти данных и приведенной в табл. 1.10.
При активном использовании периферийных устройств обычно выбирается
второй вариант размещения (PIE Vector), когда для каждого запроса преры-
вания периферийного устройства выделяется место в памяти данных под
свой собственный вектор перехода на подпрограмму обслуживания запроса
прерывания периферийного устройства.
24
Табли ц а 1 . 1 0
Способы размещения в памяти таблицы векторов прерываний

Таблица
векторов Объем и место размещения Условие выбора
прерываний
М0 Vector 32×32-разрядных слова в начальной области VMAP = 0
ОЗУ 00 0000 — 00 003F (банк М0)
PIE Vector 256×16-разрядных слов в области ОЗУ пери- VMAP = 0, ENPIE = 1
ферийных устройств 00 0D00 — 00 0DFF
BROM Vector 32×32-разрядных слова в конечной области VMAP = 1, ENPIE = 0, MP/MC# = 0
загрузочного ПЗУ 3F FFC0 — 3F FFFF (в режиме микроконтроллера)
XINTF Vector 32×32-разрядных слова в конечной области VMAP = 1, ENPIE = 0, MP/MC# = 1
внешнего ОЗУ 3F FFC0 — 3F FFFF (в режиме микропроцессора)

Контрольные вопросы

1. Необходимо подключить к оценочной плате eZdspTM F2812 дополнительную вне-


шнюю память объемом 512К слов. Достаточно ли выведенных на разъем P2 разрядов
адресной шины XA0—XA18 для прямой адресации каждой ячейки внешней расширен-
ной памяти?
2. Какой сигнал выборки кристалла внешней памяти XZCS0AND1# или ZXCS2# сле-
дует использовать для этой цели?
3. Микросхема внешней памяти имеет следующие управляющие сигналы — выборки
кристалла CS#, разрешения записи WE#, разрешения вывода данных OE#. Какие сиг-
налы управления на разъеме P2 можно использовать для подключения этой микросхемы?
TM
4. Нарисуйте схему подключения микросхемы внешней памяти к eZdsp F2812.

1.3. ВВЕДЕНИЕ В ИНТЕГРИРОВАННУЮ СРЕДУ РАЗРАБОТКИ


И ОТЛАДКИ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
CODE COMPOSER STUDIO

Основы модульной разработки


программного обеспечения
Современная концепция разработки и отладки программного обеспечения
микропроцессорных систем управления предполагает модульный подход,
когда любая сложная задача разбивается на ряд программных модулей, разра-
батываемых и отлаживаемых одновременно несколькими программистами, с
последующим объединением наработок в один общий проект. При этом
каждый файл, содержащий программный код (программный модуль), может
быть написан независимо от других файлов и протестирован (отлажен).
Программные модули могут быть написаны на языке Ассемблер конкрет-
ного процессора (исходный файл с расширением .asm) или на языке высо-
кого уровня С/С++ (исходный файл с расширением .c).
Преимущество программирования на Ассемблере заключается в возмож-
ности максимального использования всех достоинств архитектуры и системы
25
команд процессора, минимизации времени выполнения рабочей программы и
объема требуемой памяти. Например, в семействе микроконтроллеров ‘C28
имеются: специальные способы адресации кольцевых буферов, позволяю-
щие предельно эффективно решать задачи цифровой фильтрации; битовый
процессор для эффективной работы с периферией, реализации логических
контроллеров и дискретных автоматов. Недостатки программирования на
Ассемблере — сложность языка, зависимость от конкретного типа процес-
сора, большая трудоемкость и длительность разработки программного про-
дукта, необходимость освоения нового языка при переходе на новый тип
процессора.
Основное преимущество программирования на языке высокого уровня
состоит в универсальности языка — его аппаратной независимости от типа
процессора. Функцию адаптации к конкретному процессору выполняет про-
грамма компилятор. Главный недостаток — более высокие требования к
ресурсам процессора как по производительности, так и по объему памяти
программ/данных. В связи с ростом производительности современных мик-
роконтроллеров, увеличением объема встроенной памяти, развитием двухъ-
ядерных архитектур центрального процессора с аппаратной поддержкой
вычислений с фиксированной и плавающей точкой, специальных средств
оптимизации выходного кода этот недостаток постепенно нивелируется.
Поэтому в настоящем курсе основное внимание уделено технологии разра-
ботки и отладки программ на языке высокого уровня С/С++.
Традиционные технологии разработки программного обеспечения пред-
полагают использование для написания и редактирования исходных файлов
программ любого текстового редактора, который позволяет сохранять
информацию в стандартном ASCII-коде (без символов форматирования), а
также ряда специальных программ: транслятора с языка Ассемблер, компи-
лятора с языка С/С++, компоновщика, библиотекаря, загрузчика, отладчика
и др. Вызов специальных программ производится поочередно в командной
строке с указанием входных обрабатываемых файлов и опций, задающих
режим работы программы.
Объединение всех перечисленных программ, включая текстовый редак-
тор, в одну интегрированную среду разработки позволяет значительно
упростить и облегчить труд программиста. На рис. 1.8 показаны как отдель-
ные компоненты, входящие в интегрированную среду разработки Code
Composer Studio (далее CCS), так и типовые процессы, связанные с основ-
ными этапами разработки и отладки программ:
1) во встроенном редакторе ведется подготовка текстов исходных про-
граммных модулей на языках Ассемблер и С/С++, создаются пользователь-
ские библиотеки часто применяемых определений и макросов (макробиблио-
теки);
2) исходные файлы программных модулей на языке Ассемблер (.asm)
обрабатываются транслятором с языка Ассемблер с генерацией выходных
файлов в перемещаемом объектном коде (.obj), а также файлов листингов
(.lst). Файлы листингов содержат программы в машинных кодах и списки
ошибок, например неверных инструкций;
26
Текстовый
редактор

Исходный файл
Исходный файл
на Ассемблере
на СИ (.с)
(.asm)

Библиотека
Компилятор
макросов и
С/С++
описаний на С

Файл
на Ассемблере
(.asm) Библиотека
макросов
на Ассемблере

Транслятор
с Ассемблера

Перемещаемый
Стандартные Объектные
объектный Файл объектные библиотеки
файл (.obj) листинга (.lst) библиотеки пользователя

Файл
управления
компоновкой Компоновщик
(lnk.cmd)
Code
Абсолютный Composer
объектный
файл(.out)
Файл карты Studio
загрузки (.map)

Файлы Средства
Загрузчик/
ввода данных графического
Отладчик
отображения

Файлы
Симулятор
вывода данных
SIM

Аппаратная часть

Оценочная Плата Внутрисхемный Контроллер


плата разработчика эмулятор пользователя
eZdspTM EVM XDS DSP Board

Рис. 1.8. Разработка программного обеспечения в среде Code Composer Studio

27
3) исходные файлы программных модулей на языке С/С++ (.c) обрабаты-
ваются компилятором с языка С/С++. Создаются промежуточные выходные
файлы на Ассемблере (.asm), которые в свою очередь обрабатываются транс-
лятором с Ассемблера с созданием выходных объектных файлов в перемеща-
емом объектном коде (.obj). Компилятор имеет встроенные средства оптими-
зации выходного кода;
4) специальная утилита внутреннего листинга связывает исходную про-
грамму на С/С++ с выходной программой на Ассемблере, чтобы разработчик
мог понять, как они коррелируют друг с другом, и при необходимости
выполнить «ручную оптимизацию»;
5) для стандартизации процесса разработки программного обеспечения
используется «Общий формат объектных файлов» Common Object File
Format (COFF) как для программ, написанных на Ассемблере, так и для про-
грамм, написанных на С/С++. Такой подход позволяет разрабатывать часть
программных модулей проекта (критичных ко времени выполнения и объ-
ему требуемой памяти) на Ассемблере, а часть — на С/С++;
6) вышеописанный процесс может повторяться для любого числа исход-
ных модулей;
7) ряд уже отлаженных программных модулей в перемещаемом объект-
ном коде может быть объединен в пользовательскую библиотеку объектных
модулей, содержащую набор часто вызываемых пользовательских функций;
8) могут использоваться также стандартные объектные библиотеки,
поставляемые производителями систем разработки ПО, например, для
вычислений в формате с плавающей точкой;
9) множество программных модулей в перемещаемом объектном коде
(.obj), в том числе нужные файлы из объектных библиотек, объединяются в
одну выходную программу в абсолютном объектном коде (.out) с помощью
компоновщика;
10) компоновщик эффективно распределяет ресурсы памяти, доступные
в конкретном микропроцессорном устройстве, для каждого модуля проекта.
При этом используется специальный командный файл управления компонов-
кой (lnk.cmd), который не только описывает структуру имеющихся ресурсов
памяти программ и данных на целевой плате (банки памяти), но и задает
правила размещения в этих банках различных секций программных модулей;
11) выходом процесса компоновки является абсолютный объектный файл
(.out), который может быть загружен и выполнен на целевой плате с DSP-
процессором в реальном масштабе времени. Дополнительно может генери-
роваться файл с картой загрузки (.map), в котором содержится информация о
том, в какие банки памяти и конкретно по каким адресам размещены все сек-
ции проекта;
12) в состав интегрированной среды CCS входит также загрузчик и
отладчик программ, с помощью которого можно выполнить рабочую про-
грамму по шагам, с точками останова или в реальном времени;
13) дополнительные возможности включают в себя: средства наблюдения
за состоянием регистров процессора и переменных в памяти; модификации
значений переменных «на лету»; утилиту подключения файлов ввода и
28
вывода данных для тестирования программ; встроенные средства графиче-
ского отображения и анализа информации, например получения фазового
портрета или частотного спектра сигнала;
14) еще более мощные возможности могут быть добавлены самим про-
граммистом с использованием специальной технологии расширения функций
интегрированной среды CCS;
15) гарантируется аппаратная совместимость со всеми оценочными (типа
eZdsp) и более мощными отладочными (EVM) платами, выпускаемыми фир-
мой Texas Instruments в помощь разработчикам, а также возможность
отладки с любыми платами, созданными пользователями, через стандарт-
ный внутрисхемный эмулятор XDS (главное средство профессиональной
отладки);
16) в состав CCS входит также программно-логическая модель процессора
(симулятор), допускающая загрузку и отладку программ без использования
каких-либо дополнительных аппаратных средств — только с использова-
нием компьютера. Это позволяет отлаживать часть программных модулей
удаленно, в том числе в домашних условиях.
Таким образом, интегрированная среда CCS поддерживает написание
модульного программного кода и позволяет работать над проектом несколь-
ким программистам одновременно, сокращая срок разработки приложения.
Отладка и обновление кода становятся быстрее. Новые проекты могут быть
разработаны с меньшими сроками, так как в них могут использоваться уже
отлаженные модули из предыдущих проектов. При использовании С/С++ код
становится аппаратно-независимым, что позволяет программисту модуля не
думать о распределении памяти. Эта работа будет сделана на этапе компо-
новки проекта. При изменении любого из модулей производится новая
сборка проекта и вопросы распределения памяти решаются заново, чтобы
исключить вероятность возникновения конфликтов.

Секционирование программных модулей на Ассемблере

Любой программный модуль состоит из секций — отдельных частей,


предназначенных для размещения однотипной информации. В исходном
модуле на Ассемблере с помощью приведенных ниже директив Ассемблера
можно создавать секции трех типов (по умолчанию):
1) .text — кодовая секция — для размещения выполняемого програм-
много кода;
2) .data — секция данных — для размещения констант и таблиц констант,
т.е. инициализированных данных;
3) .bss — секция переменных — для резервирования места в оперативной
памяти под неинициализированные переменные.
Дополнительно разрешается создавать произвольное число пользователь-
ских секций (поименованных пользователем) трех базовых типов «.data»,
«.text», «.bss».
29
Таким образом, существует всего два класса секций:
1) инициализируемые секции (Initialized sections): содержат код «.text»
или фиксированные (заранее определенные пользователем данные) «.data».
Пользовательская секция такого класса создается с помощью директивы
Ассемблера «.sect»;
2) неинициализируемые секции (Uninitialized sections): лишь резерви-
руют место в памяти для размещения неинициализированных данных «.bss».
Пользовательская секция такого класса создается с помощью директивы
Ассемблера «.usect».
Все секции по способу доступа к данным со стороны процессора (по спо-
собу адресации в программе на Ассемблере) делятся на два класса:
• отображаемые на память программ (Страница 0 — Page 0);
• отображаемые на память данных (Страница 1 — Page 1).
Если секция по способу доступа к данным относится к отображаемой на
память программ, то это еще не означает, что она фактически размещается в
ПЗУ или флэш-памяти. В процессе отладки программного обеспечения ПЗУ
может быть размещено в одном из банков кодового ОЗУ и только после
завершения отладки «прошито» во флэш-память. Это возможно за счет уни-
фикации памяти в микроконтроллерах ‘C28x, когда одна и та же физическая
область памяти может работать и как память данных, и как память программ.
Инициализируемые секции всегда отображаются на память программ, а
неинициализируемые — на память данных. Секции, отображаемые на
память данных, могут фактически размещаться только в оперативном запо-
минающем устройстве.
Секции, перемещаемые независимо друг от друга, являются перемещае-
мыми. Все секции одного и того же типа из разных программных модулей
после компоновки объединяются вместе и занимают одну непрерывную
область в конкретном банке памяти целевого устройства. Секции каждого
типа могут размещаться отдельно от других. Это одна из главных функций
компоновщика, которая называется локализацией, размещением (рис. 1.9).
Обычно секции .text и .data после объединения размещаются во флэш-памяти
или ПЗУ (на этапе наладки в кодовом ОЗУ), а секции переменных .bss —
в оперативной памяти (ОЗУ).
Так как большинство микропроцессорных систем имеют несколько раз-
личных типов памяти, секционирование помогает более эффективно исполь-
зовать память на целевой плате, в том числе перераспределять память в про-
цессе работы над проектом. Например, необходимо определить
пользовательскую секцию initial, которая будет содержать все процедуры
инициализации вашего устройства, и разместить ее в области памяти про-
грамм (во флэш-память) или определить пользовательскую секцию vector,
содержащую таблицу векторов прерываний, и разместить ее вначале в
памяти данных, а после завершения отладки — во флэш-памяти. Заводские
настройки можно сохранить в однократно-программируемом ПЗУ, как пока-
зано на рис. 1.9, или во флэш-памяти.
30
Объектный
файл 1

Секция
.text

Секция
.data
Память целевой платы

Секция Память
.bss программ
(флэш-память)

Память
Объектный программ
файл 2 (ЭППЗУ)

Секция Память
.text данных
(ОЗУ)

Секция
.data

Секция
.bss

Рис. 1.9. Объединение однотипных секций двух объектных файлов

Секционирование программных модулей на языке С/С++


Рассмотрим, например, простейшую программу, написанную на языке
С/С++:

int x = 2;
int y = 7;
void main (void)
{
long z;
z = x + y;
}

31
Комментарии для начинающих:
1) любая программа на языке С/С++ состоит из одной или более функций.
Главная, или основная, функция программы называется main (). Имя главной
функции не может быть изменено программистом и всегда должно быть
main (). Круглые скобки после имени main указывают на то, что имя main
принадлежит функции;
2) тип void (пустой тип) перед именем главной функции указывает на то,
что главная функция не возвращает никакого значения;
3) тип void (пустой тип) вместо списка параметров в объявлении главной
функции main (void) означает, что главная функция не принимает аргументов;
4) тело любой функции, в том числе главной функции main, состоит из
составного оператора { }, причем открывающая фигурная скобка { соот-
ветствует началу составного оператора (begin), а закрывающая фигурная
скобка } — концу составного оператора (end);
5) в теле любой функции могут содержаться два блока: описания перемен-
ных и выполнения;
6) блок описания переменных включает операторы описания типов пере-
менных. Они имеют следующий синтаксис:
<Тип переменной> <Имя_переменной_1, Имя_переменной_2 [,…]>;
В примере, приведенном на с. 31 оператор описания типа переменной
имеет вид:
long z;
Он описывает переменную z как длинное целое число (типа long);
7) блок выполнения в программе содержит только оператор присваива-
ния: z = x + y;
8) переменные, описанные в самом начале программы перед главной фун-
кцией main(), являются глобальными переменными, а переменные, описан-
ные в теле функции, — локальными переменными. Локальные переменные
обычно размещаются в стеке;
9) в операторах объявления типов переменных допускается «попутная»
инициализация переменных. Она выполняется с помощью обычных операто-
ров присваивания:
int x = 2;
int y = 7;
Эти два оператора определяют переменные x и y как целые числа со зна-
ком в дополнительном коде (типа int) и инициализируют их значениями 2 и 7
соответственно.
С помощью языка С/С++ допускается одновременное описание несколь-
ких переменных одного и того же типа одним оператором:
int x =2, y = 7;
Не рекомендуется в одном операторе описания типов переменных смеши-
вать инициализированные и неинициализированные переменные.
Проанализировав эту программу, можно заметить, что она одновременно
содержит код и разные типы данных, которые могут быть как глобальными,
так и локальными переменными. В языке С/С++ секции определяются
неявно, т.е., в отличие от Ассемблера, специальных директив объявления
32
секций нет. Это упрощает программирование, так как функцию создания
нужных секций автоматически берет на себя компилятор. Однако механиз-
мом размещения секций в памяти целевого устройства по-прежнему управ-
ляет пользователь. Из этого правила есть одно исключение — имеются спе-
циальные директивы препроцессора (pragma), которые используются для
явного объявления секций данных, выделяемых под переменные типа
«структура» для резервирования памяти данных под регистры периферий-
ных устройств (рис. 1.10).
Для размещения программного кода в С/С++ используется кодовая секция
.text (как на Ассемблере), для размещения локальных переменных — секция
стека .stack, для размещения глобальных переменных — секция глобальных
переменных .ebss, а для размещения констант, предназначенных для началь-
ной инициализации переменных, — секция констант инициализации .cinit
(см. рис. 1.10).
Кодовые секции должны отображаться на память программ, а секции с
глобальными и локальными переменными — на память данных. Название
каждой секции, как и в Ассемблере, начинается с точки:
• секция глобальных переменных .ebss;
• секция констант для инициализации глобальных переменных .cinit;
• секция локальных переменных, расположенных в стеке, .stack;
• кодовая секция с выполняемыми инструкциями процессора .text.
Как и переменные на языке С/С++, секции могут быть инициализирован-
ными и неинициализированными. Список секций, которые могут создаваться
компилятором С/С++, с краткими комментариями назначения секций, пред-
ставлен в табл. 1.11.

Секция Секция
глобальных констант для
переменных инициализации
Global Vars переменных Init Vals
.ebss .cinit

int x = 2;
int y = 7; Секция
локальных
переменных
void main (void) Local Vars
.stack
{
long z;
z = x + y;
}
Кодовая секция .text

Рис. 1.10. Секции в программе на С/С++

33
Таблица 1.11
Секции, создаваемые компилятором языка С/С++
Отображается Возможное
Имя
Описание назначения на страницу место
секции
памяти размещения
Инициализированные секции
.text Исполняемый программный код Программ ПЗУ или ОЗУ
Page 0 (при отладке)
.cinit Константы для начальной инициализации глобаль- Программ ПЗУ или ОЗУ
ных и статических переменных Page 0 (при отладке)
.pinit Таблицы инициализации глобальных конструкто- Программ ПЗУ или ОЗУ
ров языка С++ Page 0 (при отладке)
.switch Таблицы векторов переходов для операторов пере- Программ ПЗУ или ОЗУ
ключения switch Page 0
(для опции —mt)
Данных
Page 1
.econst Переменные и массивы, жестко установленные как Данных ПЗУ или ОЗУ
константы, например const int k = 3; Page 1 (при отладке)
Неинициализированные секции
.ebss Глобальные и статические переменные Данных ОЗУ
Page 1
.stack Область системного стека, локальные переменные Данных ОЗУ
Page 1
(Младшие 64К)
.esysmem Системная память для функций динамического рас- Данных ОЗУ
пределения памяти Page 1

С точки зрения языка С/С++, а именно способа доступа к данным, секции


могут отображаться либо на память программ (Страница 0 — Page 0), либо
на память данных (Страница 1 — Page 1). Так, секция исполняемого програм-
много кода .text всегда отображается на память программ. Однако при
отладке программы, что и будем делать, ее целесообразно разместить в кодо-
вом ОЗУ (с доступом, как к программной памяти) и только после завершения
отладки — в настоящем ПЗУ или флэш-памяти. Поэтому в качестве возмож-
ного места размещения секции .text в табл. 1.11 указано ПЗУ или ОЗУ.
Секция глобальных и статических переменных .ebss должна отобра-
жаться на память данных (Страница 1 — Page 1). Она предназначена для
резервирования места под переменные, которые могут быть как предвари-
тельно проинициализированными, так и нет. Фактическим местом размеще-
ния секции может быть только оперативная память. Каждая переменная опи-
сывается на С/С++ с помощью оператора объявления типа переменной,
чтобы зарезервировать память, необходимую для размещения в ней значения
переменной. Обычно переменным не присваивается какое-либо значение.
Изменить это значение может только работающая программа.
Для того чтобы проинициализировать глобальные или статические пере-
менные начальными значениями, т.е. выполнить предустановку перемен-
ных, применяется следующая технология: во время запуска программы
34
(Startup time) специальная процедура загрузчика С-программы (C boot
routine) копирует данные из секции инициализации глобальных переменных
.cinit (которая обычно расположена в постоянной памяти) и сохраняет их в
секции .ebss. Далее управление передается программе пользователя.
Естественно, что при отладке, так же как и секция .text, секция .cinit с
таблицей констант инициализации переменных может загружаться в кодовое
ОЗУ.
Секция констант .econst содержит жестко установленные константы,
значения которых не будут меняться, в том числе строковые константы. С
точки зрения С/С++ это всего лишь переменные, поэтому секция .econst
должна отображаться на память данных (Страница 1, Page 1), как и секция
.ebss. Фактическим местом размещения секции будет ПЗУ или ОЗУ (при
отладке).
Префикс «е» перед именем секции .bss, а также перед именами других
секций (.const — econst; sysmem — esysmem) означает расширенную за пре-
делы 64К слов область памяти, которая используется в микроконтроллерах
‘C28x.
Одной из важнейших, при программировании на С/С++, является секция
системного стека .stack, которая используется не только для сохранения
адресов возврата из подпрограмм и процедур обслуживания прерываний, но
и в качестве места размещения локальных переменных, определенных только
внутри конкретной функции. Кроме того, при обращении к любой функции
через стек передаются параметры (аргументы функции) и возвращаются
результаты. Секция стека всегда отображается на память данных и факти-
чески располагается в ОЗУ.
Секция системной памяти .esysmem используется специальной функ-
цией языка far malloc для динамического распределения памяти. Если эта
функция не используется, то секция .esysmem остается пустой — ее размер
будет равен 0.
Таким образом:
• секции .text, .cinit и .switch должны отображаться на память программ
(Страница 0, Page 0). Их фактическое расположение в готовом изделии —
ПЗУ или флэш-память, а при отладке — кодовое ОЗУ;
• секция .econst должна отображаться на память данных (Страница 1,
Page1) и размещаться в готовом изделии в ПЗУ или флэш-памяти, а при
отладке — в кодовом ОЗУ;
• секции .ebss, .stack, и .esysmem должны отображаться на память дан-
ных (Страница 1, Page 1) и размещаться исключительно в оперативной
памяти (ОЗУ).
Один из возможных вариантов окончательного размещения секций
С-программы в памяти микроконтроллера ‘C28x (после завершения отладки)
показан на рис. 1.11. Секции .cinit и .text размещаются во флэш-памяти, секция
переменных .ebss — в банке M0 встроенного ОЗУ, а секция стека .stack —
в банке M1 встроенного ОЗУ.
35
Объединенные
секции всех
объектных
файлов
Память
целевой платы

Секция 0x00 0000


.ebss Банк M0
M0SARAM
0x400(1К слово)
0x00 0400
Секция Банк М1
.stack M1SARAM
0x400 (1К слово)

Секция 0x3D 8000


.cinit Флэш-память
FLASH
0X20000 (128К слов)

Секция
.text

Рис. 1.11. Один из вариантов размещения секций СИ-программы в памяти ‘C28x

Замечания
1. Объекты, которые декларируются в языке СИ как удаленные (far), или
объекты, предназначенные для использования в расширенной памяти (large
memory), должны размещаться в секциях .ebss/.econst. Напротив, близкие
объекты (near objects) могут размещаться в секциях .bss/.const. За размеще-
нием объектов по секциям следит сам компоновщик проекта.
2. Окружение реального времени языка С/С++ поддерживает динамичес-
кое распределение памяти (system heap — «кучи» системы) в начальной
области памяти данных с помощью функции malloc и динамическое распре-
деление памяти в любой области памяти данных с помощью функции
far_malloc. Для реализации этих задач служат секции .sysmem и .esysmem.

1.4. ТЕХНОЛОГИЯ РАСПРЕДЕЛЕНИЯ


РЕСУРСОВ ПАМЯТИ ЦЕЛЕВОЙ ПЛАТЫ
С ПОМОЩЬЮ ФАЙЛА УПРАВЛЕНИЯ КОМПОНОВКОЙ

Компоновщик выполняет две основные функции, имеющие отношение к


секциям. Первая функция использует входные секции в перемещаемом объ-
ектном коде как компонуемые блоки и объединяет входные секции одного и
того же типа (если в процессе компоновки участвуют больше чем один файл)
в одну выходную секцию в перемещаемом объектном коде с тем же именем.
36
С помощью второй функции компоновщик заменяет относительные адреса
абсолютными, выполняя размещение объединенных секций в памяти уст-
ройства с учетом двух факторов:
1) карты памяти конкретной микропроцессорной системы;
2) указаний программиста по распределению секций в памяти целевого
устройства.

Командный файл компоновщика (.cmd)


Для автоматического управления процессом компоновки в интегрирован-
ной среде CCS на этапе сборки проекта (build) или пересборки проекта
(rebuild) после модификации (редактирования) одного или нескольких фай-
лов проекта используется командный файл управления компоновкой (.cmd),
который создается программистом.
В этом файле:
• с помощью директивы MEMORY специфицируются различные
области памяти, физически присутствующие на целевой плате (встроенное
ОЗУ однократного доступа SARAM, встроенная флэш-память, расширенная
память и т.п.);
• с помощью директивы SECTIONS даются указания по распределению
секций программы по физически имеющимся областям памяти, описанным
директивой MEMORY.

Описание карты памяти целевой платы


С помощью директивы MEMORY описывается конфигурация памяти
целевой микропроцессорной системы. Необходимо задать имена отдельным
областям памяти, указать стартовые адреса и длины этих областей памяти.
Формат описания любой области памяти следующий:
Name: origin = 0x????, length = 0x????
Здесь:
Name Имя физически существующей в микропроцессорной системе области памяти. Жела-
тельно, чтобы имя точно отображало специфику области памяти, например FLASH —
флэш-память программ, M0SARAM — банк М0 ОЗУ однократного доступа
origin Начальный адрес области памяти в шестнадцатеричной системе счисления
length Длина области памяти в шестнадцатеричной системе счисления

Например, в микроконтроллерах ‘C28 встроенная флэш-память начина-


ется с адреса 0x3D8000 и имеет объем 128К слов, что соответствует длине
0х20000 (131072 в десятичной системе счисления). Если присвоить этой
области памяти имя FLASH, то ее описание примет вид:
MEMORY
{
FLASH: origin = 0x3D8000, length = 0x20000
}
37
Любая целевая плата содержит несколько различных областей памяти
(банков памяти), каждая из которых должна иметь свое имя. В этом случае
внутри составного оператора, между скобками { }, должны располагаться
несколько операторов определения областей памяти. Так, в микроконтролле-
рах ‘C28 описание двух банков ОЗУ однократного доступа M0 и M1 объемом
1К слово каждый может выглядеть следующим образом:

MEMORY
{
M0SARAM: origin = 0x000000, length = 0x0400
M1SARAM: origin = 0x000400, length = 0x0400
}

Уже отмечалось, что каждая секция программы на Ассемблере или на


С/С++ должна быть отображена либо на память программ (Page 0), либо на
память данных (Page 1). Поэтому в директиве MEMORY сначала описыва-
ются все банки памяти, которые входят в программную память, а затем те,
которые входят в память данных. Напоминаем, что кодовое ОЗУ на этапе
отладки может быть отнесено к памяти программ. Таким образом, полное
описание карты памяти имеет формат:

MEMORY
{
PAGE 0:/* Память программ */
FLASH:origin = 0x3D8000, length = 0x20000
PAGE 1:/* Память данных */
M0SARAM: origin = 0x000000, length = 0x0400
M1SARAM: origin = 0x000400, length = 0x0400
}

Дополнительные рекомендации:
1) обязательно указывайте после имени страницы памяти и имени сег-
мента памяти двоеточие;
2) используйте комментарии, как это принято в языке С/С++: начало ком-
ментария /*, конец комментария */. Использование комментариев с операто-
ром // недопустимо;
3) располагайте вложенные записи «лесенкой», что сделает описание
областей памяти целевой платы более наглядным.

Размещение секций
Директива SECTIONS определяет, в какие физические области памяти
целевой платы будут размещены те или иные секции программы. Например,
для размещения секций .text и .cinit во флэш-память, секции .ebss в банк M0
встроенного ОЗУ, а секции .stack — в банк M1 встроенного ОЗУ нужно дать
следующие указания компоновщику:
38
SECTIONS
{
.text:> FLASH PAGE 0
.ebss:> M0SARAM PAGE 1
.cinit:> FLASH PAGE 0
.stack:> M1SARAM PAGE 1
}

Окончательно командный файл управления компоновкой будет иметь вид

MEMORY
{
PAGE 0:/* Память программ */
FLASH: origin = 0x3D8000, length = 0x20000
PAGE 1:/* Память данных */
M0SARAM: origin = 0x000000, length = 0x0400
M1SARAM: origin = 0x000400, length = 0x0400
}
SECTIONS
{
.text:>FLASH PAGE 0
.ebss:>M0SARAM PAGE 1
.cinit:>FLASH PAGE 0
.stack:>M1SARAM PAGE 1
}

Компоновщик соберет вместе все секции .text исполняемого програм-


много кода из всех файлов проекта и разместит выходной исполняемый код
во флэш-памяти на странице PAGE 0 (в памяти программ). Так же он посту-
пает и с секциями инициализации переменных .cinit. Напротив, выходная
объединенная секция переменных .ebss будет размещена в банке M0 памяти
данных, а секция стека .stack — в банке M1 памяти данных.

Практическая работа

1. Изучите внимательно карту памяти микроконтроллера ‘F2812, пред-


ставленную в табл. 1.8. Считая, что объема встроенной в микроконтроллер
памяти достаточно для решения ваших задач (расширенная память не
используется), составьте описание карты памяти, необходимое для создания
файла управления компоновкой. Предложите свои имена для названия
отдельных областей памяти, определите начальные адреса и длины областей
памяти. Заполните шаблон, приведенный ниже:
39
MEMORY
{
PAGE __: /* Память программ */
_____: origin = ________, length = ________
_______: /* Память данных */
_____: origin = ________, length = ________
_____: origin = ________, length = ________
_____: origin = ________, length = ________
}
2. Какую область памяти Вы отвели под память программ? Почему?
3. При выполнении практических работ рекомендуется использовать банк
H0 ОЗУ для загрузки выполняемого программного кода. Размер этой области
памяти 8К слов вполне достаточен для размещения и отладки самых слож-
ных программ. При этом флэш-память не используется во избежание воз-
можных проблем, связанных с недостаточной квалификацией обучающихся.
Как описать карту памяти для этого случая? Сделайте это.
4. А теперь — самое сложное. Попытайтесь распределить секции программы
между отдельными областями памяти микроконтроллера (см. табл. 1.11).
5. Создайте командный файл управления компоновкой (lnk.cmd) в любом
текстовом редакторе.

Контрольные вопросы
1. Каково назначение отдельных банков встроенной памяти микроконтроллера?
2. Чем отличается секционирование программы на Ассемблере от секционирования
на языке высокого уровня С/С++?
3. Зачем нужен общий формат объектных файлов, создаваемый Ассемблером и
С/С++?
4. Может ли секция, отображенная на страницу Page 0 (память программ), реально
размещаться в ОЗУ?
5. Может ли секция, отображенная на страницу Page 1 (память данных), реально раз-
мещаться во флэш-памяти?

40
Глава 2

СОЗДАНИЕ И ОТЛАДКА ПРОСТЫХ ПРОГРАММ


НА ЯЗЫКЕ ВЫСОКОГО УРОВНЯ С/С++
В СРЕДЕ CODE COMPOSER STUDIO

2.1. ПРОЕКТ. СОЗДАНИЕ НОВОГО ПРОЕКТА

В лаборатории можно работать с драйвером оценочной платы eZdspTM


F2812 и загружать программы в память микроконтроллера, отлаживая их
непосредственно в целевой плате. Можно работать и с драйвером симуля-
тора. В последнем случае понадобится только компьютер с установленной
программой CCS. В верхней строчке CCS, приведенной на рис. 2.1, распола-
гаются разделы меню от меню Файлов (Files) до меню Помощи (Help).
Слева и под основным меню находятся панели инструментов. Будем посте-
пенно в процессе выполнения практических работ осваивать возможности
интегрированной среды CCS.

Рис. 2.1. Общий вид окна интегрированной среды CCS при запуске

41
Проект — это набор всех файлов, требуемых для создания исполняемого
выходного файла (.out), который может быть загружен и выполнен на целе-
вой плате. В состав проекта могут входить:
исходные файлы на Ассемблере и на языке С/С++;
файлы с описанием макросов на Ассемблере и на языке С/С++, так назы-
ваемые подключаемые файлы;
файлы в перемещаемом объектном коде, входящие в стандартные библио-
теки или библиотеки пользователя;
командные файлы управления компоновкой.
Важнейшей особенностью проекта является то, что отсутствует необхо-
димость в копировании файлов из разных мест в один каталог. Проект содер-
жит лишь ссылки на фактическое расположение файлов. Это позволяет
любому числу пользователей из любого числа проектов ссылаться на одни и
те же библиотечные файлы, файлы управления компоновкой, не засоряя ком-
пьютер множеством копий одних и тех же модулей.
Работа в среде CCS начинается либо с создания нового проекта путем
выбора пункта меню Project → New, либо с открытия уже существующего
проекта выбором пункта меню Project → Open (рис. 2.2). Работая над про-
ектом, можно постепенно добавлять файлы в проект (подключать файлы к
проекту) с помощью команды Project → Add files to Project. Пользуясь тем же
меню, в конце работы можно сохранить проект Project → Save и закрыть его
Project → Close. Как обычно, при закрытии проекта можно сохранить нара-
ботки или отказаться от сохранения, если накопленная информация не нужна.
Создание нового проекта по команде Project → New выполняется с помо-
щью диалогового окна, изображенного на рис. 2.3, которое позволяет ввести
имя проекта в поле Project Name и задать директорию размещения проекта
в поле Location. Как и во многих других приложениях под Windows, кнопка
«…» позволят вызвать на экран окно «Обзора папок», в котором можно
выбрать нужную директорию из имеющихся или создать новый каталог.
Если указанные в диалоговом окне каталоги и подкаталоги не существуют,
то они будут автоматически созданы. Это одно из удобств, которое предо-
ставляет интегрированная среда.
При вводе имени проекта одновременно в соответствии с вводимым име-
нем меняется название подкаталога последнего уровня, в котором будет раз-
мещен проект. Таким образом, имя проекта будет строго соответствовать
названию каталога для размещения файлов проекта.

Рис. 2.2. Команда, с которой начинается работа над проектом

42
В диалоговом окне создания нового проекта есть две дополнительные
опции: тип проекта Project Type и целевой микроконтроллер Target. Первая
опция имеет два возможных значения:
• Executable (.out) — создание выходного исполняемого файла в абсо-
лютном объектном коде. Применяется для загрузки и отладки выходного
файла проекта в целевом устройстве;
• Library (.lib) — создание выходного библиотечного файла в перемеща-
емом объектном коде. Применяется для объединения в библиотеку пользова-
теля нескольких уже отлаженных файлов типовых функций. При выборе
этого значения CCS, по существу, работает в качестве программы «библиоте-
каря», интегрируя его возможности.
Вторая опция может иметь три значения, в зависимости от типа целевого
микроконтроллера из семейства ‘C2000, для которого выполняется разра-
ботка программного обеспечения: TMS320C24XX, TMS320C27XX,
TMS320C28XX. Воспользуемся последним значением.
После закрытия диалогового окна создадим файл с расширением .pjt,
который будет содержать все параметры компилятора с языка высокого
уровня С/С++, Ассемблера и компоновщика для процесса сборки проекта.
Вначале эти параметры устанавливаются по умолчанию, а затем в процессе
работы над проектом могут редактироваться. Как уже упоминалось, назва-
ния каталога проекта и собственно файла проекта будут одинаковыми. В
примере, представленном на рис. 2.3, каталог LAB2 будет содержать файл
проекта LAB2.pjt.
При создании нового проекта внутри каталога проекта будет создана
директория Debug (Отладка), в которую в последующем будут записываться
непосредственно исполняемые файлы, полученные в процессе трансляции и
компоновки. Как только новый проект будет создан, появляются как бы вир-
туальные папки, которые могут заполняться в процессе работы над проек-
том, в том числе папка с исходными файлами (Source), с библиотечными
файлами (Libraries), с подключаемыми файлами (Include) и т.п., приведен-
ные на рис. 2.4.

Рис. 2.4. Начальное содержимое


Рис. 2.3. Диалоговое окно создания нового проекта каталога проекта

43
Виртуальной папка называется потому, что она может не содержать фай-
лов, а лишь ссылки на их фактическое место расположения. Вначале эти
папки пусты, о чем свидетельствует отсутствие слева от имени папки значка
«+». Если этот значок есть, то папка имеет какое-то содержимое. Щелкнув на
значке «+» кнопкой мыши, можете раскрыть содержимое виртуальной
папки.
Подключить файл к проекту не означает физически скопировать его в
один из каталогов проекта. Как увидите, это касается не только библиотеч-
ных файлов, но и исходных файлов. Конечно, можно создать исходный файл
непосредственно в каталоге Source. Однако, чтобы среда CCS «увидела»
этот файл, его все равно придется подключить к проекту с помощью
команды Project → Add files to Project.

Практическая работа
1. Создайте новый проект для выполнения практической работы № 2.
Выберите в качестве названия проекта имя LAB2 и разместите проект в
каталоге C:\C28X\LABS\LAB2.
2. Проанализируйте содержимое созданного каталога LAB2 и подкатало-
гов, например, с помощью стандартной программы Windows «Проводник».
3. Сохраните проект. Закройте CCS.
4. Вновь запустите среду CCS. Откройте уже созданный проект.

2.2. СОЗДАНИЕ И РЕДАКТИРОВАНИЕ ФАЙЛА


ИСХОДНОЙ ПРОГРАММЫ НА ЯЗЫКЕ С/С++

Исходные файлы могут создаваться в любом удобном для пользователя


текстовом редакторе, способном генерировать выходные файлы в формате
ASCII без дополнительных символов форматирования. Это требование явля-
ется обязательным, так как исходные файлы будут обрабатываться трансля-
тором с языка Ассемблер или компилятором С/С++. Однако для работы с
исходными файлами лучше всего подходит встроенный в интегрированную
среду CCS редактор, который можно вызвать командой меню File → New →
→ Source File (Файл → Новый → Исходный файл), представленной на рис. 2.5.
Для быстрого вызова встроенного редактора можно воспользоваться
также горячей клавишей <Ctrl+N>. Приемы работы во встроенном редакторе
не отличаются от общеизвестных. Дополнительно встроенный редактор про-
изводит автоматическое цветовое выделение фрагментов текста в зависи-

Рис. 2.5. Вызов встроенного редактора для создания файла с исходной программой

44
Рис. 2.6. Первая программа на С/С++

мости от их функционального назначения, что очень удобно в процессе


редактирования и отладки программ. Работая в среде CCS, постепенно оце-
ните все преимущества встроенного редактора, в том числе возможности
автогенерации имен периферийных регистров и битовых полей по первым
набранным буквам — возможности автопродолжения или автонабора.
После завершения работы по созданию или редактированию исходного
файла его необходимо сохранить на диске. Можно воспользоваться стандарт-
ными командами меню «Файл»: File → Save (Сохранить) или File → Save
As… (Сохранить как…), а также панелью инструментов CCS (пиктограм-
мой дискеты). В общем случае исходные файлы могут размещаться в любом
каталоге, однако более удобно, если они сохраняются в каталоге конкретного
проекта, для которого и создаются.

Практическая работа
1. Вызовите встроенный текстовый редактор.
2. В появившемся окне Untitled1, пользуясь стандартными приемами
редактирования, введите текст исходной программы на языке С/С++ (см. гл. 1).
3. Сохраните работу в каталоге LAB2. Убедитесь в том, что программа
выглядит так, как показано на рис. 2.6.
4. Дайте объяснения цветовому выделению фрагментов текста, которые
выполняет встроенный текстовый редактор.

2.3. СОЗДАНИЕ И РЕДАКТИРОВАНИЕ


ФАЙЛА УПРАВЛЕНИЯ КОМПОНОВКОЙ

В гл. 1 подробно рассмотрели все аспекты создания файла управления


компоновкой (lnk.cmd) для конкретного целевого устройства. В учебных
целях целесообразно размещать программный код исключительно во встро-
енной оперативной памяти микроконтроллера, без использования флэш-
памяти. Для этого в наибольшей степени подходит банк H0 встроенного ОЗУ
45
как имеющий достаточно большую емкость (8К слов). Таким образом, сек-
ции .text и .cinit должны отображаться на страницу 0 памяти программ
(Page 0) и размещаться в кодовом ОЗУ H0SARAM.
Секции .ebss и .stack должны обязательно отображаться на страницу 1
памяти данных (Page 1). При этом секция .ebss для относительно небольших
программ с малым числом переменных может размещаться в банке M1
SARAM, а секция .stack — в банке M0, или наоборот. Последний вариант
может оказаться предпочтительней, так как указатель стека после сброса
процессора автоматически устанавливается на начало банка M1.
Для создания и редактирования файла управления компоновкой можно
использовать встроенный в среду CCS редактор. По умолчанию имя этого
файла может быть lnk, а расширение — .cmd. Можно также связать имя
командного файла с именем проекта, например назвать его lab2.cmd.

Практическая работа
1. Создайте командный файл компоновщика lab2.cmd, пользуясь возмож-
ностями встроенного текстового редактора. Один из возможных вариантов
файла показан на рис. 2.7.
2. Сохраните файл в каталоге проекта LAB2.

Рис. 2.7. Файл управления компоновкой для отладки программ в кодовом ОЗУ

46
Замечание
В общем случае среди секций, отображаемых на память программ, может
быть и секция .reset. Она является частью библиотеки реального времени
выполнения rts2800_ml.lib. Если после описания размещения этой секции
напечатать TYPE=DSECT, то компоновщик будет игнорировать ее —
не будет размещать.

2.4. ПОДКЛЮЧЕНИЕ ФАЙЛОВ К ПРОЕКТУ

Подключение файлов к проекту производится с помощью команды меню


Project → Add Files to Project… (добавить файл к проекту). Пользуясь
встроенным редактором CCS, можно создать любое число файлов на языке
С/С++ или Ассемблере, а также большое число файлов управления компо-
новкой под различные конфигурации вашего оборудования. Однако создать
файл и подключить его к проекту — это разные вещи. Только после подклю-
чения файла к проекту его можно обрабатывать в среде CCS.
Подключение файла к проекту производится с помощью диалогового
окна команды, изображенного на рис. 2.8, которое содержит стандартные
средства перемещения по каталогам. Если знаете, где находится файл и
какое имя он имеет, то можете сразу ввести эти данные. Выделив нужный
файл кнопкой мыши, по команде Открыть его можно добавить в проект.
Для облегчения поиска подключаемых файлов служит опция Тип файлов,
где можно выбрать один из следующих вариантов, приведенных на рис. 2.9.
Как видно из выпадающего меню, к проекту можно подключать:
исходные файлы на языке С (*.c) или на объектно-ориентированном
языке С++ (*.cpp);
исходные файлы на языке Ассемблер (*.a);

Рис. 2.8. Диалоговое окно команды подключения файлов к проекту

47
библиотечные файлы в перемещаемом объектном коде (*.l) или отде-
льные, уже оттранслированные ранее, объектные файлы (*.o);
командные файлы управления компоновкой (*.cmd), а также ряд допол-
нительных файлов, в частности файлов данных, используемых при отладке
(*.dat).
При работе с программой на языке С потребуется подключение библио-
теки функций реального времени выполнения, обычно находящейся в ката-
логе C:\ti\c2000\cgtools\lib\rts2800_ml.lib. С ее помощью выполняются
предварительная инициализация процессора, загрузка начальных значений
переменных (предустановка), а также ряд других функций.
Как только какой-то файл подключается к проекту, слева от списка вирту-
альных каталогов проекта, напротив соответствующей папки, появится знак
«+». Так, при добавлении исходного файла знак «+» появится слева от папки
Source, а при добавлении библиотечного файла — слева от папки Libraries.
Щелкнув на виртуальном каталоге мышкой, увидите подключенные к
проекту файлы. Если ошиблись и подключили не тот файл, установите кур-
сор на имя этого файла и вызовите правой кнопкой мыши контекстно-зави-
симое меню. В этом меню есть команда Remote from Project (удалить из
проекта). Выбрав ее, отключите файл.
Если после операции подключения файлов к проекту щелкнуть мышкой
на значке «+» слева от Project в окне проекта, то появится дерево проекта,
содержащее все подключенные к проекту файлы. На рис. 2.10 показано
дерево проекта после подключения к проекту исходного файла с программой
на языке С lab2.c, командного файла управления компоновкой LAB2.cmd и
библиотеки реального времени выполнения rts2800_ml.lib.
Дерево проекта очень удобно для быстрого редактирования исходных
файлов. Можно всего лишь дважды щелкнуть мышкой на имени этого файла
и сразу попасть в режим редактирования или, щелкнув один раз, вызвать
контекстно-зависимое меню, а затем из него — текстовый редактор.

Рис. 2.10. Дерево проекта после под-


Рис. 2.9. Опции выбора типа подключаемого файла ключения файлов

48
Практическая работа
1. Добавьте к проекту LAB2 созданный исходный файл с программой на
языке С lab2.c. Убедитесь, что файл появился в дереве проекта. Отключите
его от проекта с помощью контекстно-зависимого меню и затем подключите
вновь.
2. Добавьте в проект файл управления компоновкой LAB2.cmd. Имя
этого файла появилось в дереве каталогов и файлов проекта. Теперь имеете
возможность открывать и редактировать с помощью встроенного текстового
редактора уже два файла lab2.c и LAB2.cmd. Проверьте эти возможности.
3. Добавьте к проекту библиотеку функций реального времени выполне-
ния, находящуюся в каталоге “C:\ti\c2000\cgtools\lib\rts2800_ml.lib”.

2.5. УСТАНОВКА ОПЦИЙ КОМПИЛЯТОРА И КОМПОНОВЩИКА


Опции задают режим работы компилятора, Ассемблера и компоновщика.
Они управляют процессами компиляции, трансляции и компоновки и уста-
навливаются исходя из имеющихся ресурсов памяти и производительности
центрального процессора в целевой микропроцессорной системе. Когда
создается новый проект, Code Composer Studio формирует два возможных
набора опций, называемых конфигурациями: одна конфигурация Debug —
Отладочная, а вторая Release — Конечная (ее можно рассматривать как
Оптимизированную). Конфигурации можно переключать. Выбор конкрет-
ной конфигурации производится селектором выбора текущей конфигура-
ции, находящимся в верхней левой части экрана интегрированной среды
CCS, изображенным на рис. 2.11.
Можно воспользоваться также командой меню Project → Configurations..
(Проект → Конфигурации..) и переключить текущую конфигурацию опций
в открывшемся окне, представленном на рис. 2.12.

Рис. 2.11. Селектор выбора текущей конфигурации опций

Рис. 2.12. Окно выбора текущей конфигурации

49
Чтобы облегчить процесс настройки опций сборки проекта, CCS предла-
гает графический пользовательский интерфейс настройки опций. На
начальном этапе работы нас вполне удовлетворят установки по умолчанию.
Сейчас исследуем некоторые из них. Если выбрать в меню Project → Build
Options… (Проект → Опции сборки…), то на экране появится окно, приве-
денное на рис. 2.13, с набором опций компилятора (Compiler) по умолчанию
для категории Basic (Основные).
Как видно, для отладочной конфигурации Debug предполагается:
работа центрального процессора в режиме истинного ‘C28xx с использо-
ванием всех преимуществ системы команд этого процессора (процессор
может работать и в других режимах, в частности совместимости с ‘C24xx и
‘C27xx);
полная символьная отладка;
отсутствие каких-либо методов оптимизации программного кода.
В верхней части окна показано, как выглядит установленный список
опций в командной строке вызова компилятора. К счастью, этот набор вво-
дится автоматически самой интегрированной средой CCS и ручного
набора не требуется.
Переключимся на закладку опций компоновщика (Linker), изображенных
на рис. 2.14. Видно, что по умолчанию Code Composer Studio будет создавать
два типа выходных файлов .out и .map. Файл с расширением .out содержит
исполняемый код, загружаемый в DSP-микроконтроллер, а файл .map —
отчет компоновщика по распределению и использованию памяти в целевой
плате. Оба выходных файла будут размещаться в подкаталоге Debug теку-
щего проекта. Опция модели автоинициализации (Autoinit Model) установ-
лена в состояние Run-time Autoinitialization (автоинициализация в реаль-
ном масштабе времени).
Одной из важнейших опций является опция размера стека (Stack Size).
Значение 0x200 в шестнадцатеричной системе счисления соответствует

Рис. 2.13. Опции компилятора по умолчанию в режиме отладки

50
Рис. 2.14. Опции компоновщика по умолчанию

емкости стека в 512 слов. Этого достаточно для большинства простых проек-
тов. Можно, например, увеличить размер стека вдвое, а затем закрыть окно
опций сборки проекта, нажав клавишу ОК. Сделанные изменения будут
сохранены.

Практическая работа
1. Исследуйте различные категории опций компилятора, но пока не
меняйте их.
2. В опциях компоновщика увеличьте вдвое размер стека до 1 К слова.
Сохраните настройки.

2.6. СБОРКА И ЗАГРУЗКА ПРОЕКТА


Четыре кнопки в центральной части горизонтальной панели инструментов,
приведенные на рис. 2.15, отвечают за процесс компиляции и сборки проекта.
Их название и назначение приведены в табл. 2.1.
Компиляция или трансляция отдельного исходного файла (клавиша 1)
выполняется тогда, когда этот файл только создан и необходимо убедиться,
что в файле отсутствуют синтаксические ошибки, например, неверно запи-
санные команды на языке Ассемблер, неправильные директивы, неправиль-
ные операторы языка С/С++.

Рис. 2.15. Кнопки управления процессом компиляции и сборки проекта

51
Табл и ц а 2.1
Назначение клавиш управления компиляцией и сборкой проекта

Кнопка Название Описание


1 Compile File Компилировать или ассемблировать текущий открытый файл
(исходный)
2 Incremental Build Компилировать или ассемблировать только файлы, изменившиеся
с момента предыдущей работы над проектом, а затем компоновать
3 Rebuild All Перекомпилировать или переассемблировать все файлы проекта,
а затем скомпоновать
4 Stop Build Остановить генерацию кода (любой из вышеперечисленных
процессов, если он запущен)

В процессе компиляции могут выдаваться не только ошибки, но и предуп-


реждения. Предупреждения — это не фатальные ошибки. Например, при
компиляции файла с программой lab2.c в нижнем окне Build (Сборка) будет
отображен процесс компиляции:
указана командная строка вызова компилятора cl2000 со всеми текущими
опциями;
выведено предупреждение о том, что переменная z была установлена, но
далее нигде не используется;
приведен общий итог компиляции — 0 ошибок, 1 предупреждение,
0 замечаний (рис. 2.16).
Все сообщения об ошибках и предупреждениях выводятся красным цветом.
Как видите, ошибок в программе нет, но есть предупреждение о том, что
вычисленная переменная не используется. Это соответствует истине, так как
в нашей простой программе производится лишь вычисление локальной
переменной z внутри главной функции main. Если получите сообщение об
ошибке, «прокрутите окно сборки проекта», пока не увидите, выделенное
красным цветом сообщение. Двойным щелчком мыши по сообщению можно
сразу перейти к строке исходного текста, в которой содержится ошибка.
При этом курсор будет автоматически установлен на соответствующую
строку — можно делать исправления.
Если этап трансляции и объединения нескольких файлов проекта уже
пройден и необходимо добавить к проекту еще один исходный файл, то
выполняется команда последовательной сборки проекта (клавиша 2). Нако-
нец, можно не думать о том, в каком состоянии находятся все исходные
файлы проекта и просто перекомпилировать весь проект (клавиша 3). Это

Рис. 2.16. Окно сборки проекта Build

52
Рис. 2.17. Установка опции автоматической загрузки программы после сборки

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


мере работы над проектом в любых исходных файлах.
Последняя клавиша с символом x прерывает начатый процесс сборки.
Следующий этап работы над проектом состоит в загрузке выходного кода
программы (файла с расширением .out) в память целевой платы. Для этой
цели в меню File предусмотрена команда Load Program… (Загрузка про-
граммы…), приведенная на рис. 2.17.
Code Composer Studio может осуществлять и автоматическую загрузку
программы после сборки в память микроконтроллера. Найдите в меню пункт
Option → Customize… (Опции → Настройки), а затем выберете закладку
Program Load Options (Опции загрузки программы) и установите
«галочку» напротив позиции Load Program After Build (Загрузка про-
граммы после сборки), нажмите OK (см. рис. 2.17). В этом случае сразу
после сборки проекта и создания выходного исполняемого файла он будет
автоматически загружаться в целевую плату.

Практическая работа
1. Выполните компиляцию исходного файла lab2.c. Убедитесь в том, что
ошибок нет.
53
Рис. 2.18. Сообщение об ошибке компиляции

2. Сознательно внесите ошибку в программу на языке С. Например, опус-


тите символ «;» в конце оператора присваивания. Повторяя процесс компи-
ляции, получите сообщение о том, что символ ‘;’ в конце оператора отсут-
ствует (expected a «;») (рис. 2.18).
3. Исправьте ошибку. Перекомпилируйте программу.
4. Нажмите кнопку Build All (Собрать все) и посмотрите, как компиля-
тор и компоновщик выполняются в окне сборки. Проверьте наличие ошибок.
Поэкспериментируйте. Например, в командном файле в описании кодового
ОЗУ замените цифру 0 на букву О (часто встречающаяся опечатка):
HOSARAM. Выполните сборку. Проанализируйте полученные сообщения об
ошибках.
5. Исправьте ошибку. На будущее учтите, что одна ошибка может вызы-
вать сразу несколько сообщений об ошибках во время сборки, что и про-
изошло в данном случае.
6. Выполните новую сборку проекта (теперь ошибки должны отсутство-
вать).

Если установлена опция автоматической загрузки выходного исполняе-


мого файла в память микроконтроллера после завершения сборки проекта,
то процесс загрузки будет сопровождаться автоматическим открытием окна
дизассемблера, приведенного на рис. 2.19, в котором отображаются машин-
ный код программы пользователя и соответствующие ему команды на языке
Ассемблер. Начальный адрес размещения программы пользователя соот-
ветствует имени основной программы main и началу кодовой секции .text.
При этом счетчик команд автоматически установится на метку _c_int00
начала процедуры инициализации. Текущее положение счетчика команд в
окне дизассемблера отображается зеленой стрелкой слева от адреса соот-
ветствующей ячейки памяти.
Для того чтобы информация в окне дизассемблера отображалась пра-
вильно, необходимо в меню GEL → Addressing Mode → C28x_Mode
выбрать режим адресации истинного ‘C28x — микроконтроллера, как пока-
зано на рис. 2.20. В противном случае, например когда случайно установлен
режим адресации ‘С27х, коды ‘C28x будут интерпретироваться неверно. При
этом некоторые инструкции могут вообще не распознаваться и заменяться
выделенной красным цветом директивой .word с указанием неизвестного
кода операции.
Дадим короткий предварительный комментарий о назначении процедуры
инициализации. Она предназначена:
для инициализации собственно микроконтроллера, т.е. установки режима
его работы;
54
Рис. 2.19. Окно дизассемблера сразу после загрузки исполняемого файла в память

Рис. 2.20. Задание режима адресации для правильного отображения команд

для инициализации переменных, которые должны иметь определенные


начальные значения — для предустановки значений переменных. Например,
первая ассемблерная команда в процедуре инициализации MOV @SP,
#0x0000 выполняет загрузку указателя стека процессора SP нулевым значе-
нием. Это правильно, так как задали расположение сегмента стека .stack в
банке M0 ОЗУ, начиная с нулевого адреса. Вторая команда SPM 0 задает
начальный режим сдвига произведения в аппаратном умножителе микрокон-
троллера (нет никакого сдвига). Третья команда SETC OBJMODE устанав-
ливает объектный режим работы микроконтроллера (истинный ‘C28xx), а
четвертая команда CLRC AMODE — режим адресации (истинного ‘C28xx).
Далее следует еще ряд команд инициализации микроконтроллера, в част-
ности команда установки начального значения указателя текущей стра-
ницы памяти данных MOVW DP, #0x0000.
Процедура инициализации генерируется автоматически с помощью биб-
лиотеки реального времени выполнения, и пользовать может вообще не
иметь представления о том, что там конкретно делается. Главное, чтобы про-
цессор и память были правильно подготовлены к решению задачи пользова-
теля. Если пишете программу исключительно на Ассемблере, то можете
сами проинициализировать процессор в своей исходной программе и под-
ключение библиотеки реального времени выполнения не понадобится.
55
Контрольные вопросы

1. К какой области памяти микроконтроллера относится начальный адрес 0x3F 8000?


Правильно ли размещен программный код нашей программы main?
2. Правильно ли выполнена инициализация указателя стека?

2.7. АНАЛИЗ ФАЙЛА С КАРТОЙ ЗАГРУЗКИ

Файл карты загрузки .map содержит информацию о фактическом месте


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

OUTPUT FILE NAME: <./Debug/LAB2.out>


ENTRY POINT SYMBOL: "_c_int00" address: 003f800a

Видно, что точкой входа в программу является адрес размещения про-


граммы инициализации “_c_int00”.
Далее приводится информация о текущей конфигурации памяти целе-
вого устройства: список блоков памяти, входящих в память программ (PAGE
0) и память данных (PAGE 1) с указанием начальных адресов блоков, длин
блоков и их фактической загрузки (колонка used — «используется»):

MEMORY CONFIGURATION
name origin length used attr fill
---------------------- -------- -------- -------- ---- --------
PAGE 0: H0SARAM 003f8000 00002000 000000c7 RWIX
PAGE 1: M0SARAM 00000000 00000400 00000200 RWIX
M1SARAM 00000400 00000400 00000086 RWIX

Выдается карта фактического размещения секций программы в памяти


устройства с указанием страницы памяти программ (Page 0) или памяти дан-
ных (Page 1), начального адреса и длины соответствующей секции:
56
SECTION ALLOCATION MAP
output attributes/
section page origin length input sections
-------- ---- ---------- ---------- ----------------
.text 0 003f8000 000000a4
003f8000 0000000a lab2.obj (.text)
003f800a 00000046 rts2800_ml.lib : boot.obj (.text)
003f8050 0000004b : exit.obj (.text)
003f809b 00000009 : _lock.obj (.text)
.cinit 0 003f80a4 00000021
003f80a4 00000008 lab2.obj (.cinit)
003f80ac 0000000e rts2800_ml.lib : exit.obj (.cinit)
003f80ba 0000000a : _lock.obj (.cinit)
003f80c4 00000001 --HOLE-- [fill = 0]
.reset 0 003f80c6 00000002
003f80c6 00000002 rts2800_ml.lib : boot.obj (.reset)
.data 1 00000000 00000000 UNINITIALIZED
.bss 1 00000000 00000000 UNINITIALIZED
.stack 1 00000000 00000200 UNINITIALIZED
.ebss 1 00000400 00000086 UNINITIALIZED
00000400 00000080 rts2800_ml.lib : exit.obj (.ebss)
00000480 00000004 : _lock.obj (.ebss)
00000484 00000002 lab2.obj (.ebss)

Обратите внимание на колонку под названием атрибуты (attributes), где


указано, из каких исходных секций собрана результирующая секция. Видно,
что в кодовую секцию .text входят четыре отдельные секции, из которых
только одна (первая) относится к исходной программе lab2.obj (.text), а
остальные три — являются «обслуживающими» и автоматически формиру-
ются компилятором с использованием библиотеки реального времени
выполнения rts2800_ml.lib, например секция начальной загрузки boot.obj
(.text). Удобно, что для каждой секции указываются ее начальный адрес раз-
мещения в памяти и длина. Таким образом, получаете информацию о месте
размещения «обслуживающих» программ и можете при желании изучить их
код.
Из общей длины кодовой секции.text 0a4h (164 слова) только 10 слов
(0ah) занимает код нашей программы, остальное — код «обслуживающих»
программ. Аналогичная ситуация и в секции инициализации констант .cinit:
из 33 слов (21h), отведенных в памяти программ для этой секции, только
восемь слов относятся к секции инициализации собственно программы
пользователя lab2.obj (.cinit), а остальные — к секциям инициализации кон-
стант для «обслуживающих» программ.
57
Видно, что секция данных .ebss содержит только два слова, зарезервиро-
ванных собственно программой пользователя — секция lab2.obj (.ebss),
остальные 84h (132) слова относятся к секциям данных «обслуживающих»
программ.
В конце файла карты загрузки .map приводится список всех глобальных
имен, отсортированный по именам:

GLOBAL SYMBOLS: SORTED ALPHABETICALLY BY Name


address name
-------- ----
00000000 .bss
00000000 .data
003f8000 .text

00000200 __STACK_SIZE

003f8000 _main
00000485 _x
00000484 _y

а также список глобальных имен, отсортированных по адресам:

GLOBAL SYMBOLS: SORTED BY Symbol Address


address name
-------- ----
00000000 ___data__

00000200 __STACK_SIZE

00000484 _y
00000485 _x
003f8000 .text
003f8000 _main

Переменные нашей программы (x, y) размещаются в ОЗУ по адресам 485h


и 484h соответственно.

Практическая работа
1. Средствами операционной системы, например, с помощью «Провод-
ника» исследуйте содержимое каталога LAB2. Есть ли там автоматически
созданный подкаталог Debug (Отладка)? Содержатся ли в нем следующие
файлы: LAB2.obj — выходной объектный файл в перемещаемом объектном
58
коде, созданный компилятором; LAB2.out — исполняемый выходной файл в
неперемещаемом объектном коде, созданный компоновщиком; LAB2.map —
файл с картой загрузки?
2. Проанализируйте содержимое файла с картой загрузки. Правильно ли
распределена память микроконтроллера? Соответствует ли это распределе-
ние содержимому командного файла lab2.cmd?
3. Распределены ли секции программы в соответствии с указаниями в
командном файле lab2.cmd?

2.8. ИСПОЛЬЗОВАНИЕ ОКОН ОТЛАДОЧНОЙ СРЕДЫ CCS

Для того чтобы сразу после загрузки перейти к основной программе на


языке С/С++ main(), в меню Debug нужно выбрать команду Go Main (Пере-
ход на главную программу). При этом автоматически будет выполнена про-
цедура начальной инициализации, созданная в процессе сборки проекта с
помощью библиотеки реального времени выполнения rts2800_ml.lib (рис. 2.21).
Обычно во время отладки программы требуется следить за состоянием гло-
бальных и локальных переменных. Для этого в среде Code Composer Studio
существует несколько способов. Рассмотрим лишь два наиболее простых:
окно памяти (memory window) и окна наблюдения (watch window) —
окна наблюдаемых переменных.
Чтобы открыть окно содержимого памяти (memory window), которое
часто называют окном «дампа» памяти, нужно воспользоваться командой
меню View → Memory (Просмотр → Память). Появится диалоговое окно
этой команды, приведенное на рис. 2.22, которое позволит в явной форме
задать начальный адрес интересующей пользователя области памяти или
задать область памяти неявно, как содержащую интересующую нас перемен-
ную. В последнем случае для задания адреса нужно воспользоваться опреде-
ленным в программе символическим именем переменной. Например, для

Рис. 2.21. Окно исходной программы на языке С/С++

59
отображения в окне «дампа» памяти содержимого локальной переменной z
из программы lab2.c необходимо в поле адреса (Address:) ввести &z, что на
языке С/С++ означает адрес переменной z (а не ее значение). Помните, что
Code Composer Studio отличает символы, написанные в верхнем и нижнем
регистрах. Если записать в поле адреса &z, то получим сообщение об
ошибке, так как переменная z не определена (имя z в процессорах ‘C28 носит
специальный битовый флаг нулевого результата текущей операции).
В поле Format (Формат) можно задать желаемый стиль отображения
данных, например: Hex — C Style (шестнадцатеричные данные в формате
языка С/С++) или Hex — TI Style (шестнадцатеричные данные в формате
фирмы Texas Instruments, стандартном для микропроцессорной техники).
Если необходимо интерпретировать данные в памяти как 16-разрядные
целые числа со знаком в дополнительном коде, то выберите формат 16-Bit
Signed Int. Имеется возможность отображать содержимое памяти и в стан-
дартном формате числа с плавающей точкой (Use IEEE Float). При этом
подразумевается, что под каждое число такого типа выделены две последо-
вательные ячейки памяти (длинное слово). На рис. 2.23 показаны различные
форматы отображения содержимого области памяти с интересующей нас
локальной переменной z.
В диалоговом окне опций «дампа» памяти в поле Page (Страница) указы-
вается нужная страница памяти (память программ или память данных). Так
как переменная z расположена в ОЗУ, то указывается страница памяти дан-
ных (Data). Нажав кнопку OK, увидите, что окно «дампа» памяти открылось
и начальный адрес указывает на переменную z.

Рис. 2.22. Диалоговое окно опций «дампа» Рис. 2.23. Варианты отображения содержи-
памяти мого памяти

60
В нашем примере переменная z явля-
ется локальной и расположена в стеке, а
ее адрес совпадает с дном стека. Напом-
ним, что сегмент стека разместили в банке
M0 ОЗУ, начиная с адреса 0x0000. Стек в
процессорах ‘C28xx увеличивается в
направлении от младших адресов к стар-
шим.
В процессе отладки программы можно
быстро изменять содержимое памяти по Рис. 2.24. Диалоговое окно редактирова-
любому адресу, используя двойной щел- ния памяти
чок мыши на содержимом нужной ячейки
памяти. При этом открывается диалоговое окно редактирования памяти,
изображенное на рис. 2.24, которое позволяет модернизировать значение не
только в текущей ячейке, но и в других последовательно расположенных за
ней (вверх или вниз) ячейках памяти.
Отредактированное значение автоматически выделяется в окне «дампа»
памяти красным цветом как напоминание программисту о том, что данные
были изменены.
Для удобства интерпретации данных можно менять текущий формат
отображения данных, например от шестнадцатеричной формы представле-
ния переходить к десятичной. Для этого необходимо, находясь в окне
«дампа» памяти (когда окно активно), щелкнуть правой кнопкой мыши для
вызова контекстно-зависимого меню и выбрать команду Properties (Свой-
ства). Появится уже знакомое окно Memory Window Options (Опций окна
памяти), в котором из выпадающего меню следует выбрать желаемый фор-
мат представления данных. Таким образом, в процессе отладки нет необхо-
димости пользоваться дополнительными средствами, например калькулято-
ром, — все операции по переводу чисел из одной системы счисления в
другую будет выполнять интегрированная среда CCS.

Практическая работа
1. Выполните переход на главную программу main(). Убедитесь в том, что
окно исходной программы на С/С++ открылось и программа готова к
отладке.
2. Откройте окно «дампа» памяти данных для наблюдения за значением
переменной z. Поэкспериментируйте с различными форматами представле-
ния данных, пользуясь контекстно-зависимым меню, прокомментируйте
полученные результаты.
3. Попробуйте модифицировать значения в отдельных ячейках памяти
данных и в определенной непрерывной области.
4. Закройте окно «дампа» памяти и откройте его вновь, указав в качестве
начального адреса области памяти текущее содержимое указателя стека SP.
Объясните, почему в этом случае символ & перед SP не ставится? Какая
область памяти отображается в окне? Почему?

61
Следующим типом окна, которое широко используется при отладке про-
грамм на языке С/С++, является окно наблюдаемых переменных (watch
window), изображенное на рис. 2.25. Для того чтобы отобразить в таком окне
значение локальной переменной z, нужно выбрать пункт меню View Watch
Window (Просмотр → Окно наблюдения) и щелкнуть мышкой на закладке
Watch Locals (Наблюдаемые локальные переменные).
Окно Watch Window будет по умолчанию содержать локальные перемен-
ные для текущей выполняемой функции. Напомним, что все локальные пере-
менные располагаются в стеке. В языке С/С++ при вызове любой функции
автоматически выделяется место в стеке под локальные переменные этой
функции. После передачи результатов в вызывающую функцию место в
стеке автоматически высвобождается.
Текущее значение локальной переменной можно изменять и в окне
наблюдаемых переменных, что значительно удобнее, чем в окне «дампа»
памяти. Для этого нужно щелкнуть кнопкой мыши в столбце Value (Значе-
ние) в строке с именем переменной z для активизации режима редактирова-
ния, ввести новое значение переменной и завершить ввод нажатием клавиши
Enter. Можно также изменить формат отображения данных. Для этого
достаточно щелкнуть мышью в позиции Radix (Основание системы счис-
ления) и выбрать требуемую форму представления, приведенную на
рис. 2.26: шестнадцатеричное (hex), десятичное (dec), двоичное (bin) или
восьмеричное число (oct), символ (char), число в формате с плавающей точ-
кой (float), в научном формате высокой точности (scientific), число без знака
(undigned).
Вторая закладка Watch1 в окне наблюдаемых переменных Watch Window
предназначена для добавления глобальных переменных (см. рис. 2.25). Если
выбрать эту закладку и щелкнуть левой кнопкой мыши в пустом контейнере
в колонке Name (Имя), то откроется поле ввода, в котором можно будет
набрать имя интересующей нас переменной, завершив ввод имени нажатием
клавиши Enter. Операцию добавления глобальных переменных можно мно-
гократно повторять. Знак & перед символическим именем глобальной пере-
менной не ставится. В колонке Value (Значение) выводятся текущие значе-

Рис. 2.26. Возможные


форматы представления
данных в окне наблюда-
Рис. 2.25. Окно наблюдаемых переменных в момент открытия емых переменных

62
Рис. 2.27. Окно наблюдаемых глобальных переменных

ния переменных, в колонке Type (Тип) — тип глобальных переменных, а в


колонке Radix (Основание системы счисления) — формат представления
данных (рис. 2.27).

Практическая работа
1. Откройте окно локальных переменных Watch Locals. Модифицируйте
значение локальной переменной z. Убедитесь в том, что адекватные измене-
ния происходят и в окне «дампа» памяти.
2. Добавьте в окно наблюдаемых переменных глобальные переменные x и
y. Соответствуют ли их текущие значения начальным значениям, указанным
в программе?
3. Откройте второе окно Watch 1 «дампа» памяти, указав в качестве
начального адреса &y. Проверьте, что значения наблюдаемых переменных в
окне Watch Window такие же, как в окне «дампа» памяти Memory Window,
приведенном на рис. 2.28. Опробуйте механизм редактирования значений
глобальных переменных. Убедитесь, что изменение значения переменной в
одном из окон автоматически приводит к изменению его значения в другом
окне, а модифицированные значения переменных подсвечиваются красным
цветом.
4. Исследуйте файл с картой загрузки lab2.map и убедитесь, что адреса
размещения глобальных переменных x и y действительно 0x0485 и 0x0484.
5. В какой области встроенного ОЗУ компоновщик разместил глобальные
переменные?

Рис. 2.28. Второе окно «дампа» памяти с глобальными переменными x и y

63
2.9. ВЫПОЛНЕНИЕ ПРОГРАММЫ ПО ШАГАМ
Выполнение функции main() по шагам осуществляется с помощью горя-
чей клавиши F8 или кнопки Single Step на вертикальной панели инструмен-
тов. Желтая стрелка в окне исходной программы на С/С++ указывает на
текущий активный оператор. Информация в окнах памяти и наблюдаемых
переменных автоматически обновляется, что позволяет проконтролировать
ход выполнения программы и дать заключение о ее работоспособности.

2.10. ЯЗЫКИ С/C++ И АССЕМБЛЕР


Компилятор с языка С/С++ создает
промежуточный выходной файл на
языке Ассемблер. После трансляции
ассемблерного файла генерируется
выходной исполняемый файл в машин-
ном коде (.out). Результаты трансляции
сохраняются в файле листинга (.lst).
Этот файл можно проанализировать,
поскольку он является текстовым.
Кроме того, интегрированная среда
CCS предоставляет уникальную воз-
можность одновременного отображе-
ния в окне исходного кода программы
на языке С/С++ соответствующего ему
кода программы на языке Ассемблер.
Это очень удобно для изучающих архи-
тектуру процессора и его систему
команд параллельно с освоением языка
высокого уровня С/С++, а также для
квалифицированных пользователей,
которые в состоянии самостоятельно
выполнить оптимизацию, заменив
неоптимизированные участки кода
оптимизированными. Выберем в меню
View (Просмотр) команду Mixed
Source/ASM (Смешанный исходный
код на С/С++ и Ассемблере), как
показано на рис. 2.29. Окно исходного
кода С-программы преобразится так,
как приведено на рис. 2.30.
Видно, что код основной про-
Рис. 2.29. Задание режима одновременного граммы main размещается, начиная с
отображения кодов на С/С++ и Ассемблере адреса 3F8000, т.е. в начале банка H0
64
Рис. 2.30. Смешанное представление кода исходной программы на языке С/С++ и на Ассемблере

встроенного ОЗУ, отведенного нами под память программ. Для того чтобы
понять ассемблерный код, сгенерированный компилятором, необходимо
знать унифицированный принцип выделения места под локальные перемен-
ные и порядок доступа к ним:
• место под локальные переменные называется фреймом локальных пере-
менных и всегда резервируется в стеке в начале выполнения любой функ-
ции, в том числе функции main;
• резервирование выполняется за счет инкрементирования указателя
стека SP на величину, равную длине фрейма локальных переменных в коли-
честве слов памяти, выделенных под эти переменные. При этом указатель
стека всегда будет показывать на «первую свободную ячейку памяти», не
относящуюся к фрейму локальных переменных;
• доступ к любой локальной переменной внутри фрейма производится с
помощью стековой базовоиндексной предекрементной адресации —
SP[index], где функцию базы выполняет указатель стека SP, а индекс — зада-
ется явно;
• перед выходом из функции место в стеке, выделенное под локальные
переменные, высвобождается. Это делается с помощью декрементирования
указателя стека SP на величину, равную длине фрейма локальных переменных.
Дадим покомандный комментарий к программе на Ассемблере.
1. В программе используется одна локальная переменная z типа long. Поэ-
тому фрейм локальных переменных имеет длину 2 (два слова). Чтобы выде-
лить место под локальную переменную, указатель стека SP нужно дважды
инкрементировать:
65
ADDB SP, #2 ;Сложение содержимого указателя стека
;с байтовой константой 2
;Константа перед сложением расширяется
;слева нулями до слова

Для последующего доступа к переменной z компилятор должен будет


использовать адресацию типа –SP [2].
2. Из анализа карты загрузки программы .map следует, что глобальные пере-
менные располагаются в памяти данных по адресам: x → 0485h; y → 484h.
Для обращения к ним с использованием прямой адресации указатель теку-
щей страницы памяти DP должен быть проинициализирован значением:
484h/40h = 12h (размер страницы памяти данных в процессорах ‘C28 равен
64 словам или 40h). Таким образом, переменные x и y находятся на странице
памяти номер 12h (на 18-й) и имеют относительные адреса: x → 5; y → 4.
Для инициализации указателя текущей страницы DP используется команда
непосредственной загрузки 16-разрядного слова в регистр DP:
MOVW DP,#012H ;DP(15:0) = 16-битовой константе

3. Необходимо выполнить сложение целых чисел со знаком в дополни-


тельном коде x + y. Причем по умолчанию компилятор будет делать это в
рамках 16-разрядной арифметики, так как переменные x и y имеют тип
16-разрядное целое (int). Это означает, что автоматического контроля пере-
полнения при сложении и перехода к 32-разрядной арифметике не предпола-
гается! Сложение будет производиться исключительно в рамках младшего
слова аккумулятора. Для этой операции установка флага SXM не имеет зна-
чения. Однако дальше результат сложения должен быть присвоен 32-разряд-
ной переменной z, т.е. потребуется преобразование числа из 16-разрядного
знакового формата в 32-разрядный знаковый. Именно для этой цели устанав-
ливается флаг SXM — режим автоматического расширения знакового раз-
ряда:
SETC SXM ;Режим расширения знакового разряда

4. Загружаем первое слово y в младшую часть аккумулятора, используя


прямую страничную адресацию:
MOV AL,@4

5. Добавляем второе слово x (исключительно 16-разрядное сложение,


перенос «С» в старшую часть аккумулятора AH не записывается):
ADD AL,@5

6. Выполняем загрузку 16-разрядного числа со знаком из младшей части


аккумулятора AL в 32-разрядный аккумулятор АСС с автоматическим рас-
66
ширением знакового разряда (именно для этого понадобилось установить
флаг SXM):
МОV ACC,@AL<<5

7. Полученное 32-разрядное число сохраняем в стеке по месту располо-


жения локальной переменной z:
MOVL *-SP[2],ACC

8. Ликвидируем фрейм локальных переменных, т.е. восстанавливаем


начальное значение указателя стека:
SUBB SP,#2

9. Выполняем команду выхода из процедуры:


LRETR

Практическая работа
1. Исследуйте работу программы в пошаговом режиме при различных
наборах входных переменных: (x, y) = (2, 5); (32766, 1); (32766, 2); (–1, –2);
(–32767, –1); (–32767, –2).
2. Объясните результат. К сожалению, его нельзя предсказать, глядя на
программу, написанную на языке С/С++. Для сохранения результата специ-
ально выбрали 32-разрядную переменную, чтобы учесть возможные пере-
полнения.
3. Подумайте, как же быть, если необходим 32-разрядный результат с уче-
том возможных переполнений.

Контрольные вопросы
1. Чем отличаются локальные переменные от глобальных и где они размещаются?
2. Зачем необходимо подключать к проекту библиотеку реального времени?

67
Глава 3

ЗНАКОМСТВО С ЯЗЫКОМ ПРОГРАММИРОВАНИЯ СИ


ДЛЯ МИКРОКОНТРОЛЛЕРОВ TMS320x28xx.
ОСВОЕНИЕ МЕТОДОВ ОТЛАДКИ ПРОГРАММ
В СРЕДЕ CODE COMPOSER STUDIO

3.1. КРАТКИЕ ОБЩИЕ СВЕДЕНИЯ


О ЯЗЫКЕ ПРОГРАММИРОВАНИЯ Си

Язык программирования С (стандартная версия) и С++ (с возможностями


объектно-ориентированного программирования) в настоящее время является
одним из базовых языков программирования высокого уровня и применяется
при решении наиболее сложных задач, в том числе требующих качествен-
ного управления большим числом периферийных устройств в реальном вре-
мени. Не случайно он стал базовым и для разработки программного обеспе-
чения высокопроизводительных сигнальных микроконтроллеров фирмы
Texas Instruments. Далее вместо названия двух версий языка С/С++ будем
использовать одно общее имя «Си».
Язык Си для ‘C28xx полностью соответствует международному стан-
дарту С99, и для его качественного изучения необходимо пользоваться соот-
ветствующей литературой. Транслятор Си для ‘C28xx имеет некоторые осо-
бенности, связанные с архитектурой центрального процессора, а также с
методикой доступа к периферийным регистрам процессора. Осваивая техно-
логию разработки и отладки программного обеспечения на языке Си в среде
Code Composer Studio, будем, естественно, обращать внимание на эти осо-
бенности.
Как уже известно, содержать главную функцию main() должна любая про-
грамма на языке Си, пример которой приведен ниже:

Лист. 3.1. Пример простой программы на языке Си


int i; /* описание переменной типа целое со знаком */
void main(void) /* вызов главной функции программы */
{
i++; /* текст основной программы: */
/* инкрементируем переменную i */
}

68
В этом простом примере переменная i типа целое (int) всего лишь один
раз инкрементируется. Для обозначения операции инкрементирования в
языке Си применяются два подряд набранных символа «плюс» ++, а для
обозначения операции декрементирования — два подряд набранных знака
«минус» – –. Таким образом, оператор i++; эквивалентен оператору i = i + 1;.
В общем случае программа на языке Си состоит из одной или более функ-
ций. Каждая функция создается для решения определенной подзадачи и
может быть вызвана из главной функции. Эти функции могут описываться
как в текущем файле, так и в каком-либо другом файле. Кроме того, ряд
наиболее важных функций может быть предварительно разработан, отлажен
и включен в библиотеку стандартных функций. Библиотеки стандартных
функций обычно поставляются разработчиками компилятора Си или разра-
ботчиками микроконтроллера, а пользовательские библиотеки создаются
программистами для решения своих типовых задач.
Функциям можно давать любые имена по усмотрению разработчика, но
main — это особое имя — главная, основная функция. Выполнение любой
программы всегда начинается с главной функции. Каждая программа на
языке Си должна содержать эту функцию. Из главной программы можно
вызывать другие функции, описанные в текущей программе, а также библио-
течные функции.
Круглые скобки, следующие за именем функции, содержат список аргу-
ментов функции. Если функция не имеет аргументов, как в примере выше, то
скобки все равно нужно указывать (). Все операторы, входящие в тело функ-
ции, заключаются в фигурные скобки { }. Обращение (вызов) функции осу-
ществляется указанием ее имени, вслед за которым следует заключенный в
круглые скобки список аргументов.
В языке Си предполагается, что все переменные должны быть обяза-
тельно описаны до момента их использования. Обычно это делается в начале
функции до первого выполняемого оператора. Если забудете вставить описа-
ние какой-либо переменной, то получите диагностическое сообщение об
ошибке в процессе трансляции программы.
Для ввода комментария в языке Си используются либо два подряд распо-
ложенных символа косой черты // (если комментарий не переходит на дру-
гую строку), либо специальные символы начала /* и конца комментария */.
Все операторы программы на языке Си заканчиваются точкой с запятой.
Мы постепенно будем изучать базовые возможности языка Си.

Имена переменных и констант


Имена переменных в Си составляются из букв и цифр. Первый символ
должен быть обязательно буквой. Символ подчеркивания «_» также счита-
ется буквой. Он часто используется в качестве разделителя для составных
имен переменных. Компилятор различает одни и те же имена, набранные
прописными и строчными буквами. Традиционная практика при программи-
ровании на Си — использовать строчные буквы для имен переменных, а про-
писные — для символических имен констант. Ключевые слова, такие как if,
69
else, int, float и т.д., зарезервированы и не могут использоваться в качестве
имен переменных. Ключевые слова записываются строчными буквами.
Основная рекомендация при выборе имен переменных — использовать
имена, отражающие назначение (смысл) переменных в программном модуле.

Типы данных
В языке Си имеется несколько стандартных типов данных: int — целое
число; float — вещественное число в формате с плавающей точкой одинарной
точности; char — символ. Имеется также ряд квалификаторов стандарт-
ных типов: short (короткое), long (длинное), unsigned (беззнаковое),
signed (знаковое). Квалификаторы типа short и long указывают на различные
размеры переменных, а квалификаторы signed и unsigned — на формат
представления — число со знаком в дополнительном коде или число без
знака соответственно.
Перечень скалярных типов данных, поддерживаемых процессором
TMS320x28xx, с указанием длины и способа представления переменной
каждого типа, а также диапазона возможного изменения значений перемен-
ных, представлен в табл. 3.1.
Табл и ц а 3.1
Типы данных языка Си, поддерживаемые компилятором ‘C28xx
Диапазон
Тип Длина Представление
Минимум Максимум
char, signed char 16 бит Код ASCII –32768 32767
символьный, знаковый символьный
unsigned char 16 бит Код ASCII 0 65535
беззнаковый символьный
short 16 бит 2s complement –32768 32767
короткий Дополнительный код
unsigned short 16 бит Binary 0 65535
беззнаковый короткий Двоичный код
int, signed int 16 бит 2s complement –32768 32767
целый, знаковый целый Дополнительный код
unsigned int 16 бит Binary 0 65535
беззнаковый целый Двоичный код
long, signed long 32 бита 2s complement –2 147 483 648 2 147 483 647
длинный, знаковый длинный Дополнительный код
unsigned long 32 бита Binary 0 4 294 967 295
беззнаковый длинный Двоичный код
enum 16 бит 2s complement –32768 32767
перечисление, целый Дополнительный код
float 32 бита Формат IEEE 32 бита 1,17549435e–38 3,4028235e+38
вещественный
double 32 бита Формат IEEE 32 бита 1,17549435e–38 3,4028235e+38
вещественный двойной точности
long double 32 бита Формат IEEE 32 бита 1,17549435e–38 3,4028235e+38
вещественный длинный двойной
точности
pointers 16 бит Binary 0 0xFFFF
указатель Двоичный код
far pointers 22 бита Binary 0 0x3FFFFF
длинный указатель Двоичный код

70
Примеры описаний переменных с различными квалификаторами типа
приведены ниже:

short int a;
long int b;
unsigned int result;

Переменная а имеет тип «короткое целое», переменная b — длинное


целое, а переменная result — беззнаковое целое.
Слово int в таких ситуациях может быть опущено, что обычно и делается.
Количество битов, отводимых под эти объекты, зависит от типа централь-
ного процессора, в частности от длины операндов, обрабатываемых в АЛУ,
и, естественно, от реализации компилятора. В нашем случае все переменные
типа int являются 16-разрядными.

Замечания

1. В языке Си для процессоров TMS320x28xx тип короткого целого short


signed int эквивалентен типу int с точки зрения объема памяти, требуемого
для размещения переменной, под такие переменные, как и под переменные
типа int, отводится одно слово.
2. Процессоры ‘C28x имеют 32-разрядное АЛУ, оптимизированное для
работы с 32-разрядными словами в формате с фиксированной точкой. Это
обеспечивает достаточную точность при решении задач управления реаль-
ного времени, поэтому вещественные типы двойной (double) и прецизион-
ной точности (long double) не отличаются по формату представления от
переменных типа float. Они существуют для обеспечения совместимости со
стандартом языка Си.
3. Операции с 8-разрядными словами (байтами) выполняются как част-
ный случай 16-разрядных операций. При необходимости можно использо-
вать ассемблерные вставки в программах на Си, написанные с применением
мощной системы команд процессоров ‘C28x по работе непосредственно с
байтами.
4. Так как процессоры TMS320x28xx имеют АЛУ, оптимизированное для
работы с 16-разрядными и 32-разрядными операндами, а также 16-разряд-
ную встроенную и внешнюю память, то единицей представления информа-
ции для них является не байт (8 разрядов), как для многих других процессо-
ров, а слово (16 разрядов). В стандарте языка Си (ANSI C) определена
функция sizeof, которая должна возвращать число байтов, необходимое для
размещения в памяти переменной данного типа. В нашем случае эта функ-
ция будет возвращать не число байтов, а число слов. Применяя ее для пере-
менных типа char или int, получите 1 — одно слово (см. табл. 3.1).
71
Форматы представления констант
В языке Си существует специальная система обозначений для двоичных,
восьмеричных и шестнадцатеричных констант: окончание b в константе ука-
зывает на двоичную константу, лидирующий 0 (нуль) указывает на восьме-
ричную константу, а последовательность из расположенных впереди симво-
лов 0x или 0Х — на шестнадцатеричную константу. Например, десятичное
число 31 можно записать как 00011111b в двоичной системе счисления, как
037 в восьмеричной системе счисления и как 0x1F — в шестнадцатеричной.

Операторы описания переменных


Описание переменной выполняется посредством указания ее типа с помо-
щью специально зарезервированного ключевого слова (см. табл. 3.1) и сле-
дующего за ним символического имени переменной. Тип переменной может
дополнительно иметь квалификатор типа. Оператор описания переменной,
как и любой другой оператор в языке Си, заканчивается точкой с запятой.
Например, для описания переменной i в качестве целого 16-разрядного
числа в дополнительном коде (int) необходимо ввести:
int i; /* описание переменной типа целое со знаком */
Как уже известно, если оператор описания переменной расположен до
главной функции main(), то переменная относится к так называемым гло-
бальным переменным и под нее резервируется нужное число ячеек в
памяти данных. Если оператор описания переменной находится в теле функ-
ции, то данная переменная является локальной и размещается в стеке.
Место в памяти под нее выделяется только в процессе вызова функции.
Оператор описания нескольких переменных состоит из спецификатора
типа и следующего за ним списка переменных, в котором через запятую
перечисляются все имена переменных, например:
int x, y, z;

Переменные могут быть описаны несколькими операторами. Так, вместо


предыдущей одной строки можно использовать три:
int x;
int y;
int z;
Такая запись занимает больше места, но она удобна для добавления инди-
видуального комментария к каждому описанию переменной и для последую-
щих модификаций программы.
Переменным могут быть присвоены начальные значения непосред-
ственно внутри описаний их типа. Такие переменные называют инициализи-
рованными. В этом случае за именем переменной следуют знак равенства и
константа инициализации, например:
72
int a=5;
int b=10;
Допускается групповая инициализация переменных в одном описании,
например:
int a=b=c=d=5;
Язык Си допускает также частичную инициализацию переменных в
одном и том же описании, однако это является плохим стилем программиро-
вания:
int a, b, c=d=5;
Если переменная является внешней (определенной в другом программном
модуле) или статической, то инициализация проводится только один раз —
до начала выполнения программы. Термин статическая переменная эквива-
лентен термину глобальная переменная. Такие переменные определяются
вне функций, под них резервируется часть статической памяти данных, поэ-
тому они существуют все время, пока выполняется программа. Инициализа-
ция таких переменных выполняется с применением библиотеки реального
времени выполнения и фактически скрыта от пользователя.
Все переменные, определенные внутри функций, по умолчанию счита-
ются локальными. Они располагаются в стеке. Инициализируемым явно
локальным переменным начальные значения присваиваются при каждом
обращении к функции, в которой они описаны. Все прочие переменные, не
инициализируемые явно, в начале выполнения программы имеют неопреде-
ленные значения (т.е. содержат «мусор»).
Внешние и статические переменные по умолчанию инициализируются
нулем, но тем не менее их явная инициализация является признаком хоро-
шего стиля программирования.
Если хотите, чтобы переменная, определенная внутри функции, относи-
лась к статической переменной, то в ее объявлении укажите специальный
префикс static, который является зарезервированным ключевым словом. В
этом случае переменная будет существовать столько же, сколько и про-
грамма. Как объявить строку символов с именем name длиной 30 элементов,
показано ниже:
static char name [30];

Основные арифметические операции


Основные арифметические операции с примерами использования пред-
ставлены в табл. 3.2.
Типовые операции сложения, вычитания, умножения и деления +, –, *, /
работают так же, как и в большинстве других языков программирования,
например в Паскале. В качестве операндов арифметических операций могут
выступать не только переменные и константы, но и любые выражения.
73
Табл и ц а 3.2
Основные арифметические операции

Операция Описание Пример


– Унарный минус x = –y;
+ Сложение — сумма значений i = j + 2;
– Вычитание — разность значений i = j – 7;
* Умножение — произведение значений a = b * c;
/ Деление — частное от деления a = b/c;
% Остаток от деления — деление по модулю a = b % c;
++ Инкрементирование — увеличение на 1 i = ++j;
–– Декрементирование — уменьшение на 1 i = – –j;

Если операция деления / применяется к выражениям целого или символь-


ного типа, то остаток от деления отбрасывается. Это действие называется
«усечением». Например, результатом деления 5/2 является число 2, а резуль-
татом операции получения остатка 5%2 — число 1. Операция % называется
также операцией деления по модулю. Если операция деления выполняется
над числами с плавающей точкой, то результат будет числом с плавающей
точкой. Если один из операндов будет целым числом, а второй — числом с
плавающей точкой, то перед операцией деления все операнды будут автома-
тически преобразованы к формату числа с плавающей точкой.
При вычислении выражений самый высокий уровень старшинства имеет
операция унарного минуса, затем идут операции умножения и деления и,
наконец, операции сложения и вычитания. Операции одного уровня стар-
шинства (умножения, деления или сложения, вычитания) выполняются после-
довательно слева направо. Порядок вычислений можно задать с помощью
круглых скобок. Явное указание порядка вычисления выражения с помощью
круглых скобок считается хорошим стилем программирования, так как это
улучшает «читаемость» выражения и позволяет не задумываться о приорите-
тах операций.
Арифметические операции группируются слева направо. Порядок выпол-
нения ассоциативных и коммутативных операций типа + (–) не фиксируется.
Компилятор может перегруппировывать даже заключенные в круглые скобки
выражения, связанные с такими операциями. Таким образом, а + (b + c)
может быть вычислено как (a + b) + c. Это редко приводит к какому-либо
расхождению, но если необходимо обеспечить строго определенный порядок
вычислений, то нужно использовать явные промежуточные переменные или
расставить скобки () в выражении в соответствующем порядке.
Действия, предпринимаемые при положительном и отрицательном пере-
полнении (выходе за максимально или минимально допустимое значение
переменной данного типа), зависят от архитектуры центрального процес-
сора, технологии обработки данных в АЛУ и установленного при инициали-
зации режима работы процессора. Так, для микроконтроллеров ‘C28xx
74
может быть установлен режим насыщения, когда превышение максималь-
ного значения 16-разрядного или 32-разрядного числа со знаком в дополни-
тельном коде будет вызывать автоматическое ограничение результата макси-
мально или минимально возможным значением переменной данного типа.
Полный контроль переполнений реализуется при программировании на
Ассемблере. В языке Си для микроконтроллеров TMS320x28xx переполне-
ние не учитывается, как уже выяснилось в гл. 2 при выполнении работы № 2.
Поэтому разработчик программы на Си должен исключить выход перемен-
ных за пределы допустимого формата. Это один из существенных недостат-
ков языка Си, который может быть устранен за счет использования ассемб-
лерных вставок или написания наиболее важных функций на Ассемблере.

Особенности операций инкремента и декремента


Операции инкрементирования ++ и декрементирования – – предназначены
для увеличения и уменьшения значений переменных на 1. Оператор ++ добав-
ляет 1 к значению операнда, а оператор — вычитает 1 из значения операнда.
Особенность операций ++ и – – состоит в том, что их можно использовать
и как префиксные операторы (помещая соответствующий знак операции
перед переменной, например ++n), и как постфиксные операторы (помещая
соответствующий знак операции после переменной n++). В обоих случаях n
увеличивается на 1. Но выражение ++n увеличивает n до того, как значение
n будет использовано, а n++ — после использования значения переменной.
В первом случае речь идет о «преинкрементировании» или «предекременти-
ровании», а во втором случае — о «постинкрементировании» или «постде-
крементировании».
Операции инкрементирования и декрементирования имеют самый высо-
кий уровень старшинства. Более высокий приоритет имеют только круглые
скобки. Поэтому выражение x*y++ означает (x)*(y++). Так как выполняется
операция постинкрементирования значения y, то результатом данного выра-
жения будет произведение значения переменной x на переменную y, а побоч-
ным результатом — инкрементирование y после вычисления выражения.
Операции инкрементирования и декрементирования текущего содержи-
мого указателей выполняются особым образом. Главное отличие состоит в
том, что значение указателя получает приращение не на единицу, а на число,
соответствующее размеру данных в памяти, на которые ссылается указатель
sizeof(*ptr). Так, при работе с обычными целыми числами, занимающими в
памяти одно слово, указатель инкрементируется/декрементируется на еди-
ницу, а при работе с длинными целыми числами (long) — на два. Это позво-
ляет с помощью операции инкрементирования/декрементирования указате-
лей автоматически перемещаться к очередным элементам массива.
Приведем несколько примеров:

Лист. 3.2. Пример постинкрементирования


int n = 5;
x = n++;

75
В примере лист. 3.2 переменной x сначала будет присвоено текущее зна-
чение переменной n (т.е. 5), а затем значение переменной n увеличится на
единицу (операция постинкрементирования). Таким образом, переменная x
будет равна пяти, а переменная n — шести.

Лист. 3.3. Пример преинкрементирования


int n = 5;
x = ++n;

В примере лист. 3.3 используется операция преинкрементирования значе-


ния переменной. Поэтому сначала значение n увеличивается на единицу, а
затем новое значение n присваивается переменной x. Таким образом, обе
переменные x и n будут равны шести.

Контрольные вопросы
1. Объясните термины «преинкрементирование», «постдекрементирование».
2. Подумайте и скажите, чему будут равны переменные x, y и z после выполнения
кода лист 3.4.

Лист. 3.4. Пример программы


int z;
int x = 5;
int y = 4;
void main(void){z = x++ - --y;}

Логические операции
В языке Си следует различать два вида логических операций:
логические операции, которые выполняются над булевыми переменными
с получением булевого результата;
побитовые логические операции, которые могут выполняться одновре-
менно над всеми битами слова с получением результата также в виде слова.
Список логических операций первого типа приведен в табл. 3.3.
В логических выражениях можно использовать любое число условий,
связанных между собой логическими операциями. Таких операций три:
Логическое И (&&), Логическое ИЛИ (||) и Логическое НЕ (!).
Табл и ц а 3.3
Логические операции

Операция Описание Пример


&& И с = a && b;
|| Или с = a || b;
! Не, отрицание с = a ! b;

76
Выражения, связанные операциями && и ||, вычисляются слева направо,
причем их рассмотрение прекращается сразу же, как только становится ясно,
будет ли результат истиной или ложью:

Лист. 3.5. Пример использования логических операций


x || y //ИЛИ. Логическое выражение y вычисляется
//только тогда, когда x=false
x && y //И.Логическое выражение y вычисляется
//только тогда, когда x=true

Старшинство операции && выше, чем ||, и обе они младше операций
отношения и равенства.
Унарная операция отрицания ! преобразует ненулевой или истинный опе-
ранд (true) в 0 (false), а нулевой или ложный операнд (false) в 1 (true).
Рассмотренные логические операции применяются, главным образом,
при вычислении логических выражений, которые в свою очередь применя-
ются в условных операторах и операторах организации циклов.

Поразрядные (побитовые) операции


В отличие от многих других языков программирования, в Си определен
полный набор поразрядных логических операций. Эти операции применяются
над 16-разрядными словами типа int (целое) или unsigned int (беззнаковое
целое).
Наиболее часто побитовые логические операции используются для
сброса, установки или инвертирования одного бита или группы битов в
слове. При этом логическая операция выполняется с непосредственным опе-
рандом — маской. Подобные операции применяются для работы с регист-
рами встроенных периферийных устройств микроконтроллеров, в том числе
для инициализации этих устройств и управления ими в реальном времени.
При выполнении арифметических операций над числами в разных форма-
тах широко используются операции сдвига влево или вправо на заданное
число зарядов. При этом число разрядов сдвига указывается через пробел
сразу после знака соответствующей операции << или >>. Такой сдвиг явля-
ется логическим — на освободившиеся места записываются логические
нули, а «вытесняемые» за пределы слова разряды — теряются.
Табл и ц а 3.4
Поразрядные операции
Операция Описание Пример
& И a & b;
| Или a | b;
^ Исключающее ИЛИ a ^ b;
~ Не a ~ b;
>> Сдвиг вправо a >> b;
<< Сдвиг влево a << b;

77
Следует быть внимательным и отличать побитовые логические операции
& и | от логических операций над булевыми переменными && и ||. Напри-
мер, пусть имеем две переменные типа int: a=1 (0001b) и b=2 (0010b).
Результат операции побитового «И» a&b даст нулевое значение, в то время
как результат вычисления логического выражения a&&b окажется равным
единице. Это объясняется тем, что с точки зрения языка Си любое число,
отличное от 0 в логическом выражении, рассматривается как истина (true).
Примеры использования поразрядных операций приведены в Лист. 3.6.

Лист. 3.6. Примеры использования поразрядных операций


int x,y,z;
...
x = y & 0xFF; //выделение младшего байта слова
z = y >> 8; //выделение старшего байта слова

Операции отношения и сравнения


Операциями отношения пользуются в логических выражениях, в част-
ности, для организации условных ветвлений в программах. Операции типа
больше >, больше или равно >=, меньше < и меньше или равно <= имеют
одинаковое старшинство. Непосредственно за ними по уровню старшинства
следуют операции равенства == и неравенства !=, которые тоже имеют оди-
наковое старшинство. Операции отношения младше арифметических опера-
ций, так что выражение a < b – 1 рассматривается как выражение a < (b – 1).
Читатели, которые уже познакомились с системой команд микроконтрол-
леров ‘C28xx, обратили внимание на то, что в самом процессоре операции
сравнения для чисел без знака и для чисел со знаком отличаются. Для чисел
со знаком существует операция «больше», а для чисел без знака «выше». В
языке Си такого различия нет. Поэтому предполагается, что сравниваться
могут только выражения одинакового типа, например int или unsigned int. В
том случае, если сравниваются выражения разного типа — компилятор
выдает предупреждение об ошибке.

Табл и ц а 3.5
Операции отношения и сравнения

Операция Описание Пример


> Больше чем a > b;
>= Больше или равно a >= b;
< Меньше чем a < b;
<= Меньше или равно a <= b;
== Равно a == b;
!= Не равно a != b;

78
Оператор присваивания
В языке Си символом оператора присваивания является знак =. В одном
операторе присваивания можно присвоить одно и то же значение многим
переменным. Для этого используется оператор множественного присваива-
ния, например:
x=z=y=0;
В программах на Си широко используются операторы составного присва-
ивания: вначале над переменной выполняется указанная перед символом =
арифметическая или логическая операция, после чего результат операции
сохраняется в переменной. Составное присваивание выполняется в режиме:
«Чтение старого значения переменной — Модификация — Запись нового
значения». При этом оператор присваивания становится более компактным.
Например, операция увеличения значения переменной x на пять может
выглядеть следующим образом:
x=x+5;
а при использовании оператора составного присваивания:
x+=5;
Варианты составных операций присваивания приведены в табл. 3.6
Обратите внимание, что операции поразрядной конъюнкции И, дизъюнк-
ции ИЛИ и Исключающего ИЛИ задаются с помощью символов &, | и ^
соответственно. В приведенных примерах для четырех младших битов пере-
менной y производятся: очистка, установка и побитовое инвертирование
соответствующих разрядов.
Табл и ц а 3.6
Операции присваивания

Операция Описание Пример


= Присваивание x = y;
*= Умножение с присваиванием x *=y; (x = x * y;)
/= Деление с присваиванием x /= y; (x = x / y;)
%= Остаток от деления с присваиванием x %= y; (x = x % y;)
+= Сложение с присваиванием x += y; (x = x + y;)
–= Вычитание с присваиванием x – = y; (x = x – y;)
<<= Сдвиг влево на заданное число разрядов с присваива- x <<= 2; (x = x << 2;)
нием
>>= Сдвиг вправо на заданное число разрядов с присваива- x >>= 5; (x = x >> 5;)
нием
&= Поразрядное И с присваиванием y &= 0xFFF0 (y = y & 0xFFF0)
|= Поразрядное ИЛИ с присваиванием y |= 0xFFF0 (y = y | 0xFFF0)
^= Поразрядное Исключающее ИЛИ с присваиванием y ^= 0xFFF0 (y = y ^ 0xFFF0)

79
3.2. ИСПОЛЬЗОВАНИЕ СТАНДАРТНЫХ БИБЛИОТЕК

Стандарт языка Си предполагает использование ряда стандартных биб-


лиотек. Они предназначены для: вычисления математических функций,
таких как синус, косинус и др.; организации ввода/вывода данных; эффек-
тивной обработки строк символов; динамического распределения памяти и
т.п. Для каждой из таких библиотек вместе с компилятором поставляются
стандартные заголовочные файлы, в которых определяются все необходи-
мые для использования библиотеки типы переменных, макросы и функции.
Для того чтобы использовать библиотечные функции, необходимо с помощью
директивы препроцессора #include включить в текст исходной программы
заголовочный файл соответствующей библиотеки. Например, для подключе-
ния библиотеки математических функций нужно выполнить директиву:
#include <math.h>
После этого станет возможным доступ к функциям этой библиотеки,
например к функции вычисления косинуса:
#include <math.h>
float x, y;

x = cos(y);

Существуют два варианта использования директивы #include:


#include <имя_файла>
#include “имя_файла”

При использовании угловых скобок в задании имени файла подключае-


мый файл ищется в одном из стандартных каталогов, причем текущий ката-
лог проекта с исходными файлами не просматривается. При использовании
имени файла, заключенного в кавычки, наоборот, поиск производится в теку-
щем каталоге проекта с файлами на исходном языке. Сами библиотечные
функции хранятся в скомпилированном виде в перемещаемом объектном
коде и подключаются к программе только на этапе компоновки.
Для отладки программного обеспечения, разрабатываемого для персо-
нальных компьютеров, широко используется стандартная библиотека
ввода/вывода stdio. Она обеспечивает все нужные пользователю функции
для работы с файлами и устройствами ввода/вывода (клавиатурой и дисп-
леем), что позволяет разрабатывать интерактивные программы с контролем
хода их выполнения на экране дисплея и c сохранением результатов в файлах.
Технология отладки программного обеспечения для встроенных систем
управления заметно отличается от технологии отладки программ на языке
Си для персональных компьютеров. Это отличие состоит в том, что возмож-
ности ввода/вывода данных для встроенных систем ограничены: нет под-
ключенной к плате процессора стандартной клавиатуры и дисплея. Тем
не менее интегрированная среда CCS через специальный отладочный
80
JTAG-интерфейс обеспечивает практически те же возможности ввода/
вывода данных, а порой и существенно большие. Мы уже познакомились с
окнами памяти и окнами наблюдаемых переменных, используемых в среде
CCS для отладки проектов.
Оказывается, интегрированная среда CCS позволяет дополнительно
использовать и стандартные функции ввода/вывода, освоенные большин-
ством программистов на языке Си. Для этого необходимо лишь подключить
заголовочный файл библиотеки:
#include <stdio.h>
Естественно, что в среде CCS функции, входящие в библиотеку, эмулиру-
ются. Например, вывод данных производится не на весь экран компьютера, а
в специальное, выделенное для этой цели окно.
В общем случае кроме заголовочного файла необходимо подключить к
проекту и саму библиотеку, из которой на стадии компоновки будут извле-
каться объектные модули в перемещаемом формате. Некоторые наиболее
важные библиотеки, такие как stdio, уже включены разработчиками компи-
лятора в библиотеку реального времени выполнения rts2800_ml.lib. Поэ-
тому достаточно подключить только соответствующий заголовочный файл и
библиотеку реального времени выполнения.

Практическая работа
1. Найдите в каталоге include заголовочный файл библиотеки стандарт-
ного ввода/вывода stdio.h и попытайтесь проанализировать его содержимое.
2. Есть ли в списке функций ввода/вывода функции для работы с фай-
лами: открыть — fopen, закрыть — fclose, переименовать — rename, уда-
лить — remove и т.д.?
3. Функции ввода/вывода символов и строк, например чтения символа со
стандартного устройства ввода — getchar?
4. Функции форматируемого ввода/вывода, например printf и scanf?

3.3. ФУНКЦИЯ ФОРМАТИРОВАННОГО ВЫВОДА PRINTF()

Покажем возможности использования библиотечных функций ввода/


вывода на примере функции printf() — форматируемого вывода на экран
значений переменных и символьных строк.
В простейшем случае функция используется для вывода на дисплей текс-
товых сообщений, сигнализирующих о выполнении определенной задачи,
возникновении ошибки в расчетах и т.п. При этом аргументом функции
является символьная строка, заключенная в двойные кавычки, например:
printf(“Вас приветствует интегрированная среда CCS!”);
Когда управление будет передано этой функции, то через канал связи
целевой платы с компьютером ему будет передана строка символов, являю-
81
щаяся аргументом функции. Далее уже интегрированная среда CCS выведет
в окно Stdout запрограммированное сообщение, результаты выполнения
которого представлены на рис. 3.1. Сообщение выводится в то же окно, что и
результаты трансляции и сборки проекта Build, но на другую закладку
Stdout.
Если хотите, чтобы вывод следующего сообщения начался с новой
строки, то введите в конце текущего сообщения символ перевода строки \n:
printf(“Вас приветствует интегрированная среда CCS!\n”);
Если этого не сделать, то курсор останется на месте, и следующее сооб-
щение будет продолжать предыдущее. Последовательность символов \n
является так называемой управляющей последовательностью, которая при-
меняется для ввода специальных символов, в данном случае символа пере-
вода строки Enter. Любая управляющая последовательность символов начи-
нается со знака \, например \t — символ табуляции.
Специальный символ процента %, включенный в текстовую строку, сиг-
нализирует функции printf() о том, что, начиная с текущей позиции, необхо-
димо напечатать значение переменной, имя которой указано через запятую
сразу после текстового сообщения. Следующий за символом % символ,
например, d является спецификатором преобразования и указывает на то, в
каком виде должно быть выведено значение переменной: в данном случае в
формате десятичного числа со знаком:
printf(“result = %d\n”, result); /* Вывод результата в
окно CCS.*/
Таким образом, функция printf() позволяет печатать любые текстовые
сообщения, в частности названия переменных, сопровождая их значениями
переменных в нужном формате. Комбинация символов %d является указа-
нием места в строке, начиная с которого следует выводить значение пере-
менной. Возможны различные варианты спецификации преобразования зна-
чения переменной:
%d — аргумент преобразуется в формат десятичного числа со знаком;
%o — аргумент преобразуется в формат беззнакового восьмеричного
числа (без лидирующего нуля);

Рис. 3.1. Результат выполнения функции printf()

82
%x — аргумент преобразуется в формат беззнакового шестнадцатерич-
ного числа (без лидирующих 0x);
%u — аргумент преобразуется в формат беззнакового десятичного числа;
%c — аргумент рассматривается как отдельный символ;
%s — аргумент является строкой: символы строки печатаются до тех пор,
пока не будет достигнут нулевой символ или не будет напечатано количество
символов, указанное в спецификации точности;
%e — аргумент, рассматриваемый как переменная типа float, преобразу-
ется в десятичную форму в виде экспоненциальной записи
[–]M;NNNNNNE[+–]XX, где число символов после десятичной точки N
определяется указанной точностью. Точность по умолчанию равна 6;
%f — аргумент, рассматриваемый как переменная типа float, преобразу-
ется в десятичную форму в виде [–]MMM.NNNNN, где число символов
после десятичной точки N определяется указанной точностью. Точность по
умолчанию равна 6.
Между символом % и символом преобразования могут находиться так
называемые модификаторы преобразования:
• знак «минус», который указывает на необходимость выравнивания зна-
чения преобразованного аргумента по левому краю его поля. Если этот знак
не указывается, значение автоматически выравнивается по правому краю
поля;
• строка цифр, задающая минимальную ширину поля для вывода значе-
ния. Если преобразованный аргумент имеет меньше символов, чем указан-
ная ширина поля, то он будет дополнен слева (или справа, если было указано
выравнивание по левому краю с помощью знака –) заполняющими симво-
лами до этой ширины. Заполняющим символом обычно является пробел, а
если ширина поля указывается с лидирующим нулем, то этим символом
будет нуль (лидирующий нуль в данном случае не означает восьмеричного
формата представления числа);
• точка, которая отделяет ширину поля от следующей строки цифр,
используемой для задания точности (для типов данных с плавающей точкой);
• точность (строка цифр), которая указывает на максимальное число сим-
волов, выводимых справа от десятичной точки для переменных типа float;
• модификатор длиной l, который указывает, что соответствующий эле-
мент данных имеет тип long, а не int.
Если идущий за символом % символ не является символом преобразова-
ния, то печатается сам этот символ. Следовательно, символ % можно напе-
чатать, указав % %.
Приведем простые примеры. Предположим, что на экран необходимо
вывести текущие значения трех целочисленных переменных i, j, k в виде
обычного десятичного числа и выполнить перевод строки:
printf(“i= %d; j= %d; k= %d;\n”, i, j, k);
Обратите внимание, что формат обращения к функции следующий:
printf(“Управляющая строка”, аргумент1, аргумент2, и
т.д.);

83
При этом управляющая строка представляет собой строку символов в
кавычках — фразу, которая показывает, как и в каком месте должны быть
напечатаны перечисленные вслед за управляющей строкой аргументы.
В качестве аргументов могут использоваться константы, переменные или
выражения. В последнем случае значения выражений вычисляются непо-
средственно перед выводом на экран. Функция printf() использует управля-
ющую строку для определения числа последующих аргументов. Их число
должно строго соответствовать числу вхождений символов % в управляю-
щую строку. Если количество аргументов окажется недостаточным или они
будут иметь несоответствующие типы, то будет выдано сообщение об
ошибке.
Еще один пример. Предположим, что переменная x является переменной
с плавающей точкой и на экран необходимо вывести ее значение в десяти-
чной форме записи в поле размером 10 символов, включая десятичную
точку, и точностью числом десятичных цифр справа от десятичной точки,
равной 4:

printf(“х= %10.4f \n”,x);

В заключение напомним, что использовать функцию форматированного


вывода можно только после подключения заголовочного файла стандартной
библиотеки ввода/вывода (лист. 3.7).

Лист. 3.7. Демонстрация использования библиотечной функции


/* Подключение заголовочного файла */
/* стандартной библиотеки ввода-вывода. */
/* С помощью директивы препроцессора include. */
#include <stdio.h>
/* Основная программа. */
main() /* Главная функция программы. */
{ /* Вывод строки в окно CCS. */
printf(“Вас приветствует интегрированная среда CCS!\n”);
}

3.4. НЕКОТОРЫЕ РЕКОМЕНДАЦИИ ПО ОТЛАДКЕ ПРОГРАММ

Предположим, что Вы написали простую программу суммирования двух


целых чисел (например, лист. 3.8) и хотите исследовать правильность выпол-
нения в языке Си операции суммирования на любых наборах аргументов.

Лист. 3.8. Программа суммирования двух целых чисел


/* Описание (объявление) переменных */
int a; /* Описание первого слагаемого */
int b; /* Описание второго слагаемого */
int result; /* Описание результата (суммы) */

84
Окончание лист. 3.8
/* Основная программа */
main() /* Главная функция программы */
{ /* Текст программы — подсчет суммы двух чисел */
a = 10;
b = 5;
result = a + b; /* Подсчет суммы двух чисел */
}
Отладку интересующего нас фрагмента программы удобно выполнять в
бесконечном цикле:
while(1) /* Бесконечный цикл. */
{
result = a + b; /* Расчет суммы двух чисел. */
}
При этом, открыв окно содержимого памяти или окно наблюдаемых пере-
менных, можно менять исходные значения аргументов и в реальном времени
наблюдать за результатом расчета. Результат можно вывести или в окно
наблюдаемых переменных, или в окно Stdout с использованием функции
printf().
Для организации бесконечного цикла применяют оператор while с усло-
вием, равным истине (1).

3.5. ПОДКЛЮЧЕНИЕ СТАНДАРТНОЙ БИБЛИОТЕКИ


ВВОДА/ВЫВОДА. МОДЕРНИЗАЦИЯ ФАЙЛА
УПРАВЛЕНИЯ КОМПОНОВКОЙ

Практическая работа
1. Выполняя предыдущую практическую работу (см. с. 81), Вы обратили
внимание на существование каталога, специально предназначенного для раз-
мещения проектов пользователей c:\ti\myprojects. В этом каталоге будут
размещаться и Ваши проекты. Создайте подкаталог с именем \username по
нескольким первым буквам вашей фамилии, например \petrov. Для каждой
новой работы организуйте отдельный подкаталог. Например, для текущей
работы создайте подкаталог \lab3. Напоминаем, что формирование каталогов
можно выполнить и автоматически в процессе создания нового проекта (п. 3).
2. Запустите Code Composer Studio. В открывшемся окне менеджера
параллельной отладки выберите либо режим симулятора F2812 Device
Simulator/CPU (программно-логической модели), либо режим эмулятора
F2812 PP Emulator/CPU_1, если целевая отладочная плата eZdsp28 подклю-
чена к параллельному порту компьютера и питание подано (рис. 3.2).
3. Создайте новый проект lab3.pjt с использованием команд меню
Project → New в каталоге ваших проектов.
85
Рис. 3.2. Окно менеджера параллельной отладки

4. Создайте исходную программу на языке Си по образцу, приведенному


на лист. 3.3. Подключите заголовочный файл стандартной библиотеки
ввода/вывода и добавьте функцию вывода в окно Stdout текущего значения
переменной result. Воспользуйтесь встроенным редактором интегрирован-
ной среды CCS, выбрав команду меню File → New → Source File (Новый
исходный файл) или нажав горячую клавишу Ctrl+N. Ваша программа
должна выглядеть, как программа, приведенная на рис. 3.3. Сохраните про-
грамму в каталоге \lab3.
5. Добавьте исходный Си-файл к новому проекту с помощью меню
Project → Add Files to Project… (Добавить файл к проекту). Убедитесь, что
слева от списка виртуальных каталогов проекта напротив папки Source поя-
вился знак +. Щелкните по нему мышкой. Убедитесь, что подключили нуж-
ный файл. Если ошиблись и подключили не тот файл, установите курсор на
имени этого файла и вызовите правой кнопкой мыши контекстно-зависимое
меню. Командой Remove from Project удалите ненужный файл из проекта.
Повторите операцию подключения.

Рис. 3.3. Программа суммирования двух чисел

86
6. Выполните процедуру подключения
к проекту командного файла управления
компоновкой Linker Command File
(*.cmd), созданного в предыдущей прак-
тической работе lnk.cmd (из соответству-
ющего каталога без операции копирова-
ния), а также файла библиотеки реального
времени выполнения rts2800_ml.lib.
7. Откройте список содержимого вашего
проекта, щелкая на значки + рядом с
Project, lab3.pjt, Libraries и Source. Убе-
дитесь, что проект содержит все необхо-
димые файлы (рис. 3.4).

Замечание
Если на экране CCS отсутствует окно
менеджера проекта, выберите команду
меню View → Project. Если в данный
Рис. 3.4. Окно «дерева» проекта
момент в окне менеджера проекта
выбрана иконка Bookmarks (Закладки),
щелкните на иконке File внизу плана проекта.

8. Оттранслируйте созданную программу lab3.c. Если написали про-


грамму без ошибок, то никаких сообщений об ошибках или предупреждений
получить не должны. В противном случае отредактируйте программу и повто-
рите трансляцию.
9. Выполните трансляцию и компоновку (сборку) всего проекта Project
→ Rebuild All. Действовать можно либо через меню, либо нажав кнопку
Rebuild All на панели инструментов. Как видите, в окне сборки появилось
сразу несколько сообщений об ошибках (рис. 3.5).
Проанализируем сообщения об ошибках.
Первое сообщение свидетельствует о том, что компоновщик не смог раз-
местить в банке памяти M1SARAM объемом 1024 слова (0400h) секцию
переменных .ebss, реальный размер которой составляет 2404 слова (0964h).

Рис. 3.5. Результаты сборки проекта в окне build

87
Для нормальной работы функции форматированного ввода/вывода потребо-
вался дополнительный объем памяти более 2К слов.
Во втором сообщении указывается, что для размещения динамической
памяти данных (кучи) также не хватило места: секция .system размером
0400h не поместилась в банке статического ОЗУ M0SARAM, выделенном
под стек.
Третье сообщение является следствием первых двух и свидетельствует о
том, что выходной исполняемых файл lab3.out не был создан.
Дело в том, что подключили стандартную библиотечную функцию и не
учли, что такое подключение потребует определенных ресурсов памяти.
Придется изменить командный файл компоновщика. Лучше создать
новый файл с учетом возникших дополнительных потребностей в памяти.
Для ускорения работы можно воспользоваться примером файла, рекомендуе-
мого фирмой TI для целей отладки программ в среде CCS (рис. 3.6). Пере-
именуем этот файл в lab3.cmd и снабдим комментариями на русском языке.
Как видите, мы существенно расширили объем памяти данных за счет
включения двух последовательно расположенных банков L0 и L1 встроен-
ного ОЗУ объемом по 4К слова каждый. Общий объем дополнительной
памяти в объединенном блоке L0L1RAM составляет 8К слов.
Все секции данных для размещения глобальных и статических перемен-
ных .ebss, констант .econst, а также динамически распределяемой памяти
.esystem будем размещать в этом новом блоке памяти L0L1RAM.

Контрольные вопросы
1. Объясните назначение всех секций, описанных в командном файле.
2. Почему секция .cinit должна размещаться в памяти программ?
3. В какую секцию должны попасть переменные a, b, result нашей программы?

Практическая работа
1. Отключите от проекта файл lab2.cmd и подключите к проекту файл
lab3.cmd. Выполните перекомпиляцию и сборку проекта Rebuild All. Убеди-
тесь, что в окне Build теперь отсутствуют сообщения об ошибках. Это окно
после автоматической загрузки программы в память целевой платы имеет
две закладки: Build и Sdtout. Вторая закладка имитирует терминал пользо-
вателя, на который будут выводиться все сообщения из вашей программы по
команде форматированного вывода printf().
2. Убедитесь в том, что в результате трансляции создан выходной испол-
няемый файл lab3.out, а также файл с картой загрузки lab3.map. Место рас-
положения этих файлов по умолчанию — директория \Debug.
3. Откройте файл lab3.map непосредственно из среды CCS (File → Open)
и выпишите адреса размещения переменных a, b, result в памяти данных
(они нам понадобятся в процессе отладки).
4. На основе информации в файле lab3.map проанализируйте распределе-
ние секций нашей программы по банкам памяти микроконтроллера. Соответ-
ствует ли оно командному файлу? Сколько дополнительного места в кодовой
памяти и памяти данных потребовалось для «обслуживающих» программ?
88
а)

б)

Рис. 3.6. Структура командного файла компоновщика:


а, б — распределение памяти и секций

3.6. ВЫПОЛНЕНИЕ ПРОГРАММЫ В РЕЖИМЕ ПРОГОНА

Как уже известно, если установлена опция автоматической загрузки про-


граммы сразу после сборки проекта Load Program After Build, то про-
грамма будет загружена в память целевой платы и автоматически откроется
окно дизассемблера. При этом счетчик команд установится на начало секции
инициализации _с_init_00.
Если автоматическая загрузка не предусмотрена, то можно выполнить
«ручную» загрузку по команде File → Load Program. Загружать следует
89
файл с исполняемым кодом lab3.out, который находится в каталоге:
c:\ti\myprojects\username\lab3\Debug\.
Для выполнения программы инициализации процессора, установки зна-
чений глобальных констант и инициализированных переменных применя-
ется команда Debug → Go Main — Выполнить до главной программы
main().
Теперь можно выполнить программу или в пошаговом режиме, как уже
делали, или в режиме прогона. Запуск программы в режим прогона произво-
дится командой меню Debug → Run (Отладка → Выполнить) или щелчком
мыши на кнопке Run, расположенной на панели управления. На кнопке
изображена пиктограмма «бегущего человечка».
Для остановки процесса выполнения программы пользователя использу-
ется команда меню Debug → Halt (Отладка → Останов) или соответствую-
щая клавиша на панели управления.

Практическая работа
1. Загрузите и выполните программу в пошаговом режиме. Откройте окно
наблюдаемых переменных и следите за ходом процесса.
2. Выполните программу в режиме прогона. Обновляйте значения пере-
менных a и b, контролируйте результат result в окне наблюдаемых перемен-
ных и в окне эмулятора терминала Stdout.
3. Модернизируйте программу для одновременного вывода на терминал
не только результата операции, но и значений операндов. Поэксперименти-
руйте с различными форматами вывода.

3.7. ВЫПОЛНЕНИЕ ПРОГРАММЫ С ТОЧКАМИ ОСТАНОВА

Интегрированная среда Code Composer Studio имеет широкий арсенал


средств отладки, среди которых особое место занимают точки останова.
Точка останова — это специально помеченный оператор программы, дойдя
до которого выполнение программы в режиме прогона автоматически пре-
кращается. Находясь в режиме останова процессора, исследуйте состояние
любых переменных, а затем, убедившись, что нужный фрагмент программы
выполнен правильно, продолжите ее выполнение до следующей точки оста-
нова и т.д.
Задать точку останова можно двумя способами. Первый — через команду
меню Debug → Breakpoints… (Отладка → Точки останова…). В появив-
шемся окне Break/Probe Points (Точки останова/Пробные точки) можно
выбрать тип точки останова, например, Break at Location (Останов по
месту) или Break at Location if expression is TRUE (Останов по месту,
если заданное выражение ИСТИНА) и ввести ее параметры (адрес разме-
щения или адрес и условие останова).
Второй способ установки точки останова заключается в двойном щелчке
левой кнопкой мыши в соответствующей строке исходной программы на
самой левой серой полосе окна, которая используется для индикации точек
90
Рис. 3.7. Вид программы с двумя точками останова

останова и текущего положения счетчика команд. Напротив соответствую-


щего оператора появится красная точка, которая и будет означать точку оста-
нова — рис. 3.7. Повторный двойной щелчок мыши используется для удале-
ния текущей точки останова.
Если установить точки останова напротив команд result = a + b и printf()
в окне исходной программы и запустить программу в режиме прогона Run,
то она будет останавливаться после каждого из двух операторов, включен-
ных в бесконечный цикл. После останова можно будет модифицировать зна-
чения исходных операндов a и b в окне Watch и продолжить выполнение
программы до момента получения результата в окне Stdout и т.д.

Практическая работа
1. Откройте окно наблюдаемых переменных Watch, как Вы это делали в
гл. 2. Добавьте в него переменные a, b и result.
2. Попробуйте установить точку останова, пользуясь меню CCS.
3. Исследуйте второй способ установки и снятия точек останова с помо-
щью кнопки мыши.
4. Проверьте правильность выполнения программы на различных набо-
рах исходных операндов.
5. Модернизируйте программу для исследования других математических
операций, описанных в табл. 3.2. Задайте аргументы функции printf() таким
образом, чтобы на экране одновременно отобразились значения входных
переменных и значения результатов нескольких математических операций,
например, чтобы вывод на экран выглядел следующим образом:
a = 35; b = 4; a + b = 39; a – b = 31; aæb = 140.
91
3.8. ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ ПО ИЗУЧЕНИЮ
СИСТЕМЫ КОМАНД МИКРОКОНТРОЛЛЕРА ‘C28x

Уже упоминали о том, что Ассемблер микроконтроллера ‘C28x обладает


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

Практическая работа

1. Упростите исходную программу, исключив из нее функцию форматиро-


ванного вывода, сохраните программу под другим именем, например
lab3_0.c (рис. 3.8).
2. Отсоедините от проекта файл lab3.c и подключите файл lab3_0.c.
Выполните перекомпиляцию и сборку проекта. Выполните процедуру ини-
циализации (Go Main).
3. Установите режим отображения в окне исходной программы смешан-
ного кода по команде View → Mixed Source/ASM (рис. 3.9).

Рис. 3.8. Программа lab3_0.c

92
Рис. 3.9. Программа lab3_0.c в CCS в режиме MIX MODE

Переключение режима Source Mode/


Mixed Mode можно выполнить и из кон-
текстно-зависимого меню, находясь в
окне исходной программы.
Текущее положение счетчика команд
для программы на языке Си отображается
стрелкой желтого цвета, а для программы
на языке Ассемблер — зеленого цвета.
Стрелки такого же цвета изображены и на
клавишах пошагового выполнения Source-
Single Step (Один шаг в программе на
Си) и Assembly-Single Step (Один шаг в
программе на Ассемблере).
Если щелкнуть клавишей мыши по
любой ассемблерной инструкции в окне
смешанного представления исходного
кода (именно по коду инструкции, а не по
адресу размещения команды или по полю
операнда[ов]), а затем нажать клавишу
помощи F1, то можно получить так назы-
ваемую контекстно-зависимую под-
сказку. На экран компьютера будет выдан
список всех команд процессора, имею-
щих аналогичную мнемонику, но с раз-
ными возможными вариантами задания
операнда[ов]. Например, для команды Рис. 3.10. Список всех команд, имеющих
ADD получим (рис. 3.10): мнемонику ADD

93
Теперь можно выбрать конкретную команду, руководствуясь содержимым
поля операндов. Команда ADD AL, @6 относится к классу команд, работаю-
щих с 16-разрядным аккумулятором AX. Это обозначение является общим
для обозначения как младшего слова аккумулятора AL, так и старшего слова
AH. Символическое обозначение loc16 применяется для обозначения любого
операнда в памяти данных, адресуемого с применением разрешенного в про-
цессоре способа адресации. В нашем случае это будет прямая страничная
адресация. Итак, выбираем первую команду ADD AX, loc16 и получаем
справку по ее применению (см. рис. 3.10).
Вся справочная информация по командам процессора построена одно-
типно:
• даются возможные варианты синтаксиса команды; приводится оптокод
команды, причем символ A является битовым адресом старшего или млад-
шего слова аккумулятора, а символы LLLL LLLL задают фактически исполь-
зуемый в команде способ адресации операнда; указывается, в каком объект-
ном режиме работы процессора команда может быть использована (символ
X предназначен для любого режима работы); отображается возможность
включения команды в цикл повторения RPT; приводится длительность
выполнения команды в числе циклов CYC;
• описываются операнды команды;
• приводится краткое описание действий, выполняемых командой;
• дается информация о модернизации командой флагов процессора;
• указывается возможность включения команды в цикл повторения;
• приводится простой пример использования команды с комментариями.
Ясно, что наша команда выполняет сложение содержимого 16-разрядного
аккумулятора AL с содержимым прямоадресуемой ячейки памяти данных по
адресу, смещенному относительно начала текущей страницы данных на 6
(короткий адрес операнда на странице), и записывает результат сложения
обратно в аккумулятор AL. По результатам операции сложения вырабатыва-
ются признаки отрицательного N и нулевого результата Z, переноса С и
переполнения V. Флаг С используется для анализа переполнений при работе
с числами без знака, а флаг V — для анализа переполнений при работе с чис-
лами со знаком в дополнительном коде.
Отладочная среда CCS позволяет быстро вывести на экран текущее зна-
чение регистров процессора без необходимости открывания специального
окна содержимого регистров Registers. Для этого, находясь в окне с исход-
ным кодом программы, достаточно лишь установить курсор на символичес-
ком имени регистра процессора, например AL. Его текущее содержимое
будет немедленно выведено на экран в прямоугольной рамке. К сожалению,
эта великолепная возможность не предусмотрена для переменных в памяти.
Более того, установив курсор на метку L1 в команде безусловной пере-
дачи управления SB L1, UNC, получите информацию о фактическом значе-
нии метки L1: L1=0x003F8006. Это означает, что команда короткого безу-
словного относительного перехода вернет управление по адресу
0x003F8006.
94
Таким образом, интегрированная среда CCS предоставляет разработчику
программного обеспечения великолепные возможности по изучению сис-
темы команд процессора и отладке программ как на языке высокого уровня
Си, так и на Ассемблере.

Практическая работа
1. Проанализируйте ассемблерные команды, встречающиеся в программе.
Имейте в виду, что все наши переменные являются глобальными и разме-
щены компоновщиком по следующим адресам: _a: 8086h; _b: 8084h;
_result: 8085h. Для доступа к ним применяется прямая страничная адреса-
ция. Каждая страница имеет длину 64 слова (40h). Таким образом, перемен-
ные размещаются на странице памяти с номером 8084h div 40h = 0202h.
Именно это значение используется для инициализации указателя текущей
страницы данных DP. Короткий прямой адрес переменной _а на текущей
странице памяти равен 6, переменной _b: 4, а переменной _result: 5.
2. Правильно ли мы дали комментарий к ассемблерным командам — лист. 3.7.
3. Откройте окна содержимого регистров ядра процессора View →
→ Registers → Core и статусных регистров View → Registers → Status,
окно наблюдаемых переменных. Выполните программу по шагам, наблюдая
за содержимым аккумулятора, указателя текущей страницы DP, флагов
результатов операций: Z, N, C, V, а также собственно переменных a, b, result.
Объясните результаты для различных наборов переменных, например, ука-
занных в табл. 3.7.

Лист. 3.9. Комментарий к программе на Ассемблере


3F8000 main:
3F8000 761F MOVW DP,#0x0202
;Инициализация указателя текущей
;страницы памяти данных DP.
3F8002 2806 MOV @6,#0x000A
;Инициализация операнда а числом 10.
3F8004 2804 MOV @4,#0x0005
;Инициализация операнда b числом 5.
3F8006 L1:
3F8006 9204 MOV AL,@4
;Загрузить в аккумулятор операнд b.
3F8007 9406 ADD AL,@6 ; Сложить с операндом а.
3F8008 9605 MOV @5,AL
;Сохранить результат по адресу result.
3F8009 6FFD SB L1,UNC; Повторить вычисление.
Табл и ц а 3.7
Наборы переменных для тестирования программы
a +2 +32767 –3 –32768 +32767
b +15 +2 –78 –2 –50

95
4. Убедитесь в том, что среда CCS позволяет быстро получить информа-
цию о текущем состоянии регистров процессора и значениях меток в коман-
дах передачи управления.
5. Исследуйте программу, которую Вы написали для изучения других
арифметических операций в табл. 3.2 с точки зрения реализации ее на
Ассемблере. Выполните отладку.
6. Поэкспериментируйте с различными вариантами записи операторов
присваивания, модернизировав исходную программу (см. табл. 3.3).

Контрольные вопросы
1. В чем преимущества и недостатки рассмотренных в этой работе способов отладки
программного обеспечения с использованием стандартной библиотеки ввода/вывода?
2. Сравните методы отладки программы в пошаговом режиме и с точками останова.
3. Какие действия будут выполнены следующим оператором на языке Си: x += y; ?
4. Как получить информацию о текущем содержимом регистров процессора XAR1,
SP? Укажите минимум две возможности.

96
Глава 4

БАЗОВЫЕ ВОЗМОЖНОСТИ ЯЗЫКА


ПРОГРАММИРОВАНИЯ СИ

4.1. ОПЕРАТОРЫ ВЕТВЛЕНИЯ

Условный оператор if-else


Конструкция if-else используется для принятия решения по ветвлению
программы (лист. 4.1).

Лист. 4.1. Конструкция if-else


if (выражение)
оператор 1;
else
оператор 2;

Сначала вычисляется выражение, стоящее в скобках после слова if: если


оно истинно, то выполняется оператор 1, в противном случае — оператор 2.
Вторая часть условного оператора, начинающаяся с зарезервированного
слова else, может опускаться. Это так называемая сокращенная запись услов-
ного оператора — оператор 1 выполняется только при истинности выраже-
ния. В противном случае управление просто передается следующему опера-
тору программы.
Допускается вложение условных операторов друг в друга. При этом опе-
ратор else относится к ближайшему оператору if (лист. 4.2).

Лист. 4.2. Вложение условных операторов


if (n>0)
if (a>b)
z=a;
else
z=b;

По умолчанию, часть условного оператора else относится к внутреннему


оператору if. Внешний оператор if имеет сокращенную форму записи. Если
требуется иная интерпретация, необходимо должным образом расставить
фигурные скобки, как показано в лист. 4.3.
97
Лист. 4.3. Пример оператора ветвления
if (n>0)
{
if (a>b)
z=a;
}
else
z=b;
Здесь внешний условный оператор имеет полную форму, а внутренний
оператор — сокращенную.
Реализация механизма многоступенчатого принятия решения приведена в
лист. 4.4.

Лист. 4.4. Структура if-else


if (выражение)
оператор 1
else if (выражение)
оператор 2
else if (выражение)
оператор 3
else if (выражение)
оператор 4
else
оператор 5
Выражения вычисляются по порядку. Как только одно из выражений ока-
жется истинным, выполняется соответствующий ему оператор. На этом после-
довательность проверок завершится.

Оператор переключения switch


Оператор switch (см. лист. 4.5) используется для реализации множест-
венного выбора. Вычисляется значение выражения, указанного в скобках
вслед за ключевым словом switch. Полученное значение последовательно
сравнивается с константами во всех вариантах case. Управление передается
тому набору операторов, который помечен соответствующей константой.

Лист. 4.5. Оператор переключения


switch (выражение)
{
case константное выражение 1: операторы
case константное выражение 2: операторы
case константное выражение 3: операторы
default: операторы
}

98
Каждый вариант case может помечаться целой или символьной констан-
той или константным выражением. Не допускается использование одина-
ковых константных выражений. Константные выражения не должны содер-
жать ни переменных, ни вызовов функций, так как вычисляются на этапе
компиляции программы.
Если значение выражения не соответствует ни одной константе в вариан-
тах case, то выполняются операторы, следующие за словом default, т.е. опе-
раторы по умолчанию. Это необязательная часть оператора-переключателя
switch, которая может отсутствовать.
Если значение выражения не соответствует ни одной константе, то ника-
ких действий не выполняется и управление передается следующему опера-
тору программы.
Оператор переключения имеет одну очень важную особенность: ключе-
вое слово case вместе с константой выполняет всего лишь функцию метки,
на которую передается управление для некоторого варианта значения выра-
жения. Поэтому будут выполняться не только операторы для текущего вари-
анта case, но и для всех последующих вариантов. Для выхода из переключа-
теля после выполнения необходимого оператора case служит специальный
оператор прерывания break, который необходимо поставить в конце выполне-
ния последовательности операторов case, как показано в примере лист. 4.6.

Лист. 4.6. Оператор переключения с прерыванием


switch (c)
{ case 0: case 1: case 2:
операторы
break;
case 3: case 4: case 5:
операторы
break;
case 6: case 7: case 8:
операторы
break;
default:
операторы по умолчанию
break;
}

С одной стороны, это позволяет реализовывать множественный выбор. С


другой стороны, если необходимо прервать выполнение оператора case при
срабатывании какого-либо из условий, то вслед за нужной последователь-
ностью операторов необходимо использовать специальный оператор преры-
вания break, как показано в примере лист. 4.6. Инструкция break вызывает
немедленный выход из конструкции с оператором switch.
99
Контрольные вопросы
1. В программе объявлена переменная y типа int. Может ли по значению этой пере-
менной работать оператор переключения: switch (y)?
2. В операторе switch (ch) в качестве метки использовано символическое имя пере-
менной y (case y:). Допускается ли такая запись? Почему?
3. С помощью оператора переключения реализуйте алгоритм вывода на экран назва-
ния месяца по его номеру. В случае, если передается номер несуществующего месяца
(например, 13), должно выводиться сообщение: нет такого месяца. Для вывода на экран
используйте функцию стандартной библиотеки ввода/вывода printf(), изученную в пре-
дыдущей работе.

4.2. ОПЕРАТОРЫ ОРГАНИЗАЦИИ ЦИКЛОВ


Цикл while
С операторами организации циклов уже познакомились в предыдущей
работе и даже отладили фрагмент программы, включенный в бесконечный цикл.
С помощью циклов организуют повторяющиеся вычисления. Одним из
наиболее часто встречающихся является оператор цикла while — повторять
инструкции, расположенные за оператором while до тех пор, пока значение
выражения, приведенного в скобках, остается истинным (лист. 4.7).

Лист. 4.7. Оператор цикла while


while (выражение)
{
операторы
}
Как только при очередном вычислении выражение станет ложным, цикл
завершится и управление будет передано следующему оператору за операто-
ром while. Такой цикл называется циклом с предварительной проверкой
условия выхода из цикла. В примере лист. 4.8 выход из цикла будет осущест-
влен, когда a станет больше b.

Лист. 4.8. Пример с оператором цикла while


a = 0;
b = 10;
while (a <= b)
{
a++; /* инкрементировать а */
printf("счетчик: a = %d\n", a);
}
Значение выражения в цикле while вычисляется предварительно, т.е. до
выполнения операторов тела цикла. Следовательно, если это значение ока-
жется ложным с самого начала, то операторы в теле цикла вообще не будут
выполнены.
100
Цикл do while
Оператор цикла do while аналогичен оператору цикла while, за исключе-
нием того, что условие выхода из цикла проверяется не вначале, а в конце
группы операторов, входящих в тело цикла. Это цикл с постпроверкой усло-
вия выхода из цикла (лист. 4.9).

Лист. 4.9. Оператор цикла do while


do
операторы
while (выражение);
В отличие от цикла while, в цикле do while операторы тела цикла выпол-
няются по крайней мере один раз. Далее следует вычисление выражения.
Если оно истинно, то операторы выполняются вновь. В противном случае
управление передается следующему оператору. В примере программы
лист. 4.10 на экран будет выведено значение счетчика, равное 21, так как
условие выхода из цикла проверяется после выполнения тела цикла.

Лист. 4.10. Пример с оператором цикла do while


a = 10;
b = 20;
do
{
a++; /* инкрементировать а */
printf("счетчик: a = %d\n", a);
}
while (a <= b);

Цикл for
При организации любого цикла, тело которого должно быть выполнено
заданное число раз, обязательно необходимы следующие три действия:
1) инициализация счетчика числа циклов;
2) сравнение текущего значения счетчика числа циклов с некоторым гра-
ничным значением для определения момента завершения цикла;
3) инкрементирование (или декрементирование) счетчика числа циклов
при каждом выполнении тела цикла.
В циклах while заботу о сравнении берет на себя условное выражение.
Приращение текущего состояния счетчика числа циклов реализуется либо в
теле цикла отдельной командой (см. лист. 4.10), либо с помощью «попутной»
операции инкрементирования/декрементирования непосредственно при
вычислении условного выражения, приведенного ниже:

Лист. 4.11. Пример с оператором цикла while


while (count++ <= n)

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

Лист. 4.12. Пример с оператором цикла for


for (count=0; count <= n; count++)
оператор;

В круглых скобках оператора for должны находиться три выражения, раз-


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

Лист. 4.13. Оператор цикла for


for (выражение 1; выражение 2; выражение 3)
{
операторы;
}

Любое из трех выражений в цикле for может отсутствовать, но точку с


запятой опускать нельзя. При отсутствии выражения 1 и выражения 3 счи-
тается, что их просто нет в конструкции цикла. При отсутствии выражения
2 предполагается, что его значение всегда истинно. Например, запись, приве-
денная в лист. 4.14, представляет собой «бесконечный» цикл.

Лист. 4.14. Бесконечный цикл с оператором for


for (;;)
{
операторы;
}

102
Контрольные вопросы
1. Начальное значение переменных целого типа x = 2, y = 1, k = 0, n = 9. Чему будет
равно значение переменной y после выполнения следующего цикла:

while (k<n)
{ y = y*x;
k++;
}

2. Какая информация будет выведена на экран в окно Stdout при выполнении следую-
щего цикла:

count = 1;
do
printf(“Значение счетчика count = %d\n”, count);
while (++count<=10)

3. Замените циклы циклами с оператором for. Результат должен быть таким же.

4.3. РАБОТА С УКАЗАТЕЛЯМИ

Указатель — это специальный регистр центрального процессора или


ячейка памяти, который (ая) содержит адрес некоторого объекта (перемен-
ной). С помощью указателя обеспечивается «косвенный» доступ к объекту.
Большинство операций на Ассемблере поддерживает косвенную адресацию
операндов как наиболее эффективную. С этой целью в состав центрального
процессора микроконтроллеров ‘C28x введено так называемое вспомога-
тельное АЛУ, которое обеспечивает широкий спектр операций с содержи-
мым регистров-указателей XAR1-7 (пред-, постинкрементирование и декре-
ментирование и др.). Регистры-указатели XARi являются 32-разрядными и
обеспечивают доступ ко всей расширенной памяти данных микроконтрол-
лера.
Предположим, что х — переменная, например, типа int, а рх — указатель
на эту переменную. Для того чтобы получить адрес фактического располо-
жения переменной в памяти, используется специальная операция &: она
выдает адрес объекта по его символическому имени (лист. 4.15).

Лист. 4.15. Операция возвращение адреса


рх = &х;

Таким образом, приведенная выше запись означает, что указатель на пере-


менную x, имеющий символическое имя px, проинициализирован адресом
фактического расположения переменной x в памяти, т.е. «указывает» на
переменную x.
103
Операция получения адреса & применима только к переменным и эле-
ментам массива; конструкции вида «адрес выражения», например &(х – 1), и
«адрес константы», например &3, являются бессмысленными. Нельзя также
получить адрес регистровой переменной.
Унарная операция * представляет собой операцию доступа по указателю.
Если px — это указатель, то запись *px означает «содержимое области
памяти, адресуемой указателем px». Таким образом, оператор присваивания
обеспечивает загрузку переменной y значением из области памяти, адрес
которой находится в переменной px (лист. 4.16). Обращаем ваше внимание
на то, что все операнды с косвенной адресацией на языке Ассемблер также
начинаются с предшествующего символа косвенной адресации *.

Лист. 4.16. Операция обращения к переменной по указателю


y = *рх;

Проследите, что происходит в результате выполнения следующих двух


операторов присваивания, показанных в лист. 4.17. Вначале инициализиру-
ется указатель px адресом переменной x, а затем выполняется обращение к
данным по этому указателю, т.е. фактически извлекается значение перемен-
ной x.

Лист. 4.17. Пример работы с указателем


рх = &х;
y = *рх; // Эквивалентно y = x;

Указатели должны быть описаны до момента их использования, анало-


гично всем другим переменным. Описание указателя выполняется следую-
щим образом: указывается тип переменной, на которую будет ссылаться ука-
затель, и далее приводится символическое имя указателя с предшествующим
символом *, представленным в лист. 4.18. В этом примере переменные рх,
py, pz являются указателями на переменные типа int.

Лист. 4.18. Объявление переменных типа указатель


int х,y,z;
int *рх,*py,*pz;

Смысл определения выше: переменные, на которые ссылаются указатели


px, py, pz, — целого типа int. Например, операция косвенного доступа по
указателю *рх может появиться в любом контексте, где может понадобиться
текущее значение х. Так, оператор ниже (лист. 4.19) присваивает y значение
на единицу большее, чем значение х.

Лист. 4.19. Пример работы с указателем


y = *рх + 1;

104
Ссылки на указатели могут появляться и в левой части операторов при-
сваивания. Так, первый оператор, приведенный в лист. 4.20, обеспечивает
присваивание переменной x значения, равного нулю. Второе выражение
выполняет по существу инкрементирование переменной х. Такое же дей-
ствие выполняется и с помощью оператора в третьей строке.

Лист. 4.20. Примеры операций с указателем


*рх = 0;
*рх +=1;
(*рх)++;

Круглые скобки в последнем примере необходимы, если хотим инкремен-


тировать именно переменную, которая адресуется указателем. Напротив,
если хотим добраться до текущего элемента массива, а затем «переместить»
указатель на следующий элемент массива, необходимо использовать запись,
показанную в лист. 4.21.

Лист. 4.21. Пример работы с указателем


y=*px++;

Переменной y будет присвоено значение по текущему указателю, а затем


значение указателя будет инкрементировано для доступа к следующему эле-
менту массива, т.е. будет выполнено постинкрементирование указателя.
Обратите внимание, что при объявленном указателе типа int указатель будет
автоматически инкрементироваться на 1, а при указателе типа long — на 2.
Указатели являются переменными, поэтому с ними можно обращаться,
как и с любыми другими переменными. Так, значение одного указателя
можно присваивать другому (лист. 4.22).

Лист. 4.22. Пример работы с указателем


py = px;

Контрольные вопросы
1. Указатели должны быть не только объявлены, но и обязательно проинициализиро-
ваны перед использованием. Что произойдет, если не выполнить инициализацию?
2. При описании типа указателя инициализация указателя может быть попутной.
Выполняется в двух вариантах: 1) с помощью обычного оператора присваивания;
2) посредством указания адреса переменной в скобках сразу после имени указателя.
Каким операторам эквивалентны следующие два оператора?
int *pa = &a;
int *pb (&b);

105
4.4. УКАЗАТЕЛИ И МАССИВЫ

В языке Cи существует сильная взаимосвязь между указателями и масси-


вами. Любую операцию, которую можно выполнить с помощью индексов
массивов, можно выполнить и с помощью указателей. Вариант с указате-
лями оказывается обычно более быстрым. Этот вариант выбирается и ком-
пилятором и опытным программистом, который пишет программу на языке
Ассемблер. Для начинающих программистов, изучающих программирова-
ние на языке Си, работа с указателями кажется более сложной, чем работа с
массивами. С приходом опыта точка зрения меняется.
Массив объявляется с помощью указания типа элементов массива, симво-
лического имени массива и длины массива, заключенной в квадратные
скобки (лист. 4.23).

Лист. 4.23. Объявление массива


int inp_buffer[100]; /* массив входных данных */

Массив целочисленных переменных типа int состоит из 100 элементов, к


каждому из которых можно обратиться по символическому имени массива
inp_buffer (по существу, базовому адресу массива) и индексу, указанному в
квадратных скобках, например inp_buffer[0], inp_buffer[1],...,
inp_buffer[99]. Последний допустимый индекс будет на 1 меньше общего
числа элементов массива, указанного в объявлении. Обращение к i-му эле-
менту массива возможно с помощью выражения inp_buffer[i].
Объявим указатель input типа int для доступа к элементам массива, как
показано в лист. 4.24. Инициализация указателя может быть выполнена
начальным адресом массива любым из приведенных ниже способов:

Лист. 4.24. Инициализация указателя


int *input;
/*указатель для доступа к элементам массива */
input = inp_buffer; /*базовый адрес массива */
input = &inp_buffer[0];
/*адрес первого элемента массива */

Существует очень тесное соответствие между индексацией и арифмети-


кой указателей. В действительности компилятор преобразует ссылку на мас-
сив в указатель на начало массива.
После того как указатель на массив определен, к элементам массива воз-
можен доступ с помощью указателя. Так, оператор, приведенный в лист. 4.25,
выполняет копирование первого элемента массива inp_buffer[0] в x.

Лист. 4.25. Доступ к элементу через указатель


x = *input;

106
Для доступа к i-му элементу массива можно воспользоваться выражением
inp_buffer[i] или указателем *(input+i). Последнее выражение имеет следу-
ющий смысл: доступ к переменной по указателю input, предварительно уве-
личенному на смещение, пропорциональное номеру элемента i. Для элемен-
тов типа int смещение будет равно i, а для элементов типа long — 2*i.
Будьте внимательны. Положение скобок при работе с указателями имеет
большое значение (лист. 4.26).

Лист. 4.26. Пример работы с указателем


x = (*input)+2;
// Добавить к первому элементу массива число 2
x = *(input+2);
// Загрузить третий элемент массива в переменную x

Имеется существенное различие между именем массива и указателем.


Указатель является переменной, так что операции типа input = inp_buffer и
input++ имеют смысл. Однако имя массива является всего лишь константой,
а не переменной. Поэтому конструкции типа inp_buffer=input или
inp_buffer++ использовать нельзя.

Практическая работа

1. Создайте новый проект lab4.pjt. Подключите к проекту командный


файл компоновщика из предыдущей работы lab3.cmd, а также файл библио-
теки реального времени выполнения rts2800_ml.lib. Подключите к проекту в
качестве исходного файла файл lab4_a.c. Он содержит пример простой про-
граммы на языке Си в которой объявляются два массива целых чисел по 10
элементов каждый, и выполняется копирование элементов из массива источ-
ника в массив приемник (рис. 4.1). Массив передатчик предварительно ини-
циализируется, массив приемник — нет. Исследуйте программу. Найдите
операторы описания массивов и объясните, что означает инициализировать
массив списком?
2. Выполните компиляцию и компоновку программы. Убедитесь в отсут-
ствии ошибок.
3. Как изменится оператор описания массива источника, если все кон-
станты задать не в десятичной системе счисления, а в шестнадцатеричной?
Внесите соответствующие изменения в программу. Перекомпилируйте ее.
4. Попробуйте проинициализировать только часть элементов массива
источника. При этом все оставшиеся элементы должны быть автоматически
проинициализированы нулями. Убедитесь, что этот механизм работает.
5. Выполните отладку программы в пошаговом режиме и в режиме с точ-
кой останова (можно на последнем операторе). Откройте для этого два окна
дампов памяти: для наблюдения за содержимым массива приемника и мас-
сива источника. Проследите за процессом копирования данных.
107
Рис. 4.1. Программа копирования массива с использованием цикла for

Контрольные вопросы
1. Объясните синтаксис оператора for. Почему логическое условие в операторе цикла
записано как i < 10, а не, например, так: i <= 10? Какой индекс будет иметь последний
скопированный элемент массива? Почему?
2. Какие действия выполняются в последнем операторе программы? Замените этот
оператор на более наглядный.

Среда Сode Composer Studio допускает работу с массивами и в окнах


Watch, что значительно удобнее, чем использование окон дампов памяти.
При этом вначале в позиции Name оба массива отображаются как упако-
ванные и в поле Value выводятся начальные адреса размещения массивов в
памяти. В поле Type указывается размер массива. Поле Radix используется
обычным образом — здесь указывается формат вывода данных (система
счисления). Стоит щелкнуть мышкой по значку + рядом с именем массива,
как массив «раскроется» и можно не только исследовать каждый его эле-
мент по отдельности, но и при необходимости модифицировать его значение
(рис. 4.2).

Практическая работа

1. Выполните отладку программы с использованием окна Watch.


2. Удалите из проекта файл lab4_a.c и подключите вместо него файл
lab4_b.c, представленный на рис. 4.3. Выполните отладку новой программы.
108
Рис. 4.2. Окно просмотра переменных

Рис. 4.3. Программа копирования массива с помощью цикла while

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


программе Вы отдадите предпочтение? Почему?
3. Модифицируйте программу для копирования длинных чисел со знаком
типа long. Выполните отладку.
4. Замените файл lab4_b.c в проекте на файл lab4_с.c, демонстрирующий
работу с указателями (рис. 4.4). Выполните трансляцию и компоновку.
5. Обратите внимание на то, как объявлены указатели на массив прием-
ник и на массив передатчик, как выполнена инициализация указателей.
Выполните инициализацию процессора и переменных по команде Go Main.
Исследуйте дампы памяти и докажите, что указатели на самом деле проини-
циализированы правильно.
6. Попробуйте выполнить начальную инициализацию указателей не
попутно, как это сделано в программе, а явно, с помощью отдельных опера-
торов. Убедитесь, что ваши изменения дают те же результаты.
109
Рис. 4.4. Копирование массива с использованием указателей

7. Выполните отладку программы в пошаговом режиме. Следите за указа-


телями ptr_to и ptr_from и одновременно за содержимым массивов прием-
ника и передатчика.

Контрольные вопросы
1. Какие значения примут указатели ptr_to и ptr_from в конце цикла?
2. Как работает последний оператор программы? К какому элементу массива на
самом деле прибавляется число 5? Проверьте свои соображения.
3. Перепишите программу с использованием цикла for.

4.5. ФУНКЦИИ В ЯЗЫКЕ СИ


Материал, представленный ниже, является лишь кратким введением в
технологию создания и использования функций на языке Си.
В языке Cи функции эквивалентны функциям и процедурам в языке Пас-
каль или подпрограммам на языке Ассемблер. Функции предоставляют
удобный способ заключения некоторой части программы в «черный ящик»,
который в дальнейшем можно использовать, не интересуясь его внутренним
содержимым. Если функции организованы должным образом, то можно
игнорировать то, как делается работа, — достаточно знание того, что делается.
110
Язык Cи разработан таким образом, чтобы сделать использование функ-
ций легким, удобным и эффективным. Функции позволяют разбить большие
вычислительные задачи на маленькие подзадачи и использовать в работе то,
что уже сделано другими, а не начинать каждый раз с пустого места. Мы уже
применяли некоторые внешние функции, включенные в стандартные объект-
ные библиотеки, например printf(). Прежде чем использовать такую функ-
цию, ее необходимо обязательно объявить, что мы и делали в подключаемых
заголовочных файлах. Кроме того, к проекту должна быть подключена соот-
ветствующая объектная библиотека. Пользователь может разрабатывать и
создавать свои собственные библиотеки наиболее часто используемых функ-
ций — пользовательские библиотеки.
В любом программном модуле можно использовать как внешние функции,
так и внутренние. Первые могут находиться либо в библиотеках, либо в дру-
гих программных модулях и должны быть обязательно объявлены в текущем
модуле. Внутренние функции должны быть описаны в текущем програм-
мном модуле. Любую функцию можно вызвать только в том случае, если она
описана. Для определения функций используется следующий шаблон:

Лист. 4.27. Объявление функции


тип_результата имя_функции(список_аргументов)
{
операторы_тела_функции
}

Тип результата определяет тип возвращаемого функцией значения. Фун-


кции могут и не возвращать никакого значения. В этом случае в качестве
типа результата указывается тип void (пустой).
Список аргументов содержит типы и имена формальных параметров,
указанных через запятую (если они существуют). Если в функцию не переда-
ются никакие параметры, то в списке аргументов указывается тип void.
Далее в операторных скобках {} описывается тело функции, т.е. последова-
тельность выполняемых ею действий. В качестве последнего оператора фун-
кции обычно используется оператор возврата в основную программу
return (лист. 4.28).

Лист. 4.28. Пример объявления функции


long Sum (int x, int y)
{
return ((long)x+(long)y);
}

Как видно из примера, в лист. 4.28 определена функция Sum сложения


двух переменных типа int. Из определения видно, что функция возвращает
значение типа long. Значение, вычисленное функцией, возвращается в вызы-
вающую ее программу с помощью оператора return, который имеет синтак-
сис, показанный ниже:
111
Лист. 4.29. Оператор return
return выражение;
Если функция не возвращает никакого значения, то оператор return может
вызываться без выражения либо вообще не использоваться (лист. 4.30).

Лист. 4.30. Пример оператора return


return;
В выражении для определения возвращаемого значения использованы
две операции явного преобразования типов (long) x и (long) y, которые поз-
воляют еще до операции сложения преобразовать входные 16-разрядные
переменные типа int в выходные 32-разрядные типа long (см. лист. 4.28).
После того как функция определена, ее можно использовать в программе.
Вызов функции производится по ее имени, а вместо формальных параметров
указываются фактические параметры.
Существуют два способа передачи параметров в функцию. Первый назы-
вается передачей параметров по значению, а второй — по ссылке.
Примером первого способа является вызов функции в лист. 4.31. Пара-
метры a и b вначале вычисляются, а затем записываются в стек для исполь-
зования в теле функции Sum. Параметры, переданные по значению, нельзя
изменить в теле самой функции.

Лист. 4.31. Пример вызова функции


void main(void)
{
int a = 2;
int b = 10;
long result;
result = Sum(a,b);
}
Если функция должна изменить значения аргументов, то выбирается вто-
рой способ передачи параметров — с помощью передачи указателей на
переменные. В этом случае функция будет иметь доступ к аргументам и при
необходимости может изменить их. Например, после вычисления суммы
переменных a и b можно обнулить их значения (лист. 4.32).

Лист. 4.32. Пример описания функции с аргументом типа указатель


long Sum(int *x, int *y)
{
long temp_sum = (long)(*x) + (long)(*y);
*x = 0;
*y = 0;
return temp_sum;
}

112
Как видно из определения функции, теперь в нее передаются не два зна-
чения типа int, а два указателя на переменные типа int. В теле функции вна-
чале вычисляем значение локальной переменной temp_sum, а затем обну-
ляем значения аргументов. Очевидно, что и вызов функции будет теперь
выглядеть иначе:

Лист. 4.33. Пример вызова функции с аргументом типа указатель


void main(void)
{
int a = 2;
int b = 10;
long result;
result = Sum(&a,&b);
}

Теперь вызывающая программа передает в функцию адреса переменных


a и b. Напомним, что операция & выдает адрес переменной, следовательно,
&a является значением указателя на переменную a.
Указатели в качестве аргументов обычно используются в функциях, кото-
рые должны возвращать более одного значения. Можно сказать, что Sum
возвращает три значения: результат суммирования и две обнуленные пере-
менные.

Практическая работа

1. Подключите к проекту файл lab4_e.c с программой, выполняющей сум-


мирование элементов двух массивов, размеры которых известны (рис. 4.5).
Обратите внимание на то, что функция суммирования элементов массива
Summa_Array произвольной длины описана до основной программы main()
и имеет два формальных параметра:
массив, подлежащий обработке int array[];
размер массива int m.
Запись int array[] означает, что в функцию передаются не значения всех
элементов массива, а лишь указатель на начало массива. Вторым парамет-
ром в функцию передается длина массива. Функция возвращает в основную
программу сумму элементов массива типа long, подсчитанную с учетом воз-
можных переполнений. Для последовательного суммирования элементов
двух массивов функция вызывается из основной программы дважды с раз-
ными значениями фактических параметров. При этом имена массивов x и y,
используемые в качестве фактических параметров, содержат начальные
адреса массивов и являются по существу значениями указателей на мас-
сивы.
2. Выполните отладку программы. Проследите за тем, как управление
передается функции, как выполняется возврат в основную программу.
113
Рис. 4.5. Пример использования функции

Задайте несколько разных наборов переменных, в частности таких, при кото-


рых результат намного превысит формат слова. Например, проинициализи-
руйте первый массив числами (+20 000), а второй массив числами (–30 000).
Правильно ли работает наша программа?
3. Замените исходный файл в проекте на модернизированный файл
lab4_f.c (рис. 4.6). Новая функция суммирования элементов массива отлича-
ется только тем, что в качестве формального параметра явно задан указатель
на массив. Вызов функции практически не отличается от предыдущего, так
как значения указателей ptr_x и ptr_y равны x и y соответственно. Выпол-
ните отладку программы на разных наборах данных.
4. Сравните ассемблерный код, который генерируется транслятором с
языка Си для двух рассмотренных программ. Есть ли отличия?

Контрольные вопросы

1. Должен ли тип фактического параметра функции обязательно соответствовать


типу формального параметра?
2. Какие действия выполняются оператором *pa++ = *pb++; ?
3. Напишите функцию нахождения минимального элемента массива, максимального
элемента.

114
Рис. 4.6. Работа функций через указатели

4. Разработайте функцию сортировки массива целочисленных элементов по нараста-


нию значений. Можете руководствоваться, например, таким алгоритмом:
1) найти минимальный элемент массива;
2) поменять местами найденный элемент с первым элементом массива;
3) сделать то же самое (два предыдущих шага), но начиная уже со второго элемента
массива;
4) выполнить эти действия (n – 1) раз, где n — число элементов массива.

115
Глава 5

ТЕХНОЛОГИЯ РАБОТЫ С ПЕРИФЕРИЙНЫМИ


УСТРОЙСТВАМИ МИКРОКОНТРОЛЛЕРОВ
СЕМЕЙСТВА C28xx НА ЯЗЫКЕ СИ. РАБОТА
С ПОРТАМИ ДИСКРЕТНОГО ВВОДА-ВЫВОДА,
ТАЙМЕРАМИ, КОНТРОЛЛЕРОМ ПРЕРЫВАНИЙ

5.1. СПОСОБЫ ДОСТУПА К РЕГИСТРАМ


ВСТРОЕННЫХ ПЕРИФЕРИЙНЫХ УСТРОЙСТВ
МИКРОКОНТРОЛЛЕРА НА ЯЗЫКЕ СИ

На кристалл микроконтроллеров ‘C28xx интегрировано большое число


периферийных устройств, начиная от менеджера событий и аналого-цифро-
вого преобразователя и кончая портами последовательного асинхронного и
синхронного ввода/вывода. Работа с ними выполняется с помощью соответ-
ствующих регистров, которые отображены на память данных микроконт-
роллера. Соответствующие области памяти данных называются периферий-
ными фреймами. Термин отображены на память данных следует понимать
так, что некоторые ячейки внутренней памяти данных микроконтроллера
жестко отведены производителем под регистры периферийных устройств и
не могут использоваться как память данных общего назначения (для разме-
щения переменных пользователя).
В микроконтроллерах ‘C28x нет специальных команд ввода/вывода дан-
ных типа IN/OUT. Любая команда процессора, которая обращается к памяти
данных по записи, может записать данные в регистр ПУ, и, напротив, любая
команда, которая обращается к памяти данных по чтению, может считать
данные из регистра ПУ. Непременным условием является лишь соответствие
адреса операнда адресу регистра периферийного устройства.
Суть настройки периферии (инициализации) заключается в установке
значений определенных регистров, определяющих нужный режим работы
ПУ. В простейшем случае это сводится к записи шестнадцатеричных данных
в конкретные ячейки памяти данных, выделенные под регистры ПУ, или к
выполнению побитовых логических операций между текущим содержимым
регистра ПУ и маской. Напомним, что операция «Логического И» с маской
позволяет сбросить нужные биты регистра, операция «Логического ИЛИ» —
установить нужные биты, а операция «Исключающее ИЛИ» — проинверти-
ровать.
116
Соответствующие логические операции легко выполняются на языке
Ассемблер с использованием так называемых атомарных команд «Чтение—
Модификация—Запись», которые за один цикл выполняют операцию получе-
ния данных из регистра ПУ, модификации данных (посредством выполнения
логической операции с маской) и записи результата обратно в регистр ПУ.
Аналогичные действия можно выполнить и на языке высокого уровня Си.
При этом возможны два различных подхода.
Рассмотрим суть первого подхода. Например, регистр управления ана-
лого-цифровым преобразователем (АЦП) ADCTRL1 находится в памяти
данных микроконтроллера по адресу 0x00007100. Для удобства работы с
ним посредством директивы препроцессора #define определяется соответ-
ствующий макрос ADCTRL1 (лист. 5.1). По существу, этой директивой
определяется указатель на переменную типа unsigned int по имени
ADCTRL1, который одновременно инициализируется значением адреса
регистра управления в памяти данных (0x00007100).
Дополнительный модификатор типа volatile запрещает компилятору
выполнять оптимизацию доступа к переменной по этому указателю. Дело в
том, что из некоторых регистров ПУ может потребоваться последовательная
многократная операция чтения в одну и ту же переменную. Если специфика-
тор типа volatile опустить, то компилятор может «подумать», что это описка
программиста, и исключить один из, «как ему кажется», ненужных операто-
ров из программы. Спецификатор volatile применяется к объектам, значения
которых могут меняться вне зависимости от кода программы, например, по
прерыванию или в процессе работы периферийного устройства, — говорят,
что такие переменные могут меняться «на лету».
Теперь работа с регистрами управления АЦП ADCTRL1 и ADCTRL2
может осуществляться через объявленные указатели: первым оператором в
лист. 5.1 производится запись данных (слова 0x1234) в регистр ПУ, а вторым
оператором — инвертирование 14-го бита регистра с помощью сложного
оператора присваивания. Этот оператор производит вначале считывание
текущего содержимого регистра, затем — операцию «Логического ИЛИ» с
маской 0x4000 и, наконец, операцию записи результата маскирования
обратно в регистр ПУ.
Лист. 5.1. Традиционный доступ
#define ADCTRL1(volatile unsigned int *)0x00007100
#define ADCTRL2(volatile unsigned int *)0x00007101
//Прим. volatile-запрет оптимизации доступа к переменной
void main(void)
{
*ADCTRL1 = 0x1234; // Запись регистра целиком
*ADCTRL2 |= 0x4000;
// Установка 14 бита. Прим. |= операция ИЛИ
}

Рассмотрим пример генерации ассемблерного кода компилятором Си при


работе с таймером общего назначения CPU Timer 0, приведенного в табл. 5.1.
117
Табл и ц а 5.1
Традиционный доступ к регистрам ПУ

Ассемблерный код,
Исходный Си-код
созданный компилятором Си
// Остановить CPU Timer0 MOV AL, *(0:0x0c04)
*TIMER0TCR |= 0x0010; ORB AL, #0x10
MOV *(0:0x0c04), AL

// Загрузить новое 32-разрядное MOVL XAR4, #3072


// значение периода MOVL XAR5, #65536
*TIMER0TPR32 = 0x00010000; MOVL *+XAR4[0], XAR5

// Запустить CPU Timer0 MOV AL, *(0:0x0c04)


*TIMER0TCR &= 0xFFEF; AND AL, #0xffef
MOV *(0:0x0c04), AL
Недостатки: 9 команд, 9 тактов
Код плохо читается без комментариев.
Битовые маски должен определять сам программист.

Короткий комментарий к ассемблерному тексту:


1) содержимое периферийного регистра косвенно (по значению указателя,
заданного непосредственным операндом в команде) считывается в младшее
слово аккумулятора AL. При этом значение указателя автоматически расши-
ряется нулями от слова к длинному слову для доступа ко всей расширенной
памяти микроконтроллера. Выполняется операция «Логического ИЛИ»
содержимого AL с маской. Полученный результат записывается косвенно в
память данных по тому же указателю (в регистр ПУ);
2) адрес регистра периода загружается в регистр-указатель XAR4 как
непосредственный операнд. Значение для инициализации регистра периода
загружается в регистр XAR5, выполняющий функцию регистра общего
назначения. Содержимое XAR5 косвенно копируется по адресу регистра
периода (базово-индексная адресация с преиндексированием и значением
индекса, равным 0);
3) операции, аналогичные п. 1.
Видно, что традиционный подход доступа к регистрам ПУ (с использова-
нием директивы препроцессора #define) имеет как преимущества, так и
недостатки.
Преимущества традиционного подхода:
• описание периферийных регистров достаточно простое;
• нет необходимости внесения изменений в файлы управления компо-
новкой;
• имя переменной точно соответствует имени регистра;
• имя переменной короткое.
Недостатки традиционного подхода:
• требует создания индивидуальных масок для манипулирования отдель-
ными разрядами и битовыми полями регистров. Эта задача возлагается на
программиста;
118
• нельзя отображать значения отдельных битовых полей регистров ПУ в
окнах переменных Watch Window среды Code Composer Studio — требуется
специально готовить эти поля для вывода с помощью логических функций.
Затрудняется отладка программ;
• генерируется универсальный, но не очень эффективный код. Как видно
из табл. 5.1, атомарные команды «Чтения—Модификации—Записи» микро-
контроллеров ‘C28xx компилятором не используются;
• трудно запоминать и набирать названия регистров.
Второй подход организации доступа к регистрам периферийных уст-
ройств (структурный) базируется на следующих основных принципах:
• регистры каждого встроенного периферийного устройства одного и
того же типа (менеджера событий А и В, синхронных периферийных пор-
тов А, В, C, D и т.д.) структурированы одинаковым образом самим разработ-
чиком микроконтроллеров и располагаются в памяти данных одинаково, но с
разных начальных адресов. Назовем такую совокупность ячеек памяти дан-
ных регистровым файлом периферийного устройства;
• тип регистрового файла для каждого типа периферийного устройства
одинаков и может быть описан в виде унифицированной структуры данных
ПУ данного типа, содержащей как регистры периферийного устройства
целиком, так и отдельные битовые поля или биты этих регистров. Как известно,
в языке Си можно создавать переменные типа «структура» с помощью опе-
ратора struct с описанием полей структуры и их типов;
• после того, как унифицированная структура данных регистрового
файла типового периферийного устройства описана, можно создать любое
число экземпляров переменных, имеющих этот тип, например: для двух
менеджеров событий; для двух синхронных периферийных портов; для
одного АЦП. Естественно, что переменные создаются только для тех уст-
ройств, которые будут использованы в проекте;
• все обычные глобальные переменные, создаваемые программистом на
языке Си, по умолчанию попадают в секцию .ebss и размещаются компонов-
щиком в одном из банков встроенного ОЗУ общего назначения. В нашем
случае каждая переменная, описывающая отдельное периферийное устройс-
тво, должна быть строго привязана к месту фактического расположения
регистрового файла в памяти данных микроконтроллера. Поэтому перед
созданием таких переменных обязательно объявляется специальная секция
данных для хранения регистрового файла конкретного периферийного уст-
ройства. Имя этой секции данных жестко связывается с именем переменной
(структуры) соответствующего регистрового файла. Для этой цели использу-
ется специальная директива препроцессора #pragma;
• так как теперь в программе появились нестандартные секции данных,
необходимо указать компоновщику конкретное место их размещения, т.е.
привязать секции данных регистров периферийных устройств к фактичес-
ким областям в памяти данных, где они и находятся на самом деле — потре-
буется модификация файла управления компоновкой .cmd. После этого каж-
дый регистр периферийного устройства получит свой собственный адрес,
119
знать который уже не обязательно. К регистрам можно будет обращаться по
символическим именам.
Кажется, что объем работы, который должен выполнить программист для
реализации структурного подхода доступа к периферии, очень велик. На
самом деле, фирма Texas Instruments поставляет уже готовую библиотеку
файлов для работы с периферией для всех типов выпускаемых микроконт-
роллеров. Нужно лишь научиться пользоваться этими материалами. В про-
цессе выполнения настоящей работы Вы приобретете необходимые навыки.
Итак, если подготовительная работа выполнена, то обращение к любому
регистру периферийного устройства или битовому полю выполняется как к
обычному элементу переменной типа «структура» (лист. 5.2).

Лист. 5.2. Доступ к регистрам с использованием структурного подхода


void main(void)
{
AdcRegs.ADCTRL1.all = 0x1234;
//Запись регистра целиком
AdcRegs.ADCTRL2.bit.RST_SEQ1 = 1;
//Установка 14-ого бита
}

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


(табл. 5.2).
Краткий комментарий к ассемблерному тексту:
1) компилятор автоматически инициализирует указатель текущей стра-
ницы памяти данных DP для доступа к регистровому файлу нужного пери-
ферийного устройства. Генерируется команда «Логическое ИЛИ» с маской
для установки нужного бита регистра, относящаяся к быстрым атомарным
командам типа «Чтение—Модификация—Запись», выполняемым за один
такт;

Табл и ц а 5.2
Доступ к регистрам ПУ с использованием структурного подхода

Ассемблерный код,
Исходный Си-код
созданный компилятором Си
// Остановить CPU Timer0 MOVW DP, #0030
CpuTimer0Regs.TCR.bit.TSS = 1; OR @4, #0x0010

// Загрузить новое 32-разрядное


// значение периода MOVL XAR4, #0x010000
CpuTimer0Regs.PRD.all = 0x00010000; MOVL @2, XAR4

// Запустить CPU Timer0


CpuTimer0Regs.TCR.bit.TSS = 0; AND @4, #0xffef
Код легко читается без комментариев. 5 команд, 5 тактов
Битовые маски уже встроены в структуры.

120
2) производится предварительная загрузка регистра XAR4 длинной кон-
стантой с последующим копированием содержимого регистра XAR4 по
короткому прямому адресу регистра периода;
3) нужный бит регистра управления таймером очищается с помощью ато-
марной команды «Логического И» с маской.
Как видите, компилятор создает максимально эффективный машинный
код как по объему памяти, так и по времени выполнения.
Преимущества структурного подхода:
• просто работать с индивидуальными разрядами и битовыми полями
исключительно по символическим именам;
• все необходимые маски генерирует сам компилятор;
• в окне Watch Window среды CCS может быть отображено текущее
содержимое как регистра целиком, так и каждого бита или битового поля в
отдельности. Возможна модификация значений битовых полей в процессе
отладки.
Наиболее эффективный генерируемый код — создаваемый с использова-
нием прямой страничной адресации данных и быстрых атомарных инструк-
ций.
Недостатки структурного подхода:
• трудно запомнить имена структур, однако в Code Composer Studio есть
встроенная система помощи Code Maestro, которая по первым буквам имени
выдает необходимые подсказки;
• больше приходится печатать, но можно использовать встроенные
средства авторасширения имен по первым набранным символам (помогает
Code Maestro);
• чтобы эффективно работать, необходимо один раз разобраться с систе-
мой использования готовых файлов, поставляемых Texas Instruments.
Более подробно особенности структурного подхода к программированию
периферии на языке Си рассмотрим ниже.

5.2. СТРУКТУРА ЗАГОЛОВОЧНОГО ФАЙЛА


ПЕРИФЕРИЙНОГО УСТРОЙСТВА

Мы уже отмечали, что микроконтроллер TMS320F2812, на котором бази-


руется плата eZdspTM, обладает большим количеством встроенных перифе-
рийных устройств: ADC, CAN, SPI, SCI, McBSP и др.
Каждое из них должно иметь свой регистровый файл, описанный в виде
структуры. Описания типовых структур регистровых файлов хранятся в
соответствующих заголовочных файлах. Число заголовочных файлов равно
числу типов периферийных устройств, входящих в состав микроконтроллера.
Откроем в качестве примера проект lab5 и рассмотрим более подробно
его содержимое, исходная программа которого представлена на рис. 5.1. В
состав проекта входит основная программа main.c с двумя подключенными
файлами. Один из них является специальным заголовочным файлом описа-
ния периферии ‘C28xx — DSP28_Device.h.
121
Рис. 5.1. Окно исходной программы текущего проекта

Проанализируем более подробно


состав файлов проекта в окне менед-
жера проекта. Раскрыв папку Include,
увидим заголовочные файлы, вклю-
ченные в проект, а раскрыв папку
Source — исходные файлы. По умол-
чанию предполагается, что мы
можем работать с любыми перифе-
рийными устройствами микроконт-
роллера, поэтому к проекту подклю-
чены заголовочные файлы всех
возможных периферийных уст-
ройств, приведенные на рис. 5.2.
Обратите внимание на то, что
имя заголовочного файла любого
периферийного устройства состоит
из символического имени процес-
сора DSP28, после которого через
символ подчеркивания указано сим-
волическое имя периферийного уст-
ройства данного типа, например:
Ev — для менеджера событий;
Sci — для последовательного ком-
муникационного порта; Spi — для
синхронного периферийного интер-
фейса; ECan — для расширенного
Рис. 5.2. Список заголовочных файлов проекта CAN-контроллера и т.п.

122
Регистровый файл каждого периферийного устройства описывается в
виде структуры на Си, причем отдельные регистры указываются в структуре
в порядке их фактического расположения в памяти данных, начиная с млад-
шего адреса. Пустые места также описываются с помощью неиспользуемых
резервных переменных. Обычно в качестве типа элементов структуры ука-
зывается тип Uint16 (переопределенный тип unsigned int) или тип Uint32
(переопределенный тип unsigned long). В первом случае регистр 16-разряд-
ный, во втором — 32-разрядный.
Если какой-либо регистр периферийного устройства имеет битовые поля,
то он обязательно описывается в виде структуры битовых полей. В примере
в лист. 5.3 такое описание дано для регистра управления АЦП ADCTRL1.

Лист. 5.3. Объявление структуры битового поля


/* Определение структуры битовых полей регистра управ-
ления АЦП */
struct ADCTRL1_BITS { // Описание бит
Uint16 rsvd1:4; // 3:0 Зарезервированы
Uint16 SEQ_CASC:1; // 4 Каскадный режим секвенсора
Uint16 rsvd2:1; // 5 Зарезервировано
Uint16 CONT_RUN:1; // 6 Циклическое преобразование
Uint16 CPS:1; //7 Делитель тактовой частоты ядра АЦП
Uint16 ACQ_PS:4; // 11:8 Время выборки
Uint16 SUSMOD:2; // 13:12 Режим работы с эмулятором
Uint16 RESET:1; // 14 Перезапуск АЦП
Uint16 rsvd3:1; // 15 Зарезервировано
};
/*Uint16 — новый тип, который определен как unsigned
int в заголовочном файле */
/* Определение регистра управления в виде объединения
двух полей all и bit для доступа к регистру целиком и
каждому битовому полю */
union ADCTRL1_REG {
Uint16all;
StructADCTRL1_BITSbit;
};
// Создание переменной AdcRegs типа структура
// регистрового файла struct ADC_REGS
extern volatile struct ADC_REGS AdcRegs;

Структура битовых полей имеет имя ADCTRL1_BITS и содержит описа-


ние всех битовых полей регистра в последовательности:
тип данных, размещаемых в битовом поле (обычно Uint16);
имя битового поля, точно совпадающее с именем, используемым в техни-
ческой документации на периферию микроконтроллера;
символ двоеточия, после которого указано число битов в поле.
123
Видно, что младшие 4 бита регистра зарезервированы под будущие при-
менения, следующий бит имеет имя SEQ_CASC и управляет режимом кас-
кадирования секвенсора АЦП.
Описание каждого члена структуры заканчивается точкой с запятой. Спи-
сок всех элементов структуры (всех битовых полей) заключается в фигурные
скобки { }. Для удобства программиста в поле комментария указываются
номера битов в периферийном регистре, соответствующие данному бито-
вому полю, начиная с младшего бита, а также назначение битового поля.
Для того чтобы к регистру периферийного устройства можно было обра-
титься двумя способами: как ко всему 16-разрядному слову, так и к каждому
битовому полю регистра в отдельности, — используется описание регистра
в виде объединения (union), представляющего собой частный случай струк-
туры, все поля которой располагаются по одному и тому же адресу. Объеди-
нение позволяет фактически наложить друг на друга несколько объектов.
В нашем примере объединение имеет имя регистра управления АЦП
ADCTRL1_REG и содержит два элемента: под именем all типа Uint16 и под
именем bit типа описанной выше структуры битовых полей Struct
ADCTRL1_BITS.
Такой прием позволяет по имени регистра, расширенному после точки (.)
(операция выбора элемента структуры на Си) словом all, обращаться к
регистру целиком, а словом bit — к любому битовому полю (лист. 5.4).

Лист. 5.4. Доступ к регистрам через структуру

#include “DSP281x_Device.h”
void InitAdc(void)
{
/* Перезапуск модуля АЦП (обращение к отдельному бито-
вому полю регистра)*/
AdcRegs.ADCTRL1.bit.RESET = 1;
/* Задание режима работы АЦП (обращение целиком ко
всему регистру управления)*/
AdcRegs.ADCTRL1.all = 0x0710;
};

Рассмотренная технология описания регистровых файлов позволяет уни-


фицировать обращения к регистрам на Си (лист. 5.5).

Лист. 5.5. Правила наименования структур

PeripheralName.RegisterName.all // Доступ к 16 или


// 32-разрядному регистру целиком
PeripheralName.RegisterName.bit.FieldName // Доступ к
//определенному битовому полю

124
Имена всех регистровых файлов периферийных устройств
PeripheralName определены Texas Instruments и могут быть найдены в заго-
ловочных файлах DSP28xx. Они представляют собой комбинацию заглавных
и строчных букв, например CpuTimer0Regs для регистрового файла Тай-
мера 0 центрального процессора. Имена отдельных регистров периферий-
ного устройства RegisterName такие же, как в описании на периферию мик-
роконтроллера, всегда набранные заглавными буквами, например TPR
(Регистр периода таймера). Имена отдельных битовых полей периферийных
регистров FieldName тоже полностью соответствуют документации на мик-
роконтроллер и набираются заглавными буквами, например RESET (Сброс
АЦП).

5.3. РАЗМЕЩЕНИЕ СТРУКТУР


РЕГИСТРОВЫХ ФАЙЛОВ В ПАМЯТИ ДАННЫХ

Вернитесь в конец лист. 5.3. Здесь создана глобальная переменная по


имени AdcRegs, которая имеет тип структуры регистрового файла АЦП
struct ADC_REGS. Мы уже говорили о том, что такая переменная должна
обязательно попасть в специальную секцию данных, предназначенную
исключительно для хранения этого конкретного регистрового файла. Поэ-
тому перед созданием такой переменной должна быть выполнена директива,
которая устанавливает связь между именем переменной (в нашем случае
AdcRegs) и именем предназначенной для ее размещения секции данных (в
нашем случае AdcRegsFile). Для этой цели применяется специальная дирек-
тива препроцессора #pragma DATA_SECTION, приведенная в лист. 5.6. Эту
директиву можно найти в файле описания глобальных переменных
DSP281x_GlobalVariableDefs.c.

Лист. 5.6. Объявление новой секции


#pragma DATA_SECTION(AdcRegs,"AdcRegsFile");

Смысл указанной директивы компилятору следующий: когда встретишь


переменную по имени AdcRegs, размести ее в специальной секции данных
DATA_SECTION по имени AdcRegsFile.
Теперь осталось определить область памяти микроконтроллера, в которой
фактически находятся регистры АЦП. Для этого в командном файле управ-
ления компоновкой с помощью директивы MEMORY нужно задать началь-
ный адрес банка регистров АЦП и его длину (см. гл. 1). Назовем этот банк
ADC и из технической документации на микроконтроллер определим
начальный адрес банка 0x007100 и его длину 0x20. Разместим банк ADC в
памяти данных PAGE 1, пример которой представлен в лист. 5.7.
Естественно, необходимо дать указания компоновщику, в какой банк
памяти размещать нашу нестандартную секцию данных по имени
AdcRegsFile. Эта операция выполняется с помощью уже знакомой Вам
директивы SECTIONS:
125
Лист. 5.7. Пример инициализации памяти
MEMORY {
PAGE 1:
...
ADC:origin=0x007100, length=0x000020
... }
SECTIONS {...
AdcRegsFile:>ADCPAGE=1
... }

5.4. ФАЙЛ ОПРЕДЕЛЕНИЯ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ

Подключение заголовочных файлов осуществляется с помощью специ-


альной директивы препроцессора #include. Общим файлом, содержащим
ссылки на файлы с описаниями структур регистров отдельных периферий-
ных устройств, является файл DSP28_Device.h. Он подключается к про-
грамме, как показано в лист. 5.8 (см. также рис. 5.1):

Лист. 5.8. Подключение заголовочного файла


#include “DSP28_ Device.h”

Помимо подключения всех файлов с описаниями периферии, этот файл


содержит определения дополнительных типов, масок прерываний и макро-
сов. Например, в начале файла определены значения символических имен
TRUE (Истина) и FALSE (Ложь). Определены некоторые макросы, которые
по существу представляют собой ассемблерные команды. Так, макровызов
EINT будет автоматически заменяться ассемблерной вставкой asm (clrc
INTM) — очистить флаг INTM процессора, т.е. разрешить прерывания.
Файл DSP28_Device.h является главным подключаемым заголовочным
файлом. Если включить ссылку на этот файл в программе, то все.h-файлы,
описывающие периферийные устройства, будут добавлены автоматически.

Практическая работа
1. Найдите и исследуйте главный подключаемый заголовочный файл.
2. Откройте файл с описанием регистров АЦП. Сколько всего регистров
содержит этот файл, каково их назначение? Есть ли среди регистров АЦП
регистр результата аналого-цифрового преобразования? Как производится
доступ к этому регистру? Возможен ли побитовый доступ? Почему?
3. На примере регистрового файла АЦП проследите всю последователь-
ность объявления структур — от структур битовых полей регистров до
структуры регистрового файла АЦП целиком. Где создается конкретный
экземпляр этой структуры?
4. Найдите файл, в котором объявляется специальная секция данных для
АЦП.
126
5.5. ПРОГРАММИРОВАНИЕ ПЕРИФЕРИИ ‘C28xx
С ИСПОЛЬЗОВАНИЕМ ЗАГОЛОВОЧНЫХ ФАЙЛОВ

Устройство дискретных портов ввода/вывода


Битовые порты дискретного ввода/вывода микроконтроллера объединены
в группы по 16 линий и образуют 16-разрядные регистры ввода/вывода
общего назначения GPIOx, которые обозначаются буквами A, B, C, D, E, F, G, …,
соответственно. Число таких регистров зависит от типа микроконтроллера,
например в ‘C2810/12 их всего 6 (A, B, D, E, F, G). Регистр С зарезервирован.
Каждому биту регистра ввода/вывода с номером от 0 до 15 соответствует отде-
льный вывод микроконтроллера, например GPIOA3.
Большинство выводов микроконтроллера мультиплицированы и могут
работать в одном из двух режимов: 1) в качестве битового порта
ввода/вывода общего назначения; 2) в режиме выполнения специальной фун-
кции по обслуживанию одного из встроенных периферийных устройств.
Например, упомянутый вывод GPIOA3 может быть выводом многоканаль-
ного ШИМ-генератора PWM4.
Упрощенная схема организации битового порта ввода/вывода в микрокон-
троллерах ‘C28xx представлена на рис. 5.3. Основные принципы его работы
приведены ниже:
• при сбросе процессора XRS=0, в том числе при включении питания
вывод микроконтроллера автоматически переводится в высокоимпедансное
состояние (Z-состояние) с большим входным сопротивлением. Это состоя-
ние соответствует настройке порта на ввод данных по умолчанию;
• вывод микроконтроллера можно программно сконфигурировать для
обслуживания встроенного периферийного устройства (правая часть рис. 5.3)
или для дискретного ввода/вывода информации общего назначения (левая
часть рис. 5.3). Конфигурирование выполняется с помощью одного из регис-
тров управления мультиплексорами портов ввода/вывода GPxMUX, где x —
символическое имя порта (A, B, …). Запись в регистр мультиплексора 1
обеспечивает «периферийный ввод/вывод», а запись 0 — «ввод/вывод
общего назначения». Например, чтобы вывод микроконтроллера GPIOA3
стал работать в качестве порта ввода/вывода общего назначения в бит 3
регистра управления мультиплексорами порта А GPAMUX3 необходимо
записать 0;
• если вывод сконфигурирован для работы с периферийным устрой-
ством, оно будет автоматически управлять его состоянием. В противном слу-
чае программист должен задать либо режим ввода данных, либо режим
вывода. Эта операция выполняется посредством установки (для порта
вывода) или сброса (для порта ввода) соответствующего бита в регистре
управления направлением передачи данных GPxDIR, где x — имя порта;
• для качественной защиты дискретных входов от помех используется
специальный цифровой фильтр, принцип работы которого иллюстрирует
рис. 5.4. Состояние входа Input to Qual опрашивается периодически на
127
Бит (ы) регистров
Цифровой Периферийный ввод/вывод
GPxDAT/SET/CLEAR/TOGGLE ввод/вывод

Бит Бит
Регистр Управление
регистра регистра
GPxQUAL Z-состоянием
GPxMUX GPxDIR

0 1 0 1

MUX MUX

Входной SYSCLKOUT
фильтр

Разрешение
Z-состояния

1 XRS

Внутренняя
привязка
к логическому
0 или 1

Ножка
процессора

Рис. 5.3. Устройство битового порта ввода/вывода общего назначения

Input
to Qual

1 1 0 0 0 0 0 0 0 1 0 0 0 1 1 1 1 1 1 1 1 1

Sampling Window QUALPRD

Input
from Qual

Рис. 5.4. Принцип цифровой фильтрации входных дискретных сигналов

заданной пользователем частоте — частоте квалификации (оценки) входного


сигнала. Выходной сигнал квалификатора Output from Qual устанавлива-
ется только по шести одинаковым входным выборкам подряд, в против-
ном случае он не меняется (девиация сигнала рассматривается как помеха).
Регистры управления «квалификаторами» GPxQUAL входных сигналов поз-
воляют для всех 16 линий порта x одновременно задать нужный период
выборки QUALPRD входного сигнала — от 1 до 510 тактов ЦПУ.
128
Для каждого из портов A, B, … предусмотрены еще несколько регистров,
обеспечивающих удобный ввод или вывод данных:
GPxDAT — регистры данных для считывания входной информации из
порта после цифровой фильтрации, а также для вывода данных в те битовые
порты, которые сконфигурированы на вывод;
GPxSET — регистры установки. Запись 1 устанавливает высокий потен-
циал на выходе соответствующего порта. Работают только на запись;
GPxCLEAR — регистры сброса. Запись 1 устанавливает низкий потен-
циал на выходе соответствующего. Работают только на запись;
GPxTOGGLE — регистры переключения состояния. Запись 1 переклю-
чает потенциал на выходе соответствующего порта на противоположный.
Работают только на запись.

Практическая работа

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


который описывает структуру регистрового файла, обслуживающего диск-
ретные порты ввода/вывода.
2. Далее будем учиться управлять светодиодами, установленными на оце-
ночной плате. Вернитесь к гл. 1 и определите, как подключены светодиоды?
К каким конкретно выводам микроконтроллера?
3. Убедитесь, что 14-й бит порта F GPIOF14 описан как предназначен-
ный для реализации специальной функции — прямого управления битовым
портом по состоянию флага XF процессора, а также может работать в качес-
тве порта ввода/вывода общего назначения.

Управление светодиодным индикатором

На отладочной плате eZdspTM28 установлены два светодиода. Один из


них через ограничивающее ток сопротивление подключен к шине питания и
сигнализирует о наличии питания (программно не управляемый). Другой
светодиод через буферный усилитель подключен к битовому выходу XF мик-
роконтроллера (порт GPIOF14).
Управлять светодиодом можно двумя способами:
1) сконфигурировать выход GPIOF14 как порт вывода общего назначе-
ния. Настроить регистр направления передачи данных на вывод — записать
«1» в 14-й разряд регистра GPFDIR. Установить 14-й разряд регистра
GPFDAT в «1», что соответствует включению светодиода, или в «0», что
соответствует его выключению;
2) сконфигурировать выход как XF (спецфункция). Установить статусный
бит XF в «1», что соответствует включению светодиода, или сбросить XF в
«0», что соответствует выключению светодиода. Статусный бит XF устанав-
ливается ассемблерной командой SETC XF, а сбрасывается командой
CLRC XF.
129
Практическая работа

1. Откройте файл main.c и изучите его структуру. Программа будет


написана внутри бесконечного цикла while(1) в функции main. В окне
исходной программы на языке Си введите строку Gpio и нажмите кла-
вишу Tab. Появится подсказка доступных объектов, как показано на
рис. 5.5, а.
2. Выберите GpioMuxRegs и поставьте точку. Появится список доступ-
ных регистров (см. рис. 5.5, б). Нас интересует регистр порта F —
GPFMUX. Нажмите еще раз точку, и Вам будет предложен список полей
регистра — bit и all. Если выберете all, то можно будет задать значение
регистра целиком, если — bit, то будет предоставлен выбор битовых полей
этого регистра. Выберите bit.XF_GPIOF14 и приравняйте его значение к
«1» — управление будет осуществляться по флагу XF (задана специальная
функция). Таким образом можно работать со всей периферией, не помня
названий регистров. Использование технологии CODE MAESTRO значи-
тельно упростит работу.
Важнейшие регистры процессора, изменение состояния которых воз-
можно только при инициализации, а случайная запись в них должна быть
полностью исключена, являются защищенными от непреднамеренной
записи. К таким регистрам относятся и регистры мультиплексоров портов
ввода/вывода. Для того чтобы снять защиту от записи, используется макрос
EALLOW, который расширяется одноименной ассемблерной командой, а
для того что поставить ее после выполнения процедуры инициализации —
макрос EDIS (лист. 5.9).

а) б)

Рис. 5.5. Code Maestro помогает набирать программу

130
Лист. 5.9. Пример программы управления светодиодом
// бесконечный цикл программы
while(1)
{
// введите ваш код здесь
l_counter++;
//разрешаем запись в регистр
EALLOW;
//используем вывод XF как связанный с периферией
GpioMuxRegs.GPFMUX.bit.XF_GPIOF14 = 1;
// запрещаем запись в регистр
EDIS;
// вставка асемблерной команды — установить xf
asm(" setc xf");
// вставка асемблерной команды — сбросить xf
asm(" clrc xf");
}

Как делать ассемблерные вставки в программе на языке Си, мы уже рас-


сматривали. Для этой цели используется зарезервированное слово asm,
после которого в круглых скобках и двойных кавычках можно указывать
любую ассемблерную команду. В примере, приведенном в лист. 5.9, приме-
няли команду установить флаг XF и вслед за ней команду сбросить флаг XF.
Конечно, при выполнении программы в реальном времени переключения
будут производиться с очень высокой скоростью. Однако если будем выпол-
нять программу по шагам, то увидим изменения состояния светодиодного
индикатора.

Практическая работа
1. Откомпилируйте программу и загрузите ее в микроконтроллер. Выпол-
ните команду Debug Go Main для перехода в начало функции main. Выпол-
няйте программу по шагам, используя команду Step Into (клавиша F8).
Наблюдайте за состоянием светодиода.
2. Вы уже заметили, что процедура инициализации порта ввода/вывода
оказалась внутри цикла while и выполняется многократно. Это не ошибка,
но «плохой стиль программирования» — инициализация должна выпол-
няться только один раз. Модернизируйте программу — выполните инициа-
лизацию порта до начала цикла. Исследуйте работу программы. Убедитесь,
что ничего не изменилось.
3. Остановите программу. Откройте окно статусных регистров процес-
сора и меняйте состояние флага XF в этом окне. Объясните, почему при
неработающей программе состояние выхода порта изменяется?
4. Теперь более сложное задание. Настройте линию микроконтроллера
GPIOF14 в режим порта вывода общего назначения с помощью регистра
131
управления мультиплексором GPFMUX. Задайте направление передачи дан-
ных с помощью регистра GPFDIR. Попробуйте управлять выходом XF через
регистры: GPFDAT; GPFSET, GPFCLEAR; GPFTOGGLE. Составьте три
различные программы и выполните их отладку.

Контрольные вопросы
1. Как установить скважность включения светодиода в вашей программе 25 и 12,5 %?
2. Можно ли регулировать яркость свечения светодиода? Напишите программу, кото-
рая меняла бы значение яркости горения светодиода в диапазоне от 0 до 100 % (задается
отдельной переменной) по усмотрению пользователя.

5.6. ОСНОВЫ РАБОТЫ С ТАЙМЕРАМИ ОБЩЕГО НАЗНАЧЕНИЯ

Менеджер событий является одним из важнейших периферийных уст-


ройств микроконтроллера и обеспечивает целый ряд функций, которые
используются в системах цифрового управления двигателями и системами
вторичного питания: от прямого управления отдельным силовым ключом в
режиме широтно-импульсной модуляции (ШИМ) до согласованного управ-
ления всеми ключами инвертора или активного выпрямителя. В рамках дан-
ной главы рассмотрим основы управления таймерами общего назначения
GP Timer, входящими в состав менеджеров событий. Таймеры 1 и 2 входят в
менеджер событий EVA, а таймеры 3 и 4 — в менеджер событий EVB. Тай-
меры могут работать в нескольких режимах, в том числе: модуля сравнения
процессора событий и ШИМ-генератора.
Они могут программироваться для выполнения операций с тактирова-
нием как от внутреннего сигнала тактирования высокоскоростных перифе-
рийных устройств (HSPCLK), так и от внешнего сигнала. В последнем слу-
чае таймер может выполнять также функцию счетчика внешних событий.
С каждым таймером связаны несколько регистров: период таймера —
TxPR; счетчик таймера — TxCNT; сравнение — TxCMPR; управление
режимами работы — TxCON, где x — номер таймера. Для того чтобы вклю-
чить таймер, необходимо произвести следующий минимум операций с его
регистрами:
установить режим счета таймера: вверх (нереверсивный или суммирую-
щий счетчик); вверх/вниз (реверсивный счетчик);
установить значение периода таймера в регистре TxPR;
установить флаги, разрешающие таймеру работать независимо от режима
работы процессора и режима эмуляции (в автономном режиме);
установить флаг разрешения работы таймера.
Приведенный перечень операций касается работы только счетчика тай-
мера. Для выполнения на базе таймера функции сравнения или генерации
ШИМ-сигналов потребуются дополнительные действия. Наиболее часто,
например, для генерации центрированных ШИМ-сигналов, используется
режим счета таймера вверх/вниз, когда состояние счетчика таймера увеличи-
вается на 1 с каждым импульсом входной тактовой частоты, пока не достиг-
132
нет заданного значения периода таймера. Затем направление счета автомати-
чески меняется на обратное. При достижении счетчиком таймера нулевого
состояния период счета заканчивается и направление счета вновь меняется
на противоположное (рис. 5.6).
Видно, что таймер запускается программно с помощью установки бита
разрешения работы в регистре управления TxCON.TENABLE и меняет
свое состояние по переднему фронту каждого импульса тактирования
HSPCLK. Период таймера в режиме реверсивного счета пропорционален
двукратному значению в регистре периода:
TTIMER = 2æTxPRæTHSPCLK, (5.1)
где THSPCLK — период внутреннего сигнала тактирования высокоскорост-
ных периферийных устройств.
Важнейшим преимуществом структурного подхода к описанию регистров
периферийных устройств является возможность отображения в окне наблю-
даемых переменных текущего содержимого регистров и отдельных битовых
полей, а также возможность изменения состояния регистров и битовых
полей непосредственно в окне наблюдаемых переменных. Таким образом,
находясь в среде CCS, можно последовательно проинициализировать и
включить какое-либо периферийное устройство, а затем наблюдать за его
работой, фиксируя изменение состояния периферийных регистров.
Рассмотрим последовательность действий, которую необходимо выпол-
нить для включения таймера 1. Прежде всего нужно разрешить тактирова-
ние первого менеджера событий EVA от блока тактирования высокоскорост-
ных периферийных устройств микроконтроллера HSPCLK. Для этого
можно:
отобразить в окне Watch Window (рис. 5.7) содержимое структуры
SysCtrlRegs — регистров управления системой;
выбрать регистр управления тактированием периферийных устройств
PCLKCR;
открыть битовые поля этого регистра;

Период счета таймера


TxPR = 4

4
3 3 3
2 2 2
1 1 1
TxCNT 0 0

TxCON.TENABLE

HSPCLK

Рис. 5.6. Таймер общего назначения в режиме счета вверх/вниз

133
Рис. 5.7. Включение тактирования первого менеджера событий

установить бит разрешения тактирования менеджера событий EVA —


EVAENCLK. Тем самым на первый менеджер событий, в том числе и на
таймеры, входящие в его состав, будет подана высокая тактовая частота.
Обратите внимание, что аналогичным образом можно включить тактиро-
вание любого другого периферийного устройства: АЦП, последовательных
периферийных и коммуникационных портов и т.п. Конкретное значение так-
товой частоты HSCLK будет зависеть от коэффициента деления системной
тактовой частоты SYSCLK, установленного в регистре управления делите-
лем HISPCP. По умолчанию, после включения процессора или сброса пита-
ния устанавливается коэффициент деления, равный 2.
С менеджером событий EVA связана структура периферийных регистров
EvaRegs, приведенная на рис. 5.8.
Содержимое регистра T1CNT отображает текущее состояние счетчика
таймера 1. В регистр T1PR должно быть записано значение требуемого
периода таймера (5.1). Регистр управления таймером T1CON имеет
несколько битовых полей, в частности поле TMODE, задающее один из
четырех возможных режимов работы таймера:
00 Останов и фиксация состояния таймера
01 Непрерывный счет вверх/вниз (реверсивный счетчик)
10 Непрерывный счет вверх (нереверсивный счетчик)
11 Направленный счет с внешним заданием направления счета

134
Рис. 5.8. Задание режима работы таймера 1

Битовые флаги FREE и SOFT регистра T1CON определяют режим


работы таймера в процессе отладки с использованием внутрисхемного эму-
лятора (когда процесс выполнения программы прерывается, например, для
просмотра текущего содержимого регистров):
00 Немедленный останов таймера
01 Останов таймера сразу после завершения текущего периода
10 Продолжение работы таймера (автономный режим)
11 Продолжение работы таймера (автономный режим)

Битовый флаг TENABLE разрешает работу таймера. Его можно рассмат-


ривать как флаг программного запуска таймера. Разумеется, для правильного
программирования любого периферийного устройства необходимо предва-
рительно изучить его устройство — понять назначение каждого регистра и
каждого битового поля. Так, в нашем случае битовое поле TPS отвечает за
коэффициент предварительного деления тактовой частоты, поступающей
на вход таймера. При значении 000 коэффициент деления равен 1, а при зна-
чении 111 — 128. При каждом инкрементировании кода в этом поле коэффи-
циент деления удваивается.
Поле TCLKS10 регистра T1CON определяет источник тактовых импульсов:
00 — блок тактирования высокоскоростных устройств HSPCLK; 01 — вне-
шний сигнал тактирования, поступающий на вход TCLKIN; 10 — резерв;
11 — сигнал тактирования с модуля «квадратурного декодирования» QEP.
135
Остальные битовые поля служат для управления таймером в режиме
канала сравнения и генерации ШИМ-сигналов (в этой работе не использу-
ются).

5.7. ГРАФИЧЕСКИЕ ОКНА

Среда Code Composer Studio позволяет не только наблюдать текущие зна-


чения переменных в окнах наблюдаемых переменных, но и выводить на
экран различные типы графиков, например, в функции времени. Для этой
цели предназначена команда меню View → Graph Time/Frequency (Про-
смотр → График → Время/Частота). При выполнении этой команды CCS
выведет на экран диалоговое окно графика, в котором Вы должны сделать
необходимые настройки, — рис. 5.9. Важнейшей из них является стартовый
адрес переменной Start Address. Он задается так же, как и в окнах дампов
памяти, — с помощью операции получения адреса «&» от символического
имени переменной. На рис. 5.8 показано, как вывести на экран график теку-
щего содержимого счетчика таймера 1.
Как уже знаем, программу можно выполнить в режиме прогона, в пошаго-
вом режиме или в режиме с точками останова. Имеется еще один очень важ-
ный режим, заметно облегчающий отладку, — режим Animate (Анимация).

Рис. 5.9. Окно настройки графика

136
Его можно запустить с помощью кнопки . В этом режиме программа
будет выполняться в режиме прогона до ближайшей точки останова. В
момент останова значения всех наблюдаемых переменных (в том числе в
графических окнах) будут считаны из микроконтроллера в компьютер и
экран среды CCS обновится. После этого выполнение программы автомати-
чески продолжится в режиме прогона до новой точки останова. Таким образом,
обновление информации производится от одной точки останова к другой.

Практическая работа
1. Сделайте необходимые установки для инициализации Таймера 1 и для
его программного запуска. Установите значение в регистре периода 50 000 и
максимальный коэффициент деления входной тактовой частоты. Рассчи-
тайте значение периода таймера в секундах.
2. Наблюдайте за изменением состояния таймера в окне Watch. Соответ-
ствует ли период таймера расчетному периоду?
3. Установите точку останова в программе main с бесконечным циклом и
запустите ее на исполнение с помощью кнопки Animate (Анимация).
Откройте графическое окно для отображения текущего состояния счетчика
таймера в функции времени. Поэкспериментируйте с настройками.
4. Перенастройте таймер 1 для работы с тем же периодом, но в режиме
нереверсивного счета. Объясните полученную осциллограмму.

5.8. ВВЕДЕНИЕ В СИСТЕМУ ПРЕРЫВАНИЙ


МИКРОКОНТРОЛЛЕРОВ ‘C28xx

Встроенный контроллер прерываний


Микроконтроллеры оптимизированы для работы с большим числом как
внешних, так и внутренних прерываний, поступающих от встроенных пери-
ферийных устройств. На уровне контроллера прерываний центрального
процессора поддерживаются один немаскируемый запрос прерывания NMI
и 16 маскируемых запросов прерывания (INT1—INT14, RTOSINT и
DLOGINT). Двенадцать линий запросов прерываний центрального процес-
сора INT1 — INT12 предназначены для приема запросов прерываний от
встроенных периферийных устройств (рис. 5.10). Если запрос прерывания
поступает по одной из этих линий, то соответствующий флаг в регистре пре-
рываний, ждущих обслуживания IFR, устанавливается. Если обработка
соответствующего прерывания центрального процессора разрешена в регис-
тре разрешения прерываний IER, а также разрешена обработка всех маски-
руемых прерываний (флаг INTM сброшен), то запрос прерывания начинает
обрабатываться.
Все периферийные и внешние запросы прерываний объединяются в
группы по восемь запросов с помощью блока расширения периферийных
прерываний (PIE — Peripheral Interrupt Expansion) и мультиплицируются
137
IFR(12:1) IER(12:1) INTM

INT1
INT2

1
MUX CPU
0

INT11
INT12 Глобальное
(Флаг) (Разрешение) разрешение

INTx.1
INTx.2
INTx.3
INTx.4 Периферийные или
INTx
MUX INTx.5 внешние прерывания

INTx.6
INTx.7
INTx.8
(Разрешение) (Флаг)
PIEIER(8:1) PIEIFR(8:1)

Рис. 5.10. Мультиплицирование прерываний блоком PIE

на одну из 12 линий запросов прерываний центрального процессора. Таким


образом, общее число запросов, обрабатываемых контроллером прерываний,
достигает девяносто шести.
Каждое периферийное прерывание выставляет флаг ждущего прерывания
PIEIFRx.y по определенному событию в этом периферийном устройстве,
например, по завершению периода таймера. Если соответствующее перифе-
рийное прерывание разрешено битом PIEIERx.y, то запрос прерывания посту-
пает на одну из линий INTx.
В табл. 5.3 показано распределение запросов прерываний от различных
устройств по группам. Например, все четыре запроса прерывания от таймера 1
входят в одну группу и мультиплицируются на линию INT2 запроса преры-
вания центрального процессора. Запрос прерывания, имеющий меньший
номер, имеет более высокий приоритет обслуживания. Так, любое прерыва-
ние в группе INT1 имеет более высокий приоритет по сравнению с любым
прерыванием в других группах, а прерывание PDPINTA — наивысший при-
оритет внутри группы INT1.
Уникальной особенностью контроллера прерываний ‘C28xx является воз-
можность задания одной таблицы векторов прерываний (PIE vector table)
для всех возможных периферийных прерываний. У каждого из 96 прерыва-
ний имеется свой собственный 32-разрядный вектор прерывания (началь-
ный адрес размещения процедуры обслуживания прерывания — interrupt
service routine ISR), который может модифицироваться пользователем, так
как находится в специально выделенной области ОЗУ (см. гл. 1). Эта опера-
ция называется инициализацией таблицы векторов прерываний. Допуска-
138
ется динамическое переконфигурирование таблицы векторов прерываний в
процессе работы программы.
Контроллер прерываний идентифицирует запрос периферийного прерыва-
ния и автоматически передает управление по соответствующему вектору —
адресу процедуры обслуживания прерывания (выполняется косвенная пере-
дача управления в процедуру). При этом в стеке автоматически сохраня-
ется контекст (содержимое важнейших регистров процессора). На опера-
цию перехода и сохранения контекста тратится всего девять циклов ЦП.
Никакой дополнительной программной идентификации источника запроса
прерывания не требуется. При выходе из процедуры обслуживания прерыва-
ния контекст автоматически восстанавливается. Таким образом, одной из
важнейших характеристик контроллера прерываний ‘C28xx являются его
предельное быстродействие и встроенная автоматизация входа и выхода из
подпрограмм обслуживания прерывания.

Табл и ц а 5.3
Таблица прерываний

Пре- Прерывания блока расширения периферийных прерываний


рыва-
ния
про-
цес- INTx.1 INTx.2 INTx.3 INTx.4 INTx.5 INTx.6 INTx.7 INTx.8
сора

INT1 PDPINTA PDPINTB Резерв XINT1 XINT2 ADCINT TINT0 WAKEINT


(EV-A) (EV-B) (ADC) (TIMER0) (LPM/WD)

INT2 CMP1INT CMP2INT CMP3INT T1PINT T1CINT T1UFINT T1OFINT Резерв


(EV-A) (EV-A) (EV-A) (EV-A) (EV-A) (EV-A) (EV-A)

INT3 T2PINT T2CINT T2UFINT T2OFINT CAPINT1 CAPINT2 CAPINT3 Резерв


(EV-A) (EV-A) (EV-A) (EV-A) (EV-A) (EV-A) (EV-A)

INT4 CMP4INT CMP5INT CMP6INT T3PINT T3CINT T3UFINT T3OFINT Резерв


(EV-B) (EV-B) (EV-B) (EV-B) (EV-B) (EV-B) (EV-B)

INT5 T4PINT T4CINT T4UFINT T4OFINT CAPINT4 CAPINT5 CAPINT6 Резерв


(EV-B) (EV-B) (EV-B) (EV-B) (EV-B) (EV-B) (EV-B)

INT6 SPIRXINTA SPITXINTA Резерв Резерв MRINT MXINT Резерв Резерв


(SPI) (SPI) (McBSP) (McBSP)

INT7 Резерв Резерв Резерв Резерв Резерв Резерв Резерв Резерв

INT8 Резерв Резерв Резерв Резерв Резерв Резерв Резерв Резерв

INT9 SCIRXINTA SCITXINTA SCIRXINTB SCITXINTB ECAN0INT ECAN1INT Резерв Резерв


(SCI-A) (SCI-A) (SCI-B) (SCI-B) (CAN) (CAN)

INT10 Резерв Резерв Резерв Резерв Резерв Резерв Резерв Резерв

INT11 Резерв Резерв Резерв Резерв Резерв Резерв Резерв Резерв

INT12 Резерв Резерв Резерв Резерв Резерв Резерв Резерв Резерв

139
Порядок обслуживания прерываний
1. Если какое-либо событие в периферийном устройстве генерирует
запрос прерывания, то в одном из регистров этого устройства выставляется
флаг запроса прерывания interrupt flag (IF). Это прерывание может быть
разрешено или запрещено непосредственно внутри периферийного устрой-
ства с помощью соответствующего бита разрешения прерывания IE. Для
каждого запроса прерывания (каждого события) существуют свои флаги IF и
IE. Например, для таймера можно установить разрешение генерации
запроса прерывания по периоду и запретить генерацию запроса прерывания
по переполнению вверх и вниз. Если событие произошло и генерация
запроса прерывания разрешена, то периферийное устройство выставляет
запрос прерывания в контроллер периферийных прерываний PIE (см.
рис. 5.10).
2. Если прерывание запрещено на уровне периферийного устройства, то
флаг запроса прерывания остается установленным до тех пор, пока не будет
очищен программным путем. Если после того, как запрос прерывания в
периферийном устройстве возник (флаг IF установился), разрешить это пре-
рывание, то будет сформирован запрос прерывания в контроллер периферий-
ных прерываний PIE.
3. Флаг запроса прерывания IF, выставленный в регистре соответствую-
щего периферийного устройства, должен быть очищен вручную (програм-
мным путем) в процедуре обслуживания прерывания.
4. Для каждого источника мультиплицированного прерывания в каждой
из 12 групп имеется соответствующий флаг ждущего запроса прерывания
(PIEIFRx.y) и флаг разрешения этого периферийного прерывания
(PIEIERx.y). Дополнительно для каждой группы периферийных прерыва-
ний (INT1 — INT12) имеется свой собственный флаг разрешения группы
прерываний PIEACKx. Активное значение этого флага «логический 0». В
конце процедуры обслуживания периферийного прерывания флаг PIEACKx
должен программно очищаться, чтобы разрешить последующую обработку
прерываний группы.
5. Если на вход контроллера периферийных прерываний поступает запрос
прерывания, то соответствующий флаг «ждущего» запроса прерывания
(PIEIFRx.y) устанавливается. Если соответствующее периферийное преры-
вание разрешено и бит (PIEIERx.y) установлен, то производится тестирова-
ние бита разрешения прерываний для всей группы периферийных прерыва-
ний PIEACKx. Активное значение этого флага низкое. При разрешении
прерываний от группы PIEACKx = 0 контроллер периферийных прерываний
посылает запрос прерывания центральному процессору по линии INTx.
Если PIEACKx = 1 (нет разрешения группы), то контроллер прерываний
будет ждать до тех пор, пока программа не сбросит этот флаг, после чего
сформирует запрос прерывания ЦПУ.
6. Если одновременно несколько периферийных прерываний в группе
ждут обслуживания и для каждого из них установлены флаги «ждущего»
прерывания PIEIFRx.y и разрешения прерывания PIEIERx.y, то будет
140
обслуживаться прерывание с наивысшим приоритетом, т.е. имеющее мень-
ший номер в табл. 5.3. Так, для 1-й группы наивысшим приоритетом обла-
дает прерывание PDPINTA (внешнее по аварии в силовых ключах). Поэтому
если одновременно с ним ждет обслуживания прерывание от АЦП
(ADCINT), то будет обслуживаться прерывание PDPINTA как имеющее
более высокий приоритет.
7. Как только запрос прерывания выставляется на одну из линий запроса
прерывания центрального процессора INTx, то разрешение прерываний от
группы периферийных прерываний, выставившей запрос, аппаратно снима-
ется: PIEACKx = 1. Прием запросов прерываний от этой группы блокиру-
ется.
8. Если запрос прерывания поступает на одну из линий запроса прерыва-
ний центрального процессора INTx, то выставляется соответствующий флаг
«ждущего» запроса прерывания IFR. Это прерывание будет обрабатываться
только в том случае, когда прерывание по соответствующей линии INTx раз-
решено в регистре разрешения прерываний центрального процессора IER, а
также при условии, что флаг глобального маскирования прерываний сбро-
шен INTM = 0.
9. Перед обработкой прерывания автоматически выполняются следую-
щие действия:
• очищаются биты IFRx (флаг ждущего глобального прерывания) и IERx
(флаг разрешения глобального прерывания);
• устанавливается флаг INTM (маскирование всеx прерываний до
момента завершения обслуживания текущего прерывания);
• очищается флаг EALLOW (разрешается доступ по записи к защищен-
ным регистрам);
• «смывается» конвейер команд;
• адрес возврата в фоновую программу сохраняется в стеке;
• выполняется автоматическое сохранение контекста в стеке.
10. Автоматически извлекается вектор нужной процедуры обслужива-
ния прерывания из таблицы векторов прерываний на основе информации из
контроллера периферийных прерываний о запросе, вызвавшем прерывание
(PIEIERx и PIEIFRx). Флаг PIEIFRx автоматически аппаратно очищается.
Выполняется косвенный переход на начало процедуры обслуживания преры-
вания.
11. Выполняются все необходимые действия по обслуживанию прерыва-
ния.
12. Перед выходом из процедуры обслуживания прерывания:
• программно очищается флаг ждущего прерывания IF в соответствую-
щем регистре периферийного устройства (см. п. 1);
• разрешаются периферийные прерывания от группы, вызвавшей преры-
вание, с помощью программной очистки флага PIEACKx (см. п. 7);
• переразрешаются все маскируемые прерывания посредством сброса
флага INTM.
13. Выполняется возврат из подпрограммы в фоновую программу, в про-
цессе которого автоматически восстанавливается контекст.
141
Пример программирования прерывания от таймера
Предположим, что таймер 1 должен генерировать периодические преры-
вания через определенный интервал времени, т.е. выполнять роль интер-
вального таймера. Эта задача возникает при отсчете определенных времен-
ных интервалов (электронные реле времени), при реализации операционных
систем реального времени, при задании нужного интервала квантования ана-
логовых сигналов и т.п.
Один из возможных вариантов программы, реализующей эту функцию,
представлен в лист. 5.11. Листинг содержит достаточно подробные коммен-
тарии, что позволит понять механизм использования шаблонов для описания
таблицы векторов прерываний, последовательность необходимых действий
при инициализации системы прерываний, в процедуре обслуживания преры-
вания и в так называемой фоновой программе. Мы будем использовать пре-
рывание по отрицательному переполнению таймера T1UFINT, которое воз-
никает в момент обнуления счетчика таймера, т.е. в конце каждого периода.
Счетчик числа вызовов обработчика прерывания инкрементируется при каж-
дом вызове.

Замечание
В процессе выхода из процедуры обслуживания прерывания и автомати-
ческого восстановления контекста будет восстановлено и содержимое ста-
тусных регистров. Поэтому сбрасывать флаг INTM программно не обяза-
тельно.

Практическая работа
1. Удалите файл main.c из проекта и добавьте файл main_a.c. Для этого
перейдите в меню Project → Add Files to Project… и в появившемся окне из
папки lab5\src выберите файл main_a.c. Откройте его в среде Code
Composer.
2. Откомпилируйте программу и загрузите ее в память микроконтроллера.
Установите точку останова, как показано на рис. 5.11. Напомним, что это
можно сделать, дважды щелкнув мышью на сером фоне слева от нужной
строки.

Рис. 5.11. Установка точки останова в программе

142
3. Добавьте переменную isr_ticker в окно просмотра. Для этого перемес-
тите курсор на ее имя и нажмите правую кнопку мыши. Во всплывающем
контекстно-зависимом меню выберите Add to Watch Window.
4. Запустите программу в режиме Animate и последите за изменением
значения переменной.
5. Изучите содержимое файла main_a.c. Соответствует ли эта программа
описанному в теоретической части алгоритму обслуживания прерываний?
Попробуйте понять, как работают готовые функции, поставляемые Texas
Instruments, такие как InitSysCtrl(); InitPieCtrl(); InitPieVectTable().
6. Измените процедуру обслуживания прерывания от таймера 1, напри-
мер исключите команду разрешения обработки всех маскируемых прерыва-
ний перед выходом из процедуры. Объясните последствия.

Контрольные вопросы
1. Какие прерывания называются маскируемыми?
2. Что такое прерывание центрального процессора и периферийное прерывание?
Зачем нужен модуль расширения периферийных прерываний?
3. Можно ли считать, что контроллер прерываний имеет фиксированный приоритет
обслуживания прерываний? Чем определяется этот приоритет?
4. Какие флаги аппаратно сбрасываются при переходе к процедуре обслуживания
прерывания? Зачем это делается?
5. Можно ли внутри процедуры обслуживания текущего прерывания разрешить
обслуживание другого (более приоритетного)? Что нужно сделать для этого?

Практическая работа
1. Рассчитайте значение регистра периода таймера 1 для получения час-
тоты прерываний 5 кГц. Модернизируйте программу, чтобы таймер генери-
ровал второе прерывание по достижению в счетчике таймера значения, рав-
ного значению в регистре периода.
2. Дополните программу второй процедурой обслуживания прерываний
по периоду таймера. Установите в обработчике процедуры обслуживания
прерывания персональный счетчик ее вызовов. Убедитесь в том, что оба
обработчика прерываний вызываются с одинаковой частотой.
3. Можно ли получить частоту генерации прерываний 1 кГц? Это будет
соответствовать «кванту» времени в 1 мс. Модернизируйте программу для
отсчета любой произвольной выдержки времени, заданной в числе [мс] отдель-
ной переменной. После отсчета выдержки времени состояние линии XF мик-
роконтроллера должно изменяться на противоположное и «реле времени»
должно запускаться вновь. Отладьте эту программу в режиме с точками
останова.

5.9. ОТЛАДКА ПРОГРАММ В РЕАЛЬНОМ ВРЕМЕНИ


Особенностью процессоров серии C28xx является возможность отладки
программы без прерывания процесса ее выполнения в реальном времени. Эта
возможность особенно полезна, когда необходимо отслеживать изменение
143
переменных, менять их значения и при
этом остановка программы недопус-
тима. Так, при работе ШИМ-генера-
тора, управляющего силовыми клю-
чами инвертора, остановка программы
по событию Breakpoint (точка оста-
нова) приведет к фиксации текущего
состояния ключей инвертора, что
может вызвать аварию, например пре-
вышение максимально допустимого
тока в двигателе.
Работа в реальном времени позво-
ляет существенно упростить и уско-
рить процесс отладки систем встроен-
ного управления оборудованием.
Для перехода в режим реального
времени нужно выбрать в CCS
команду меню Debug → Real Time
Mode (Отладка → Режим реального
времени), приведенную на рис. 5.12.
Появится предупреждение о пере-
ходе в режим реального времени, и о
том, что использование точек оста-
нова запрещено. Нужно убедиться, что
точки останова исключены из про-
Рис. 5.12. Выбор режима реального времени
граммы, и ответить «Да» (рис. 5.13).
Далее можно запустить программу
на выполнение.
На элементах, требующих постоянного наблюдения (например, в графи-
ческих окнах), необходимо выбрать в контекстном меню режим Continuous
Refresh (Непрерывное обновление), представленный на рис. 5.14.
Для регулирования скорости обновления данных существует команда
CCS View → Real-Time Refresh Options (Просмотр → Опции обновления

Рис. 5.13. Предупреждение системы об ограничении возможностей отладки программы в


режиме реального времени

144
Рис. 5.15. Установка опций обновления в
режиме реального времени

Рис. 5.14. Выбор режима непрерывного


обновления информации

в режиме реального времени), изображенная на рис. 5.15. Здесь Вы можете


задать интервал обновления данных в количестве [мс] (Refresh every __ ms),
а также установить галочку Global Continuous Refresh (Непрерывное
обновление всех глобальных переменных).
Таким образом, в каждом микроконтроллере производителем заложена
как бы операционная система реального времени, которая работает по пре-
рываниям от отладочного JTAG-интерфейса. Тем самым пользователь полу-
чает уникальную возможность со стороны компьютера, подключенного к
контроллеру через JTAG-интерфейс, наблюдать за ходом выполнения про-
граммы, не останавливая ни центральный процессор, ни задействованные в
программе периферийные устройства. Необходимо лишь помнить о том, что
возможности отладочного интерфейса JTAG по скорости доступа к данным
ограничены.
Предупреждение: перед перезагрузкой программы режим реального вре-
мени рекомендуется отключать.

Практическая работа

1. Включите режим реального времени, как описано выше. Запустите


первую программу без точек останова с разрешением постоянного обновле-
ния содержимого окна наблюдения Watch Window. Объясните отличия в
выполнении программы.
2. Исследуйте в реальном времени программу, изменяющую состояние
светодиода через каждую секунду (см. п. 3 предыдущего задания). Соответ-
ствуют ли временные интервалы расчетным значениям?
145
Контрольные вопросы
1. Что такое режим реального времени выполнения?
2. Почему в этом режиме при отладке программы необходимо отказаться от точек
останова?
3. Можно ли в режиме реального времени наблюдать за состоянием счетчика тай-
мера? Как быть, если дискретность обновления данных через JTAG в реальном времени
окажется недостаточной?

146
Глава 6
РАБОТА С БИБЛИОТЕКОЙ IQmath

6.1. ПРЕДСТАВЛЕНИЕ ЧИСЕЛ В МИКРОКОНТРОЛЛЕРЕ

Двоичные числа
Двоичная система счисления используется в микропроцессорах, так как
каждый ее значащий разряд имеет всего два возможных состояния, что легко
реализуется на транзисторной логике. Двоичная система, как и десятичная,
является позиционной системой счисления. Каждый разряд имеет вес, определя-
ющийся его позицией в числе. Основание двоичной системы счисления — 2,
поэтому весовые коэффициенты являются степенями двойки. Для целой
части числа (слева от двоичной точки) располагаются весовые коэффици-
енты 1, 2, 4, 8 и т.д., а справа, в области дробной части числа, 0,5; 0,25; 0,125
и т.д.
Для перевода числа из двоичной системы в десятичную необходимо
умножить каждый разряд двоичного числа на соответствующий ему весовой
коэффициент и сложить произведения:
011012 = 0æ16 + 1æ8 + 1æ4 + 0æ2 + 1æ1 = 8 + 4 + 1 = 13. (6.1)
Аналогичным способом производится перевод дробных и вещественных
чисел:
111,012 = 1æ4 + 1æ2 + 1æ1 + 0æ0,5 + 1æ0,25 = 7,25. (6.2)

Дополнительный код
Числа в процессорной технике могут быть как беззнаковыми, так и знако-
выми. Для представления знаковых чисел используется дополнительный код:
такое представление отрицательного числа, когда его сумма с соответствую-
щим положительным числом дает нуль в рамках принятого формата числа
(дополняет положительное число до нуля). При этом старший разряд числа
рассматривается как знаковый: 0 — число положительное, 1 — отрицательное.
При получении десятичного эквивалента числа в дополнительном коде (–1)
умножается на двоичное значение старшего (знакового) разряда и его вес:
011012 = –0æ16 + 1æ8 + 1æ4 + 0æ2 + 1æ1 = 13;
111,012 = –1æ4 + 1æ2 + 1æ1 + 0æ0,5 + 1æ0,25 = –0,75.
Выражение (6.1) не изменилось, так как старший знаковый разряд ока-
зался равен 0.
147
Получение дополнительного кода
Дополнительный код числа можно получить, воспользовавшись одним из
приведенных ниже способов:
• вычесть число из нуля (полученный результат той же разрядности
будет дополнением до нуля);
• инвертировать число и прибавить к нему единицу;
• оставить все нули с правой стороны числа неизменными, оставить
неизменной первую единицу, остальные разряды проинвертировать.
Рассмотрим примеры перевода чисел, приведенные на рис. 6.1.
Независимо от способа вычисления получается одинаковый результат.
Наиболее простым и не требующим расчетов является третий способ. В про-
граммных реализациях используется первый или второй способ как подде-
рживаемые системой команд процессора.

Расширение знакового разряда


при преобразовании формата
В микроконтроллерах семейства ‘C28xx АЛУ и большинство регистров
являются 32-разрядными, в то время как память данных 16-разрядная. При
этом поддерживаются как 16-разрядные, так и 32-разрядные вычисления.
Для того чтобы загрузить знаковое 16-разрядное число в 32-разрядный
регистр, требуется провести специальное преобразование формата, которое
называют расширением знакового разряда. При работе с числами без знака
преобразование выполняется простым расширением числа нулями.
В целях уменьшения объема иллюстративного материала для примера
рассмотрим 4-разрядные числа и 8-разрядные регистры.
В первом примере, показанном на рис. 6.2, выполняется загрузка в регистр
беззнакового числа 12. При этом все старшие разряды заполняются нулями.

0 0 0 0 0 0 0 0, 0 0
– –
0 1 1 0 1 1 1 1, 0 1
1 0 0 1 1 0 0 0, 1 1

0 1 1 0 1 1 1 1, 0 1

Инверсия Инверсия
1 0 0 1 0 0 0 0, 1 0
+ +
0 0 0 0 1 0 0 0, 0 1
1 0 0 1 1 0 0 0, 1 1

0 1 1 0 1 1 1 1, 0 1
Копирование до первой Копирование до первой
Инверсия единицы включительно Инверсия единицы включительно
1 0 0 1 1 0 0 0, 1 1

Рис. 6.1. Вычисление дополнительного кода числа

148
Данные 1 1 0 0 12

Регистр 0 0 0 0 1 1 0 0 12

Рис. 6.2. Загрузка беззнаковых чисел

Данные 1 1 0 0 = –23 + 22 = –4
Загрузка и расширение
знакового разряда

Регистр 1 1 1 1 1 1 0 0 = –27 + 26 + 25 + 24 + 23 + 22 =
= –128 + 64 + 32 + 16 + 8 + 4 =
= –4

Рис. 6.3. Загрузка знаковых чисел с расширением знакового разряда

Если программист считает, что данные в предыдущем примере представ-


ляют собой число со знаком в дополнительном коде, т.е. 11002 = –4, то для
преобразования этого числа из 4-разрядного в 8-разрядный формат потребу-
ется операция расширения знакового разряда, приведенная на рис. 6.3.
Число загружается без изменений в младшую часть регистра, а в старшую
часть регистра копируется значение знакового разряда.
При программировании на Ассемблере режим расширения знакового раз-
ряда включается установкой флага SXM в статусном регистре процессора
(ST0) с помощью специальной команды Ассемблера SETC SXM (для зада-
ния режима работы со знаковыми числами) и сбрасывается командой CLRC
SXM (для задания режима работы с беззнаковыми числами). После такой
инициализации преобразование 16-разрядных чисел в 32-разрядные произ-
водится автоматически в процессе загрузки числа в аккумулятор или при
выполнении арифметических операций над содержимым 32-разрядного
аккумулятора и 16-разрядной ячейки памяти данных.
На языке Си операция преобразования формата должна быть определена
явно, в противном случае нужное действие выполняется с операндами в
исходном формате и возможно усечение результата (получение недостовер-
ных данных).

Контрольные вопросы
1. Переведите в десятичную систему счисления следующие числа без знака:
110001011,11012; 110010,1101112; 0110,1002; 0,111111112.
2. Переведите в десятичную систему счисления следующие числа со знаком в допол-
нительном коде:
110001011,11012; 110010,1101112; 0110,1002; 1,111111112.
3. Получите тремя возможными способами дополнительный код для следующих чисел:
110001011,11012, 110010,1101112, 0110,1002; 1,111111112.
4. Зависит ли механизм преобразования чисел из 16-разрядного формата в 32-разряд-
ный от того, является ли преобразуемое число целым, дробным или вещественным?
5. Какой диапазон чисел со знаком можно представить в формате 8.24?

149
6.2. ОСОБЕННОСТИ РАБОТЫ С ДВОИЧНЫМИ ЧИСЛАМИ

Проблемы двоичных вычислений


Для иллюстрации проблем, которые могут возникать при выполнении
арифметических операций с двоичными числами, воспользуемся примером
умножения 4-разрядных чисел, приведенным на рис. 6.4.
Предположим, что вычисляется потребляемая электрическая мощность
посредством умножения значения тока (4 А) на значение напряжения (3 В).
Получается мощность, равная 12 Вт. Для анализа выполненной операции
умножения зададимся следующими вопросами:
• что представляют собой два исходных числа и какой ожидается результат?
• как сохранить результат в памяти? Совпадают ли форматы исходных
чисел и формат результата умножения (произведения)?
Допустим, что исходные данные представляют собой 4-разрядные целые
числа со знаком. Диапазон их возможного изменения от –8 до +7. При умно-
жении результат умещается в восьми разрядах. Диапазон возможных значе-
ний результата операции умножения от –56 до +64, диапазон 8-разрядного
выходного формата произведения от –128 до +127 (см. рис. 6.4).
Сохранить результат без потери знаков можно в две 4-разрядные ячейки
памяти. Сохранять только младшую часть нельзя, так как она может не вмес-
тить весь результат умножения. Уже в данном конкретном примере ответ
равен 12, что выходит из диапазона допустимых значений знаковых 4-раз-
рядных чисел, и, будучи сохраненной, младшая часть результата будет в
дальнейших вычислениях восприниматься числом –4, что совершенно недо-
пустимо.
Если, как было предложено, сохранять обе части числа (старшую и млад-
шую) в 4-разрядной памяти, то при дальнейших операциях умножения
результата на какое-либо другое число разрядность ответа будет расти и
будет требоваться еще больше памяти для его хранения и больше операций
для обработки и дальнейших вычислений.
Отсюда видно, что величины в физических размерностях крайне
неудобно использовать в расчетах, так как это ведет к постоянному увеличе-
нию разрядности вычислений. Поэтому необходимо выбрать другой, более
удобный способ представления данных в микропроцессоре.

0 1 0 0 4
* 0 0 1 1 * 3
0 0 0 0 0 1 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Аккумулятор 0 0 0 0 1 1 0 0 1 2

Память ?

Рис. 6.4. Двоичное умножение

150
Относительные единицы. Форматы
представления чисел в относительных единицах
Для расчета математических моделей, описания алгоритмов системы
управления, измерений электрических и механических величин и цифровой
фильтрации удобно использовать систему относительных единиц. Относи-
тельные единицы получаются делением реальной физической величины на
некоторое базовое значение. Базовые значения можно выбирать по-разному,
однако чаще всего за базовое число принимают номинальное значение физи-
ческой величины.
Если принять за базовый ток номинальный ток двигателя, а за базовое
напряжение — номинальное напряжение двигателя, то в результате перемно-
жения тока на напряжение в относительных единицах будет получаться
мощность, тоже представленная в относительных единицах. Преимущество
такого подхода состоит в том, что всякий раз система работает с числами
вполне определенного диапазона значений. Например, ток в преобразова-
тельной технике редко бывает в несколько раз больше номинального (то же
касается напряжения и мощности).
Рассмотрим пример: необходимо рассчитать относительное значение
мощности при заданных относительных величинах тока и напряжения.
Пусть ток будет равен 1,25, а напряжение 0,75 номинальных значений. Выбе-
рем формат 2.2 для представления тока и напряжения, где два разряда будут
отведены под целую часть числа и два разряда — под дробную (рис. 6.5).
Вычисления производятся, как показано в левой части рис. 6.5. Получен-
ный точный результат P* = U*I* = 1,25æ0,75 = 0,9375 представлен в формате
4.4 с четырьмя целыми и четырьмя дробными разрядами. Общее правило
состоит в том, что при умножении вещественных чисел, представленных в
форматах с фиксированной точкой (m.n) и (p.q), включая знаковый разряд в
целой части, получаем произведение в формате (m+p).(n+q).
Если договориться о том, что произведение сохраняется в исходном фор-
мате множителей, т.е. в формате 2.2, то необходимо отбросить по два разряда
с каждой стороны (см. рис. 6.5). Сразу возникает вопрос: можно ли так
делать? Будет ли результат иметь ту же точность, что и исходные операнды?
Если известно, что в конкретной системе мощность не может быть
больше номинальной (базовой) в 2 раза, то предложенный формат результата

0 1,0 1 1,25 X X, X E
* 0 0,1 1 * 0,75 * X X, X E
0 0 0 0 0 1 0 1 0 0 0 0 E E E E
0 0 0 0 1 0 1 0 0 0 0 Y Y Y E 0
0 0 0 0 0 0 0 0 0 0 Y Y Y E 0 0
0 0 0 0 0 0 0 0 0 Y Y Y E 0 0 0
Аккумулятор 0 0 0 0, 1 1 1 1 0,9375 Z Z Z Z, R E E E

Память 0 0 , 1 1 0,75 Z Z, R E

Рис. 6.5. Вычисление мощности

151
подходит. Иначе необходимо увеличить число разрядов целой части множи-
телей, например выбрать для них формат 3.2 или 4.2.
Второе обстоятельство, вызывающее сомнение в правильности расчетов,
связано с отсечением половины разрядов в дробной части. Действительно,
точное значение результата из 0,9375 превратилось в 0,75.
Для определения погрешности этой операции необходимо рассмотреть
процесс умножения, изображенный в правой части рис. 6.5. Здесь символом
«X» обозначены разряды исходных чисел, символ «E» обозначает младшие
разряды исходных чисел, в которые заложена ошибка округления. При умно-
жении ошибки «E» на любое число «X» или «E» получается ошибка «E», что
видно по первой строчке частичных произведений. При умножении «X» на
«X» получается достоверный разряд частичного произведения «Y». При сло-
жении частичных произведений, в котором участвуют «Y», получаются раз-
ряды достоверного ответа «Z». Первый разряд, в сумму которого попали
ошибки «E», можно отнести к округленному, но не достоверному разряду
«R», а остальные разряды будут содержать слишком большие ошибки и не
будут достоверными вовсе. Получается, что дробная часть результата, сохра-
ненная в памяти, содержит только округленный разряд и разряд ошибки
округления, так что точность ответа в результате отсечения половины дроб-
ных разрядов не ухудшилась.
Рассмотренный пример оперирует с числами малой разрядности. В реаль-
ных системах управления на базе микроконтроллеров ‘C28xx используются,
как минимум, 16-разрядные вещественные числа, например, с четырьмя зна-
ками в целой части. Такие числа имеют формат 4.12. Так как для представле-
ния целой части числа, включая знаковый разряд, выделяются четыре раз-
ряда, то реализуется восьмикратная перегрузочная способность по любой из
переменных, представленных в относительных единицах (относительно
базового значения). Если точности 12 двоичных разрядов после запятой
недостаточно, то можно использовать 32-разрядный формат, например 4.28.
Таким образом, если разрабатываете программное обеспечение системы
управления, то не следует работать с физическими величинами — перехо-
дите к относительным единицам и выбирайте формат представления пере-
менных с фиксированной точкой, например 4.28 или 8.24, который, с одной
стороны, обеспечит нужную перегрузочную способность по переменным
вашего проекта, а с другой — нужную точность вычислений.
Система команд микроконтроллеров ‘C28xx оптимизирована для работы
с числами в формате с фиксированной точкой. Более того, есть средства
автоматического преобразования формата произведения к исходному фор-
мату множителей. Например, при умножении дробных чисел (перегрузочная
способность переменной равна 1) в формате 1.31 результат в формате 2.62
автоматически, сначала за счет сдвига влево на один разряд приводится к
формату 1.63, а затем за счет отбрасывания младших 32 неточных разрядов —
к исходному формату множителей 1.31.
Мощная система команд микроконтроллеров ‘C28xx послужила предпо-
сылкой к созданию специальной библиотеки вычислений с числами в фикси-
рованном формате (i.q), которая получила название IQmath. Эта библиотека
152
поддерживает вычисления с вещественными переменными, представлен-
ными в одинаковом формате (с одним и тем же числом двоичных разрядов
после точки q) и позволяет менять формат чисел при необходимости.
Библиотека IQmath максимально приближена к пользователям, занимаю-
щимся разработкой систем управления реального времени, где скорость
вычислений и точность имеют определяющее значение. Ее использование
предполагает представление переменных в относительных единицах, что
позволяет разрабатывать одно и то же программное обеспечение для объек-
тов различной мощности, например для всей серии преобразователей час-
тоты или серии комплектных электроприводов.

Контрольные вопросы
1. В каком формате будет результат умножения 16-разрядных целых чисел без знака
(исходный формат 16.0)? 32-разрядных целых чисел без знака (исходный формат 32.0)?
2. Докажите на примерах, что при умножении дробных чисел без знака в формате
(0.16) точность произведения остается на уровне точности множителей.
3. По какой из переменных для вентильного моментного электропривода должна
быть установлена максимальная перегрузочная способность (по току, скорости, моменту,
ускорению)? Какая именно? Какой при этом будет формат представления переменных?

6.3. ОСНОВЫ РАБОТЫ С БИБЛИОТЕКОЙ IQmath


И СО СТАНДАРТНОЙ БИБЛИОТЕКОЙ ПОДДЕРЖКИ
ОПЕРАЦИЙ С ПЛАВАЮЩЕЙ ТОЧКОЙ

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


создания ее математического описания. Для проверки качества регулирова-
ния на начальном этапе применяют моделирование в одном из математичес-
ких пакетов (например, MatLab), т.е. система управления вместе с моделью
обсчитывается с переменными в формате с плавающей точкой, которые не
имеют ограничений по динамическому диапазону (могут быть представлены
практически любые по абсолютному значению величины). После того как
доказана работоспособность созданных алгоритмов, можно произвести
перевод системы в относительные единицы и, выбрав необходимый формат
представления переменных с фиксированной точкой (i.q), написать про-
грамму для микроконтроллера.
Программа системы управления может быть разработана и для перемен-
ных в формате с плавающей точкой, однако все вычисления в этом формате
поддерживаются системой команд на аппаратном уровне (два параллельно
работающих АЛУ с фиксированной и плавающей точкой) только в новейших
микроконтроллерах семейства ‘C2833x, которые начали производиться
только с 2008 г.
В оценочной плате eZdsp2812 установлен микроконтроллер
TMS320F2812, который не поддерживает аппаратно операции с плавающей
точкой. Поэтому все программы с использованием переменных в формате с
плавающей точкой производят вычисления, прибегая к длинным процедурам
153
умножения, деления, сложения и вычитания, скорость выполнения которых
в десятки раз ниже операций целочисленного умножения (или умножения
над числами с фиксированной точкой).
Поддержка плавающей точки в языке Си осуществляется посредством
стандартной библиотеки реального времени выполнения rts2800_ml.lib,
а вычислений с фиксированной точкой в произвольном формате (i.q) —
посредством библиотеки IQmath.

Числа с плавающей точкой


Числа в формате с плавающей точкой представляются в ‘C28xx в 32-раз-
рядном виде, соответствующем международному стандарту IEEE Std. 754
Single Precision Floating-Point (числа в формате с плавающей точкой одно-
кратной точности). Для определения переменной в формате с плавающей
точкой используется стандартный тип float. Типы double и long double не
поддерживаются, хотя компилятор не выдает сообщений об ошибке. Они
будут откомпилированы как тип float. Формат числа типа float показан на
рис. 6.6.
Как видно, число с плавающей точкой имеет один знаковый разряд,
восемь разрядов для представления так называемой смещенной экспоненты
e = exp + 127 и 23 разряда для представления мантиссы (дробной части числа 1.f).
При этом единица в мантиссе явно не присутствует, но подразумевается.
Формат числа с плавающей точкой перекрывает большой диапазон значений
–38
(от 1,920929æ10 до 3,4028235æ1038) и допускает также представление
нуля, бесконечности и «Не Числа» (NaN).
Использование этого формата при вычислениях требует значительно
больших ресурсов процессорного времени, чем при использовании форма-
тов с фиксированной точкой. К недостатку формата float следует отнести
также и то, что при кажущейся высочайшей точности в реальных задачах,

31 30 23 22 0
s e e e e e e e e f f f f f f f f f f f f f f f f f f f f f f f

1 знаковый разряд 8-разрядная экспонента 23-разрядная мантисса


if ((e = 255)and ( f ≠ 0 )) then v = NaN ;
if ((e = 255)and ( f= 0 )) then v = ⎡(−1) ⎤ ⋅ ∞;

s

if (0 < e < 255 ) then v = (−1) ⋅ ⎡⎣ 2 ⎤⎦ ⋅ (1. f );
s
⎡ ⎤ e −127
⎣ ⎦
if ((e = 0 )and ( f ≠ 0 )) then v = ⎡(−1) ⎤ ⋅ ⎣⎡ 2e−126 ⎦⎤ ⋅ (0. f );

s

if ((e = 0 )and ( f = 0 )) then v = ⎡(−1) ⎤ ⋅ 0.

s

Рис. 6.6. Формат числа с плавающей точкой и правила представления

154
тем не менее, могут возникать проблемы при операциях с большими и
малыми числами. Приведем типичный пример.
Необходимо для системы управления преобразователем частоты реализо-
вать интегратор выходной частоты инвертора напряжения в угол вектора
напряжения, а затем вычислить синус и косинус этого угла, структурная
схема преобразования которого приведена на рис. 6.7.
Как показано на схеме, задание частоты преобразуется в угловую частоту,
интегрируется в угол и вычисляются синус и косинус этого угла.
При решении задачи в микроконтроллере все непрерывные уравнения
должны быть преобразованы к разностным уравнениям с учетом интервала
дискретизации (квантования) по времени. В нашем случае задача должна
решаться на каждом периоде широтно-импульсной модуляции ключей
инвертора TШИМ. Обычно эта частота равна 5—10 кГц. Примем для опреде-
ленности, что частота квантования задается интервальным таймером 1 и
равна TШИМ = 5 кГц.
Переведем непрерывное уравнение интегратора в дискретную область:
Y ( p )- 1
----------- = --- ; (6.3)
X(p) p
yp = x; (6.4)
yk – yk – 1
yp = ------------------------- ; (6.5)
T ШИМ

yk – yk – 1
------------------------- = x k ; (6.6)
T ШИМ
yk = yk – 1 + TШИМ xk. (6.7)
Выражая входное воздействие интегратора xk через частоту f и коэффици-
ент 2π и имея в виду, что выходом интегратора является угловое положение
вектора напряжения θk, получаем:
θk = θk – 1 + 2πTШИМ f. (6.8)

f ω0 1 θ sin(θ)
2π — SIN
t
p

cos(θ)
COS

Рис. 6.7. Структурная схема преобразования частоты в угол и синус и косинус угла

155
Таким образом, на каждом периоде ШИМ (с частотой 5000 раз в секунду)
должна вызываться процедура увеличения текущего углового положения
вектора напряжения на некоторую величину, зависящую от частоты f. После
определения нового значения угла должен выполняться расчет синусов и
косинусов угла.
На лист. 6.1 представлена программа, которая состоит из основной (фоновой)
программы и процедуры обслуживания прерывания по периоду таймера 1,
вызываемой с частотой TШИМ = 5 кГц (см. гл. 5). Фоновая программа выпол-
няет инициализацию переменных и содержит пустой (бесконечный) цикл.
Все вычисления выполняются в процедуре обслуживания прерывания.

Лист. 6.1. Преобразование задания частоты в угол и синус и косинус угла


#include <math.h>
...
float x,y,teta,pi,f,Tpwm;
interrupt void t1pr_isr(void);
void main(void)
{...
teta=0;
f=50;
Tpwm=1.0/5000;
pi=3.1415926535897932384626433832795;

for(;;)
{
}
}
interrupt void t1pr_isr(void)
{
teta+=2*pi*f*Tpwm;
x=sin(teta);
y=cos(teta);
}

Напомним, как можно отлаживать программы с прерываниями. После


запуска программы установим точку останова в процедуре обслуживания
прерывания, представленную на рис. 6.8.

Рис. 6.8. Точка останова в процедуре обслуживания прерывания

156
Для наблюдения за периодически меняющейся переменной откроем гра-
фическое окно, приведенное на рис. 6.9. На экране появится окно настроек
осциллографа, поля которого нужно проинициализировать, например, в
соответствии с рис. 6.10. В качестве стартового адреса зададим адрес пере-
менной x. В качестве типа переменной укажем 32-bit IEEE floating point
(число в формате с плавающей точкой). После подтверждения настроек и
запуска программы в режиме Анимация с помощью кнопки программа
начнет выполняться, останавливаясь в точке останова, обновляя содержимое
окна осциллографа и окна наблюдения переменных и запускаясь снова. Гра-
фик переменной x будет изменяться по синусоидальному закону, изображен-
ному на рис. 6.11.
Обратите внимание, что вывод точек на график производится «как бы с
частотой 5000 Гц» (при каждом обращении к процедуре обслуживания пре-
рывания), а частота выходного сигнала задана равной 50 Гц. Таким образом,
на одном периоде сигнала выводится ровно 100 значений переменной x. На
самом деле режим «Анимации» не является режимом реального времени.
Процессор прерывается в точке останова для передачи информации с оце-
ночной платы в компьютер. Если этот процесс займет больше времени, чем
период ШИМ, то один или несколько периодов ШИМ могут быть пропущен-
ными. Поэтому на графике по оси времени указано не абсолютное время, а
номер выборки данных.
В программе допущена грубая ошибка, которая незаметна в процессе
прогона, пока угол θ еще мал. Изменив его на большее значение, например
102900 радиан, можно убедиться, что функции синуса и косинуса непра-
вильно вычисляются при больших значениях угла (рис. 6.12).

Рис. 6.9. Меню вызова окна построения осциллограмм

157
Рис. 6.10. Настройки окна осциллографирования

Рис. 6.11. Результат работы программы

Если теперь значение угла разделить на 2πf, то окажется, что система


управления такого электропривода проработает всего 327 с и «развалится»,
что абсолютно недопустимо.
Как видите, высокая точность вычислений с числами в формате с плаваю-
щей точкой еще не является гарантией устойчивости вычислительного алго-
ритма.
158
Рис. 6.12. Неверный расчет функции синуса «большого угла», нарушение в работе интегратора

Практическая работа

1. Выполните отладку рассмотренной программы.


2. Проверьте, будет ли «разваливаться» функция вычисления косинуса
так же, как и функция синуса. Для того чтобы понять причины этого
эффекта, рассмотрите исходный файл библиотеки стандартных функций
C:\ti\c2000\lib\cgtool\lib\rts.src. Найдите в нем объявление и реализацию
функции синуса, рассмотрите внимательно комментарии разработчиков биб-
лиотеки. Вам станет понятно, что функции синуса и косинуса точно рабо-
тают только в определенном диапазоне изменения аргумента, а при выходе
из него точность расчетов не гарантируется.
3. Изменяя значение угла θ, установите, при каком его значении прекра-
тится интегрирование. В чем причина остановки интегратора? Можно ли
объяснить причину остановки тем, что начиная с определенного большого
значения угла прибавление маленькой добавки из-за неизбежного округле-
ния результата в формате с плавающей точкой перестает сказываться?
4. Можно ли модернизировать программу выше, чтобы не происходила
потеря точности вычислений, сохраняя тип переменных float? Сделайте это.
Выполните отладку модернизированной программы.
159
Числа с фиксированной точкой
и операции над ними в библиотеке IQmath
Библиотека IQmath позволяет работать с 32-разрядными числами с фик-
сированной точкой. Число разбивается на целую и дробную части. Название
формата (i.q) определяется количеством разрядов целой части числа до деся-
тичной точки, включая знаковый разряд, и количеством разрядов дробной
части числа. Например, число с 8 целыми и 24 дробными разрядами имеет
формат 8.24, представленный на рис. 6.13. Можно использовать и другое
обозначение формата — Q24.
Большая разрядность числа в формате Q24 позволяет иметь высокую точ-
ность и широкий диапазон значений: от –128 до +127,99999994.
Если система управления работает в относительных единицах, то боль-
шая перегрузочная способность дает возможность практически без предва-
рительной переработки переходить от исходных уравнений, написанных для
формата с плавающей точкой, к уравнениям, написанным с использованием
функций библиотеки IQmath.
В качестве примера рассмотрим типовую операцию d = aæb + c, выполняемую
над числами формата Q24 без использования библиотеки IQmath (лист. 6.2).

Лист. 6.2. Классическая реализация IQ вычислений на языке Си


int32 D, A, B, C; // числа в формате Q24
D = (int32)((int64)A*(int64)B)>>24)+С;

Данный фрагмент программы определяет числа в формате Q24 как целые.


Только программист обязан помнить, что это числа с фиксированной точкой,
и знать ее положение. Процессор работает с этими числами, как с целыми.
Для умножения двух 32-разрядных чисел их необходимо предварительно
преобразовать к 64-разрядному формату, так как формат операндов в языке
Си определяет формат результата умножения. Если этого не сделать, как
было показано ранее, то можно получить неправильный, усеченный результат.
После умножения получается 64-разрядное число в формате 16.48 с 16
целыми и 48 дробными разрядами. Его следует привести к формату числа С,
сдвинув вправо на 24 разряда и уничтожив 24 лишних разряда, которые, как
было показано, не являются достоверными (рис. 6.14). После сдвига резуль-
тат умножения переводится в 32-разрядный формат (отсекается старшая
часть) и к нему добавляется значение переменной С.

31 30 24 23 0
s i i i i i i i f f f f f f f f f f f f f f f f f f f f f f f f

1 знаковый разряд 7 разрядов целой части 24 разряда дробной части

Рис. 6.13. Формат числа Q24

160
I8,Q24 A

*
I16,Q48
I8,Q24 B
>>24

ssssssssssssssssssssssssI16,Q24

I8,Q24

+ I8,Q24 C

I8,Q24 I8,Q24 D

Рис. 6.14. Работа алгоритма имитации IQmath средствами языка Си

В IQmath для определения формата переменных используется тип _iq.


Операции сложения и вычитания не претерпевают изменений, однако Си (в
отличие от Си++) не поддерживает перегрузки функций, поэтому умноже-
ние, деление и другие операции требуют вызова специальных функций
_IQmpy, _IQdiv и т.д. Для приведенного примера программа для IQmath
должна быть написана, как показано в лист. 6.3.

Лист. 6.3. Пример вычислений с использованием IQmath


_iq a,b,c,d;
d=_IQmpy(a,b)+c;

Данная запись значительно проще для восприятия. Она не содержит опе-


раций сдвигов, заложенных в функцию _IQmpy на стадии создания библио-
теки ее разработчиками. Код, который будет исполнять микроконтроллер,
показан на рис. 6.15.
Алгоритм расчета несколько отличается от расчета, приведенного на
рис. 6.15. Он адаптирован к возможностям ядра микроконтроллеров семей-
ства ‘C28хх и выполняется всего за семь тактов.

Рис. 6.15. Программа с функцией IQmath и ее эквивалент в машинном коде

161
Вначале регистр умножителя XT загружается значением переменной a с
использованием прямой страничной адресации (адрес 12 на текущей стра-
нице памяти). Затем производятся операции умножения содержимого регис-
тра XT на значение переменной b (адрес 4 на текущей странице памяти).
Две операции умножения служат для получения младшей и старшей части
64-разрядного произведения, которые записываются в регистры P и ACC
соответственно.
Следующая команда выполняет сдвиг сдвоенного 64-разрядного регистра
(ACC:P) влево на восемь разрядов, в результате чего в старшей части (в
аккумуляторе — ACC) оказывается результат умножения, приведенный к
формату с восемью целыми и 24 дробными разрядами. К нему добавляется
значение переменной c (адрес 18 на текущей странице памяти), затем резуль-
тат сохраняется в переменную d по адресу 14 на текущей странице памяти.
Алгоритм расчета показан на рис. 6.16.

Лист. 6.4. Использование библиотеки IQmath в Си++


iq a, b, c, d; // данные в формате Q24
iq operator * (const iq &a, const iq &b)
{
return(((int64)a*(int64)b)>>24);
}
void main(void)
{
d = a * b + c;
}

Здесь описывается функция-операция: вслед за типом возвращаемого


результата iq указывается ключевое слово operator, за которым следует знак

ACC:P I8,Q24 a

*
I16,Q48
I8,Q24 b
<<8

I8,Q48 00000000

I8,Q24 ACC

+ I8,Q24 c

I8,Q24 I8,Q24 d

Рис. 6.16. Вычисления с использованием функции библиотеки IQmath

162
переопределяемой операции *, после которого в круглых скобках указан спи-
сок параметров с их типами. Тело переопределяемой операции описывается,
как обычно, между фигурных скобок { }.
Приведенный код является лишь примером. На самом деле в заголовоч-
ном файле IQmathLibCPP.h содержится более сложное определение с вызо-
вом функции _IQmpy(a,b). Таким образом, в языке Си++, объявив вещест-
венные переменные типа iq с фиксированной точкой, дальше можно
пользоваться обычными операторами, такими как «*», «/» и т.д. Они автома-
тически будут замещаться соответствующими функциями библиотеки
IQmath.
Список функций, содержащихся в библиотеке IQmath, приведен в табл. 6.1
в сравнении с функциями стандартной библиотеки поддержки вычислений с
плавающей точкой.

Табл и ц а 6.1
Основные функции библиотеки IQmath

Операция Плавающая точка IQmath Си IQmath Си++


Определение типа float a,b; _iq a,b; iq a,b;
Определение константы a = 1.2345 a = _IQ(1.2345) a = IQ(1.2345)
Умножение a*b _IQmpy(a,b) a*b
Деление a/b _IQdiv(a,b) a/b
Сложение a+b a+b a+b
Вычитание a–b a–b a–b
Булевы >, >=, <, <=, ==, >, >=, <, <=, ==, >, >=, <, <=, ==,
!=, &&, || !=, &&, || !=, &&, ||
Синус sin(a) _IQsin(a) IQsin(a)
Косинус cos(a) _IQcos(a) IQcos(a)
Синус (угол в относительных еди- sin(a*2pi) _IQsinPU(a) IQsinPU(a)
ницах)
Косинус (угол в относительных cos(a*2pi) _IQcosPU(a) IQcosPU(a)
единицах)
Арктангенс atan(a) _IQatan(a) IQatan(a)
Угол координаты atan2(a,b) _IQatan2(a,b) IQatan2(a,b)
Угол координаты в относительных atan2(a,b)/2pi _IQatan2PU(a,b) IQatan2PU(a,b)
единицах
Амплитуда sqrt(a*a+b*b) _IQmag(a,b) IQmag(a,b)
Квадратный корень sqrt(a) _IQsqrt(a) IQsqrt(a)
Обратное значение квадратного 1/sqrt(a) _IQisqrt(a) IQisqrt(a)
корня
Насыщение if (a>pos) a=pos; _IQsat(a,pos,neg) IQsat(a,pos,neg)
if (a<neg) a=neg;

163
Контрольные вопросы
1. Как Вы понимаете функцию синус угла в относительных единицах?
2. Какие функции необходимо использовать при переходе от декартовой системы
координат к полярной?
3. Как выполнить обратный переход от полярной системы координат к декартовой?

Пример разработки и отладки программы инерционного


фильтра с использованием библиотеки IQmath
Рассмотрим пример реализации программного модуля цифрового филь-
тра с простейшей передаточной функцией инерционного звена:
Y(p) 1
W ( p ) = ------------ = -------------------- ; (6.9)
X ( p ) Tф p + 1
Tф yp + y = x, (6.10)
где Tф — постоянная времени фильтра.
Для преобразования уравнения фильтра из непрерывной области в дис-
кретную введем интервал дискретизации по времени h. Тогда производная в
уравнении (6.12) может быть представлена в виде разности первого порядка:
yk – yk – 1
- + yk = xk ;
T ф ------------------------ (6.11)
h
T T ф⎞
⎛ 1 + -----ф-⎞ y = ⎛ ----- - y + xk ; (6.12)
⎝ h⎠ k ⎝ h⎠ k–1
T
-----ф-
h 1
y k = ----------------- y k – 1 + ----------------- x k . (6.13)
Tф Tф
1 + ------ 1 + ------
h h
Упростив выражение, получим:
Tф h -x
-y
y k = ---------------- + ---------------- . (6.14)
Tф + h k – 1 Tф + h k
Если теперь в уравнении выделить коэффициенты при yk – 1 и xk, то оно
преобразуется к виду:
yk = ky1yk – 1 + kx0xk, (6.15)
Tф h
где k y1 = ----------------- , а k x0 = ----------------- .
Tф + h Tф + h
Для определения типа данных, используемых инерционным фильтром, и
описания прототипа функций, выполняющих инициализацию фильтра и
обработку фильтром входных данных, создадим файл filter.h, который при-
веден ниже:
164
Лист. 6.5. Заголовочный файл модуля инерционного фильтра filter.h
#ifndef filter_h
#define filter_h
typedef struct { _iq x;
_iq y;
_iq kx0;
_iq ky1;
_iq Th;
void (*Init)();
void (*Execute)();
} FILTER_DATA;
#define FILTER_DEFAULTS { 0,0,0,0,\
_IQ(10),\
(void (*)(void))Filter_Init, \
(void (*)(void))Filter_Execute,\
}
void Filter_Init(FILTER_DATA *);
void Filter_Execute(FILTER_DATA *);
#endif
В представленном файле определен новый тип переменной с именем
FILTER_DATA на основе структуры, состоящей из входной величины xk
(переменная x), выходной величины yk (переменная y), коэффициентов kx0 и
T
ky1, параметра Th, который определяет отношение -----ф- , и двух указателей на
h
функции инициализации и расчета фильтра. В структуре величина yk – 1,
которая используется при расчете yk, отсутствует. Это связано с тем, что рас-
считанное на предыдущем такте работы фильтра выходное значение yk для
нового такта расчета является как раз предыдущим значением выходной
величины yk – 1. Поэтому достаточно иметь только одну переменную, значе-
ние которой обновляется в процессе расчета.
Члены структуры void (*Init) (void) и void (*Execute) (void) являются
указателями на функции, где:
void — тип возвращаемого функцией значения;
Init и Execute — имена указателей на функцию инициализации фильтра и
собственно процедуру фильтрации;
(void) — описание передаваемых в функцию переменных (в данном слу-
чае список параметров пуст, так как в функцию ничего не передается).
После объявления типа структуры данных фильтра определяются значения
по умолчанию, которые следует использовать для автоматической инициали-
зации данных типа FILTER_DATA. По умолчанию в FILTER_DEFAULTS
прописаны нулевые значения для входа, выхода и коэффициентов фильтра,
значение 10 для коэффициента фильтрации, а также адреса функций инициа-
лизации и расчета фильтра, синтаксис которых рассмотрим подробнее на
примере функции инициализации:
165
(void (*) (void)) Filter_Init — преобразование функции Filter_Init к ука-
зателю, объявленному в структуре. Здесь void (*) (void) является безымян-
ным указателем (обратите внимание на сходство с указателем, объявленным
в структуре).
В конце файла описываются прототипы функций, в которые передаются
указатели на структуру FILTER_DATA.
Строго говоря, преобразование одного типа функции к другому не вполне
корректно, но используется в Code Composer Studio, поскольку в момент
объявления типа структуры FILTER_DATA невозможно объявить тип пере-
даваемых переменных в функции, которые являются членами этого же типа
(проще говоря, нельзя использовать имя типа внутри своего же объявления).
Главный файл модуля фильтра filter.c (см. лист. 6.6) содержит обращение
к заголовочным файлам библиотеки IQmath и filter.h. Затем следует проце-
дура инициализации, с помощью которой рассчитываются коэффициенты
ky1, kx0 и основная функция фильтра.
Лист. 6.6. Главный файл модуля инерционного фильтра filter.c
#include "IQmathLib.h"
#include "filter.h"
void Filter_Init(FILTER_DATA *filter)
{
filter->ky1=_IQdiv(filter->Th,(filter->Th+_IQ(1)));
filter->kx0=_IQdiv(_IQ(1),(filter->Th+_IQ(1)));
}
void Filter_Execute(FILTER_DATA *filter)
{
filter->y=_IQmpy(filter->kx0,filter->x)+
_IQmpy(filter->ky1,filter->y);
}
Оператор «–>» используется для обращения к членам структуры по ее
указателю (не путать с оператором «.», который используется непосред-
ственно с именем структуры).
Готовую функцию подключите к проекту, добавив в проект файл filter.c.
Пример обращения к функции из main.c показан в лист. 6.7.
Вызов фильтра следует делать не из фоновой программы, а из процедуры
обслуживания прерывания интервального таймера, задающего период дис-
кретизации, чтобы была точно определена постоянная интегрирования. В
данном случае помещение основной процедуры фильтрации в фоновую про-
грамму сделано в целях упрощения.
В начале программы подключаются заголовочные файлы. Определяется
структура данных фильтра sample типа FILTER_DATA, который сразу ини-
циализируется значениями по умолчанию. В главной функции программы
main можно изменить значения по умолчанию, если это требуется, и выпол-
нить процедуру инициализации. В цикле во входную переменную sample.x
записывается входное воздействие, а после вызова процедуры фильтра из
sample.y извлекается результат фильтрации.
166
Лист. 6.7. Вызов фильтра из главной программы main.c
#include "IQmathLib.h"
#include "filter.h"
FILTER_DATA sample=FILTER_DEFAULTS;
_iq x,y;
void main(void)
{
sample.Th=_IQ(10);
sample.Init(sample);
for(;;)
{
sample.x=x;
sample.Execute(sample);
y=sample.y;
}
}

Предлагаемая методика описания функции цифрового фильтра на первый


взгляд кажется избыточно сложной, однако она настоятельно рекомендуется
программистам ввиду следующих преимуществ:
• в одном проекте можно объявить сколько угодно экземпляров фильтра
для работы с различными входными переменными: токами фаз двигателя,
сигналом задания частоты, сигналом обратной связи по давлению и темпера-
туре и т.п.;
• цифровой фильтр для каждой из переменных может иметь свой соб-
ственный период квантования по времени и свою собственную постоянную
времени. Например, постоянная времени фильтра для токовых сигналов
может быть равной 0,01 с, а для сигналов давления и температуры — 5—10 с.
При этом нет необходимости изменять основные функции фильтрации.
Достаточно только правильно выполнить инициализацию соответствующей
структуры;
• описанный подход строго соответствует концепции модульности про-
граммного обеспечения, когда для описания любого числа однотипных задач
(процессов) требуется всего лишь один программный модуль, который
может многократно вызываться с различными значениями параметров.
В качестве входного сигнала для проверки работы цифрового инерцион-
ного фильтра удобно использовать функцию синуса с наложенными на нее
высокочастотными составляющими помех, например:
x = sin(t) + 0,2sin(45t) + 0,2sin(65t). (6.16)
Время t может имитироваться переменной, значение которой увеличива-
ется на некоторую константу в каждом цикле фоновой программы. Добавим
в программу переменную t типа _iq и запишем выражение для расчета вход-
ной переменной фильтра x (лист. 6.8).
167
Лист. 6.8. Расчет входного зашумленного сигнала
t+=_IQ(0.05);
x=_IQsin(t)+_IQmpy(_IQsin (_IQmpy(_IQ(45),t)),_IQ(0.2))
+_IQmpy(_IQsin (_IQmpy(_IQ(65),t)),_IQ(0.2));

Если запустить программу, а в качестве наблюдаемой переменной в окно


осциллографа вывести x, то можно наблюдать синусоиду с наложением
шума, однако приблизительно один раз за 40 периодов основной гармоники
наблюдается разрыв функции, представленный на рис. 6.17.
Это происходит из-за переполнения формата переменной t, которая не
может хранить значения, превышающие +127,99999994.
При достижении максимального значения происходит двоичное перепол-
нение переменной t — она становится отрицательной величиной со значе-
нием, равным –128. Если рассчитать значение синуса для углов +128 и
–128 радиан, то они будут равны +0,788 и –0,788 соответственно, что и обра-
зует разрыв в функции, изображенный на рис. 6.17.
Причина кроется в том, что периодичность изменения t не соответствует
периодичности функции sin(t). Чтобы периодичность совпадала, используют
углы, представленные не в радианах, а в относительных единицах. Тогда

Рис. 6.17. Разрыв в функции синуса входной переменной x

168
одному обороту или 2π в радианах соответствует единица, т.е. синус берется
не от угла, а от оборота. Это очень удобно тем, что в формате Q24 перемен-
ная t может хранить 256 целых оборотов (значение t меняется от
+127,99999994 до –128). Так как число оборотов во всем диапазоне значений
t целое, то и функция синуса от числа оборотов будет непрерывной.
Зная об этой проблеме, разработчики библиотеки IQmath предусмотрели
целый ряд тригонометрических функций для работы с углами в относи-
тельных единицах: _IQsinPU, _IQcosPU и др. Полный перечень этих функ-
ций приведен в табл. 6.1.
Так как в дальнейшем предполагается выводить результаты работы филь-
тра в окно осциллографа с анализом спектра с помощью преобразования
Фурье, то будет удобно, если один период синуса основной гармоники будет
сформирован количеством точек, равным степени двойки. Поэтому новое
приращение для переменной t будет равно 1/256 (лист. 6.9).

Лист. 6.9. Основная часть цикла тестирования функции фильтра


t+=_IQ(0.00390625);
x=_IQsinPU(t)
+_IQmpy(_IQsinPU(_IQmpy(_IQ(45),t)),_IQ(0.2))
+_IQmpy(_IQsinPU(_IQmpy(_IQ(65),t)),_IQ(0.2));
sample.x=x;
sample.Execute(sample);
y=sample.y;

Для получения сразу двух графиков (входа и выхода фильтра) в окне


осциллографа следует вызвать окно свойств осциллографа и изменить пара-
метр Display Type, как показано на рис. 6.18, а, а затем указать адрес второго
осциллографируемого параметра — рис. 6.18, б.
После запуска программы верхний луч осциллографа отображает график
изменения входного сигнала фильтра, а нижний — значение выходного
отфильтрованного сигнала (рис. 6.19).
Чтобы оценить степень фильтрации сигнала, можно воспользоваться
инструментом быстрого преобразования Фурье (БПФ), предоставляемым
средой Code Composer Studio. Для этого необходимо создать еще два окна
осциллографа для входного и выходного сигналов.
Настройки окон следует выполнить, как показано на рис. 6.20, выбрать
тип осциллограммы FFT Magnitude (амплитудное БПФ), тип сигнала Real
(действительный сигнал). Количество выборок для преобразования установ-
лено равным 256, что соответствует одному периоду синусоиды основной
гармоники входного сигнала.
Следующая важная настройка касается формата входных чисел. Встроен-
ное в Code Composer преобразование Фурье не совершает операцию деления
интеграла гармонических составляющих на количество выборок, поэтому
отображаемое значение амплитуды будет больше реального в 128 раз (поло-
вина от общего количества точек). Чтобы амплитуда соответствовала дей-
169
а)

б)

Рис. 6.18. Настройка осциллографа на два канала

ствительной, необходимо скорректировать формат входных данных, увели-


7
чив количество дробных разрядов на семь (2 = 128 — половина емкости
буфера преобразования), и установить формат Q31 (Q24 + 7). Чтобы по гори-
зонтальной оси были отложены гармоники относительно основной, следует
установить параметр частоты сбора информации равный 256 Гц, что соот-
ветствует количеству выборок в буфере БПФ.
170
Рис. 6.19. Результаты цифровой фильтрации:
входной и выходной сигналы

Результат работы БПФ показан на рис. 6.21. Подробно исследовать сигнал


можно, устанавливая с помощью мыши курсор (Data Cursor) в те точки гра-
фика, где амплитуды отличны от нуля. В исходном сигнале есть: первая гар-
моника с амплитудой, равной 1; 45-я гармоника с амплитудой, равной 0,2;
65-я гармоника с амплитудой, равной 0,2. Это полностью соответствует опи-
санию входного сигнала в программе, приведенной на лист. 6.9. В выходном
отфильтрованном сигнале присутствуют первая гармоника с амплитудой
0,9684, 45-я гармоника с амплитудой 0,0181 и 65-я гармоника с амплитудой
0,0133. Результаты опыта показывают, что простой инерционный фильтр
эффективно справляется с высокими гармониками, однако вносит амплитуд-
ные и фазовые искажения в основной сигнал.

Практическая работа

1. Исследуйте входной сигнал для переменной t, представленной в радиа-


нах. Убедитесь в разрывности переменной x.
2. Перейдите к относительным единицам. Отладьте программу.
171
Рис. 6.20. Настройки окна быстрого преобразования Фурье

3. Освойте технологию анализа частотного спектра входного и выходного


сигнала фильтра с использованием преобразования Фурье. Оцените фазовые
и амплитудные искажения, вносимые фильтром.
4. Измените постоянную времени фильтра, например, вдвое. Объясните
полученные результаты.
5. Создайте второй экземпляр данных инерционного фильтра, соедините
два фильтра последовательно, чтобы их работа соответствовала двойному
инерционному фильтру. Исследуйте работу фильтра с помощью средств
осциллографирования и БПФ. Сравните степень фильтрации для двух иссле-
дованных вариантов фильтров.
6. Создайте второй источник зашумленного сигнала с другими парамет-
рами помех. Осуществите его фильтрацию. Сколько входных сигналов
может обрабатываться одним модулем фильтра в одной программе?
172
Рис. 6.21. Результата работы БПФ
7. Представьте уравнение двойного инерционного фильтра
1
W ( p ) = ---------------------------- в дискретной области. Напишите модуль программного
2
( Tф p + 1 )
обеспечения такого фильтра. Сравните его работу с работой двух последова-
тельно соединенных инерционных фильтров. Какой вариант программы
выполняется быстрее?

6.4. ИЗМЕНЕНИЕ ФОРМАТА ЧИСЕЛ


ПРИ РАБОТЕ С БИБЛИОТЕКОЙ IQmath
По умолчанию библиотека IQmath настроена на работу с числами фор-
мата Q24, однако если для конкретной задачи не хватает точности или дина-
мического диапазона, то формат чисел может быть изменен как для всего
проекта, так и для отдельных его частей.
В файле IQmathLib.h, подключаемом ко всем файлам, использующим
IQmath, содержится описание константы GLOBAL_Q (число дробных раз-
рядов), которая может быть изменена по усмотрению программиста проекта
в диапазоне от 1 до 30. Все функции в программе будут использовать именно
это значение Q. Эта константа задает формат представления чисел по умол-
чанию, обрабатываемых функциями библиотеки IQmath. Для некоторых
типовых значений константы диапазоны изменения чисел и точность их
представления приведены в табл. 6.2.
173
Табл и ц а 6.2
Диапазоны представления и точности чисел в Q-форматах

Значение
GLOBAL_Q Вес младшего разряда
максимальное минимальное

28 +7,999 999 996 –8,000 000 000 0,000 000 004

24 +127,999 999 94 –128,000 000 00 0,000 000 06

20 +2047,999 999 –2048,000 000 0,000 001

Лист. 6.10. Принудительное указание формата Q-чисел


#include "IQmathLib.h"
_iq23 a, b, c;
void main(void)
{
a=_IQ23(1.1);
b=_IQ23(1.2);
c=_IQ23mpy(a,b);
}

Если по каким-либо причинам программисту необходимо изменить фор-


мат, то это можно сделать в отдельном фрагменте программы, как показано в
лист. 6.10. Следует помнить, что формат переменных внутри одной строки
должен быть одинаковый. В библиотеке IQmath предусмотрены функции
преобразования форматов, которые приведены в табл. 6.3.

Табл и ц а 6.3
Функции преобразования форматов в библиотеке IQmath

Операция Плавающая точка IQmath Си IQmath Си++

Из iq в iqN a _IQtoIQN(a) IQtoIQN(a)

Из iqN в iq a _IQNtoIQ(a) IQNtoIQ(a)

Целая часть (long)a _IQint(a) IQint(a)

Дробная часть a-(long)a _IQfrac(a) IQfrac(a)

Умножение на целое число a*(float)b _IQmpyI32(a,b) IQmpyI32(a,b)

Целая часть при умножении (long)(a*(float)b) _IQmpyI32int(a,b) IQmpyI32int(a,b)


на целое число

Дробная часть при умножении a-(long)(a*(float)b) _IQmpyI32frac(a,b) IQmpyI32frac(a,b)


на целое число

Из iq во float a _IQtoF(a) IQtoF(a)

174
Практическая работа
1. Измените значение константы GLOBAL_Q, чтобы проект инерционного
фильтра, рассмотренный выше, работал с переменными в формате Q28. Сказы-
вается ли повышение точности представления переменных на работе фильтра?
2. Измените программу цифрового инерционного фильтра, чтобы он имел
2000-кратную перегрузочную способность по входному воздействию. Как
изменятся строки вызова процедуры фильтрации и передачи фильтру пара-
метров в функции main? Как сказывается уменьшение точности представле-
ния переменных на работе фильтра?
3. Проанализируйте код перевода числа формата GLOBAL_Q в формат
переменных модуля цифрового фильтра. Объясните суть производимых пре-
образований.

6.5. СРАВНЕНИЕ ПРОИЗВОДИТЕЛЬНОСТИ


ПРИ РАБОТЕ С ЧИСЛАМИ В РАЗЛИЧНЫХ ФОРМАТАХ
Сравнение 32-разрядных и 16-разрядыных вычислений
Микроконтроллеры ‘C28xx оптимизированы как для 16-разрядных, так и
для 32-разрядных вычислений. В ряде случаев возникает соблазн использо-
вать 16-разрядные вычисления для того, чтобы уменьшить размер програм-
много кода и увеличить скорость его выполнения. Разумеется, это можно
делать, но только в том случае, когда точности вычислений достаточно. Биб-
лиотекой IQmath 16-разрядная арифметика с фиксированной точкой не под-
держивается. Поэтому пользователь должен создавать свою собственную
библиотеку вычислений с вещественными 16-разрядными числами. Затраты
времени на разработку такой библиотеки могут быть неоправданными, осо-
бенно в том случае, когда требуются форматы с различным положением
десятичной точки.
На DSP-микроконтроллерах семейства ‘С28хх 32-разрядные операции c
вещественными числами в форматах с фиксированной точкой выполняются
почти так же эффективно, как 16-разрядные операции, однако при сущест-
венно более высокой точности. Именно поэтому библиотека IQmath подде-
рживает 32-разрядные вычисления. Большой объем встроенной памяти дан-
ных в микроконтроллерах ‘C28xx, а также возможность ее расширения с
помощью внешних БИС статического ОЗУ практически полностью снимают
проблему экономии памяти данных под переменные проекта.
Практика показывает, что рационально использовать целочисленные
16-разрядные вычисления, особенно при работе с периферией. Однако
вычисления с вещественными 16-разрядными числами нецелесообразны.
Проще и точнее использовать для подобных задач 32-разрядные вычисления,
поддерживаемые библиотекой IQmath. При этом значительно сокращается
время разработки и отладки программ, время до выхода готового изделия на
рынок. Мы рекомендуем этот подход для реализации цифровых регуляторов
и фильтров, наблюдателей состояния, модулей преобразования координат в
системах векторного управления и т.п.
175
Сравнение производительности вычислений
с плавающей точкой и фиксированной точкой
с помощью встроенных в Code Composer Studio средств
Библиотека IQmath сложна в использовании (по сравнению с плавающей
точкой), имеет малый диапазон значений величин, что требует постоянной
проверки исходных уравнений на возможность переполнения формата.
Насколько действительно велика необходимость ее применения в задачах
реального времени?
Чтобы ответить на этот вопрос, необходимо измерить производитель-
ность программы, выполняющей одинаковые действия для плавающей точки
и фиксированной точки, поддерживаемой библиотекой IQmath. Например,
сравним выполнение двух тестовых задач: решение линейного уравнения и
непрерывную развертку угла с вычислением его косинуса и синуса, каждая
из которых реализована в формате с плавающей точкой и в формате с фикси-
рованной точкой библиотеки IQmath.
Программа, представленная на лист. 6.11, содержит фрагменты для плава-
ющей (все переменные, начинающиеся с буквы f) и фиксированной точек.
В среде CCS имеются специальные средства оценки числа тактов микро-
процессора, затраченных на выполнение определенного программного кода.
Чтобы воспользоваться ими, необходимо включить счетчик числа тактов
микроконтроллера, выбрав пункт меню Profiler/Enable Clock (Профай-
лер/Разрешить подсчет тактов), а затем вывести содержимое счетчика
числа тактов на экран компьютера с помощью пункта меню Profiler/View
Clock (Профайлер/Просмотр числа тактов). Профайлером называется
комплекс специальных средств отладочной среды CCS, позволяющих оце-
нить время выполнения некоторого фрагмента программы в числе тактов
процессора.
Дальше необходимо установить точку останова внутри цикла, запустив
программу на выполнение в режиме «Анимация» с помощью кнопки ,и
создать два окна осциллографа для наблюдения за текущими значениями
переменных fx и x, изображенных на рис. 6.22. Как видно из рисунка, оба
фрагмента программы с вычислениями в формате с плавающей (верхний
график) и фиксированной (нижний график) точками работают идентично.

Лист. 6.11. Программа для измерения производительности вычислений


#include <math.h>
#include "IQmathLib.h"
_iq x,y,teta,pi,f,Tpwm;
float fx,fy,fteta,fpi,ff,fTpwm;
_iq a,b,c,d;
float fa,fb,fc,fd;

176
void main(void)
{
fteta=0;
teta=0;
ff=50;
f=_IQ(50);
fTpwm=1.0/5000;
Tpwm=_IQ(0.0002);
fpi=3.1415926535897932384626433832795;
pi=_IQ(3.1415926535897932384626433832795);
a=_IQ(2);
b=_IQ(2.5);
c=_IQ(3.3);
fa=2;
fb=2.5;
fc=3.3;
fd=fa*fb+fc;
d=_IQmpy(a,b)+c;
for(;;)
{
fteta+=2*fpi*ff*fTpwm;
fx=sin(fteta);
fy=cos(fteta);
teta+=_IQmpy(f,Tpwm);
x=_IQsinPU(teta);
y=_IQcosPU(teta);
}
}

Рис. 6.22. Работа тестовой программы измерения производительности

177
а) б) в)

Рис. 6.23. Счетчик числа тактов

Измерение производительности выполняется для конкретных строк про-


граммы. Перезагрузим программу и выполним ее до строки, содержащей
вычисление переменной fd. После этого двойным нажатием левой клавиши
мыши обнулим счетчик числа тактов микроконтроллера (рис. 6.23, а).
Выполним строку программы с кодом в формате с плавающей точкой. Счет-
чик числа тактов покажет значение, соответствующее длительности выпол-
нения этого фрагмента программы (рис. 6.23, б). Вновь обнулим счетчик
числа тактов и выполним еще одну строку программы с вычислениями в
формате с фиксированной точкой. Количество тактов на выполнение данной
операции показано на рис. 6.23, в. Данный пример доказывает более чем
30-кратное превосходство по скорости вычислений технологии, заложенной
в библиотеку IQmath, над стандартными вычислениями с плавающей точкой
для микроконтроллеров ‘C28xx.
Выполнение трех последующих строчек кода в теле цикла займет 5823
такта для вычислений с плавающей точкой и 100 тактов для вычислений в
формате с фиксированной точкой с использованием библиотеки IQmath. Раз-
ница в скорости вычислений увеличилась до 58 раз. Таким образом, при
использовании библиотеки IQmath выигрыш в производительности может
достигать нескольких десятков.

Практическая работа

1. Выполните описанные выше действия по оценке производительности


различных фрагментов тестовой программы.
2. Произведите измерение времени выполнения функции цифрового
инерционного фильтра, написанного с использованием библиотеки IQmath.
3. Исправьте функцию фильтра для работы с переменными в формате с
плавающей точкой. Измерьте время выполнения модернизированной функ-
ции несколько раз при различных значениях входных данных. Усредните
результат. Сравните производительность обеих программ.
4. Рассчитайте, какой процент от периода ШИМ занимает фильтрация
токов двух фаз каждым из вариантов фильтра, если за период ШИМ измере-
ние тока и вызов процедуры фильтрации производятся 4 раза по каждому
току, а частота ШИМ составляет 5 кГц. Можно ли вообще использовать в
таких задачах вычисления с плавающей точкой?
178
Глава 7

ЭФФЕКТИВНЫЕ СПОСОБЫ
ЦИФРОВОЙ ФИЛЬТРАЦИИ СИГНАЛОВ

Данная глава посвящена изучению структуры цифровых фильтров высо-


кого порядка. Знакомству с методами адресации кольцевых буферов и специ-
альными командами умножения с накоплением, методами реализации филь-
тров. Освоению правил оформления модулей на языке Ассемблер и
технологии передачи параметров из Си-программы в программу на Ассемб-
лере, возврата результатов. Освоению стандартных методов расчета пара-
метров цифровых фильтров в пакете моделирования MatLab. Исследованию
работы 16-разрядных и 32-разрядных фильтров с конечной и бесконечной
импульсной характеристикой.

7.1. ЦИФРОВОЙ 16-РАЗРЯДНЫЙ ФИЛЬТР


С КОНЕЧНОЙ ИМПУЛЬСНОЙ ХАРАКТЕРИСТИКОЙ

Структура КИХ-фильтра
В общем случае цифровой фильтр с конечной импульсной характеристи-
кой (КИХ-фильтр, в зарубежной терминологии — FIR-фильтр) описывается
следующим разностным уравнением:

N
y(n) = ∑ hk x ( n – k )
k=0
(7.1)

или с использованием дискретного преобразования Лапласса передаточной


функцией:

N –k
H(z) = ∑ hk z
k=0
. (7.2)

Импульсная передаточная функция КИХ-фильтра идентична коэффици-


ентам фильтра:

⎧ h , 0 < n < N;
h(n) = ⎨ n (7.3)
⎩ 0, otherwise.

179
Блок-схема фильтра (потоковая диаграмма сигналов) представлена на
рис. 7.1 и соответствует приведенным разностным уравнениям и дискретной
передаточной функции. Реализация такого цифрового фильтра в виде много-
секционной линии задержки по существу канонизирована в цифровой обра-
ботке сигналов (является общепринятым стандартом). Число элементов
задержки в линии задержки равно порядку фильтра.
Выходное управляющее воздействие (результат работы фильтра) пред-
ставляет собой сумму произведений величин, сохраненных в линии
задержки, на соответствующие коэффициенты. Заметьте, что число коэффи-
циентов в фильтре всегда на единицу больше, чем порядок фильтра. Это свя-
зано с тем, что текущая выборка x(n), которая представляет собой входное
воздействие, всегда участвует в процессе вычислений.
Входные и выходные решетчатые функции показаны на рис. 7.2. Здесь x0,
x1 и т.д. — это последовательные выборки входной переменной фильтра.
Если фильтр имеет, например, пятый порядок, то будет всего шесть коэффи-
циентов фильтра h0, h1, h2, h3, h4, h5, которые участвуют в расчете выходного
воздействия:

y(n) = h0x(n) + h1x(n – 1) + h2x(n – 2) + h3x(n – 3) + h4x(n – 4) + h5x(n – 5). (7.4)

Если предположить, что буфер выборок вначале пуст, то выходное значе-


ние y0 будет рассчитываться только на основе входной выборки x0, выходное
значение y1 уже на основе двух выборок x0 и x1 и т.д. Начиная с шестой
выборки x5, все предыдущие выборки будут участвовать в расчете выходной
переменной фильтра, т.е. буфер выборок окажется полностью заполненным.

Рис. 7.1. Блок-схема фильтра с конечной импульсной характеристикой

180
Рис. 7.2. Входные и выходные решетчатые функции

Специальные команды умножения с накоплением


для реализации цифровых фильтров
В микроконтроллерах ‘C28xx предусмотрены специальные способы адре-
сации и специальные команды для эффективного решения задач цифровой
фильтрации. Речь идет, прежде всего, о сохранении входных выборок дан-
ных в так называемых кольцевых буферах в памяти данных и о применении
команд умножения с накоплением, в которых один из операндов команды
умножения является выборкой данных в кольцевом буфере, а другой — кон-
стантой в кодовой памяти или памяти данных. Такая команда может быть
выполнена в цикле повторения с числом повторений, равным порядку филь-
тра, и автоматическим накоплением всех частных произведений.
Команды умножения с накоплением оптимизированы для работы с веще-
ственными числами со знаком в формате с фиксированной точкой и могут
быть 16-разрядными и 32-разрядными. В последнем случае предусматрива-
ются две различные команды умножения с накоплением, позволяющие после-
довательно вычислить старшую и младшую части 64-разрядного результата.
Как правило, в цифровых фильтрах все переменные (выборки) и коэффи-
циенты представляются в относительных единицах, т.е. в форматах 1.15 или
1.31. Это не только обеспечивает высокую точность расчетов, но и позволяет
181
Рис. 7.3. Принцип реализации вычислений одновременно по двум выборкам

избежать переполнений в процессе выполнения операций накопления произ-


ведений. С этой целью обычно устанавливается автоматический сдвиг част-
ного произведения вправо на нужное число разрядов.
Принцип работы одной из самых мощных команд двойного умножения с
накоплением DMAC, оперирующей 16-разрядными выборками и коэффици-
ентами, показан на рис. 7.3. Команда обеспечивает реализацию вычислений
одновременно по двум «ответвлениям» диаграммы фильтра, т.е. по двум
выборкам x0, x1, последовательно адресуемым регистром-указателем XAR6,
и двум коэффициентам h0, h1, последовательно адресуемым регистром-
указателем XAR7. Результат первой операции умножения накапливается в
32-разрядном аккумуляторе A, а второй операции — в 32-разрядном регис-
тре произведения P. Команда может использоваться либо для решения одно-
временно двух разных задач фильтрации, либо для ускорения решения одной
задачи фильтрации (как продемонстрировано на рис. 7.3). Обе операции
умножения и накопления выполняются за один цикл процессора.
В мнемонике команды используются следующие обозначения:
• (ACC:P) — для задания двух 32-разрядных приемников результатов
операций умножения с накоплением;
• *XAR6 %++ — для специальной косвенной адресации двух первых
множителей (выборок), содержащихся в кольцевом буфере. После каждого
доступа текущее содержимое указателя XAR6 автоинкрементируется;
• *XAR7++ — для обычной косвенной адресации двух вторых множите-
лей (коэффициентов фильтра), расположенных в кодовой памяти. После каж-
дого доступа текущее содержимое указателя XAR7 автоинкрементируется.

Кольцевые буфера
В системе команд микроконтроллеров ‘C28xx поддерживается несколько
способов адресации кольцевых буферов. Один из них реализуется с помо-
щью так называемой «бит-реверсной адресации» — косвенной адресации с
изменением направления распространения бита переноса. Он используется
во всех сигнальных процессорах Texas Instruments и является своеобразной
«визитной карточкой» процессоров этой фирмы. Некоторая сложность и
182
«ненаглядность» метода заставили разработчиков фирмы предложить дру-
гие, более очевидные по технологии использования методы адресации. В
качестве примера рассмотрим один из них, представленный на рис. 7.4.
Буфер выборок адресуется с использованием специального способа адре-
сации через регистр-указатель XAR6. Признаком такой особой адресации
является символ %, стоящий после имени регистра-указателя: *XAR6 %++.
Указатель, по достижении адреса последней ячейки буфера (верхней границы
буфера) автоматически переходит на первую ячейку буфера. При декремен-
тировании указателя контроль нижней границы кольцевого буфера отсут-
ствует. Это связано с тем, что в большинстве алгоритмов можно обойтись
без реверса направления прохода кольцевого буфера.
Предполагается, что под кольцевой буфер выделяется область памяти
данных, выровненная по числу 256, т.е., начальный адрес кольцевого буфера
может быть любым с восемью младшими битами, равными нулю. Конечный
адрес кольцевого буфера отличается от начального величиной смещения,
заданной в регистре AR1(7:0), точнее в младшем байте регистра AR1. Таким
образом, длина кольцевого буфера ограничивается 256 словами. Примени-
тельно к фильтру с конечной импульсной характеристикой объем кольцевого
буфера позволяет реализовывать фильтры вплоть до 255-го порядка включи-
тельно, что достаточно для большинства приложений.

Загрузка
новой выборки
Начальное состояние x[n] Состояние
кольцевого буфера кольцевого буфера
после прохода после загрузки выборки

XAR6
00000000 x[n – 3] x[n – 4]
Начальный x[n – 2] x[n – 3]
адрес x[n – 1] x[n – 2]
x[n] x[n – 1]
x[n – N] x[n]
Старшая часть
XAR6 x[n – (N – 1)] x[n – N]
указателя адреса
кольцевого буфера x[n – (N – 2)] x[n – (N – 1)]
AR6(31:8)
не меняется
адрес
x[n – 8] x[n – 9]
x[n – 7] x[n – 8]
x[n – 6] x[n – 7]
XAR6 x[n – 5] x[n – 6]
x[n – 4] x[n – 5]

AR1(7:0)

Конечный
адрес

Рис. 7.4. Кольцевой буфер *AR6%++

183
Удобно хранить в кольцевом буфере выборки в обратном порядке — начи-
ная с самой «старой» выборки. В таком же порядке нужно записать и коэф-
фициенты фильтра в кодовую память: hN, hN – 1, hN – 2, …, h2, h1, h0, т.е. начиная
с коэффициента при самой «старой» выборке. Вычисления производятся,
начиная с самых «старых» значений выборок, в направлении «новых» значе-
ний с помощью команды умножения с накоплением MAC.
В этом случае перед запуском очередной процедуры фильтрации (нового
прохода фильтра) указатель кольцевого буфера XAR6 будет показывать на
«самую старую» выборку входной переменной x[n – N] (см. рис. 7.4). Она
должна быть заменена «новым» входным значением. Запись входной пере-
менной x[n] нужно выполнить по текущему указателю XAR6 c поставтоинк-
рементированием, используя адресацию кольцевых буферов. При этом ука-
затель будет автоматически показывать на «новое» самое «старое» значение:
для предыдущего прогона фильтра это переменная x[n – (N – 1)], а для оче-
редного прогона фильтра — переменная x[n – N] (см. правую часть рис. 7.4).
Операция умножения с накоплением сопровождается перемещением ука-
зателя XAR7 с автопостинкрементированием по буферу коэффициентов
фильтра в программной памяти и указателя XAR6 с автопостинкрементиро-
ванием и автоматическим контролем верхней границы кольцевого буфера, а
также возвратом к началу буфера при пересечении границы.
После вычисления выходной переменной фильтра указатель буфера коэф-
фициентов будет показывать на следующий после расположения таблицы
коэффициентов адрес («неизвестно куда») и должен быть обязательно пере-
инициализирован (при очередном вызове). Указатель кольцевого буфера вер-
нется на исходную позицию, т.е. будет опять показывать на «самую старую»
выборку, которая и должна быть заменена новым входным значением.
Если в кольцевом буфере расположены 16-разрядные слова, то их общее
количество будет на единицу больше порядка КИХ-фильтра, т.е. N + 1. При
этом в индексный регистр кольцевого буфера AR1(7:0) следует записать
число, равное порядку фильтра N. Например, фильтр пятого порядка имеет
шесть выборок данных, расположенных по адресам 0, 1, 2, 3, 4, 5. Макси-
мально допустимое смещение адреса кольцевого буфера равно пяти, т.е.
порядку фильтра. Если в кольцевом буфере расположены 32-разрядные
слова, то последняя ячейка кольцевого буфера будет иметь адрес 2*N, кото-
рый и должен быть загружен в индексный регистр AR1(7:0). Например, для
фильтра пятого порядка с 32-разрядными коэффициентами индексный
регистр кольцевого буфера загружается числом 5æ2 = 10.

Алгоритм расчета 16-разрядного КИХ-фильтра


Рассмотрим алгоритм расчета КИХ-фильтра с использованием команды
двойного умножения с накоплением, приведенный на рис. 7.5. При этом
одновременно извлекаются две выборки входных переменных и два коэффици-
ента, упакованных в 32-разрядные слова. После извлечения данных указатели
автопостинкрементируются (двукратно). Упаковка коэффициентов фильтра в
32-разрядные слова должна быть строго согласована с упаковкой выборок.
184
Линейный буфер Кольцевой буфер
коэффициентов выборок
XAR7 h5 h2 XAR6 x[n – 5] x[n – 2]
h4 h1 x[n – 4] x[n – 1]
h3 h0 x[n – 3] x[n]

+* +*
+
а)
Линейный буфер Кольцевой буфер
коэффициентов выборок
h5 h2 XAR6 x[n – 5] x[n – 2]
h4 h1 x[n – 4] x[n – 1]
h3 h0 x[n – 3] x[n]
XAR7
б)

Линейный буфер Кольцевой буфер


коэффициентов выборок
XAR7 h5 h2 XAR6 x[n – 2]
h4 h1 x[n – 4] x[n – 1]
h3 h0 x[n – 3] x[n]
в)

Линейный буфер Кольцевой буфер


коэффициентов выборок
XAR7 h5 h2 x[n – 3] x[n]
h4 h1 XAR6 x[n – 5] x[n – 2]
h3 h0 x[n – 4] x[n – 1]
г)

Рис. 7.5. Работа команды умножения с накоплением:


а, б — состояние до и после расчета; в — замена «самой старой» выборки; г — получение новой
выборки и инкрементирование указателя кольцевого буфера

Продемонстрируем механизм согласования на примере фильтра пятого


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

1 ⎫
y ( n ) = h 2 x ( n – 2 ) + h 1 x ( n – 1 ) + h 0 x ( n ); ⎪
2

y ( n ) = h 5 x ( n – 5 ) + h 4 x ( n – 4 ) + h 3 x ( n – 3 ); ⎬ (7.5)

1 2 ⎪
y ( n ) = y ( n ) + y ( n ). ⎭

185
Обе операции умножения с накоплением должны производиться с учетом
установленного режима сдвига произведения (PM). Последняя операция
суммирования осуществляется без сдвига. После завершения расчета указа-
тель XAR7 на таблицу коэффициентов фильтра должен быть переинициали-
зирован. Указатель кольцевого буфера XAR6 вернется в исходную позицию.
Старшее слово 32-разрядного кольцевого буфера будет содержать самую старую
выборку x[n – 5], которая замещается младшим словом — выборкой x[n – 2].
На освободившееся место необходимо записать новую выборку x[n]. Эта
запись должна сопровождаться инкрементированием указателя кольцевого
буфера. Таким образом, указатель кольцевого буфера будет по-прежнему
показывать на слово с «самой старой» выборкой (с точки зрения нового про-
гона фильтра). Можно начинать новый расчет.
Если порядок фильтра четный, то число коэффициентов будет нечетным.
Самый простой способ работы с таким фильтром состоит в том, чтобы
искусственно повысить порядок фильтра на единицу и сделать самый стар-
ший коэффициент равным 0. Например, для фильтра четвертого порядка
можно выполнить расчет на базе фильтра пятого порядка с коэффициентом
h5 = 0. Никаких других изменений не требуется.
Установка индексного регистра кольцевого буфера AR1[7:0] должна быть
выполнена, как для буфера с 32-разрядными выборками. Общее число 16-
разрядных слов в буфере N + 1. Адрес последнего 16-разрядного слова на
единицу меньше, т.е. N. Адрес последнего двойного слова еще на единицу
меньше, т.е. N – 1.
Рассмотренные принципы организации вычислений графически проил-
люстрированы на рис. 7.5.
Диаграмма последовательной загрузки выборок в предварительно очи-
щенный кольцевой буфер фильтра показана на рис. 7.6.
Прежде чем привести ассемблерный текст функции, выполняющей рас-
чет КИХ-фильтра, необходимо описать соглашения по умолчанию, установ-
ленные разработчиками транслятора с языка высокого уровня С/С++ для вза-
имодействия программ на языке С/С++ и модулей, написанных на
Ассемблере.

Контрольные вопросы
1. Найдите в справочнике по системе команд обычную (не сдвоенную) команду
умножения с накоплением MAC. Опишите алгоритм ее выполнения.
2. Сравните производительность команд MAC и DMAC. Есть ли смысл при реализа-
ции КИХ-фильтров пользоваться более сложной командой DMAC?
3. Какую ассемблерную команду выберите для реализации КИХ-фильтра, работаю-
щего с 32-разрядными коэффициентами и выборками в формате 1.31? Нужна ли в этом
случае младшая часть частичных произведений?

186
Рис. 7.6. Диаграмма последовательной загрузки выборок

187
7.2. ИНТЕРФЕЙС С ПРОГРАММОЙ НА АССЕМБЛЕРЕ,
ВЫЗЫВАЕМОЙ ИЗ СИ-ПРОГРАММЫ

Соглашения по умолчанию
Интерфейс с функцией, написанной на языке Ассемблер, может быть пра-
вильным только в том случае, если строго следовать соглашениям по вызову
функций, а также соглашениям по использованию регистров центрального
процессора. В этом случае из программы на С/С++ гарантируется:
доступ к переменным, определенным в программе на Ассемблере, и
вызов функций, реализованных на Ассемблере;
доступ к переменным и функциям, объявленным в программе на C/C++,
из программы на Ассемблере.
Следуйте строго рекомендациям, приведенным ниже:
• все функции, где бы они ни были написаны: в программе на языке
С/С++ или на языке Ассемблер — должны выполнять установленные согла-
шения;
• модификация специально выделенных регистров центрального процес-
сора, обслуживающих процесс обмена между программными модулями,
должна быть заблокирована. К этим специальным регистрам относятся:
регистры-указатели XAR1, XAR2, XAR3 и указатель стека SP;
• если указатель стека используется нормальным образом (в соответ-
ствии с принятыми соглашениями), то специально заботиться о нем нет
необходимости. Ассемблерная функция свободна в использовании стека.
Она может записывать в стек любое число переменных, но должна следить
за тем, чтобы до возврата из процедуры все записанные переменные были
извлечены из стека. Это и есть обязательное условие корректной работы с
указателем стека SP. Например, если по каким-либо причинам ассемблерной
функции понадобится регистр-указатель XAR1 (в частности, для хранения
смещения верхней границы кольцевого буфера), то его текущее содержимое
до выполнения тела функции необходимо сохранить в стеке, а перед выхо-
дом из функции — извлечь из стека;
• любые другие регистры процессора, кроме специально зарезервиро-
ванных XAR1, XAR2, XAR3, SP, могут свободно использоваться без необхо-
димости сохранения их содержимого;
• если вызываете Си-функцию из программы на языке Ассемблер, то
загрузите аргументы в предназначенные для этого регистры и запишите в
стек оставшиеся аргументы в соответствии с тем, как описано в пункте
«Вызов функции» (см. с. 189). Те же самые соглашения работают и при
вызове функции на языке Си;
• так как при вызове функций на языке Си по умолчанию используется
специальный регистр адреса возврата счетчика команд RPC, то и в проце-
дурах на Ассемблере должны использоваться команды вызова и возврата,
работающие с этим регистром: LCR и LRETR;
188
• переменные типа long и float должны сохраняться в памяти следую-
щим образом: младшее значащее слово по младшему адресу, старшее — по
старшему адресу;
• структуры возвращаются так, как описано на с. 191 в п. 5 «Как отвечает
вызванная функция»;
• модуль на языке Ассемблер не должен использовать секцию .cinit ни
для каких других задач, кроме инициализации глобальных переменных. Нару-
шение таблицы инициализации посредством записи в секцию .cinit другой
информации может вызвать непредсказуемые последствия;
• компилятор С/C++ автоматически добавляет к каждому идентифика-
тору предшествующий ему символ подчеркивания (_). Таким образом,
если хотите обратиться из модуля на языке Ассемблер к какому-либо объ-
екту на языке Си, то должны перед именем этого объекта указать символ
подчеркивания. Например, если на языке С/C++ объект имеет имя x, то из
программы на Ассемблере к нему следует обратиться по имени _x. Это сде-
лано для того, чтобы в программах на Ассемблере и С/С++ можно было
пользоваться одинаковыми идентификаторами без возникновения конфликта
имен;
• любой объект или функция, которые описаны в программе на языке
Ассемблер и которые должны быть доступны или вызываться из программы
на С/С++ должны быть объявлены как глобальные с помощью специальных
директив Ассемблера .global или .def. Эти директивы объявляют соответс-
твующие символические имена «публично доступными», что позволяет ком-
поновщику разрешать все ссылки на них из программы на языке C/C++;
• соответственно доступ из ассемблерной программы к функции на
языке С/С++ или какому-либо объекту должен сопровождаться объявлением
имени этой функции или объекта как внешнего имени с помощью директив
.global или .ref. В этом случае при трансляции ассемблерного файла будет
создана неразрешенная внешняя ссылка, разрешение которой выполнит уже
компоновщик (т.е. назначит фактический адрес).

Вызов функции
Если вызывающая функция производит вызов другой функции, то она
выполняет следующую последовательность действий:
1) содержимое некоторых регистров центрального процессора, которые
содержат значения переменных, не используемых в вызываемой функции,
однако необходимых для последующих расчетов после возврата результата
из вызываемой функции, должно быть сохранено в стеке, если вызываемая
функция может изменить значения этих регистров;
2) если вызываемая функция возвращает структуру, то вызывающая
функция выделяет место в памяти под эту структуру и передает адрес этого
места в вызываемую функцию в качестве первого аргумента. Обратите осо-
бое внимание на это правило, так как оно является базовым при создании
библиотечных функций на Ассемблере;
189
3) аргументы, передаваемые вызываемой функции, размещаются в регис-
трах и, если это необходимо, в стеке. При размещении аргументов в регист-
рах используется следующая схема:
а) если имеется несколько 32-разрядных аргументов (типа long или
float), то первый аргумент помещается в 32-разрядный аккумулятор
ACC(AH/AL). Все другие 32-разрядные аргументы или указатели функ-
ций размещаются в стеке в обратном порядке;
б) аргументы типа указателей (Pointer arguments) размещаются в
регистрах XAR4 и XAR5. Все другие указатели размещаются в стеке;
в) остающиеся 16-разрядные аргументы размещаются в следующей
последовательности, если это возможно: AL, AH, XAR4, XAR5;
4) некоторые оставшиеся аргументы, которые не были размещены в
регистрах, размещаются в стеке в обратном порядке. Таким образом, самый
левый аргумент будет записан в стек самым последним. Все 32-разрядные
аргументы выравниваются в стеке по четным адресам;
5) аргументы типа структуры передаются адресами этих структур. Вызы-
ваемая функция должна сделать локальную копию этой структуры.
Для функций, которые объявлены с пропусками, т.е. функций, которые
вызываются с варьируемым числом аргументов, соглашения несколько
модифицируются. Последний, явно задекларированный аргумент размеща-
ется в стек так, что его адрес в стеке может использоваться как ссылка для
доступа к другим незадекларированным аргументам.
Пример вызова функции, который показывает, где размещаются аргу-
менты, представлен ниже:
func1 (int a, int b, long c);
XAR4 XAR5 AH/AL
func1 (long a, int b, long c);
AH/AL XAR4 stack
vararg (int a, int b, int c,...)
AL AH stack
Вызванная функция возвращает управление вызывающей функции по
команде «длинного» возврата из подпрограммы LRETR. В счетчик команд
записывается значение из регистра RPC (это и есть адрес возврата), далее из
стека в регистр RPC восстанавливается предыдущий адрес возврата.

Как отвечает вызванная функция


Вызванная функция выполняет следующую последовательность действий:
1) если вызванная функция модифицирует регистры XAR1, XAR2 или
XAR3, то она должна сохранить их значения, так как вызывающая функция
предполагает, что значения в этих регистрах не должны меняться. Значения
во всех других регистрах могут быть модифицированы без сохранения их
содержимого;
2) вызванная функция резервирует достаточно места в стеке для размеще-
ния всех своих локальных переменных, а также аргументов функции. Это
размещение выполняется в самом начале тела функции добавлением кон-
станты к текущему значению указателя стека;
190
3) если вызванная функция ожидает аргумент типа структуры, то она
получит взамен указатель на эту структуру. Если записи в структуру дела-
ются из вызванной функции, то должно быть зарезервировано место в стеке
под локальную копию этой структуры и структура должна быть скопирована
с помощью указателя на структуру, переданного в функцию. Если записи в
структуру не делаются, то доступ к структуре из вызванной функции может
быть выполнен косвенно через аргумент-указатель на структуру. Необхо-
димо позаботиться о том, чтобы функция, которая принимает структуру в
качестве аргумента, была правильным образом описана как с точки зрения,
где она вызывается (так, чтобы в качестве аргумента был передан адрес
структуры), так и с точки зрения, где она декларируется (так, чтобы функция
выполнила копирование структуры в локальную копию структуры);
4) далее вызванная функция выполняет собственно код функции;
5) затем вызванная функция возвращает результат. Он помещается в один
из регистров процессора в соответствии со следующими соглашениями:
• 16-разрядное целое значение — в младшее слово аккумулятора AL;
• 32-разрядное целое значение — в аккумулятор ACC;
• 16- или 22-разрядный указатель в регистр XAR4.
Если функция возвращает структуру, то вызывающая функция выделяет
место в памяти под эту структуру и передает в качестве параметра адрес
области возврата через регистр XAR4. Для возврата структуры вызванная
функция копирует структуру в блок памяти, указатель на который передан с
помощью специального аргумента. В этом случае вызывающая функция
должна фактически сообщить вызываемой функции, куда вернуть структуру.
Например, в операторе s = f(x) s является структурой и функция f должна
вернуть структуру. На самом деле вызывающая функция должна передать в
качестве параметра в вызываемую функцию адрес структуры, т.е. вызов дол-
жен быть оформлен следующим образом f(&s,x). Функция f в процессе
выполнения скопирует возвращаемую структуру данных непосредственно в
s, выполняя автоматическое присваивание. Если вызывающая функция не
будет использовать возвращенное значение структуры, то в качестве первого
аргумента должно быть передано нулевое значение адреса. Это предписы-
вает вызываемой функции не копировать возвращаемую структуру. Следует
позаботиться о том, чтобы правильно объявить функцию, которая возвра-
щает структуру, как с точки зрения, откуда она вызывается (так, чтобы был
передан дополнительный аргумент), так и с точки зрения, где она описыва-
ется (чтобы функция «знала» о необходимости копирования результата);
6) вызванная функция высвобождает ранее зарезервированный локаль-
ный фрейм в стеке, вычитая из значения указателя стека SP ту же самую
величину, которая была добавлена при резервировании локального фрейма;
7) вызванная функция восстанавливает значения во всех регистрах, сохра-
ненные на первом шаге (XAR1, XAR2 или XAR3).
Пример, представленный на лист. 7.1, иллюстрирует, как функция на языке
Си по имени main вызывает ассемблерную функцию по имени asmfunc. Эта
функция asmfunc принимает свой единственный параметр (аргумент), добав-
ляет к нему константу 5, сохраняет полученное в глобальной переменной Си,
названной gvar, и возвращает результат в вызывающую функцию.
191
В этом примере объявление extern предписывает компилятору использо-
вать соглашение по использованию имен, принятое для программ на Си, т.е.
все глобальные Си-имена будут иметь то же самое имя с дополнительным
предшествующим символом подчеркивания. Таким образом, когда компиля-
тор пытается разрешить ссылку на глобальное имя asmfunc, он предпола-
гает, что программист на Ассемблере дал подпрограмме точно такое же имя,
но с символом подчеркивания и описал его в качестве «общедоступного»
(глобального имени) директивой .global _asmfunc.

Лист. 7.1. Фрагмент программы на языке Си


extern int asmfunc(int a);/* Внешняя функция asmfunc,
получающая один целый параметр a и возвращающая целый
результат. */
int gvar = 0;/* Глобальная переменная gvar,
инициализируемая 0 */
}
void main(){
int i = 5;/* Локальная переменная i,
инициализируемая 5-ой */
i = asmfunc(i);/* Вызов ассемблерной функции с
передачей ей значения i. */
}

Лист. 7.2. Фрагмент программы на языке Ассемблер


/* Фрагмент программы на языке Ассемблер: */
.global _gvar ;Переменная _gvar декларируется
;как внешняя.
.global _asmfunc ;Имя функции _asmfunc декларируется
;как глобальное.
_asmfunc:
MOVZ DP, #_gvar ;Указатель текущей страницы
;на глобальную переменную _gvar.
ADDB AL, #5 ;Добавить 8-разрядное целое со знаком
;(+5) к переданному через аккумулятор
;значению аргумента i.
MOV @_gvar, AL ;Сохранить в глобальной переменной
;_gvar.
LRETR ;Возврат из подпрограммы.
Параметр i передается в процедуру через регистр AL.
Фактически ассемблерная функция делает значительно больше, чем это
можно понять, глядя на программу, составленную на языке Си. Она берет
переданный ей через регистр AL аргумент типа целое и добавляет к нему
константу (+5). Результат остается в AL в качестве возвращаемого пара-
метра. Однако функция еще и записывает его в глобальную переменную
gvar, что абсолютно не отражено в исходом коде на Си.
192
7.3. ИССЛЕДОВАНИЕ РАБОТЫ МОДУЛЯ ФИЛЬТРА
С КОНЕЧНОЙ ИМПУЛЬСНОЙ ХАРАКТЕРИСТИКОЙ

Мы подготовили проект lab7 со всеми необходимыми файлами для иссле-


дования работы фильтра с конечной импульсной характеристикой и даже
сохранили рабочее пространство проекта (окружение проекта) в файле
lab7\debug\fir16.wks. Остается открыть этот файл, скомпилировать проект и
загрузить его в микроконтроллер. После запуска программы в режиме
ANIMATE в нижнем графическом окне увидите сигнал задания для филь-
тра, а в верхнем графическом окне — результат работы фильтра (рис. 7.7).
Попытаемся разобраться в структуре и содержании файлов проекта.
Откроем заголовочный файл fir.h. В нем содержится описание нового типа
переменной структуры цифрового фильтра с конечной импульсной харак-
теристикой FIR16 (лист. 7.3).

Рис. 7.7. Рабочая область программы в CCS

193
Лист. 7.3. Содержание заголовочного файла fir.h
typedef struct {
long *coeff_ptr; /* Указатель на коэффициенты
фильтра. */
long *dbuffer_ptr; /* Указатель на буфер выбо-
рок. */
int cbindex; /* Верхняя граница кольце-
вого буфера. */
int order; /* Порядок фильтра. */
int input; /* Входное значение (новая
выборка). */
int output; /* Выходное значение филь-
тра. */
void (*init)(void *); /* Указатель на функцию ини-
циализации. */
void (*calc)(void *); /* Указатель на функцию
обработки */
} FIR16;
Для создания нескольких экземпляров модулей цифровой фильтрации
необходимо просто объявить нужное число переменных типа FIR16. Таким
образом, в одной и той же программе могут работать сразу несколько филь-
тров разного порядка, с разными настройками (коэффициентами), каждый из
которых будет иметь свой собственный кольцевой буфер выборок и таблицу
коэффициентов. При этом алгоритм работы всех фильтров унифицируется.
По существу обработчик является общим. Каждый раз он будет работать с
теми параметрами, которые переданы ему при вызове.
Дальше описывается так называемый инициализатор структуры модуля филь-
тра FIR16_DEFAULTS, который обеспечивает инициализацию значений перемен-
ных типа FIR16 и настраивает указатели на нужные функции (лист. 7.4). Вслед за
этим приводится описание прототипов ассемблерных функций (лист. 7.5).
Лист. 7.4. Инициализатор структуры модуля фильтра
#define FIR16_DEFAULTS { (long *)NULL, \
(long *)NULL, \
0, \
50, \
0,\
0,\
(void (*)(void *))FIR16_init,\
(void (*)(void *))FIR16_calc}
Лист. 7.5. Прототипы ассемблерных функций
void FIR16_init(void *);
void FIR16_calc(void *);
В файле содержится также несколько инициализаторов коэффициентов
фильтров для наиболее часто применяемых фильтров: низкочастотного,
высокочастотного и полосового.
194
Для экспериментов предлагаем использовать фильтры 50-го порядка,
у которых число коэффициентов равно 51. Так как алгоритм фильтрации,
реализованный с помощью команды двойного умножения с накоплением,
требует объединения коэффициентов в длинные слова, то порядок фильтра
искусственно увеличивается на единицу и число слов с коэффициентами
оказывается равным (51 + 1)/2 = 26.
Помните, что каждый из коэффициентов представлен в формате 1.15
(в относительных единицах). Два коэффициента объединены в одно длинное
слово и используются в инициализаторе. Как рассчитать коэффициенты цифро-
вого фильтра с помощью специальных компьютерных программ, если задана
частота дискретизации фильтра, его тип и полоса пропускания, показано ниже.
Лист. 7.6. Инициализатор коэффициентов фильтра
#define FIR16_LPF50 {\
1292,4261126,4654326,5440731,6751414,8455303,10749008,\
13501458,16712654,20382597,24445752,28902122,33620635,38601293,\
43713025,48890297,54002037,59048247,63832319,68288718,\
72351908,75956353,79036519,81461331,83230791,84279361}

Инициализатор коэффициентов низкочастотного фильтра с полосой про-


пускания 100 Гц и частотой дискретизации 10 кГц приведен в лист. 7.6. Амп-
литудно-частотная (АЧХ) и фазочастотная (ФЧХ) характеристики такого
фильтра представлены на рис. 7.8.
Теперь рассмотрим файл fir.c, в котором используется модуль низкочастот-
ного фильтра 50-го порядка LPF, коэффициенты которого рассчитаны и нахо-
дятся в заголовочном файле fir.h. Фильтр обеспечивает частоту среза 100 Гц.

Рис. 7.8. АЧХ и ФЧХ фильтра низкой частоты

195
Лист. 7.7. Пример работы с КИХ-фильтром
/*======================================
* Работа 7.
* Пример использования фильтра FIR16.
*=====================================*/
#include "IQMathLib.h"
#include "fir.h"
#define FIR_ORDER 50 /*Порядок фильтра. */
// Создание экземпляра модуля цифровой фильтрации fir
FIR16 fir= FIR16_DEFAULTS;
/* Определение буфера выборок для фильтра 50-го порядка и
размещение в секции "firldb". */
#pragma DATA_SECTION(dbuffer,"firldb");
long dbuffer[(FIR_ORDER+2)/2];
/* Определение массива постоянных коэффициентов
и его инициализация. */
long coeff[(FIR_ORDER+2)/2]= FIR16_LPF50;
/* Определяем переменные.*/
float SigInFreq = 100.0; // Частота входного сигнала, Гц.
float FiltPer = 0.0001; // Время дискретизации фильтра, с.
long l_counter = 0; // Счетчик для контроля работы программы.
int sin_value = 0; // Значение синуса.
int sin_filter = 0; // Значения отфильтрованного сигнала.
/* Начало основной программы. */
void main()
{ /* Инициализация модуля фильтра. */
fir.order=FIR_ORDER; // Настраиваем порядок фильтра.
fir.dbuffer_ptr= dbuffer;
//Передаем адрес буфера выборок.
fir.coeff_ptr=coeff;
//Передаем адрес массива коэффициентов.
fir.init(&fir); //Вызываем функцию инициализации.
/*Основной цикл программы.*/
while(1)
{
l_counter++; // Инкрементируем счетчик.
// Определение шага дискретизации угла.
Teta+= _IQ16mpy(_IQ16(SigInFreq),_IQ16(FiltPer));
// Т.к. значение sin имеет формат 16.16, то
// входной сигнал приводим к формату 1.15.
sin_value =_IQ16sinPU(Teta)>>1;
fir.input = sin_value;
//Передаем новое значение на вход фильтра
fir.calc(&fir); //Вызываем функцию подсчета.
sin_filter = fir.output;
//Передаем значение в программу.
}
}

196
Практическая работа
1. Самостоятельно получите осциллограммы работы фильтра низкой час-
тоты, представленные на рис. 7.9.

а)

б)

в)
Рис. 7.9. Работа фильтра:
а—в — на частоте 50, 100 и 200 Гц соответственно

197
2. В каких единицах измеряется время на осциллограммах? Соответ-
ствует ли период входного и выходного сигнала заданной частоте?
3. Убедитесь, что амплитуда и угол отставания отфильтрованного сигнала
от входного соответствуют расчетным АЧХ и ФЧХ фильтра (см. рис. 7.8).
4. Измените код программы таким образом, чтобы получить входной
синусоидальный сигнал с шумом. Исследуйте степень подавления шума на
разных частотах.

7.4. ИСПОЛЬЗОВАНИЕ ПАКЕТА MatLab


ДЛЯ РАСЧЕТА КОЭФФИЦИЕНТОВ ФИЛЬТРА

Существует большое разнообразие цифровых фильтров (низкой частоты,


высокой частоты, полосовые и т.п.). Для удобства пользователей, применяю-
щих библиотеку цифровой фильтрации в своих приложениях, при участии
фирмы Texas Instruments разработаны специальные приложения к пакету
MatLab, позволяющие автоматизировать процесс расчета коэффициентов
для фильтров с заданными выходными характеристиками. Одно из таких
приложений — ezfir16_rus.m.
Для работы с программой запустите приложение MatLab, измените теку-
щую рабочую папку на c:\ti\user\lab7\matlab\ezFIR и загрузите проект
ezfir16_rus.
Программа предложит ввести необходимые данные и условия для расчета
фильтра:
1) порядок фильтра;
2) тип фильтра;
3) вариант фильтра;
4) частоту дискретизации (выборки);
5) частоту среза или полосу частот;
6) название файла для сохранения рассчитанных коэффициентов (необхо-
димо указывать расширение для текстового файла, например *.txt).
Интерфейс программы показан на рис. 7.10. Программа генерирует файл
из коэффициентов для программного модуля фильтра, готовый к примене-
нию, а также АЧХ и ФЧХ фильтра.

Практическая работа

1. Выполните генерацию коэффициентов фильтра низкой частоты 50-го


порядка с полосой пропускания 100 Гц и частотой дискретизации 10 кГц.
2. Выполните генерацию коэффициентов высокочастотного фильтра (про-
пускает только высокие частоты) 50-го порядка с частотой дискретизации
20 кГц и частотой среза 4000 Гц. Объясните вид АЧХ и ФЧХ фильтра, приве-
денный на рис. 7.11.
198
Рис. 7.10. Интерфейс программы расчета коэффициентов фильтра в среде MatLab

Рис. 7.11. АЧХ и ФЧХ высокочастотного фильтра

3. Рассчитайте параметры полосового фильтра с полосой пропускания


4000—6000 Гц, работающего с частотой дискретизации 20 кГц. Объясните
вид АЧХ и ФЧХ этого фильтра, представленный на рис. 7.12, а.
4. Рассчитайте параметры полосового фильтра задержки («режекторного»)
исключающего пропускание сигнала в диапазоне частот 4000—6000 Гц.
Объясните АЧХ и ФЧХ фильтра, изображенные на рис. 7.12, б.
199
а)

б)

Рис. 7.12. АЧХ и ФЧХ полосового фильтра:


а — с полосой пропускания; б — с полосой задержки

5. Выберите цифровой фильтр, который позволит подавить электромаг-


нитную помеху на частоте ШИМ 5 кГц во входном сигнале с датчика тока
фазы двигателя, максимальная частота которого не превышает 500 Гц.
Отладьте программу цифровой фильтрации.

Контрольные вопросы
1. Приведите конкретные примеры из области привода и силовой электроники необ-
ходимости использования различных типов цифровых фильтров.
2. Можно ли одновременно в приложении использовать несколько фильтров низкой
частоты с различной полосой пропускания? Будут ли они иметь общую программу-обра-
ботчик? Общий буфер выборок?
3. Почему буфер выборок располагается в специально выделенной для этого секции
данных, а все остальные переменные — в общей памяти данных?

200
7.5. ФИЛЬТРЫ 16- И 32-РАЗРЯДНЫЕ
С БЕСКОНЕЧНОЙ ИМПУЛЬСНОЙ ХАРАКТЕРИСТИКОЙ
Общая информация о рациональной структуре фильтра.
Биквадратные секции
В общем случае фильтр с бесконечной импульсной характеристикой IIR,
представленный на рис. 7.13, описывается следующим разностным уравнением:
N N
y(n) = – ∑ a k y ( n – k ) + k∑
k=0 =0
bk x ( n – k ) (7.6)

или эквивалентной передаточной функцией:


N –k
∑ bk z
k=0
H ( z ) = ----------------------
-. (7.7)
N –k
∑ k
k=0
a z

Проблема реализации подобных фильтров на базе арифметики конечной


точности заставляет разрабатывать различные структуры фильтров. Методы
прямой реализации фильтров IIR чрезвычайно чувствительны к квантова-
нию параметров и в общем случае не рекомендуются для использования в
практических приложениях. Можно показать, что разделение передаточной
функции на секции малого порядка с последующим параллельным соедине-
нием секций или их каскадированием значительно снижает чувствитель-
ность к квантованию коэффициентов фильтра. Таким образом, рекоменду-
ется использовать каскадную конфигурацию из секций второго порядка
(Second order section — SOS). Такие секции известны как биквадратные сек-
ции (Biquads). В соответствии с этим фильтры получили название биквад-
ратных фильтров. На следующей блок-схеме показана каскадная реализа-
ция IIR-фильтра четвертого порядка на основе двух биквадратных секций.

x(n) d1(n) b01 y1(n) d2(n) b02 y(n)


+ + + +

z–1 z–1

–a11 b11 –a12 b12

d1(n – 1) d2(n – 1)

z–1 z–1

–a21 b21 –a22 b22

d1(n – 2) d2 (n – 2)

Рис. 7.13. Блок-схема фильтра с бесконечной импульсной характеристикой

201
Рассмотрим принцип реализации биквадратного фильтра более подробно
на примере одной секции второго порядка.
Разностное уравнение для БИХ-фильтра второго порядка имеет вид
y(n) = b0x(n) + b1x(n – 1) + b2x(n – 2) – a1y(n – 1) – a2 y(n – 2). (7.8)
Выполняя дискретное преобразование Лапласса, получаем следующую
дискретную передаточную функцию:
–1 –2
y [ z ] b0 + b1 z + b2 z
H [ z ] = ---------- = ----------------------------------------------- . (7.9)
x[z] 1 + a1 z + a2 z
–1 –2

Этой передаточной функции соответствует прямая структура БИХ-филь-


тра второго порядка, показанная на рис. 7.14, а.
Структура содержит четыре элемента запаздывания на такт квантования
и, как уже указывалось, при фиксированной разрядной сетке для представле-
ния коэффициентов может не обеспечивать требуемую точность расчетов.
Представим дискретную передаточную функцию фильтра в виде произве-
дения двух передаточных функций:
–1 –2
y [ z ] b0 + b1 z + b2 z
H [ z ] = ---------- = ----------------------------------------------- =
x[z] 1 + a1 z + a2 z
–1 –2

⎛ ⎞

1
1 – 2
( –1
= H 1 [ z ]H 2 [ z ] = ⎜ ---------------------------------------------⎟ b 0 + b 1 z + b 2 z
–2
. ) (7.10)
⎝1 + a z + a z ⎠
1 2

x(n) y(n)
b0 +

z–1 z–1
x(n – 1) b1 –a1 y(n – 1)

z–1 z–1
x(n – 2) b2 –a2 y(n – 2)

а)

x(n) d(n) y(n)


+ b0 +

z–1
d(n – 1)
–a1 b1

z–1
d(n – 2)
–a2 b2

б)

Рис. 7.14. Структура БИХ-фильтра:


а, б — второго и четвертого порядков

202
Первая передаточная функция соответствует рекурсивному фильтру (РФ),
описываемому разностным уравнением:
d(n) = x(n) – a1d(n – 1) – a2d(n – 2), (7.11)

а вторая передаточная функция — нерекурсивному фильтру (НРФ), описыва-


емому разностным уравнением:
y(n) = b0d(n) + b1d(n – 1) + b2d(n – 2). (7.12)

В соответствии с этими двумя разностными уравнениями фильтр может


иметь каноническую структуру, содержащую только два элемента задержки
(вместо четырех).
Как видно, структура фильтра четвертого порядка, представленная на
рис. 7.14, б, состоит из двух каскадно-включенных фильтров второго
порядка. За счет каскадирования подобных структур можно реализовать
биквадратный фильтр любого порядка. Коэффициенты имеют двойной
индекс. Первое число относится к номеру коэффициента внутри биквадрат-
ной секции, а второе — к номеру секции.
Коэффициенты биквадратных секций каскадного фильтра могут быть
сгенерированы в пакете MatLab на основе требуемой спецификации филь-
тра (полосы пропускания, частоты среза и т.п.). Несмотря на то что общий
коэффициент усиления входного сигнала не может превысить единицу,
коэффициенты усиления соседних узлов в биквадратных секциях могут
существенно различаться, что определяется исключительно требуемыми
параметрами фильтра. Пользователь должен позаботиться об ограничении
коэффициентов усиления соседних узлов до единицы во избежание возмож-
ных переполнений.
Первым шагом является идентификация коэффициентов усиления каждой
биквадратной секции относительно входного сигнала, а затем масштабиро-
вание входа каждой подсекции в целях исключения переполнений. Масшта-
бирование входного сигнала секции эквивалентно масштабированию коэф-
фициента b в предыдущей секции.
Специалистами TI разработано приложение на MatLab, позволяющее
проектировать IIR-фильтры без переполнений на основе сформулированного
алгоритма. Отмасштабированные коэффициенты биквадратных секций (SOS),
коэффициент масштабирования входа (Input Scaling Factor), Q-формат,
используемый для представления коэффициентов, и число биквадратных
секций сохраняются в файле, который применяется для инициализации
фильтра IIR5BIQ16 и IIR5BIQ32. Коэффициент масштабирования входа
ограничивает коэффициент передачи первого узла первой биквадратной сек-
ции единицей. Пользователь может использовать приложения eziir16 и
eziir32 для генерации коэффициентов фильтра.
Схема расположения коэффициентов биквадратного фильтра в памяти
программ и буфера задержки в памяти данных представлена на рис. 7.15.
Данные для каждой секции располагаются в виде последовательного мас-
203
Буфер 16-разрядных Буфер задержки
коэффициентов (16-разрядный)

coeff_ptr
a21 Младший адрес dk(n – 1)

a11 dk(n – 2)

b21

b11
d2(n – 1)
b01
d2(n – 2)
a22
d1(n – 1)
a12 dbuffer_ptr
d1(n – 2)
b22

b12

b02

a2k

a1k

b2k

b1k

b0k Старший адрес

Рис. 7.15. Схема расположения коэффициентов БИХ-фильтра в памяти

сива коэффициентов a2k, a1k, b2k, b1k, b0k, где k — номер биквадратной сек-
ции. Каждая секция имеет в буфере выборок две ячейки для хранения пере-
менных dk(n – 1) и dk(n – 2). После выборки этих переменных в процессе
расчета фильтра должны выполняться операции копирования: dk(n – 1) →
→ dk(n – 2); dk(n) → dk(n – 1).
Отличие 32-разрядного фильтра от 16-разрядного состоит только в раз-
рядности коэффициентов и переменных. Коэффициенты и переменные
буфера выборок являются длинными словами. Младшее слово располагается
по меньшему адресу.

204
Исследование модуля фильтра
с бесконечной импульсной характеристикой (IIR)
Модуль реализует каскадный биквадратный фильтр с бесконечной
импульсной характеристикой (IIR filter) на основе 16- и 32-разрядной линии
задержки.
Откройте рабочее пространство lab7/debug/iir.wks. Скомпилируйте про-
ект и загрузите его в микроконтроллер (рис. 7.16).

Рис. 7.16. Рабочая область программы в CCS

В заголовочном файле iir.h описана структура объектов IIR5BIQ16 и


IIR5BIQ32:

Лист. 7.8. Содержание заголовочного файла iir.h


//Определение структуры модуля фильтра IIR5BIQ32.
typedef struct {
void (*init)(void *); //Указатель на функцию инициализации.
void (*calc)(void *); //Указатель на адрес вызова функции.
long *coeff_ptr; //Указатель на коэффициенты фильтра.
long *dbuffer_ptr; //Указатель на буфер задержки.
int nbiq; // Число биквадратных секций фильтра Q0.
int input; // Последняя входная выборка данных Q15.
long isf; // Коэффициент масштабирования входа.

205
Окончание лист. 7.8
int qfmat; /* Q формат коэффициентов Q0. */
int output; /* Выход фильтра Q14. */
}IIR5BIQ16;
//Определение структуры модуля фильтра IIR5BIQ32.
typedef struct {
void (*init)(void *); //Указатель на функцию инициализации.
void (*calc)(void *); //Указатель на адрес вызова функции.
long *coeff_ptr; // Указатель на коэффициенты фильтра.
long *dbuffer_ptr; // Указатель на буфер задержки.
int nbiq; /* Число биквадратных секций фильтра Q0. */
int input; /* Последняя входная выборка данных Q15. */
long isf; /* Коэффициент масштабирования входа. */
long output32; /* Выход фильтра в формате Q30. */
int output16; /* Выход фильтра в формате Q14. */
int qfmat; /* Q формат коэффициентов Q0. */
}IIR5BIQ32;
Определение модуля создается как специальный тип данных. Это удобно
для организации интерфейса с модулем фильтра IIR16. Для того чтобы
создать множество экземпляров модуля, необходимо просто объявить нуж-
ное число переменных типа IIR5BIQ16. Таким образом, в одной и той же
программе могут работать сразу несколько программ фильтрации.
Описание констант приведено ниже:
Лист. 7.9. Инициализатор структуры модулей фильтров
#define IIR5BIQ16_DEFAULTS { (void (*)(void
*))IIR5BIQ16_init,\
(void (*)(void *))IIR5BIQ16_calc,\
(int *)NULL, \
(int *)NULL, \
0, \
0, \
0, \
0, \
0}
#define IIR5BIQ32_DEFAULTS { (void (*)(void
*))IIR5BIQ32_init,\
(void (*)(void *))IIR5BIQ32_calc, \
(long *)NULL, \
(long *)NULL, \
0, \
0, \
0, \
0, \
0, \
0}

206
IIR5BIQ16_DEFAULTS и IIR5BIQ32_DEFAULTS — структуры сим-
вольных констант для инициализации модулей IIR5BIQ16 è IIR5BIQ32.
Описываются прототипы функций для инициализации фильтра IIR и для
вычисления выходного значения фильтра:

Лист. 7.10. Прототипы ассемблерных функций


//Прототипы для функций
void IIR5BIQ16_calc(void *);
void IIR5BIQ32_calc(void *);
void IIR5BIQ16_init(IIR5BIQ16 *);
void IIR5BIQ32_init(IIR5BIQ32 *);

Приводятся структуры с рассчитанными коэффициентами для низкочас-


тотного, высокочастотного и полосового 16- и 32-разрядных фильтров. При-
мер такой структуры для низкочастотного фильтра показан ниже:

Лист. 7.11. Инициализатор коэффициентов фильтра


/* Коэффициенты для низкочастотного 16-разрядного
фильтра */
#define IIR16_LPF_COEFF {-14150,30370,4469,8937,4469}
#define IIR16_LPF_ISF150
#define IIR16_LPF_NBIQ1
#define IIR16_LPF_QFMAT14

Рассмотрим структуру основного файла iir.c, изображенную ниже:

Лист. 7.12. Пример работы с БИХ-фильтром


#include "iir.h"
#include "IQmathlib.h"
/* Создание экземпляра модуля IIR5BIQD16 */
IIR5BIQ16 iir=IIR5BIQ16_DEFAULTS;
/* Определение буфера задержек для каскадного IIR филь-
тра с 6 биквадратными секцями и размещение его (буфера)
в секции "iirfilt". */
#pragma DATA_SECTION(dbuffer,"iirfilt");
int dbuffer[2*IIR16_LPF_NBIQ];
/* Определение массива коэффициэнтов. */
const int coeff[5*IIR16_LPF_NBIQ]=IIR16_LPF_COEFF;
/* Определение переменных.*/
float SigInFreq = 100.0; //Частота входного сигнала, Гц.
float FiltPer = 0.0001; //Период дискретизации фильтра, с.
_iq Teta =0; //Электрический угол в относительных единицах
long l_counter = 0; //Счетчик для контроля работы программы
int sin_value = 0; // Значение синуса входного сигнала

207
Окончание лист. 7.12
int sin_filter = 0; //Значение выходного отфильтрован-
ного сигнала.
/* Начало основной программы. */
void main(void)
{/* Инициализация модуля фильтра. */
iir.dbuffer_ptr=dbuffer;
iir.coeff_ptr=(int *)coeff;
iir.qfmat=IIR16_LPF_QFMAT;
iir.nbiq=IIR16_LPF_NBIQ;
iir.isf=IIR16_LPF_ISF;
iir.init(&iir);
/* Основной цикл программы. */
while(1)
{
l_counter++; // Инкрементируем счетчик.
//Определение приращения электрического угла
Teta+= _IQ16mpy(_IQ16(SigInFreq),_IQ16(FiltPer));
//Так как выходное значение функции sin имеет
//формат 16.16, приводим входной сигнал к формату 1.15
sin_value =_IQ16sinPU(Teta)>>1;
iir.input = sin_value;
iir.calc(&iir);
sin_filter = iir.output;
}
} /* Конец программы. */

Обратите внимание на то, что у 32-разрядного фильтра две выходные


переменные 16- и 32-разрядная: output16 и output32.

Практическая работа
1. Исследуйте приложение MatLab для генерации коэффициентов IIR-
фильтров.
2. Пользуясь описанной в предыдущей главе технологией, убедитесь в
правильности работы IIR-фильтра низкой частоты.

Контрольные вопросы
1. К какому из типов фильтров (КИХ, БИХ) можно отнести простейшие ПИ-, ПИД-,
ПИИ-регуляторы?
2. Какова последовательность работы с библиотечными функциями цифровой филь-
трации, написанными на Ассемблере?

208
Глава 8

ТЕХНОЛОГИЯ РАЗРАБОТКИ И ОТЛАДКИ


СИСТЕМЫ ЦИФРОВОГО УПРАВЛЕНИЯ
ДВИГАТЕЛЕМ ПОСТОЯННОГО ТОКА

8.1. АППАРАТНАЯ СТРУКТУРА СИСТЕМЫ УПРАВЛЕНИЯ

Аппаратная структура системы управления двигателем постоянного тока


(ДПТ) с независимым возбуждением, построенная на базе классического
мостового шестиключевого инвертора напряжения на IGBT-транзисторах
или интеллектуальных силовых модулях представлена на рис. 8.1. Две
стойки моста используются для реализации мостового четырехключевого
реверсивного широтно-импульсного преобразователя напряжения для управ-
ления по цепи якоря (далее ПЯ) и одна стойка (нижний силовой ключ и верх-
ний обратный диод) — для реализации нереверсивного ШИМ-преобразова-
теля для управления двигателем по цепи возбуждения (далее ПВ). Таким

Udc

le_зад
ПИ
Силовой
Модуль
трехфазный
ШИМ
инвертор
la_зад
ПИ

le

la

ДПТ

Рис. 8.1. Аппаратная структура системы управления

209
образом, на базе классического инвертора напряжения создается сразу два
преобразователя для двухзонной (с ослаблением поля и без) четырехквад-
рантной системы управления ДПТ, которая применяется обычно в станоч-
ных электроприводах.
Система управления реализуется на сигнальном микроконтроллере и
обеспечивает формирование ШИМ-сигналов управления четырьмя ключами
ПЯ и одним ключом ПВ на базе многоканального ШИМ-генератора менед-
жера событий. Несущая частота ШИМ высокая — до 5—10 кГц. Обратные
связи по току якоря (далее индекс «а») и току возбуждения (далее индекс
«е») реализуются с помощью гальванически развязанных датчиков тока типа
«ЛЕМ», выходные сигналы которых вводятся в контроллер через АЦП. Регу-
ляторы токов программно реализованные.
На схеме не показаны датчик скорости, в качестве которого можно
использовать тахогенератор или импульсный датчик положения, и регулятор
скорости. В первом случае для ввода сигнала с тахогенератора используется
АЦП, а во втором — «квадратурный декодер» менеджера событий. Регуля-
тор скорости, как и регуляторы токов, реализуется программно.
Все расчеты в системе управления должны выполняться за один период
ШИМ, который является интервалом дискретизации. Модель объекта управ-
ления может иметь тот же самый интервал квантования. В данной главе в
целях упрощения системы к объекту управления отнесены кроме двигателя
и силовых преобразователей также датчики токов и скорости.

8.2. МАТЕМАТИЧЕСКАЯ МОДЕЛЬ ДВИГАТЕЛЯ


ПОСТОЯННОГО ТОКА В ОТНОСИТЕЛЬНЫХ ЕДИНИЦАХ

Математическое описание двигателя постоянного тока в физических


координатах, без учета насыщения в цепи возбуждения, при управлении
напряжением по цепи возбуждения и по цепи якоря хорошо известно:
di
U в = R в i в + L в ------в- ; (8.1)
dt
di
U я = R я i я + L я ------я- + ( kΦ )ω ; (8.2)
dt

M – M с = J Σ dω
------- ; (8.3)
dt
M = (kΦ)iя; (8.4)
( kΦ ) ном
kΦ = --------------------
-i . (8.5)
i в ном в
Первое уравнение описывает контур возбуждения машины, второе —
цепь якоря, третье — является уравнением движения, четвертое — опреде-
ляет связь между током якоря и электромагнитным моментом машины,
210
а пятое — между потоком и током возбуждения. В общем случае последнее
уравнение может быть нелинейным и задавать так называемую кривую
намагничивания машины. Модель двигателя постоянного тока имеет
несколько параметров:
Rв — активное сопротивление цепи возбуждения;
Lв — индуктивность цепи возбуждения;
Rя — активное сопротивление цепи якоря;
Lя — индуктивность цепи якоря;
(kΦ)ном — номинальное значение коэффициента передачи между током
якоря iя и электромагнитным моментом M, между электродвижущей силой E
и скоростью ω;
JΣ — суммарный момент инерции двигателя и присоединенных масс;
Mс — момент статической нагрузки.
Смоделируем работу двигателя непосредственно в сигнальном микрокон-
троллере. Для повышения скорости вычислений откажемся от использова-
ния арифметики с плавающей точкой в пользу арифметики с фиксированной
точкой, поддерживаемой библиотекой IQMath. При этом обязательным тре-
бованием является переход от физических единиц к относительным.
Удобно за базовые величины принять такие, которые характеризуют
работу двигателя в номинальном режиме. Так как кратности превышения
номинальных переменных машины (тока якоря, тока возбуждения, момента,
скорости) не превышают 5—7, то для представления переменных можно
использовать формат 4.28, обеспечивающий наибольшую точность расчетов.
Формат 8.24 позволяет получить несколько больший и гарантированно
достаточный динамический диапазон представления переменных без суще-
ственного ухудшения точности. В качестве базового времени удобно выбрать
единицу физического времени — 1 с. При этом все процессы в модели будут
протекать в том же масштабе времени, что и в реальном двигателе.
Итак, за базовые значения переменных в цепи возбуждения принимаются:
Uв баз = Uв ном; (8.6)
iв баз = iв ном; (8.7)
tбаз = Tбаз = 1 с. (8.8)
Если выбрать базовые значения сопротивления и индуктивности в кон-
туре возбуждения через уже определенные базовые величины:
U в баз
R в баз = -------------- ; (8.9)
i в баз

U в баз T баз
L в баз = ------------------------- , (8.10)
i в баз

211
то уравнение цепи возбуждения в относительных единицах не будет отли-
чаться от соответствующего уравнения в физических единицах, за исключе-
нием символа «*», характеризующего относительные переменные:
*
* * * * di в
Uв = Rв iв + L в ------- , (8.11a)
*
dt
или
*
* *⎛ * * di в⎞
Uв = Rв ⎜ iв + T в -------⎟ , (8.11б)
⎝ *
dt ⎠
*
где R в — безразмерное значение активного сопротивления цепи возбужде-
*
ния; L в — безразмерное значение индуктивности цепи возбуждения;
*
* Lв
Tв = ------ — безразмерная постоянная времени контура возбуждения, чис-
*

ленно совпадающая со значением реальной постоянной времени.
Принимая для цепи якоря за базовые значения электрических перемен-
ных (напряжения и тока) их номинальные значения, а за базовое значение
скорости — скорость идеального холостого хода двигателя при номинальном
напряжении на якоре и номинальном токе возбуждения, получаем похожее
уравнение, отличающееся от своего аналога в физической области только
символами «*» безразмерных переменных и параметров:
Uя баз = Uя ном; (8.12)
iя баз = iя ном; (8.13)
tбаз = Tбаз = 1 с; (8.14)

U я баз
R я баз = -------------- ; (8.15)
i я баз

U я баз T баз
L я баз = ------------------------- ; (8.16)
i я баз

(kΦ)баз = (k)ном; (8.17)

U я баз
ω баз = ω 0 ном = ------------------- , (8.18)
( kΦ ) баз

откуда
212
*
* * * * di я * *
Uя = Rя iя + L я ------- + ( kΦ ) ω , (8.19)
*
dt
или
*
* *⎛ * * di я⎞ * *
Uя = Rя ⎜ iя + T я -------⎟ + ( kΦ ) ω , (8.20)
⎝ *
dt ⎠
*
где R я — безразмерное значение активного сопротивления цепи якоря;
*
* Lя *
Lя — безразмерное значение индуктивности цепи якоря; = ------ — безраз- Tя
*

мерная постоянная времени якоря, численно равная электромагнитной посто-
янной времени двигателя в физических единицах.
Примем за базовое значение электромагнитного момента двигателя его
номинальное значение при номинальном токе якоря и номинальном токе воз-
буждения:
Mбаз = Mном = (kΦ)номiя ном. (8.21)
В теории электропривода инерционность механических процессов учи-
тывается так называемой электромеханической постоянной времени. Ее зна-
чение равно времени разгона двигателя без нагрузки от нуля до скорости
идеального холостого хода при номинальном значении электромагнитного
момента:
J Σ ω баз
T м = ----------------- . (8.22)
M баз
С учетом этих соотношений уравнение механического равновесия двига-
теля в относительных единицах примет вид
*
* * * dω
M – M c = T м ---------- , (8.23)
*
dt

* Tм
где T м = ---------- — безразмерная электромеханическая постоянная времени.
T баз
Два последних уравнения, связывающих между собой ток с моментом и
поток с током возбуждения, в относительных единицах примут еще более
простой вид:
* * *
M = ( kΦ ) i я ; (8.24)
* *
( kΦ ) = i в . (8.25)

213
Таким образом, двигатель постоянного тока с управлением по цепи якоря
и возбуждения может быть описан системой дифференциальных уравнений
третьего порядка:
*
* *⎛ * * di в⎞
Uв = Rв ⎜ iв + T в -------⎟ ; (8.26)
⎝ *
dt ⎠
*
* *⎛ * * di я⎞ * *
Uя = Rя ⎜ iя + T я -------⎟ + iв ω ; (8.27)
⎝ *
dt ⎠
*
* * * * dω
i в i я – M c = T м ---------- . (8.28)
*
dt
Обратите внимание на то, что динамика двигателя полностью определя-
*
ется соотношением постоянных времени: возбуждения T в , электромагнит-
* *
ной T я и электромеханической T м . Более того, численные значения безраз-
мерных постоянных времени равны соответствующим физическим
значениям этих постоянных. Это очень удобно при отладке и настройке сис-
темы управления. Выбранная система базовых величин действительно упро-
щает цифровую реализацию модели ДПТ, однако не лишена недостатков.
Необходимо помнить, что базовые значения токов и напряжений, сопротив-
лений и индуктивностей в цепях якоря и возбуждения различаются. Вслед-
ствие этого, например, базовые значения мощностей по цепям якоря и воз-
буждения различны. Для расчета полной мощности, потребляемой
двигателем, потребуется корректирующий коэффициент.
Структурная схема двигателя постоянного тока в относительных единицах
представлена на рис. 8.2. Соответствующие передаточные функции двигателя
будут использованы далее при синтезе регуляторов системы управления.

Uв* 1/Rв* iв*


Tв*p +1

ω*
x
iв*
E* –
Uя* 1/Rя* iя* M* 1 ω*
x
+ Tя*p + 1 + – Tм*p

Mс*

Рис. 8.2. Структурная схема двигателя постоянного тока в относительных единицах

214
Контрольные вопросы
1. Получите передаточную функцию ДПТ по управлению по цепи якоря, считая, что
ток в контуре возбуждения установлен и равен номинальному.
2. Отношением каких двух постоянных времени будет определяться качество пере-
ходных процессов в двигателе? Сформулируйте условие апериодического процесса,
колебательного.

8.3. ДИСКРЕТНАЯ МОДЕЛЬ ДВИГАТЕЛЯ ПОСТОЯННОГО ТОКА

Как уже отмечали, модель двигателя будет рассчитываться в микроконт-


роллере на интервале дискретизации Tдиск, равном периоду ШИМ. Для пере-
хода от непрерывных уравнений к разностным представим полученную в
(8.26)—(8.28) систему уравнений в форме задачи Коши:
*
di в 1 * 1- i *
------- = ------------ U – ----- ; (8.29)
* * * в * в
dt Rв Tв Tв

*
di я * * * 1- i *
*
1
------- = ------------
* *
( U я – i в ω ) – -----
* я
; (8.30)
dt Rя Tя Tя

*
1 * * *
- ( i i – Mс ) .

---------- = ----- (8.31)
* * в я
dt Tм
Заменив непрерывные переменные дискретными, а производные левыми
разностями:
* * *
dx x [k] – x [k – 1]
-------- ⇒ -------------------------------------------- ,
* *
dt T диск
получим:
* *
iв [ k ] – iв [ k – 1 ] 1 * 1 *
------------------------------------------ = ------------ U в [ k ] – ------ i в [ k ] ; (8.32)
* * * *
T диск Rв Tв Tв

* *
iя [ k ] – iя [ k – 1 ] *
1
------------------------------------------ = ------------
* * *
( U я [ k ] – i *в [ k ]ω * [ k ] ) – -----
1 *
-i [k] ;
* я
(8.33)
T диск Rя Tя Tя

* *
ω [k] – ω [k – 1] 1 * * *
- ( i [ k ]i я [ k ] – M с ) .
---------------------------------------------- = ----- (8.34)
* * в
T диск Tм

215
После выделения значений переменных на k-м интервале квантования
через значения переменных на предыдущем (k – 1)-м интервале квантования,
система разностных уравнений примет вид:
* * *
iв [ k ] = K1 Uв [ k ] + K2 iв [ k – 1 ] ; (8.35)

* * * * *
i я [ k ] = K 3 ( U я [ k ] – i в [ k ]ω [ k ] ) + K 4 i я [ k – 1 ] ; (8.36)
* * * * *
ω [ k ] = K 5 ( i в [ k ]i я [ k ] – M с ) + ω [ k – 1 ] , (8.37)
где значения пяти коэффициентов, описывающих поведение двигателя в
динамике, равны:
*
T диск
K 1 = --------------------------------------- ; (8.38)
* * *
R в ( T в + T диск )
*

K 2 = -------------------------- ; (8.39)
* *
T в + T диск
*
T диск
K 3 = --------------------------------------- ; (8.40)
* * *
R я ( T я + T диск )
*

K 4 = -------------------------- ; (8.41)
* *
T я + T диск
*
T диск
K 5 = ------------- . (8.42)
*

Как видите, дискретная модель ДПТ представляет собой систему разно-
стных уравнений третьего порядка, реализация которой в микроконтроллере
требует использования, главным образом, операций умножения с накопле-
нием. Коэффициенты K1—K5 вычисляются один раз в процессе инициализа-
ции системы по пяти параметрам двигателя и относительному значению
интервала квантования.

8.4. ПЕРЕДАТОЧНЫЕ ФУНКЦИИ СИЛОВЫХ ПРЕОБРАЗОВАТЕЛЕЙ

Для упрощения работы реальные силовые преобразователи моделируются


вместе с двигателем. При этом реализуются только полученные статические
выходные характеристики преобразователей, представленные на рис. 8.3—
8.5. Динамические особенности преобразователей, связанные с эквивалент-
216
Uвых ПЯ U *вых ПЯ
+Ud*c я
+Udc

0 0,5 1 γ –1 +1 U *
y ПЯ

–Udc
–Ud*c я

а) б)

Рис. 8.3. Выходные характеристики преобразователя в цепи якоря

+Ud*c я
Uy*ПЯ Ud*c я U *вых ПЯ

Tп*p + 1
–Ud*c я

Рис. 8.4. Блок-схема преобразователя в цепи якоря

+Ud*c в
Uy*ПВ Ud*c в U *вых ПВ

Tп*p + 1
–Ud*c в

Рис. 8.5. Блок-схема преобразователя в цепи возбуждения

ным запаздыванием на интервал дискретизации, автоматически учитыва-


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

Силовой преобразователь в цепи якоря


Преобразователь в цепи якоря (ПЯ) построен по классической мостовой
схеме инвертора напряжения и является реверсивным. Для получения пере-
даточной функции ПЯ примем следующие допущения:
1) преобразователь в цепи якоря работает во втором импульсном режиме
(+U, –U) на фиксированной частоте ШИМ. Прямой сигнал управления пода-
ется одновременно на левый верхний и правый нижний силовой ключи, а
инверсный сигнал — одновременно на левый нижний и правый верхний
ключи. Для управления инвертором достаточно одного канала ШИМ-генера-
тора с комплиментарными выходами;
2) ШИМ-генератор микроконтроллера обеспечивает автоматическую
защиту силовых ключей каждой стойки инвертора по «мертвому времени».
217
Значение величины «мертвого времени» на два порядка меньше периода
ШИМ и может не учитываться в математическом описании инвертора;
3) ШИМ-генератор автоматически перезагружается новой уставкой
скважности ШИМ γ в начале очередного периода ШИМ. Так как переза-
грузка из теневого регистра скважности в рабочий регистр скважности про-
изводится автоматически аппаратно в начале очередного периода ШИМ, то
преобразователь имеет эквивалентное запаздывание по управлению на один
период ШИМ, т.е. на интервал дискретизации Tдиск.;
4) в первом приближении звено запаздывания можно заменить инерцион-
ным звеном с такой же постоянной времени TПЯ = Tдиск;
5) входное напряжение ПЯ будем считать постоянным и равным напряже-
нию в звене постоянного тока Udc.
Выходная характеристика преобразователя в цепи якоря — зависимость
выходного напряжения от скважности управляющего ШИМ-сигнала пока-
зана на рис. 8.3, a. Аналитически она описывается соотношением.
Uвых ПЯ = Udc(–1 + 2γ).
Значение величины скважности γ меняется в диапазоне от 0 до 1. Введем
в рассмотрение входное управляющее воздействие преобразователя, связан-
ное со скважностью соотношением:
*
U y ПЯ = ( – 1 + 2γ ) .
С учетом этого и после перехода к относительным единицам выходная
характеристика преобразователя в цепи якоря, приведенная на рис. 8.3, б,
примет вид:
* * *
U вых ПЯ = U dc я U y ПЯ ,
* U dc
где U dc я = -------------- .
U я баз
Таким образом, коэффициент передачи преобразователя в цепи якоря
равен безразмерному значению напряжения в звене постоянного тока по
отношению к базовому напряжению цепи якоря. Итоговая передаточная фун-
кция ПЯ показана на рис. 8.4. Силовой преобразователь представляется
*
инерционным звеном с коэффициентом усиления U dc я и постоянной вре-
* *
мени T п , равной интервалу дискретизации T диск . На выходе инерционного
*
звена имеется звено ограничения сигнала на уровне ± U dc я .

Силовой преобразователь в цепи возбуждения


Отличительной особенностью этого преобразователя является его нере-
версивность. Он построен на базе одного силового ключа, управляемого
широтно-импульсным сигналом с регулируемой скважностью и одного
218
обратного диода. Преобразователь работает в первом импульсном режиме
(+U, 0). Выходная характеристика преобразователя имеет вид
Uвых ПВ = Udcγ.
Входное управляющее воздействие преобразователя в относительных
единицах равно скважности ШИМ-сигнала:
*
U y ПВ = γ .
С учетом базового значения напряжения в цепи возбуждения выходная
характеристика ПВ в относительных единицах примет вид
* * *
U вых ПВ = U dc в U y ПВ ,
* U dc
где U dc в = -------------- .
U в баз
*
Так как эквивалентная инерционность преобразователя T п такая же, как и
для преобразователя в цепи якоря, передаточная характеристика ПВ отличается
от передаточной характеристики ПЯ только значением коэффициента передачи
и уставкой блока ограничения выходного сигнала, изображенного на рис.