Глава 7 ПРИМЕРЫ ВСТРОЕННЫХ СИСТЕМ УПРАВЛЕНИЯ

ПОСЛЕ ИЗУЧЕНИЯ ГЛАВЫ ВЫ СМОЖЕТЕ:

• Применить структурный системный подход к проектированию встроенных микроконтроллерных систем;

• Создать детальное описание проекта;

• Выбрать модули в составе 68HC12, необходимые для конкретного применения;

• Описать структуру программного обеспечения встроенной микроконтроллерной системы, используя структуру программы и блок-схему алгоритма;

• Написать программу на языке Си для конкретного приложения.

Представим себе красивый, старинный дубовый сундучок для инструментов, подобный показанному на рис. 7.1. Каждая его секция разбита на отделения, для хранения ценных инструментов в каждом из лотков. На каждом из войлочных лотков аккуратно написано название инструмента. В том же лотке хранится также и инструкция по применению инструмента. В предыдущих главах мы описывали аппаратные средства, программное обеспечение, и средства разработки систем. Именно они и лежат в этом «сундучке» инструментов, позволяющих разрабатывать, производить и внедрять встроенные системы управления. В этой главе мы покажем на различных примерах, как разрабатываются встраиваемые микроконтроллерные системы. Мы тщательно выбрали проекты, чтобы показать, как используются подсистемы МК 68HC12 и HCS12 для выполнения разнообразных задач. Методы совместного использования этих периферийных модулей мы покажем на ряде примеров. Для каждого такого примера, мы приведем детальное описание проекта, список используемых в нем подсистем 68HC12, краткие основы теории, если это необходимо, детальную структуру программы, сопровождаемую блок-схемой алгоритма и хорошо документированным программным кодом. Будут рассмотрены следующие примеры применения:

• Система привода для робота, движущегося вдоль стенок лабиринта;

• Лазерный проектор;

• Цифровой вольтметр;

• Система стабилизации скорости вращения двигателя с оптическим тахометром;

• Парящий робот;

• Система защиты от несанкционированного внедрения на базе нечеткой логики;

• Электронная версия популярной игры в «15».

Рис. 7.1. Старинный сундук, с несколькими ящиками. В лотки ящиков мы уложили инструментальные средства, рассмотренные в предшествующих главах


В конце главы мы рассмотрим также процедуру программирования Flash-памяти с помощью отладочной платы «B32».

7.1. Система привода робота, движущегося вдоль стенок лабиринта

7.1.1. Описание проекта

В техническом задании на этот проект, предлагается разработать автономный робот, который мог бы проходить через неизвестный лабиринт. Робот должен двигаться через лабиринт, находя стенки лабиринта с помощью инфракрасных (ИК) локаторов (пар излучатель-приемник), и принимая решения, продвигаться вперед или назад, чтобы продолжить путь через лабиринт. При своем движении робот должен также избегать «мин» (магнитов), скрытых в полу лабиринта. Робот обнаруживает «мины» с помощью датчика Холла. Если робот обнаруживает «мину», то он останавливается, дает задний ход и объезжает ее.

Конструкция робота приведена на рис. 7.2. Корпус его состоит из двух легких связанных вместе алюминиевых платформ. На нижней платформе расположены два двигателя постоянного тока, приводящие в движение два больших колеса, установленных с обеих сторон корпуса. При использовании этой конструкции с двумя колесами, робот может управляться подобно танку. То есть, чтобы выполнить поворот по часовой или против стрелки на двигатели подаются сигналы. Два маленьких ролика с обеих сторон от робота используются, чтобы обеспечить его равновесие и устойчивость. Находясь в покое, робот опирается на три точки. На верхней платформе установлены пять ИК пар излучатель-приемник, которые позволяют роботу обнаружить стенки прямо перед собой и с любой стороны от корпуса. Верхняя платформа содержит также отладочную плату с микроконтроллером 68HC12, которая используется, чтобы принимать входные сигналы, вырабатывать решения, основанные на этих сигналах, и формировать сигналы управления двигателями для реализации этих решений. На нижней пластине робота установлен датчик Холла, позволяющий обнаруживать магнитные «мины».

Рис. 7.2. Робот, движущийся вдоль стенок лабиринта

Робот оборудован двумя основными колесами, с приводом от двигателей постоянного тока. Когда робот движется вперед, он постоянно следит за положением стенок, используя инфракрасные локаторы (пары излучатель-приемник), и за «минами», скрытыми в полу, с помощью датчика Холла.


Чтобы начать движение через неизвестный лабиринт, робот помещается вблизи от его входа. Предназначение робота состоит в том, чтобы проходить через лабиринты, избегая столкновения со стенками и контакта с «минами», скрытыми в полу. Робот едет вперед, когда на оба двигателя поступает одинаковое постоянное напряжение. Когда робот движется вперед с помощью двигателей постоянного тока, он непрерывно проверяет свое положение относительно стенок и магнитов с помощью пяти ИК локаторов и датчика Холла.

Стенки лабиринта окрашены белой краской с высоким коэффициентом отражения, чтобы ИК-сигналы, поступающие от источников, отражались от стенок обратно на приемники. Если робот приближается к стенке, то ее присутствие обнаруживается соответствующим ИК локатором. Например, если робот приближается к углу, расположенному с правой стороны (см. рис. 7.3), то передняя стенка обнаруживается передним локатором, а правая — правым локатором. Робот затем отвечает на принятые им входные сигналы, поворачивая влево, чтобы избежать столкновения со стенками.

Рис. 7.3. Робот, разработанный, чтобы обнаруживать близлежащие стенки

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


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

• Аналого-цифровое преобразование выходных сигналов ИК-датчиков;

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

• Разработка функций управления приводом, осуществляющих движение робота вперед и назад и повороты влево или вправо;

• Создание механизма для обработки выходных сигналов датчика Холла;

• Обеспечение функции, позволяющей останавливаться, давать задний ход и обходить обнаруженную «мину»;

• Отображение выполняемых функций на символьном ЖК индикаторе.

7.1.2. Подсистемы 68HC12, используемые в проекте

По списку требуемых функций мы можем определить, какие периферийные модули МК 68HC12 необходимо использовать и какие другие внешние устройства будут необходимы для решения нашей задачи. К используемым устройствам и системам относятся:

• Датчики ИК-излучения и датчики Холла;

• Модуль аналого-цифрового преобразования ATD в составе МК 68HC12 для оцифровки сигналов инфракрасных датчиков и датчика Холла;

• Модуль ШИМ МК 68HC12 для модуляции ширины импульса;

• Интерфейс сопряжения МК с ЖК дисплеем;

• Интерфейс сопряжения МК с ИК-датчиками;

• Интерфейс сопряжения МК с датчиком Холла;

• Интерфейс драйвера двигателя;

• Аккумуляторные батареи, для питания двигателей, датчиков, и отладочной платы MC68HC912B32EVB.

7.1.3. Компоненты системы

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

• Модуль АЦП в составе МК семейства 68HC12;

• Модуль ШИМ в составе МК семейства 68HC12;

• Интерфейс и программное обеспечение ЖК дисплея;

• Интерфейс драйвера двигателя;

• Батарею для питания системы.

Если вы еще не познакомились с этими устройствами и подсистемами МК, вернитесь к соответствующим главам. Мы же остановимся на двух темах, которые не рассматривали ранее: на паре излучатель-приемник, образующей ИК-локатор, и на датчике Холла.

Пара ИК излучатель-приемник. В паре ИК излучатель-приемник объединены источник и приемник инфракрасного (ИК) излучения. Источником является светоизлучающий диод с соответствующей схемой, а приемником — фототранзистор, чувствительный к ИК-диапазону излучения с собственной схемой, показанной на рис. 7.4. Для питания ИК диода используются электрические цепи, описанные ранее в разделе, посвященном светодиодам. Фототранзистор имеет светочувствительный переход база-база-эмиттер. Когда свет соответствующей длины волны падает на переход, в нем возникает базовый ток. В цепь эмиттера включен резистор нагрузки, сопротивление которого позволяет обеспечить необходимую величину выходного напряжения. Часто вместо резистора с фиксированным сопротивлением используется 10-оборотный измерительный потенциометр, позволяющий индивидуально подстраивать чувствительность каждого приемника. График зависимости выходного напряжения от расстояния до стенки лабиринта может быть получен экспериментально. Выходной сигнал каждого приемника подается на канал АЦП микроконтроллера 68HC12.

Рис. 7.4. Пара излучатель-приемник — ИК-локатор

Резистор (Re) ограничивает ток ИК излучателя на уровне номинального значения (Ie). Ток приемника формирует на резисторе Rd выходное напряжение приемника (Vout)


Датчики Холла, как и показывает их название, используют эффект Холла, чтобы генерировать напряжение, пропорциональное напряженности обнаруженного магнитного поля. На рынке имеются датчики Холла двух типов: (1) переключатели и (2) линейные датчики. Датчик переключающегося типа, обнаружив присутствие магнитного поля, фиксируется во включенном состоянии. Он остается в этом состоянии, даже если магнитное поле исчезает.

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

Рис. 7.5. Датчик Холла HAL114 компании Micronas


Датчики Холла поставляются несколькими изготовителями. Мы выбрали простой датчик с тремя выводами HAL114 фирмы Micronas, схема включения которого содержит два резистора R, RL и конденсатор C, как показано на рис. 7.5. Как и ранее, график зависимости выходного напряжения от расстояния до стенки лабиринта может быть получен экспериментально. Выходной сигнал с датчика подается на канал АЦП микроконтроллера 68HC12. Полная схема интерфейса связи МК 68HC12 с аппаратными средствами робота показана на рис. 7.6. Закончив на этом краткий обзор аппаратных средств, мы перейдем к обзору программного обеспечения робота.

Рис. 7.6. Интерфейс между аппаратными средствами робота и 68HC12

7.1.4. Структура программы и блок-схема алгоритма

Рабочая программа для этого проекта разработана Томом Шеи, бывшим студентом университета штата Вайоминг, с помощью компилятора ImageCraft ICC12. Прежде чем представить полный текст программы, рассмотрим структуру основной программы и блок-схему ее алгоритма, представленные на рис. 7.7. Мы просим читателя, самостоятельно разработать блок-схему алгоритма для каждой из функций в качестве домашней работы (см. задание 12).

а) Структура программы

б) Блок-схема алгоритма UML

Рис. 7.7. К программе управления роботом, движущимся вдоль стенок лабиринта

7.1.5. Программный код

/********************************************************************/

/*имя файла: robot.c                         */

/* Система управления роботом, движущимся в лабиринте: это система  */

/* состоящих из излучателя и приемника, чтобы определять свое положение*/

/* относительно стенок лабиринта.                  */

/* опорного напряжения, то стенка находится в непосредственной близости*/

/* от робота.Основываясь на информации, получаемой от пяти датчиков,*/

/* робот может определять, какое направление дальнейшего движения  */

/* избрать, чтобы избежать столкновения со стенками лабиринта.    */

/* Датчик Холла позволяет роботу обнаружить магниты или "скрытые мины",*/

/* установленные под полом лабиринта. Робот имеет также ЖК дисплей  */

/* для сообщения информации пользователю. Программа использует метод*/

/* полинга для считывания результатов АЦП.Сигнал модуля ШИМ     */

/* управляет драйвером двигателей колес робота.           */

/*Автор: Томас Шеи. Дата создания: 18 октября 2002         */

/*Последняя редакция: 4 декабря 2002                */

/********************************************************************/ 

/* Включенные файлы*/ 

#include <912b32.h> 

#include  


/*Пороги датчиков были определены экспериментально*/ 

#define opto_threshold 0x50 /* порог оптического датчика */ 

#define hes_threshold 0x80 /* порог датчика Холла */ 

#define forward 0 

#define half_left 1 

#define half_right 2 

#define left_turn 3 

#define right_turn 4 

#define back_up 5 


/*глобальные переменные*/ 

unsigned int i=0,j=0; /*переменные для программной задержки */ 

unsigned char sens[6]={0, 0, 0, 0, 0, 0};/*массив результатов АЦП */ 


/*прототипы функций*/ 

void init_adc(void); /*инициализация АЦП */ 

void read_adc(void); /*считывание значений АЦП */ 

void decision(void); /*передача решения о повороте, основанного на */ 

           /*данных АЦП* / 

void init_pwm(void); /*инициализация ШИМ */ 

void pwm_motors(const char a); */активация ШИМ для пересылки */ 

void lcd_init(void); /* инициализация дисплея */ 

int putchar(char c); /*вывод символа на дисплей */ 

int putcommand(char с); /*вывод команды на дисплей */ 

void delay_25(void); /*подпрограмма задержки на 2,5 с */ 

void lcd_print(char *string); /*вывод строки на ЖК дисплей */ 


void main() {

 asm(".area vectors(abs)\n" /*инициализация вектора сброса МК */ 

  " org 0xFFF8\n" 

  " .word 0x8000, 0x8000, 0x8000, 0x8000\n" 

  ".text"); 

 lcd_init(); /*инициализация ЖК дисплея */ 

 lcd_print("LCD initialized"); 

 void delay_25(void); /* задержки на 2,5 с */ 

 init_adc(); /*инициализация АЦП */ 

 lcd_print("ADC initialized"); 

 void delay_25(void); /* задержки на 2,5 с */ 

 init_pwm(); /*инициализация ШИМ */ 

 lcd_print("PWM initialized"); 

 void delay_25(void); /* задержки на 2,5 с */ 

 while(1) / *непрерывный цикл */ 

 {

  read_adc(); /* считать текущее значение из АЦП */ 

  decision(); /* принять решение о направлении движения */ 

 }

} /*конец программы main*/ 


********************************************************************/

/*initialize_adc: инициализация АЦП контроллера 68HC12       */

/*******************************************************************/

void init_adc() {

 ATDCTL2 = 0x80; /*Установить бит ADPU для подачи питания на АЦП */

 ATDCTL3 = 0x00;

 ATDCTL4 = 0x7F; /* частоту P_CLK установить на 125 кГц */ 

 /* время преобразования: 32 ATD CLK, */ 

 /*1 считывание каждые 256 мкс /* 

 for(i=0; i<67; i++) /*задержка 100 мкс при 8 МГц E_CLK */ 

 {

  ;

 }

}

/********************************************************************/


/********************************************************************/

/*read_adc: считывание результатов из АЦП              */

/********************************************************************/

void read_adc() {

 ATDCTL5 = 0x50; /*Установить АЦП на режим многоканального,*/

         /* преобразования 8 каналов */ 

 while((ATDSTAT & 0x8000) == 0)/* проверка бита SCF для окончания */

                 /*преобразования */

 {

  ;

 }

 /* сохранения результата в глобальном массиве */

 sens[0] = ADR7H; /*дальний левый датчик */

 sens[l] = ADR6H; /*средний правый датчик */

 sens[2] = ADR5H; /*центральный датчик */

 sens[3] = ADR4H; /* средний правый датчик */

 sens[4] = ADR3H; /* дальний правый датчик */

 sens[5] = ADR2H; /*датчик Холла*/

}


/********************************************************************/

/*decision(): решение о повороте основано на информации, полученной от*/

/* пяти датчиков. Порог датчика Холла (hes_threshold) и порог    */

/* оптического датчика (opto_threshold) определяются экспериментально.*/

/********************************************************************/

void decision() {

 if (sens[5] < hes_threshold) { /* датчик Холла нашел "мину", */

 pwm_motors(back_up); /* робот движется назад и определяются */

 /* дальнейшие действия/*

 if (sens[0] > opto_threshold) pwm_motors(right_turn);

 else pwm_motors(left_turn);

 for(i=0; i<0xFFFF; i++){ /*задержка вращения двигателя */

  for(j=0; j<15; j++) {

   ;

  }

 }

 }

 /*если обнаруживает три стенки (тупик), то движется назад */

 else if((sens[2]>opto_threshold) && (sens[0]>opto_threshold)

  && (sens[4]>opto_threshold)) {

  pwm_motors(back_up);

 }

 /*если стенки спереди и слева, поворачивает направо */

 else if((sens[0]>opto_threshold)&&(sens[2]>opto_threshold)) {

  pwm_motors(right_turn);

 }

 /*если стенки спереди и справа, поворачивает налево */

 else if((sens[2]>opto_threshold)&&(sens[4]>opto_threshold)) {

  pwm_motors(left_turn);

 }

 /*если стенка спереди справа, делает полуповорот направо*/ 

else if(sens[1]>opto_threshold) {

  pwm_motors(half_right);

 }

 /*если стенка спереди слева, делает полуповорот налево */

 else if(sens[3] > opto_threshold) {

  pwm_motors (half_left) ;

 }

 /*если стенки вблизи нет, продолжает движение вперед */

 else {

  pwm_motors(forward);

 }

}


/********************************************************************/

/*init_pwm(): инициализация ШИМ контроллера 68HС12          */

/********************************************************************/

void init_pwm() {

 PWTST= 0x00;

 PWCTL= 0x00; /*Режим фронтовой ШИМ */

 WCLK= 0x3F; /*Каналы без каскадного соединения, E_CLK/128 */

 PWPOL= 0x0F; /*set pins high then low transition */

 DDRP = 0xFF; /*Порт PORT T на вывод */

 PWEN = 0x0F; /*Активизировать выход ШИМ */

 PWPER0 = 250; /*Установить частоту ШИМ 250 Гц */

 PWPER1 = 250;

 PWPER2 = 250;

 PWPER3 = 250;

 PWDTY0 = 0; /*начальная установка ШИМ на отсутствие движения*/

 PWDTY1 = 0;

 PWDTY2 = 0;

 PWDTY3 = 0;

}


/********************************************************************/

/*pwm_motors: /*выполнение определенного поворота          */

/********************************************************************/

void pwm_motors(const char a) {

 for (i = 0;i<2000;i++) /*задержка на 3 мс чтобы позволить двигателю*/

 {           /* отреагировать*/

 }

 switch(a) { /*определение вида поворота */

 case 0: /* движение вперед */

  PWDTY0 = 200; /*регистры коэффициента заполнения ШИМ */

  PWDTY1 = 250;

  PWDTY2 = 250;

  PWDTY3 = 200;

  lcd_print("Forward\n");

  break;

 case 1: /*полуповорот налево */

  PWDTY0 = 0; /*регистры коэффициента заполнения ШИМ */

  PWDTY1 = 250;

  PWDTY2 = 250;

  PWDTY3 = 125;

  lcd_print("Half Left\n");

  break;

 case 2: /*полуповорот направо*/

  PWDTY0 = 125; /*регистры коэффициента заполнения ШИМ */

  PWDTY1 = 250;

  PWDTY2 = 250;

  PWDTY3 = 0;

  lcd_print("Half Right\n");

  break;

 case 3: /*поворот налево*/

  PWDTY0 = 125; /*регистры коэффициента заполнения ШИМ */

  PWDTY1 = 250;

  PWDTY2 = 0;

  PWDTY3 = 125;

  lcd_print("Left Turn\n");

  break;

 case 4: /*поворот направо*/

  PWDTY0 = 125; /*регистры коэффициента заполнения ШИМ */

 PWDTY1 = 0;

  PWDTY2 = 250;

  PWDTY3 = 125;

  lcd_print("Right Turn\n");

  break;

 case 5: /*задний ход*/

  PWDTY0 = 125; /*регистры коэффициента заполнения ШИМ */

  PWDTY1 = 0;

  PWDTY2 = 0;

  PWDTY3 = 125;

  for(i=0; i<0xFFFF; i++) { /* Задержка в 1,25 с перед движением назад*/

  for(j=0; j<15; j++) {

   ;

  }

  }

 lcd_print("Back Up\n");

  break;

 default: /*по умолчанию движение вперед, малая скорость */

  PWDTY0 = 63; /*регистры коэффициента заполнения ШИМ */

  PWDTY1 = 250;

  PWDTY2 = 250;

  PWDTY3 = 63;

  lcd_print("Error\n");

  break;

 }

}


/********************************************************************/

/*lcd_init(): инициализация режима работы ЖК дисплея         */

/*Последовательность команд инициализации определяется изготовителем*/

/*PORTA: магистраль данных, PORTB[2:1]: линия R/S, линия разрешения E*/

/********************************************************************/

void lcd_init() {

 DDRA=0xff; /*порт PORTA на вывод */

 DDRB=0x06; /* порт PORTB [2:1] на вывод */

 /*последовательности команд для инициализации ЖК дисплея */

 putcommand(0x38);

 putcommand(0x38);

 putcommand(0x38);

 putcommand(0x38);

 putcommand(0x0f);

 putcommand(0x01);

 putcommand(0x06);

 putcommand(0x00);

 /*очистка дисплея, возврат курсора */

 putcommand(0x00);

}


/********************************************************************/

/*putchar(char c): вывод символа на дисплей             */

/********************************************************************/

int putchar(char c) {

 PORTA=C;

 PORTB= PORTB |0x04;

 PORTB= PORTB |0x02;

 PORTB= PORTB&0xfd;

 for (i=0; i<100; i++); /*задержка на *150 мкс до того, как ЖКД */

             /* сможет принять информацию */

 return с;

}

/********************************************************************/


/********************************************************************/

/*putcommand(char c): выдача команды управления для ЖК дисплея    */

/********************************************************************/

int putcommand(char с) {

 PORTA= с;

 PORTB= PORTB&0xfb;

 PORTB= PORTB|0x02;

 PORTB= PORTB&0xfd;

 for (i=0; i<100; i++) /* задержка на *150 мкс до того, как ЖКД сможет*/

            /*принять информацию */

 {

  ;

 }

 return c;

}


/********************************************************************/

/*delay_25(): задержка на 2.5 с                   */

/********************************************************************/

void delay_25() {

 for (i=0; i<0xFFFF; i++) {

  for (j=0; j<30; j++) {

  ;

  }

 }

}


/********************************************************************/

/*lcd_print(): вывод строки символов на дисплей.           */

/********************************************************************/

void lcd_print(char *string) {

 putcommand(0x02); /*возврат курсора ЖКД */

 while (*(string) != '\0') {

  putchar(*string);

  string++;

 }

}

/********************************************************************/

7.2. Лазерный проектор

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

7.2.1. Описание проекта

Система имеет семь образцов изображений, которые проектируются лазером на стену или киноэкран. Мы выбираем один из них для проекции, нажав соответствующую кнопку. Как только изображение выбрано, подсвечивается светодиод, соответствующий выбранному варианту. Затем система управления открывает лазерный затвор, позволяя лазерному лучу пройти на пару гальванометрических зеркал. Микроконтроллер 68HC12 генерирует сигналы управления, позволяющие изменять угол поворота зеркал, чтобы создать предварительно записанное в памяти изображение с помощью внешних по отношению к МК цифро-аналоговых преобразователей (ЦАП). Выбранное изображение выводится однократно при каждом нажатии кнопки. Конструкция системы приведена на рис. 7.8.

Рис.7.8. Встроенная система управления лазерным проектором

7.2.2. Подсистемы 68HC12 используемые в проекте

Основываясь на кратком описании проекта, мы можем определить периферийные модули МК 68HC12 необходимо использовать и внешние устройства которые которые будут использоваться для решения нашей задачи:

• Восемь двухпозиционных переключателей с аппаратной противодребезговой защитой, подключены к порту ввода МК;

• Восемь светодиодных индикаторов для логических выходов с тремя состояниями, подключены к порту вывода МК;

• Двухканальный ЦАП, связанный с МК по последовательному интерфейсу SPI, или два порта МК 68HC12, конфигурированных как выходные;

• Лазерный источник;

• Затвор и драйвер затвора;

• Два гальванометрических зеркала.

7.2.3. Описание некоторых компонентов системы

Как и прежде, рассмотрим доступные инструментальные средства из нашего инструментального ящика, позволяющие выполнить все требуемые функции.

В нашем сундучке инструментов уже имеются следующие компоненты:

• Противодребезговые переключатели;

• Восемь светодиодных индикаторов с тремя состояниями;

• ЦАП;

Однако мы еще не рассматривали лазеры, лазерные зеркала, лазерные затворы и гальванометрические зеркала. Рассмотрим эти устройства. Мы рассмотрим также более подробно технологию ЦАП.

Рис. 7.9. Типичная схема подключения ЦАП MC1408P8 фирмы Motorola


Однако мы еще не рассматривали лазеры, лазерные зеркала, лазерные затворы и гальванометрические зеркала. Рассмотрим эти устройства. Мы рассмотрим также более подробно технологию ЦАП.

Цифро-аналоговые преобразователи (ЦАП). В главе 6 мы обсуждали основы цифро-аналоговых преобразователей (ЦАП). Поскольку МК 68HC12 не имеют в своем составе модуля ЦАП, необходимо воспользоваться внешними ИС ЦАП. Для данного проекта нам нужны фактически два отдельных канала ЦАП, чтобы управлять X и Y каналами гальванометров. Существует много различных ИС ЦАП, совместимых с 68HC12 и удовлетворяющих требованиям данного проекта. Их можно разделить на две категориии: ЦАП с последовательными или с параллельными входами. ЦАП с последовательными входами обычно подключается к МК с помощью интерфейса SPI. Читатель, интересующийся более подробным описанием этого типа интерфейса может обратиться книге Pack and Barrett [2002, гл. 10]. В этом примере, мы используем два 8-разрядных ЦАП с параллельными входами. Имеется широкое разнообразие таких ЦАП. В этом проекте мы используем ИС MC1408P8 фирмы Motorola. Типовая схема подключения ЦАП MC1408P8 к порту вывода МК показана на рис. 7.9. Выходное напряжение ЦАП определяется величиной опорного напряжения Vref, коэффициентом обратной связи операционного усилителя (определяется R14 и Ro) и цифровым кодом на входах A8…A1. Зависимость напряжения на выходе ЦАП в функции перечисленных параметров приведена на рис. 7.9. Величина опорного напряжения и номиналы резисторов определяются схемой подключения и не могут быть изменены в процессе эксплуатации. А вот кодовая комбинация на входах A8…A1 постоянно изменяется в процессе управления. И в соответствии с передаточной характеристикой ЦАП изменяется напряжение на выходе V0. Электрические характеристики цифровых входов ЦАП (A8…A1) позволяют выполнить их прямое подключение к выводам порта МК 68HC12. В соответствие с техническими условиями необходимо, чтобы напряжение V0 изменялось в диапазоне ±1 В при изменении кода на входах A8…A1 от $FF. Данное требование может быть выполнено при следующих номиналах резисторов схемы и опорного напряжения ЦАП:

• Vref = 5,0 В

• R14=R15= 1 кОм

• Ro = (2/5) R14 = 400 Ом

• RB = 2 (R14) = 2 кОм

Эти значения получены из решения уравнения для выходного напряжения, приведенного на рис. 7.9 для двух различных случаев: (1), когда выходное напряжение равно +1 В, и (2), когда выходное напряжение составляет 0 В; опорное напряжение Vref удобно выбрать равным 5 В. Эти значения составляющих обеспечивают выходное напряжение –1,0 В для двоичного кода $00 и выходное напряжения 0,992 В для двоичного кода $FF. Двоичные коды, заключенные между этими крайними значениями кода, обеспечивают 256 значений аналогового выходного напряжения линейно изменяющегося от –1,0 до 0.992 В.

Лазеры. Лазер (слово получено из сокращения light amplification by stimulated emission of radiation — усилитель света на базе вынужденного излучения) представляет собой источник света с рядом специфических свойств и характеристик. Он, как считают, является монохроматическим (одна длина волны или очень узкий диапазон длин волн), когерентным (фронты волн находятся в фазе друг с другом), и нерасходящимся. Что же это означает? В основном, лазер обеспечивает одноцветный источник света с узким, подобным карандашу, лучом. Лазер с самого начала нашел применение фактически во всех областях промышленности и медицины [12]. Читателя, интересующегося более подробным знакомством с этой увлекательной темой, мы отсылаем к разделу «Что еще прочитать» в конце данной главы. Для рассматриваемого устройства, мы используем маломощный (менее 3 мВт) лазер в видимом диапазоне излучения. Лазеры этого типа доступны в нескольких различных исполнениях. Имеется ряд гелий-неоновых (HeNe) лазеров различных цветов. Однако газоразрядные трубки таких лазеров обычно имеют длину 25 см и диаметр 5 см. Более новая технология — твердотельный лазер с диодной накачкой (DPSS), маломощный лазер, для нескольких частот в видимой области. Его длина составляет приблизительно 5 см и диаметр 1,5 см, он питается от маленького выпрямителя [3]. Мы используем этот тип лазера для данного проекта. Эти лазеры достаточно просты в обращении. Вы подключаете их к сети, и сразу появляется луч.

При работе с лазерами применяется стандарт безопасности ANSI Z136. 1 «Безопасность при применении лазеров» [1], касающийся безопасной работы с приборами на базе лазеров. Если вы планируете реализовать подобный проект, мы советуем вам получить копию этого документа и подробно ознакомиться с ним. Стандарт делит лазеры на различные категории (классы от 1 до 4), основываясь на степени их опасности для пользователя. Чем выше номер класса, тем больше опасность. Лазер, который мы используем в этом приложении, принадлежит к классу 3a, обеспечивая мощность излучения 1…5 мВт в видимом диапазоне. Это тот же класс лазеров, к которому принадлежит лазерная указка. Хотя эта мощность относительно мала, обращаться с лазерами необходимо с особой осторожностью. Необходимо также предпринимать особые меры предосторожности при юстировке оптики, связанной с лазерной системой, чтобы предотвратить поражение глаз лазерным излучением. Ни в коем случае нельзя смотреть непосредственно в лазерный источник. Стандарт ANSI касается также других требований безопасности при использовании этого класса лазеров, включая предупредительные знаки, безопасное размещение лазера, обучение безопасному обращению с лазерами и ограничение доступа к ним.

Зеркала. Имеются оптические зеркала самых различных форм, размеров, и толщины, рассчитанные на различный диапазон частот излучения. Для этого применения, мы используем зеркала с нанесением покрытия на переднюю поверхность. Это означает, что зеркало имеет отражающее покрытие на наружной поверхности стекла. Такая технология предотвращает появление многократных отражений между передней и задней поверхностями зеркала. Кроме того, находящееся на передней поверхности отражающее покрытие должно быть рассчитано на соответствующую длину волны. То есть оно должно правильно отражать свет в интересующем нас диапазоне частот. Для этого приложения, мы используем лазер в видимом диапазоне (с длиной волны от 400 до 700 нм). Различные изготовители и поставщики обеспечивают широкий диапазон зеркал с различными покрытиями [3]. Оптические зеркала устанавливаются на вращающихся гальванометрических подвесках при помощи легких держателей.

Лазерные затворы. Лазерный затвор — это просто апертура для лазера, перекрывающая лазерный луч. В закрытом состоянии затвор обычно перекрывается створками. На створки наносится теплостойкое покрытие, способное выдерживать высокую плотность энергии лазера. Створки управляются драйвером лазерного затвора, имеющим логический вход, совместимый с транзисторно-транзисторной логикой (ТТЛ) и генерирующим выходной сигнал, согласованный с характеристиками затворов ряда изготовителей. Они имеют широкий диапазон диаметров апертуры: от 2 до 45 мм [13].

Гальванометры. В гальванометрах, называемых также оптическими сканерами, применяется эффективный метод перемещения лазерного луча. Гальванометры обеспечивают угол поворота зеркала точно соответствующий заданному значению входного напряжения. В идеале, должна быть обеспечена линейная связь между входным напряжением и углом поворота. Кроме того, гальванометры характеризуются максимальным и минимальным углами поворота. Легко приобрести гальванометры для углов до ±30 [4]. Гальванометры управляются внешними усилителями. Обычно это твердотельные усилители с переменным выходным сопротивлением. На их вход подается напряжение ±1,0 В, при этом ток управляющий поворотом зеркала гальванометра пропорционален входному напряжению. Как правило, обеспечивается регулировка нулевого смещения и коэффициента усиления драйверов гальванометров.

На рынке имеются сканирующие X–Y системы, которые используют для оптического сканирования в плоскости X–Y два зеркала, и два отдельных гальванометра.

Зеркала помещены перпендикулярно друг к другу. Лазерный луч следует по пути, показанному на рис. 7.10. Управляющие сигналы для X- и Y-гальванометров формируются отдельными драйверами, и позволяют проецировать лазерное излучение в любую точку плоскости X–Y. Путем последовательного вывода ряда точек могут быть созданы различные образы.

Рис. 7.10. Сканирующая XY система

7.2.4. Аппаратные средства

Рис. 7.11. Внешний аппаратный интерфейс для 68HC12


Функциональная схема управление лазерным проектором приведена на рис. 7.11. На первый взгляд эта схема может показаться сложной. Однако, Вы уже знакомы с каждой из ее подсистем, что облегчает понимание общего устройства системы. Линейка из восьми противодребезговых переключателей, подключенных к порту PORTA микроконтроллера 68HC12, используется для выбора образа, которое го пилообразного сигнала. При этом начинает светиться светодиод, связанный с PORTB[4].

Линейка из восьми противодребезговых переключателей, подключенных к порту PORTA микроконтроллера 68HC12, используется для выбора образа, которое должно быть воспроизведено лазером. Когда образ выбран, подсвечивается соответствующий ему светодиод на PORTB. Например, если мы нажимаем переключатель, связанный с выводом PORTA[4], должно выводиться изображение правого пилообразного сигнала. При этом начинает светиться светодиод, связанный с PORTB[4].

Чтобы показать, какое из изображений было выбрано, используется линейка светодиодных индикаторов. Когда изображение выбрано, соответствующие двоичные значения появляются на выходе порта PORTS[7:0] для X-гальванометра и порта PORTT[7:0] для Y-гальванометра. Они подаются на ЦАП для X-канала и ЦАП для Y-канала, соответственно. ЦАП преобразует каждое из двоичных значений в аналоговый сигнал, способный управлять усилителями гальванометров.

Сигнал управления, открывающий и закрывающий створки затвора, выводится на порт PORTP[0]. Логическая «1» открывает створки, а логический «0» закрывает их. Как и для гальванометров, для створок необходимо преобразовать логический сигнал контроллера 68HC12 в аналоговый сигнал, достаточный для того, чтобы открыть или закрыть створки. Контроллер принимает ТТЛ-совместимый входной сигнал, и преобразует его в сигнал, способный управлять приводом створок.

Завершив на этом обсуждение аппаратных средств, мы подробно рассмотрим в следующем разделе программное обеспечение для управления данной системой.

7.2.5. Структура программы и блок-схема алгоритма

Структура программы и блок-схема алгоритма для системы управления лазерным проектором представлены на рис. 7.12. Обе диаграммы довольно очевидны. Диаграмма интерфейса приводится, чтобы показать преобразование двоичного кода, поступающего от микроконтроллера 68HC12, в аналоговый сигнал ±1 В, необходимый для усилителей гальванометра.

Рис. 7.12. К программе управления лазерным проектором: блок-схема алгоритма (слева), структура программы (вверху справа), шестнадцатеричные значения к диаграмме ЦАП (внизу справа)


Система сначала инициализируется, устанавливая порты в необходимый режим (вход или выход) и закрывая лазерный затвор. Затем программа считывает информацию из порта PORTA, чтобы определить, нажат ли переключатель. Если он нажат, используется команда переключателя, позволяющая определить выбранное изображение. Код, необходимый для создания данного изображения обеспечивается выбором переключателя. Чтобы создать изображение, лазер сначала перемещается в требуемую исходную позицию для заданной траектории. Затем открывается лазерный затвор, и соответствующая траектория обеспечивается последовательностью положений лазерного луча в различные моменты времени. Мы приводим несколько примеров, и затем предлагаем вам самостоятельно, сформировать оставшиеся варианты в качестве домашней работы (задание 14).

7.2.6. Программный код

//********************************************************************

//имя файла: laser.с

//функции: программа для управления лазерным проектором

//контроллер: отладочная плата 68HC12B32 фирмы Motorola

//Выводы отладочной платы микроконтроллера 68HC12B32 фирмы Motorola:

//Port A: Конфигурирован как входной порт, активируемый нажатием

// бездребезговых переключателей на каждом входе

//Port B: Конфигурирован как выходной порт для управления светодиодным

// индикатором

//Port S: Конфигурирован как выходной порт, создающий двоичный код

// на канале X ЦАП

//Port T: Конфигурирован как выходной порт, создающий двоичный код

// на канале Y ЦАП

//Port P[0]: Конфигурирован как выходной порт, создающий TTL совместимый

// сигнал для управления затвором

//авторы: Steve Barrett and Daniel Pack

//создан: февраль 20, 2003

//последняя редакция: март 3, 2004

//********************************************************************

//включенные файлы

//********************************************************************

#include <912b32.h>


//функции прототипов

void initialize_ports(void); //инициализация портов

void shutter(int); //открытие/закрытие створок

void position_laser(unsigned char, unsigned char); // положение лазера

void delay(void);


#define open 1

#define close 0


//main program*************************************************************

//global variables

unsigned char new_PORTA, old_PORTA = 0xFF;

int i;

int go;

void main(void) {

 //инициализировать вектор reset B32

 asm(".area vectors(abs)\n"

  ".org 0xFFF8\n"

  ".word 0x8000, 0x8000, 0x8000, 0x8000\n"

  ".text");

 go = 1; //начало цикла while

 initialize_ports(); //инициализировать порты

 shutter(close); //закрыть створки

 position_laser(0x80,0x80); //расположить луч в центре

 while(go) { //продолжать, пока не нажмут кнопку Stop

  new_PORTA = PORTA; //read PORTA input switches

  if (new_PORTA != old_PORTA) switch(new_PORTA){ //формировать образ по значению

                          //нажатой клавиши

  case 0x7F: //PA7 - Точка в центре экрана

   PORTB = 0x80;// подсвечивает светодиод порта PORTE

   position_laser(0x80,0x80);

   shutter(open);

   delay();

   shutter(close);

   PORTB=0x00; //включает красные светодиоды индикатора

   break;

  case 0xBF: //PA6- горизонтальная линия

   position_laser(0x00,0x80);

   shutter(open);

   for{i=0; i<=255; i++) {

   i = (unsigned char)(i);

   position_laser(i, 0x80);

   delay();

   }

   shutter(close);

   PORTB=0x00; //включает красные светодиоды индикатора

   break;

  case 0xDF: //PA5 - Вертикальная линия

   PORTB = 0x20; // подсвечивает светодиод порта PORTB

   position_laser(0x80,0x00);

   shutter(open);

   for(i=0; i<=255;i++) {

   i=(unsigned char)(i);

   position_laser(0x80,i);

   delay();

   }

   shutter(close);

   PORTB=0x00; //включает красные светодиоды индикатора

   break;

  case 0xFF: //PA4 - Правая пила под углом 45 градусов

        // с Юго-Запада на СВ

  PORTB = 0x10; // подсвечивает светодиод порта PORTB

   position_laser(0x00,0x00);

   shutter(open);

   for(i=0; i<=255; i++) {

   i = (unsigned char)(i);

   position_laser(i, i);

   delay();

   }

  shutter(close);

   PORTB=0x00; //включает красные светодиоды индикатора

   break;

  case 0xF7: //PA3 - Левая пила под углом 45 градусов

       //с ЮВ на СЗ

   PORTB = 0x08; // подсвечивает светодиод порта PORTB

   delay();

   PORTB=0x00; // включает красные светодиоды индикатора

   break;

  case 0xFB: //PA2 Окружность

   PORTB = 0x04; // подсвечивает светодиод порта PORTB

   delay();

   PORTB=0x00; //включает красные светодиоды индикатора

  break;

  case 0xFD: //PA1 Синусоида

   PORTB = 0x02; // подсвечивает светодиод порта PORTB

   delay();

   PORTB=0x00; // включает красные светодиоды индикатора

   break;

  case 0xFE: //PA0 Остановка

   PORTB = 0x01; // подсвечивает светодиод порта PORTB

   position_laser(0x00,0x00);

   shutter(close);

   delay();

   PORTB=0x00; // включает красные светодиоды индикатора

   go = 0;

   break;

  case 0xFF:

   break;

  default:

   ; //все остальные случаи

  }//конец switch(new_PORTA)

  old_PORTA = new_PORTA;

  }//конец if(new_PORTA ! = old_PORTA)

 }//конец while(go)

}//конец main


//********************************************************************

// initialize_ports: производится конфигурация портов в качестве

//входных/выходных

//********************************************************************

void initialize_ports(void)

{

DDRA=0x00; //установить PORTA в качестве входного порта

DDRB=0xFF; //установить PORTB в качестве выходного порта

PORTB=0x00; //включить красные светодиоды индикатора

DDRS=0xFF; //установить PORTT в качестве выходного порта

DDRP=0xFF; //установить PORTP в качестве выходного порта

}

//********************************************************************

/// /shutter(int action) : открытие и закрытие затвора

//********************************************************************

void shutter(int action) {

 if (action == open) PORTP = 0x01;

 if (action == close) PORTP = 0x00;

}


//********************************************************************

//position_laser(unsigned char x_pos, unsigned char y_pos): посылает

//сигнал управления для каналов X и Y гальванометра из портов PORTS

//и PORTT соответственно.

//********************************************************************

void position_laser(char x_pos,char y_pos) {

 PORTS = x_pos;

 PORTT = y_pos;

}


//********************************************************************

//delay(void): создает задержку

//********************************************************************

void delay(void) {

 int j;

 for(j=0x0000; j<0x1000; j=j+0x01) {

  asm("nop");

 }

}

//********************************************************************

7.2.7. Испытания устройства

До подсоединения компонентов системы к МК 68HC12, мы должны полностью проверить схему. В главе 5 мы рассматривали методики проверки, позволяющие моделировать входы системы переключателями, а выходы светодиодами. Наше устройство уже содержит переключатели и светодиоды для такой проверки. Однако, как мы проверим аналоговые сигналы? Имеется два метода, позволяющих легко проверить связь друг с другом сигналов на каналах X и Y:

1) использование перьевого X-Y графопостроителя,

2) использование классической контрольно-измерительной методики, связанной с получением так называемых фигур Лиссажу.

При первой методике, выходной аналоговый сигнал, формируемый X-каналом ЦАП, переключается с X-канала гальванометра на X-канал перьевого X-Y графопостроителя, а сигнал с Y-канала гальванометра на Y-канал на графопостроителя. Сигнал управления затвором может быть подан на драйвер пера графопостроителя, смещающийся вверх и вниз. Необходим плоттер со специальными характеристиками, чтобы определить, требуется ли схема интерфейса между ТТЛ совместимым сигналом для управления затвором от 68HC12 и управления движением пера вверх и вниз. После подключения микроконтроллера 68HC12 к графопостроителю, каждое из изображений может быть полностью проверено.

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

После подключения 68HC12 к осциллографу, каждое из изображений также может быть полностью проверено. Дополнительную информация о фигурах Лиссажу можно найти в [2].

7.2.8. Заключительные испытания системы управления

После полной проверки программного обеспечения, оно может быть испытано совместно с устройством управления лазером. Реальные устройства описываются в литературе, выпускаемой изготовителями оптических устройств и в учебниках, посвященных оптическим блокам [3, 8].

7.3. Цифровой вольтметр

7.3.1. Описание проекта

Для этого проекта мы должны разработать цифровой вольтметр (ЦВ), способный измерять входной аналоговый сигнал в диапазоне от +10 до –10 В. Измеряемое напряжение, отображается на ЖК дисплее, число знакомест которого позволяет отображать числа от 0 до 100.

Диапазон входных измеряемых напряжений для модуля аналого-цифрового преобразования ATD МК 68HC12 составляет 0…5 В. Чтобы измерить входное напряжение в более широком диапазоне, необходим внешний интерфейс. Поэтому мы преобразуем входной сигнал ±10 В в сигнал, лежащий в диапазоне от 0 до 5 В.

7.3.2. Системы 68HC12 используемые в проекте

Для реализации проекта мы должны будем использовать следующие модули в составе МК 68HC12, внешние устройства и программы управления:

• Модуль ATD в составе МК 68HC12;

• Символьный ЖК индикатор;

• Интерфейс преобразователя диапазона входного сигнала;

• Алгоритм преобразования измеряемого напряжения в ASCII код для отображения на ЖК индикаторе.

Прежде чем разрабатывать программное обеспечение, рассмотрим некоторые аппаратные решения.

7.3.3. Расчет интерфейса модуля ATD

В разделе 5.9 мы описали, как подключить аналоговое устройство ввода данных к МК 68HC12, использовав методику расчета интерфейса преобразователя. Мы можем применить этот материал для разработки устройства, изменяющего диапазон входного напряжения от исходного ±10 В до диапазона от 0 до 5 В, совместимого с подсистемой аналого-цифрового преобразования для 68HC12.

Структурная схема согласующего устройства приводится на рис. 7.13. Это устройство должно сформировать напряжение 5 В на входе АЦП микроконтроллера, когда на вход вольтметра подается 10 В, и 0 В на входе АЦП при напряжении в –10 В на входе вольтметра. Чтобы выполнить такое преобразование, входной сигнал должен быть умножен на масштабирующий коэффициент, и, кроме того, должно быть создано напряжение смещения. В нашей схеме операцию масштабирования выполняет блок K, и его выходной сигнал суммируется с сигналом смещения B.

Рис. 7.13. К расчету согласующего устройства


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

V2max = V2min * K + B

V1max = V1min * K + B

Нетрудно установить, что V1min = –10 В, а V2min = + 10 В, в то время как V1max = 0 В, и V2max = 5 В. Подставим эти значения в нашу систему уравнений:

5 = 10 * K + В

0 = (–10) * K + В

В результате решения системы получим масштабный множитель K = 0.25, и напряжение смещения B = 2.5 В. Cоздадим схему на ОУ с коэффициентом передачи 0.25, и добавим напряжение смещения в 2.5 В.

При работе вольтметра после преобразования входного напряжения встроенным в МК АЦП, мы должны выполнить пересчет кода оцифровки, чтобы получить фактически измеренное входное напряжение для вывода его на дисплей. Эта операция выполняется с помощью программного обеспечения.

7.3.4. Структура программы и блок-схема алгоритма

Мы приводили структуру программы и блок-схему алгоритма для каждого из рассматриваемых проектов. Для этого проекта мы оставляем разработку структуры и блок схемы читателю в качестве домашней работы (задание 17).

7.3.5. Программа управления

/********************************************************************/

/* Имя файла: voltmeter2.с                      */

/* Это программа для реализации простого вольтметра на базе АЦП,   */

/* встроенного в МК HC12. Приведенный программный код выполняет   */

/*одно преобразование и затем программа может вручную        */

/* перезапускаться пользователем для измерения другого напряжения  */

/********************************************************************/

#include <912b32.h>

#include 


void delay_100us(void);

void ADC_convert(void);

void delay_5ms(void);


void main(void) {

 asm(" .area vectors (abs)\n" /*код инициализации вектора reset B32 */ 

  " .org 0xFFF8\n"

  " .word 0x8000, 0x8000, 0x8000, 0x8000\n"

  " .text");

 initialize_LCD(); /*инициализация ЖК дисплея */

 ATDCTL2 = 0x80; /*подача питания на АЦП, разрешение прерываний */

 delay_5ms(); /*ожидание входа АЦП в рабочий режим */

 ATDCTL3 = 0x00;

 ATDCTL4 = 0x01; /*8-разрядный результат, время выборки 2 АЦП */

         /*clk, коэффициент деления 4 */

 ADC_convert(); /*АЦП преобразование */

}


/********************************************************************/

/* void ADC_convert(void): функция, осуществляющая одно преообразо- */

/* вание и сохраняющая доступ пользователя к нему. Затем функция   */

/* преобразует текущий результат в цифровое значение. Таким образом */

/* каждое отдельное число может быть выделено, преобразовано в ASCII*/

/* код и выведено на ЖКД                       */

void ADC_convert() {

 unsigned int sumadr;

 unsigned int avg_bin_voltage;

 unsigned int int_voltage;

 unsigned int tens_int;

 unsigned int ones_int;

 unsigned int tenths_int;

 unsigned int hundredths_int;

 double voltage, abs_voltage;

 char tens;

 char ones;

 char tenths;

 char hundredths;

 ATDCTL5 = 0x06; /*проводится 4 преобразования, канал 6 */

 while((ATDSTAT & 0x8000)!= 0x8000) {

 /*Подождите окончания преобразования */

 }

 /*усреднение по четырем результатам */

 sumadr = ADR0H + ADR1H + ADR2H + ADR3H;

 avg_bin_voltage = sumadr/4;

 /* преобразование результата в напряжение, лежащее в диапазоне от */

 /* 0.00 до 5.00 В */

 voltage = (avg_bin_voltage/256)*5;

 /*приведение напряжения к диапазону от -10.00 до +10.00 В */

 /*обращение процесса, выполняемого аналоговым интерфейсом */

 abs_voltage = (fabs)((voltage - 2.5) * 4);

 /*преобразование результата в целое число в диапазоне от -1000 */

 /* до +1000 */

 int_voltage = (100*voltage);

 /*Выделение и преобразование наибольшей значащей цифры в */

 /* ASCII код десятичного значения, прибавление 48, */

 /* результат дает ASCII код */

 tens_int = int_voltage/1000;

 tens = (char)(tens_int + 48);

 /*Выделение и преобразование следующей наибольшей значащей цифры в */

 /* ASCII код десятичного значения, прибавление 48, */

 /* результат дает ASCII код*/

 ones_int = int_voltage/100;

 ones = (char)(ones_int + 48);

 /*Выделение и преобразование следующей наибольшей значащей цифры в */

 /* ASCII код десятичного значения, прибавление 48, */

 /* результат дает ASCII код */

 tenths_int = (int_voltage - ones_int*100)/10;

 tenths = (char)(tenths_int + 48);

 /*Выделение и преобразование следующей наибольшей значащей цифры в */

 /* ASCII код десятичного значения, прибавление 48, */

 /* результат дает ASCII код */

 hundredths_int = (int_voltage - ones_int*100 - tenths_int*10)/1;

 hundredths = (char)(hundredths_int + 48);

 /*Вывод результата на ЖКД */

 if (voltage < 0) putchars('-'); /*Вывести отрицательный знак*/

 else putchars('+'); /*Вывести положительный знак*/

 putchars(tens);

 putchars(ones);

 putchars('.');

 putchars(tenths);

 putchars(hundredths);

 putchars(' ');

 putchars('V');

}

/********************************************************************/


/********************************************************************/

/*задержка в 100 мкс, на базе таймера с частотой 8 МГц        */

/********************************************************************/

void delay_100us(void) {

 int i;

 for (i=0; i<50; i++) {

  asm("nop");

 }

}


/********************************************************************/

/*задержка в 5 мс, на базе таймера с частотой 8 МГц         */

/********************************************************************/

void delay_5ms(void) {

 int i;

 for (i=0; i<50; i++) {

  delay_100us();

 }

}


/********************************************************************/

/*Функции инициализации посылают на ЖКД необходимую стартовую    */

/* последовательность. Формируется последовательность команд инициали-*/

/* зации соответствующих техническим данным производителя дисплея.  */

/********************************************************************/

void initialize_lcd(void) {

 delay_5ms();

 delay_5ms();

 delay_5ms(); /*задержка на 15 мс перед включением ЖКД */

 putcommands(0x38); /*установочный интерфейс */

 delay_5ms();

 putcommands(0x38);

 delay_100us();

 putcommands(0x38);

 putcommands(0x38);

 putcommands(0x0C);

 putcommands(0x01); /*Очистить дисплей */

 putcommands(0x06); /*Установить режим увеличения адреса на единицу*/

 putcommands(0x0E); /*Включить дисплей,вывести мигающий курсор */

 putcommands(0x02); /*Возврат */

}


/********************************************************************/

/* Функция вывода инициализирует порт данных, создает сигналы RS и */

/* разрешения и посылает их на соответствующий порт         */

/********************************************************************/

void putchars(unsigned char c) {

 DDRP = 0xFF; /*установить Port P как выходной */

 DDRDLC = DDRDLC | 0x0C; * установить PORTDLC[3:2] как выходной */

 PORTP = c; /* присвоить знак С порту данных */

 PORTDLC= PORTDLC|0x08 /* установить RS в 1 для данных */

 PORTDLC= PORTDLC|0x04 /* установить E в 1 ( PORTDLC[5] = 1) */

 PORTDLC = 0; /* установить E и RS в 0 */

 delay_5ms(); /* подождать 5мс */

}


/********************************************************************/

/*Функция putcommand пересылает данные в контроллер на ЖК дисплея  */

/********************************************************************/

void putcommands(unsigned char d) {

 DDRP = 0xFF; /*установить порт PORTP в качестве выходного */

 DDRDLC = DDRDLC|0x0C; /*установить выводы PORTDLC[3:2] */

            /* в качестве выходных */

 PORTDLC = PORTDLC & 0xF7; /* команда RS = 0 */

 PORTP = d; /*передача команды на на ЖКД */

 PORTDLC = PORTDLC|0x04; /*E = 1 */

 PORTDLC =0; /*E = 0 */

 delay_5ms(); /*пауза 5 мс */

}


/********************************************************************/

/*Функция lcd_print посылает строку на ЖК дисплей          */

/********************************************************************/

void lcd_print(char *string) {

 putcommands(0x02); /*установка курсора на первую строку */

           /*putcommand для выделения строки */

 while(*(string) != '\0') {

  putchars(*string);

  string++;

 }

}


/********************************************************************/

/*задержка в 5 мс, на базе таймера с частотой 8 МГц         */

/********************************************************************/

void delay_5ms(void) {

 int i;

 for (i=0; i<50; i++) {

  delay_100us();

 }

}

/********************************************************************/


/********************************************************************/

/*задержка в 100 мкс, на базе таймера с частотой 8 МГц        */

/********************************************************************/

void delay_100us(void) {

 int i;

 for(i=0; i<50; i++) {

  asm("nop");

 }

}

/********************************************************************/

7.3.6. Измерение неэлектрических величин

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

Датчик температуры. Например, мы можем подключить к МК 68HC12 градуированный по шкале Фаренгейта прецизионный датчик температуры LM34, выпускаемый компанией National Semiconductor. Этот датчик имеет постоянный коэффициент преобразования в +10 мВ на градус Фаренгейта, в диапазоне от –50 до +300 Ф. Схема подключения LM34 приведена на рис. 7.14. Она состоит из собственно датчика LM34 и цепи фильтра, образованной резистором и конденсатором. Такая схема обеспечивает прямое преобразование измеряемой температуры. Например, при 70 Ф, LM34 создает выходной сигнал в 700 мВ. Это напряжение умножается на 100, чтобы обеспечить прямое преобразование напряжения в вольтах в температуру, выраженную в градусах Фаренгейта для вывода на дисплей. Это значение должно затем быть преобразовано в ASCII код для вывода на ЖК дисплей.

Рис. 7.14. Интерфейс для прецизионного датчика температуры по шкале Фаренгейта LM34 компании National Semiconductor


Датчик влажности. Фирма Honeywell производит ряд датчиков влажности (Humidity/Moisture) HIH-3610 [5]. Эти датчики калиброваны при питающем напряжении 5 В. Датчики обеспечивают линейный выход постоянного напряжения от 0,8 к 4,07 В при относительной влажности, изменяющейся от 0 до 100%, соответственно. Эти датчики могут быть связаны со встроенным АЦП МК 68HC12 непосредственно без согласующей схемы.

7.4. Стабилизация скорости вращения двигателя с использованием оптического тахометра

7.4.1. Описание проекта

Целью этого проекта является стабилизация скорости вращения электродвигателя.

Чтобы осуществить такую стабилизацию, используем управление с обратной связью. Мы будем постоянно контролировать скорость вращения двигателя в рабочем режиме и корректировать ее, изменяя напряжение питания, подводимое к двигателю. Мы также будем показывать текущую скорость вращения двигателя, выраженную в оборотах в минуту, на ЖК индикаторе.

В проекте используется двигатель постоянного тока с постоянным магнитом, выпускаемый компанией Electro-Craft Corporation и оптический кодер [11]. Двигатель имеет следующие характеристики:

• Постоянное питающее напряжение: 12 В;

• Скорость на холостом ходу: 2500 об/мин при 12 В;

• Пусковой ток: 2 А;

• Ток холостого хода: 370 мА;

• Ток при умеренной нагрузке: 600 мА;

• Ток останова: 4 А;

• Двигатель имеет один канал оптического кодера (Servo-Tek # PMBX-60-05).

Кодер формирует 60 ТТЛ-совместимых импульсов за один оборот двигателя.

После этого краткого описания проекта возникает ряд вопросов:

1. Как контролировать скорость двигателя?

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

3. Микроконтроллер 68HC12 питается от постоянного напряжения в 5 В при очень малом выходном токе источника. Как управлять с его помощью двигателем с высоким рабочим током, питающимся от постоянного напряжения в 12 В?

4. Как можно совместить управление скоростью двигателя с выполнением других задач, стоящие перед системой?

5. Какие системы, встроенные в 68HC12, должны использоваться, чтобы выполнить эту задачу?

Как и прежде, мы обратимся сначала к нашему «сундучку инструментов», чтобы выяснить, какие инструментальные средства мы уже использовали в проектах и какие придется разрабатывать. Давайте сделаем это одновременно с рассмотрением каждого из сформулированных выше вопросов. При обсуждении воспользуемся схемой регулирования частоты вращения двигателя, представленной на рис. 7.15.

Рис. 7.15. Схема стабилизации скорости вращения двигателя


1. Как контролировать скорость двигателя? Как было упомянуто, мы используем для измерения скорости оптический кодер, формирующий 60 импульсов за оборот двигателя.

Подсчитав число импульсов на фиксированном временном интервале, можно определить скорость двигателя. Число импульсов, появляющихся внутри данного временного интервала, можно измерить, используя систему прерываний МК 68HC12 в режиме реального времени (RTI) и аккумулятор импульсов (PA). Обе эти системы обсуждались в главе 4. Как мы уже упоминали, система RTI генерирует прерывания через равные временные интервалы, определенные пользователем, чтобы «напоминать» МК о необходимости периодически выполнять задачу стабилизации, а аккумулятор PA может быть конфигурирован, чтобы считать импульсы. Объединив эти системы, мы сможем подсчитать число импульсов оптического кодера, появляющихся внутри данного временного интервала и, затем вычислить скорость вращения двигателя в оборотах в минуту.

Рассмотрим конкретный пример. Предположим, что мы конфигурировали RTI на прерывание через каждые 32.768 мс, и в течение этого интервала, PA зафиксировал появление 52 импульсов. Какова будет при этом скорость вращения двигателя в оборотах в минуту? Чтобы вычислить необходимо выполнить следующий перевод единиц измерения:

(52 импульса/32.768 мс) (1 об/60 импульсов) (1,000 мс/1 секунда) (60 с/1 мин) = 1,586 об/мин

2. Каким образом регулировать напряжение питания двигателя, чтобы изменять его скорость? В главе 4 мы обсуждали концепцию широтно-импульсной модуляции (ШИМ).

Было показано, что среднее значение напряжения на двигателе изменяется при изменении коэффициента заполнения питающего его импульсного напряжения. Коэффициент заполнения определяется как отношение времени включенного состояния ключа к периоду переключения. Если мы используем ШИМ, то сможем изменять питающее напряжение и, как следствие, скорость двигателя. Примем для простоты, что скорость двигателя линейно связана с питающим напряжением. Проверим это предположение экспериментально. Сигналы ШИМ могут быть сформированы за счет функции сравнения таймера (для всех моделей МК 68HC12) или с помощью широтно-импульсных модуляторов (только для B32 и для всех МК семейства HCS12). Используем модуль ШИМ микроконтролеров B32, который обсуждался в главе 4. Можно вспомнить также, что мы уже использовали ШИМ в настоящей главе, чтобы генерировать сигналы управления двигателем робота, проходящего через лабиринт.

3. Микроконтроллер 68HC12 питается от постоянного напряжения в 5 В при очень малом выходном токе источника. Как управлять с его помощью двигателем с высоким рабочим током, питающимся от постоянного напряжения в 12 В? В главе 5, мы обсуждали, как двигатель может управляться от 68HC12 с использованием силовых полупроводниковых ключей. Сигнал управления ШИМ подается на затвор МОП-транзистора. Двигатель подключен между полюсом напряжения питания и стоком МОП-транзистора, как показано на рис.7.15. Для этого конкретного проекта, мы используем мощный МОП-транзистор IRF530N HEXFET компании International Rectifier. Он выпускается в корпусе TO-220AB с тремя выводами и рассчитан на напряжения переключения в 100 В и рабочие токи в 17 А. Внимание!: Не забудьте включить обратный диод параллельно двигателю.

4. Как можно контролировать скорость двигателя и одновременно выполнять другие задачи, стоящие перед системой? Если мы будем использовать обычную последовательную обработку данных, наш процессор будет постоянно привязан к операциям контроля скорости двигателя, регулирования его питающего напряжения и отображения текущего значения скорости на ЖК индикаторе. Однако, как мы упомянули в ответе на вопрос 1, мы используем RTI систему, чтобы реализовать задачи, связанные с этим управлением, один раз в 33 мс. Между этими периодическими прерываниями, МК может выполнять другие задачи. Концепция периодических прерываний для управления двигателем иллюстрируется на рис. 7.16. Мы исследуем связанные с ней понятия в следующей главе.

Рис. 7.16. Программа управления двигателем в режиме прерываний


5. Какие подсистемы, встроенные в 68HC12, должны быть использованы, чтобы выполнить эту задачу? На этот вопрос мы в сущности уже ответили. Однако для завершенности обсуждения составим список подсистем необходимых для выполнения проекта:

• Модуль ШИМ;

• Модуль меток реального времени;

• Счетчик внешних событий (Аккумулятор импульсов);

• Силовой коммутатор для управления двигателем;

• Оптический кодер;

• Жидкокристаллический (ЖК) индикатор.

7.4.2. Немного теории

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

Требования к двигателю. Специфический двигатель, который мы используем — это двигатель постоянного тока, выпускаемый компанией Electro-Craft Corporation. Мы уже рассмотрели основные характеристики двигателя. Однако, имеется ряд дополнительных характеристик, которые требуются для этого проекта, таких, например, как зависимость скорости двигателя от его тока. Так как они не приводятся в информационных данных, мы получим их экспериментально. Будем изменять питающее напряжение, поданное на двигатель, и соответствующую этому напряжению скорость. Для этого подключим частотомер на выход оптического кодера, чтобы определить частоту следования импульсов при заданном питающем напряжении двигателя, и затем вычислим скорость вращения. Одновременно будем измерять также токи двигателя. Результаты эксперимента показаны на рис. 7.17.

Рис. 7.17. Результаты испытаний двигателя


Оптический кодер. Существует широкое разнообразие оптических кодеров для определения скорости вращения вала двигателя. Эти кодеры закрепляются непосредственно на валу, или могут быть связаны с валом какими-либо устройствами. При установке кодера на вращающийся вал, это устройство обеспечивает на выходе прямоугольное напряжение. Кодеры питаются от постоянного напряжения 5 В, и рассчитаны на максимальную скорость вращения в 12 000 об/мин. Мы используем оптические кодеры, чтобы обеспечить измерение скорости вращения двигателя, как описано в [11]. Установка для измерения частоты вращения показана на рис. 7.18.

Рис. 7.18. Установка для измерения скорости вращения двигателя


Прерывания в режиме реального времени. Мы используем в МК 68HC12 модуль меток реального времени (RTI), периодически прерывая работу 68HC12, чтобы измерить скорость двигателя и скорректировать коэффициент заполнения ШИМ, если это необходимо для стабилизации скорости вращения двигателя. Перед обсуждением программного кода для управления RTI, мы советуем Вам, возобновить в памяти информацию о сбросах и прерываниях. Ниже приведен краткий обзор действий, необходимых, чтобы инициализировать прерывание RTI:

• Инициализируют вектор прерывания по запросу RTI;

• Устанавливают масштабирующий коэффициент RTR[2:0];

• Устанавливают флаг RTIE разрешения прерываний от RTI в регистре RTICTL;

• Очищают флаг RTIF в регистре RTIFLG;

• Разрешают все маскируемые прерывания прерывания (команда CLI).

Приведенные ниже программный код поможет Вам ознакомиться с работой RTI. В этом примере, мы переходим к программе ISR обработки прерывания RTI, переключая флаг порта PORTP. Если вы исследуете возникающую в результате форму сигнала, показанного на рис. 7.19, с помощью осциллографа или логического анализатора, то сможете измерить период повторения системы RTI прерываний.

Рис. 7.19. Прерывания в режиме реального времени


//********************************************************************

//имя файла: RTI_test.с//

//Port P[0]: Конфигурируется как цифровой выходной порт, обеспечивающий TTL

// совместимые сигналы для управления затвором.

// авторы: Стив Барретт и Даниель Пак

//дата создания: Mar 10, 2003

//последняя редакция: Mar 10, 2004

//********************************************************************


#include <912b32.h>

#pragma interrupt_handler RTI_isr


//Функции-прототипы

//***************************************************

void initialize_ports(void); //инициализация портов

void RTI_isr(void);

void initialize_RTI(void);


//main program* *******************************************************

void main(void) {

 //инициализация вектора сброса для B32

 asm(".area vectors(abs)\n"

  " .org 0xFFF0\n" // инициализация вектора прерывания RTI

  " .word _RTI_isr\n"

  " .org 0xFFF8\n" // инициализация вектора сброса для B32

  " .word 0x8000, 0x8000, 0x8000, 0x8000\n"

  " .text");

 initialize_RTI(); //инициализация модуля RTI

 initialize_ports(); //инициализация портов

 PORTP = 0x01; // разрешение PORTP вывод 0

 asm("cli"); //разрешение всех маскируемых прерываний

 .

 .

 .

}


//********************************************************************

// определения функций

// ********************************************************************


//Initialize_ports: начальная конфигурация

//для портов входа/выхода

// ********************************************************************

void initialize_ports(void) {

 DDRP=0xFF; //порт PORTP устанавливается как выходной

}


// ********************************************************************

//RTI_isr: подпрограмма обслуживания прерываний по RTI

//*********************************************************************

void RTI_isr(void) {

 RTIFLG = 0x80; //сбрасывается флаг RTIF

 PORTP =~(PORTP); //переключить выходы PORTP

}

// ********************************************************************


// ********************************************************************

//initialize_RTI:конфигурирует модуль RTI

// ********************************************************************

void initialize_RTI(void) {

 RTICTL = 0x86; // устанавливается таймер RTI

 RTIFLG = 0x80; //сбрасывается флаг RTIF

}

// ********************************************************************

Аккумулятор импульсов. Число импульсов, поступающих от оптического кодера, подсчитывается аккумулятором импульсов (PA). Он инициализируется в начале программы и затем фиксирует текущее число импульсов за интервал каждого прерывания RTI. По известному интервалу времени между прерываниями RTI (32.768 мс) можно определить число импульсов, поступивших на PA между двумя прерываниями, и затем вывести на дисплей скорость вращения двигателя. Для конфигурации PA системы существует специальный код.

// **********************************************************************

//initialize_PA: инициализация модуля аккумулятора импульсов МК 68HC12

// **********************************************************************

void initialize_PA(void) {

 TIOS = 0x00; //Конфигурировать канал 7 для работы счетчика импульсов

 TCTL1 = 0x00; //кодера - 3 оператора

 OC7M = 0x00;

 TSCR = 0x80; //Установить бит разрешения таймера

 PACTL = 0x70; //Разрешить работу PA, режим счета событий, событие

        // по фронту импульса,

}

Объединим теперь эту программу с программой для RTI, чтобы подсчитать число импульсов кодера, появившихся за интервал прерывания. Получив, этот результат, преобразуем его в число об/мин и выведем полученное значение на ЖК индикатор.

// **********************************************************************

//имя файла: motor.с

//автор: Steve Barrett and Daniel Pack

//дата создания: Mar 10, 2003

//последняя редакция: Mar 25, 2004

// **********************************************************************

// включенные файлы *****************************************************

#include <912b32.h>

#pragma interrupt_handler RTI_isr //объявить подпрограмму прерывания по RTI


// функции-прототипы ****************************************************

void initialize_ports(void); //инициализировать порты

void RTI_isr(void); //подпрограмма обслуживания прерываний RTI

void initialize_RTI(void); // инициализировать систему RTI

void initialize_PA(void); //инициализировать аккумулятор импульсов (PA)

void initialize_LCD(void); // инициализировать ЖК индикатор

void display_count_LCD(unsigned int); //вывод текущего содержимого аккумулятора PA

                    // на ЖК индикатор

void putchars(unsigned char); //функция поддержки ЖКИ - вывести символ

void putcommands(unsigned char);// функция поддержки ЖКИ вывести команду

void delay_5ms(void); //задержка 5 мс

void delay_100us(void); //задержка 100 мкс

unsigned int old_count; //последнее значение, записанное в (PA)

int RTI_int_count =0; //используется для подсчета RTI прерываний


//главная программа****************************************************

void main(void) {

 asm(" .area vectors(abs) \n" //inline assembly statement

  ".org 0xFFF0\n" //инициализация вектора прерываний RTI

  ".word _RTI_isr\n"

  " .org 0xFFF8\n" // инициализация вектора reset для 68HC12 B32

  " .word 0x8000, 0x8000, 0x8000, 0x8000\n"

  " .text");

 void initialize_ports(void); // инициализация портов

 initialize_LCD(); //инициализация ЖКИ

 initialize_RTI(); //инициализация модуля RTI

 initialize_PA(); //инициализация аккумулятора импульсов

 asm("cli"); //разрешение глобального маскирования

       //прерываний

 while(1) //продолжение цикла до следующего прерывания

 {

  ;

 }

}

//********************************************************************


//********************************************************************

//initialize_ports: определяет направление передачи портов

//********************************************************************

void initialize_ports(void) {

 DDRP = 0xFF; // порт PORTP устанавливается как выходной для ШИМ

 DDRT = 0x00; // PORTT устанавливается как входной, вывод PORTT[7]

        // в качестве входа аккумулятора импульсов PA

 DDRB = 0xFF; // PORTB устанавливается как выходной - порт

        // данных для ЖКД

 DDRDLC = 0xFF; // PORT DLC устанавливается как выходной - сигналы

         // управления для ЖКИ

}


//********************************************************************

//RTI_isr: подпрограмма прерывания по RTI

//********************************************************************

void RTI_isr(void) {

 unsigned int new_count;

 unsigned int pulse_count;

 float max_count = 65535.0;

 new_count = PACNT; //передается текущее число импульсов, записанное в PA

 if (new_count > old_count) //определяется приращение числа импульсов

  pulse_count = new_count - old_count;

 else pulse_count = (unsigned int)(max_count-(float)(old_count = new_count));

 RTI_int_count = RTI_int_count + 1;// изменяется показание счетчика

                  //RTI-прерываний

 if (RTI_int_count == 10) // изменяется показание ЖКД через

              // каждые 10 прерываний

 {

  display_count_LCD(pulse_count); //изменяется ЖКИ

  RTI_int_count = 0; //сбрасывается счетчик прерываний RTI

 }

 old_count = new_count;

 RTIFLG = 0x80; //сбрасывается RTI

}


// ********************************************************************

// initialize_RTI:конфигурирует регистры, связанные с RTI

// - регистр RTICTL

// -- разрешает работу модуля RTI установкой бита RTIE

// -- период RTI в 32.768 мс

// - сбрасывает бит RTIF в регистре флагов (RTIFLG)

// ********************************************************************

void initialize_RTI(void) {

 RTICTL = 0x86; // устанавливается период RTI на 32.768 мс

 RTIFLG = 0x80; //сбрасывается флаг RTIF

}


// ********************************************************************

//initialize_PA: инициализация модуля аккумулятора импульсов

// ********************************************************************

void initialize_PA(void) {

 TIOS = 0x00; // Конфигурирует канал 7 для работы

 TCTL1 = 0x00; // счетчика импульсов оптического кодера

 OC7M = 0x00;

 TSCR = 0x80; // устанавливает бит разрешения работы таймера

 PACTL = 0x70; // разрешает работу РА, режим счета событий,

        // по фронту импульса,

}


/********************************************************************/

/* initialize_LCD: инициализации ЖКИ                 */

/* передает на ЖКИ стартовую последовательность команд управления  */

/* - PORTDLC[3]: линия управления RS ЖКИ              */

/* - PORTDLC[2]: линия управления E для ЖКИ             */

/* - PORTB: двунаправленная магистраль данных для ЖКИ        */

/********************************************************************/

void initialize_LCD(void) {

 delay_5ms();

 delay_5ms();

 delay_5ms(); /*ожидание в течение 15 мс перед включением ЖКИ */

 putcommands(0x38); /*разрядность данных интерфейса 8 бит */

 delay_5ms(); /*задержка */

 putcommands(0x38); /*интерфейс в 8 бит */

 delay_100us(); /*задержка */

 putcommands(0x38); /* разрядность данных интерфейса 8 бит */

 putcommands(0x38); /* интерфейс в 8 бит */

 putcommands(0x0C); /*включение дисплея */

 putcommands(0x01); /*очистка дисплея */

 putcommands(0x06); /*установка режима инкремента адреса */

 putcommands(0x00);

 putcommands(0x00);

 putcommands(0xC0); /*курсор на линию 2 знакоместо 1 */

 putchars('R'); /* вывести "PRM" - скорость */

        /*в об/мин на строку 2 ЖКИ */

 putchars('P');

 putchars('M');

}


/********************************************************************/

/*putchars: функция посылает ASCII код для вывода на ЖКИ       */

/********************************************************************/

void putchars(unsigned char c) {

 PORTB = c; /*вывести на порт PORTB код символа */

 PORTDLC = PORTDLC|0x08; /*установить RS в 1 для передачи данных */

 PORTDLC = PORTDLC|0x04; /*установить E в 1 */

 PORTDLC = 0x00; /* установить E и RS в 0 */

 delay_100us(); delay_100us();

}


/********************************************************************/

/*putcommands: функция посылает команду управления ЖКИ       */

/********************************************************************/

void putcommands(unsigned char d) {

 PORTDLC = PORTDLC&0xF7; /*установить RS в 0 для передачи команды */

 PORTDLC = PORTDLC|0x04; /*установить E в 1 */

 PORTDLC = 0x00; /* установить E и RS в 0 */

 delay_100us();

 delay_100us();

}


/********************************************************************/

/* delay_5ms: программная задержка 5 мс               */

/********************************************************************/

void delay_5ms(void) {

 int i;

 for (i=0; i<50; i++) {

  delay_100us();

 }

}


/********************************************************************/

/* delay_100us:программная задержка в 100 мс            */

/********************************************************************/

void delay_100us(void) {

 int i;

 for(i=0; i<800; i++) {

  asm("nop");/*выполнение команды nор ассемблера занимает 1 период*/

 }

}


/********************************************************************/

/* display_count_LCD: преобразует целое число в ASCII символ     */

/* для вывода на ЖКИ                         */

/********************************************************************/

void display_count_LCD(unsigned int count) {

 unsigned int thousands_int;

 unsigned int hundreds_int;

 unsigned int tens_int;

 unsigned int ones_int;

 char thousands;

 char hundreds;

 char tens;

 char ones;

 /*выбирает и преобразует наибольшую значащую цифру в десятичное */

 /* значение + 48, образуя ASCII код */

 thousands_int = count/1000;

 thousands = (char)(thousands_int + 48);

 /*выбирает и преобразует следующую наибольшую значащую цифру */

 /* в десятичное значение + 48, образуя ASCII код */

 hundreds_int = (count - thousands_int*1000)/100;

 hundreds = (char)(hundreds_int + 48);

 /*выбирает и преобразует следующую наибольшую значащую цифру */

 /* в десятичное значение + 48, образуя ASCII код */

 tens_int = (count - thousands_int*10 0 0 - hundreds_int*100)/10;

 tens = (char)(hundreds_int + 48);

 /*выбирает и преобразует следующую наибольшую значащую цифру */

 /* в десятичное значение + 48, образуя ASCII код */

 ones_int = (count-thousands_int*1000-hundreds_int*100-tens_int*10);

 ones = (char)(ones_int + 48);

 /*выводит результат на ЖКИ*/

 putcommands(0x80); /*курсор ЖКИ переводится на строку 1, позицию 1*/

 putchars(thousands);

 putchars(hundreds);

 putchars(tens);

 putchars(ones);

}

/********************************************************************/

/********************************************************************/

7.4.3. Анализ

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

Стабилизируем скорость двигателя на уровне 1600 об/мин. Чтобы сделать это, мы должны определить требуемый коэффициент заполнения ШИМ для 1600 об/мин. Из полученного нами графика (рис. 7.17) мы видим, что для обеспечения скорости вращения приблизительно в 1600 об/мин, необходимо подать на двигатель напряжение в 8 В. При питании двигателя от источника в 12 В соответствующий коэффициент заполнения ШИМ составит 66.7 % (8 В/ 12 В).

Чтобы достичь коэффициента заполнения ШИМ в 66.7%, установим значение периода ШИМ на 256 единиц, а коэффициент заполнения ШИМ на 172 единицы.

Как мы уже указывали при предыдущем обсуждении системы ШИМ, эти значения устанавливаются в регистрах PWPER0 и PWDTY0, соответственно.

Мы используем сигнал управления ШИМ с частотой 976 Гц. Для этого частоту таймера ШИМ, равную 8 МГц, необходимо поделить на 32. При этом мы получим частоту в 250 кГц (период 4 мкс) и используем ее в качестве синхронизирующей для системы ШИМ. Поскольку мы используем период в 256 импульсов, частота управления ШИМ будет равна 976 Гц (период = 4 мкс/импульс×256 импульсов).

Ниже приведен исходный текст программы, позволяющей изменять коэффициент заполнения ШИМ, чтобы стабилизировать скорость на уровне 1600 об/мин.

Эта функция используется начальной установки скорости вращения двигателя.

/********************************************************************/

/*init_PWM(): инициализация модуля ШИМ контроллера 68HC12      */

/********************************************************************/

void init_PWM() {

 PWTST = 0x00; /*Установить порт ШИМ в нормальный режим */

 PWCTL = 0x00; /*установить режим фронтовой ШИМ */

 PWCLK = 0x28; /*без объединения каналов, ECLK/128*/

 PWPOL = 0x01; /*активный уровень 1 */

 DDRP = 0xFF; /*Порт P конфигурировать как выходной */

 PWEN = 0x01; /*установить выход ШИМ */

 PWPER0 = 255; /*установить для ШИМ период, соответствующий 976 Гц */

 PWDTY0 = 171; /*установить начальный коэффициент заполнения */

        /* на отсутствие движения */

}

/********************************************************************/

Если в процессе выполнения программы обработки прерывания RTI скорость вращения превышает 1600 об/мин, коэффициент заполнения ШИМ уменьшается на 1. С другой стороны, если скорость вращения меньше, чем 1600 об/мин, коэффициент заполнения ШИМ увеличивается на 1. Каждое приращение или снижения коэффициента заполнения ШИМ приводит к изменению скорости на 8.5 об/мин. Попробуйте вычислить это изменение в качестве домашней работы (задание 19).

Код, позволяющий сравнить опорную скорость с действительной и скорректировать ее обеспечивается в программе обработки RTI прерывания (RTLISR). В этой распечатке кода, функции поддержки ЖКД были помещены в файл для включения LCD.H с их функциями-прототипами.

7.4.4. Структура программы и блок-схема алгоритма

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

а) Управление скоростью двигателя. Блок-схема алгоритма

б) Управление скоростью двигателя. Структура программы

Рис. 7.20. К программе управления скоростью двигателя

7.4.5. Программный код

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

//********************************************************************

//имя файла: motor.с

//авторы: Стив Барретт и Даниэль Пак

//дата создания: 10 марта 2003

//последняя редакция: 2 марта 2004

//********************************************************************

//включенные файлы

#include <912b32.h>

#include  //Функции поддержки ЖКИ

#pragma interrupt_handler RTI_isr//Объявляет подпрограмму обработки

                 // прерывания по запросу (RTI)


//используемые подпрограммы

void initialize_ports(void); // Инициализация портов

void RTI_isr(void); //программа обработки прерывания по RTI

void initialize_RTI(void); // Инициализация модуля RTI

void initialize_PA(void); // Инициализация модуля счетчика событий PA

void initialize_PWM(void); // Инициализация модуля ШИМ


//глобальные переменные

unsigned int old_count; // последнее содержимое PA

int RTI_int_count = 0; // Используется для подсчета числа

            // прерывания RTI

unsigned char PWM_duty_cycle=172; // Начальный коэффициент заполнения ШИМ

unsigned int desired_motor_RPM = 1600; //эталон скорости двигателя


//********************************************************************

void main(void) {

 asm(".area vectors(abs)\n" // команда встроенного ассемблерного кода

  ".org 0xFFF0\n" // Инициализировать вектор прерывания RTI

  ".word _RTI_isr\n"

  ".org 0xFFF8\n"

  ".word 0x8000, 0x8000, 0x8000, 0x8000\n"

  ".text");

 initialize_ports(); // Инициализация портов

 initialize_LCD(); // Инициализация ЖКИ

 initialize_RTI(); // Инициализация модуля RTI

 initialize_PA(); // Инициализация модуля PA

 initialize_PWM(); //Инициализация модуля ШИМ

 asm("cli"); // Разрешить все маскируемые прерывания

 while(1) // Непрерывный цикл в ожидании прерывания

 {

  ;

 }

}


//********************************************************************

//initialize_ports: обеспечивает начальную конфигурацию портов

//ввода - вывода

//********************************************************************

void initialize_ports(void) {

 DDRP = 0xFF; // Установить PORTP как выходной для ШИМ

 DDRT = 0x00; //Установить PORTT как входной, а вывод PORTT[7]

       // как вход аккумулятора PA

 DDRB = 0xFF; //PORTB на вывод - двунаправленная

       //магистраль данных для ЖКИ

 DDRDLC = 0xFF; //порт PORT DLC на вывод - линии управления ЖКИ

}


//********************************************************************

//RTI_isr: Программа обработки прерывания по меткам реального времени

//********************************************************************

void RTI_isr(void) {

 unsigned int new_count;

 unsigned int pulse_count;

 unsigned int current_RPM;

 float max_count = 65535.0;

 new_count = PACNT; // Получить текущее число импульсов в аккумуляторе

 if (new_count > old_count) // Определить текущее число импульсов

  pulse_count = new_count - old_count;

 else pulse_count = (unsigned int)(max_count-(float)(old_count+new_count));

 // Преобразовать число импульсов кодера в об/мин

 current_RPM = (unsigned int)(pulse_count/0.032768);

 // Изменить число прерываний RTI в счетчике

 RTI_int_count = RTI_int_count + 1;

 if (RTI_int_count == 10) // Изменить на ЖКИ значение после 10 прерываний RTI

 {

  display_count_LCD(current_RPM); // Изменить показания ЖКИ

  RTI_int_count=0; // Сбросить счетчик прерываний RTI

 }

 // Изменить значение скорости двигателя

 if (current_RPM < desired_motor_RPM) PWM_duty_cycle = PWM_duty_cycle + 1; // Ускорить двигатель

 else PWM_duty_cycle = PWM_duty_cycle - 1; // Замедлить двигатель

 // Изменить скорость двигателя с помощью ШИМ

 PWDTY0 = PWM_duty_cycle; //коэффициент заполнения

 old_count = new_count;

 RTIFLG = 0x80; //сбросить флаг прерывания RTI

}


//********************************************************************

// Initialize_RTI: конфигурирует регистры связанные с RTI

//- Регистр управления RTI (RTICTL):

// - разрешение RTI, флаг RTIE

// - установка периода RTI на 32.768 мс

// - сброс RTI, бит RTIF регистре Флагов RTI (RTIFLG)

//********************************************************************

void initialize_RTI(void) {

 RTICTL = 0x86; // Установить период RTI 32.768 мс

 RTIFLG = 0x80; // сброс RTI

}


//********************************************************************

// Initialize_RTI_PA: инициализация 68HC12 аккумулятора импульсов PA

//********************************************************************

void initialize_PA(void) {

 TIOS = 0x00; // конфигурировать канал 7 для работы в качестве счетчика

 TCTL1 = 0x00; // импульсов оптического кодера

 OC7M = 0x00; // Включить бит разрешения таймера

 TSCR = 0x80; // Установить разрешение для аккумулятора по фронту

        //импульса,

 PACTL = 0x50; // Режим счета событий

}


/********************************************************************/

//initialize_PWM: генерировать сигнал ШИМ 976 Гц с */

// коэффициентом заполнения 67.2% */

/********************************************************************/

void initialize_PWM(void) {

 PWTST =0x00; /*установить ШИМ в режим нормальной работы */

 PWCTL =0x00; /*установить выравнивание по левой границ */

 PWCLK = 0x28; /*без конкатенации, разделить clk на 32 */

 PWPOL = 0x01; /*состояние : высокое затем переход к низкому */

 DDRP = 0xFF; /*установить порт PORTP как выходной */

 PWEN = 0x01; /*разрешение на ШИМ для канала 0 */

 PWPER0= 0xFF; /* установить период равным 256 */

 PWDTY0= PWM duty_cycle; /* установить коэффициент заполнения равным 172 */

}

/********************************************************************/

7.4.6. Испытания

Установка для испытания системы стабилизации скорости вращения была показана на рис. 7.18. Это — очень сложная система, содержащая целый ряд компонентов и микроконтроллерную систему 68HC12. Чтобы гарантировать успешную работу системы, настоятельно рекомендуется применять обсужденный в главе 2 метод измерений. Собрав и запустив всю систему, можно включить двигатель на умеренную нагрузку, и убедиться, что система стабилизирует скорость вращения на уровне в 1600 об/мин.

7.5. Парящий робот

7.5.1. Описание проекта

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

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

Цель этого проекта состояла в том, чтобы разработать и изготовить автономную систему парящего робота, управляемую микроконтроллером 68HC9S12. Наиболее трудным аспектом системы является проблема стабильного положения робота, которое должно постоянно поддерживаться контроллером. На рис. 7.21 показана фотография робота на базе парящей рамы. Этот проект был создан студентом старших курсов.

Рис. 7.21. Робот на базе парящей рамы


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

Парящий робот должен удовлетворять следующим общим требованиям:

1. Быть автономным;

2. Иметь рабочую область размером 16×16 футов. (40,6×40,6 см);

3. Весить не больше, чем 2,5 фунта (1,14 кг);

4. Обладать собственными средствами взлета и посадки;

5. Использовать двигатели постоянного тока;

6. Поднимать до 4,5 фунтов (2,05 кг), включая собственный вес;

7. Поддерживать постоянную высоту парения;

8. Осуществлять движение во всех направлениях;

9. Избегать препятствий;

10. Иметь энергонезависимую память данных;

11. Стоить не более $500,00.

7.5.2. Системы HCS12 используемые в проекте

В этом проекте будут использоваться следующие под системы HCS12:

• Подсистема входного захвата таймера;

• Модуль АЦП;

• Подсистема ШИМ.

7.5.3. Теоретическое обсуждение

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

Для управления использовалась T-плата компании ImageCraft с МК семейства HCS12 (рис. 7.22). Частота тактирования МК равна 25 МГц, резидентная Flash-память программ МК равна 256 Кб, оперативная память — 12 Кб и энергонезависимая память данных типа EEPROM — 4 Кб. Для бортовой схемы весьма желательна миниатюризация платы. Для управления двигателями постоянного тока используется встроенный модуль ШИМ. Пьезогирометрические датчики для трех осей вращения обеспечивают изменение углов тангажа, крена и рыскания для управления парящим роботом. Выходы гиродатчика поданы на вход таймера МК и используются, чтобы корректировать скорости вращения всех четырех двигателей парящего робота. В дополнение к гиродатчикам, на роботе установлены четыре инфракрасных датчика. Они обнаруживают преграды, когда робот приближается к стенкам или препятствиям. Выходы датчиков поданы на входной порт АЦП, их сигналы обеспечивают выбор алгоритма управления полетом, позволяющего избежать столкновений со стенками или препятствиями.

Рис. 7.22. Плата с МК НCS12


В качестве гиродатчиков используются три пьезогиродатчика GYA350 компании Futaba. Мы выбрали эти датчики, поскольку они специально предназначены для авиамоделей. Датчик весит 26 г и размещается в корпусе 27 мм×27 мм×20 мм. Он обеспечивает сигналы ШИМ частотой 55 Гц, при этом изменение ширины импульса указывает направление движения датчика и, следовательно, направление движения парящего робота. Этот гиродатчик может также работать в режиме поддержки заданного направления (режим heading-hold), при котором микроконтроллер может определить ширину импульса на выходе датчика летящего робота. При использовании функции входного захвата таймера, робот проверяет направление, оценивая выходные ШИМ сигналы гиродатчиков.

В дополнение к гиродатчикам робот использует для измерения расстояния четыре инфракрасных пары передатчик/приемник GP2D12 фирмы Sharp, чтобы избежать столкновения с любыми объектами. Эти датчики могут обеспечивать диапазон выходных напряжений, соответствующих расстоянию обнаружения от 10 до 80 см. Эти напряжения преобразуются в соответствующие цифровые значения с помощью модуля ATD микроконтроллера. Датчик легок и размещается в корпусе 45 мм×14 мм×20 мм.

Для взлета использовались четыре двигателя постоянного тока Graupner Speed300, питающиеся от постоянного напряжения 6 В. Каждый двигатель весит 50 г при диаметре вала в 2 мм и потребляемом токе до 5 A. На валу двигателя установлена маленькая пластмассовая шестерня, которая приводит во вращение большую шестерню на оси пропеллера, как показано на рис. 7.21.

7.5.4. Структура программы и блок-схема алгоритма

Схема подключения на рис. 7.23 показывает связи компонентов парящего робота с контроллером. На рис. 7.24 схематично показана конструкция робота со всеми компонентами. Обратите внимание, что ШИМ-сигналы регулирующие скорость вращения подаются на ИС силового коммутатора, которая обеспечивают достаточный ток для двигателей постоянного тока. Чтобы обеспечить питание системы используется несколько батарей. Сигналы поступают в МК от датчиков, а выходные сигналы МК управляют ИС коммутаторов двигателей.

Рис. 7.23. Схема подключения управляющих компонентов парящего робота

Рис. 7.24. Диаграмма движения робота со всеми бортовыми компонентами


На рис. 7.25 представлена структура программы для парящего робота, в которой показаны направления потоков данных между программными модулями. На рис. 7.26 показана блок-схема алгоритма программы для управления роботом.

Рис. 7.25. Структура программы парящего робота


Рис. 7.26. Блок-схема алгоритма программы парящего робота

7.5.5. Программный код

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

//********************************************************************

// filename: flying.с

// Описание программы: Эта программа запускает четыре двигателя и затем

// управляет скоростью вращения каждого из них. После взлета робота

// программа проверяет каждый из датчиков для определения положения

// робота. При обнаружении преграды или крыши, скорости вращения двигателей

// корректируются, чтобы обеспечить правильное направление движения.

//

// Авторы: Джоэль Перлин, Даниэль Пак, Стив Барретт

// Дата создания: 27 июля 2004

// используемая память: программа - 0x1000, данные - 0x3000 и

// стек - 0x4000

//********************************************************************

#include 

#include "hcs12dp256.h"


#pragma abs_address 0x3000

unsigned int count1; //счетчики переменных

unsigned int count2;

unsigned int sensor;

char sensoravg; //сохраняют данные датчиков в виде 8-разрядных чисел

volatile unsigned p; // текущий счетчик

#pragma end_abs_address


//********************************************************************

void main(void) {

 //Инициализация робота

 PWME = 0x00; //запрет ШИМ

 DDRA = 0xFF; //конфигурирование портов A и В как выходных

 DDRB = 0xFF;

 PORTA = 0xAA; //подача питания на датчики

 PORTB = 0xFF; //индикация режима программы на линейке светодиодов

        //Инициализация модуля ATD

 PORTAD1 = 0x00; //конфигурирование портов как входных

 ATD1CTL2 = 0xC2; //инициализация ATD с установкой флагов

          //преобразования в каналах

 ATD1CTL3 = 0x00; // функция установки ATD

 ATD1CTL4 = 0x80; //установить 8-разрядный режим

 PORTB = 0xFE; //показать состояние программы на линейке

        // светодиодов

 // Инициализация режима входного захвата

 TSCR1 = 0x80; //включение таймера (нормальный режим)

 TSCR2 = 0x80; //установить период переполнения счетчика временной

        // базы 8.192 мс, коэффициент деления = 1

 TIOS = 0x00; //установить каналы таймера в режим входного захвата

 TMSK1 = 0xE0;//биты разрешения прерывания по событиям на линиях [7:5]

 TFLG1 = 0xE0; //очистить флаги прерываний TFLG1

 PORTB = 0xFC; // показать состояние программы на линейке светодиодов

 // инициализация ШИМ

 PWMCTL = 0x00; //установить 8-разрядный режим

 PWMCAE = 0x10; //установить фронтовую ШИМ

 PWMPOL = 0x5F; //выбрать активным высокий логический уровень

         //назначить режим ШИМ для каналов 0,1,2,3,4 и 6

 PWMCLK = 0x50; //каналы 0,1,4 тактируются CLOCK_A и каналы

         //2,3,6 - CLOCK_В

 PWMSCLA = 0x20; //период A 0x20 = 4.1 мс

 PWMSCLB = 0x04; //период B 0x02 = 255 мкс, выбрать

         // наибольший коэффициент заполнения для всех каналов

 PWMPER0 = 255;

 PWMPER1 = 255;

 PWMPER2 = 255;

 PWMPER3 = 255;

 //Запуск двигателей

 PWME = PWME | 0x5F; // разрешения режима ШИМ для каналов 0,1,2,3,4 и 5

 PWMDTY0 = 80; // установить коэффициенты заполнения

         // для каналов 0,1,2 и 3

 PWMDTY1 = 80;

 PWMDTY2 = 80;

 PWMDTY3 = 80;

 // Взлет

 while (PWMTY0 < 200) {

  PWMDTY0 = PWMDTY0 + 1;

  PWMDTY1 =PWMDTY1 + 1;

  PWMDTY2 = PWMDTY2 + 1;

  PWMDTY3 = PWMDTY3 + 1;

 }

 P = 20;

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

 while (battery == 1) //проверить включение батареи

 {

  // двигатель 1

  ATD0CLT5 = 0x04; //режим оцифровки многоканальный

  while ((ATD0STAT0 & 0x80) == 0);

  sensoravg = ATD0DR4H; //установить PAD00 для датчика 1

  if ((int)sensoravg > 80) {

  PWMDTY0 = PWMDTY0 + 20;

  delay2();

  for (i=0; i

  }

  // двигатель 2

  ATD0CLT5 = 0x04;

 while ((ATD0STAT0 & 0x80) == 0);

  sensoravg = ATD0DR5H; //установить PAD00 для датчика 2

  if ((int)sensoravg > 80) {

  PWMDTY1 = PWMDTY1 + 20;

  delay2();

  for (i=0; i

  }

  // двигатель 3

  ATD0CLT5 = 0x04;

 while ((ATD0STAT0 & 0x80) == 0);

  sensoravg = ATD0DR6H; //установить PAD00 для датчика 3

  if ((int)sensoravg > 80) {

  PWMDTY2 = PWMDTY2 + 0;

  delay2();

  for (i=0; i

  }

  // двигатель 4

  ATD0CLT5 = 0x04;

  while ((ATD0STAT0 & 0x80) == 0);

  sensoravg = ATD0DR7H; //установить PAD00 для датчика 4

  if ((int)sensoravg > 80) {

  PWMDTY7 = PWMDTY7 + 20;

  delay2();

  for (i=0; i

  }

 } //конец while

 // снизить скорость двигателей для посадки

 while (PWMDTY0 > 80) {

  PWMDTY0--;

  PWMDTY1--;

  PWMDTY2--;

  PWMDTY3--;

  delay1();

 }

 // остановить двигатели

 PWME = 0x00;

}

//********************************************************************

7.5.6. Некоторые комментарии

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

7.6. Система защиты компьютера, основанная на нечеткой логике

7.6.1. Описание проекта

В этом разделе описана основанная на нечеткой логике система защиты от вторжения, которая может защитить ваш компьютер от несанкционированного внешнего доступа. В частности, рассматриваемая система может с помощью встроенных в МК 68HC12/HCS12 функций поддержки нечеткой логики обнаруживать внедрение постороннего гипертекста (HTTP) при туннелировании информации по сетевой шине компьютера. Туннелирование применяется для легальной установки сеансов связи между ведущим компьютером внутри компьютерной сети и компьютером, внешним по отношению к этой сети. «Злоумышленник» использует такой внешний компьютер, чтобы установить тайный сеанс связи, формируя несанкционированные сообщения внутри пакетов обычного протокола HTTP. Чтобы предотвратить такие действия, общество защиты компьютерной информации разработало в прошедшем десятилетии ряд информационных систем. Цель описываемой микроконтроллерной системы защиты состоит не в том, чтобы заменить существующие коммерческие системы, а в том, чтобы помогать таким системам, оценивая присутствие в данных сетевого трафика внедренных посторонних HTTP сообщений.

Цель данного проекта состоит в создании переносной микроконтроллерной системы для анализа туннелирования HTTP. Эта система должна обнаруживать следующие злонамеренные и несанкционированные действия при туннелирования HTTP:

• атаки на интерактивные сеансы туннелирования;

• атаки с помощью внедрения скриптов в сеансах туннелирования;

• внедрение несанкционированных видео и звуковых сигналов в передаваемых потоках информации.

Система защиты должна удовлетворять следующим системным требованиям:

1. Использовать МК семейства;

2. Иметь светодиодное табло для отображения состояния трафика;

3. Обнаруживать интерактивные сеансы туннелирования;

4. Проводить поиск внедренных скриптов при туннелировании;

5. Обнаруживать несанкционированное видео и звуковые сеансы передачи;

6. Использовать алгоритмы нечеткой логики для определения состояния сетевого трафика.

7.6.2. Использование системы HCS12

Этот проект широко использует команды для поддержки алгоритмов нечеткой логикой, которые имеются в списке команд процессорного ядра HCS12.

7.6.3. Основы теории

На рис. 7.27 показан полный процесс, который должна реализовать микроконтроллерная система защиты, чтобы идентифицировать нежелательный сетевой трафик. Система обрабатывает набор из шести входных сигналов, создаваемых ПК, используя алгоритмы нечеткой логики. (Обзор команд 68HC12 для реализации законов управления с нечеткой логикой см. в [11]). Входными сигналами для системы являются оценки уровня членства; нулевая оценка означает отсутствие членства, в то время как максимальная оценка представляет совершенное членство. Первые три входных сигнала отражают оценки членства для профилей поведения. Профиль поведения составлен из таких атрибутов сеанса, как размер пакета, число пакетов сеанса, продолжительность сеанса, соотношение между большими и малыми пакетами, направления передачи данных, средний размер пакета, изменение стандартного размера пакета и общих размеров полученных пакетов.

Рис. 7.27. Определение оценок членства для трех наборов и двух множеств (шесть входов)

Оценивается соответствие конфигурации поведения при различных типах атаки и соответствие набору ключевых слов, характерных для таких атак. Исследуются три типа атак: на интерактивное туннелирование, с помощью внедренных скриптов и с помощью несанкционированных видео и звуковых потоков. Каждому из типов атаки сопоставляется две оценки. Это множество из шести преобразуется в результирующую оценку сеанса трафика


При сравнении каждого файла на атакующее поведение (у нас три таких файла, описывающих три типа несанкционированной активности при туннелировании HTTP: атаки на интерактивные сеансы туннелирования, атаки с помощью скриптов и внедрение видео и аудио потоков) с профилем данных обычного трафика шины ПК система обнаружения вторжения формирует оценку членства во множестве действий туннелирования HTTP (tunneling activity detection — TAD). Другие три входа системы формируют оценки членства в множестве ключевых слов, характерных для каждого из видов атаки.

Пакеты данных в сеансе туннелирования HTTP, содержат одно или несколько ключевых слов, по которым можно идентифицировать присутствие туннелирования. Испытуемый ПК сравнивает 40 ключевых слов со словами, которые появляются в данных сеанса. Оценка соответствия передается переносной системе в качестве второй входной величины.

На рис. 7.27 показана вся система с необходимыми входными сигналами. На рис. 7.28 приведено размещение переносной системы для обнаружения вторжения в сетевую среду. В беспроводной сети переносная система может быть связана с персональным компьютером. Библиотечные подпрограммы Libpcap используются, чтобы выбрать пакеты сеанса из необработанных данных Internet, программа Psplice проводит синтаксический анализ отдельных пакетов, а модуль предварительной обработки сравнивает данные пакета по трем профилям поведения и по оценке членства во множестве ключевых слов. Более подробно о библиотечных подпрограммах, программе Psplice и модуле предварительной обработки, см. в [11].

Рис. 7.28. Структура связей переносной системы обнаружения вторжения с общей сетью

7.6.4. Структура программы и блок-схема алгоритма

На рис. 7.29 приведена структура аппаратных средств системы HTTP. Структура программы для системы защиты относительно проста. Она состоит из трех машин с нечеткой логикой, каждая из которых работает как программный модуль, показанный на на рис. 7.27. Чтобы увеличить быстродействие, можно использовать три платы HCS12 для обнаружения на шине трех действий вторжения. На рис. 7.30 показана блок-схема алгоритма для системы защиты.

Рис. 7.29. Структура аппаратных средств системы защиты


Рис. 7.30. Блок-схема алгоритма UML для системы защиты

7.6.5. Описание системы

На рис. 7.31a, показан внешний вид переносной системы при работе. ЖК дисплей показывает следующее начальное сообщение:

Portable HTTP

TAD System

Version 1.0

На рис. 7.31б, показан следующий типовой экран после того, как сеанс оценен:

IA: 60 Med Alert

SA: 40 Low Alert

S : 40 Low Alert

a) Компоненты, которые составляют систему контроллера. В левой нижней части показана плата 68HC12, используемая для интерфейса ПК с целевой платой, показанной в правом нижнем углу фотографии. Целевая плата использует микроконтроллер 68HC129S12DP256B, входящий в семейство контроллеров HCS12

б) Жидкокристаллический дисплей отображает типовое сообщение

Рис. 7.31. Микроконтроллерная система оценки атак на HTTP туннелирование (TAD)


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

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

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

Как только переносная система безопасности TAD получает набор шести оценок, он пересылается на три подсистемы с нечеткой логикой — подсистемы фаззификации. Каждая из подсистем TAD вычисляет оценку для каждого из видов атаки на туннелирование. Опишем теперь системы фаззификации.

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

Начнем с входных лингвистических переменных. Все подсистема имеют одинаковую градацию входных переменных. Имеются три значения переменной, описывающей уровень пар конфигурации поведения: низкий уровень членства, средний уровень и высокий уровень. Входные функции членства показаны на рис. 7.32.

Рис. 7.32. Входные функции членства для вторжения в интерактивное туннелирование подсистемы TAD


В дополнение к профилю поведения, подсистема получает также соответствующую оценку членства ключевых слов, выраженную натуральным числом пределах от 0 до 255. Функцию на рис. 7.32 можно применить и для оценки членства ключевых слов. Как показано на рисунке, использовано идентичное описаниие входной функции членства. Снова ось у представляет уровень членства от 0 до 255, и значение оси x определяет оценку членства для полученных ключевых слов.

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

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

Рис. 7.33. Выходные функции членства для интерактивного вмешательства


Вывод лингвистических переменных функции членства на рис. 7.33 может выглядеть необычно для экспертов по система с нечеткой логикой. Как мы покажем далее, проектировщики МК HCS12 фирмы Motorola упростили процесс дефаззификации, сопоставив каждой выходной функции с нечеткой логикой одно значение. Для текущей подсистемы, мы применяем девять правил, показанных в табл. 7.1, чтобы преобразовать входные переменные в выходные.

Значения для профиля поведения Значения для ключевых слов Выходные величины
Низкое Низкое Нет опасности
Низкое Среднее Низкая опасность
Низкое Высокое Опасность
Среднее Низкое Низкая опасность
Среднее Среднее Средняя опасность
Среднее Высокое Опасность
Высокое Низкое Средняя опасность
Высокое Среднее Опасность
Высокое Высокое Высокая опасность

Таблица 7.1. Правила фаззификации, позволяющие преобразовать входные переменные в выходные


Мы использовали общий метод минимакса, показанный на рис. 7.34, чтобы получить значения членства для выходных переменных. То есть наибольшее из значений членства входных функций отображается в соответствующей выходной функции. Если после применения всех правил, и выходная функция имеет более одного значения, то для выходной функции выбирается наименьшее значение. Для дефаззификации выходного значения, все выходные функции и соответствующие значения членства используются, чтобы вычислить центральное выходное значение. Полученные значения выходных функций функций (показанные на оси X рис. 7.33), умножаются на значения членства, вычисленные по девяти правилам, результаты складываются и сумма делится на число членов. Конечное числовое значение и соответствующее сообщение распечатано на экране ЖК дисплее, чтобы сообщить пользователю состояние сеанса. Таким образом, для каждого сеанса, ЖК дисплей высвечивает три числа, характеризующих три типа активности вторжения: атаки на интерактивные сеансы туннелирования, атаки с помощью скриптов и внедрение видео и аудио потоков.

Рис. 7.34. Процедуры передачи членства оценивают, используя метод Минимакса

7.6.6. Обсуждение проекта

В последнее время среди различных систем обнаружения вторжения наибольший интерес проявляется к системе обнаружения атак на туннелирование HTTP. Основной причиной такого внимания служит широкое распространение трафика HTTP в Internet. Большинство брандмейстеров не выполняет обширных проверок, чтобы проверить все атаки на туннелирование HTTP. Такие проверки весьма трудоемки, поскольку трафик HTTP составляет наибольшую часть приходящего и уходящего трафика для любой большой организации.

Микроконтроллерная система может взять на себя часть функций системы обнаружения вторжения, связанных с атаками на туннелирование HTTP. Система использует свойства сессии, чтобы проверить данные трафика Internet, которые составляют сессию Internet — программные связи между двумя компьютерами для передачи данных. Хорошим примером сессии является набор адреса сети в браузере. Ваш компьютер запрашивает сессию у ведущего компьютера, который содержит страницу сети, которая вам необходима. Как только вы закрываете веб-сайт и переходите к другому сайту, вы закрываете одну сессию и запускаете другую.

7.6.7. Программный код

//********************************************************************

// Файл: micro.с

// Функция программы: Устанавливает шесть IDS оценок членства и создает

// оценку опасности вторжения

// Авторы: Даниэль Пак, Барри Муллинз, Стив Барретт

// Дата создания: 17 июня 2004

// Установки: Program=0x1000, Data=0x3000, Stack=0x4000

//********************************************************************

#include 

#include "hcs12dp256.h"


//********************************************************************

//функции поддержки

//====================================================================

// _HC12Setup: выключить сторожевой таймер COP

//====================================================================

void _HC12Setup(void) {

 COPCTL = 0x00; // выключить сторожевой таймер COP

}


//********************************************************************

//delay: подпрограмма задержки

//********************************************************************

void delay(void) { //подпрограмма задержки

 volatile unsigned n, m;

 m = 10;

 do {

  n = 0;

  do {

  n--;

  } while(n);

  m--;

 } while(m);

}


//********************************************************************

// status_wait: время ожидания подпрограмма установки связи с ЖКД

//********************************************************************

void status_wait(void) { //время ожидания подпрограмма установки связи с ЖК дисплеем

 char temp = 0x00;

 DDRA = 0x00;

 PORTB = 0xF9;

 while ((temp & 0x03) != 0x03) {

  PORTB = 0xFF;

  temp = PORTA;

  PORTB = 0xF9;

 }

 PORTB = 0xFF;

 DDRA = 0xFF;

}


//********************************************************************

// command: пересылка команд на ЖК дисплей

//********************************************************************

void command(unsigned char n) { // пересылка команд на ЖК дисплей

 status_wait();

 PORTA = n;

 PORTB = 0xFF;

 PORTB = PORTB & 0xFA;

 PORTB = 0xFF;

}

//********************************************************************


//********************************************************************

//data: пересылка данных на ЖК дисплей

//********************************************************************

void data(unsigned char n) { // пересылка данных на ЖК дисплей

 status_wait();

 PORTA = n;

 PORTB = PORTB & 0xF2;

 PORTB = 0xFF;

}


//********************************************************************

// LCD_char: функция пересылки символа на ЖК дисплей

//********************************************************************

void LCD_char(unsigned char n) {

 // функция пересылки символа на ЖК дисплей

 data(n - 0x20);

 command(0xC0);

}


//********************************************************************

// newline: пересылка новой строки на ЖК дисплей

//********************************************************************

void newline(void) {

 // пересылка новой строки на ЖК дисплей

 int i;

 for (i=0; i<16; i++) LCD_char(' ');

}


//********************************************************************

// LCD_output: пересылка последовательности символов на ЖК дисплей

//********************************************************************

void LCD_output(char s[]) {

 // пересылка последовательности символов на ЖК дисплей

 int n = 0;

 while (s[n] != '\0') {

  LCD_char(s[n]);

  ++n;

 }

}


//********************************************************************

// Reset_cursor: возврат курсора

//********************************************************************

void Reset_cursor(void) { // возврат курсора

 data(0x00);

 data(0x10);

 command(0x24);

}


//********************************************************************

//Clearscreen: очистка экрана ЖКД

//********************************************************************

void Clearscreen(void) { // очистка экрана ЖКД

 int i,j;

 Reset_cursor();

 for (i=0; i<16; i++) for(j=0; j<16; j++) LCD_char(' ');

 Reset_cursor();

}


//********************************************************************

// Initlcd: инициализация ЖКД

//********************************************************************

void Initlcd(void) { // инициализация ЖКД

 PORTB = 0xEF; //принудительный сброс

 delay();

 PORTB = 0xFF; //все линии команд на высоком уровне

 status_wait();

 command(0x80); // установить режим текста

 data(0x00); // установить младший байт адреса текста (L)

 data(0x10); // установить младший байт адреса текста (H)

 command(0x40); //установить адрес команды текста

 data(0x10); //установить область текста

 data(0x00);

 command(0x41);

 command(0x94); //включить текстовый дисплей

 command(0xA7); //курсор 8×8 позиций

 Clearscreen();

 Reset_cursor();

}


//********************************************************************

// InitMes: начальное сообщение

//********************************************************************

void InitMes(void) { // начальное сообщение

 unsigned char k;

 for(k=0; k<3; k++) newline();

 LCD_output(" Portable HTTP");

 newline();

 LCD_output(" TAD System.");

 newline();

 LCD_output(" version 1.0");

}


//********************************************************************

// numdisplay: отображение чисел на ЖК дисплее

//********************************************************************

void numdisplay(char s) { //отображение чисел на ЖК дисплее

 char k;

 newline();

 k = s;

 s = s>>4;

 if (s > 0x08) data(s + 0x17);

 else data(s + 0x10);

 command(0xC0);

 k = k & 0x0F;

 if (k > 0x08) data(k + 0x17);

 else data(k + 0x10);

 command(0xC0);

}


//********************************************************************

// Секция данных - инициализация табличных данных

//********************************************************************

#pragma abs_address 0x3000

char BeP[12] = {0x00, 0x70, 0x00, 0x10,

 0x40, 0xC0, 0x10, 0x10,

 0x90, 0xFF, 0x10, 0x00};


char KeM[12] = {0x00, 0x70, 0x00, 0x10,

 0x40, 0xC0, 0x10, 0x10,

 0x90, 0xFF, 0x10, 0x00};


char OT[5] = {0x40, 0x60, 0x80, 0xA0, 0xC0};


char IMV[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};


char OMV[5] = {0x00, 0x00, 0x00, 0x00, 0x00};


// правило

char rules[45] = {0x00,0x03,0xFE,0x06, 0xFE,

 0x00, 0x04, 0xFE, 0x07, 0xFE,

 0x00, 0x05, 0xFE, 0x08, 0xFE,

 0x01, 0x03, 0xFE, 0x07, 0xFE,

 0x01, 0x04, 0xFE, 0x08, 0xFE,

 0x01, 0x05, 0xFE, 0x09, 0xFE,

 0x02, 0x03, 0xFE, 0x08, 0xFE,

 0x02, 0x04, 0xFE, 0x09, 0xFE,

 0x02, 0x05, 0xFE, 0x0A, 0xFF};


char result[3] = (0x00, 0x00, 0x00};

#pragma end_abs_address


//********************************************************************

//Основная программа

//********************************************************************

void main(void) {

 int index;

 char temp = 0x00;

 /* определение интерактивного туннелирования */

 asm("LDX #$3000");

 asm("LDY #$301D");

 asm("LDAA $4000"); //оценка профиля поведения

 asm("MEM");

 asm("MEM");

 asm("MEM"); //фаззификация

 asm("LDAA $4001"); //оценка членства ключевых слов

 asm("MEM");

 asm("MEM");

 asm("MEM"); //фаззификация

 asm("LDY #$301D");

 asm("LDX #$3028");

 asm("LDAA #$FF"); //инициализация минимума и бита V

 asm("REV"); //применение правил фаззификации

 asm("LDX #$3018"); //дефаззификация

 asm("LDY #$3023");

 asm("LDAB #$05");

 asm("WAV");

 asm("EDIV");

 asm("TFR Y,D");

 asm("STAB $3055"); //сохранение результата

 PORTB = 0xff;

 DDRB = 0xff;

 delay2();

 PORTB = 0x7F; //проверка платы с использованием ЖКД

 delay2();

 PORTB = 0xFF;

 /*Определение туннелирования скриптов*/

 asm("LDX #$3000");

 asm("LDY #$301D");

 asm("LDAA $4002"); //оценка профиля поведения

 asm("MEM");

 asm("MEM");

 asm("MEM"); //фаззификация

 asm("LDAA #$PF"); //оценка членства ключевых слов

 asm("MEM");

 asm("MEM");

 asm("MEM"); //фаззификация

 asm("LDY #$301D");

 asm("$3028");

 asm("$4003"); //инициализация минимума и бита V

 asm("REV"); //применение правил фаззификации

 asm("LDX #$3018"); //дефаззификация

 asm("LDY #$3023");

 asm("LDAB #$05");

 asm("WAV");

 asm("EDIV");

 asm("TFR Y,D");

 asm("STAB $3056"); //сохранение результата

 PORTB = 0xff;

 DDRB = 0xff;

 delay2();

 PORTB = 0x7F; //проверка платы с использованием ЖКД

 delay2();

 PORTB = 0xFF;

 /*Проверка внедрения потоков*/

 asm("LDX #$3000");

 asm("LDY #$301D");

 asm("LDAA $4004"); //оценка профиля поведения

 asm("MEM");

 asm("MEM");

 asm("MEM"); //фаззификация

 asm("LDAA $4005"); //оценка членства для ключевых слов

 asm("MEM");

 asm("MEM");

 asm("MEM"); //фаззификация

 asm("LDY #$301D");

 asm("LDX #$3028");

 asm("LDAA $4003"); //инициализация минимума и бита V

 asm("REV"); //применение правил фаззификации

 asm("LDX #$3018"); //дефаззификация

 asm("LDY #$3023");

 asm("LDAB #$05");

 asm("WAV");

 asm("EDIV");

 asm("TFR Y,D");

 asm("STAB $3057"); //сохранение результата

 PORTB = 0xff;

 DDRB = 0xff;

 delay2();

 PORTB = 0x7F; //проверка платы с использованием ЖКД

 delay2();

 PORTB = 0xFF;

 //Конфигурация ЖКД

 DDRA = 0xFF;

 PORTB = 0xFF,

 Initlcd(); //Инициализация ЖКД

 InitMes(); //Инициализация сообщений

 delay2();

 Clearscreen(); //Очистить экран ЖКД

 Reset_cursor; //Возврат курсора ЖКД

 newline(); //Создать новую строку на ЖКД

 newline();

 LCD_Output("IA: ");

 numdisplay(result[0]);

 if (result[0] > 0xA0) //Вывести на дисплей предупреждение

  LCD_output(" High Alert");

 else if (result[0] > 0x60) LCD_output(" Med Alert");

 else LCD_output(" Low Alert");

 newline();

 LCD_output("SA: ");

 numdisplay(result[1]);

 if (result[0] > 0xA0) LCD_output(" High Alert");

 else if (result[1] > 0x60) LCD_output(" Med Alert");

 else LCD_output(" Low Alert");

 newline();

 LCD_output{"S: ");

 numdisplay(result[2]);

 if (result[2] > 0xA0) LCD_output(" High Alert");

 else if (result[0] > 0x60) LCD_output(" Med Alert");

 else LCD_output(" Low Alert");

}

//====================================================================

7.6.8. Некоторые комментарии

Вы можете задать вопрос о необходимости иметь отдельную микроконтроллерную систему HTTP TAD. Почему бы просто не включить функцию системы HTTP TAD в состав стандартной системы обнаружения «злоумышленника»? Цель упражнения состоит в том, чтобы показать большие возможности МК HCS12 и использование их в ряде проектов. Конечно, система, описанная в этом разделе, может быть включена в состав большой системы обнаружения «злоумышленника». Одно из преимуществ создания отдельной системы — мобильность. Это обеспечивает администраторов защиты гибким инструментом, чтобы изменять одиночную переносную систему и проверять несколько модулей внутри сети. Материалы для более подробного изучения систем информационной защиты мы вынесли в раздел «Что еще прочесть» в конце главы.

7.7. Электронная версия игры в «15»

7.7.1. Описание проекта

Наверное, вы когда-нибудь передвигали фишки игры в «пятнадцать», показанной на рис. 7.35. Этой игрой дети развлекаются дома, в летних лагерях и в поездках на заднем сиденье автомобиля. Мы были свидетелями перевода целого ряда игр с передвижением фишек в электронную форму. В этом разделе мы представляем электронную версию игры в «пятнадцать». Проект был создан Скоттом Льюисом в качестве работы для получения звания «senior project design» старшего программиста.

Рис. 7.35. Игра в «15»


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

Рис. 7.36. Игра в «15». Левое поле показывает произвольное начальное состояние фишек, а на правом приведена окончательная позиция, достижение которой является целью игры


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

Требования к системе для игры в «15» следующие:

1. Использовать микроконтроллер;

2. Позволить пользователю идентифицировать номер фишки с помощью устройства ввода данных;

3. Позволить пользователю перемещать выбранную фишку на пустое поле;

4. Отображать текущие расположения всех фишек.

7.7.2. Системы HCS12 используемые в проекте

Этот проект использует различные модули 68HC12, и интерфейс, связывающий их с несколькими переключателями и графическим ЖК дисплеем.

7.7.3. Основы теории

Система использует отладочную плату 68HC12B32EVB, графический ЖК дисплей AND 1391, ИС внешнего ОЗУ (RAM 6264) и внешнего ПЗУ (EPROM 27256), а также программируемую ИС (GAL16V8), набор кнопок и ИС триггера защелки 74HC373. На рис. 7.37 показана структурная схема игровой системы. Хотя мы могли бы использовать внутреннюю память 68HC12, но для расширения опыта читателя мы использовали внешнюю память и расширенный режим работы микроконтроллера. Для создания соответствующих сигналов управления и декодирования адреса памяти, мы использовали программируемую ИС GAL16V8, включив ее между микросхемами памяти и МК. Интегральная схема GAL16V8 программируется так, чтобы ОЗУ размещалось в адресном пространстве начиная с $2000 и заканчивая $3FFF, а программируемое ПЗУ — в ячейках памяти с адресами $4000…$7FFF. Как вы увидите, программа, представленная в этом разделе, запускается из ОЗУ. Разместив программу во внешнем ПЗУ, можно сделать систему автономной.

Рис. 7.37. Блок-диаграмма микропроцессорной системы для игры в «15»

7.7.4. Схемное решение, структура программы и блок-схема алгоритма

На рис. 7.38 показана структура микроконтроллерной системы для игры в «15». Встроенные порты отладочной платы 68HC912B32EVB используются, чтобы получать и послать данные от вспомогательной клавиатуры и модуля ЖК-дисплея. Принципиальная схема этой системы с расширением памяти приведена на рис. 7.39.

Рис. 7.38. Структура микропроцессорной системы для игры в «15»


Рис. 7.39. Принципиальная схема системы для игры в «15»


На рис. 7.40 показана структура программы для игры в «15», в которой главная программа обращается к шести субмодулям. На рисунке показан также ряд функций, выполняемых каждым субмодулем. Соответствующая блок схема алгоритма приведена на рис. 7.41.

Рис. 7.40. Структура программы для игры в «15»


Рис. 7.41. Блок схема алгоритма программы для игры в «15»

7.7.5. О компонентах системы

Интеллектуальный точечный графический дисплей AND 1391 — это полная точечная матрица ЖКД со встроенным модулем контроллера дисплея и ОЗУ буфера экрана. Он может выводить 21 символ на каждой из 18 строк дисплея. Вы можете прочесть об этом дисплее и функциях его обеспечения в главе 5.

7.7.6. Программный код

//Имя файла: sliding.с

//Программа: Игра в "15"

//Авторы: Scott Lewis, Daniel Pack,

//Дата создания: Начата 27 апреля 2004

//окончена 7 Мая 2004

// Описание: Эта программа реализует игру в "15". Цель игры

// заключается в том, чтобы выстроить фишки по по ряду номеров. Вы

// можете только перемещать на пустое место любую соседнюю фишку.

// Программа постоянно находится в памяти системы по адресу

// $2000. Программа отображает исходное состояние фишек, затем

// показывает состояние к которому необходимо прийти в

// результате. Программа размещает все фишки в случайном

// порядке, и затем позволяет пользователю начать игру

// Конфигурация системы

// Программа: 0x2000

// Данные: 0x3500

// Стек: 0x4000

//

// PDLC0: /WR  PP0 Data 0 PS0: Не используется

// PDLC1: /RD   PP1 Data 1 PS1: Не используется

// PDLC2: /CE   PP2 Data 2 PS2: Кнопка "Влево"

// PDLC3: C/D   PP3 Data 3 PS3: Кнопка "Выбор"

// PDLC4: /Reset PP4 Data 4 PS4: Кнопка "Вниз"

// PDLC5: NC   PP5 Data 5 PS5: Кнопка "Вправо"

// PDLC6: NC   PP6 Data 6 PS6: Кнопка "Вверх"

// PDLC1: NC   PP7 Data 7 PS7: Не используется

//

// Выводы ЖКД

// GND  -  2  1 - GND

// -14V -  3  4 - +5V

// /WR  -  5  6 - /RD

// /CE  -  7  8 - C/D

// NC  -  9 10 - /Reset

// DO  - 11 12 - D1

// D2  - 13 14 - D3

// D4  - 15 16 - D5

// D6  - 17 18 - D7

// GND - 19 20 - NC

//******************************************************


#include <912b32.h> // описание портов - header file

           // приведен в приложении

//********************************************************************

// Постоянные

//********************************************************************

#define ARRAY_MAX 15

#define ZERO 0x00

#define L_BUTTON 0x04 // сигналы кнопок

#define R_BUTTON 0x20

#define U_BUTTON 0x40

#define D_BUTTON 0x10

#define S BUTTON 0x08


#define LEFT  1 // выбор направления

#define RIGHT  2

#define UP   3

#define DOWN  4

#define SELECT 5


#define SIZE  4 // Размер строки/колонки


//********************************************************************

// Используемые функции

//********************************************************************

int check_win(int array[ARRAY_MAX+1]);

void convert_multi_single(int A[SIZE][SIZE], int В[SIZE][SIZE]);

void convert_single_multi(int A[ARRAY_MAX+1], int В[SIZE][SIZE]);

void display_board(int A[SIZE][SIZE], int row, int col, int direction);

void display_board2(int board_array[ARRAY_MAX+1]);

void display_error(int n);

void display_intro(void);

void display_win(void);

void get_move(int *direction, int *row, int *col, int *select);

void randomize_board(int board_array[]);

void swap(int row, int col, int new_row, int new_col, int array[SIZE][SIZE]);

void swap2(int from, int to, int array[ARRAY_MAX+1]);

void try_move(int move, int row, int col, int array[SIZE][SIZE]);

unsigned char mode(unsigned char num, int modulus);

void LCD_output(char s[]);

void int2char(int i);

void pause(void);

void delay(int usec);

void enable(void);

void initialize_LCD(void);

void command(unsigned char n);

void data(unsigned char n);

void write(void);

void read(void);

void status_wait(void);

void LCD_char(unsigned char n);

void Clearscreen(void);

void newline(void);

void Reset_cursor(void);


//*****************************************************************

//Переменные

//*****************************************************************

#pragma abs_address 0x3600

static int win_array[ARRAY_MAX+1]

 = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0};

#pragma end_abs_address


#pragma abs_address 0x3500

int board_array_single [ARRAY_MAX+1];

int board_array_multi[SIZE][SIZE];

int win; //использовать как булевы переменные

int direction; //направление движения

int row; //выделить ряд

int col; //выделить колонку

int select; //если кнопка нажата

int i; //текущая переменная

#pragma end_abs_address


//*****************************************************************

// Основная программа

//*****************************************************************

void main() {

 win = 0; //инициализировать все переменные

 direction = 0;

 row = 1;

 col = 1;

 select = 0;

 i = 0;

 DDRDLC = 0x1F; //конфигурировать порт DDRDLC как выходной

 DDRP = 0xFF; // конфигурировать линии порта P как выходные

 DDRS = 0x00; // конфигурировать линии порта S как входные

 TSCR = 0x80; //включить таймер

 initialize_LCD(); //инициализировать ЖКД

 Clearscreen();

 Reset_cursor();

 display_intro();

 for (i=0; i < ARRAY_MAX+1; i++) board_array_single[i] = win_array[i];

 convert_single_multi(board_array_single, board_array_multi);

 display_board(board_array_multi, row, col, direction);

 pause(); // ожидание, пока пользователь не нажмет кнопку X

 LCD_output("Now the board is"); // вывод сообщения

 LCD_output("randomized. You ");

 LCD_output("may begin by ");

 LCD_output("choosing a piece");

 LCD_output("to move, and its");

 LCD_output("direction. ");

 newline();

 pause();

 randomize_board(board_array_single); // Случайный выбор исходного

                   // положения фишек

 convert_single_multi(board_array_single, board_array_multi);

 display_board(board_array_multi, row, col, direction);

 while(win == 0){ //цикл повторяется до успешного окончания игры

  while (select == 0) {

   //если была нажата кнопка x, то задается направ-

  // ление движения ряд и колонка выделенной фишки

  get_move(&direction, &row, &col, &select);

   Reset_cursor(); // установить курсор в верхнее положение

  if (select == 0)

   display_board(board_array_multi, row, col, direction);

  }

 //проверить корректность движения, повторить его, если

 // оно некорректно, или вывести сообщение об ошибке

  try_move(direction, row-1, col-1, board_array_multi);

  select = 0;

  convert_multi_single(board_array_multi, board_array_single);

  win = check_win(board_array_single);

  Clearscreen();

  // показать текущее состояние игры

  display_board(board_array_multi, row, col, direction);

 }

 display_win(); // вывести сообщение об успешном окончании игры

}


//*****************************************************************

// display_intro: DISPLAY INTRO MESSAGE

//*****************************************************************

void display_intro {

 newline();

 LCD_output(" WELCOME ");

 LCD_output(" TO ");

 LCD_output(" SLIDING PUZZLE ");

 new_line();

 pause();

 LCD_output("The object of ");

 LCD_output("this game is to ");

 LCD_output("move each #'ed ");

 LCD_output("puzzle piece so ");

 LCD_output("that you end up ");

 LCD_output("in the order ");

 LCD_output("seen below. The ");

 LCD_output("star shows the ");

 LCD_output("current piece ");

 LCD_output("selected. You ");

 LCD_output("can choose a ");

 LCD_output("different piece ");

 LCD_output(" by using the ");

 LCD_output(" arrow buttons ");

 newline();

 pause();

 LCD_output("and select the ");

 LCD_output("piece you want ");

 LCD_output("to move by ");

 LCD_output("pressing the X ");

 // кнопка "Выбор"

 LCD_output("button. Choose ");

 LCD_output("the direction to ");

 LCD_output("move that piece ");

 LCD_output("with the arrows. ");

 new_line();

 LCD_output("WINDING ");

 LCD_output("CONFIGURATION: ");

 newline();

}


//*****************************************************************

// display_win: ВЫВОД СООБЩЕНИЯ О ПОБЕДЕ

//*****************************************************************

void display_win() {

 LCD_output(" YOU WIN!!! ");

 LCD_output("CONGRATULATIONS ");

}


//********************************************************************

// get_move: ДВИЖЕНИЕ ФИШКИ: задается позиция фишки, выбранной игро-

// ком и направление ее движения

//********************************************************************

void get_move(int* direction, int *row, int *col, int* select) {

 int n = 0;

 int button = 0;

 unsigned char temp = ZERO;

 newline();

 LCD_output("Choose move or ");

 LCD_output("select piece: ");

 while (button == 0) {

 // цикл выполняется, пока нажата кнопка

  temp = PORTS;

  temp = temp & 0x7С

  switch (temp) //какая кнопка нажата?

  {

  case L_BUTTON:

  button = LEFT;

  break;

  case R_BUTTON:

  button = RIGHT;

  break;

  case U_BUTTON:

  button = UP;

  break;

  case D_BUTTON:

  button = DOWN;

  break;

  case S_BUTTON:

  button = SELECT;

  break;

  } // конец цикла switch

 }//конец цикла while

 n = 0;

 switch (button) //реакция на нажатие кнопки

 {

 case UP:

  if (*row > 1) *row -= 1;

  else display_error(UP);

  break;

 case DOWN:

  if (*row < SIZE) *row += 1;

  else display_error(DOWN);

 break;

 case LEFT:

  if (*col > 1) *col -= 1;

  else display_error(LEFT);

  break;

 case RIGHT:

  if (*col < SIZE) *col += 1

  else display_error(RIGHT);

  break;

 case SELECT:

  *select = 1;

  LCD_output("Pick a direction");

  *direction = 0;

  while (*direction == 0) {

  temp = PORTS;

  temp = temp & 0x7C;

  switch (temp) {

  case L_BUTTON:

   *direction = LEFT;

   break;

  case R_BUTTON:

   *direction = RIGHT;

   break;

  case U_BUTTON:

   *direction = UP;

   break;

  case D_BUTTON:

   *direction = DOWN;

   break;

  }

  }

  break;

 }

}


//********************************************************************

// randomize_board: ВЫБОР СЛУЧАЙНОГО ИСХОДНОГО СОСТОЯНИЯ ФИШЕК

//********************************************************************

void randomize_board(int board_array[]) {

 int temp = 0;

 int i;

 unsigned char temp2 = 0x00;

 for (i=0; i

  temp2 = TCNTL;

  temp = mod(temp2,15); //случайное значение using the TCNT counter

  swap2(i,temp, board_array);

 }

}


//********************************************************************

// MOD: МАТЕМАТИЧЕСКАЯ ФУНКЦИЯ

//********************************************************************

unsigned char mod(unsigned char num, int modulus) {

 while ((num - modulus) > 0) num = num - modulus;

 return num;

}


//********************************************************************

// display_board2: Выводит табло, как одну колонку значений

//********************************************************************

void display_board2(int board_array[ARRAY_MAX+1]) {

 int = 0;

 int n;

 for (i=0; i

  n = board_array[i];

  int2char(n);

 }

 LCD_output("\n");

}


//********************************************************************

// display_board: Выводит табло как массив 4×4

//********************************************************************

void display_board(int A[SIZE][SIZE], int row, int col, int direction) {

#pragma abs_address 0x0800

 int i;

 int j;

 int num;

#pragma end_abs_address

 newline();

 LCD_output("| Column ");

 LCD_output("| 1 2 3 4 ");

 LCD_output("-------------------");

 for (i=0; i < SIZE; i++) {

  j=0;

  switch(i) {

  case 0:

   LCD_output("R 1 |");

  break;

  case 1:

   LCD_output("o 2 |");

  break;

  case 2:

   LCD_output("w 3 |");

  break;

  case 3:

   LCD_output(" 4 |");

  break;

  }

 for (j=0; j < SIZE; j++) {

  num = A[i][j];

  if (num == 0) LCD_output(" ");

  else int2char(num);

  if ((i+1 == row) && (j+1) == col))

   LCD_output("*");

  else LCD_output(" ");

 }

 }

 newline();

 LCD_output("You are at (R,C)");

 LCD_output(" (");

 int2char(row);

 LCD_output(",");

 int2char(col);

 LCD_output(") =");

 int2char(A[row-1];

 LCD_output(" ");

 newline();

}


//********************************************************************

// INT2CHAR: задается как integer и выводится на ЖКД в виде

// двух символов

//********************************************************************

void int2char(int i) {

 if (i > 9) {

  LCD_output("I");

  i -= 10;

 } else {

  LCD_output('; ");

 }

 switch(i) {

 case 0:

 LCD_output("0");

  break;

 case 1:

 LCD_output("1");

  break;

 case 2:

 LCD_output("2");

  break;

 case 3:

 LCD_output("3");

  break;

 case 4:

 LCD_output("4");

  break;

 case 5:

 LCD_output("5");

  break;

 case 6:

 LCD_output("6");

  break;

 case 7:

 LCD_output("7");

 break;

 case 8:

 LCD_output("8");

  break;

 case 9:

 LCD_output("9");

  break;

 }

}


//********************************************************************

//Check_Win: Эта функция проверяет соответствие текущего положения ситуации

// победы при победе win - 1, в остальных случаях - 0

//********************************************************************

int check_win(int array[ARRAY_MAX+1]) {

 int i;

 int win = 1;

 for (i=0; i

  if (array[i] != win_array[i]) win = 0;

 }

 return win;

}


//********************************************************************

//Convert_multi_single: Эта функция преобразует двухмерный массив

//в одномерный

//********************************************************************

int convert_multi_single(int A[SIZE][SIZE], int В[ARRAY_MAX+1]) {

 int n = 0;

 int i = 0;

 int j = 0;

 for (i=0; i

  for (j=0; j

  B[n] = A[i][j] ;

  n++;

  }

 }

}


//****************************************************************

//Convert_single_multi: Эта функция преобразует одномерный массив

//в двухмерный

//****************************************************************

void convert_single_multitint A[ARRAY_MAX+1], int В[SIZE][SIZE]) {

 B[0][0] = A[0];

 B[0][1] = A[1];

 B[0][2] = A[2];

 B[0][3] = A[3];

 B[1][0] = A[4];

 B[1][1] = A[5];

 B[1][2] = A[6];

 B[1][3] = A[7];

 B[2][0] = A[8];

 B[2][1] = A[9];

 B[2][2] = A[10];

 B[2][3] = A[11];

 B[3][0] = A[13];

 B[3][2] = A[14];

 B[3][3] = A[15];

}


//********************************************************************

// Try_move: Эта функция позволяет игроку определить некорректность

// своего хода. Если он корректен, то движение выполняется,

// если же некорректен, то выводится соответствующее сообщение

//********************************************************************

void try_move(int move, int row, int col, int array[SIZE][SIZE]) {

 switch(move) {

 case UP:

  if ((row-1 >=0) && (array[row-1][col] == 0))

  swap(row, col, row-1, col, array);

 else display_error(UP);

  break;

 case DOWN:

  if ((row+1 <= SIZE) && (array[row+1][col] == 0))

  swap(row, col, row+1, col, array);

  else display_error(DOWN);

 break;

 case LEFT:

  if ((col-1 >=0) && (array[row][col-1] == 0))

  swap(row, col, row, col-1, array);

  else display_error(LEFT);

  break;

 case RIGHT:

  if ((col+1 < = SIZE) && (array[row][col+1] == 0))

  swap(row, col, row, col+1, array);

  else display_error(RIGHT);

  break;

 }

}

//********************************************************************


//********************************************************************

//Swap: Эта функция заменяет два значения двухмерным массивом.

//********************************************************************

void swap(int row, int col, int new_row, int new_col, int array[SIZE][SIZE]) {

 int temp;

 temp = array[row][col];

 array[row][col] = array[new_row][new_col];

 array[new_row][new_col] = temp;

}


//********************************************************************

//Swap2: Эта функция заменяет два значения одномерным массивом.

//********************************************************************

void swap2(int from, int to, int array[ARRAY_MAX+1]) {

 int temp = array[from];

 array[from] = array[to];

 array[to] - temp;

}


//********************************************************************

//ERROR: Эта функция выводит сообщения об ошибке

//********************************************************************

void display_error(int n) {

 LCD_Output("ERROR: ");

 switch(n) {

 case LEFT:

  LCD_output("no move L");

  break;

 case RIGHT:

  LCD_output("no move R");

  break;

 case UP:

  LCD_output("no move U");

  break;

 case DOWN:

  LCD_output("no move D");

  break;

 }

 pause();

}

//********************************************************************


//********************************************************************

//LCD_output: Эта функция выводит на дисплей строку

//********************************************************************

void LCD_output(char s[]) {

 int n = 0;

 while (s[n] != '\0') {

  LCD_char(s[n]);

  ++n;

 }

}


//********************************************************************

//Pause: Функция реализует ожидание, пока игрок не нажмет кнопку

// "Выбор"

//********************************************************************

void pause() {

 unsigned char с = ZERO;

 LCD_output("(Please press X)");

 while (c != S_BUTTON) {

  c = PORTS;

  с = c & 0x7C;

 }

 Clearscreen();

 Reset_cursor();

}


//********************************************************************

//Delay: Эта функция вводит задержку на n мкс, если входная

// величина равна n

//********************************************************************

void delay(int usec) {

 int i,j;

 for (i=0;i

  for (j=0; j < 7; j++) {}

 }

}


//********************************************************************

//Initialize_LCD: Функция инициализирует ЖКД

//********************************************************************

void initialize_LCD(void) {

 char temp = 0x00;

 PORTDLC = 0xFF;

 PORTDLC = PORTDLC & 0xEF; // сброс экрана (RESET = 0)

 delay(2000); // задержка в 2 мс

 PORTDLC = 0x7F; // выключение сброса

 write(); //включение записи

 command(0x80);

 //установка текстового режима

 data(0x0); //проверка установки слова

 data(0x10);

 command(0x40);

 data(0x10); // устанавливается размер области текста (1E)

 data(0x00); // - 0x1000

 command(0x41);

 //включается дисплей текста, курсор, выключается мигание

 command(0x94);

 command(0xA7); //курсор 8×8 точек

}


//********************************************************************

//Enable: Функция разрешает работу ИС

//********************************************************************

void enable(void) {

 PORTDLC = PORTDLC | 0x04; // Установить 1 на линии enable

 PORTDLC = PORTDLC & 0xFB; // Установить 0 на линии enable

}


//********************************************************************

//Disable: Функция запрещает работу ИС

//********************************************************************

void disable(void) {

 PORTDLC = PORTDLC | 0x04;

}


//********************************************************************

//Command: Функция посылает команду отключения на ЖКД

//********************************************************************

void command(unsigned char n) {

 status_wait();

 PORTP = n;

 PORTDLC = 0xFF;

 PORTDLC = PORTDLC & 0xFE; // сброс записи

 enable(); // сброс флага CE

 delay(10); // задержка не менее 80 нс

 disable(); // включение флага CE

}


//********************************************************************

//Data: Функция пересылает данные на ЖКД

//********************************************************************

void data(unsigned char n) {

 status_wait();

 PORTP = n;

 PORTDLC = 0xFF;

 PORTDLC = PORTDLC & 0xF7; // перевести C/D на низкий уровень

 PORTDLC = PORTDLC & 0xFE; // перевести WR на низкий уровень

 PORTDLC = PORTDLC & 0xFB;

 delay(10);

 disable();

}


//********************************************************************

//Write: Функция конфигурирует порт P как выходной

//********************************************************************

void write() {

 DDRP = 0xFF;

}


//********************************************************************

//Read: Функция конфигурирует порт P как входной

//********************************************************************

void read() {

 DDRP = 0x00;

}


//********************************************************************

//Status_wait: Создает соответствующие задержки между командами ЖКД

//********************************************************************

void status_wait() {

 char temp = 0x00;

 DDRP = 0x00;

 PORTDLC = PORTDLC | 0x0F; // сбросить все

 PORTDLC = PORTDLC & 0xFD; // сброс флага RD

 enable();

 delay(10);

 while ((temp & 0x03) != 0x03) {

  temp = PORTP;

 }

 disable();

 DDRP = 0xFF;

}


//********************************************************************

//LCD_char: Функция выводит ASCII код на экран ЖКД

//********************************************************************

void LCD_char(unsigned char n) {

 data(n-0x20);

 command(0xC0);

}


//********************************************************************

//Clearscreen: Функция очищает экран ЖКД

//********************************************************************

void Clearscreen() {

 int i,j;

 Reset_cursor();

 for (i=0; i < 16; i++) for (j=0; j<16; j++) LCD_char(' ');

 Reset_cursor();

}


//********************************************************************

//Newline: Функция выводит пустую строку на экран ЖКД

//********************************************************************

void newline() {

 int i;

 for (i=0; i < 16; i++)

 LCD_char(' ');

}


//********************************************************************

//Reset_cursor: Функция возвращает курсор ЖКД в начальную позицию

//********************************************************************

void Reset_cursor() {

 data(0x00);

 data(0x10);

 command(0x24);

}

//********************************************************************

7.7.7. Некоторые комментарии

Система для игры в «15» использует пять кнопок вспомогательной клавиатуры, чтобы перемещать фишки: стрелку «Вверх», стрелку «Вниз», стрелку «Вправо», стрелку «Влево» и кнопку «Выбор». Кроме того, расположение фишки определяется номерами строки и столбца. Однажды сформированная, система может использоваться для игры взрослыми и детьми.

7.8. Программирование резидентного Flash ПЗУ микроконтроллера B32 в составе платы отладки MC68HC912B32EVB

В главе 4 мы обсуждали конфигурацию памяти МК B32 семейства 68HC12. Как было упомянуто, в состав МК B32 входит память программ типа Flash объемом 32Кб. В установленном на плате отладки MC68HC912B32EVB микроконтроллере в области Flash ПЗУ записана программа монитора D-Bug12. Если вы желаете использовать отведенный под нее объем для записи части рабочей программы, то необходимо стереть программу монитора D-Bug12. Но неплохо было бы сохранить ее, чтобы снова записать в память при необходимости.

Имеется несколько способов, с помощью которых можно перепрограммировать резидентную память МК на плате отладки MC68HC912B32EVB. Все они предполагают использование двух плат отладки, одна из которых реализует функцию интерфейса BDM для связи МК второй платы с ПК с целью программирования микроконтроллера второй платы. Подробная методика организации программирования таким способом изложена в [6].

В этом разделе мы расскажем, как программировать резидентную Flash память МК семейства 68HC12, воспользовавшись кабелем интерфейса BDM типа CABLE12 фирмы P&E Microcomputer Systems и программным обеспечением PROG12Z FLASH/EEPROM. Заметим, что, несмотря на название, CABLE12 не является лишь соединителем. В его состав входят аппаратные средства интерфейса между ПК и МК семейства 68HC12, которые позволяют осуществлять взаимодействие этих двух устройств в процессе отладки программ и для занесения программы в энергонезависимую память МК.

Конфигурация системы, использующей эти инструментальные средства, показана на рис. 7.42. Как видно из рисунка, CABLE12 связан с ведущим ПК через стандартный 25-жильный кабель параллельного порта. С другой стороны CABLE12 связан с программируемой B32 EVB шестижильным BDM кабелем.

Рис. 7.42. Схема соединения компонентов для программирования резидентной энергонезависимой памяти МК семейства 68HC12/HCS12


Этот кабель подключается к разъему W9 отладочной платы MC68HC912B32EVB. Красный провод кабеля соответствует штырьку 1 на колодке W9.

Программируемый МК B32 EVB связан также с ведущим ПК через последовательный порт связи. Он должен, кроме того, иметь обычные кабели подключения питания (+5 В, земля). Дополнительно, в процессе программирования должны быть поданы напряжение +12 В (Vpp) и земля на колодку W8. Будьте внимательны, не перепутайте полярность при подключении этого питания. Кроме того, колодка W7 должна быть конфигурирована для Vpp.

Как только аппаратные средства cконфигурированы, можно программировать флеш-память EEPROM в следующей последовательности:

1. Подать питание на B32 (+5 В и +12 В).

2. Найти программное обеспечение PROG12Z на вашем компьютере (c:\pemicro\Prog12z\prog12z).

3. Дважды щелкнуть на prog12z.

4. Появляется окно Connect Assistant, которое поможет установить связь с переходная приставка CABLE12 BDM интерфейс.

• Проверьте параметры настройки окна, затем нажмите OK.

• В окне состояния должно появиться сообщение о появлении связи программы prog12z с интерфейсом CABLE12 BDM.

• Если связь ПК с интерфейсом CABLE12 отсутствует, на экране ПК появится информация для поиска неисправностей.

5. Должно появиться всплывающее меню Specify Programming Algorithm to Use!

• Выберите правильный программный модуль с расширением '

*.12P
' P&E, чтобы использовать необходимый драйвер программирования. Например, чтобы программировать Flash память объемом 32Кб, входящую в состав МК B32, выберите
912B32_32K.12Р
.

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

6. Затем появляется окно Base Address.

• Вы должны определить базовый адрес программируемой Flash памяти.

• Информация о базовом адресе обеспечивается в карте памяти программируемого МК, которая, в том числе, приводится в Руководстве пользователя отладочной платы MС68HC912B32EVB (68HC12 M68EVB912B32 Evaluation Board User's Manual, Таблица 3–5, страницы 3–55)

• Адрес начала блока Flash памяти равен $8000.

• Введите это значение в ПК и нажмите OK.

7. Щелкните SM Show Module, чтобы отобразить текущее содержание модуля Flash памяти в МК.

8. Выполните операцию стирания текущего модуля перед программированием нового модуля во Flash память.

Предостережение: программа монитора D-BUG12 пока еще во Flash-памяти, это и есть текущий модуль в памяти МК B32. Как только вы сотрете модуль, код монитора отладки D-BUG12 будет потерян.

Сотрите модуль.

9. Выберите SS Specify Record и затем *.S19 для загрузки в буфер программы программатора на ПК модуля, подлежащего программирования в МК.

10. Выберите PM, чтобы инициировать программирование модуль во Flash-память МК.

7.9. Заключение по главе 7

В этой главе мы рассмотрели ряд встроенных микроконтроллерных систем на базе МК 68HC12 и HCS12. В частности мы описали робот, движущийся в лабиринте, лазерный проектор, цифровой вольтметр, стабилизатор скорости вращения двигателя с оптическим тахометром, парящий робот, систему защиты компьютерной сети на базе нечеткой логики и электронную версию популярной игры в «15». Для всех этих систем мы привели описание проекта, системные требования, основную информацию, структуру программы, блок схему алгоритма и код программы на языке Си. Хотя функции встроенных систем существенно различаются, мы показали, что для их создания можно использовать одну и ту же методику.

7.10. Что еще прочитать?

1. American National Standards Institute (ANSI) Z136.1, Safe Use of Lasers (ANSI Z136.1), 1993.

2. Cooper, W. D. Electronic Instrumentation and Measurement Techniques. Upper Saddle River, NJ: Prentice-Hall, 1970.

3. Edmund Industrial Optics, Barrington, NJ, www.edmundoptics.com, 2004.

4. GSI Lumonics, «General Scanning Scanners and Drivers.» www.gsilumonics.com, 2004.

5. Honeywell Sensing and Control, www.honeywell.com/sensing, 2004.

6. Lind, Magnus. Motorola M68HC912B32EVB Evaluation Board: PCPODTarget Set Up, Western Washington University Electronics Engineering Technology, Bellingham, WA, http://eet.etec.www.edu.

7. 68HC12 M68EVB912B32 Evaluation Board User's Manual, 68EVB912B32 UM/D, Motorola Inc., 1997.

8. LINOS Photonics, Milford, MA, www.linos.com. Newport Corporation Irvine, CA, www.newport.com, 2004.

10. Pack, D. J., W. Strelein, S. Webster, and R. Cunningham. «Detecting HTTP Tunneling Activities.» Paper presented at the annual Information Assurance Workshop, West Point, NY, June 2002.

11. Servo-Tek, «Encoders and Other Position/Velocity Sensors for Motion Control», www.servotek.com, 2004.

12. Vij, D. R. and K. Mahesh. Medical Applications of Lasers. Kluwer Academic Publishers, 2002.

13. Vincent Associates, «Uniblitz Electronic Drive Equipment and Shutters.» www.uniblitz.com, 2004.

7.11. Вопросы и задания

Основные

1. Опишите принцип работы ИК локатора (пары излучатель-приемник), который используется роботом для обнаружения стенок лабиринта.

2. Зачем нужен роботу датчик Холла?

3. Опишите принцип работы ЦАП с параллельным и последовательным интерфейсом.

4. Что такое лазер?

5. Что такое гальванометр?

6. Опишите два способа увеличения разрешающей способности модуля ATD в системах на базе МК 68HC12.

7. Опишите принцип действия оптического кодера.

8. Для чего во встроенных системах применяется прерывания от модуля меток реального времени RTI ?

9. Какие преимущества можно извлечь, применяя при проектировании функциональные схемы системы, структуры программы и блок-схемы алгоритма?

10. В чем различия между системными требованиями и параметрами системы при проектировании встроенных МП систем?

11. Какая процедура может помочь в выборе конкретной модели МК семейства 68HC12, удовлетворяющего требованиям проекта, из большого числа модификаций МК этой серии?

Более сложные

1. Создайте блок-схемы алгоритмов для каждой функции, используемой системой робота, движущегося в лабиринте (раздел 7.1.5).

2. Разработайте эксперимент, позволяющий точно установить, удовлетворяет ли проектному заданию время запаздывания, формируемое устройством задержки в лазерном проекторе.

3. Каковы различия между лазерами различных типов?

4. Если удалить инверторы 74HC04 из структуры входного интерфейса лазерной системы, то как можно обеспечить программную корректировку такого изменения?

5. Пересчитайте значения сопротивлений и Vref, чтобы обеспечить работу DAC0808LCN при питании этой ИС напряжением ±4 В.

6. Разработайте структуру программы и блок-схему алгоритма для поддержки функций цифрового вольтметра.

7. Какова разрешающая способность 8-разрядного цифрового вольтметра, описанного в разделе 7.3? Чем она определяется? Опишите метод, позволяющий увеличить разрешающую способность. Подсказка: вспомните назначение бита S10BM в регистре ATDCTL4. В чем заключается решение проблемы?

8. В разделе, описывающем проект стабилизации частоты вращения двигателя, мы указали, что каждое инкрементирование коэффициента заполнения ШИМ приводит к изменению скорости приблизительно на 8.5 об/мин. Вы согласны с результатами этого анализа? Подтвердите ваш ответ вычислениями.

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

10. Разработайте методику испытаний системы стабилизации частоты вращения двигателя, основанную на восходящих методах проектирования.


Исследовательские

1. Замените параллельный интерфейс цифро-аналогового преобразователя DAC0808LCN в устройстве управления лазерным проектором на последовательный в стандарте SPI. Внесите изменения в схему электрических соединений для ЦАП и также в текст функции move-laser (x,y) для программного обслуживания ЦАП.

2. Измените код, содержащийся в laser.с (раздел 7.2.6) таким образом, чтобы выбранный образ постоянно воспроизводился, пока не будет выбран другой.

3. Запишите функцию для каждого образа в laser.с (раздел 7.2.6).

4. Измените программное обеспечение для цифрового вольтметра, чтобы осуществить работу с 10-разрядным кодом оцифровки модулем ATD.

5. Разработайте и реализуйте метеостанцию с тремя каналами измерения: окружающей температуры, относительной влажности и барометрического давления. Отобразите каждую из измеренных величин через интервал в 3 секунды на ЖК индикаторе. Разработайте структуру программы, блок-схемы алгоритмов, код программы и методику проверки для метеостанции.

Загрузка...