PIClist RUS
микроконтроллеры PIC и интерфейсы
техническая документация
статьи и разработки на русском языке

Уроки PIC24 - Глава 2. Главный цикл

« назад на главную страницу

Оригинал: "Programming 16-Bit PIC Microcontrollers in C. Learning to Fly the PIC24", Lucio Di Jasio, 2007

Перевод с английского © piclist.ru.


План урока

В этом уроке мы рассмотрим синтаксис основных циклов языка Си, а также познакомимся с ещё одним периферийным модулем - 16-разрядным таймером Timer1. Также мы впервые воспользуемся двумя новыми возможностями MPLAB SIM: режимом "Animate" ("Анимация") и инструментом "Logic Analyzer" ("Логический анализатор").

Перечень необходимого

Для этого урока нам потребуются те же самые программные компоненты, которые мы использовали ранее:
1) среда разработки MPLAB IDE;
2) программный симулятор MPLAB SIM;
3) компилятор MPLAB C30.

Теперь создайте в MPLAB IDE новый проект:
1) Выберите "Project->Project Wizard", чтобы начать создание нового проекта;
2) Выберите устройство PIC24FJ128GA010, нажмите "Next";
3) Выберите компилятор MPLAB C30 и нажмите "Next";
4) Создайте новую папку с именем "Loop", назовите проект "A Loop in the Pattern" и нажмите "Next";
5) Копировать файлы из предыдущего урока не нужно, нажмите "Next";
6) Нажмите "Finish".

Теперь добавьте в проект файл "p24fj128ga010.gld". Как правило, его можно найти в каталоге с установленным MPLAB IDE "C:/Program Files/Microchip/" в подкаталоге "MPLAB C30/support/gld/".

7) Откройте окно редактора;
8) Наберите заголовок главной программы:


// 
//   Главный цикл
//

9) Выберите "Project->AddNewFiletoProject" и сохраните файл как "loop.c", он автоматически добавится в список исходных файлов проекта;
10) Сохраните проект.

Урок

Один из главных вопросов, который может придти на ум после первого урока, - а что же произойдёт, когда выполнится весь код функции main()? На самом деле не произойдёт ничего особенного: микроконтроллер просто "сбросится", и вся программа выполнится снова... и снова... Потому что компилятор сразу после кода функции main() помещает специальную команду программного сброса.

Во встраиваемых приложениях нам нужно, чтобы программа выполнялась бесконечно с момента включения питания и до его выключения. А если мы позволяем программе полностью выполняться, сбрасываться и снова выполняться, то это как раз похоже на такую организацию приложения, при которой программа выполняется, пока на микроконтроллер подается питание. Но такой вариант может работать только в некоторых ограниченных случаях, и, выполняя программу в таком "цикле", вы делаете её весьма ущербной. Достигнув конца программы и выполнив сброс, микроконтроллер окажется в самом начале кода, и ему придётся выполнять всю инициализацию, включая код C0. И как бы ни была коротка инициализационная часть, это сделает "цикл" неполноценным: в самом деле, нет никакой необходимости в инициализации всех SFR-регистров и глобальных переменных при каждом выполнении программы, и это, конечно же, замедлит приложение. Поэтому лучше заняться разработкой особого, т.н. "главного цикла" приложения. Давайте рассмотрим основные варианты организации циклов на языке Си.1

1 В этой главе рассматривается только цикл while, другие же варианты циклов см. в следующей главе. - прим. пер.

Цикл while

В языке Си, по меньшей мере, есть три способа кодирования циклов. Первый из них - цикл while:

while (x)
{
  // здесь располагается ваш код...
}

Всё, что вы поместите между двумя фигурными скобками ({}) будет повторяться до тех пор, пока логическое выражение в круглых скобках (x) возвращает значение "истина". Но что же такое логическое выражение на Си?

Прежде всего, стоит сказать, что в языке Си нет различий между логическим и арифметическим выражением. В языке Си значения булевой логики TRUE ("истина") и FALSE ("ложь") представляются целыми числами по простому правилу:

- FALSE представлена целым нулём (0)
- TRUE представлена любым целым кроме 0

Так что числа "1", "13" и даже "-278" - это "истина".

Для оценки логических выражений используются следующие логические операторы:

||   логический оператор OR
&&   логический оператор AND
!    логический оператор NOT

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

(пусть a = 17, b = 1, т.е. оба имеют значение "истина")

( a || b )   = истина
( a && b )   = истина
( !a )       = ложь

А вот операторы, которые сравнивают числа (любые типы целых чисел, а также с плавающей точкой) и возвращают логическое значение:

==   оператор "равно" - обратите внимание, что он состоит из двух знаков равенства, чтобы отличить его от оператора присваивания;
!=   оператор "не равно";
>    оператор "больше";
>=   оператор "больше или равно";
<    оператор "меньше";
<=   оператор "меньше или равно".

Вот несколько примеров:

(пусть a = 10)

( a > 1 )     = истина
( -a >= 0 )   = ложь
( a == 17 )   = ложь
( a != 3 )    = истина

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

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

Вот несколько любопытных примеров циклов:

 while (0)
{
 // здесь располагается ваш код...
}

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

А вот другой пример:

 while (1)   
{
 // здесь располагается ваш код...
}

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

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

#define TRUE   1
#define FALSE  0

и постоянно использовать их в своём коде, например:

while (TRUE)
{
 // здесь располагается ваш код...
}

Пришло время добавить в наш файл "loop.c" несколько новых строк и поместить в него цикл while.

#include <p24fj128ga010.h>

main()
{
 // инициализация управляющих регистров
 TRISA =   0xff00; // выводы 0..7 порта PORTA настраиваем как выходы
 
 // главный цикл приложения
 while( 1)
 {
    PORTA = 0xff; // включить выводы 0-7
    PORTA = 0;    // выключить все выводы
 } 
}

Структура этой программы-примера, по существу, является типичной структурой программы для встраиваемых приложений, написанной на Си. Она всегда состоит из двух основных частей:

1) Инициализация, которая включает в себя инициализацию периферийных модулей и переменных и выполняется только один раз, в начале.

2) Главный цикл, который содержит все функции управления, задающие поведение приложения, и выполняется бесконечно.

Анимированная симуляция

Выполните компиляцию и компоновку программы "loop.c", а также подготовьте программный симулятор.

Давайте попробуем протестировать код этого примера с помощью режима "Animate" ("Debugger->Animate"). В этом режиме симулятор выполняет программу на Си построчно, после каждой строки делая паузу на 1/2 секунды, чтобы мы могли понаблюдать за промежуточными результатами. Если вы добавите регистр PORTA в окно наблюдения "Watch", вы увидите, как его значение чередуется между 0xff и 0x00.

Скорость выполнения в режиме "Animate" задаётся параметром "Animation Step Time" на вкладке "Animation/Real Time Updates" диалогового окна "Debug->Settings", по умолчанию значение этого параметра равно 500 мс.

Конечно, режим "Animate" - ценное и занимательное средство отладки, но оно даёт искажённое представление о реальном времени выполнения программы. На практике, если бы мы запустили наш пример на настоящей плате, например, на Explorer 16, на которой PIC24 тактируется частотой 32 МГц, то подключенные к PORTA светодиоды мигали бы настолько быстро, что наши глаза просто не смогли бы этого заметить. Ведь частота мигания светодиодов составила бы несколько миллионов раз в секунду!

Чтобы замедлить мигание светодиодов до удобной частоты два раза в секунду, предлагаем воспользоваться таймером. Таймер - один из ключевых периферийных модулей, встроенных во все микроконтроллеры PIC24. Из пяти таймеров, доступных в PIC24FJ128GA010, для этого примера мы выберем первый таймер, Timer1. Это один из наиболее гибких и простых периферийных модулей. Нам лишь нужно заглянуть в спецификацию на PIC24, посмотреть блок-схему и регистр управления модуля Timer1 и подобрать правильные инициализационные значения.

Блок-схема модуля 16-разрядного таймера Timer1

Рис. 2-1. Блок-схема модуля 16-разрядного таймера Timer1

Имеется три SFR-регистра, управляющих большинством функций таймера Timer1:
- TMR1 - содержит значение 16-разрядного счётчика;
- T1CON - управляет включением и рабочим режимом таймера;
- PR1 - используется для выполнения периодического сброса таймера (в нашем случае не потребуется).

Для начала нам нужно сбросить регистр TMR1, чтобы начать отсчёт с нуля:

TMR1 = 0;

Затем нам нужно проинициализировать T1CON так, чтобы таймер работал в простой конфигурации:
- Timer1 активирован: TON = 1;
- В качестве источника тактирования выбран главный тактовый генератор (Fosc/2): TCS = 0;
- Предделитель установлен в максимальное значение (1:256): TCKPS = 11;
- Поскольку для тактирования таймера мы используем внутренний тактовый генератор, функции входного стробирования и синхронизации не потребуются: TGATE = 0, TSYNC = 0;
- Нас не волнует поведение в режиме IDLE: TSIDL = 0 (по умолчанию).

Управляющий регистр T1CON модуля Timer1

Рис. 2-2. Управляющий регистр T1CON модуля Timer1

Теперь соберём эти биты в одно 16-битное значение и запишем его в регистр T1CON:

T1CON = 0b1000000000110000;

или в более компактной шестнадцатеричной записи:

T1CON = 0x8030;

После инициализации таймера мы входим в цикл, в котором будем ожидать достижения регистром TMR1 желаемого значения, заданного константой DELAY:

 while(TMR1 < DELAY) { } // ждём

При условии, что используется кварц на 32 МГц, константе DELAY нужно назначить такое значение, которое даст задержку примерно в четверть секунды. Полное время задержки, обеспечиваемое циклом TMR1, можно вычислить по следующей формуле:

Tdelay = (2/Fosc) * 256 * DELAY

При Tdelay = 256 мс, решив это уравнение относительно DELAY, мы получаем значение 16000:

#define DELAY 16000

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

#include <p24fj128ga010.h>
#define DELAY  16000
main()
{
 // инициализация управляющих регистров
 TRISA = 0xff00;     // Выводы 0..7 порта PORTA настраиваем как выходы
 T1CON = 0x8030;     // TMR1 включен, предделитель 1:256, Tclk/2

 // главный цикл приложения
 while(1)
 {
   // 1. Включаем выводы 0-7 и ждём 1/4 секунды
   PORTA = 0xff; 
   TMR1 =  0;		    // обнуляем счётчик
   while (TMR1 < DELAY) { } // просто ждём
 
   // 2. Выключаем все выводы и ждём 1/4 секунды
   PORTA = 0x00;
   TMR1 =  0; 		     // обнуляем счётчик
   while ( TMR1 < DELAY) { } // просто ждём

  } // главный цикл
} // main

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

Теперь выполним сборку проекта и проверим его в работе. Если у вас есть демонстрационная плата Explorer16, вы можете попробовать запустить код прямо сейчас. Светодиоды должны мигать с комфортной частотой примерно два раза в секунду.

Если же вы попытаетесь запустить тот же самый код в симуляторе MPLAB SIM, то столкнётесь с тем, что здесь изменения PORTA происходят гораздо медленнее, чем на настоящем контроллере PIC24 с кварцем 32МГц (скорость выполнения в симуляторе зависит от скорости работы вашего ПК).

Если вы воспользуетесь режимом "Animate", то будет ещё хуже, ведь анимация добавляет дополнительную задержку примерно в полсекунды между выполнением каждой строки кода. Поэтому, исключительно в целях отладки с применением симулятора, вы можете назначить константе DELAY меньшее значение (например, 16).

Применение логического анализатора

В завершение урока предлагаем попробовать поработать с новым инструментом симулятора - логическим анализатором (Logic Analyzer).

Логический анализатор даёт вам графическое и весьма эффективное отображение записанных значений для любого числа выходных линий I/O, но при этом требует некоторой осторожности при начальной настройке.

Перед выполнением каких-либо действий необходимо убедиться, что функция "Tracing" симулятора включена:
1) Выберите диалоговое окно "Debug->Settings", а затем перейдите на вкладку "Osc/Trace".
2) В разделе опций "Tracing" поставьте галочку "Trace All".
3) Теперь откроем окно Анализатора из меню "View->Simulator" логического анализатора.

Окно логического анализатора

Рис. 2-3. Окно логического анализатора

4) Теперь нажмите на кнопку "Channels", чтобы вызвать диалоговое окно выбора каналов.

Диалоговое окно выбора каналов

Рис. 2-4. Диалоговое окно выбора каналов

5) Здесь вы можете выбрать выходные выводы устройства, которое вы хотели бы отобразить. В нашем случае выберите RA0 и нажмите "Add =>".
6) Нажмите кнопку "OK", чтобы закрыть окно.

Ненадолго запустите код (), затем нажмите кнопку "Halt" (). Окно логического анализатора должно отобразить чёткий меандр, как показано на Рис. 2-5.

Окно логического анализатора с диаграммой

Рис. 2-5. Окно логического анализатора с диаграммой

Примечание от DL36: В окне логического анализатора отображаются только те данные, которые находятся на данный момент в буфере. Для изменения отображаемого отрезка времени нужно увеличить размер буфера (Debug->Settings, параметр Buffer Size).

Подводим итоги

В этом коротком уроке мы узнали о том, как компилятор поступает с завершением программы. И впервые сделали "скелет" для нашего маленького проекта, разделив функцию main() на часть инициализации и бесконечный цикл. Для этого мы изучили синтаксис цикла while и заодно вкратце коснулись темы логических выражений. Урок мы завершили окончательным примером, в котором в первый раз применили модуль таймера, а также поработали с логическим анализатором и вывели выходную диаграмму для вывода RA0.

Замечания для экспертов по ассемблеру

Логические выражения на Си могут оказаться коварными для тех программистов на ассемблере, которым приходилось иметь дело с бинарными операторами с такими же названиями (AND, OR, NOT...). В Си тоже есть набор бинарных операторов, но здесь мы их не рассматривали во избежание путаницы. Бинарные логические операторы берут пары битов от обоих операндов и вычисляют результат согласно определённой таблице истинности. Логические операторы, с другой стороны, рассматривают каждый операнд (независимо от количества битов в них) как одинарное булево значение.

Взгляните на следующий пример с однобайтовыми операндами:

            11110101                11110101 (TRUE)
бинарный OR 00001000  логический OR 00001000 (TRUE)
            --------                --------
    даёт    11111101        даёт    00000001 (TRUE)

Замечания для экспертов по микроконтроллерам PIC

Наверняка вы заметили, что исчез Timer0. Но вы ничего не потеряли, так как оставшиеся пять таймеров PIC24 обладают всеми функциональными возможностями таймера Timer0. Все SFR-регистры, управляющие таймерами, имеют названия, подобные тем, что использовались в микроконтроллерах PIC16 и PIC18, и почти идентичны по структуре. Тем не менее, разработчики добавили несколько новых функций:

- все таймеры теперь 16-разрядные;
- каждый таймер имеет 16-разрядный регистр периода;
- новый механизм "сдваивания" таймеров для обеспечения 32-разрядного режима для таймеров Timer2/3 и Timer4/5;
- таймеру Timer1 добавлена возможность стробирования внешним тактовым генератором.

Замечания для экспертов по Си

Если вам приходилось программировать на Си на ПК, вы наверняка привыкли к тому, что по завершении функции main() управление должно возвращаться операционной системе. Хотя для PIC24 доступны различные операционные системы реального времени (RTOS), в большинстве приложений они не требуются (во всех наших уроках - тоже). По умолчанию компилятор C30 предполагает, что операционной системы, которой можно вернуть управление, нет, и делает самое безопасное из возможного - сброс.

Советы и трюки

Некоторые встраиваемые приложения разрабатываются с тем расчётом, что главный цикл будет выполняться бесконечно в течение месяцев или даже лет без выключения или выполнения команды сброса. Но управляющие регистры микроконтроллера являются простыми ячейками памяти ОЗУ. Вероятность, что колебания напряжения питания (не обнаруживаемые схемой сброса по снижению питания), электромагнитные импульсы, излучаемые каким-нибудь "шумным" оборудованием, находящимся по соседству, или даже пресловутое космическое излучение могут изменить их содержимое, мала, но вполне возможна. Дайте только время, и вы увидите, что это произойдёт. Если вы разрабатываете приложение, которое должно надёжно работать в таких больших временных масштабах, вам необходимо серьёзно задуматься об обеспечении периодического "обновления" наиболее важных регистров управления периферийных модулей, используемых приложением.

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

Упражнения

1) Вместо мигания светодиодов выведите на выводы порта PORTA счётчик.
2) Вместо мигания сделайте "бегущую дорожку".


© PIClist-RUS (piclist.ru), 2009 г.

PIClist RUS (piclist.ru) © 2009
все права сохранены. перепечатка статей и переводов с данного сайта запрещена.