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

691_Mikushin_A.V._Programmirovanie_mikroprotsessorov_

.pdf
Скачиваний:
48
Добавлен:
12.11.2022
Размер:
1.96 Mб
Скачать

Программа, приведенная в листинге 3, реализует генератор эталонных интервалов времени с периодом 50мс. Нам же требуется генератор импульсов с периодом 1с. То есть, требуется ещё один делитель частоты с коэффициентом деления 20. Его можно реализовать на одиночной ячейке внутренней памяти микроконтроллера.

Для этого выделим 8-ми разрядную ячейку. Это мы делаем при помощи оператора объявления переменной char Delit=20;. Где конкретно будет находиться эта ячейка при программировании на языке C-51 не важно, однако важно, чтобы в первоначальный момент времени нем содержалось число 20. Если интересно где же конкретно компилятор разместил эту переменную, то можно воспользоваться окном дизассемблера в отладчике программ. Новый вариант программы приведен в листинге 4.

Листинг 4. Исходный текст программы, обеспечивающей вызов подпрограммы часов один раз в секунду.

#include<reg51.h> #include "clock.h"

...

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

Подпрограмма реализации часов

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

void Clock(void)

{

}

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

ВЫПОЛНЕНИЕ ПРОГРАММЫ НАЧИНАЕТСЯ ОТСЮДА

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

char Delit=20;

//8-ми разрядная ячейка памяти для делителя частоты (1с)

void main(void)

 

{//-------------

 

ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------

Timer0_Init();

//Настроить таймер T0 на прерывания с периодом 50мс

//-------------

ОСНОВНАЯ ПРОГРАММА МИКРОКОНТРОЛЛЕРА--------------------

while(1)

//Бесконечный цикл

{if(--

Delit==0) //Если прошла одна секунда, то

{Delit=20;

//настроить делитель на коэффициент деления 20

Clock();

//и вызвать подпрограмму счётчика секунд.

}

 

 

PCON=1;

//Перевести микроконтроллер в пониженный режим

}

 

//потребления тока и подождать переполнения таймера

}

 

 

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

111

Подпрограмма реализации часов Clock вызывается точно один раз в секунду. Это обеспечивается тем, что мы организовали счётчик на 20. При вычитании из числа 20 единицы потребуется ровно двадцать вычитаний, а так как проход по циклу мы обеспечили с периодом 50 мс, то 20 вычитаний мы выполним ровно за одну секунду. Через секунду переменная Delit обнулится и мы попадём в составной оператор условного оператора if, расположенный в начале основного цикла программы.

В составном операторе будет восстановлено первоначальное значение переменной, что обеспечит повторный заход в этот оператор ровно через секунду. Кроме того в составном операторе осуществляется вызов подпрограммы часов. Для наглядности имя этой подпрограммы было выбрано Clock, что на английском языке означает часы. В подпрограмме Clock пока никаких действий не выполняется, поэтому подпрограмму можно считать подпрограммой-заглушкой, необходимой только для трансляции исходного текста программы и проверки работы делителя на 20.

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

Разработка подпрограммы часов.

Теперь можно приступить к реализации подпрограммы часов. Напомню, что при написании этой подпрограммы мы уверены, что попадаем в неё один раз в секунду, поэтому можно эту подпрограмму рассматривать отдельно и независимо от остальной программы. Более того, эту подпрограмму можно разместить в отдельном файле и подключить ее к проекту. Так мы и сделаем.

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

Листинг 5. Исходный текст подпрограммы, реализующей счётчик секунд.

unsigned char SEC=60; //Переменная счётчика секунд

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

Подпрограмма реализации часов

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

void Clock(void)

{if(--SEC==0) //Если прошла одна минута, то

SEC=60; //настроить делитель на коэффициент деления 60

}

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

112

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

Теперь усложним подпрограмму часов и введём в их состав счётчик минут. Для того, чтобы подпрограмма была более наглядной при этом немного изменим организацию счётчика секунд. При разработке счётчика минут обратим внимание, что его содержимое нужно отображать на светодиодном индикаторе. При этом показания минут должны увеличиваться. Поэтому счётчик минут выполним с помощью команды увеличения содержимого переменной на единицу. Новый вариант подпрограммы часов приведен в листинге 6.

Листинг 6. Исходный текст подпрограммы, реализующей счётчики секунд и минут.

unsigned

char

SEC=60;

//Переменная

счётчика

секунд

unsigned

char

MIN=0;

//Переменная

счётчика

минут

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

Подпрограмма реализации часов

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

void Clock(void)

{if(--SEC!=0)

//Если одна минута ещё не прошла, то

return;

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

SEC=60;

//настроить счётчик секунд на коэффициент деления 60

if(++MIN!=60)

//Если один час ещё не прошёл, то

return;

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

MIN=0;

//обнулить показания счётчика минут

}

 

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

Листинг 7. Дизассемблированный текст подпрограммы, реализующей счётчики секунд и минут.

7:void Clock(void)

8:{if(--SEC!=0) //Если одна минута ещё не прошла, то

9:return; //вернуться из подпрограммы в основной цикл

C:0x0099 D5080D DJNZ SEC(0x08),C:00A9

10:SEC=60; //настроить счётчик секунд на коэффициент деления 60

C:0x009C

75083C MOV

SEC(0x08),#0x3C

11:if(++MIN!=60) //Если один час ещё не прошёл, то

12:return; //вернуться из подпрограммы в основной цикл

C:0x009F

0509

INC

MIN(0x09)

C:0x00A1

E509

MOV

A,MIN(0x09)

C:0x00A3

B43C03

CJNE

A,#0x3C,C:00A9

13:

MIN=0;

//обнулить показания счётчика минут

C:0x00A6

E4

CLR

A

C:0x00A7

F509

MOV

MIN(0x09),A

14: }

 

 

C:0x00A9

22

RET

 

11: void Timer0_Init(void)

113

Мы видим, что одинаковые по внешнему виду команды условного оператора приводят к различному машинному коду, отличающемуся в два раза, как по объему, так и по быстродействию. Именно поэтому использовать команду увеличения стоит использовать только тогда, когда это определяется какими либо причинами, как, например, в нашем случае.

Теперь добавим в программу счетчик часов. Реализуем его подобно счётчику минут. Новый вариант подпрограммы часов приведен в листинге 8.

Листинг 8. Исходный текст подпрограммы, реализующей счётчики секунд, минут и часов.

unsigned char SEC=60;

//Переменная счётчика секунд

unsigned char MIN=0;

//Переменная счётчика минут

unsigned char Chas=0;

//Переменная счётчика часов

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

Подпрограмма реализации часов

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

void Clock(void)

{if(--SEC!=0)

//Если одна минута ещё не прошла, то

return;

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

SEC=60;

//настроить счётчик секунд на коэффициент деления 60

if(++MIN!=60)

//Если один час ещё не прошёл, то

return;

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

MIN=0;

//обнулить показания счётчика минут

if(++Chas!=24)//Если сутки ещё не прошли, то

return;

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

Chas=0;

//обнулить показания счётчика часов

}

 

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

Разработка подпрограммы индикации.

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

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

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

114

циклом, в котором производится вызов подпрограммы индикации, приведен в листинге 9.

Листинг 9. Исходный текст основной программы.

#include<reg51.h> #include "clock.h" #include "Indic.h"

...

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

ВЫПОЛНЕНИЕ ПРОГРАММЫ НАЧИНАЕТСЯ ОТСЮДА

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

char Delit=20;

//8-ми разрядная ячейка памяти для делителя частоты (1с)

void main(void)

 

{//-------------

 

ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------

Timer0_Init();

//Настроить таймер T0 на прерывания с периодом 50мс

//-------------

ОСНОВНАЯ ПРОГРАММА МИКРОКОНТРОЛЛЕРА--------------------

while(1)

//Бесконечный цикл

{

 

 

Indic();

//Произвести индикацию состояния часов на светодиодных

 

 

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

if(--

Delit==0) //Если прошла одна секунда, то

{Delit=20;

//настроить делитель на коэффициент деления 20

Clock();

//и вызвать подпрограмму счётчика секунд.

}

 

 

PCON=1;

//Перевести микроконтроллер в пониженный режим

}

 

//потребления тока и подождать переполнения таймера

}

 

 

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

Теперь можно заняться подпрограммой блока индикации. На принципиальной схеме часов не указано, что и на каком индикаторе должно отображаться. Зададимся, что на индикаторе, подключенном к порту P0, будут отображаться единицы минут. На индикаторе, подключенном к порту P1, будут отображаться десятки минут. На индикаторе, подключенном к порту P2, будут отображаться десятки минут. На индикаторе, подключенном к порту P3, будут отображаться десятки минут.

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

Для декодирования двоично-десятичного кода предусмотрим подпрограм- му-функцию decod. При этом декодируемое двоично-десятичное число будет

115

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

Точно так же как и в случае с часами для индикации создадим отдельный файл (модуль). Исходный текст модуля индикации приведен в листинге 10.

Листинг 10. Исходный текст модуля индикации.

#include<reg51.h> #include "clock.h"

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

Подпрограмма семисегментного дешифратора

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

char Decod(char inp)

{

}

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

Подпрограмма блока индикации

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

void Indic(void) {char tmp;

tmp=MIN%10; //Считать содержимое счётчика минут и выделить младшую тетраду P1=Decod(tmp); //Преобразовать её в семисегментный код и передать через

//порт P1 на индикатор

tmp=MIN/10; //Считать содержимое счётчика минут и выделить старшую тетраду P2=Decod(tmp); //Преобразовать её в семисегментный код и передать через

//порт P2 на индикатор

tmp=Chas%10;

//Считать содержимое

счётчика часов

и выделить младшую тетраду

P0=Decod(tmp); //Преобразовать её в семисегментный код и передать через

 

//порт P0

на индикатор

 

tmp=Chas/10;

//Считать

содержимое счётчика минут и выделить старшую тетраду

P3=Decod(tmp); //Преобразовать её в семисегментный код и передать через //порт P3 на индикатор

}

Выделение единиц минут осуществляется при помощи операции взятия остатка от деления на константу 10. Эта операция выполняется при использовании символа ‘%’. При этом осуществляется целочисленное беззнаковое деление содержимого счетчика минут на число 10. В результате в остатке мы получим единицы минут. Это число не будет превышать числа 10, а значит для его представления достаточно четырех бит (тетрады).

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

116

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

Листинг 11. Дизассемблированный текст подпрограммы индикации.

14:void Indic(void)

15:{char tmp;

16:tmp=MIN%10; //Считать содержимое счётчика минут и выделить младшую тетраду

C:0x0099

E50A

MOV

A,MIN(0x0A)

C:0x009B

75F00A

MOV

B(0xF0),#MIN(0x0A)

C:0x009E

84

DIV

AB

C:0x009F

AFF0

MOV

R7,B(0xF0)

17:P1=Decod(tmp); //Преобразовать её в семисегментный код и передать через

18:

 

//порт P1 на индикатор

19:

 

 

 

C:0x00A1

110A

ACALL

Decod(C:000A)

C:0x00A3

8F90

MOV

P1(0x90),R7

20:tmp=MIN/10; //Считать содержимое счётчика минут и выделить старшую тетраду

C:0x00A5

E50A

MOV

A,MIN(0x0A)

C:0x00A7

75F00A

MOV

B(0xF0),#MIN(0x0A)

C:0x00AA

84

DIV

AB

C:0x00AB

FF

MOV

R7,A

21:P2=Decod(tmp); //Преобразовать её в семисегментный код и передать через

22:

 

//порт P2 на индикатор

23:

 

 

 

C:0x00AC

110A

ACALL

Decod(C:000A)

C:0x00AE

F5A0

MOV

P2(0xA0),A

Как видно из этого листинга, деление осуществляется встроенной командой целочисленного деления DIV AB. В качестве примера неправильного использования данных операций воспользуемся листингом 12, где переменная MIN объявлена не как unsigned char, а как просто char.

Листинг 12. Дизассемблированный текст подпрограммы индикации

спеременной MIN char типа.

14:void Indic(void)

15:{char tmp;

16:tmp=MIN%10; //Считать содержимое счётчика минут и выделить младшую тетраду

C:0x0099

E50A

MOV

A,MIN(0x0A)

C:0x009B

75F00A

MOV

B(0xF0),#MIN(0x0A)

C:0x009E

11CA

ACALL

C?SCDIV(C:00CA)

C:0x00A0

AFF0

MOV

R7,B(0xF0)

17:P1=Decod(tmp); //Преобразовать её в семисегментный код и передать через

18:

 

//порт P1 на индикатор

19:

 

 

 

C:0x00A2

110A

ACALL

Decod(C:000A)

C:0x00A4

8F90

MOV

P1(0x90),R7

20:tmp=MIN/10; //Считать содержимое счётчика минут и выделить старшую тетраду

C:0x00A6

E50A

MOV

A,MIN(0x0A)

C:0x00A8

75F00A

MOV

B(0xF0),#MIN(0x0A)

117

C:0x00AB

11CA

ACALL

C?SCDIV(C:00CA)

C:0x00AD

FF

MOV

R7,A

21:P2=Decod(tmp); //Преобразовать её в семисегментный код и передать через

22:

 

//порт P2 на индикатор

23:

 

 

 

C:0x00AE

110A

ACALL

Decod(C:000A)

C:0x00B0

F5A0

MOV

P2(0xA0),A

Как видно из приведенного листинга, в этой программе вместо машинной команды DIV AB используется подпрограмма знакового деления C?SCDIV, что вовсе не входило в наши планы, так как вряд ли эта подпрограмма состоит из одной машинной команды. Этот пример показывает насколько важен выбор типа переменной для каждого конкретного случая.

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

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

Разработка подпрограммы семисегментного дешифратора.

Рассмотрим подпрограмму преобразования двоично-десятичного кода в семисегментный. Декодирование осуществляется за счёт таблицы перекодировки. Количество строк в таблице соответствует количеству комбинаций дво- ично-десятичного кода, то есть равно десяти. Нули и единицы в получаемом коде определяются свечением сегментов светодиодного индикатора.

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

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

118

Листинг 13. Исходный текст подпрограммы семисегментного дешифратора.

char code TabSemiSeg[]= // abcdefg

 

 

 

{0xfe,

// 11111110b - символ '0'

 

a

 

0xbd,

// 10110000b - символ '1'

-------

0xed,

// 11101101b - символ '2'

|

 

|

0xf9,

// 11111001b - символ '3'

f|

 

| b

0xb3,

// 10110011b - символ '4'

|

g

|

0xdb,

// 11011011b - символ '5'

-------

0xdf,

// 11011111b

- символ '6'

|

 

|

0xf0,

// 11110000b

- символ '7'

e|

 

|c

0xff,

// 11111111b

- символ '8'

|

d

|

0xfb};

// 11111011b

- символ '9'

-------

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

Подпрограмма семисегментного дешифратора

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

char Decod(unsigned char inp) {return TabSemiSeg[inp];

}

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

Разработка блока коррекции часов.

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

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

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

119

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

Пример временной диаграммы сигнала на контактах кнопки приведен на рисунке 40. На этой временной диаграмме четко просматривается зона дребезга контактов. Для иллюстрации эффекта подавления дребезга контактов за счет ввода информации в строго определенные моменты времени, на временной диаграмме, приведенной на рисунке 40, выбран наихудший случай момента взятия отсчета. Этот момент совпадает с зоной дребезга контактов. При этом на выводе порта микроконтроллера может быть считан сигнал, как логического нуля, так и логической единицы. Но даже в этом случае дополнительного импульса не возникает! Дребезг контактов приводит только к неопределенности определения времени нажатия кнопки, который не превышает времени реакции системы, равной 50 мс.

Рисунок 40. Временные диаграммы напряжения на контактах кнопки и сигнала, введенного в микроконтроллер

Новый вариант программы с включенной в цикл подпрограммой опроса кнопок блока коррекции времени приведен в листинге 14.

Листинг 14. Исходный текст основной программы с подпрограммой опроса кнопок коррекции времени.

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

ВЫПОЛНЕНИЕ ПРОГРАММЫ НАЧИНАЕТСЯ ОТСЮДА

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

char Delit=20;

 

//8-ми разрядная ячейка памяти для делителя частоты (1с)

void main(void)

 

{//-------------

 

ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------

Timer0_Init();

//Настроить таймер T0 на прерывания с периодом 50мс

//-------------

ОСНОВНАЯ ПРОГРАММА МИКРОКОНТРОЛЛЕРА--------------------

while(1)

 

//Бесконечный цикл

{

 

 

OprosKnop();

 

//Опросить кнопки

Indic();

 

//Произвести индикацию состояния часов на светодиодных

 

 

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

if(--Delit==0) //Если прошла одна секунда, то

{Delit=20;

 

//настроить делитель на коэффициент деления 20

Clock();

 

//и вызвать подпрограмму счётчика секунд.

}

 

 

PCON=1;

 

//Перевести микроконтроллер в пониженный режим

}

 

//потребления тока и подождать переполнения таймера

}

 

 

120