Дата последнего обновления файла 08.06.2005

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

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

Максимальный коэффициент деления на таймере можно получить 65536. Попробуем необходимый коэффициент деления генератора эталонных интервалов времени равный 12000000 разделить на несколько сомножителей. Прежде всего, выделим сомножитель, равный 12. Это внутренний делитель таймера, который всегда присутствует в составе микросхемы и не может быть нами отключен. То есть нам остаётся реализовать делитель на 1000000.

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

Как уже обсуждалось ранее, алгоритм и программу можно писать одновременно. Начнём написание программы с организации простейшей программы-монитора. Этот первоначальный вариант программы приведен на рисунке 9.18.

Как видно из приведённого исходного текста программы эта программа пока ничего не делает. Однако в этой программе предусмотрен бесконечный цикл, который не позволит программе когда-либо завершиться до выключения питания устройства. По циклу при подключенном к микроконтроллеру кварцевом резонаторе 12МГц программа будет проходить один раз за 2 мкс.

;-------------СБРОС МИКРОКОНТРОЛЛЕРА---------------------------
  ORG 0
  Jmp init  ;Переход на начало выполнения программы

;-------------ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------
init:
;-------------ОСНОВНАЯ ПРОГРАММА МИКРОКОНТРОЛЛЕРА--------------
Begin:
  Jmp begin  ;Переход на начало выполнения программы
  end

Рисунок 9.18. Исходный текст первоначального варианта программы.

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

Программа в основном режиме может ожидать переполнения таймера, опрашивая флаг TF0, но при использовании прерываний от таймера можно значительно снизить потребление микроконтроллера. Экономия реализуется при переведении микроконтроллера в спящий режим командой orl PCON,#1. После выполнения этой команды микроконтроллер может проснуться только после сброса микроконтроллера или возникновения прерывания.

Для реализации прохождения микроконтроллером по основному циклу один раз в 50мс разместим команду orl PCON,#1 в конце цикла. В качестве источника прерываний будет служить таймер T0. Переход на начало цикла будет осуществляться сразу после возврата из подпрограммы обслуживания прерывания.

Так как прерывания нам потребуются каждые 50 мс, то разрешение прерываний от таймера разместим тоже в подпрограмме инициализации таймера. Разрешение прерываний от таймера производится записью единиц в биты регистра специального назначения IE. Разрешение прерываний от таймера производится битом ET0, а общее разрешение всех прерываний битом EA. Установим эти биты в единичное состояние командой orl IE,#10000010b.

Теперь микроконтроллер каждые 50 мс будет не только просыпаться, но и передавать управление на вектор прерывания таймера T0. Этот вектор находится по адресу 0Bh. Подпрограмму повторной записи числа 50мс в таймер нужно поместить точно на этот адрес. Размещение подпрограммы на векторе прерывания можно произвести директивой ORG 0Bh. Так как пока нет прерываний от других источников, то подпрограмму обслуживания прерывания не будем выносить в конец программы.

;-------------СБРОС МИКРОКОНТРОЛЛЕРА---------------------------------
  ORG

  jmp init  ;Переход на начало выполнения программы

;-------------ВЕКТОР ПРЕРЫВАНИЯ ТАЙМЕРА T0---------------------------
 ORG 0Bh
  mov TH0, #HIGH(-50000) ;Загрузить старший байт таймера
  mov TL0, #LOW(-50000)  ;Загрузить младший байт таймера
  reti

;-------------ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------
init:
  call Timer0_Init ;Настроить таймер T0 на прерывания с периодом 50мс

;-------------ОСНОВНАЯ ПРОГРАММА МИКРОКОНТРОЛЛЕРА--------------------
Begin:
  orl PCON,#1;Перевести микроконтроллер в пониженный режим
             ;потребления тока и подождать переполнения таймера
  jmp begin  ;Переход на начало выполнения программы
;-------------КОНЕЦ ОСНОВНОЙ ПРОГРАММЫ МИКРОКОНТРОЛЛЕРА--------------

;********************************************************************
;Подпрограмма настройки таймера T0 на 50мс режим работы
;********************************************************************
Timer0_Init:
    mov TMOD,#00000001b    ;перевести таймер T0 в первый режим
             ;||||||||      работы, а T1 - в нулевой режим
             ;||||||++------Перевести таймер T0 в 
             ;||||||        шестнадцатиразрядный режим работы
             ;|||||+--------Синхронизироваться от внутреннего
             ;|||||         генератора
             ;||||+---------Запретить управление таймером от
             ;||||          ножки INT0
             ;||++----------Перевести таймер T1 в
             ;||            тринадцатиразрядный режим
             ;|+------------Синхронизироваться от внутреннего
             ;|+------------генератора
             ;+-------------Запретить управление таймером
             ;              от ножки INT1

;Настройка таймера на генерацию 50-ти миллисекундного интервала
;времени
  mov TH0, #HIGH(-50000) ;Загрузить старший байт таймера
  mov TL0, #LOW(-50000)  ;Загрузить младший байт таймера

  orl IE,#10000010b      ;Разрешить прерывания от таймера
  SetB TR0               ;включить таймер
  ret                    ;и вернуться в основную программу

  end

Рисунок 9.19. Исходный текст программы с 50мс циклом.

Исходный текст варианта программы c 50 мс циклом приведен на рисунке 9.19. Этот вариант программы можно тщательно проверить на работоспособность на программном эмуляторе или непосредственно на собранном устройстве. При измерении тока микроконтроллера будут отчётливо видны импульсы повышенного тока с периодом 50мс.

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

;-------------ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ----------------------------------
Delit    equ 8  ;Номер ячейки памяти делителя
NachStek equ 8  ;Номер начальной ячейки памяти стека

;-------------СБРОС МИКРОКОНТРОЛЛЕРА---------------------------------
 ORG 0
  jmp init  ;Переход на начало выполнения программы

;-------------ВЕКТОР ПРЕРЫВАНИЯ ТАЙМЕРА T0---------------------------
 ORG 0Bh
  mov TH0, #HIGH(-50000) ;Загрузить старший байт таймера
  mov TL0, #LOW(-50000)  ;Загрузить младший байт таймера
  reti

;-------------ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------
init:
  mov SP,#NachStek ;Изменить размер стека
  call Timer0_Init ;Настроить таймер T0 на прерывания с периодом 50мс
  mov Delit,#20    ;Настроить делитель на коэффициент деления 20 

;-------------ОСНОВНАЯ ПРОГРАММА МИКРОКОНТРОЛЛЕРА--------------------
Begin:
  DJNZ Delit,Sleep ;Если прошла одна секунда, то
    mov Delit,#20    ;настроить делитель на коэффициент деления 20 
    call Clock       ;и вызвать подпрограмму реализации часов.

Sleep:
  orl PCON,#1;Перевести микроконтроллер в пониженный режим
             ;потребления тока и подождать переполнения таймера
  jmp begin  ;Переход на начало выполнения программы
;-------------КОНЕЦ ОСНОВНОЙ ПРОГРАММЫ МИКРОКОНТРОЛЛЕРА--------------


;********************************************************************
;Подпрограмма реализации часов
;********************************************************************
Clock:
  ret

Рисунок 9.20. Исходный текст программы с 1 сек циклом.

Для выделения переменной воспользуемся директивой equ. Выделим для делителя ячейку с адресом 8. Это ячейка находится непосредственно после нулевого банка регистров. Кроме того, теперь потребуется изменить первоначальное значение указателя стека, равное 8, иначе при первом же вызове подпрограммы содержимое делителя будет уничтожено. Новый вариант программы приведен на рисунке 9.20.

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

Учитывая, что программа изменится незначительно, приведем исходный текст только изменённых участков программы. Эти участки программы приведены на рисунке 9.20. В первой части объявляется переменная SEC будет осуществляться счёт секунд. Во второй части этой переменной присваивается первоначальное значение, а переключение счётчика производится непосредственно в подпрограмме Clock

;-------------ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ----------------------------------
Delit    equ 8  ;Номер ячейки памяти делителя
SEC      equ 9  ;Номер ячейки памяти счётчика секунд
NachStek equ 9  ;Номер начальной ячейки памяти стека

  ...

;-------------ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------
init:
  mov SP,#NachStek ;Изменить размер стека
  call Timer0_Init ;Настроить таймер T0 на прерывания с периодом 50мс
  mov Delit,#20    ;Настроить делитель на коэффициент деления 20 
  mov SEC,#60      ;Настроить счётчик секунд на коэффициент деления 60

  ...

;********************************************************************
;Подпрограмма реализации часов
;********************************************************************
Clock:
  DJNZ SEC,ExitClock;Если прошла одна минута, то
    mov SEC,#60       ;настроить счётчик секунд на коэффициент деления 60
ExitClock:
  ret

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

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

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

;-------------ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ----------------------------------
Delit    equ 8  ;Номер ячейки памяти делителя
SEC      equ 9  ;Номер ячейки памяти счётчика секунд
MIN      equ 10 ;Номер ячейки памяти счётчика минут
NachStek equ 10 ;Номер начальной ячейки памяти стека

  ...

;-------------ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------
init:
  mov SP,#NachStek ;Изменить размер стека
  call Timer0_Init ;Настроить таймер T0 на прерывания с периодом 50мс
  mov Delit,#20    ;Настроить делитель на коэффициент деления 20 
  mov SEC,#60      ;Настроить счётчик секунд на коэффициент деления 60
  mov MIN,#0       ;Обнулить счётчик минут

  ...

;********************************************************************
;Подпрограмма реализации часов
;********************************************************************
Clock:
  DJNZ SEC,ExitClock;Если прошла одна минута, то
    mov SEC,#60       ;настроить счётчик секунд на коэффициент деления 60

    mov A,MIN             ;
    add A,#1              ;Переключить счётчик минут
    DA A                  ;
    mov MIN,A             ;

    CJNE A,#60h,ExitClock ;Если прошло 60 минут,
      mov MIN,#0            ;то обнулить счётчик минут
ExitClock:
  ret

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

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

Команды ADD и INC могут быть выполнены только над аккумулятором, поэтому в аккумулятор сначала копируется содержимое переменной счётчика минут, а затем, после выполнения операции двоично-десятичного суммирования, снова сохраняется в переменной счётчика минут. Исходный текст программы с подпрограммой реализации часов приведен на рисунке 9.23.

;-------------ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ----------------------------------
Delit    equ 8  ;Номер ячейки памяти делителя
SEC      equ 9  ;Номер ячейки памяти счётчика секунд
MIN      equ 10 ;Номер ячейки памяти счётчика минут
Chas     equ 11 ;Номер ячейки памяти счётчика минут
NachStek equ 11 ;Номер начальной ячейки памяти стека

  ...

;-------------ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------
init:
  mov SP,#NachStek ;Изменить размер стека
  call Timer0_Init ;Настроить таймер T0 на прерывания с периодом 50мс
  mov Delit,#20    ;Настроить делитель на коэффициент деления 20 
  mov SEC,#60      ;Настроить счётчик секунд на коэффициент деления 60
  mov MIN,#0       ;Обнулить счётчик минут
  mov Chas,#0      ;Обнулить счётчик часов

  ...

;********************************************************************
;Подпрограмма реализации часов
;********************************************************************
Clock:
  DJNZ SEC,ExitClock;Если прошла одна минута, то
    mov SEC,#60       ;настроить счётчик секунд на коэффициент деления 60
    Call IncMin
    JC ExitClock
      Call IncChas
ExitClock:
  ret

;********************************************************************
;Подпрограмма увеличения содержимого счётчика минут
;********************************************************************
IncMin:
  mov A,MIN               ;
  add A,#1                ;Переключить счётчик минут
  DA A                    ;
  mov MIN,A               ;

  CJNE A,#60h,ExitIncMin  ;Если прошло 60 минут,
    mov MIN,#0              ;то обнулить счётчик минут
ExitIncMin:
  ret

;********************************************************************
;Подпрограмма увеличения содержимого счётчика часов
;********************************************************************
IncChas:
    mov A,Chas           ;
    add A,#1             ;Переключить счётчик часов
    DA A                 ;
    mov Chas,A           ;

    CJNE A,#24h,ExitIncHr;Если прошло 24 часа,
      mov Chas,#0          ;то обнулить счётчик часов
ExitIncHr:
  ret

Рисунок 9.23. Исходный текст участков программы, отвечающих за реализацию электронных часов.

Для того чтобы полностью воспользоваться преимуществами структурного программирования, операторы, выполняющие увеличение показаний счётчика минут, оформлены в виде подпрограммы. В результате подпрограмма реализации часов выглядит полностью как алгоритм. Для переключения счётчика часов используется команда проверки флага переноса C. Этот флаг устанавливается командой CJNE.

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

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

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

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

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

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

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

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

Delit    equ 8  ;Номер ячейки памяти делителя
SEC      equ 9  ;Номер ячейки памяти счётчика секунд
MIN      equ 10 ;Номер ячейки памяти счётчика минут
Chas     equ 11 ;Номер ячейки памяти счётчика минут
NachStek equ 11 ;Номер начальной ячейки памяти стека

;-------------СБРОС МИКРОКОНТРОЛЛЕРА---------------------------------
 ORG 0
  jmp init  ;Переход на начало выполнения программы

;-------------ВЕКТОР ПРЕРЫВАНИЯ ТАЙМЕРА T0---------------------------
 ORG 0Bh
  mov TH0, #HIGH(-50000) ;Загрузить старший байт таймера
  mov TL0, #LOW(-50000)  ;Загрузить младший байт таймера
  reti

;-------------ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------
init:
  mov SP,#NachStek ;Изменить размер стека
  call Timer0_Init ;Настроить таймер T0 на прерывания с периодом 50мс
  mov Delit,#20    ;Настроить делитель на коэффициент деления 20
  mov SEC,#60      ;Настроить счётчик секунд на коэффициент деления 60
  mov MIN,#0       ;Обнулить счётчик минут
  mov Chas,#0      ;Обнулить счётчик часов
;-------------ОСНОВНАЯ ПРОГРАММА МИКРОКОНТРОЛЛЕРА--------------------
Begin:
  call Indic       ;Произвести индикацию состояния часов на светодиодных
                   ;индикаторах
  DJNZ Delit,Sleep ;Если прошла одна секунда, то
    mov Delit,#20    ;настроить делитель на коэффициент деления 20 
    call Clock       ;и вызвать подпрограмму счётчика секунд.
Sleep:
  orl PCON,#1;Перевести микроконтроллер в пониженный режим
             ;потребления тока и подождать переполнения таймера
  jmp begin  ;Переход на начало выполнения программы
;-------------КОНЕЦ ОСНОВНОЙ ПРОГРАММЫ МИКРОКОНТРОЛЛЕРА--------------

;********************************************************************
;Подпрограмма блока индикации
;********************************************************************
Indic:
  ret

Рисунок 9.24. Исходный текст программы с вызовом подпрограммы блока индикации.

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

;********************************************************************
;Подпрограмма блока индикации
;********************************************************************
Indic:
  Mov A,MIN   ;Считать содержимое счётчика минут
  AnL A,#0Fh  ;Выделить младшую тетраду
  Call Decod  ;Преобразовать её в семисегментный код
  Mov  P1,A   ;и передать через порт P1 на индикатор

  Mov A,MIN   ;Считать содержимое счётчика минут
  SWAP A      ;Выделить
  AnL A,#0Fh  ;старшую тетраду
  Call Decod  ;Преобразовать её в семисегментный код
  Mov  P2,A   ;и передать через порт P2 на индикатор

  Mov A,Chas  ;Считать содержимое счётчика часов
  AnL A,#0Fh  ;Выделить младшую тетраду
  Call Decod  ;Преобразовать её в семисегментный код
  Mov  P0,A   ;и передать через порт P0 на индикатор

  Mov A,Chas  ;Считать содержимое счётчика часов
  SWAP A      ;Выделить
  AnL A,#0Fh  ;старшую тетраду
  Call Decod  ;Преобразовать её в семисегментный код
  Mov  P3,A   ;и передать через порт P3 на индикатор
ret

Рисунок 9.25. Исходный текст подпрограммы индикации.

Старшая тетрада счётчика выделяется аналогичным образом. Отличие заключается в том, что перед логическим умножением старшая и младшая тетрады меняются местами при помощи команды SWAP.

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

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

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

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

;********************************************************************
;Подпрограмма семисегментного декодера
;********************************************************************
Decod:
  Mov DPTR,#TabSemiSeg ;Установить указатель на начало таблицы
  MovC A,@A+DPTR       ;Считать значение кода и 
  ret                  ;вернуть через аккумулятор

TabSemiSeg:
;     abcdefg
  DB 11111110b ;символ '0'       a
  DB 10110000b ;символ '1'    -------
  DB 11101101b ;символ '2'   |       |
  DB 11111001b ;символ '3'  f|       | b
  DB 10110011b ;символ '4'   |   g   | 
  DB 11011011b ;символ '5'    ------- 
  DB 11011111b ;символ '6'   |       |
  DB 11110000b ;символ '7'  e|       |c
  DB 11111111b ;символ '8'   |   d   |
  DB 11111011b ;символ '9'    -------

Рисунок 9.26. Исходный текст подпрограммы семисегментного декодера.

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

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

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

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

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

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

Теперь можно заняться реализацией подпрограммы опроса кнопок. Её задача состоит в копировании логических уровней с выводов порта во внутреннюю переменную микроконтроллера SostKn. Так как это будет происходить быстро и с постоянной задержкой по времени, то время взятия отсчетов (а также время вывода на индикаторы) будет строго постоянным. Исходный текст подпрограммы опроса кнопок приведён на рисунке 9.29.

В этой подпрограмме кнопки, разнесённые по портам, сводятся в одну переменную. Копирование производится битовыми командами MOV через флаг переноса C.

;-------------ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ----------------------------------
Delit    equ 8  ;Номер ячейки памяти делителя
SEC      equ 9  ;Номер ячейки памяти счётчика секунд
MIN      equ 10 ;Номер ячейки памяти счётчика минут
Chas     equ 11 ;Номер ячейки памяти счётчика часов
SostKn   equ 12 ;Номер ячейки переменной состояния кнопок
NachStek equ 12 ;Номер начальной ячейки памяти стека

;-------------СБРОС МИКРОКОНТРОЛЛЕРА---------------------------------
 ORG 0
  jmp init  ;Переход на начало выполнения программы

;-------------ВЕКТОР ПРЕРЫВАНИЯ ТАЙМЕРА T0---------------------------
 ORG 0Bh
  mov TH0, #HIGH(-50000) ;Загрузить старший байт таймера
  mov TL0, #LOW(-50000)  ;Загрузить младший байт таймера
  reti

;-------------ИНИЦИАЛИЗАЦИЯ МИКРОКОНТРОЛЛЕРА-------------------------
init:
  mov SP,#NachStek ;Изменить размер стека
  call Timer0_Init ;Настроить таймер T0 на прерывания с периодом 50мс
  mov Delit,#20    ;Настроить делитель на коэффициент деления 20 
  mov SEC,#60      ;Настроить счётчик секунд на коэффициент деления 60
  mov MIN,#0       ;Обнулить счётчик минут
  mov Chas,#0      ;Обнулить счётчик часов

;-------------ОСНОВНАЯ ПРОГРАММА МИКРОКОНТРОЛЛЕРА--------------------
Begin:
  call OprosKnop   ;Опросить кнопки
  call Indic       ;Произвести индикацию состояния часов на светодиодных
                   ;индикаторах

  DJNZ Delit,Sleep ;Если прошла одна секунда, то
    mov Delit,#20    ;настроить делитель на коэффициент деления 20 
    call Clock       ;и вызвать подпрограмму реализации часов.

Sleep:
  orl PCON,#1;Перевести микроконтроллер в пониженный режим
             ;потребления тока и подождать переполнения таймера
  jmp begin  ;Переход на начало выполнения программы
;-------------КОНЕЦ ОСНОВНОЙ ПРОГРАММЫ МИКРОКОНТРОЛЛЕРА--------------


;********************************************************************
;Подпрограмма опроса кнопок
;********************************************************************
OprosKnop:
  ret

Рисунок 9.28. Исходный текст программы с вызовом подпрограммы блока индикации.

;********************************************************************
;Подпрограмма опроса кнопок
;********************************************************************
OprosKnop:
  Mov A,SostKn   ;Считать переменную состояния кнопок

  Mov C,P1.7     ;Скопировать состояние кнопки "уст сек"
  Mov AСС.0,C    ;в нулевой бит аккумулятора

  Mov C,P2.7     ;Скопировать состояние кнопки "уст мин"
  Mov AСС.1,C    ;в первый бит аккумулятора

  Mov C,P3.7     ;Скопировать состояние кнопки "уст часов"
  Mov AСС.2,C    ;во второй бит аккумулятора

  Mov SostKn,A   ;Сохранить новое значение переменной состояния кнопок
  ret

Рисунок 9.29. Исходный текст подпрограммы опроса кнопок.

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

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

;********************************************************************
;Подпрограмма блока коррекции часов
;********************************************************************
CorrClock:
  Mov A,SostKn   ;Считать переменную состояния кнопок
  JB ACC.0,      ;Если нажата кнопка "уст сек",
    Mov SEC,#0     ;то обнулить переменную SEC
  ret

Рисунок 9.30. Исходный текст подпрограммы коррекции показания часов.

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

Если такая скорость коррекции показаний часов пять раз в секунду покажется слишком высокой, то можно изменить соотношение коэффициентов деления таймера и делителя, например: 32*31250. Тогда можно будет выбрать скорость коррекции показания часов из набора вариантов два или четыре раза в секунду.

;********************************************************************
;Подпрограмма блока коррекции часов
;********************************************************************
CorrClock:
  Mov A,SostKn           ;Считать переменную состояния кнопок
  JB ACC.0,TstKnMin      ;Если нажата кнопка "уст сек",
    Mov SEC,#0             ;то обнулить переменную SEC
TstKnMin:
  Mov  A,Delit           ;Если в предделителе
  ANL  A,#3              ;число кратно 2,
  CJNE A,#2,ExitCorrClock;

    Mov A,SostKn           ;то считать переменную состояния кнопок.
    JB ACC.1,TstKnChs      ;Если нажата кнопка "уст мин",
      Call IncMin            ;то увеличить содержимое счётчика минут
TstKnChs:
    JB ACC.2, ExitCorrClock;Если нажата кнопка "уст мин",
      Call IncChas           ;то увеличить содержимое счётчика часов
ExitCorrClock:
  ret

Рисунок 9.31. Изменённый исходный текст подпрограммы коррекции показания часов.

В приведенном варианте программы проверяется два младших бита делителя. Для выделения двух младших бит используется логическое умножение содержимого переменной Delit с маской 00011b. Так как эти биты принимают значение 10b пять раз в секунду, то, соответствующее количество раз, будет вызываться подпрограмма увеличения значения содержимого счётчика минут (или часов).

Литература:

  1. Микушин А.В. Занимательно о микроконтроллерах. СПб, БХВ-Петербург, 2006.
  2. Микушин А.В., Сажнев А.М., Сединин В.И. Цифровые устройства и микропроцессоры. СПб, БХВ-Петербург, 2010.
  3. часы-будильник из жесткого диска

Вместе со статьей "Проектирование цифровых устройств на микроконтроллерах" читают:

Разработка принципиальной схемы микропроцессорного устройства
http://digteh.ru/MCU/princsx.php

Проектирование цифровых устройств на микроконтроллерах
http://digteh.ru/MCU/strsxustr.php


Автор Микушин А. В. All rights reserved. 2001 ... 2015

Предыдущие версии сайта:
http://neic.nsk.su/~mavr
http://digital.sibsutis.ru/

пЕИРХМЦ@Mail.ru


Rambler's Top100